Hyprland/src/render/OpenGL.cpp

3085 lines
129 KiB
C++
Raw Normal View History

#include <GLES3/gl32.h>
#include <hyprgraphics/color/Color.hpp>
#include <hyprutils/string/String.hpp>
#include <hyprutils/path/Path.hpp>
#include <random>
#include <pango/pangocairo.h>
2022-04-04 19:44:25 +02:00
#include "OpenGL.hpp"
#include "Renderer.hpp"
2022-04-04 19:44:25 +02:00
#include "../Compositor.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "../config/ConfigValue.hpp"
#include "../config/ConfigManager.hpp"
#include "../desktop/LayerSurface.hpp"
#include "../protocols/LayerShell.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/ColorManagement.hpp"
#include "../protocols/types/ColorManagement.hpp"
#include "../managers/HookSystemManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../helpers/fs/FsUtils.hpp"
#include "debug/HyprNotificationOverlay.hpp"
#include "hyprerror/HyprError.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/RectPassElement.hpp"
#include "pass/PreBlurElement.hpp"
#include "pass/ClearPassElement.hpp"
#include "render/Shader.hpp"
#include <string>
#include <xf86drm.h>
#include <fcntl.h>
#include <gbm.h>
#include <filesystem>
#include "./shaders/Shaders.hpp"
core: begin using CFileDescriptor from hyprutils (#9122) * config: make fd use CFileDescriptor make use of the new hyprutils CFileDescriptor instead of manual FD handling. * hyprctl: make fd use CFileDescriptor make use of the new hyprutils CFileDescriptor instead of manual FD handling. * ikeyboard: make fd use CFileDescriptor make use of the new CFileDescriptor instead of manual FD handling, also in sendKeymap remove dead code, it already early returns if keyboard isnt valid, and dont try to close the FD that ikeyboard owns. * core: make SHMFile functions use CFileDescriptor make SHMFile misc functions use CFileDescriptor and its associated usage in dmabuf and keyboard. * core: make explicit sync use CFileDescriptor begin using CFileDescriptor in explicit sync and its timelines and eglsync usage in opengl, there is still a bit left with manual handling that requires future aquamarine change aswell. * eventmgr: make fd and sockets use CFileDescriptor make use of the hyprutils CFileDescriptor instead of manual FD and socket handling and closing. * eventloopmgr: make timerfd use CFileDescriptor make the timerfd use CFileDescriptor instead of manual fd handling * opengl: make gbm fd use CFileDescriptor make the gbm rendernode fd use CFileDescriptor instead of manual fd handling * core: make selection source/offer use CFileDescriptor make data selection source and offers use CFileDescriptor and its associated use in xwm and protocols * protocols: convert protocols fd to CFileDescriptor make most fd handling use CFileDescriptor in protocols * shm: make SHMPool use CfileDescriptor make SHMPool use CFileDescriptor instead of manual fd handling. * opengl: duplicate fd with CFileDescriptor duplicate fenceFD with CFileDescriptor duplicate instead. * xwayland: make sockets and fds use CFileDescriptor instead of manual opening/closing make sockets and fds use CFileDescriptor * keybindmgr: make sockets and fds use CFileDescriptor make sockets and fds use CFileDescriptor instead of manual handling.
2025-01-30 12:30:12 +01:00
using namespace Hyprutils::OS;
using namespace NColorManagement;
2022-04-04 19:44:25 +02:00
const std::vector<const char*> ASSET_PATHS = {
#ifdef DATAROOTDIR
DATAROOTDIR,
#endif
"/usr/share",
"/usr/local/share",
};
static inline void loadGLProc(void* pProc, const char* name) {
void* proc = (void*)eglGetProcAddress(name);
if (proc == nullptr) {
Debug::log(CRIT, "[Tracy GPU Profiling] eglGetProcAddress({}) failed", name);
abort();
}
*(void**)pProc = proc;
}
static enum eLogLevel eglLogToLevel(EGLint type) {
switch (type) {
case EGL_DEBUG_MSG_CRITICAL_KHR: return CRIT;
case EGL_DEBUG_MSG_ERROR_KHR: return ERR;
case EGL_DEBUG_MSG_WARN_KHR: return WARN;
case EGL_DEBUG_MSG_INFO_KHR: return LOG;
default: return LOG;
}
}
2022-04-04 19:44:25 +02:00
static const char* eglErrorToString(EGLint error) {
switch (error) {
case EGL_SUCCESS: return "EGL_SUCCESS";
case EGL_NOT_INITIALIZED: return "EGL_NOT_INITIALIZED";
case EGL_BAD_ACCESS: return "EGL_BAD_ACCESS";
case EGL_BAD_ALLOC: return "EGL_BAD_ALLOC";
case EGL_BAD_ATTRIBUTE: return "EGL_BAD_ATTRIBUTE";
case EGL_BAD_CONTEXT: return "EGL_BAD_CONTEXT";
case EGL_BAD_CONFIG: return "EGL_BAD_CONFIG";
case EGL_BAD_CURRENT_SURFACE: return "EGL_BAD_CURRENT_SURFACE";
case EGL_BAD_DISPLAY: return "EGL_BAD_DISPLAY";
case EGL_BAD_DEVICE_EXT: return "EGL_BAD_DEVICE_EXT";
case EGL_BAD_SURFACE: return "EGL_BAD_SURFACE";
case EGL_BAD_MATCH: return "EGL_BAD_MATCH";
case EGL_BAD_PARAMETER: return "EGL_BAD_PARAMETER";
case EGL_BAD_NATIVE_PIXMAP: return "EGL_BAD_NATIVE_PIXMAP";
case EGL_BAD_NATIVE_WINDOW: return "EGL_BAD_NATIVE_WINDOW";
case EGL_CONTEXT_LOST: return "EGL_CONTEXT_LOST";
}
return "Unknown";
}
2022-04-04 19:44:25 +02:00
static void eglLog(EGLenum error, const char* command, EGLint type, EGLLabelKHR thread, EGLLabelKHR obj, const char* msg) {
Debug::log(eglLogToLevel(type), "[EGL] Command {} errored out with {} (0x{}): {}", command, eglErrorToString(error), error, msg);
}
2022-04-04 19:44:25 +02:00
static int openRenderNode(int drmFd) {
auto renderName = drmGetRenderDeviceNameFromFd(drmFd);
if (!renderName) {
// This can happen on split render/display platforms, fallback to
// primary node
renderName = drmGetPrimaryDeviceNameFromFd(drmFd);
if (!renderName) {
Debug::log(ERR, "drmGetPrimaryDeviceNameFromFd failed");
return -1;
}
Debug::log(LOG, "DRM dev {} has no render node, falling back to primary", renderName);
drmVersion* render_version = drmGetVersion(drmFd);
if (render_version && render_version->name) {
Debug::log(LOG, "DRM dev versionName", render_version->name);
if (strcmp(render_version->name, "evdi") == 0) {
free(renderName);
renderName = (char*)malloc(sizeof(char) * 15);
strcpy(renderName, "/dev/dri/card0");
}
drmFreeVersion(render_version);
}
}
2022-04-04 19:44:25 +02:00
Debug::log(LOG, "openRenderNode got drm device {}", renderName);
2022-04-04 19:44:25 +02:00
int renderFD = open(renderName, O_RDWR | O_CLOEXEC);
if (renderFD < 0)
Debug::log(ERR, "openRenderNode failed to open drm device {}", renderName);
free(renderName);
return renderFD;
}
void CHyprOpenGLImpl::initEGL(bool gbm) {
std::vector<EGLint> attrs;
if (m_exts.KHR_display_reference) {
attrs.push_back(EGL_TRACK_REFERENCES_KHR);
attrs.push_back(EGL_TRUE);
}
attrs.push_back(EGL_NONE);
m_eglDisplay = m_proc.eglGetPlatformDisplayEXT(gbm ? EGL_PLATFORM_GBM_KHR : EGL_PLATFORM_DEVICE_EXT, gbm ? m_gbmDevice : m_eglDevice, attrs.data());
if (m_eglDisplay == EGL_NO_DISPLAY)
RASSERT(false, "EGL: failed to create a platform display");
attrs.clear();
EGLint major, minor;
if (eglInitialize(m_eglDisplay, &major, &minor) == EGL_FALSE)
RASSERT(false, "EGL: failed to initialize a platform display");
const std::string EGLEXTENSIONS = (const char*)eglQueryString(m_eglDisplay, EGL_EXTENSIONS);
m_exts.IMG_context_priority = EGLEXTENSIONS.contains("IMG_context_priority");
m_exts.EXT_create_context_robustness = EGLEXTENSIONS.contains("EXT_create_context_robustness");
m_exts.EXT_image_dma_buf_import = EGLEXTENSIONS.contains("EXT_image_dma_buf_import");
m_exts.EXT_image_dma_buf_import_modifiers = EGLEXTENSIONS.contains("EXT_image_dma_buf_import_modifiers");
if (m_exts.IMG_context_priority) {
Debug::log(LOG, "EGL: IMG_context_priority supported, requesting high");
attrs.push_back(EGL_CONTEXT_PRIORITY_LEVEL_IMG);
attrs.push_back(EGL_CONTEXT_PRIORITY_HIGH_IMG);
}
if (m_exts.EXT_create_context_robustness) {
Debug::log(LOG, "EGL: EXT_create_context_robustness supported, requesting lose on reset");
attrs.push_back(EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
attrs.push_back(EGL_LOSE_CONTEXT_ON_RESET_EXT);
}
2024-07-29 14:27:05 +02:00
auto attrsNoVer = attrs;
attrs.push_back(EGL_CONTEXT_MAJOR_VERSION);
attrs.push_back(3);
attrs.push_back(EGL_CONTEXT_MINOR_VERSION);
2024-07-29 14:27:05 +02:00
attrs.push_back(2);
attrs.push_back(EGL_NONE);
m_eglContext = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data());
if (m_eglContext == EGL_NO_CONTEXT) {
2024-07-29 14:27:05 +02:00
Debug::log(WARN, "EGL: Failed to create a context with GLES3.2, retrying 3.0");
attrs = attrsNoVer;
attrs.push_back(EGL_CONTEXT_MAJOR_VERSION);
attrs.push_back(3);
attrs.push_back(EGL_CONTEXT_MINOR_VERSION);
attrs.push_back(0);
attrs.push_back(EGL_NONE);
m_eglContext = eglCreateContext(m_eglDisplay, EGL_NO_CONFIG_KHR, EGL_NO_CONTEXT, attrs.data());
m_eglContextVersion = EGL_CONTEXT_GLES_3_0;
2024-07-29 14:27:05 +02:00
if (m_eglContext == EGL_NO_CONTEXT)
2024-07-29 14:27:05 +02:00
RASSERT(false, "EGL: failed to create a context with either GLES3.2 or 3.0");
}
if (m_exts.IMG_context_priority) {
EGLint priority = EGL_CONTEXT_PRIORITY_MEDIUM_IMG;
eglQueryContext(m_eglDisplay, m_eglContext, EGL_CONTEXT_PRIORITY_LEVEL_IMG, &priority);
if (priority != EGL_CONTEXT_PRIORITY_HIGH_IMG)
Debug::log(ERR, "EGL: Failed to obtain a high priority context");
else
Debug::log(LOG, "EGL: Got a high priority context");
}
eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, m_eglContext);
}
static bool drmDeviceHasName(const drmDevice* device, const std::string& name) {
for (size_t i = 0; i < DRM_NODE_MAX; i++) {
if (!(device->available_nodes & (1 << i)))
continue;
if (device->nodes[i] == name)
return true;
}
return false;
}
EGLDeviceEXT CHyprOpenGLImpl::eglDeviceFromDRMFD(int drmFD) {
EGLint nDevices = 0;
if (!m_proc.eglQueryDevicesEXT(0, nullptr, &nDevices)) {
Debug::log(ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed");
return EGL_NO_DEVICE_EXT;
}
if (nDevices <= 0) {
Debug::log(ERR, "eglDeviceFromDRMFD: no devices");
return EGL_NO_DEVICE_EXT;
}
std::vector<EGLDeviceEXT> devices;
devices.resize(nDevices);
if (!m_proc.eglQueryDevicesEXT(nDevices, devices.data(), &nDevices)) {
Debug::log(ERR, "eglDeviceFromDRMFD: eglQueryDevicesEXT failed (2)");
return EGL_NO_DEVICE_EXT;
}
drmDevice* drmDev = nullptr;
if (int ret = drmGetDevice(drmFD, &drmDev); ret < 0) {
Debug::log(ERR, "eglDeviceFromDRMFD: drmGetDevice failed");
return EGL_NO_DEVICE_EXT;
}
for (auto const& d : devices) {
auto devName = m_proc.eglQueryDeviceStringEXT(d, EGL_DRM_DEVICE_FILE_EXT);
if (!devName)
continue;
if (drmDeviceHasName(drmDev, devName)) {
Debug::log(LOG, "eglDeviceFromDRMFD: Using device {}", devName);
drmFreeDevice(&drmDev);
return d;
}
}
drmFreeDevice(&drmDev);
Debug::log(LOG, "eglDeviceFromDRMFD: No drm devices found");
return EGL_NO_DEVICE_EXT;
}
CHyprOpenGLImpl::CHyprOpenGLImpl() : m_drmFD(g_pCompositor->m_drmFD) {
const std::string EGLEXTENSIONS = (const char*)eglQueryString(EGL_NO_DISPLAY, EGL_EXTENSIONS);
Debug::log(LOG, "Supported EGL extensions: ({}) {}", std::count(EGLEXTENSIONS.begin(), EGLEXTENSIONS.end(), ' '), EGLEXTENSIONS);
m_exts.KHR_display_reference = EGLEXTENSIONS.contains("KHR_display_reference");
loadGLProc(&m_proc.glEGLImageTargetRenderbufferStorageOES, "glEGLImageTargetRenderbufferStorageOES");
loadGLProc(&m_proc.eglCreateImageKHR, "eglCreateImageKHR");
loadGLProc(&m_proc.eglDestroyImageKHR, "eglDestroyImageKHR");
loadGLProc(&m_proc.eglQueryDmaBufFormatsEXT, "eglQueryDmaBufFormatsEXT");
loadGLProc(&m_proc.eglQueryDmaBufModifiersEXT, "eglQueryDmaBufModifiersEXT");
loadGLProc(&m_proc.glEGLImageTargetTexture2DOES, "glEGLImageTargetTexture2DOES");
loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR");
loadGLProc(&m_proc.eglGetPlatformDisplayEXT, "eglGetPlatformDisplayEXT");
loadGLProc(&m_proc.eglCreateSyncKHR, "eglCreateSyncKHR");
loadGLProc(&m_proc.eglDestroySyncKHR, "eglDestroySyncKHR");
loadGLProc(&m_proc.eglDupNativeFenceFDANDROID, "eglDupNativeFenceFDANDROID");
loadGLProc(&m_proc.eglWaitSyncKHR, "eglWaitSyncKHR");
RASSERT(m_proc.eglCreateSyncKHR, "Display driver doesn't support eglCreateSyncKHR");
RASSERT(m_proc.eglDupNativeFenceFDANDROID, "Display driver doesn't support eglDupNativeFenceFDANDROID");
RASSERT(m_proc.eglWaitSyncKHR, "Display driver doesn't support eglWaitSyncKHR");
if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_enumeration"))
loadGLProc(&m_proc.eglQueryDevicesEXT, "eglQueryDevicesEXT");
if (EGLEXTENSIONS.contains("EGL_EXT_device_base") || EGLEXTENSIONS.contains("EGL_EXT_device_query")) {
loadGLProc(&m_proc.eglQueryDeviceStringEXT, "eglQueryDeviceStringEXT");
loadGLProc(&m_proc.eglQueryDisplayAttribEXT, "eglQueryDisplayAttribEXT");
}
if (EGLEXTENSIONS.contains("EGL_KHR_debug")) {
loadGLProc(&m_proc.eglDebugMessageControlKHR, "eglDebugMessageControlKHR");
static const EGLAttrib debugAttrs[] = {
EGL_DEBUG_MSG_CRITICAL_KHR, EGL_TRUE, EGL_DEBUG_MSG_ERROR_KHR, EGL_TRUE, EGL_DEBUG_MSG_WARN_KHR, EGL_TRUE, EGL_DEBUG_MSG_INFO_KHR, EGL_TRUE, EGL_NONE,
};
m_proc.eglDebugMessageControlKHR(::eglLog, debugAttrs);
}
RASSERT(eglBindAPI(EGL_OPENGL_ES_API) != EGL_FALSE, "Couldn't bind to EGL's opengl ES API. This means your gpu driver f'd up. This is not a hyprland issue.");
bool success = false;
if (EGLEXTENSIONS.contains("EXT_platform_device") || !m_proc.eglQueryDevicesEXT || !m_proc.eglQueryDeviceStringEXT) {
m_eglDevice = eglDeviceFromDRMFD(m_drmFD);
if (m_eglDevice != EGL_NO_DEVICE_EXT) {
success = true;
initEGL(false);
}
}
if (!success) {
Debug::log(WARN, "EGL: EXT_platform_device or EGL_EXT_device_query not supported, using gbm");
if (EGLEXTENSIONS.contains("KHR_platform_gbm")) {
success = true;
m_gbmFD = CFileDescriptor{openRenderNode(m_drmFD)};
if (!m_gbmFD.isValid())
RASSERT(false, "Couldn't open a gbm fd");
m_gbmDevice = gbm_create_device(m_gbmFD.get());
if (!m_gbmDevice)
RASSERT(false, "Couldn't open a gbm device");
initEGL(true);
}
}
RASSERT(success, "EGL does not support KHR_platform_gbm or EXT_platform_device, this is an issue with your gpu driver.");
auto* const EXTENSIONS = (const char*)glGetString(GL_EXTENSIONS);
RASSERT(EXTENSIONS, "Couldn't retrieve openGL extensions!");
m_extensions = EXTENSIONS;
Debug::log(LOG, "Creating the Hypr OpenGL Renderer!");
Debug::log(LOG, "Using: {}", (char*)glGetString(GL_VERSION));
Debug::log(LOG, "Vendor: {}", (char*)glGetString(GL_VENDOR));
Debug::log(LOG, "Renderer: {}", (char*)glGetString(GL_RENDERER));
Debug::log(LOG, "Supported extensions: ({}) {}", std::count(m_extensions.begin(), m_extensions.end(), ' '), m_extensions);
m_exts.EXT_read_format_bgra = m_extensions.contains("GL_EXT_read_format_bgra");
RASSERT(m_extensions.contains("GL_EXT_texture_format_BGRA8888"), "GL_EXT_texture_format_BGRA8888 support by the GPU driver is required");
if (!m_exts.EXT_read_format_bgra)
Debug::log(WARN, "Your GPU does not support GL_EXT_read_format_bgra, this may cause issues with texture importing");
if (!m_exts.EXT_image_dma_buf_import || !m_exts.EXT_image_dma_buf_import_modifiers)
Debug::log(WARN, "Your GPU does not support DMABUFs, this will possibly cause issues and will take a hit on the performance.");
2023-07-20 17:47:49 +02:00
#ifdef USE_TRACY_GPU
loadGLProc(&glQueryCounter, "glQueryCounterEXT");
loadGLProc(&glGetQueryObjectiv, "glGetQueryObjectivEXT");
loadGLProc(&glGetQueryObjectui64v, "glGetQueryObjectui64vEXT");
#endif
TRACY_GPU_CONTEXT;
initDRMFormats();
initAssets();
static auto P = g_pHookSystem->hookDynamic("preRender", [&](void* self, SCallbackInfo& info, std::any data) { preRender(std::any_cast<PHLMONITOR>(data)); });
RASSERT(eglMakeCurrent(m_eglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT), "Couldn't unset current EGL!");
m_globalTimer.reset();
2022-04-04 19:44:25 +02:00
}
CHyprOpenGLImpl::~CHyprOpenGLImpl() {
if (m_eglDisplay && m_eglContext != EGL_NO_CONTEXT)
eglDestroyContext(m_eglDisplay, m_eglContext);
if (m_eglDisplay)
eglTerminate(m_eglDisplay);
eglReleaseThread();
if (m_gbmDevice)
gbm_device_destroy(m_gbmDevice);
}
std::optional<std::vector<uint64_t>> CHyprOpenGLImpl::getModsForFormat(EGLint format) {
// TODO: return std::expected when clang supports it
if (!m_exts.EXT_image_dma_buf_import_modifiers)
return std::nullopt;
EGLint len = 0;
if (!m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, 0, nullptr, nullptr, &len)) {
Debug::log(ERR, "EGL: Failed to query mods");
return std::nullopt;
}
if (len <= 0)
return std::vector<uint64_t>{};
std::vector<uint64_t> mods;
std::vector<EGLBoolean> external;
mods.resize(len);
external.resize(len);
m_proc.eglQueryDmaBufModifiersEXT(m_eglDisplay, format, len, mods.data(), external.data(), &len);
std::vector<uint64_t> result;
// reserve number of elements to avoid reallocations
result.reserve(mods.size());
bool linearIsExternal = false;
for (size_t i = 0; i < std::min(mods.size(), external.size()); ++i) {
if (external[i]) {
if (mods[i] == DRM_FORMAT_MOD_LINEAR)
linearIsExternal = true;
continue;
}
result.push_back(mods[i]);
}
// if the driver doesn't mark linear as external, add it. It's allowed unless the driver says otherwise. (e.g. nvidia)
if (!linearIsExternal && std::find(mods.begin(), mods.end(), DRM_FORMAT_MOD_LINEAR) == mods.end() && mods.size() == 0)
mods.push_back(DRM_FORMAT_MOD_LINEAR);
return result;
}
void CHyprOpenGLImpl::initDRMFormats() {
const auto DISABLE_MODS = envEnabled("HYPRLAND_EGL_NO_MODIFIERS");
if (DISABLE_MODS)
Debug::log(WARN, "HYPRLAND_EGL_NO_MODIFIERS set, disabling modifiers");
if (!m_exts.EXT_image_dma_buf_import) {
Debug::log(ERR, "EGL: No dmabuf import, DMABufs will not work.");
return;
}
std::vector<EGLint> formats;
if (!m_exts.EXT_image_dma_buf_import_modifiers || !m_proc.eglQueryDmaBufFormatsEXT) {
formats.push_back(DRM_FORMAT_ARGB8888);
formats.push_back(DRM_FORMAT_XRGB8888);
Debug::log(WARN, "EGL: No mod support");
} else {
EGLint len = 0;
m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, 0, nullptr, &len);
formats.resize(len);
m_proc.eglQueryDmaBufFormatsEXT(m_eglDisplay, len, formats.data(), &len);
}
if (formats.size() == 0) {
Debug::log(ERR, "EGL: Failed to get formats, DMABufs will not work.");
return;
}
Debug::log(LOG, "Supported DMA-BUF formats:");
std::vector<SDRMFormat> dmaFormats;
// reserve number of elements to avoid reallocations
dmaFormats.reserve(formats.size());
for (auto const& fmt : formats) {
std::vector<uint64_t> mods;
if (!DISABLE_MODS) {
auto ret = getModsForFormat(fmt);
if (!ret.has_value())
continue;
mods = *ret;
} else
mods = {DRM_FORMAT_MOD_LINEAR};
m_hasModifiers = m_hasModifiers || mods.size() > 0;
// EGL can always do implicit modifiers.
mods.push_back(DRM_FORMAT_MOD_INVALID);
dmaFormats.push_back(SDRMFormat{
.drmFormat = fmt,
.modifiers = mods,
});
std::vector<std::pair<uint64_t, std::string>> modifierData;
// reserve number of elements to avoid reallocations
modifierData.reserve(mods.size());
auto fmtName = drmGetFormatName(fmt);
Debug::log(LOG, "EGL: GPU Supports Format {} (0x{:x})", fmtName ? fmtName : "?unknown?", fmt);
for (auto const& mod : mods) {
auto modName = drmGetFormatModifierName(mod);
modifierData.emplace_back(std::make_pair<>(mod, modName ? modName : "?unknown?"));
free(modName);
}
free(fmtName);
mods.clear();
std::sort(modifierData.begin(), modifierData.end(), [](const auto& a, const auto& b) {
if (a.first == 0)
return false;
if (a.second.contains("DCC"))
return false;
return true;
});
for (auto const& [m, name] : modifierData) {
Debug::log(LOG, "EGL: | with modifier {} (0x{:x})", name, m);
mods.emplace_back(m);
}
}
Debug::log(LOG, "EGL: {} formats found in total. Some modifiers may be omitted as they are external-only.", dmaFormats.size());
if (dmaFormats.size() == 0)
Debug::log(WARN,
"EGL: WARNING: No dmabuf formats were found, dmabuf will be disabled. This will degrade performance, but is most likely a driver issue or a very old GPU.");
m_drmFormats = dmaFormats;
}
EGLImageKHR CHyprOpenGLImpl::createEGLImage(const Aquamarine::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 (m_hasModifiers && 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 = m_proc.eglCreateImageKHR(m_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: {}", eglGetError());
return EGL_NO_IMAGE_KHR;
}
return image;
}
void CHyprOpenGLImpl::logShaderError(const GLuint& shader, bool program, bool silent) {
GLint maxLength = 0;
if (program)
glGetProgramiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
else
glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &maxLength);
std::vector<GLchar> errorLog(maxLength);
if (program)
glGetProgramInfoLog(shader, maxLength, &maxLength, errorLog.data());
else
glGetShaderInfoLog(shader, maxLength, &maxLength, errorLog.data());
std::string errorStr(errorLog.begin(), errorLog.end());
const auto FULLERROR = (program ? "Screen shader parser: Error linking program:" : "Screen shader parser: Error compiling shader: ") + errorStr;
Debug::log(ERR, "Failed to link shader: {}", FULLERROR);
if (!silent)
g_pConfigManager->addParseError(FULLERROR);
}
GLuint CHyprOpenGLImpl::createProgram(const std::string& vert, const std::string& frag, bool dynamic, bool silent) {
auto vertCompiled = compileShader(GL_VERTEX_SHADER, vert, dynamic, silent);
2022-12-01 13:36:07 +00:00
if (dynamic) {
if (vertCompiled == 0)
return 0;
} else
RASSERT(vertCompiled, "Compiling shader failed. VERTEX nullptr! Shader source:\n\n{}", vert);
2022-04-04 19:44:25 +02:00
auto fragCompiled = compileShader(GL_FRAGMENT_SHADER, frag, dynamic, silent);
2022-12-01 13:36:07 +00:00
if (dynamic) {
if (fragCompiled == 0)
return 0;
} else
RASSERT(fragCompiled, "Compiling shader failed. FRAGMENT nullptr! Shader source:\n\n{}", frag);
2022-04-04 19:44:25 +02:00
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);
2022-12-01 13:36:07 +00:00
if (dynamic) {
if (ok == GL_FALSE) {
logShaderError(prog, true, silent);
2022-12-01 13:36:07 +00:00
return 0;
}
2022-12-01 13:36:07 +00:00
} else {
if (ok != GL_TRUE)
logShaderError(prog, true);
2022-12-01 13:36:07 +00:00
RASSERT(ok != GL_FALSE, "createProgram() failed! GL_LINK_STATUS not OK!");
}
2022-04-04 19:44:25 +02:00
return prog;
}
GLuint CHyprOpenGLImpl::compileShader(const GLuint& type, std::string src, bool dynamic, bool silent) {
2022-04-04 19:44:25 +02:00
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);
2022-12-01 13:36:07 +00:00
if (dynamic) {
if (ok == GL_FALSE) {
logShaderError(shader, false, silent);
2022-12-01 13:36:07 +00:00
return 0;
}
2022-12-01 13:36:07 +00:00
} else {
if (ok != GL_TRUE)
logShaderError(shader, false);
2022-12-01 13:36:07 +00:00
RASSERT(ok != GL_FALSE, "compileShader() failed! GL_COMPILE_STATUS not OK!");
}
2022-04-04 19:44:25 +02:00
return shader;
}
void CHyprOpenGLImpl::beginSimple(PHLMONITOR pMonitor, const CRegion& damage, SP<CRenderbuffer> rb, CFramebuffer* fb) {
m_renderData.pMonitor = pMonitor;
const GLenum RESETSTATUS = glGetGraphicsResetStatus();
if (RESETSTATUS != GL_NO_ERROR) {
std::string errStr = "";
switch (RESETSTATUS) {
case GL_GUILTY_CONTEXT_RESET: errStr = "GL_GUILTY_CONTEXT_RESET"; break;
case GL_INNOCENT_CONTEXT_RESET: errStr = "GL_INNOCENT_CONTEXT_RESET"; break;
case GL_UNKNOWN_CONTEXT_RESET: errStr = "GL_UNKNOWN_CONTEXT_RESET"; break;
default: errStr = "UNKNOWN??"; break;
}
RASSERT(false, "Aborting, glGetGraphicsResetStatus returned {}. Cannot continue until proper GPU reset handling is implemented.", errStr);
return;
}
TRACY_GPU_ZONE("RenderBeginSimple");
const auto FBO = rb ? rb->getFB() : fb;
glViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y);
m_renderData.projection = Mat3x3::outputProjection(pMonitor->m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
m_renderData.monitorProjection = Mat3x3::identity();
if (pMonitor->m_transform != WL_OUTPUT_TRANSFORM_NORMAL) {
const Vector2D tfmd = pMonitor->m_transform % 2 == 1 ? Vector2D{FBO->m_size.y, FBO->m_size.x} : FBO->m_size;
m_renderData.monitorProjection.translate(FBO->m_size / 2.0).transform(wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0);
}
m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor];
if (!m_shadersInitialized)
initShaders();
m_renderData.damage.set(damage);
m_renderData.finalDamage.set(damage);
m_fakeFrame = true;
m_renderData.currentFB = FBO;
FBO->bind();
m_offloadedFramebuffer = false;
m_renderData.mainFB = m_renderData.currentFB;
m_renderData.outFB = FBO;
m_renderData.simplePass = true;
}
void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, CFramebuffer* fb, std::optional<CRegion> finalDamage) {
m_renderData.pMonitor = pMonitor;
2023-07-20 17:47:49 +02:00
const GLenum RESETSTATUS = glGetGraphicsResetStatus();
if (RESETSTATUS != GL_NO_ERROR) {
std::string errStr = "";
switch (RESETSTATUS) {
case GL_GUILTY_CONTEXT_RESET: errStr = "GL_GUILTY_CONTEXT_RESET"; break;
case GL_INNOCENT_CONTEXT_RESET: errStr = "GL_INNOCENT_CONTEXT_RESET"; break;
case GL_UNKNOWN_CONTEXT_RESET: errStr = "GL_UNKNOWN_CONTEXT_RESET"; break;
default: errStr = "UNKNOWN??"; break;
}
RASSERT(false, "Aborting, glGetGraphicsResetStatus returned {}. Cannot continue until proper GPU reset handling is implemented.", errStr);
return;
}
TRACY_GPU_ZONE("RenderBegin");
2022-08-12 17:10:07 +02:00
glViewport(0, 0, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y);
2022-04-04 19:44:25 +02:00
m_renderData.projection = Mat3x3::outputProjection(pMonitor->m_pixelSize, HYPRUTILS_TRANSFORM_NORMAL);
2022-04-04 19:44:25 +02:00
m_renderData.monitorProjection = pMonitor->m_projMatrix;
if (m_monitorRenderResources.contains(pMonitor) && m_monitorRenderResources.at(pMonitor).offloadFB.m_size != pMonitor->m_pixelSize)
destroyMonitorResources(pMonitor);
m_renderData.pCurrentMonData = &m_monitorRenderResources[pMonitor];
2022-06-29 12:54:53 +02:00
if (!m_shadersInitialized)
initShaders();
const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat;
2022-04-09 15:01:28 +02:00
// ensure a framebuffer for the monitor exists
if (m_renderData.pCurrentMonData->offloadFB.m_size != pMonitor->m_pixelSize || DRM_FORMAT != m_renderData.pCurrentMonData->offloadFB.m_drmFormat) {
m_renderData.pCurrentMonData->stencilTex->allocate();
m_renderData.pCurrentMonData->offloadFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pCurrentMonData->mirrorFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pCurrentMonData->mirrorSwapFB.alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pCurrentMonData->offloadFB.addStencil(m_renderData.pCurrentMonData->stencilTex);
m_renderData.pCurrentMonData->mirrorFB.addStencil(m_renderData.pCurrentMonData->stencilTex);
m_renderData.pCurrentMonData->mirrorSwapFB.addStencil(m_renderData.pCurrentMonData->stencilTex);
2022-04-10 14:32:18 +02:00
}
2022-04-09 15:01:28 +02:00
if (m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated() && m_renderData.pMonitor->m_mirrors.empty())
m_renderData.pCurrentMonData->monitorMirrorFB.release();
m_renderData.damage.set(damage_);
m_renderData.finalDamage.set(finalDamage.value_or(damage_));
m_fakeFrame = fb;
2022-12-01 13:36:07 +00:00
if (m_reloadScreenShader) {
m_reloadScreenShader = false;
static auto PSHADER = CConfigValue<std::string>("decoration:screen_shader");
applyScreenShader(*PSHADER);
2022-12-01 13:36:07 +00:00
}
m_renderData.pCurrentMonData->offloadFB.bind();
m_renderData.currentFB = &m_renderData.pCurrentMonData->offloadFB;
m_offloadedFramebuffer = true;
m_renderData.mainFB = m_renderData.currentFB;
m_renderData.outFB = fb ? fb : g_pHyprRenderer->getCurrentRBO()->getFB();
2022-04-04 19:44:25 +02:00
}
void CHyprOpenGLImpl::end() {
static auto PZOOMRIGID = CConfigValue<Hyprlang::INT>("cursor:zoom_rigid");
2023-04-16 14:48:38 +01:00
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderEnd");
// end the render, copy the data to the main framebuffer
if (m_offloadedFramebuffer) {
m_renderData.damage = m_renderData.finalDamage;
m_endFrame = true;
2022-09-13 15:25:42 +02:00
CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
2022-04-09 15:01:28 +02:00
if (m_renderData.mouseZoomFactor != 1.f) {
const auto ZOOMCENTER = m_renderData.mouseZoomUseMouse ?
(g_pInputManager->getMouseCoordsInternal() - m_renderData.pMonitor->m_position) * m_renderData.pMonitor->m_scale :
m_renderData.pMonitor->m_transformedSize / 2.f;
monbox.translate(-ZOOMCENTER).scale(m_renderData.mouseZoomFactor).translate(*PZOOMRIGID ? m_renderData.pMonitor->m_transformedSize / 2.0 : ZOOMCENTER);
2023-04-16 14:48:38 +01:00
if (monbox.x > 0)
monbox.x = 0;
if (monbox.y > 0)
monbox.y = 0;
if (monbox.x + monbox.width < m_renderData.pMonitor->m_transformedSize.x)
monbox.x = m_renderData.pMonitor->m_transformedSize.x - monbox.width;
if (monbox.y + monbox.height < m_renderData.pMonitor->m_transformedSize.y)
monbox.y = m_renderData.pMonitor->m_transformedSize.y - monbox.height;
2023-04-16 14:48:38 +01:00
}
m_applyFinalShader = !m_renderData.blockScreenShader;
if (m_renderData.mouseZoomUseMouse)
m_renderData.useNearestNeighbor = true;
// copy the damaged areas into the mirror buffer
// we can't use the offloadFB for mirroring, as it contains artifacts from blurring
if (!m_renderData.pMonitor->m_mirrors.empty() && !m_fakeFrame)
saveBufferForMirror(monbox);
m_renderData.outFB->bind();
blend(false);
if (m_finalScreenShader.program < 1 && !g_pHyprRenderer->m_crashingInProgress)
renderTexturePrimitive(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox);
else
renderTexture(m_renderData.pCurrentMonData->offloadFB.getTexture(), monbox, 1.f);
blend(true);
m_renderData.useNearestNeighbor = false;
m_applyFinalShader = false;
m_endFrame = false;
}
2022-04-09 15:01:28 +02:00
// reset our data
m_renderData.pMonitor.reset();
m_renderData.mouseZoomFactor = 1.f;
m_renderData.mouseZoomUseMouse = true;
m_renderData.blockScreenShader = false;
m_renderData.currentFB = nullptr;
m_renderData.mainFB = nullptr;
m_renderData.outFB = nullptr;
// if we dropped to offMain, release it now.
// if there is a plugin constantly using it, this might be a bit slow,
// but I havent seen a single plugin yet use these, so it's better to drop a bit of vram.
if (m_renderData.pCurrentMonData->offMainFB.isAllocated())
m_renderData.pCurrentMonData->offMainFB.release();
// check for gl errors
const GLenum ERR = glGetError();
if (ERR == GL_CONTEXT_LOST) /* We don't have infra to recover from this */
RASSERT(false, "glGetError at Opengl::end() returned GL_CONTEXT_LOST. Cannot continue until proper GPU reset handling is implemented.");
2022-04-04 19:44:25 +02:00
}
void CHyprOpenGLImpl::setDamage(const CRegion& damage_, std::optional<CRegion> finalDamage) {
m_renderData.damage.set(damage_);
m_renderData.finalDamage.set(finalDamage.value_or(damage_));
}
// TODO notify user if bundled shader is newer than ~/.config override
static std::string loadShader(const std::string& filename) {
const auto home = Hyprutils::Path::getHome();
if (home.has_value()) {
const auto src = NFsUtils::readFileAsString(home.value() + "/hypr/shaders/" + filename);
if (src.has_value())
return src.value();
}
for (auto& e : ASSET_PATHS) {
const auto src = NFsUtils::readFileAsString(std::string{e} + "/hypr/shaders/" + filename);
if (src.has_value())
return src.value();
}
if (SHADERS.contains(filename))
return SHADERS.at(filename);
throw std::runtime_error(std::format("Couldn't load shader {}", filename));
}
static void loadShaderInclude(const std::string& filename, std::map<std::string, std::string>& includes) {
includes.insert({filename, loadShader(filename)});
}
static void processShaderIncludes(std::string& source, const std::map<std::string, std::string>& includes) {
for (auto it = includes.begin(); it != includes.end(); ++it) {
Hyprutils::String::replaceInString(source, "#include \"" + it->first + "\"", it->second);
}
}
static std::string processShader(const std::string& filename, const std::map<std::string, std::string>& includes) {
auto source = loadShader(filename);
processShaderIncludes(source, includes);
return source;
}
// shader has #include "CM.glsl"
static void getCMShaderUniforms(SShader& shader) {
shader.skipCM = glGetUniformLocation(shader.program, "skipCM");
shader.sourceTF = glGetUniformLocation(shader.program, "sourceTF");
shader.targetTF = glGetUniformLocation(shader.program, "targetTF");
shader.srcTFRange = glGetUniformLocation(shader.program, "srcTFRange");
shader.dstTFRange = glGetUniformLocation(shader.program, "dstTFRange");
shader.targetPrimaries = glGetUniformLocation(shader.program, "targetPrimaries");
shader.maxLuminance = glGetUniformLocation(shader.program, "maxLuminance");
shader.dstMaxLuminance = glGetUniformLocation(shader.program, "dstMaxLuminance");
shader.dstRefLuminance = glGetUniformLocation(shader.program, "dstRefLuminance");
shader.sdrSaturation = glGetUniformLocation(shader.program, "sdrSaturation");
shader.sdrBrightness = glGetUniformLocation(shader.program, "sdrBrightnessMultiplier");
shader.convertMatrix = glGetUniformLocation(shader.program, "convertMatrix");
}
// shader has #include "rounding.glsl"
static void getRoundingShaderUniforms(SShader& shader) {
shader.topLeft = glGetUniformLocation(shader.program, "topLeft");
shader.fullSize = glGetUniformLocation(shader.program, "fullSize");
shader.radius = glGetUniformLocation(shader.program, "radius");
shader.roundingPower = glGetUniformLocation(shader.program, "roundingPower");
}
bool CHyprOpenGLImpl::initShaders() {
auto shaders = makeShared<SPreparedShaders>();
const bool isDynamic = m_shadersInitialized;
static const auto PCM = CConfigValue<Hyprlang::INT>("render:cm_enabled");
try {
std::map<std::string, std::string> includes;
loadShaderInclude("rounding.glsl", includes);
loadShaderInclude("CM.glsl", includes);
shaders->TEXVERTSRC = processShader("tex.vert", includes);
shaders->TEXVERTSRC300 = processShader("tex300.vert", includes);
shaders->TEXVERTSRC320 = processShader("tex320.vert", includes);
GLuint prog;
if (!*PCM)
m_cmSupported = false;
else {
const auto TEXFRAGSRCCM = processShader("CM.frag", includes);
prog = createProgram(shaders->TEXVERTSRC300, TEXFRAGSRCCM, true, true);
if (m_shadersInitialized && m_cmSupported && prog == 0)
g_pHyprNotificationOverlay->addNotification("CM shader reload failed, falling back to rgba/rgbx", CHyprColor{}, 15000, ICON_WARNING);
m_cmSupported = prog > 0;
if (m_cmSupported) {
shaders->m_shCM.program = prog;
getCMShaderUniforms(shaders->m_shCM);
getRoundingShaderUniforms(shaders->m_shCM);
shaders->m_shCM.proj = glGetUniformLocation(prog, "proj");
shaders->m_shCM.tex = glGetUniformLocation(prog, "tex");
shaders->m_shCM.texType = glGetUniformLocation(prog, "texType");
shaders->m_shCM.alphaMatte = glGetUniformLocation(prog, "texMatte");
shaders->m_shCM.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shCM.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shCM.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
shaders->m_shCM.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shCM.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
shaders->m_shCM.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
shaders->m_shCM.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
shaders->m_shCM.applyTint = glGetUniformLocation(prog, "applyTint");
shaders->m_shCM.tint = glGetUniformLocation(prog, "tint");
shaders->m_shCM.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
shaders->m_shCM.createVao();
} else
Debug::log(ERR,
"WARNING: CM Shader failed compiling, color management will not work. It's likely because your GPU is an old piece of garbage, don't file bug reports "
"about this!");
}
const auto FRAGSHADOW = processShader(m_cmSupported ? "shadow.frag" : "shadow_legacy.frag", includes);
const auto FRAGBORDER1 = processShader(m_cmSupported ? "border.frag" : "border_legacy.frag", includes);
const auto FRAGBLURPREPARE = processShader(m_cmSupported ? "blurprepare.frag" : "blurprepare_legacy.frag", includes);
const auto FRAGBLURFINISH = processShader(m_cmSupported ? "blurfinish.frag" : "blurfinish_legacy.frag", includes);
const auto QUADFRAGSRC = processShader("quad.frag", includes);
const auto TEXFRAGSRCRGBA = processShader("rgba.frag", includes);
const auto TEXFRAGSRCRGBAPASSTHRU = processShader("passthru.frag", includes);
const auto TEXFRAGSRCRGBAMATTE = processShader("rgbamatte.frag", includes);
const auto FRAGGLITCH = processShader("glitch.frag", includes);
const auto TEXFRAGSRCRGBX = processShader("rgbx.frag", includes);
const auto TEXFRAGSRCEXT = processShader("ext.frag", includes);
const auto FRAGBLUR1 = processShader("blur1.frag", includes);
const auto FRAGBLUR2 = processShader("blur2.frag", includes);
prog = createProgram(shaders->TEXVERTSRC, QUADFRAGSRC, isDynamic);
if (!prog)
return false;
shaders->m_shQUAD.program = prog;
getRoundingShaderUniforms(shaders->m_shQUAD);
shaders->m_shQUAD.proj = glGetUniformLocation(prog, "proj");
shaders->m_shQUAD.color = glGetUniformLocation(prog, "color");
shaders->m_shQUAD.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shQUAD.createVao();
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBA, isDynamic);
if (!prog)
return false;
shaders->m_shRGBA.program = prog;
getRoundingShaderUniforms(shaders->m_shRGBA);
shaders->m_shRGBA.proj = glGetUniformLocation(prog, "proj");
shaders->m_shRGBA.tex = glGetUniformLocation(prog, "tex");
shaders->m_shRGBA.alphaMatte = glGetUniformLocation(prog, "texMatte");
shaders->m_shRGBA.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shRGBA.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shRGBA.matteTexAttrib = glGetAttribLocation(prog, "texcoordMatte");
shaders->m_shRGBA.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shRGBA.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
shaders->m_shRGBA.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
shaders->m_shRGBA.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
shaders->m_shRGBA.applyTint = glGetUniformLocation(prog, "applyTint");
shaders->m_shRGBA.tint = glGetUniformLocation(prog, "tint");
shaders->m_shRGBA.useAlphaMatte = glGetUniformLocation(prog, "useAlphaMatte");
shaders->m_shRGBA.createVao();
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAPASSTHRU, isDynamic);
if (!prog)
return false;
shaders->m_shPASSTHRURGBA.program = prog;
shaders->m_shPASSTHRURGBA.proj = glGetUniformLocation(prog, "proj");
shaders->m_shPASSTHRURGBA.tex = glGetUniformLocation(prog, "tex");
shaders->m_shPASSTHRURGBA.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shPASSTHRURGBA.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shPASSTHRURGBA.createVao();
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBAMATTE, isDynamic);
if (!prog)
return false;
shaders->m_shMATTE.program = prog;
shaders->m_shMATTE.proj = glGetUniformLocation(prog, "proj");
shaders->m_shMATTE.tex = glGetUniformLocation(prog, "tex");
shaders->m_shMATTE.alphaMatte = glGetUniformLocation(prog, "texMatte");
shaders->m_shMATTE.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shMATTE.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shMATTE.createVao();
prog = createProgram(shaders->TEXVERTSRC, FRAGGLITCH, isDynamic);
if (!prog)
return false;
shaders->m_shGLITCH.program = prog;
shaders->m_shGLITCH.proj = glGetUniformLocation(prog, "proj");
shaders->m_shGLITCH.tex = glGetUniformLocation(prog, "tex");
shaders->m_shGLITCH.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shGLITCH.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shGLITCH.distort = glGetUniformLocation(prog, "distort");
shaders->m_shGLITCH.time = glGetUniformLocation(prog, "time");
shaders->m_shGLITCH.fullSize = glGetUniformLocation(prog, "screenSize");
shaders->m_shGLITCH.createVao();
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCRGBX, isDynamic);
if (!prog)
return false;
shaders->m_shRGBX.program = prog;
getRoundingShaderUniforms(shaders->m_shRGBX);
shaders->m_shRGBX.tex = glGetUniformLocation(prog, "tex");
shaders->m_shRGBX.proj = glGetUniformLocation(prog, "proj");
shaders->m_shRGBX.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shRGBX.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shRGBX.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shRGBX.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
shaders->m_shRGBX.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
shaders->m_shRGBX.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
shaders->m_shRGBX.applyTint = glGetUniformLocation(prog, "applyTint");
shaders->m_shRGBX.tint = glGetUniformLocation(prog, "tint");
shaders->m_shRGBX.createVao();
prog = createProgram(shaders->TEXVERTSRC, TEXFRAGSRCEXT, isDynamic);
if (!prog)
return false;
shaders->m_shEXT.program = prog;
getRoundingShaderUniforms(shaders->m_shEXT);
shaders->m_shEXT.tex = glGetUniformLocation(prog, "tex");
shaders->m_shEXT.proj = glGetUniformLocation(prog, "proj");
shaders->m_shEXT.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shEXT.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shEXT.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shEXT.discardOpaque = glGetUniformLocation(prog, "discardOpaque");
shaders->m_shEXT.discardAlpha = glGetUniformLocation(prog, "discardAlpha");
shaders->m_shEXT.discardAlphaValue = glGetUniformLocation(prog, "discardAlphaValue");
shaders->m_shEXT.applyTint = glGetUniformLocation(prog, "applyTint");
shaders->m_shEXT.tint = glGetUniformLocation(prog, "tint");
shaders->m_shEXT.createVao();
prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR1, isDynamic);
if (!prog)
return false;
shaders->m_shBLUR1.program = prog;
shaders->m_shBLUR1.tex = glGetUniformLocation(prog, "tex");
shaders->m_shBLUR1.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shBLUR1.proj = glGetUniformLocation(prog, "proj");
shaders->m_shBLUR1.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shBLUR1.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shBLUR1.radius = glGetUniformLocation(prog, "radius");
shaders->m_shBLUR1.halfpixel = glGetUniformLocation(prog, "halfpixel");
shaders->m_shBLUR1.passes = glGetUniformLocation(prog, "passes");
shaders->m_shBLUR1.vibrancy = glGetUniformLocation(prog, "vibrancy");
shaders->m_shBLUR1.vibrancy_darkness = glGetUniformLocation(prog, "vibrancy_darkness");
shaders->m_shBLUR1.createVao();
prog = createProgram(shaders->TEXVERTSRC, FRAGBLUR2, isDynamic);
if (!prog)
return false;
shaders->m_shBLUR2.program = prog;
shaders->m_shBLUR2.tex = glGetUniformLocation(prog, "tex");
shaders->m_shBLUR2.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shBLUR2.proj = glGetUniformLocation(prog, "proj");
shaders->m_shBLUR2.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shBLUR2.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shBLUR2.radius = glGetUniformLocation(prog, "radius");
shaders->m_shBLUR2.halfpixel = glGetUniformLocation(prog, "halfpixel");
shaders->m_shBLUR2.createVao();
prog = createProgram(m_cmSupported ? shaders->TEXVERTSRC300 : shaders->TEXVERTSRC, FRAGBLURPREPARE, isDynamic);
if (!prog)
return false;
shaders->m_shBLURPREPARE.program = prog;
if (m_cmSupported)
getCMShaderUniforms(shaders->m_shBLURPREPARE);
shaders->m_shBLURPREPARE.tex = glGetUniformLocation(prog, "tex");
shaders->m_shBLURPREPARE.proj = glGetUniformLocation(prog, "proj");
shaders->m_shBLURPREPARE.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shBLURPREPARE.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shBLURPREPARE.contrast = glGetUniformLocation(prog, "contrast");
shaders->m_shBLURPREPARE.brightness = glGetUniformLocation(prog, "brightness");
shaders->m_shBLURPREPARE.createVao();
prog = createProgram(m_cmSupported ? shaders->TEXVERTSRC300 : shaders->TEXVERTSRC, FRAGBLURFINISH, isDynamic);
if (!prog)
return false;
shaders->m_shBLURFINISH.program = prog;
// getCMShaderUniforms(shaders->m_shBLURFINISH);
shaders->m_shBLURFINISH.tex = glGetUniformLocation(prog, "tex");
shaders->m_shBLURFINISH.proj = glGetUniformLocation(prog, "proj");
shaders->m_shBLURFINISH.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shBLURFINISH.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shBLURFINISH.brightness = glGetUniformLocation(prog, "brightness");
shaders->m_shBLURFINISH.noise = glGetUniformLocation(prog, "noise");
shaders->m_shBLURFINISH.createVao();
prog = createProgram(m_cmSupported ? shaders->TEXVERTSRC300 : shaders->TEXVERTSRC, FRAGSHADOW, isDynamic);
if (!prog)
return false;
if (m_cmSupported)
shaders->m_shSHADOW.program = prog;
getCMShaderUniforms(shaders->m_shSHADOW);
getRoundingShaderUniforms(shaders->m_shSHADOW);
shaders->m_shSHADOW.proj = glGetUniformLocation(prog, "proj");
shaders->m_shSHADOW.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shSHADOW.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shSHADOW.bottomRight = glGetUniformLocation(prog, "bottomRight");
shaders->m_shSHADOW.range = glGetUniformLocation(prog, "range");
shaders->m_shSHADOW.shadowPower = glGetUniformLocation(prog, "shadowPower");
shaders->m_shSHADOW.color = glGetUniformLocation(prog, "color");
shaders->m_shSHADOW.createVao();
prog = createProgram(m_cmSupported ? shaders->TEXVERTSRC300 : shaders->TEXVERTSRC, FRAGBORDER1, isDynamic);
if (!prog)
return false;
shaders->m_shBORDER1.program = prog;
if (m_cmSupported)
getCMShaderUniforms(shaders->m_shBORDER1);
getRoundingShaderUniforms(shaders->m_shBORDER1);
shaders->m_shBORDER1.proj = glGetUniformLocation(prog, "proj");
shaders->m_shBORDER1.thick = glGetUniformLocation(prog, "thick");
shaders->m_shBORDER1.posAttrib = glGetAttribLocation(prog, "pos");
shaders->m_shBORDER1.texAttrib = glGetAttribLocation(prog, "texcoord");
shaders->m_shBORDER1.bottomRight = glGetUniformLocation(prog, "bottomRight");
shaders->m_shBORDER1.fullSizeUntransformed = glGetUniformLocation(prog, "fullSizeUntransformed");
shaders->m_shBORDER1.radiusOuter = glGetUniformLocation(prog, "radiusOuter");
shaders->m_shBORDER1.gradient = glGetUniformLocation(prog, "gradient");
shaders->m_shBORDER1.gradient2 = glGetUniformLocation(prog, "gradient2");
shaders->m_shBORDER1.gradientLength = glGetUniformLocation(prog, "gradientLength");
shaders->m_shBORDER1.gradient2Length = glGetUniformLocation(prog, "gradient2Length");
shaders->m_shBORDER1.angle = glGetUniformLocation(prog, "angle");
shaders->m_shBORDER1.angle2 = glGetUniformLocation(prog, "angle2");
shaders->m_shBORDER1.gradientLerp = glGetUniformLocation(prog, "gradientLerp");
shaders->m_shBORDER1.alpha = glGetUniformLocation(prog, "alpha");
shaders->m_shBORDER1.createVao();
} catch (const std::exception& e) {
if (!m_shadersInitialized)
throw e;
Debug::log(ERR, "Shaders update failed: {}", e.what());
return false;
}
m_shaders = shaders;
m_shadersInitialized = true;
Debug::log(LOG, "Shaders initialized successfully.");
g_pHyprError->destroy();
return true;
}
2022-12-01 13:36:07 +00:00
void CHyprOpenGLImpl::applyScreenShader(const std::string& path) {
static auto PDT = CConfigValue<Hyprlang::INT>("debug:damage_tracking");
m_finalScreenShader.destroy();
2022-12-01 13:36:07 +00:00
if (path == "" || path == STRVAL_EMPTY)
return;
std::ifstream infile(absolutePath(path, g_pConfigManager->getMainConfigPath()));
2022-12-01 13:36:07 +00:00
if (!infile.good()) {
g_pConfigManager->addParseError("Screen shader parser: Screen shader path not found");
return;
}
std::string fragmentShader((std::istreambuf_iterator<char>(infile)), (std::istreambuf_iterator<char>()));
m_finalScreenShader.program = createProgram( //
fragmentShader.starts_with("#version 320 es") // do not break existing custom shaders
?
m_shaders->TEXVERTSRC320 :
(fragmentShader.starts_with("#version 300 es") // support lower es versions
?
m_shaders->TEXVERTSRC300 :
m_shaders->TEXVERTSRC),
fragmentShader, true);
if (!m_finalScreenShader.program) {
// Error will have been sent by now by the underlying cause
2022-12-01 13:36:07 +00:00
return;
}
m_finalScreenShader.proj = glGetUniformLocation(m_finalScreenShader.program, "proj");
m_finalScreenShader.tex = glGetUniformLocation(m_finalScreenShader.program, "tex");
m_finalScreenShader.time = glGetUniformLocation(m_finalScreenShader.program, "time");
if (m_finalScreenShader.time != -1)
m_finalScreenShader.initialTime = m_globalTimer.getSeconds();
m_finalScreenShader.wl_output = glGetUniformLocation(m_finalScreenShader.program, "wl_output");
m_finalScreenShader.fullSize = glGetUniformLocation(m_finalScreenShader.program, "screen_size");
if (m_finalScreenShader.fullSize == -1)
m_finalScreenShader.fullSize = glGetUniformLocation(m_finalScreenShader.program, "screenSize");
if (m_finalScreenShader.time != -1 && *PDT != 0 && !g_pHyprRenderer->m_crashingInProgress) {
// The screen shader uses the "time" uniform
// Since the screen shader could change every frame, damage tracking *needs* to be disabled
g_pConfigManager->addParseError("Screen shader: Screen shader uses uniform 'time', which requires debug:damage_tracking to be switched off.\n"
"WARNING: Disabling damage tracking will *massively* increase GPU utilization!");
}
m_finalScreenShader.texAttrib = glGetAttribLocation(m_finalScreenShader.program, "texcoord");
m_finalScreenShader.posAttrib = glGetAttribLocation(m_finalScreenShader.program, "pos");
m_finalScreenShader.createVao();
2022-12-01 13:36:07 +00:00
}
void CHyprOpenGLImpl::clear(const CHyprColor& color) {
RASSERT(m_renderData.pMonitor, "Tried to render without begin()!");
2022-04-04 19:44:25 +02:00
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderClear");
glClearColor(color.r, color.g, color.b, color.a);
2022-04-14 16:43:29 +02:00
if (!m_renderData.damage.empty()) {
for (auto const& RECT : m_renderData.damage.getRects()) {
2022-04-14 16:43:29 +02:00
scissor(&RECT);
glClear(GL_COLOR_BUFFER_BIT);
}
}
2022-04-14 17:00:35 +02:00
scissor(nullptr);
2022-04-04 19:44:25 +02:00
}
void CHyprOpenGLImpl::blend(bool enabled) {
if (enabled) {
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); // everything is premultiplied
} else
glDisable(GL_BLEND);
m_blend = enabled;
}
void CHyprOpenGLImpl::scissor(const CBox& originalBox, bool transform) {
RASSERT(m_renderData.pMonitor, "Tried to scissor without begin()!");
2022-04-04 21:45:35 +02:00
2022-09-04 19:27:38 +02:00
if (transform) {
CBox box = originalBox;
const auto TR = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform));
box.transform(TR, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y);
glScissor(box.x, box.y, box.width, box.height);
glEnable(GL_SCISSOR_TEST);
return;
2022-09-04 19:27:38 +02:00
}
glScissor(originalBox.x, originalBox.y, originalBox.width, originalBox.height);
2022-04-04 19:44:25 +02:00
glEnable(GL_SCISSOR_TEST);
2022-04-04 21:45:35 +02:00
}
2022-09-04 19:27:38 +02:00
void CHyprOpenGLImpl::scissor(const pixman_box32* pBox, bool transform) {
RASSERT(m_renderData.pMonitor, "Tried to scissor without begin()!");
2022-04-14 16:43:29 +02:00
2022-05-18 20:35:24 +02:00
if (!pBox) {
2022-04-14 16:43:29 +02:00
glDisable(GL_SCISSOR_TEST);
return;
}
CBox newBox = {pBox->x1, pBox->y1, pBox->x2 - pBox->x1, pBox->y2 - pBox->y1};
scissor(newBox, transform);
2022-04-14 16:43:29 +02:00
}
2022-09-04 19:27:38 +02:00
void CHyprOpenGLImpl::scissor(const int x, const int y, const int w, const int h, bool transform) {
CBox box = {x, y, w, h};
scissor(box, transform);
}
void CHyprOpenGLImpl::renderRect(const CBox& box, const CHyprColor& col, int round, float roundingPower) {
if (!m_renderData.damage.empty())
renderRectWithDamage(box, col, m_renderData.damage, round, roundingPower);
}
void CHyprOpenGLImpl::renderRectWithBlur(const CBox& box, const CHyprColor& col, int round, float roundingPower, float blurA, bool xray) {
if (m_renderData.damage.empty())
return;
CRegion damage{m_renderData.damage};
damage.intersect(box);
CFramebuffer* POUTFB = xray ? &m_renderData.pCurrentMonData->blurFB : blurMainFramebufferWithDamage(blurA, &damage);
m_renderData.currentFB->bind();
// make a stencil for rounded corners to work with blur
scissor(nullptr); // allow the entire window and stencil to render
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glEnable(GL_STENCIL_TEST);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
renderRect(box, CHyprColor(0, 0, 0, 0), round, roundingPower);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
glStencilFunc(GL_EQUAL, 1, 0xFF);
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
scissor(box);
CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
m_endFrame = true; // fix transformed
const auto SAVEDRENDERMODIF = m_renderData.renderModif;
m_renderData.renderModif = {}; // fix shit
renderTextureInternalWithDamage(POUTFB->getTexture(), MONITORBOX, blurA, damage, 0, 2.0f, false, false, false);
m_endFrame = false;
m_renderData.renderModif = SAVEDRENDERMODIF;
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
glDisable(GL_STENCIL_TEST);
glStencilMask(0xFF);
glStencilFunc(GL_ALWAYS, 1, 0xFF);
scissor(nullptr);
renderRectWithDamage(box, col, m_renderData.damage, round, roundingPower);
}
void CHyprOpenGLImpl::renderRectWithDamage(const CBox& box, const CHyprColor& col, const CRegion& damage, int round, float roundingPower) {
RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!");
RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!");
2022-04-04 21:45:35 +02:00
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderRectWithDamage");
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(
newBox, wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2022-04-04 21:45:35 +02:00
useProgram(m_shaders->m_shQUAD.program);
glUniformMatrix3fv(m_shaders->m_shQUAD.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
// premultiply the color as well as we don't work with straight alpha
glUniform4f(m_shaders->m_shQUAD.color, col.r * col.a, col.g * col.a, col.b * col.a, col.a);
2022-04-04 21:45:35 +02:00
CBox transformedBox = box;
transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y);
const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);
2022-05-17 13:16:37 +02:00
// Rounded corners
glUniform2f(m_shaders->m_shQUAD.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(m_shaders->m_shQUAD.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform1f(m_shaders->m_shQUAD.radius, round);
glUniform1f(m_shaders->m_shQUAD.roundingPower, roundingPower);
2022-05-17 13:16:37 +02:00
glBindVertexArray(m_shaders->m_shQUAD.shaderVao);
2022-04-04 21:45:35 +02:00
if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) {
CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height};
damageClip.intersect(damage);
2023-07-19 20:09:49 +02:00
if (!damageClip.empty()) {
for (auto const& RECT : damageClip.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
} else {
for (auto const& RECT : damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
2022-05-17 13:16:37 +02:00
}
2022-04-04 21:45:35 +02:00
glBindVertexArray(0);
2024-04-03 14:09:58 +01:00
scissor(nullptr);
2022-04-05 14:33:54 +02:00
}
void CHyprOpenGLImpl::renderTexture(SP<CTexture> tex, const CBox& box, float alpha, int round, float roundingPower, bool discardActive, bool allowCustomUV, GLenum wrapX,
GLenum wrapY) {
RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!");
2022-04-14 16:43:29 +02:00
renderTextureInternalWithDamage(tex, box, alpha, m_renderData.damage, round, roundingPower, discardActive, false, allowCustomUV, true, wrapX, wrapY);
2022-04-14 17:00:35 +02:00
scissor(nullptr);
2022-04-14 16:43:29 +02:00
}
void CHyprOpenGLImpl::renderTextureWithDamage(SP<CTexture> tex, const CBox& box, const CRegion& damage, float alpha, int round, float roundingPower, bool discardActive,
syncobj: use eventfd instead of stalling fd checks (#9437) * syncobj: cleanup and use uniqueptrs cleanup a bit missing removals if resource not good, erasing from containers etc. make use of unique ptrs instead. and add default destructors. * syncobj: rework syncobj entirerly remove early buffer release that was breaking explicit sync, the buffer needs to exist until the surface commit event has been emitted and draw calls added egl sync points, move to eventfd signaling instead of stalling sync point checks, and recommit pending commits if waiting on a signal. add a CDRMSyncPointState helper class. move a few weak pointers to shared pointers so they dont destruct before we need to use them. * syncobj: queue pending states for eventfd eventfd requires us to queue pending stats until ready and then apply to current, and also when no ready state exist commit the client commit on the current existing buffer, if there is one. * syncobj: clear current buffer damage clear current buffer damage on current buffer commits. * syncobj: cleanup code and fix hyprlock remove unused code, and ensure we dont commit a empty texture causing locksession protocol and gtk4-layer-shell misbehaving. * syncobj: ensure buffers are cleaned up ensure the containers having the various buffers actually gets cleaned up from their containers, incase the CSignal isnt signaled because of expired smart pointers or just wrong order destruction because mishaps. also move the acquire/point setting to buffer attaching. instead of on precommit. * syncobj: remove unused code, optimize remove unused code and merge sync fds if fence is valid, remove manual directscanout buffer dropping that signals release point on pageflip, it can cause us to signal the release point while still keeping the current buffer and rendering it yet again causing wrong things. * syncobj: delay buffer release on non syncobj delay buffer releases on non syncobj surfaces until next commit, and check on async buffers if syncobj and drop and signal the release point on backend buffer release. * syncobj: ensure we follow protocol ensure we follow protocol by replacing acquire/release points if they arrive late and replace already existing ones. also remove unneded brackets, and dont try to manual lock/release buffers when it comes to explicit protocol. it doesnt care about buffer releases only about acquire and release points and signaling them. * syncobj: lets not complicate things set points in precommit, before checking protocol errors and we catch any pending acquire/release points arriving late. * syncobj: move SSurfaceState to types remove destructor resource destroying, let resources destroys them on their events, and move SSurfaceStates to types/SurfaceState.hpp * syncobj: actually store the merged fd have to actually store the mergedfd to use it. * syncobj: cleanup a bit around fences ensure the current asynchronous buffer is actually released on pageflip not the previous. cleanup a bit FD handling in commitPendingAndDoExplicitSync, and reuse the in fence when syncing surfaces. * syncobjs: ensure fence FD doesnt leak calling resetexplicitfence without properly ensuring the FD is closed before will leak it, store it per monitor and let it close itself with the CFileDescriptor class. * syncobj: ensure buffers are actually released buffers were never being sent released properly. * types: Defer buffer sync releaser until unlock * syncobj: store directscanout fence in monitor ensure the infence fd survives the scope of attemptdirectscanout so it doesnt close before it should have. * syncobj: check if if acquire is expired we might hit a race to finish on exit where the timeline just has destructed but the buffer waiter is still pending. and such we removeAllWaiters null dereferences. * syncobj: code style changes remove quack comment, change to m_foo and use a std::vector and weakpointer in the waiter for removal instead of a std::list. * syncobj: remove unused async buffer drop remove unused async buffer drop, only related to directscanout and is handled elsewhere. --------- Co-authored-by: Lee Bousfield <ljbousfield@gmail.com>
2025-03-14 15:08:20 +01:00
bool allowCustomUV) {
RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!");
syncobj: use eventfd instead of stalling fd checks (#9437) * syncobj: cleanup and use uniqueptrs cleanup a bit missing removals if resource not good, erasing from containers etc. make use of unique ptrs instead. and add default destructors. * syncobj: rework syncobj entirerly remove early buffer release that was breaking explicit sync, the buffer needs to exist until the surface commit event has been emitted and draw calls added egl sync points, move to eventfd signaling instead of stalling sync point checks, and recommit pending commits if waiting on a signal. add a CDRMSyncPointState helper class. move a few weak pointers to shared pointers so they dont destruct before we need to use them. * syncobj: queue pending states for eventfd eventfd requires us to queue pending stats until ready and then apply to current, and also when no ready state exist commit the client commit on the current existing buffer, if there is one. * syncobj: clear current buffer damage clear current buffer damage on current buffer commits. * syncobj: cleanup code and fix hyprlock remove unused code, and ensure we dont commit a empty texture causing locksession protocol and gtk4-layer-shell misbehaving. * syncobj: ensure buffers are cleaned up ensure the containers having the various buffers actually gets cleaned up from their containers, incase the CSignal isnt signaled because of expired smart pointers or just wrong order destruction because mishaps. also move the acquire/point setting to buffer attaching. instead of on precommit. * syncobj: remove unused code, optimize remove unused code and merge sync fds if fence is valid, remove manual directscanout buffer dropping that signals release point on pageflip, it can cause us to signal the release point while still keeping the current buffer and rendering it yet again causing wrong things. * syncobj: delay buffer release on non syncobj delay buffer releases on non syncobj surfaces until next commit, and check on async buffers if syncobj and drop and signal the release point on backend buffer release. * syncobj: ensure we follow protocol ensure we follow protocol by replacing acquire/release points if they arrive late and replace already existing ones. also remove unneded brackets, and dont try to manual lock/release buffers when it comes to explicit protocol. it doesnt care about buffer releases only about acquire and release points and signaling them. * syncobj: lets not complicate things set points in precommit, before checking protocol errors and we catch any pending acquire/release points arriving late. * syncobj: move SSurfaceState to types remove destructor resource destroying, let resources destroys them on their events, and move SSurfaceStates to types/SurfaceState.hpp * syncobj: actually store the merged fd have to actually store the mergedfd to use it. * syncobj: cleanup a bit around fences ensure the current asynchronous buffer is actually released on pageflip not the previous. cleanup a bit FD handling in commitPendingAndDoExplicitSync, and reuse the in fence when syncing surfaces. * syncobjs: ensure fence FD doesnt leak calling resetexplicitfence without properly ensuring the FD is closed before will leak it, store it per monitor and let it close itself with the CFileDescriptor class. * syncobj: ensure buffers are actually released buffers were never being sent released properly. * types: Defer buffer sync releaser until unlock * syncobj: store directscanout fence in monitor ensure the infence fd survives the scope of attemptdirectscanout so it doesnt close before it should have. * syncobj: check if if acquire is expired we might hit a race to finish on exit where the timeline just has destructed but the buffer waiter is still pending. and such we removeAllWaiters null dereferences. * syncobj: code style changes remove quack comment, change to m_foo and use a std::vector and weakpointer in the waiter for removal instead of a std::list. * syncobj: remove unused async buffer drop remove unused async buffer drop, only related to directscanout and is handled elsewhere. --------- Co-authored-by: Lee Bousfield <ljbousfield@gmail.com>
2025-03-14 15:08:20 +01:00
renderTextureInternalWithDamage(tex, box, alpha, damage, round, roundingPower, discardActive, false, allowCustomUV, true);
scissor(nullptr);
}
static std::map<std::pair<uint32_t, uint32_t>, std::array<GLfloat, 9>> primariesConversionCache;
void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const NColorManagement::SImageDescription& imageDescription,
const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR) {
glUniform1i(shader.sourceTF, imageDescription.transferFunction);
glUniform1i(shader.targetTF, targetImageDescription.transferFunction);
const auto targetPrimaries = targetImageDescription.primariesNameSet || targetImageDescription.primaries == SPCPRimaries{} ?
getPrimaries(targetImageDescription.primariesNamed) :
targetImageDescription.primaries;
const GLfloat glTargetPrimaries[8] = {
targetPrimaries.red.x, targetPrimaries.red.y, targetPrimaries.green.x, targetPrimaries.green.y,
targetPrimaries.blue.x, targetPrimaries.blue.y, targetPrimaries.white.x, targetPrimaries.white.y,
};
glUniformMatrix4x2fv(shader.targetPrimaries, 1, false, glTargetPrimaries);
glUniform2f(shader.srcTFRange, imageDescription.getTFMinLuminance(), imageDescription.getTFMaxLuminance());
glUniform2f(shader.dstTFRange, targetImageDescription.getTFMinLuminance(), targetImageDescription.getTFMaxLuminance());
const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference;
glUniform1f(shader.maxLuminance, maxLuminance * targetImageDescription.luminances.reference / imageDescription.luminances.reference);
glUniform1f(shader.dstMaxLuminance, targetImageDescription.luminances.max > 0 ? targetImageDescription.luminances.max : 10000);
glUniform1f(shader.dstRefLuminance, targetImageDescription.luminances.reference);
glUniform1f(shader.sdrSaturation,
modifySDR && m_renderData.pMonitor->m_sdrSaturation > 0 && targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrSaturation :
1.0f);
glUniform1f(shader.sdrBrightness,
modifySDR && m_renderData.pMonitor->m_sdrBrightness > 0 && targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
const auto cacheKey = std::make_pair(imageDescription.getId(), targetImageDescription.getId());
if (!primariesConversionCache.contains(cacheKey)) {
const auto mat = imageDescription.getPrimaries().convertMatrix(targetImageDescription.getPrimaries()).mat();
const std::array<GLfloat, 9> glConvertMatrix = {
mat[0][0], mat[1][0], mat[2][0], //
mat[0][1], mat[1][1], mat[2][1], //
mat[0][2], mat[1][2], mat[2][2], //
};
primariesConversionCache.insert(std::make_pair(cacheKey, glConvertMatrix));
}
glUniformMatrix3fv(shader.convertMatrix, 1, false, &primariesConversionCache[cacheKey][0]);
}
void CHyprOpenGLImpl::passCMUniforms(const SShader& shader, const SImageDescription& imageDescription) {
passCMUniforms(shader, imageDescription, m_renderData.pMonitor->m_imageDescription, true);
}
void CHyprOpenGLImpl::renderTextureInternalWithDamage(SP<CTexture> tex, const CBox& box, float alpha, const CRegion& damage, int round, float roundingPower, bool discardActive,
bool noAA, bool allowCustomUV, bool allowDim, GLenum wrapX, GLenum wrapY) {
RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!");
RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!");
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderTextureInternalWithDamage");
2023-01-07 13:12:02 +01:00
alpha = std::clamp(alpha, 0.f, 1.f);
2022-04-05 14:33:54 +02:00
if (damage.empty())
return;
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
static const auto PDT = CConfigValue<Hyprlang::INT>("debug:damage_tracking");
static const auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough");
static const auto PENABLECM = CConfigValue<Hyprlang::INT>("render:cm_enabled");
2022-08-30 12:46:17 +02:00
// get the needed transform for this texture
const bool TRANSFORMS_MATCH = wlTransformToHyprutils(m_renderData.pMonitor->m_transform) == tex->m_transform; // FIXME: combine them properly!!!
eTransform TRANSFORM = HYPRUTILS_TRANSFORM_NORMAL;
if (m_endFrame || TRANSFORMS_MATCH)
TRANSFORM = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform));
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2022-04-05 14:33:54 +02:00
SShader* shader = nullptr;
2023-04-04 14:49:58 +01:00
bool usingFinalShader = false;
const bool CRASHING = m_applyFinalShader && g_pHyprRenderer->m_crashingInProgress;
2022-12-01 13:36:07 +00:00
auto texType = tex->m_type;
2023-04-04 14:49:58 +01:00
if (CRASHING) {
shader = &m_shaders->m_shGLITCH;
2023-04-04 14:49:58 +01:00
usingFinalShader = true;
} else if (m_applyFinalShader && m_finalScreenShader.program) {
shader = &m_finalScreenShader;
2022-12-01 13:36:07 +00:00
usingFinalShader = true;
} else {
if (m_applyFinalShader) {
shader = &m_shaders->m_shPASSTHRURGBA;
usingFinalShader = true;
} else {
switch (tex->m_type) {
case TEXTURE_RGBA: shader = &m_shaders->m_shRGBA; break;
case TEXTURE_RGBX: shader = &m_shaders->m_shRGBX; break;
case TEXTURE_EXTERNAL: shader = &m_shaders->m_shEXT; break; // might be unused
default: RASSERT(false, "tex->m_iTarget unsupported!");
}
2022-12-01 13:36:07 +00:00
}
2022-04-05 14:33:54 +02:00
}
if (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.RGBX.valueOrDefault()) {
shader = &m_shaders->m_shRGBX;
texType = TEXTURE_RGBX;
}
2023-03-26 02:00:24 +01:00
2022-04-05 14:33:54 +02:00
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex->m_target, tex->m_texID);
2022-04-05 14:33:54 +02:00
glTexParameteri(tex->m_target, GL_TEXTURE_WRAP_S, wrapX);
glTexParameteri(tex->m_target, GL_TEXTURE_WRAP_T, wrapY);
if (m_renderData.useNearestNeighbor) {
glTexParameteri(tex->m_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(tex->m_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
2023-04-16 14:48:38 +01:00
} else {
glTexParameteri(tex->m_target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(tex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2023-04-16 14:48:38 +01:00
}
2022-04-05 14:33:54 +02:00
const auto imageDescription =
m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? m_renderData.surface->m_colorManagement->imageDescription() : SImageDescription{};
const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */
|| (imageDescription == m_renderData.pMonitor->m_imageDescription) /* Source and target have the same image description */
|| ((*PPASS == 1 || (*PPASS == 2 && imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ)) && m_renderData.pMonitor->m_activeWorkspace &&
m_renderData.pMonitor->m_activeWorkspace->m_hasFullscreenWindow &&
m_renderData.pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) /* Fullscreen window with pass cm enabled */;
if (!skipCM && !usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX))
shader = &m_shaders->m_shCM;
useProgram(shader->program);
2022-04-05 14:33:54 +02:00
if (shader == &m_shaders->m_shCM) {
glUniform1i(shader->texType, texType);
passCMUniforms(*shader, imageDescription);
}
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
2022-04-05 14:33:54 +02:00
glUniform1i(shader->tex, 0);
if ((usingFinalShader && *PDT == 0) || CRASHING) {
glUniform1f(shader->time, m_globalTimer.getSeconds() - shader->initialTime);
} else if (usingFinalShader && shader->time != -1) {
// Don't let time be unitialised
glUniform1f(shader->time, 0.f);
}
if (usingFinalShader && shader->wl_output != -1)
glUniform1i(shader->wl_output, m_renderData.pMonitor->m_id);
if (usingFinalShader && shader->fullSize != -1)
glUniform2f(shader->fullSize, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);
2023-06-10 16:10:26 +02:00
2023-04-04 14:49:58 +01:00
if (CRASHING) {
glUniform1f(shader->distort, g_pHyprRenderer->m_crashingDistort);
glUniform2f(shader->fullSize, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);
2023-04-04 14:49:58 +01:00
}
2022-12-01 13:36:07 +00:00
if (!usingFinalShader) {
glUniform1f(shader->alpha, alpha);
2023-03-17 23:16:13 +00:00
if (discardActive) {
glUniform1i(shader->discardOpaque, !!(m_renderData.discardMode & DISCARD_OPAQUE));
glUniform1i(shader->discardAlpha, !!(m_renderData.discardMode & DISCARD_ALPHA));
glUniform1f(shader->discardAlphaValue, m_renderData.discardOpacity);
2023-03-17 23:16:13 +00:00
} else {
glUniform1i(shader->discardOpaque, 0);
glUniform1i(shader->discardAlpha, 0);
2023-03-17 23:16:13 +00:00
}
2022-12-01 13:36:07 +00:00
}
2022-04-05 14:33:54 +02:00
CBox transformedBox = newBox;
transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
2022-04-05 15:50:47 +02:00
const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y);
const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);
2022-04-05 15:50:47 +02:00
2022-12-01 13:36:07 +00:00
if (!usingFinalShader) {
// Rounded corners
glUniform2f(shader->topLeft, TOPLEFT.x, TOPLEFT.y);
glUniform2f(shader->fullSize, FULLSIZE.x, FULLSIZE.y);
glUniform1f(shader->radius, round);
glUniform1f(shader->roundingPower, roundingPower);
2022-12-01 13:36:07 +00:00
if (allowDim && m_renderData.currentWindow) {
if (m_renderData.currentWindow->m_notRespondingTint->value() > 0) {
const auto DIM = m_renderData.currentWindow->m_notRespondingTint->value();
glUniform1i(shader->applyTint, 1);
glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM);
} else if (m_renderData.currentWindow->m_dimPercent->value() > 0) {
glUniform1i(shader->applyTint, 1);
const auto DIM = m_renderData.currentWindow->m_dimPercent->value();
glUniform3f(shader->tint, 1.f - DIM, 1.f - DIM, 1.f - DIM);
} else
glUniform1i(shader->applyTint, 0);
} else
2022-12-01 13:36:07 +00:00
glUniform1i(shader->applyTint, 0);
2022-08-30 12:46:17 +02:00
}
glBindVertexArray(shader->shaderVao);
if (allowCustomUV && m_renderData.primarySurfaceUVTopLeft != Vector2D(-1, -1)) {
const float customUVs[] = {
m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVTopLeft.x,
m_renderData.primarySurfaceUVTopLeft.y, m_renderData.primarySurfaceUVBottomRight.x, m_renderData.primarySurfaceUVBottomRight.y,
m_renderData.primarySurfaceUVTopLeft.x, m_renderData.primarySurfaceUVBottomRight.y,
};
2022-04-05 14:33:54 +02:00
glBindBuffer(GL_ARRAY_BUFFER, shader->shaderVboUv);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(customUVs), customUVs);
} else {
glBindBuffer(GL_ARRAY_BUFFER, shader->shaderVboUv);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(fullVerts), fullVerts);
}
2022-04-05 14:33:54 +02:00
if (!m_renderData.clipBox.empty() || !m_renderData.clipRegion.empty()) {
CRegion damageClip = m_renderData.clipBox;
if (!m_renderData.clipRegion.empty()) {
if (m_renderData.clipBox.empty())
damageClip = m_renderData.clipRegion;
else
damageClip.intersect(m_renderData.clipRegion);
}
2023-07-19 20:09:49 +02:00
if (!damageClip.empty()) {
for (auto const& RECT : damageClip.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
} else {
for (auto const& RECT : damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
2022-05-10 09:56:58 +02:00
}
2022-04-05 14:33:54 +02:00
glBindVertexArray(0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindTexture(tex->m_target, 0);
2022-04-05 16:47:03 +02:00
}
void CHyprOpenGLImpl::renderTexturePrimitive(SP<CTexture> tex, const CBox& box) {
RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!");
RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!");
TRACY_GPU_ZONE("RenderTexturePrimitive");
if (m_renderData.damage.empty())
return;
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
// get transform
const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform));
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
SShader* shader = &m_shaders->m_shPASSTHRURGBA;
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex->m_target, tex->m_texID);
useProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1i(shader->tex, 0);
glBindVertexArray(shader->shaderVao);
for (auto const& RECT : m_renderData.damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
scissor(nullptr);
glBindVertexArray(0);
glBindTexture(tex->m_target, 0);
}
void CHyprOpenGLImpl::renderTextureMatte(SP<CTexture> tex, const CBox& box, CFramebuffer& matte) {
RASSERT(m_renderData.pMonitor, "Tried to render texture without begin()!");
RASSERT((tex->m_texID > 0), "Attempted to draw nullptr texture!");
2023-11-04 19:32:50 +00:00
TRACY_GPU_ZONE("RenderTextureMatte");
if (m_renderData.damage.empty())
2023-11-04 19:32:50 +00:00
return;
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
2023-11-04 19:32:50 +00:00
// get transform
const auto TRANSFORM = wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform));
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(newBox, TRANSFORM, newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2023-11-04 19:32:50 +00:00
SShader* shader = &m_shaders->m_shMATTE;
2023-11-04 19:32:50 +00:00
useProgram(shader->program);
glUniformMatrix3fv(shader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
2023-11-04 19:32:50 +00:00
glUniform1i(shader->tex, 0);
glUniform1i(shader->alphaMatte, 1);
glActiveTexture(GL_TEXTURE0);
glBindTexture(tex->m_target, tex->m_texID);
2023-11-04 19:32:50 +00:00
glActiveTexture(GL_TEXTURE0 + 1);
auto matteTex = matte.getTexture();
glBindTexture(matteTex->m_target, matteTex->m_texID);
2023-11-04 19:32:50 +00:00
glBindVertexArray(shader->shaderVao);
2023-11-04 19:32:50 +00:00
for (auto const& RECT : m_renderData.damage.getRects()) {
2023-11-04 19:32:50 +00:00
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
scissor(nullptr);
2023-11-04 19:32:50 +00:00
glBindVertexArray(0);
glBindTexture(tex->m_target, 0);
2023-11-04 19:32:50 +00:00
}
2022-04-24 16:41:01 +02:00
// This probably isn't the fastest
// but it works... well, I guess?
//
// Dual (or more) kawase blur
CFramebuffer* CHyprOpenGLImpl::blurMainFramebufferWithDamage(float a, CRegion* originalDamage) {
if (!m_renderData.currentFB->getTexture()) {
Debug::log(ERR, "BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)");
return &m_renderData.pCurrentMonData->mirrorFB; // return something to sample from at least
}
return blurFramebufferWithDamage(a, originalDamage, *m_renderData.currentFB);
}
CFramebuffer* CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* originalDamage, CFramebuffer& source) {
TRACY_GPU_ZONE("RenderBlurFramebufferWithDamage");
2023-07-20 17:51:38 +02:00
const auto BLENDBEFORE = m_blend;
blend(false);
glDisable(GL_STENCIL_TEST);
2022-04-24 16:41:01 +02:00
// get transforms for the full monitor
const auto TRANSFORM = wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform));
CBox MONITORBOX = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(MONITORBOX, TRANSFORM);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2022-04-09 16:51:08 +02:00
// get the config settings
static auto PBLURSIZE = CConfigValue<Hyprlang::INT>("decoration:blur:size");
static auto PBLURPASSES = CConfigValue<Hyprlang::INT>("decoration:blur:passes");
static auto PBLURVIBRANCY = CConfigValue<Hyprlang::FLOAT>("decoration:blur:vibrancy");
static auto PBLURVIBRANCYDARKNESS = CConfigValue<Hyprlang::FLOAT>("decoration:blur:vibrancy_darkness");
2022-04-09 16:51:08 +02:00
2022-05-09 21:37:41 +02:00
// prep damage
2023-07-19 20:09:49 +02:00
CRegion damage{*originalDamage};
damage.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
damage.expand(*PBLURPASSES > 10 ? pow(2, 15) : std::clamp(*PBLURSIZE, (int64_t)1, (int64_t)40) * pow(2, *PBLURPASSES));
2022-09-25 20:07:48 +02:00
// helper
const auto PMIRRORFB = &m_renderData.pCurrentMonData->mirrorFB;
const auto PMIRRORSWAPFB = &m_renderData.pCurrentMonData->mirrorSwapFB;
CFramebuffer* currentRenderToFB = PMIRRORFB;
// Begin with base color adjustments - global brightness and contrast
// TODO: make this a part of the first pass maybe to save on a drawcall?
{
static auto PBLURCONTRAST = CConfigValue<Hyprlang::FLOAT>("decoration:blur:contrast");
static auto PBLURBRIGHTNESS = CConfigValue<Hyprlang::FLOAT>("decoration:blur:brightness");
PMIRRORSWAPFB->bind();
glActiveTexture(GL_TEXTURE0);
auto currentTex = source.getTexture();
glBindTexture(currentTex->m_target, currentTex->m_texID);
glTexParameteri(currentTex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
useProgram(m_shaders->m_shBLURPREPARE.program);
// From FB to sRGB
const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{};
glUniform1i(m_shaders->m_shBLURPREPARE.skipCM, skipCM);
if (!skipCM) {
passCMUniforms(m_shaders->m_shBLURPREPARE, m_renderData.pMonitor->m_imageDescription, SImageDescription{});
glUniform1f(m_shaders->m_shBLURPREPARE.sdrSaturation,
m_renderData.pMonitor->m_sdrSaturation > 0 &&
m_renderData.pMonitor->m_imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrSaturation :
1.0f);
glUniform1f(m_shaders->m_shBLURPREPARE.sdrBrightness,
m_renderData.pMonitor->m_sdrBrightness > 0 &&
m_renderData.pMonitor->m_imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ?
m_renderData.pMonitor->m_sdrBrightness :
1.0f);
}
glUniformMatrix3fv(m_shaders->m_shBLURPREPARE.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(m_shaders->m_shBLURPREPARE.contrast, *PBLURCONTRAST);
glUniform1f(m_shaders->m_shBLURPREPARE.brightness, *PBLURBRIGHTNESS);
glUniform1i(m_shaders->m_shBLURPREPARE.tex, 0);
glBindVertexArray(m_shaders->m_shBLURPREPARE.shaderVao);
if (!damage.empty()) {
for (auto const& RECT : damage.getRects()) {
scissor(&RECT, false /* this region is already transformed */);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
glBindVertexArray(0);
currentRenderToFB = PMIRRORSWAPFB;
}
// declare the draw func
auto drawPass = [&](SShader* pShader, CRegion* pDamage) {
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
2022-04-09 16:51:08 +02:00
glActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
glBindTexture(currentTex->m_target, currentTex->m_texID);
glTexParameteri(currentTex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
2022-04-09 16:51:08 +02:00
useProgram(pShader->program);
2022-04-09 16:51:08 +02:00
// prep two shaders
glUniformMatrix3fv(pShader->proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(pShader->radius, *PBLURSIZE * a); // this makes the blursize change with a
if (pShader == &m_shaders->m_shBLUR1) {
glUniform2f(m_shaders->m_shBLUR1.halfpixel, 0.5f / (m_renderData.pMonitor->m_pixelSize.x / 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y / 2.f));
glUniform1i(m_shaders->m_shBLUR1.passes, *PBLURPASSES);
glUniform1f(m_shaders->m_shBLUR1.vibrancy, *PBLURVIBRANCY);
glUniform1f(m_shaders->m_shBLUR1.vibrancy_darkness, *PBLURVIBRANCYDARKNESS);
} else
glUniform2f(m_shaders->m_shBLUR2.halfpixel, 0.5f / (m_renderData.pMonitor->m_pixelSize.x * 2.f), 0.5f / (m_renderData.pMonitor->m_pixelSize.y * 2.f));
2022-04-09 16:51:08 +02:00
glUniform1i(pShader->tex, 0);
glBindVertexArray(pShader->shaderVao);
2023-07-19 20:09:49 +02:00
if (!pDamage->empty()) {
for (auto const& RECT : pDamage->getRects()) {
2022-09-04 19:27:38 +02:00
scissor(&RECT, false /* this region is already transformed */);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
2022-04-09 16:51:08 +02:00
glBindVertexArray(0);
2022-04-09 16:51:08 +02:00
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
};
2022-04-24 16:41:01 +02:00
// draw the things.
// first draw is swap -> mirr
PMIRRORFB->bind();
glBindTexture(PMIRRORSWAPFB->getTexture()->m_target, PMIRRORSWAPFB->getTexture()->m_texID);
2022-04-24 16:41:01 +02:00
// damage region will be scaled, make a temp
2023-07-19 20:09:49 +02:00
CRegion tempDamage{damage};
2022-04-24 16:41:01 +02:00
// and draw
for (auto i = 1; i <= *PBLURPASSES; ++i) {
tempDamage = damage.copy().scale(1.f / (1 << i));
drawPass(&m_shaders->m_shBLUR1, &tempDamage); // down
}
2022-04-24 16:41:01 +02:00
for (auto i = *PBLURPASSES - 1; i >= 0; --i) {
tempDamage = damage.copy().scale(1.f / (1 << i)); // when upsampling we make the region twice as big
drawPass(&m_shaders->m_shBLUR2, &tempDamage); // up
2022-04-09 17:06:09 +02:00
}
2022-04-09 16:51:08 +02:00
// finalize the image
{
static auto PBLURNOISE = CConfigValue<Hyprlang::FLOAT>("decoration:blur:noise");
static auto PBLURBRIGHTNESS = CConfigValue<Hyprlang::FLOAT>("decoration:blur:brightness");
if (currentRenderToFB == PMIRRORFB)
PMIRRORSWAPFB->bind();
else
PMIRRORFB->bind();
glActiveTexture(GL_TEXTURE0);
auto currentTex = currentRenderToFB->getTexture();
glBindTexture(currentTex->m_target, currentTex->m_texID);
glTexParameteri(currentTex->m_target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
useProgram(m_shaders->m_shBLURFINISH.program);
glUniformMatrix3fv(m_shaders->m_shBLURFINISH.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform1f(m_shaders->m_shBLURFINISH.noise, *PBLURNOISE);
glUniform1f(m_shaders->m_shBLURFINISH.brightness, *PBLURBRIGHTNESS);
glUniform1i(m_shaders->m_shBLURFINISH.tex, 0);
glBindVertexArray(m_shaders->m_shBLURFINISH.shaderVao);
if (!damage.empty()) {
for (auto const& RECT : damage.getRects()) {
scissor(&RECT, false /* this region is already transformed */);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
glBindVertexArray(0);
if (currentRenderToFB != PMIRRORFB)
currentRenderToFB = PMIRRORFB;
else
currentRenderToFB = PMIRRORSWAPFB;
}
// finish
glBindTexture(PMIRRORFB->getTexture()->m_target, 0);
blend(BLENDBEFORE);
return currentRenderToFB;
}
void CHyprOpenGLImpl::markBlurDirtyForMonitor(PHLMONITOR pMonitor) {
m_monitorRenderResources[pMonitor].blurFBDirty = true;
}
void CHyprOpenGLImpl::preRender(PHLMONITOR pMonitor) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>("decoration:blur:new_optimizations");
static auto PBLURXRAY = CConfigValue<Hyprlang::INT>("decoration:blur:xray");
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
if (!*PBLURNEWOPTIMIZE || !m_monitorRenderResources[pMonitor].blurFBDirty || !*PBLUR)
return;
// ignore if solitary present, nothing to blur
if (!pMonitor->m_solitaryClient.expired())
return;
// check if we need to update the blur fb
// if there are no windows that would benefit from it,
// we will ignore that the blur FB is dirty.
2022-08-01 12:23:09 +02:00
auto windowShouldBeBlurred = [&](PHLWINDOW pWindow) -> bool {
if (!pWindow)
return false;
if (pWindow->m_windowData.noBlur.valueOrDefault())
return false;
if (pWindow->m_wlSurface->small() && !pWindow->m_wlSurface->m_fillIgnoreSmall)
return true;
const auto PSURFACE = pWindow->m_wlSurface->resource();
const auto PWORKSPACE = pWindow->m_workspace;
const float A = pWindow->m_alpha->value() * pWindow->m_activeInactiveAlpha->value() * PWORKSPACE->m_alpha->value();
if (A >= 1.f) {
// if (PSURFACE->opaque)
// return false;
2023-07-19 20:09:49 +02:00
CRegion inverseOpaque;
pixman_box32_t surfbox = {0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y};
CRegion opaqueRegion{PSURFACE->m_current.opaque};
inverseOpaque.set(opaqueRegion).invert(&surfbox).intersect(0, 0, PSURFACE->m_current.size.x, PSURFACE->m_current.size.y);
2023-07-19 20:09:49 +02:00
if (inverseOpaque.empty())
return false;
}
return true;
};
2022-08-01 12:23:09 +02:00
bool hasWindows = false;
for (auto const& w : g_pCompositor->m_windows) {
if (w->m_workspace == pMonitor->m_activeWorkspace && !w->isHidden() && w->m_isMapped && (!w->m_isFloating || *PBLURXRAY)) {
// check if window is valid
if (!windowShouldBeBlurred(w))
continue;
2022-08-01 12:23:09 +02:00
hasWindows = true;
2022-09-25 20:07:48 +02:00
break;
2022-08-01 12:23:09 +02:00
}
}
for (auto const& m : g_pCompositor->m_monitors) {
for (auto const& lsl : m->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
if (!ls->m_layerSurface || ls->m_xray != 1)
2023-08-09 22:03:24 +02:00
continue;
// if (ls->layerSurface->surface->opaque && ls->alpha->value() >= 1.f)
// continue;
2023-08-09 22:03:24 +02:00
hasWindows = true;
break;
}
}
}
2022-08-01 12:23:09 +02:00
if (!hasWindows)
return;
g_pHyprRenderer->damageMonitor(pMonitor);
m_monitorRenderResources[pMonitor].blurFBShouldRender = true;
}
void CHyprOpenGLImpl::preBlurForCurrentMonitor() {
2023-07-20 17:51:38 +02:00
TRACY_GPU_ZONE("RenderPreBlurForCurrentMonitor");
const auto SAVEDRENDERMODIF = m_renderData.renderModif;
m_renderData.renderModif = {}; // fix shit
// make the fake dmg
CRegion fakeDamage{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
const auto POUTFB = blurMainFramebufferWithDamage(1, &fakeDamage);
// render onto blurFB
m_renderData.pCurrentMonData->blurFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y,
m_renderData.pMonitor->m_output->state->state().drmFormat);
m_renderData.pCurrentMonData->blurFB.bind();
clear(CHyprColor(0, 0, 0, 0));
m_endFrame = true; // fix transformed
renderTextureInternalWithDamage(POUTFB->getTexture(), CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y}, 1, fakeDamage, 0,
2.0f, false, true, false);
m_endFrame = false;
m_renderData.currentFB->bind();
m_renderData.pCurrentMonData->blurFBDirty = false;
m_renderData.renderModif = SAVEDRENDERMODIF;
m_monitorRenderResources[m_renderData.pMonitor].blurFBShouldRender = false;
}
void CHyprOpenGLImpl::preWindowPass() {
if (!preBlurQueued())
return;
g_pHyprRenderer->m_renderPass.add(makeShared<CPreBlurElement>());
2022-08-01 12:23:09 +02:00
}
bool CHyprOpenGLImpl::preBlurQueued() {
static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>("decoration:blur:new_optimizations");
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
return m_renderData.pCurrentMonData->blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pCurrentMonData->blurFBShouldRender;
}
bool CHyprOpenGLImpl::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>("decoration:blur:new_optimizations");
static auto PBLURXRAY = CConfigValue<Hyprlang::INT>("decoration:blur:xray");
2023-08-09 22:03:24 +02:00
if (!m_renderData.pCurrentMonData->blurFB.getTexture())
2023-08-09 22:03:24 +02:00
return false;
if (pWindow && pWindow->m_windowData.xray.hasValue() && !pWindow->m_windowData.xray.valueOrDefault())
2023-08-09 22:03:24 +02:00
return false;
if (pLayer && pLayer->m_xray == 0)
2023-08-09 22:03:24 +02:00
return false;
if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY)
2023-08-09 22:03:24 +02:00
return true;
if ((pLayer && pLayer->m_xray == 1) || (pWindow && pWindow->m_windowData.xray.valueOrDefault()))
2023-08-09 22:03:24 +02:00
return true;
return false;
}
void CHyprOpenGLImpl::renderTextureWithBlur(SP<CTexture> tex, const CBox& box, float a, SP<CWLSurfaceResource> pSurface, int round, float roundingPower, bool blockBlurOptimization,
float blurA, float overallA) {
RASSERT(m_renderData.pMonitor, "Tried to render texture with blur without begin()!");
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderTextureWithBlur");
2022-05-02 23:03:22 +02:00
// make a damage region for this window
CRegion texDamage{m_renderData.damage};
texDamage.intersect(box.x, box.y, box.width, box.height);
2022-05-02 23:03:22 +02:00
// While renderTextureInternalWithDamage will clip the blur as well,
// clipping texDamage here allows blur generation to be optimized.
if (!m_renderData.clipRegion.empty())
texDamage.intersect(m_renderData.clipRegion);
2023-07-19 20:09:49 +02:00
if (texDamage.empty())
return;
m_renderData.renderModif.applyToRegion(texDamage);
2024-04-03 14:09:58 +01:00
2022-05-09 21:37:41 +02:00
// amazing hack: the surface has an opaque region!
2023-07-19 20:09:49 +02:00
CRegion inverseOpaque;
if (a >= 1.f && pSurface && std::round(pSurface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w &&
std::round(pSurface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) {
pixman_box32_t surfbox = {0, 0, pSurface->m_current.size.x * pSurface->m_current.scale, pSurface->m_current.size.y * pSurface->m_current.scale};
inverseOpaque = pSurface->m_current.opaque;
inverseOpaque.invert(&surfbox).intersect(0, 0, pSurface->m_current.size.x * pSurface->m_current.scale, pSurface->m_current.size.y * pSurface->m_current.scale);
2022-05-09 21:37:41 +02:00
2023-07-19 20:09:49 +02:00
if (inverseOpaque.empty()) {
renderTexture(tex, box, a, round, roundingPower, false, true);
return;
}
} else
inverseOpaque = {0, 0, box.width, box.height};
inverseOpaque.scale(m_renderData.pMonitor->m_scale);
2023-01-31 12:29:23 +00:00
2023-08-09 22:03:24 +02:00
// vvv TODO: layered blur fbs?
const bool USENEWOPTIMIZE = shouldUseNewBlurOptimizations(m_renderData.currentLS.lock(), m_renderData.currentWindow.lock()) && !blockBlurOptimization;
2022-08-01 15:32:20 +02:00
CFramebuffer* POUTFB = nullptr;
if (!USENEWOPTIMIZE) {
inverseOpaque.translate(box.pos());
m_renderData.renderModif.applyToRegion(inverseOpaque);
2024-04-03 14:09:58 +01:00
inverseOpaque.intersect(texDamage);
POUTFB = blurMainFramebufferWithDamage(a, &inverseOpaque);
} else
POUTFB = &m_renderData.pCurrentMonData->blurFB;
2022-05-10 09:19:54 +02:00
m_renderData.currentFB->bind();
2022-08-01 15:29:49 +02:00
// make a stencil for rounded corners to work with blur
scissor(nullptr); // allow the entire window and stencil to render
2022-08-01 15:29:49 +02:00
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
2022-04-24 16:41:01 +02:00
2022-08-01 15:29:49 +02:00
glEnable(GL_STENCIL_TEST);
2022-04-24 16:41:01 +02:00
glStencilFunc(GL_ALWAYS, 1, 0xFF);
2022-08-01 15:29:49 +02:00
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
2022-04-24 16:41:01 +02:00
2022-08-01 15:29:49 +02:00
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
if (USENEWOPTIMIZE && !(m_renderData.discardMode & DISCARD_ALPHA))
renderRect(box, CHyprColor(0, 0, 0, 0), round, roundingPower);
2022-08-01 15:32:20 +02:00
else
renderTexture(tex, box, a, round, roundingPower, true, true); // discard opaque
2022-08-01 15:29:49 +02:00
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
2022-04-24 16:41:01 +02:00
glStencilFunc(GL_EQUAL, 1, 0xFF);
2022-08-01 15:29:49 +02:00
glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
2022-04-24 16:41:01 +02:00
// stencil done. Render everything.
const auto LASTTL = m_renderData.primarySurfaceUVTopLeft;
const auto LASTBR = m_renderData.primarySurfaceUVBottomRight;
CBox transformedBox = box;
transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
CBox monitorSpaceBox = {transformedBox.pos().x / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x,
transformedBox.pos().y / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y,
transformedBox.width / m_renderData.pMonitor->m_pixelSize.x * m_renderData.pMonitor->m_transformedSize.x,
transformedBox.height / m_renderData.pMonitor->m_pixelSize.y * m_renderData.pMonitor->m_transformedSize.y};
m_renderData.primarySurfaceUVTopLeft = monitorSpaceBox.pos() / m_renderData.pMonitor->m_transformedSize;
m_renderData.primarySurfaceUVBottomRight = (monitorSpaceBox.pos() + monitorSpaceBox.size()) / m_renderData.pMonitor->m_transformedSize;
static auto PBLURIGNOREOPACITY = CConfigValue<Hyprlang::INT>("decoration:blur:ignore_opacity");
2024-04-03 14:09:58 +01:00
setMonitorTransformEnabled(true);
if (!USENEWOPTIMIZE)
setRenderModifEnabled(false);
renderTextureInternalWithDamage(POUTFB->getTexture(), box, (*PBLURIGNOREOPACITY ? blurA : a * blurA) * overallA, texDamage, round, roundingPower, false, false, true);
if (!USENEWOPTIMIZE)
setRenderModifEnabled(true);
2024-04-03 14:09:58 +01:00
setMonitorTransformEnabled(false);
2022-05-28 18:57:32 +02:00
m_renderData.primarySurfaceUVTopLeft = LASTTL;
m_renderData.primarySurfaceUVBottomRight = LASTBR;
// render the window, but clear stencil
glClearStencil(0);
glClear(GL_STENCIL_BUFFER_BIT);
2022-05-17 13:16:37 +02:00
// draw window
glDisable(GL_STENCIL_TEST);
renderTextureInternalWithDamage(tex, box, a * overallA, texDamage, round, roundingPower, false, false, true, true);
2022-08-01 15:29:49 +02:00
glStencilMask(0xFF);
2022-08-01 15:29:49 +02:00
glStencilFunc(GL_ALWAYS, 1, 0xFF);
scissor(nullptr);
2022-04-09 16:51:08 +02:00
}
void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad, int round, float roundingPower, int borderSize, float a, int outerRound) {
RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!");
RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!");
2022-04-05 16:47:03 +02:00
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderBorder");
if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault()))
return;
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
if (borderSize < 1)
2022-08-05 22:21:14 +02:00
return;
int scaledBorderSize = std::round(borderSize * m_renderData.pMonitor->m_scale);
scaledBorderSize = std::round(scaledBorderSize * m_renderData.renderModif.combinedScale());
2022-06-26 19:39:56 +02:00
// adjust box
newBox.x -= scaledBorderSize;
newBox.y -= scaledBorderSize;
newBox.width += 2 * scaledBorderSize;
newBox.height += 2 * scaledBorderSize;
2022-06-26 19:39:56 +02:00
2022-11-26 17:56:43 +00:00
round += round == 0 ? 0 : scaledBorderSize;
2022-06-26 19:39:56 +02:00
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(
newBox, wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2022-06-26 19:39:56 +02:00
const auto BLEND = m_blend;
blend(true);
2022-06-26 19:39:56 +02:00
useProgram(m_shaders->m_shBORDER1.program);
const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{};
glUniform1i(m_shaders->m_shBORDER1.skipCM, skipCM);
if (!skipCM)
passCMUniforms(m_shaders->m_shBORDER1, SImageDescription{});
2022-04-05 16:47:03 +02:00
glUniformMatrix3fv(m_shaders->m_shBORDER1.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4fv(m_shaders->m_shBORDER1.gradient, grad.m_colorsOkLabA.size() / 4, (float*)grad.m_colorsOkLabA.data());
glUniform1i(m_shaders->m_shBORDER1.gradientLength, grad.m_colorsOkLabA.size() / 4);
glUniform1f(m_shaders->m_shBORDER1.angle, (int)(grad.m_angle / (PI / 180.0)) % 360 * (PI / 180.0));
glUniform1f(m_shaders->m_shBORDER1.alpha, a);
glUniform1i(m_shaders->m_shBORDER1.gradient2Length, 0);
2025-01-26 15:19:42 +00:00
CBox transformedBox = newBox;
transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y);
const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);
glUniform2f(m_shaders->m_shBORDER1.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(m_shaders->m_shBORDER1.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform2f(m_shaders->m_shBORDER1.fullSizeUntransformed, (float)newBox.width, (float)newBox.height);
glUniform1f(m_shaders->m_shBORDER1.radius, round);
glUniform1f(m_shaders->m_shBORDER1.radiusOuter, outerRound == -1 ? round : outerRound);
glUniform1f(m_shaders->m_shBORDER1.roundingPower, roundingPower);
glUniform1f(m_shaders->m_shBORDER1.thick, scaledBorderSize);
glBindVertexArray(m_shaders->m_shBORDER1.shaderVao);
if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) {
CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height};
damageClip.intersect(m_renderData.damage);
if (!damageClip.empty()) {
for (auto const& RECT : damageClip.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
} else {
for (auto const& RECT : m_renderData.damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
glBindVertexArray(0);
blend(BLEND);
}
void CHyprOpenGLImpl::renderBorder(const CBox& box, const CGradientValueData& grad1, const CGradientValueData& grad2, float lerp, int round, float roundingPower, int borderSize,
float a, int outerRound) {
RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!");
RASSERT(m_renderData.pMonitor, "Tried to render rect without begin()!");
TRACY_GPU_ZONE("RenderBorder2");
if (m_renderData.damage.empty() || (m_renderData.currentWindow && m_renderData.currentWindow->m_windowData.noBorder.valueOrDefault()))
return;
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
if (borderSize < 1)
return;
int scaledBorderSize = std::round(borderSize * m_renderData.pMonitor->m_scale);
scaledBorderSize = std::round(scaledBorderSize * m_renderData.renderModif.combinedScale());
// adjust box
newBox.x -= scaledBorderSize;
newBox.y -= scaledBorderSize;
newBox.width += 2 * scaledBorderSize;
newBox.height += 2 * scaledBorderSize;
round += round == 0 ? 0 : scaledBorderSize;
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(
newBox, wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
const auto BLEND = m_blend;
blend(true);
useProgram(m_shaders->m_shBORDER1.program);
glUniformMatrix3fv(m_shaders->m_shBORDER1.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4fv(m_shaders->m_shBORDER1.gradient, grad1.m_colorsOkLabA.size() / 4, (float*)grad1.m_colorsOkLabA.data());
glUniform1i(m_shaders->m_shBORDER1.gradientLength, grad1.m_colorsOkLabA.size() / 4);
glUniform1f(m_shaders->m_shBORDER1.angle, (int)(grad1.m_angle / (PI / 180.0)) % 360 * (PI / 180.0));
if (grad2.m_colorsOkLabA.size() > 0)
glUniform4fv(m_shaders->m_shBORDER1.gradient2, grad2.m_colorsOkLabA.size() / 4, (float*)grad2.m_colorsOkLabA.data());
glUniform1i(m_shaders->m_shBORDER1.gradient2Length, grad2.m_colorsOkLabA.size() / 4);
glUniform1f(m_shaders->m_shBORDER1.angle2, (int)(grad2.m_angle / (PI / 180.0)) % 360 * (PI / 180.0));
glUniform1f(m_shaders->m_shBORDER1.alpha, a);
glUniform1f(m_shaders->m_shBORDER1.gradientLerp, lerp);
2022-04-05 16:47:03 +02:00
2025-01-26 15:19:42 +00:00
CBox transformedBox = newBox;
transformedBox.transform(wlTransformToHyprutils(invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
2022-11-21 18:09:47 +00:00
const auto TOPLEFT = Vector2D(transformedBox.x, transformedBox.y);
2022-11-21 18:09:47 +00:00
const auto FULLSIZE = Vector2D(transformedBox.width, transformedBox.height);
2022-04-05 16:47:03 +02:00
glUniform2f(m_shaders->m_shBORDER1.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(m_shaders->m_shBORDER1.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform2f(m_shaders->m_shBORDER1.fullSizeUntransformed, (float)newBox.width, (float)newBox.height);
glUniform1f(m_shaders->m_shBORDER1.radius, round);
glUniform1f(m_shaders->m_shBORDER1.radiusOuter, outerRound == -1 ? round : outerRound);
glUniform1f(m_shaders->m_shBORDER1.roundingPower, roundingPower);
glUniform1f(m_shaders->m_shBORDER1.thick, scaledBorderSize);
2022-06-26 19:39:56 +02:00
glBindVertexArray(m_shaders->m_shBORDER1.shaderVao);
2022-06-26 19:39:56 +02:00
if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) {
CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height};
damageClip.intersect(m_renderData.damage);
2023-07-19 20:09:49 +02:00
if (!damageClip.empty()) {
for (auto const& RECT : damageClip.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
} else {
for (auto const& RECT : m_renderData.damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
2022-06-26 19:39:56 +02:00
}
glBindVertexArray(0);
blend(BLEND);
2022-04-05 20:49:15 +02:00
}
void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roundingPower, int range, const CHyprColor& color, float a) {
RASSERT(m_renderData.pMonitor, "Tried to render shadow without begin()!");
RASSERT((box.width > 0 && box.height > 0), "Tried to render shadow with width/height < 0!");
RASSERT(m_renderData.currentWindow, "Tried to render shadow without a window!");
2022-06-25 20:28:40 +02:00
if (m_renderData.damage.empty())
return;
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderShadow");
CBox newBox = box;
m_renderData.renderModif.applyToBox(newBox);
static auto PSHADOWPOWER = CConfigValue<Hyprlang::INT>("decoration:shadow:render_power");
2022-06-25 20:28:40 +02:00
const auto SHADOWPOWER = std::clamp((int)*PSHADOWPOWER, 1, 4);
2022-06-25 20:28:40 +02:00
const auto col = color;
2022-06-25 20:28:40 +02:00
Mat3x3 matrix = m_renderData.monitorProjection.projectBox(
newBox, wlTransformToHyprutils(invertTransform(!m_endFrame ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform)), newBox.rot);
Mat3x3 glMatrix = m_renderData.projection.copy().multiply(matrix);
2022-06-25 20:28:40 +02:00
blend(true);
2022-06-25 20:28:40 +02:00
useProgram(m_shaders->m_shSHADOW.program);
const bool skipCM = !m_cmSupported || m_renderData.pMonitor->m_imageDescription == SImageDescription{};
glUniform1i(m_shaders->m_shSHADOW.skipCM, skipCM);
if (!skipCM)
passCMUniforms(m_shaders->m_shSHADOW, SImageDescription{});
2022-06-25 20:28:40 +02:00
glUniformMatrix3fv(m_shaders->m_shSHADOW.proj, 1, GL_TRUE, glMatrix.getMatrix().data());
glUniform4f(m_shaders->m_shSHADOW.color, col.r, col.g, col.b, col.a * a);
2022-06-25 20:28:40 +02:00
const auto TOPLEFT = Vector2D(range + round, range + round);
const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round));
const auto FULLSIZE = Vector2D(newBox.width, newBox.height);
2022-06-25 20:28:40 +02:00
// Rounded corners
glUniform2f(m_shaders->m_shSHADOW.topLeft, (float)TOPLEFT.x, (float)TOPLEFT.y);
glUniform2f(m_shaders->m_shSHADOW.bottomRight, (float)BOTTOMRIGHT.x, (float)BOTTOMRIGHT.y);
glUniform2f(m_shaders->m_shSHADOW.fullSize, (float)FULLSIZE.x, (float)FULLSIZE.y);
glUniform1f(m_shaders->m_shSHADOW.radius, range + round);
glUniform1f(m_shaders->m_shSHADOW.roundingPower, roundingPower);
glUniform1f(m_shaders->m_shSHADOW.range, range);
glUniform1f(m_shaders->m_shSHADOW.shadowPower, SHADOWPOWER);
2022-06-25 20:28:40 +02:00
glBindVertexArray(m_shaders->m_shSHADOW.shaderVao);
2022-06-25 20:28:40 +02:00
if (m_renderData.clipBox.width != 0 && m_renderData.clipBox.height != 0) {
CRegion damageClip{m_renderData.clipBox.x, m_renderData.clipBox.y, m_renderData.clipBox.width, m_renderData.clipBox.height};
damageClip.intersect(m_renderData.damage);
2023-07-19 20:09:49 +02:00
if (!damageClip.empty()) {
for (auto const& RECT : damageClip.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
}
} else {
for (auto const& RECT : m_renderData.damage.getRects()) {
scissor(&RECT);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}
2022-06-25 20:28:40 +02:00
}
glBindVertexArray(0);
2022-06-25 20:28:40 +02:00
}
void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) {
if (!m_renderData.pCurrentMonData->monitorMirrorFB.isAllocated())
m_renderData.pCurrentMonData->monitorMirrorFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y,
m_renderData.pMonitor->m_output->state->state().drmFormat);
m_renderData.pCurrentMonData->monitorMirrorFB.bind();
2022-09-13 15:25:42 +02:00
blend(false);
renderTexture(m_renderData.currentFB->getTexture(), box, 1.f, 0, 2.0f, false, false);
2022-09-13 15:25:42 +02:00
blend(true);
m_renderData.currentFB->bind();
2022-09-13 15:25:42 +02:00
}
void CHyprOpenGLImpl::renderMirrored() {
auto monitor = m_renderData.pMonitor;
auto mirrored = monitor->m_mirrorOf;
const double scale = std::min(monitor->m_transformedSize.x / mirrored->m_transformedSize.x, monitor->m_transformedSize.y / mirrored->m_transformedSize.y);
CBox monbox = {0, 0, mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale};
// transform box as it will be drawn on a transformed projection
monbox.transform(wlTransformToHyprutils(mirrored->m_transform), mirrored->m_transformedSize.x * scale, mirrored->m_transformedSize.y * scale);
monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2;
monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2;
2022-09-13 15:25:42 +02:00
const auto PFB = &m_monitorRenderResources[mirrored].monitorMirrorFB;
if (!PFB->isAllocated() || !PFB->getTexture())
2022-09-13 15:25:42 +02:00
return;
g_pHyprRenderer->m_renderPass.add(makeShared<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}));
CTexPassElement::SRenderData data;
data.tex = PFB->getTexture();
data.box = monbox;
data.replaceProjection = Mat3x3::identity()
.translate(monitor->m_pixelSize / 2.0)
.transform(wlTransformToHyprutils(monitor->m_transform))
.transform(wlTransformToHyprutils(invertTransform(mirrored->m_transform)))
.translate(-monitor->m_transformedSize / 2.0);
g_pHyprRenderer->m_renderPass.add(makeShared<CTexPassElement>(data));
2022-09-13 15:25:42 +02:00
}
void CHyprOpenGLImpl::renderSplash(cairo_t* const CAIRO, cairo_surface_t* const CAIROSURFACE, double offsetY, const Vector2D& size) {
static auto PSPLASHCOLOR = CConfigValue<Hyprlang::INT>("misc:col.splash");
static auto PSPLASHFONT = CConfigValue<std::string>("misc:splash_font_family");
static auto FALLBACKFONT = CConfigValue<std::string>("misc:font_family");
const auto FONTFAMILY = *PSPLASHFONT != STRVAL_EMPTY ? *PSPLASHFONT : *FALLBACKFONT;
const auto FONTSIZE = (int)(size.y / 76);
const auto COLOR = CHyprColor(*PSPLASHCOLOR);
2022-07-10 15:41:26 +02:00
PangoLayout* layoutText = pango_cairo_create_layout(CAIRO);
PangoFontDescription* pangoFD = pango_font_description_new();
2022-07-10 15:41:26 +02:00
pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());
pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);
pango_font_description_set_style(pangoFD, PANGO_STYLE_NORMAL);
pango_font_description_set_weight(pangoFD, PANGO_WEIGHT_NORMAL);
pango_layout_set_font_description(layoutText, pangoFD);
cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);
2022-07-10 15:41:26 +02:00
int textW = 0, textH = 0;
pango_layout_set_text(layoutText, g_pCompositor->m_currentSplash.c_str(), -1);
pango_layout_get_size(layoutText, &textW, &textH);
textW /= PANGO_SCALE;
textH /= PANGO_SCALE;
2022-07-10 15:41:26 +02:00
cairo_move_to(CAIRO, (size.x - textW) / 2.0, size.y - textH - offsetY);
pango_cairo_show_layout(CAIRO, layoutText);
pango_font_description_free(pangoFD);
g_object_unref(layoutText);
2022-07-10 15:41:26 +02:00
cairo_surface_flush(CAIROSURFACE);
}
SP<CTexture> CHyprOpenGLImpl::loadAsset(const std::string& filename) {
std::string fullPath;
for (auto& e : ASSET_PATHS) {
std::string p = std::string{e} + "/hypr/" + filename;
std::error_code ec;
if (std::filesystem::exists(p, ec)) {
fullPath = p;
break;
} else
Debug::log(LOG, "loadAsset: looking at {} unsuccessful: ec {}", filename, ec.message());
}
if (fullPath.empty()) {
m_failedAssetsNo++;
Debug::log(ERR, "loadAsset: looking for {} failed (no provider found)", filename);
return m_missingAssetTexture;
}
const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str());
if (!CAIROSURFACE) {
m_failedAssetsNo++;
Debug::log(ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath);
return m_missingAssetTexture;
}
const auto CAIROFORMAT = cairo_image_surface_get_format(CAIROSURFACE);
auto tex = makeShared<CTexture>();
tex->allocate();
tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)};
const GLint glIFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB32F : GL_RGBA;
const GLint glFormat = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_RGB : GL_RGBA;
const GLint glType = CAIROFORMAT == CAIRO_FORMAT_RGB96F ? GL_FLOAT : GL_UNSIGNED_BYTE;
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
glBindTexture(GL_TEXTURE_2D, tex->m_texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
if (CAIROFORMAT != CAIRO_FORMAT_RGB96F) {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
}
glTexImage2D(GL_TEXTURE_2D, 0, glIFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA);
cairo_surface_destroy(CAIROSURFACE);
return tex;
}
SP<CTexture> CHyprOpenGLImpl::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) {
SP<CTexture> tex = makeShared<CTexture>();
static auto FONT = CConfigValue<std::string>("misc:font_family");
const auto FONTFAMILY = fontFamily.empty() ? *FONT : fontFamily;
const auto FONTSIZE = pt;
const auto COLOR = col;
auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 1920, 1080 /* arbitrary, just for size */);
auto CAIRO = cairo_create(CAIROSURFACE);
PangoLayout* layoutText = pango_cairo_create_layout(CAIRO);
PangoFontDescription* pangoFD = pango_font_description_new();
pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());
pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);
pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
pango_font_description_set_weight(pangoFD, static_cast<PangoWeight>(weight));
pango_layout_set_font_description(layoutText, pangoFD);
cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);
int textW = 0, textH = 0;
pango_layout_set_text(layoutText, text.c_str(), -1);
if (maxWidth > 0) {
pango_layout_set_width(layoutText, maxWidth * PANGO_SCALE);
pango_layout_set_ellipsize(layoutText, PANGO_ELLIPSIZE_END);
}
pango_layout_get_size(layoutText, &textW, &textH);
textW /= PANGO_SCALE;
textH /= PANGO_SCALE;
pango_font_description_free(pangoFD);
g_object_unref(layoutText);
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH);
CAIRO = cairo_create(CAIROSURFACE);
layoutText = pango_cairo_create_layout(CAIRO);
pangoFD = pango_font_description_new();
pango_font_description_set_family_static(pangoFD, FONTFAMILY.c_str());
pango_font_description_set_absolute_size(pangoFD, FONTSIZE * PANGO_SCALE);
pango_font_description_set_style(pangoFD, italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL);
pango_font_description_set_weight(pangoFD, static_cast<PangoWeight>(weight));
pango_layout_set_font_description(layoutText, pangoFD);
pango_layout_set_text(layoutText, text.c_str(), -1);
cairo_set_source_rgba(CAIRO, COLOR.r, COLOR.g, COLOR.b, COLOR.a);
cairo_move_to(CAIRO, 0, 0);
pango_cairo_show_layout(CAIRO, layoutText);
pango_font_description_free(pangoFD);
g_object_unref(layoutText);
cairo_surface_flush(CAIROSURFACE);
tex->allocate();
tex->m_size = {cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE)};
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
glBindTexture(GL_TEXTURE_2D, tex->m_texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->m_size.x, tex->m_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, DATA);
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
return tex;
}
void CHyprOpenGLImpl::initMissingAssetTexture() {
SP<CTexture> tex = makeShared<CTexture>();
tex->allocate();
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 512, 512);
const auto CAIRO = cairo_create(CAIROSURFACE);
cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_NONE);
cairo_save(CAIRO);
cairo_set_source_rgba(CAIRO, 0, 0, 0, 1);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);
cairo_paint(CAIRO);
cairo_set_source_rgba(CAIRO, 1, 0, 1, 1);
cairo_rectangle(CAIRO, 256, 0, 256, 256);
cairo_fill(CAIRO);
cairo_rectangle(CAIRO, 0, 256, 256, 256);
cairo_fill(CAIRO);
cairo_restore(CAIRO);
cairo_surface_flush(CAIROSURFACE);
tex->m_size = {512, 512};
// copy the data to an OpenGL texture we have
const GLint glFormat = GL_RGBA;
const GLint glType = GL_UNSIGNED_BYTE;
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
glBindTexture(GL_TEXTURE_2D, tex->m_texID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA);
cairo_surface_destroy(CAIROSURFACE);
cairo_destroy(CAIRO);
m_missingAssetTexture = tex;
}
void CHyprOpenGLImpl::useProgram(GLuint prog) {
if (m_currentProgram == prog)
return;
glUseProgram(prog);
m_currentProgram = prog;
}
void CHyprOpenGLImpl::initAssets() {
initMissingAssetTexture();
2023-07-13 14:32:30 +02:00
m_lockDeadTexture = loadAsset("lockdead.png");
m_lockDead2Texture = loadAsset("lockdead2.png");
2022-04-10 14:32:18 +02:00
m_lockTtyTextTexture = renderText(
std::format("Running on tty {}",
g_pCompositor->m_aqBackend->hasSession() && g_pCompositor->m_aqBackend->session->vt > 0 ? std::to_string(g_pCompositor->m_aqBackend->session->vt) : "unknown"),
CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true);
2022-04-12 21:49:35 +02:00
m_screencopyDeniedTexture = renderText("Permission denied to share screen", Colors::WHITE, 20);
ensureBackgroundTexturePresence();
}
void CHyprOpenGLImpl::ensureBackgroundTexturePresence() {
static auto PNOWALLPAPER = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PFORCEWALLPAPER = CConfigValue<Hyprlang::INT>("misc:force_default_wallpaper");
const auto FORCEWALLPAPER = std::clamp(*PFORCEWALLPAPER, static_cast<int64_t>(-1L), static_cast<int64_t>(2L));
if (*PNOWALLPAPER)
m_backgroundTexture.reset();
else if (!m_backgroundTexture) {
// create the default background texture
std::string texPath = "wall";
// get the adequate tex
if (FORCEWALLPAPER == -1) {
std::mt19937_64 engine(time(nullptr));
std::uniform_int_distribution<> distribution(0, 2);
texPath += std::to_string(distribution(engine));
} else
texPath += std::to_string(std::clamp(*PFORCEWALLPAPER, (int64_t)0, (int64_t)2));
texPath += ".png";
2022-04-10 14:32:18 +02:00
m_backgroundTexture = loadAsset(texPath);
2023-07-13 14:32:30 +02:00
}
}
void CHyprOpenGLImpl::createBGTextureForMonitor(PHLMONITOR pMonitor) {
RASSERT(m_renderData.pMonitor, "Tried to createBGTex without begin()!");
Debug::log(LOG, "Creating a texture for BGTex");
static auto PRENDERTEX = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PNOSPLASH = CConfigValue<Hyprlang::INT>("misc:disable_splash_rendering");
if (*PRENDERTEX)
return;
// release the last tex if exists
const auto PFB = &m_monitorBGFBs[pMonitor];
PFB->release();
PFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, pMonitor->m_output->state->state().drmFormat);
if (!m_backgroundTexture) // ?!?!?!
return;
2023-07-13 14:32:30 +02:00
// create a new one with cairo
SP<CTexture> tex = makeShared<CTexture>();
tex->allocate();
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y);
const auto CAIRO = cairo_create(CAIROSURFACE);
cairo_set_antialias(CAIRO, CAIRO_ANTIALIAS_GOOD);
cairo_save(CAIRO);
cairo_set_source_rgba(CAIRO, 0, 0, 0, 0);
cairo_set_operator(CAIRO, CAIRO_OPERATOR_SOURCE);
cairo_paint(CAIRO);
cairo_restore(CAIRO);
if (!*PNOSPLASH)
renderSplash(CAIRO, CAIROSURFACE, 0.02 * pMonitor->m_pixelSize.y, pMonitor->m_pixelSize);
cairo_surface_flush(CAIROSURFACE);
tex->m_size = pMonitor->m_pixelSize;
2022-07-10 15:41:26 +02:00
2022-04-10 14:32:18 +02:00
// copy the data to an OpenGL texture we have
const GLint glFormat = GL_RGBA;
const GLint glType = GL_UNSIGNED_BYTE;
const auto DATA = cairo_image_surface_get_data(CAIROSURFACE);
glBindTexture(GL_TEXTURE_2D, tex->m_texID);
2022-08-13 22:33:51 +02:00
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, GL_BLUE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, GL_RED);
glTexImage2D(GL_TEXTURE_2D, 0, glFormat, tex->m_size.x, tex->m_size.y, 0, glFormat, glType, DATA);
2022-04-10 14:32:18 +02:00
cairo_surface_destroy(CAIROSURFACE);
cairo_destroy(CAIRO);
2022-04-12 21:49:35 +02:00
// render the texture to our fb
PFB->bind();
CRegion fakeDamage{0, 0, INT16_MAX, INT16_MAX};
blend(true);
clear(CHyprColor{0, 0, 0, 1});
// first render the background
if (m_backgroundTexture) {
const double MONRATIO = m_renderData.pMonitor->m_transformedSize.x / m_renderData.pMonitor->m_transformedSize.y;
const double WPRATIO = m_backgroundTexture->m_size.x / m_backgroundTexture->m_size.y;
Vector2D origin;
double scale = 1.0;
if (MONRATIO > WPRATIO) {
scale = m_renderData.pMonitor->m_transformedSize.x / m_backgroundTexture->m_size.x;
origin.y = (m_renderData.pMonitor->m_transformedSize.y - m_backgroundTexture->m_size.y * scale) / 2.0;
} else {
scale = m_renderData.pMonitor->m_transformedSize.y / m_backgroundTexture->m_size.y;
origin.x = (m_renderData.pMonitor->m_transformedSize.x - m_backgroundTexture->m_size.x * scale) / 2.0;
}
CBox texbox = CBox{origin, m_backgroundTexture->m_size * scale};
renderTextureInternalWithDamage(m_backgroundTexture, texbox, 1.0, fakeDamage);
}
CBox monbox = {{}, pMonitor->m_pixelSize};
renderTextureInternalWithDamage(tex, monbox, 1.0, fakeDamage);
// bind back
if (m_renderData.currentFB)
m_renderData.currentFB->bind();
Debug::log(LOG, "Background created for monitor {}", pMonitor->m_name);
2022-04-10 14:32:18 +02:00
}
void CHyprOpenGLImpl::clearWithTex() {
RASSERT(m_renderData.pMonitor, "Tried to render BGtex without begin()!");
auto TEXIT = m_monitorBGFBs.find(m_renderData.pMonitor);
if (TEXIT == m_monitorBGFBs.end()) {
createBGTextureForMonitor(m_renderData.pMonitor.lock());
TEXIT = m_monitorBGFBs.find(m_renderData.pMonitor);
2022-07-07 20:16:40 +02:00
}
if (TEXIT != m_monitorBGFBs.end()) {
CTexPassElement::SRenderData data;
data.box = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
data.flipEndFrame = true;
data.tex = TEXIT->second.getTexture();
g_pHyprRenderer->m_renderPass.add(makeShared<CTexPassElement>(data));
}
2022-04-19 19:01:23 +02:00
}
void CHyprOpenGLImpl::destroyMonitorResources(PHLMONITORREF pMonitor) {
2023-11-30 01:18:55 +00:00
g_pHyprRenderer->makeEGLCurrent();
if (!g_pHyprOpenGL)
return;
auto RESIT = g_pHyprOpenGL->m_monitorRenderResources.find(pMonitor);
if (RESIT != g_pHyprOpenGL->m_monitorRenderResources.end()) {
RESIT->second.mirrorFB.release();
RESIT->second.offloadFB.release();
RESIT->second.mirrorSwapFB.release();
RESIT->second.monitorMirrorFB.release();
RESIT->second.blurFB.release();
RESIT->second.offMainFB.release();
RESIT->second.stencilTex->destroyTexture();
g_pHyprOpenGL->m_monitorRenderResources.erase(RESIT);
}
auto TEXIT = g_pHyprOpenGL->m_monitorBGFBs.find(pMonitor);
if (TEXIT != g_pHyprOpenGL->m_monitorBGFBs.end()) {
TEXIT->second.release();
g_pHyprOpenGL->m_monitorBGFBs.erase(TEXIT);
}
2022-04-19 19:01:23 +02:00
if (pMonitor)
Debug::log(LOG, "Monitor {} -> destroyed all render data", pMonitor->m_name);
2022-04-19 19:01:23 +02:00
}
void CHyprOpenGLImpl::saveMatrix() {
m_renderData.savedProjection = m_renderData.projection;
}
void CHyprOpenGLImpl::setMatrixScaleTranslate(const Vector2D& translate, const float& scale) {
m_renderData.projection.scale(scale).translate(translate);
}
void CHyprOpenGLImpl::restoreMatrix() {
m_renderData.projection = m_renderData.savedProjection;
}
2023-10-21 14:15:48 +01:00
void CHyprOpenGLImpl::bindOffMain() {
if (!m_renderData.pCurrentMonData->offMainFB.isAllocated()) {
m_renderData.pCurrentMonData->offMainFB.alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y,
m_renderData.pMonitor->m_output->state->state().drmFormat);
m_renderData.pCurrentMonData->offMainFB.addStencil(m_renderData.pCurrentMonData->stencilTex);
}
m_renderData.pCurrentMonData->offMainFB.bind();
clear(CHyprColor(0, 0, 0, 0));
m_renderData.currentFB = &m_renderData.pCurrentMonData->offMainFB;
2023-10-21 14:15:48 +01:00
}
void CHyprOpenGLImpl::renderOffToMain(CFramebuffer* off) {
CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
renderTexturePrimitive(off->getTexture(), monbox);
2023-10-21 14:15:48 +01:00
}
void CHyprOpenGLImpl::bindBackOnMain() {
m_renderData.mainFB->bind();
m_renderData.currentFB = m_renderData.mainFB;
2023-10-21 14:15:48 +01:00
}
void CHyprOpenGLImpl::setMonitorTransformEnabled(bool enabled) {
m_endFrame = enabled;
}
2024-04-03 14:09:58 +01:00
void CHyprOpenGLImpl::setRenderModifEnabled(bool enabled) {
m_renderData.renderModif.enabled = enabled;
2024-04-03 14:09:58 +01:00
}
uint32_t CHyprOpenGLImpl::getPreferredReadFormat(PHLMONITOR pMonitor) {
return pMonitor->m_output->state->state().drmFormat;
}
std::vector<SDRMFormat> CHyprOpenGLImpl::getDRMFormats() {
return m_drmFormats;
}
2024-01-07 18:35:44 +01:00
void SRenderModifData::applyToBox(CBox& box) {
2024-04-03 14:09:58 +01:00
if (!enabled)
return;
for (auto const& [type, val] : modifs) {
2024-01-07 18:35:44 +01:00
try {
switch (type) {
case RMOD_TYPE_SCALE: box.scale(std::any_cast<float>(val)); break;
case RMOD_TYPE_SCALECENTER: box.scaleFromCenter(std::any_cast<float>(val)); break;
case RMOD_TYPE_TRANSLATE: box.translate(std::any_cast<Vector2D>(val)); break;
case RMOD_TYPE_ROTATE: box.rot += std::any_cast<float>(val); break;
case RMOD_TYPE_ROTATECENTER: {
const auto THETA = std::any_cast<float>(val);
const double COS = std::cos(THETA);
const double SIN = std::sin(THETA);
box.rot += THETA;
const auto OLDPOS = box.pos();
box.x = OLDPOS.x * COS - OLDPOS.y * SIN;
box.y = OLDPOS.y * COS + OLDPOS.x * SIN;
}
}
} catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToBox!"); }
}
}
2024-04-03 14:09:58 +01:00
void SRenderModifData::applyToRegion(CRegion& rg) {
if (!enabled)
return;
for (auto const& [type, val] : modifs) {
2024-04-03 14:09:58 +01:00
try {
switch (type) {
case RMOD_TYPE_SCALE: rg.scale(std::any_cast<float>(val)); break;
case RMOD_TYPE_SCALECENTER: rg.scale(std::any_cast<float>(val)); break;
case RMOD_TYPE_TRANSLATE: rg.translate(std::any_cast<Vector2D>(val)); break;
case RMOD_TYPE_ROTATE: /* TODO */
case RMOD_TYPE_ROTATECENTER: break;
}
} catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::applyToRegion!"); }
}
}
float SRenderModifData::combinedScale() {
if (!enabled)
return 1;
float scale = 1.f;
for (auto const& [type, val] : modifs) {
2024-04-03 14:09:58 +01:00
try {
switch (type) {
case RMOD_TYPE_SCALE: scale *= std::any_cast<float>(val); break;
case RMOD_TYPE_SCALECENTER:
case RMOD_TYPE_TRANSLATE:
case RMOD_TYPE_ROTATE:
case RMOD_TYPE_ROTATECENTER: break;
}
} catch (std::bad_any_cast& e) { Debug::log(ERR, "BUG THIS OR PLUGIN ERROR: caught a bad_any_cast in SRenderModifData::combinedScale!"); }
}
return scale;
}
UP<CEGLSync> CEGLSync::create() {
EGLSyncKHR sync = g_pHyprOpenGL->m_proc.eglCreateSyncKHR(g_pHyprOpenGL->m_eglDisplay, EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
if (sync == EGL_NO_SYNC_KHR) {
Debug::log(ERR, "eglCreateSyncKHR failed");
return nullptr;
}
// we need to flush otherwise we might not get a valid fd
glFlush();
int fd = g_pHyprOpenGL->m_proc.eglDupNativeFenceFDANDROID(g_pHyprOpenGL->m_eglDisplay, sync);
if (fd == EGL_NO_NATIVE_FENCE_FD_ANDROID) {
Debug::log(ERR, "eglDupNativeFenceFDANDROID failed");
return nullptr;
}
UP<CEGLSync> eglSync(new CEGLSync);
eglSync->m_fd = CFileDescriptor(fd);
eglSync->m_sync = sync;
eglSync->m_valid = true;
return eglSync;
}
CEGLSync::~CEGLSync() {
if (m_sync == EGL_NO_SYNC_KHR)
return;
if (g_pHyprOpenGL && g_pHyprOpenGL->m_proc.eglDestroySyncKHR(g_pHyprOpenGL->m_eglDisplay, m_sync) != EGL_TRUE)
Debug::log(ERR, "eglDestroySyncKHR failed");
}
CFileDescriptor& CEGLSync::fd() {
return m_fd;
}
CFileDescriptor&& CEGLSync::takeFd() {
return std::move(m_fd);
}
bool CEGLSync::isValid() {
return m_valid && m_sync != EGL_NO_SYNC_KHR && m_fd.isValid();
}