Hyprland/src/render/Renderer.cpp

3702 lines
153 KiB
C++
Raw Normal View History

2022-03-17 20:22:29 +01:00
#include "Renderer.hpp"
#include "../Compositor.hpp"
#include "../helpers/math/Math.hpp"
#include <algorithm>
#include <aquamarine/output/Output.hpp>
#include <cmath>
#include <filesystem>
#include "../config/ConfigValue.hpp"
#include "../config/ConfigManager.hpp"
#include "../managers/CursorManager.hpp"
#include "../managers/PointerManager.hpp"
#include "../managers/input/InputManager.hpp"
#include "../managers/animation/AnimationManager.hpp"
#include "../desktop/view/Window.hpp"
#include "../desktop/view/LayerSurface.hpp"
#include "../desktop/view/GlobalViewMethods.hpp"
#include "../desktop/state/FocusState.hpp"
2024-04-30 16:32:05 +01:00
#include "../protocols/SessionLock.hpp"
#include "../protocols/LayerShell.hpp"
2024-05-10 23:28:33 +01:00
#include "../protocols/XDGShell.hpp"
2024-05-10 02:27:54 +01:00
#include "../protocols/PresentationTime.hpp"
2024-05-11 17:13:20 +01:00
#include "../protocols/core/DataDevice.hpp"
#include "../protocols/core/Compositor.hpp"
#include "../protocols/DRMSyncobj.hpp"
#include "../protocols/LinuxDMABUF.hpp"
#include "../helpers/sync/SyncTimeline.hpp"
#include "../hyprerror/HyprError.hpp"
#include "../debug/HyprDebugOverlay.hpp"
#include "../debug/HyprNotificationOverlay.hpp"
#include "../layout/LayoutManager.hpp"
#include "../layout/space/Space.hpp"
#include "../i18n/Engine.hpp"
#include "desktop/DesktopTypes.hpp"
#include "../event/EventBus.hpp"
#include "helpers/CursorShapes.hpp"
#include "helpers/MainLoopExecutor.hpp"
2025-06-23 15:33:09 +03:00
#include "helpers/Monitor.hpp"
#include "macros.hpp"
#include "../managers/screenshare/ScreenshareManager.hpp"
#include "pass/TexPassElement.hpp"
#include "pass/ClearPassElement.hpp"
#include "pass/RectPassElement.hpp"
#include "pass/RendererHintsPassElement.hpp"
#include "pass/SurfacePassElement.hpp"
#include "debug/log/Logger.hpp"
#include "../protocols/ColorManagement.hpp"
#include "../protocols/types/ContentType.hpp"
#include "../helpers/MiscFunctions.hpp"
#include "render/AsyncResourceGatherer.hpp"
#include "render/Framebuffer.hpp"
#include "render/OpenGL.hpp"
#include "render/Texture.hpp"
#include "render/pass/BorderPassElement.hpp"
#include "render/pass/PreBlurElement.hpp"
#include <hyprutils/math/Mat3x3.hpp>
#include <hyprutils/math/Region.hpp>
#include <hyprutils/math/Vector2D.hpp>
#include <hyprutils/memory/SharedPtr.hpp>
#include <optional>
#include <pango/pangocairo.h>
2022-03-17 20:22:29 +01:00
#include <hyprutils/utils/ScopeGuard.hpp>
#include <random>
using namespace Hyprutils::Utils;
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 enum NContentType::eContentType;
using namespace NColorManagement;
extern "C" {
#include <xf86drm.h>
}
static int cursorTicker(void* data) {
g_pHyprRenderer->ensureCursorRenderingMode();
wl_event_source_timer_update(g_pHyprRenderer->m_cursorTicker, 500);
return 0;
}
IHyprRenderer::IHyprRenderer() {
m_globalTimer.reset();
pushMonitorTransformEnabled(false);
if (g_pCompositor->m_aqBackend->hasSession()) {
size_t drmDevices = 0;
for (auto const& dev : g_pCompositor->m_aqBackend->session->sessionDevices) {
const auto DRMV = drmGetVersion(dev->fd);
if (!DRMV)
continue;
drmDevices++;
std::string name = std::string{DRMV->name, DRMV->name_len};
std::ranges::transform(name, name.begin(), tolower);
if (name.contains("nvidia"))
m_nvidia = true;
else if (name.contains("i915"))
m_intel = true;
else if (name.contains("softpipe") || name.contains("Software Rasterizer") || name.contains("llvmpipe"))
m_software = true;
Log::logger->log(Log::DEBUG, "DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor, DRMV->version_patchlevel,
std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len});
drmFreeVersion(DRMV);
}
m_mgpu = drmDevices > 1;
} else {
Log::logger->log(Log::DEBUG, "Aq backend has no session, omitting full DRM node checks");
const auto DRMV = drmGetVersion(g_pCompositor->m_drm.fd);
if (DRMV) {
std::string name = std::string{DRMV->name, DRMV->name_len};
std::ranges::transform(name, name.begin(), tolower);
if (name.contains("nvidia"))
m_nvidia = true;
else if (name.contains("i915"))
m_intel = true;
else if (name.contains("softpipe") || name.contains("Software Rasterizer") || name.contains("llvmpipe"))
m_software = true;
Log::logger->log(Log::DEBUG, "Primary DRM driver information: {} v{}.{}.{} from {} description {}", name, DRMV->version_major, DRMV->version_minor,
DRMV->version_patchlevel, std::string{DRMV->date, DRMV->date_len}, std::string{DRMV->desc, DRMV->desc_len});
} else {
Log::logger->log(Log::DEBUG, "No primary DRM driver information found");
}
drmFreeVersion(DRMV);
}
if (m_nvidia)
Log::logger->log(Log::WARN, "NVIDIA detected, please remember to follow nvidia instructions on the wiki");
// cursor hiding stuff
static auto P = Event::bus()->m_events.input.keyboard.key.listen([&](IKeyboard::SKeyEvent e, Event::SCallbackInfo&) {
if (m_cursorHiddenConditions.hiddenOnKeyboard)
return;
m_cursorHiddenConditions.hiddenOnKeyboard = true;
ensureCursorRenderingMode();
});
static auto P2 = Event::bus()->m_events.input.mouse.move.listen([&](Vector2D pos, Event::SCallbackInfo&) {
if (!m_cursorHiddenConditions.hiddenOnKeyboard && m_cursorHiddenConditions.hiddenOnTouch == g_pInputManager->m_lastInputTouch &&
m_cursorHiddenConditions.hiddenOnTablet == g_pInputManager->m_lastInputTablet && !m_cursorHiddenConditions.hiddenOnTimeout)
return;
m_cursorHiddenConditions.hiddenOnKeyboard = false;
m_cursorHiddenConditions.hiddenOnTimeout = false;
m_cursorHiddenConditions.hiddenOnTouch = g_pInputManager->m_lastInputTouch;
m_cursorHiddenConditions.hiddenOnTablet = g_pInputManager->m_lastInputTablet;
ensureCursorRenderingMode();
});
static auto P3 = Event::bus()->m_events.monitor.focused.listen([&](PHLMONITOR mon) {
g_pEventLoopManager->doLater([this]() {
if (!g_pHyprError->active())
return;
for (auto& m : g_pCompositor->m_monitors) {
arrangeLayersForMonitor(m->m_id);
}
});
});
static auto P4 = Event::bus()->m_events.window.updateRules.listen([&](PHLWINDOW window) {
if (window->m_ruleApplicator->renderUnfocused().valueOrDefault())
addWindowToRenderUnfocused(window);
2025-11-21 18:48:45 +00:00
});
m_cursorTicker = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, cursorTicker, nullptr);
wl_event_source_timer_update(m_cursorTicker, 500);
m_renderUnfocusedTimer = makeShared<CEventLoopTimer>(
std::nullopt,
[this](SP<CEventLoopTimer> self, void* data) {
static auto PFPS = CConfigValue<Hyprlang::INT>("misc:render_unfocused_fps");
if (m_renderUnfocused.empty())
return;
bool dirty = false;
for (auto& w : m_renderUnfocused) {
if (!w) {
dirty = true;
continue;
}
if (!w->wlSurface() || !w->wlSurface()->resource() || shouldRenderWindow(w.lock()))
continue;
w->wlSurface()->resource()->frame(Time::steadyNow());
auto FEEDBACK = makeUnique<CQueuedPresentationData>(w->wlSurface()->resource());
FEEDBACK->attachMonitor(Desktop::focusState()->monitor());
FEEDBACK->discarded();
PROTO::presentation->queueData(std::move(FEEDBACK));
}
if (dirty)
std::erase_if(m_renderUnfocused, [](const auto& e) { return !e || !e->m_ruleApplicator->renderUnfocused().valueOr(false); });
if (!m_renderUnfocused.empty())
m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS));
},
nullptr);
g_pEventLoopManager->addTimer(m_renderUnfocusedTimer);
}
IHyprRenderer::~IHyprRenderer() {
if (m_cursorTicker)
wl_event_source_remove(m_cursorTicker);
}
WP<CHyprOpenGLImpl> IHyprRenderer::glBackend() {
return g_pHyprOpenGL;
}
bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor) {
if (!pWindow->visibleOnMonitor(pMonitor))
2022-03-20 18:49:40 +01:00
return false;
if (!pWindow->m_workspace && !pWindow->m_fadingOut)
return false;
if (!pWindow->m_workspace && pWindow->m_fadingOut)
2025-05-10 23:53:05 +01:00
return pWindow->workspaceID() == pMonitor->activeWorkspaceID() || pWindow->workspaceID() == pMonitor->activeSpecialWorkspaceID();
if (pWindow->m_pinned)
2022-09-10 13:11:02 +02:00
return true;
// if the window is being moved to a workspace that is not invisible, and the alpha is > 0.F, render it.
if (pWindow->m_monitorMovedFrom != -1 && pWindow->m_movingToWorkspaceAlpha->isBeingAnimated() && pWindow->m_movingToWorkspaceAlpha->value() > 0.F && pWindow->m_workspace &&
!pWindow->m_workspace->isVisible())
return true;
const auto PWINDOWWORKSPACE = pWindow->m_workspace;
if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_monitor == pMonitor) {
if (PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() || PWINDOWWORKSPACE->m_alpha->isBeingAnimated() || PWINDOWWORKSPACE->m_forceRendering)
return true;
// if hidden behind fullscreen
if (PWINDOWWORKSPACE->m_hasFullscreenWindow && !pWindow->isFullscreen() && (!pWindow->m_isFloating || !pWindow->m_createdOverFullscreen) && pWindow->m_alpha->value() == 0)
return false;
if (!PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !PWINDOWWORKSPACE->m_alpha->isBeingAnimated() && !PWINDOWWORKSPACE->isVisible())
return false;
2022-07-28 16:33:45 +02:00
}
if (pWindow->m_monitor == pMonitor)
2022-03-20 18:49:40 +01:00
return true;
if ((!pWindow->m_workspace || !pWindow->m_workspace->isVisible()) && pWindow->m_monitor != pMonitor)
return false;
// if not, check if it maybe is active on a different monitor.
if (pWindow->m_workspace && pWindow->m_workspace->isVisible() && pWindow->m_isFloating /* tiled windows can't be multi-ws */)
internal: refactor fullscreen states (#7104) * refactor fullscreen modified: src/Compositor.cpp modified: src/Compositor.hpp modified: src/config/ConfigManager.cpp modified: src/config/ConfigManager.hpp modified: src/debug/HyprCtl.cpp modified: src/desktop/LayerSurface.cpp modified: src/desktop/Window.cpp modified: src/desktop/Window.hpp modified: src/desktop/Workspace.cpp modified: src/desktop/Workspace.hpp modified: src/events/Windows.cpp modified: src/helpers/Monitor.cpp modified: src/layout/DwindleLayout.cpp modified: src/layout/DwindleLayout.hpp modified: src/layout/IHyprLayout.cpp modified: src/layout/IHyprLayout.hpp modified: src/layout/MasterLayout.cpp modified: src/layout/MasterLayout.hpp modified: src/managers/KeybindManager.cpp modified: src/managers/KeybindManager.hpp modified: src/managers/input/IdleInhibitor.cpp modified: src/managers/input/InputManager.cpp modified: src/managers/input/Swipe.cpp modified: src/protocols/ForeignToplevelWlr.cpp modified: src/render/Renderer.cpp modified: src/render/decorations/CHyprGroupBarDecoration.cpp * clean up modified: src/config/ConfigManager.cpp modified: src/debug/HyprCtl.cpp modified: src/desktop/Window.hpp modified: src/desktop/Workspace.cpp modified: src/events/Windows.cpp modified: src/managers/KeybindManager.cpp modified: src/managers/input/Swipe.cpp * fix mapWindow fullscreen modified: src/events/Windows.cpp * fix typo modified: src/desktop/Workspace.cpp * add fullscreenstate modified: src/config/ConfigManager.cpp modified: src/events/Windows.cpp * change syncFullscreen to lower modified: src/config/ConfigManager.hpp * initialize fs state modified: src/desktop/Window.hpp
2024-07-31 17:55:52 +00:00
return !pWindow->isFullscreen(); // Do not draw fullscreen windows on other monitors
2022-03-20 18:49:40 +01:00
if (pMonitor->m_activeSpecialWorkspace == pWindow->m_workspace)
2022-05-31 14:01:00 +02:00
return true;
// if window is tiled and it's flying in, don't render on other mons (for slide)
if (!pWindow->m_isFloating && pWindow->m_realPosition->isBeingAnimated() && pWindow->m_animatingIn && pWindow->m_monitor != pMonitor)
return false;
if (pWindow->m_realPosition->isBeingAnimated()) {
if (PWINDOWWORKSPACE && !PWINDOWWORKSPACE->m_isSpecialWorkspace && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated())
return false;
// render window if window and monitor intersect
// (when moving out of or through a monitor)
CBox windowBox = pWindow->getFullWindowBoundingBox();
if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated())
windowBox.translate(PWINDOWWORKSPACE->m_renderOffset->value());
windowBox.translate(pWindow->m_floatingOffset);
const CBox monitorBox = {pMonitor->m_position, pMonitor->m_size};
if (!windowBox.intersection(monitorBox).empty() && (pWindow->workspaceID() == pMonitor->activeWorkspaceID() || pWindow->m_monitorMovedFrom != -1))
return true;
}
2022-03-20 18:49:40 +01:00
return false;
}
bool IHyprRenderer::shouldRenderWindow(PHLWINDOW pWindow) {
if (!validMapped(pWindow))
return false;
const auto PWORKSPACE = pWindow->m_workspace;
if (!pWindow->m_workspace)
return false;
if (pWindow->m_pinned || PWORKSPACE->m_forceRendering)
2022-09-10 13:11:02 +02:00
return true;
if (PWORKSPACE && PWORKSPACE->isVisible())
return true;
for (auto const& m : g_pCompositor->m_monitors) {
if (PWORKSPACE && PWORKSPACE->m_monitor == m && (PWORKSPACE->m_renderOffset->isBeingAnimated() || PWORKSPACE->m_alpha->isBeingAnimated()))
return true;
if (m->m_activeSpecialWorkspace && pWindow->onSpecialWorkspace())
return true;
}
return false;
}
void IHyprRenderer::renderWorkspaceWindowsFullscreen(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) {
PHLWINDOW pWorkspaceWindow = nullptr;
Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS);
2023-04-17 23:45:03 +01:00
// loop over the tiled windows that are fading out
for (auto const& w : g_pCompositor->m_windows) {
if (!shouldRenderWindow(w, pMonitor))
continue;
if (w->m_alpha->value() == 0.f)
continue;
if (w->isFullscreen() || w->m_isFloating)
continue;
if (pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);
}
// and floating ones too
for (auto const& w : g_pCompositor->m_windows) {
if (!shouldRenderWindow(w, pMonitor))
continue;
if (w->m_alpha->value() == 0.f)
continue;
if (w->isFullscreen() || !w->m_isFloating)
continue;
if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)
continue; // special on another are rendered as a part of the base pass
renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);
}
// TODO: this pass sucks
for (auto const& w : g_pCompositor->m_windows) {
const auto PWORKSPACE = w->m_workspace;
2022-07-28 15:56:55 +02:00
if (w->m_workspace != pWorkspace || !w->isFullscreen()) {
if (!(PWORKSPACE && (PWORKSPACE->m_renderOffset->isBeingAnimated() || PWORKSPACE->m_alpha->isBeingAnimated() || PWORKSPACE->m_forceRendering)))
2022-07-28 15:56:55 +02:00
continue;
if (w->m_monitor != pMonitor)
continue;
2022-07-28 15:56:55 +02:00
}
2022-03-21 19:18:33 +01:00
internal: refactor fullscreen states (#7104) * refactor fullscreen modified: src/Compositor.cpp modified: src/Compositor.hpp modified: src/config/ConfigManager.cpp modified: src/config/ConfigManager.hpp modified: src/debug/HyprCtl.cpp modified: src/desktop/LayerSurface.cpp modified: src/desktop/Window.cpp modified: src/desktop/Window.hpp modified: src/desktop/Workspace.cpp modified: src/desktop/Workspace.hpp modified: src/events/Windows.cpp modified: src/helpers/Monitor.cpp modified: src/layout/DwindleLayout.cpp modified: src/layout/DwindleLayout.hpp modified: src/layout/IHyprLayout.cpp modified: src/layout/IHyprLayout.hpp modified: src/layout/MasterLayout.cpp modified: src/layout/MasterLayout.hpp modified: src/managers/KeybindManager.cpp modified: src/managers/KeybindManager.hpp modified: src/managers/input/IdleInhibitor.cpp modified: src/managers/input/InputManager.cpp modified: src/managers/input/Swipe.cpp modified: src/protocols/ForeignToplevelWlr.cpp modified: src/render/Renderer.cpp modified: src/render/decorations/CHyprGroupBarDecoration.cpp * clean up modified: src/config/ConfigManager.cpp modified: src/debug/HyprCtl.cpp modified: src/desktop/Window.hpp modified: src/desktop/Workspace.cpp modified: src/events/Windows.cpp modified: src/managers/KeybindManager.cpp modified: src/managers/input/Swipe.cpp * fix mapWindow fullscreen modified: src/events/Windows.cpp * fix typo modified: src/desktop/Workspace.cpp * add fullscreenstate modified: src/config/ConfigManager.cpp modified: src/events/Windows.cpp * change syncFullscreen to lower modified: src/config/ConfigManager.hpp * initialize fs state modified: src/desktop/Window.hpp
2024-07-31 17:55:52 +00:00
if (!w->isFullscreen())
2022-07-28 16:33:45 +02:00
continue;
if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
if (shouldRenderWindow(w, pMonitor))
renderWindow(w, pMonitor, time, pWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN, RENDER_PASS_ALL);
if (w->m_workspace != pWorkspace)
continue;
pWorkspaceWindow = w;
}
if (!pWorkspaceWindow) {
// ?? happens sometimes...
pWorkspace->m_hasFullscreenWindow = false;
return; // this will produce one blank frame. Oh well.
}
// then render windows over fullscreen.
for (auto const& w : g_pCompositor->m_windows) {
if (w->workspaceID() != pWorkspaceWindow->workspaceID() || !w->m_isFloating || (!w->m_createdOverFullscreen && !w->m_pinned) || (!w->m_isMapped && !w->m_fadingOut) ||
2025-01-27 13:41:38 +00:00
w->isFullscreen())
continue;
if (w->m_monitor == pWorkspace->m_monitor && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)
continue; // special on another are rendered as a part of the base pass
renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);
2022-05-31 14:20:41 +02:00
}
}
2022-05-31 14:20:41 +02:00
void IHyprRenderer::renderWorkspaceWindows(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time) {
PHLWINDOW lastWindow;
2022-05-31 14:20:41 +02:00
Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOWS);
2022-05-31 14:20:41 +02:00
std::vector<PHLWINDOWREF> windows, tiledFadingOut;
windows.reserve(g_pCompositor->m_windows.size());
for (auto const& w : g_pCompositor->m_windows) {
if (w->isHidden() || (!w->m_isMapped && !w->m_fadingOut))
continue;
2022-05-31 14:20:41 +02:00
if (!shouldRenderWindow(w, pMonitor))
continue;
windows.emplace_back(w);
}
// Non-floating main
for (auto& w : windows) {
if (w->m_isFloating)
continue; // floating are in the second pass
// some things may force us to ignore the special/not special disparity
const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());
if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
// render active window after all others of this pass
if (w == Desktop::focusState()->window()) {
lastWindow = w.lock();
continue;
}
// render tiled fading out after others
if (w->m_fadingOut) {
tiledFadingOut.emplace_back(w);
w.reset();
continue;
}
// render the bad boy
renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_MAIN);
w.reset();
}
if (lastWindow)
renderWindow(lastWindow, pMonitor, time, true, RENDER_PASS_MAIN);
lastWindow.reset();
// render tiled windows that are fading out after other tiled to not hide them behind
for (auto& w : tiledFadingOut) {
renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_MAIN);
}
// Non-floating popup
for (auto& w : windows) {
if (!w)
continue;
if (w->m_isFloating)
continue; // floating are in the second pass
// some things may force us to ignore the special/not special disparity
const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());
if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
// render the bad boy
renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_POPUP);
w.reset();
}
// floating on top
for (auto& w : windows) {
if (!w)
continue;
if (!w->m_isFloating || w->m_pinned)
continue;
// some things may force us to ignore the special/not special disparity
const bool IGNORE_SPECIAL_CHECK = w->m_monitorMovedFrom != -1 && (w->m_workspace && !w->m_workspace->isVisible());
if (!IGNORE_SPECIAL_CHECK && pWorkspace->m_isSpecialWorkspace != w->onSpecialWorkspace())
continue;
if (pWorkspace->m_isSpecialWorkspace && w->m_monitor != pWorkspace->m_monitor)
continue; // special on another are rendered as a part of the base pass
// render the bad boy
renderWindow(w.lock(), pMonitor, time, true, RENDER_PASS_ALL);
}
2022-03-21 19:18:33 +01:00
}
void IHyprRenderer::bindOffMain() {
RASSERT(m_renderData.pMonitor->m_offMainFB->isAllocated(), "IHyprRenderer::beginRender should allocate monitor FBs")
m_renderData.pMonitor->m_offMainFB->bind();
draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), {});
m_renderData.currentFB = m_renderData.pMonitor->m_offMainFB;
}
void IHyprRenderer::bindBackOnMain() {
m_renderData.mainFB->bind();
m_renderData.currentFB = m_renderData.mainFB;
}
void IHyprRenderer::renderWindow(PHLWINDOW pWindow, PHLMONITOR pMonitor, const Time::steady_tp& time, bool decorate, eRenderPassMode mode, bool ignorePosition, bool standalone) {
if (pWindow->isHidden() && !standalone)
return;
if (pWindow->m_fadingOut) {
if (pMonitor == pWindow->m_monitor) // TODO: fix this
renderSnapshot(pWindow);
2022-04-05 20:49:15 +02:00
return;
}
2022-09-25 20:07:48 +02:00
if (!pWindow->m_isMapped)
return;
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderWindow");
const auto PWORKSPACE = pWindow->m_workspace;
const auto REALPOS = pWindow->m_realPosition->value() + (pWindow->m_pinned ? Vector2D{} : PWORKSPACE->m_renderOffset->value());
static auto PDIMAROUND = CConfigValue<Hyprlang::FLOAT>("decoration:dim_around");
2022-06-21 22:54:41 +02:00
CSurfacePassElement::SRenderData renderdata = {pMonitor, time};
CBox textureBox = {REALPOS.x, REALPOS.y, std::max(pWindow->m_realSize->value().x, 5.0), std::max(pWindow->m_realSize->value().y, 5.0)};
renderdata.pos.x = textureBox.x;
renderdata.pos.y = textureBox.y;
renderdata.w = textureBox.w;
renderdata.h = textureBox.h;
2022-11-06 17:52:09 +00:00
if (ignorePosition) {
renderdata.pos.x = pMonitor->m_position.x;
renderdata.pos.y = pMonitor->m_position.y;
} else {
const bool ANR = pWindow->isNotResponding();
if (ANR && pWindow->m_notRespondingTint->goal() != 0.2F)
*pWindow->m_notRespondingTint = 0.2F;
else if (!ANR && pWindow->m_notRespondingTint->goal() != 0.F)
*pWindow->m_notRespondingTint = 0.F;
2022-11-06 17:52:09 +00:00
}
if (standalone)
decorate = false;
// whether to use m_fMovingToWorkspaceAlpha, only if fading out into an invisible ws
const bool USE_WORKSPACE_FADE_ALPHA = pWindow->m_monitorMovedFrom != -1 && (!PWORKSPACE || !PWORKSPACE->isVisible());
renderdata.surface = pWindow->wlSurface()->resource();
renderdata.dontRound = pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);
renderdata.fadeAlpha = pWindow->m_alpha->value() * (pWindow->m_pinned || USE_WORKSPACE_FADE_ALPHA ? 1.f : PWORKSPACE->m_alpha->value()) *
(USE_WORKSPACE_FADE_ALPHA ? pWindow->m_movingToWorkspaceAlpha->value() : 1.F) * pWindow->m_movingFromWorkspaceAlpha->value();
renderdata.alpha = pWindow->m_activeInactiveAlpha->value();
renderdata.decorate = decorate && !pWindow->m_X11DoesntWantBorders && !pWindow->isEffectiveInternalFSMode(FSMODE_FULLSCREEN);
renderdata.rounding = standalone || renderdata.dontRound ? 0 : pWindow->rounding() * pMonitor->m_scale;
renderdata.roundingPower = standalone || renderdata.dontRound ? 2.0f : pWindow->roundingPower();
renderdata.blur = !standalone && shouldBlur(pWindow);
renderdata.pWindow = pWindow;
2022-03-21 19:18:33 +01:00
if (standalone) {
renderdata.alpha = 1.f;
renderdata.fadeAlpha = 1.f;
2022-12-05 18:00:57 +00:00
}
2022-07-28 12:07:41 +02:00
// apply opaque
if (pWindow->m_ruleApplicator->opaque().valueOrDefault())
2022-07-28 12:07:41 +02:00
renderdata.alpha = 1.f;
renderdata.pWindow = pWindow;
2022-03-21 19:18:33 +01:00
// for plugins
m_renderData.currentWindow = pWindow;
Event::bus()->m_events.render.stage.emit(RENDER_PRE_WINDOW);
const auto fullAlpha = renderdata.alpha * renderdata.fadeAlpha;
if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && mode != RENDER_PASS_POPUP) {
CBox monbox = {0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y};
CRectPassElement::SRectData data;
data.color = CHyprColor(0, 0, 0, *PDIMAROUND * fullAlpha);
data.box = monbox;
m_renderPass.add(makeUnique<CRectPassElement>(data));
2022-12-28 15:39:17 +01:00
}
renderdata.pos.x += pWindow->m_floatingOffset.x;
renderdata.pos.y += pWindow->m_floatingOffset.y;
// if window is floating and we have a slide animation, clip it to its full bb
if (!ignorePosition && pWindow->m_isFloating && !pWindow->isFullscreen() && PWORKSPACE->m_renderOffset->isBeingAnimated() && !pWindow->m_pinned) {
CRegion rg =
pWindow->getFullWindowBoundingBox().translate(-pMonitor->m_position + PWORKSPACE->m_renderOffset->value() + pWindow->m_floatingOffset).scale(pMonitor->m_scale);
renderdata.clipBox = rg.getExtents();
}
// render window decorations first, if not fullscreen full
if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_MAIN) {
2023-10-21 14:15:48 +01:00
const bool TRANSFORMERSPRESENT = !pWindow->m_transformers.empty();
2023-10-21 14:15:48 +01:00
if (TRANSFORMERSPRESENT) {
bindOffMain();
2023-10-21 14:15:48 +01:00
for (auto const& t : pWindow->m_transformers) {
t->preWindowRender(&renderdata);
}
}
if (renderdata.decorate) {
for (auto const& wd : pWindow->m_windowDecorations) {
if (wd->getDecorationLayer() != DECORATION_LAYER_BOTTOM)
continue;
wd->draw(pMonitor, fullAlpha);
}
for (auto const& wd : pWindow->m_windowDecorations) {
if (wd->getDecorationLayer() != DECORATION_LAYER_UNDER)
continue;
wd->draw(pMonitor, fullAlpha);
}
}
2022-06-22 15:45:56 +02:00
static auto PXWLUSENN = CConfigValue<Hyprlang::INT>("xwayland:use_nearest_neighbor");
if ((pWindow->m_isX11 && *PXWLUSENN) || pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault())
renderdata.useNearestNeighbor = true;
if (pWindow->wlSurface()->small() && !pWindow->wlSurface()->m_fillIgnoreSmall && renderdata.blur) {
CBox wb = {renderdata.pos.x - pMonitor->m_position.x, renderdata.pos.y - pMonitor->m_position.y, renderdata.w, renderdata.h};
wb.scale(pMonitor->m_scale).round();
CRectPassElement::SRectData data;
data.color = CHyprColor(0, 0, 0, 0);
data.box = wb;
data.round = renderdata.dontRound ? 0 : renderdata.rounding - 1;
data.blur = true;
data.blurA = renderdata.fadeAlpha;
data.xray = shouldUseNewBlurOptimizations(nullptr, pWindow);
m_renderPass.add(makeUnique<CRectPassElement>(data));
renderdata.blur = false;
}
renderdata.surfaceCounter = 0;
pWindow->wlSurface()->resource()->breadthfirst(
[this, &renderdata, &pWindow](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = s == pWindow->wlSurface()->resource();
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
nullptr);
renderdata.useNearestNeighbor = false;
if (renderdata.decorate) {
for (auto const& wd : pWindow->m_windowDecorations) {
if (wd->getDecorationLayer() != DECORATION_LAYER_OVER)
continue;
wd->draw(pMonitor, fullAlpha);
}
}
2023-10-21 14:15:48 +01:00
if (TRANSFORMERSPRESENT) {
IFramebuffer* last = m_renderData.currentFB.get();
for (auto const& t : pWindow->m_transformers) {
2023-10-21 14:15:48 +01:00
last = t->transform(last);
}
bindBackOnMain();
renderOffToMain(last);
2023-10-21 14:15:48 +01:00
}
2022-06-22 15:45:56 +02:00
}
m_renderData.clipBox = CBox();
if (mode == RENDER_PASS_ALL || mode == RENDER_PASS_POPUP) {
if (!pWindow->m_isX11) {
CBox geom = pWindow->m_xdgSurface->m_current.geometry;
renderdata.pos -= geom.pos();
renderdata.dontRound = true; // don't round popups
renderdata.pMonitor = pMonitor;
renderdata.squishOversized = false; // don't squish popups
renderdata.popup = true;
2023-10-24 21:28:55 +01:00
2024-03-25 16:08:55 +00:00
static CConfigValue PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>("decoration:blur:popups_ignorealpha");
renderdata.blur = shouldBlur(pWindow->m_popupHead);
2024-03-25 16:08:55 +00:00
if (renderdata.blur) {
renderdata.discardMode |= DISCARD_ALPHA;
renderdata.discardOpacity = *PBLURIGNOREA;
2024-03-25 16:08:55 +00:00
}
if (pWindow->m_ruleApplicator->nearestNeighbor().valueOrDefault())
renderdata.useNearestNeighbor = true;
2023-10-24 21:28:55 +01:00
renderdata.surfaceCounter = 0;
pWindow->m_popupHead->breadthfirst(
[this, &renderdata](WP<Desktop::View::CPopup> popup, void* data) {
if (popup->m_fadingOut) {
renderSnapshot(popup);
return;
}
if (!popup->aliveAndVisible())
2024-05-10 23:28:33 +01:00
return;
2024-11-17 16:42:30 +00:00
const auto pos = popup->coordsRelativeToParent();
const Vector2D oldPos = renderdata.pos;
renderdata.pos += pos;
renderdata.fadeAlpha = popup->m_alpha->value();
popup->wlSurface()->resource()->breadthfirst(
[this, &renderdata](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = false;
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
data);
renderdata.pos = oldPos;
2024-05-10 23:28:33 +01:00
},
&renderdata);
renderdata.alpha = 1.F;
}
if (decorate) {
for (auto const& wd : pWindow->m_windowDecorations) {
if (wd->getDecorationLayer() != DECORATION_LAYER_OVERLAY)
continue;
wd->draw(pMonitor, fullAlpha);
}
}
2022-05-17 13:16:37 +02:00
}
Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOW);
m_renderData.currentWindow.reset();
2022-03-21 19:18:33 +01:00
}
void IHyprRenderer::drawRect(CRectPassElement* element, const CRegion& damage) {
auto& data = element->m_data;
if (data.box.w <= 0 || data.box.h <= 0)
return;
if (!data.clipBox.empty())
m_renderData.clipBox = data.clipBox;
data.modifiedBox = data.box;
m_renderData.renderModif.applyToBox(data.modifiedBox);
CBox transformedBox = data.box;
transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x,
m_renderData.pMonitor->m_transformedSize.y);
data.TOPLEFT[0] = sc<float>(transformedBox.x);
data.TOPLEFT[1] = sc<float>(transformedBox.y);
data.FULLSIZE[0] = sc<float>(transformedBox.width);
data.FULLSIZE[1] = sc<float>(transformedBox.height);
data.drawRegion = data.color.a == 1.F || !data.blur ? damage : m_renderData.damage;
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};
data.drawRegion = damageClip.intersect(data.drawRegion);
}
draw(element, damage);
m_renderData.clipBox = {};
}
void IHyprRenderer::drawHints(CRendererHintsPassElement* element, const CRegion& damage) {
const auto m_data = element->m_data;
if (m_data.renderModif.has_value())
m_renderData.renderModif = *m_data.renderModif;
}
void IHyprRenderer::drawPreBlur(CPreBlurElement* element, const CRegion& damage) {
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};
draw(element, fakeDamage);
m_renderData.pMonitor->m_blurFBDirty = false;
m_renderData.pMonitor->m_blurFBShouldRender = false;
m_renderData.renderModif = SAVEDRENDERMODIF;
}
void IHyprRenderer::drawSurface(CSurfacePassElement* element, const CRegion& damage) {
const auto m_data = element->m_data;
CScopeGuard x = {[]() {
g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1);
g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);
}};
if (!m_data.texture)
return;
const auto& TEXTURE = m_data.texture;
// this is bad, probably has been logged elsewhere. Means the texture failed
// uploading to the GPU.
if (!TEXTURE->ok())
return;
const auto INTERACTIVERESIZEINPROGRESS = m_data.pWindow && g_layoutManager->dragController()->target() && g_layoutManager->dragController()->mode() == MBIND_RESIZE;
TRACY_GPU_ZONE("RenderSurface");
auto PSURFACE = Desktop::View::CWLSurface::fromResource(m_data.surface);
const float ALPHA = m_data.alpha * m_data.fadeAlpha * (PSURFACE ? PSURFACE->m_alphaModifier : 1.F);
const float OVERALL_ALPHA = PSURFACE ? PSURFACE->m_overallOpacity : 1.F;
const bool BLUR = m_data.blur && (!TEXTURE->m_opaque || ALPHA < 1.F || OVERALL_ALPHA < 1.F);
auto windowBox = element->getTexBox();
const auto PROJSIZEUNSCALED = windowBox.size();
windowBox.scale(m_data.pMonitor->m_scale);
windowBox.round();
if (windowBox.width <= 1 || windowBox.height <= 1) {
element->discard();
return;
}
const bool MISALIGNEDFSV1 = std::floor(m_data.pMonitor->m_scale) != m_data.pMonitor->m_scale /* Fractional */ && m_data.surface->m_current.scale == 1 /* fs protocol */ &&
windowBox.size() != m_data.surface->m_current.bufferSize /* misaligned */ && DELTALESSTHAN(windowBox.width, m_data.surface->m_current.bufferSize.x, 3) &&
DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ &&
(!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */ &&
(!m_data.pLS || (!m_data.pLS->m_realSize->isBeingAnimated())); /* not LS or not animated */
calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1);
auto cancelRender = false;
auto clipRegion = element->visibleRegion(cancelRender);
if (cancelRender)
return;
// check for fractional scale surfaces misaligning the buffer size
// in those cases it's better to just force nearest neighbor
// as long as the window is not animated. During those it'd look weird.
// UV will fixup it as well
if (MISALIGNEDFSV1)
m_renderData.useNearestNeighbor = true;
float rounding = m_data.rounding;
float roundingPower = m_data.roundingPower;
rounding -= 1; // to fix a border issue
if (m_data.dontRound) {
rounding = 0;
roundingPower = 2.0f;
}
const bool WINDOWOPAQUE = m_data.pWindow && m_data.pWindow->wlSurface()->resource() == m_data.surface ? m_data.pWindow->opaque() : false;
const bool CANDISABLEBLEND = ALPHA >= 1.f && OVERALL_ALPHA >= 1.f && rounding <= 0 && WINDOWOPAQUE;
if (CANDISABLEBLEND)
blend(false);
else
blend(true);
// FIXME: This is wrong and will bug the blur out as shit if the first surface
// is a subsurface that does NOT cover the entire frame. In such cases, we probably should fall back
// to what we do for misaligned surfaces (blur the entire thing and then render shit without blur)
if (m_data.surfaceCounter == 0 && !m_data.popup) {
if (BLUR)
draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
.a = ALPHA,
.blurA = m_data.fadeAlpha,
.overallA = OVERALL_ALPHA,
.round = rounding,
.roundingPower = roundingPower,
.blur = true,
.blockBlurOptimization = m_data.blockBlurOptimization,
.allowCustomUV = true,
.surface = m_data.surface,
.discardMode = m_data.discardMode,
.discardOpacity = m_data.discardOpacity,
.clipRegion = clipRegion,
.currentLS = m_data.pLS,
}),
m_renderData.damage.copy().intersect(windowBox));
else
draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
.a = ALPHA * OVERALL_ALPHA,
.round = rounding,
.roundingPower = roundingPower,
.discardActive = false,
.allowCustomUV = true,
.surface = m_data.surface,
.discardMode = m_data.discardMode,
.discardOpacity = m_data.discardOpacity,
.clipRegion = clipRegion,
.currentLS = m_data.pLS,
}),
m_renderData.damage.copy().intersect(windowBox));
} else {
if (BLUR && m_data.popup)
draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
.a = ALPHA,
.blurA = m_data.fadeAlpha,
.overallA = OVERALL_ALPHA,
.round = rounding,
.roundingPower = roundingPower,
.blur = true,
.blockBlurOptimization = true,
.allowCustomUV = true,
.surface = m_data.surface,
.discardMode = m_data.discardMode,
.discardOpacity = m_data.discardOpacity,
.clipRegion = clipRegion,
.currentLS = m_data.pLS,
}),
m_renderData.damage.copy().intersect(windowBox));
else
draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.tex = TEXTURE,
.box = windowBox,
.a = ALPHA * OVERALL_ALPHA,
.round = rounding,
.roundingPower = roundingPower,
.discardActive = false,
.allowCustomUV = true,
.surface = m_data.surface,
.discardMode = m_data.discardMode,
.discardOpacity = m_data.discardOpacity,
.clipRegion = clipRegion,
.currentLS = m_data.pLS,
}),
m_renderData.damage.copy().intersect(windowBox));
}
blend(true);
};
void IHyprRenderer::preDrawSurface(CSurfacePassElement* element, const CRegion& damage) {
m_renderData.clipBox = element->m_data.clipBox;
m_renderData.useNearestNeighbor = element->m_data.useNearestNeighbor;
pushMonitorTransformEnabled(element->m_data.flipEndFrame);
m_renderData.currentWindow = element->m_data.pWindow;
drawSurface(element, damage);
if (!m_bBlockSurfaceFeedback)
element->m_data.surface->presentFeedback(element->m_data.when, element->m_data.pMonitor->m_self.lock());
// add async (dmabuf) buffers to usedBuffers so we can handle release later
// sync (shm) buffers will be released in commitState, so no need to track them here
if (element->m_data.surface->m_current.buffer && !element->m_data.surface->m_current.buffer->isSynchronous())
m_usedAsyncBuffers.emplace_back(element->m_data.surface->m_current.buffer);
m_renderData.clipBox = {};
m_renderData.useNearestNeighbor = false;
popMonitorTransformEnabled();
m_renderData.currentWindow.reset();
}
void IHyprRenderer::drawTex(CTexPassElement* element, const CRegion& damage) {
if (!element->m_data.clipBox.empty())
m_renderData.clipBox = element->m_data.clipBox;
pushMonitorTransformEnabled(element->m_data.flipEndFrame);
if (element->m_data.useMirrorProjection)
setProjectionType(RPT_MIRROR);
m_renderData.surface = element->m_data.surface;
CScopeGuard x = {[useMirrorProjection = element->m_data.useMirrorProjection]() {
g_pHyprRenderer->popMonitorTransformEnabled();
if (useMirrorProjection)
g_pHyprRenderer->setProjectionType(RPT_MONITOR);
g_pHyprRenderer->m_renderData.surface.reset();
}};
if (element->m_data.blur) {
// make a damage region for this window
CRegion texDamage{m_renderData.damage};
texDamage.intersect(element->m_data.box.x, element->m_data.box.y, element->m_data.box.width, element->m_data.box.height);
// While renderTextureInternalWithDamage will clip the blur as well,
// clipping texDamage here allows blur generation to be optimized.
if (!element->m_data.clipRegion.empty())
texDamage.intersect(element->m_data.clipRegion);
if (texDamage.empty())
return;
m_renderData.renderModif.applyToRegion(texDamage);
element->m_data.damage = texDamage;
// amazing hack: the surface has an opaque region!
const auto& surface = element->m_data.surface;
const auto& box = element->m_data.box;
CRegion inverseOpaque;
if (element->m_data.a >= 1.f && surface && std::round(surface->m_current.size.x * m_renderData.pMonitor->m_scale) == box.w &&
std::round(surface->m_current.size.y * m_renderData.pMonitor->m_scale) == box.h) {
pixman_box32_t surfbox = {0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale};
inverseOpaque = surface->m_current.opaque;
inverseOpaque.invert(&surfbox).intersect(0, 0, surface->m_current.size.x * surface->m_current.scale, surface->m_current.size.y * surface->m_current.scale);
if (inverseOpaque.empty()) {
element->m_data.blur = false;
draw(element, damage);
m_renderData.clipBox = {};
return;
}
} else
inverseOpaque = {0, 0, element->m_data.box.width, element->m_data.box.height};
inverseOpaque.scale(m_renderData.pMonitor->m_scale);
element->m_data.blockBlurOptimization =
element->m_data.blockBlurOptimization.value_or(false) || !shouldUseNewBlurOptimizations(element->m_data.currentLS.lock(), m_renderData.currentWindow.lock());
// vvv TODO: layered blur fbs?
if (element->m_data.blockBlurOptimization) {
inverseOpaque.translate(box.pos());
m_renderData.renderModif.applyToRegion(inverseOpaque);
inverseOpaque.intersect(element->m_data.damage);
element->m_data.blurredBG = blurMainFramebuffer(element->m_data.a, &inverseOpaque);
m_renderData.currentFB->bind();
} else
element->m_data.blurredBG = m_renderData.pMonitor->m_blurFB ? m_renderData.pMonitor->m_blurFB->getTexture() : nullptr;
draw(element, damage);
} else
draw(element, damage);
m_renderData.clipBox = {};
}
void IHyprRenderer::drawTexMatte(CTextureMatteElement* element, const CRegion& damage) {
if (m_renderData.damage.empty())
return;
const auto m_data = element->m_data;
if (m_data.disableTransformAndModify) {
pushMonitorTransformEnabled(true);
m_renderData.renderModif.enabled = false;
draw(element, damage);
m_renderData.renderModif.enabled = true;
popMonitorTransformEnabled();
} else
draw(element, damage);
}
void IHyprRenderer::draw(WP<IPassElement> element, const CRegion& damage) {
if (!element)
return;
switch (element->type()) {
case EK_BORDER: draw(dc<CBorderPassElement*>(element.get()), damage); break;
case EK_CLEAR: draw(dc<CClearPassElement*>(element.get()), damage); break;
case EK_FRAMEBUFFER: draw(dc<CFramebufferElement*>(element.get()), damage); break;
case EK_PRE_BLUR: drawPreBlur(dc<CPreBlurElement*>(element.get()), damage); break;
case EK_RECT: drawRect(dc<CRectPassElement*>(element.get()), damage); break;
case EK_HINTS: drawHints(dc<CRendererHintsPassElement*>(element.get()), damage); break;
case EK_SHADOW: draw(dc<CShadowPassElement*>(element.get()), damage); break;
case EK_SURFACE: preDrawSurface(dc<CSurfacePassElement*>(element.get()), damage); break;
case EK_TEXTURE: drawTex(dc<CTexPassElement*>(element.get()), damage); break;
case EK_TEXTURE_MATTE: drawTexMatte(dc<CTextureMatteElement*>(element.get()), damage); break;
default: Log::logger->log(Log::WARN, "Unimplimented draw for {}", element->passName());
}
}
bool IHyprRenderer::preBlurQueued(PHLMONITORREF pMonitor) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>("decoration:blur:new_optimizations");
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
if (!pMonitor)
return false;
return m_renderData.pMonitor->m_blurFBDirty && *PBLURNEWOPTIMIZE && *PBLUR && m_renderData.pMonitor->m_blurFBShouldRender;
}
void IHyprRenderer::pushMonitorTransformEnabled(bool enabled) {
m_monitorTransformStack.push(enabled);
m_monitorTransformEnabled = enabled;
}
void IHyprRenderer::popMonitorTransformEnabled() {
m_monitorTransformStack.pop();
m_monitorTransformEnabled = m_monitorTransformStack.top();
}
bool IHyprRenderer::monitorTransformEnabled() {
return m_monitorTransformEnabled;
}
SP<ITexture> IHyprRenderer::createTexture(const SP<Aquamarine::IBuffer> buffer, bool keepDataCopy) {
if (!buffer)
return createTexture();
auto attrs = buffer->dmabuf();
if (!attrs.success) {
// attempt shm
auto shm = buffer->shm();
if (!shm.success) {
Log::logger->log(Log::ERR, "Cannot create a texture: buffer has no dmabuf or shm");
return createTexture(buffer->opaque);
}
auto [pixelData, fmt, bufLen] = buffer->beginDataPtr(0);
return createTexture(fmt, pixelData, bufLen, shm.size, keepDataCopy, buffer->opaque);
}
auto tex = createTexture(attrs, buffer->opaque);
if (!tex) {
Log::logger->log(Log::ERR, "Cannot create a texture: failed to create an Image");
return createTexture(buffer->opaque);
}
return tex;
}
void IHyprRenderer::renderLayer(PHLLS pLayer, PHLMONITOR pMonitor, const Time::steady_tp& time, bool popups, bool lockscreen) {
if (!pLayer)
return;
// skip rendering based on abovelock rule and make sure to not render abovelock layers twice
if ((pLayer->m_ruleApplicator->aboveLock().valueOrDefault() && !lockscreen && g_pSessionLockManager->isSessionLocked()) ||
(lockscreen && !pLayer->m_ruleApplicator->aboveLock().valueOrDefault()))
return;
static auto PDIMAROUND = CConfigValue<Hyprlang::FLOAT>("decoration:dim_around");
if (*PDIMAROUND && pLayer->m_ruleApplicator->dimAround().valueOrDefault() && !m_bRenderingSnapshot && !popups) {
CRectPassElement::SRectData data;
data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};
data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pLayer->m_alpha->value());
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
if (pLayer->m_fadingOut) {
if (!popups)
renderSnapshot(pLayer);
return;
}
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderLayer");
const auto REALPOS = pLayer->m_realPosition->value();
const auto REALSIZ = pLayer->m_realSize->value();
2023-03-17 23:16:13 +00:00
CSurfacePassElement::SRenderData renderdata = {pMonitor, time, REALPOS};
renderdata.fadeAlpha = pLayer->m_alpha->value();
renderdata.blur = shouldBlur(pLayer);
renderdata.surface = pLayer->wlSurface()->resource();
renderdata.decorate = false;
renderdata.w = REALSIZ.x;
renderdata.h = REALSIZ.y;
renderdata.pLS = pLayer;
renderdata.blockBlurOptimization = pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM || pLayer->m_layer == ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND;
renderdata.clipBox = CBox{0, 0, pMonitor->m_size.x, pMonitor->m_size.y}.scale(pMonitor->m_scale);
2024-03-25 16:08:55 +00:00
if (renderdata.blur && pLayer->m_ruleApplicator->ignoreAlpha().hasValue()) {
renderdata.discardMode |= DISCARD_ALPHA;
renderdata.discardOpacity = pLayer->m_ruleApplicator->ignoreAlpha().valueOrDefault();
}
2024-03-25 16:08:55 +00:00
if (!popups)
pLayer->wlSurface()->resource()->breadthfirst(
[this, &renderdata, &pLayer](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = s == pLayer->wlSurface()->resource();
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
&renderdata);
2022-07-27 18:02:20 +02:00
renderdata.squishOversized = false; // don't squish popups
renderdata.dontRound = true;
renderdata.popup = true;
renderdata.blur = pLayer->m_ruleApplicator->blurPopups().valueOrDefault();
renderdata.surfaceCounter = 0;
if (popups) {
pLayer->m_popupHead->breadthfirst(
[this, &renderdata](WP<Desktop::View::CPopup> popup, void* data) {
if (!popup->aliveAndVisible())
return;
const auto SURF = popup->wlSurface()->resource();
if (!SURF->m_current.texture)
return;
if (SURF->m_current.size.x < 1 || SURF->m_current.size.y < 1)
return;
Vector2D pos = popup->coordsRelativeToParent();
renderdata.localPos = pos;
renderdata.texture = SURF->m_current.texture;
renderdata.surface = SURF;
renderdata.mainSurface = false;
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
&renderdata);
}
}
void IHyprRenderer::renderIMEPopup(CInputPopup* pPopup, PHLMONITOR pMonitor, const Time::steady_tp& time) {
const auto POS = pPopup->globalBox().pos();
2024-03-24 16:08:25 +00:00
CSurfacePassElement::SRenderData renderdata = {pMonitor, time, POS};
2024-03-24 16:08:25 +00:00
const auto SURF = pPopup->getSurface();
2022-08-05 17:07:01 +02:00
2024-03-24 16:08:25 +00:00
renderdata.surface = SURF;
2022-08-05 17:07:01 +02:00
renderdata.decorate = false;
renderdata.w = SURF->m_current.size.x;
renderdata.h = SURF->m_current.size.y;
2022-08-05 17:07:01 +02:00
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
static auto PBLURIMES = CConfigValue<Hyprlang::INT>("decoration:blur:input_methods");
static auto PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>("decoration:blur:input_methods_ignorealpha");
renderdata.blur = *PBLURIMES && *PBLUR;
if (renderdata.blur) {
renderdata.discardMode |= DISCARD_ALPHA;
renderdata.discardOpacity = *PBLURIGNOREA;
}
SURF->breadthfirst(
[this, &renderdata, &SURF](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = s == SURF;
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
&renderdata);
2022-08-05 17:07:01 +02:00
}
void IHyprRenderer::renderSessionLockSurface(WP<SSessionLockSurface> pSurface, PHLMONITOR pMonitor, const Time::steady_tp& time) {
CSurfacePassElement::SRenderData renderdata = {pMonitor, time, pMonitor->m_position, pMonitor->m_position};
2023-02-03 11:58:55 +00:00
renderdata.blur = false;
renderdata.surface = pSurface->surface->surface();
2023-02-03 11:58:55 +00:00
renderdata.decorate = false;
renderdata.w = pMonitor->m_size.x;
renderdata.h = pMonitor->m_size.y;
2023-02-03 11:58:55 +00:00
renderdata.surface->breadthfirst(
[this, &renderdata, &pSurface](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = s == pSurface->surface->surface();
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
&renderdata);
2023-02-03 11:58:55 +00:00
}
void IHyprRenderer::renderAllClientsForWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& time, const Vector2D& translate, const float& scale) {
static auto PDIMSPECIAL = CConfigValue<Hyprlang::FLOAT>("decoration:dim_special");
static auto PBLURSPECIAL = CConfigValue<Hyprlang::INT>("decoration:blur:special");
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
static auto PXPMODE = CConfigValue<Hyprlang::INT>("render:xp_mode");
static auto PSESSIONLOCKXRAY = CConfigValue<Hyprlang::INT>("misc:session_lock_xray");
2026-01-16 16:40:48 +01:00
if UNLIKELY (!pMonitor)
2022-03-17 20:22:29 +01:00
return;
2026-01-16 16:40:48 +01:00
if UNLIKELY (g_pSessionLockManager->isSessionLocked() && !*PSESSIONLOCKXRAY) {
// We stop to render workspaces as soon as the lockscreen was sent the "locked" or "finished" (aka denied) event.
// In addition we make sure to stop rendering workspaces after misc:lockdead_screen_delay has passed.
if (g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied())
return;
2023-02-03 11:58:55 +00:00
}
SRenderModifData RENDERMODIFDATA;
if (translate != Vector2D{0, 0})
RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_TRANSLATE, translate));
2026-01-16 16:40:48 +01:00
if UNLIKELY (scale != 1.f)
RENDERMODIFDATA.modifs.emplace_back(std::make_pair<>(SRenderModifData::eRenderModifType::RMOD_TYPE_SCALE, scale));
2026-01-16 16:40:48 +01:00
if UNLIKELY (!RENDERMODIFDATA.modifs.empty())
m_renderPass.add(makeUnique<CRendererHintsPassElement>(CRendererHintsPassElement::SData{RENDERMODIFDATA}));
CScopeGuard x([&RENDERMODIFDATA] {
if (!RENDERMODIFDATA.modifs.empty()) {
g_pHyprRenderer->m_renderPass.add(makeUnique<CRendererHintsPassElement>(CRendererHintsPassElement::SData{SRenderModifData{}}));
}
});
2026-01-16 16:40:48 +01:00
if UNLIKELY (!pWorkspace) {
2024-04-03 14:09:58 +01:00
// allow rendering without a workspace. In this case, just render layers.
renderBackground(pMonitor);
2024-04-03 14:09:58 +01:00
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) {
renderLayer(ls.lock(), pMonitor, time);
2024-04-03 14:09:58 +01:00
}
Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER);
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) {
renderLayer(ls.lock(), pMonitor, time);
2024-04-03 14:09:58 +01:00
}
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
renderLayer(ls.lock(), pMonitor, time);
2024-04-03 14:09:58 +01:00
}
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) {
renderLayer(ls.lock(), pMonitor, time);
2024-04-03 14:09:58 +01:00
}
return;
}
2026-01-16 16:40:48 +01:00
if LIKELY (!*PXPMODE) {
renderBackground(pMonitor);
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BACKGROUND]) {
renderLayer(ls.lock(), pMonitor, time);
}
Event::bus()->m_events.render.stage.emit(RENDER_POST_WALLPAPER);
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_BOTTOM]) {
renderLayer(ls.lock(), pMonitor, time);
}
2022-03-19 13:35:04 +01:00
}
2022-08-01 12:23:09 +02:00
// pre window pass
if (preBlurQueued(pMonitor))
m_renderPass.add(makeUnique<CPreBlurElement>());
2022-08-01 12:23:09 +02:00
2026-01-16 16:40:48 +01:00
if UNLIKELY /* subjective? */ (pWorkspace->m_hasFullscreenWindow)
renderWorkspaceWindowsFullscreen(pMonitor, pWorkspace, time);
else
renderWorkspaceWindows(pMonitor, pWorkspace, time);
2022-05-31 14:01:00 +02:00
// and then special
2026-01-16 16:40:48 +01:00
if UNLIKELY (pMonitor->m_specialFade->value() != 0.F) {
const auto SPECIALANIMPROGRS = pMonitor->m_specialFade->getCurveValue();
const bool ANIMOUT = !pMonitor->m_activeSpecialWorkspace;
if (*PDIMSPECIAL != 0.f) {
CRectPassElement::SRectData data;
data.box = {translate.x, translate.y, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};
data.color = CHyprColor(0, 0, 0, *PDIMSPECIAL * (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS));
2022-12-28 15:18:23 +01:00
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
if (*PBLURSPECIAL && *PBLUR) {
CRectPassElement::SRectData data;
data.box = {translate.x, translate.y, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};
data.color = CHyprColor(0, 0, 0, 0);
data.blur = true;
data.blurA = (ANIMOUT ? (1.0 - SPECIALANIMPROGRS) : SPECIALANIMPROGRS);
m_renderPass.add(makeUnique<CRectPassElement>(data));
2022-12-28 15:18:23 +01:00
}
}
// special
for (auto const& ws : g_pCompositor->getWorkspaces()) {
if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace)
continue;
if (ws->m_hasFullscreenWindow)
renderWorkspaceWindowsFullscreen(pMonitor, ws.lock(), time);
else
renderWorkspaceWindows(pMonitor, ws.lock(), time);
2022-03-18 20:03:39 +01:00
}
2022-03-19 13:35:04 +01:00
// pinned always above
for (auto const& w : g_pCompositor->m_windows) {
if (w->isHidden() && !w->m_isMapped && !w->m_fadingOut)
continue;
if (!w->m_pinned || !w->m_isFloating)
continue;
if (!shouldRenderWindow(w, pMonitor))
continue;
// render the bad boy
renderWindow(w, pMonitor, time, true, RENDER_PASS_ALL);
}
Event::bus()->m_events.render.stage.emit(RENDER_POST_WINDOWS);
2023-04-17 23:45:03 +01:00
2022-03-19 13:35:04 +01:00
// Render surfaces above windows for monitor
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) {
renderLayer(ls.lock(), pMonitor, time);
2022-03-19 13:35:04 +01:00
}
2022-08-05 17:07:01 +02:00
// Render IME popups
for (auto const& imep : g_pInputManager->m_relay.m_inputMethodPopups) {
2024-03-24 16:08:25 +00:00
renderIMEPopup(imep.get(), pMonitor, time);
2022-08-05 17:07:01 +02:00
}
for (auto const& ls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY]) {
renderLayer(ls.lock(), pMonitor, time);
2022-03-19 13:35:04 +01:00
}
2022-03-31 17:25:23 +02:00
for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
renderLayer(ls.lock(), pMonitor, time, true);
}
}
renderDragIcon(pMonitor, time);
}
SP<ITexture> IHyprRenderer::getBackground(PHLMONITOR pMonitor) {
if (m_backgroundResourceFailed)
return nullptr;
if (!m_backgroundResource) {
// queue the asset to be created
requestBackgroundResource();
return nullptr;
}
if (!m_backgroundResource->m_ready)
return nullptr;
Log::logger->log(Log::DEBUG, "Creating a texture for BGTex");
SP<ITexture> backgroundTexture = createTexture(m_backgroundResource->m_asset.cairoSurface->cairo());
if (!backgroundTexture->ok())
return nullptr;
Log::logger->log(Log::DEBUG, "Background created for monitor {}", pMonitor->m_name);
// clear the resource after we're done using it
g_pEventLoopManager->doLater([this] { m_backgroundResource.reset(); });
// set the animation to start for fading this background in nicely
pMonitor->m_backgroundOpacity->setValueAndWarp(0.F);
*pMonitor->m_backgroundOpacity = 1.F;
return backgroundTexture;
}
2023-02-03 11:58:55 +00:00
void IHyprRenderer::renderBackground(PHLMONITOR pMonitor) {
static auto PRENDERTEX = CConfigValue<Hyprlang::INT>("misc:disable_hyprland_logo");
static auto PBACKGROUNDCOLOR = CConfigValue<Hyprlang::INT>("misc:background_color");
static auto PNOSPLASH = CConfigValue<Hyprlang::INT>("misc:disable_splash_rendering");
if (*PRENDERTEX /* inverted cfg flag */ || pMonitor->m_backgroundOpacity->isBeingAnimated())
m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));
if (!*PRENDERTEX) {
static auto PBACKGROUNDCOLOR = CConfigValue<Hyprlang::INT>("misc:background_color");
if (!pMonitor->m_background)
pMonitor->m_background = getBackground(pMonitor);
if (!pMonitor->m_background)
m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));
else {
CTexPassElement::SRenderData data;
const double MONRATIO = m_renderData.pMonitor->m_transformedSize.x / m_renderData.pMonitor->m_transformedSize.y;
const double WPRATIO = pMonitor->m_background->m_size.x / pMonitor->m_background->m_size.y;
Vector2D origin;
double scale = 1.0;
if (MONRATIO > WPRATIO) {
scale = m_renderData.pMonitor->m_transformedSize.x / pMonitor->m_background->m_size.x;
origin.y = (m_renderData.pMonitor->m_transformedSize.y - pMonitor->m_background->m_size.y * scale) / 2.0;
} else {
scale = m_renderData.pMonitor->m_transformedSize.y / pMonitor->m_background->m_size.y;
origin.x = (m_renderData.pMonitor->m_transformedSize.x - pMonitor->m_background->m_size.x * scale) / 2.0;
}
if (MONRATIO != WPRATIO)
m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(*PBACKGROUNDCOLOR)}));
data.box = {origin, pMonitor->m_background->m_size * scale};
data.a = m_renderData.pMonitor->m_backgroundOpacity->value();
data.tex = pMonitor->m_background;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
}
if (!*PNOSPLASH) {
auto monitorSize = pMonitor->m_transformedSize;
if (!pMonitor->m_splash)
pMonitor->m_splash = renderSplash([this, pMonitor](auto width, auto height, const auto DATA) { return createTexture(width, height, DATA); }, monitorSize.y / 76,
monitorSize.x, monitorSize.y);
if (pMonitor->m_splash) {
CTexPassElement::SRenderData data;
data.box = {{(monitorSize.x - pMonitor->m_splash->m_size.x) / 2.0, monitorSize.y * 0.98 - pMonitor->m_splash->m_size.y}, pMonitor->m_splash->m_size};
data.tex = pMonitor->m_splash;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
}
}
void IHyprRenderer::requestBackgroundResource() {
if (m_backgroundResource)
return;
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, sc<int64_t>(-1), sc<int64_t>(2));
if (*PNOWALLPAPER)
return;
static bool once = true;
static std::string texPath = "wall";
if (once) {
// 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, sc<int64_t>(0), sc<int64_t>(2)));
texPath += ".png";
texPath = resolveAssetPath(texPath);
once = false;
}
if (texPath.empty()) {
m_backgroundResourceFailed = true;
return;
}
m_backgroundResource = makeAtomicShared<Hyprgraphics::CImageResource>(texPath);
// doesn't have to be ASP as it's passed
SP<CMainLoopExecutor> executor = makeShared<CMainLoopExecutor>([this] {
for (const auto& m : g_pCompositor->m_monitors) {
damageMonitor(m);
}
});
m_backgroundResource->m_events.finished.listenStatic([executor] {
// this is in the worker thread.
executor->signal();
});
g_pAsyncResourceGatherer->enqueue(m_backgroundResource);
}
std::string IHyprRenderer::resolveAssetPath(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
Log::logger->log(Log::DEBUG, "resolveAssetPath: looking at {} unsuccessful: ec {}", filename, ec.message());
}
if (fullPath.empty()) {
m_failedAssetsNo++;
Log::logger->log(Log::ERR, "resolveAssetPath: looking for {} failed (no provider found)", filename);
return "";
}
return fullPath;
}
SP<ITexture> IHyprRenderer::loadAsset(const std::string& filename) {
const std::string fullPath = resolveAssetPath(filename);
if (fullPath.empty())
return m_missingAssetTexture;
const auto CAIROSURFACE = cairo_image_surface_create_from_png(fullPath.c_str());
if (!CAIROSURFACE) {
m_failedAssetsNo++;
Log::logger->log(Log::ERR, "loadAsset: failed to load {} (corrupt / inaccessible / not png)", fullPath);
return m_missingAssetTexture;
}
auto tex = createTexture(CAIROSURFACE);
cairo_surface_destroy(CAIROSURFACE);
return tex;
}
SP<ITexture> IHyprRenderer::getBlurTexture(PHLMONITORREF pMonitor) {
if (!pMonitor->m_blurFB)
return nullptr;
return pMonitor->m_blurFB->getTexture();
}
bool IHyprRenderer::shouldUseNewBlurOptimizations(PHLLS pLayer, PHLWINDOW pWindow) {
static auto PBLURNEWOPTIMIZE = CConfigValue<Hyprlang::INT>("decoration:blur:new_optimizations");
static auto PBLURXRAY = CConfigValue<Hyprlang::INT>("decoration:blur:xray");
if (!getBlurTexture(m_renderData.pMonitor))
return false;
if (pWindow && pWindow->m_ruleApplicator->xray().hasValue() && !pWindow->m_ruleApplicator->xray().valueOrDefault())
return false;
if (pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 0)
return false;
if ((*PBLURNEWOPTIMIZE && pWindow && !pWindow->m_isFloating && !pWindow->onSpecialWorkspace()) || *PBLURXRAY)
return true;
if ((pLayer && pLayer->m_ruleApplicator->xray().valueOrDefault() == 1) || (pWindow && pWindow->m_ruleApplicator->xray().valueOrDefault()))
return true;
return false;
}
void IHyprRenderer::initMissingAssetTexture() {
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);
auto tex = createTexture(CAIROSURFACE);
cairo_surface_destroy(CAIROSURFACE);
cairo_destroy(CAIRO);
m_missingAssetTexture = tex;
}
void IHyprRenderer::initAssets() {
initMissingAssetTexture();
m_screencopyDeniedTexture = renderText("Permission denied to share screen", Colors::WHITE, 20);
}
SP<ITexture> IHyprRenderer::renderText(const std::string& text, CHyprColor col, int pt, bool italic, const std::string& fontFamily, int maxWidth, int weight) {
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, sc<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, sc<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);
auto tex = createTexture(cairo_image_surface_get_width(CAIROSURFACE), cairo_image_surface_get_height(CAIROSURFACE), cairo_image_surface_get_data(CAIROSURFACE));
cairo_destroy(CAIRO);
cairo_surface_destroy(CAIROSURFACE);
return tex;
}
void IHyprRenderer::ensureLockTexturesRendered(bool load) {
static bool loaded = false;
if (loaded == load)
return;
loaded = load;
if (load) {
// this will cause a small hitch. I don't think we can do much, other than wasting VRAM and having this loaded all the time.
m_lockDeadTexture = loadAsset("lockdead.png");
m_lockDead2Texture = loadAsset("lockdead2.png");
const auto VT = g_pCompositor->getVTNr();
m_lockTtyTextTexture = renderText(std::format("Running on tty {}", VT.has_value() ? std::to_string(*VT) : "unknown"), CHyprColor{0.9F, 0.9F, 0.9F, 0.7F}, 20, true);
} else {
m_lockDeadTexture.reset();
m_lockDead2Texture.reset();
m_lockTtyTextTexture.reset();
}
}
void IHyprRenderer::renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry) {
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderLockscreen");
const bool LOCKED = g_pSessionLockManager->isSessionLocked();
if (!LOCKED) {
ensureLockTexturesRendered(false);
return;
}
const bool RENDERPRIMER = g_pSessionLockManager->shallConsiderLockMissing() || g_pSessionLockManager->clientLocked() || g_pSessionLockManager->clientDenied();
if (RENDERPRIMER)
renderSessionLockPrimer(pMonitor);
const auto PSLS = g_pSessionLockManager->getSessionLockSurfaceForMonitor(pMonitor->m_id);
const bool RENDERLOCKMISSING = (PSLS.expired() || g_pSessionLockManager->clientDenied()) && g_pSessionLockManager->shallConsiderLockMissing();
2023-02-03 11:58:55 +00:00
ensureLockTexturesRendered(RENDERLOCKMISSING);
if (RENDERLOCKMISSING)
renderSessionLockMissing(pMonitor);
else if (PSLS) {
renderSessionLockSurface(PSLS, pMonitor, now);
g_pSessionLockManager->onLockscreenRenderedOnMonitor(pMonitor->m_id);
// render layers and then their popups for abovelock rule
for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
renderLayer(ls.lock(), pMonitor, now, false, true);
}
}
for (auto const& lsl : pMonitor->m_layerSurfaceLayers) {
for (auto const& ls : lsl) {
renderLayer(ls.lock(), pMonitor, now, true, true);
}
2023-02-03 11:58:55 +00:00
}
}
2022-03-17 20:22:29 +01:00
}
2022-03-19 14:07:18 +01:00
void IHyprRenderer::renderSessionLockPrimer(PHLMONITOR pMonitor) {
static auto PSESSIONLOCKXRAY = CConfigValue<Hyprlang::INT>("misc:session_lock_xray");
if (*PSESSIONLOCKXRAY)
return;
CRectPassElement::SRectData data;
data.color = CHyprColor(0, 0, 0, 1.f);
data.box = CBox{{}, pMonitor->m_pixelSize};
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
void IHyprRenderer::renderSessionLockMissing(PHLMONITOR pMonitor) {
const bool ANY_PRESENT = g_pSessionLockManager->anySessionLockSurfacesPresent();
// ANY_PRESENT: render image2, without instructions. Lock still "alive", unless texture dead
// else: render image, with instructions. Lock is gone.
CBox monbox = {{}, pMonitor->m_pixelSize};
CTexPassElement::SRenderData data;
data.tex = (ANY_PRESENT) ? m_lockDead2Texture : m_lockDeadTexture;
data.box = monbox;
data.a = 1;
m_renderPass.add(makeUnique<CTexPassElement>(data));
if (!ANY_PRESENT && m_lockTtyTextTexture) {
// also render text for the tty number
CBox texbox = {{}, m_lockTtyTextTexture->m_size};
data.tex = m_lockTtyTextTexture;
data.box = texbox;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
}
static std::optional<Vector2D> getSurfaceExpectedSize(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface, PHLMONITOR pMonitor, bool main) {
const auto CAN_USE_WINDOW = pWindow && main;
const auto WINDOW_SIZE_MISALIGN = CAN_USE_WINDOW && pWindow->getReportedSize() != pWindow->wlSurface()->resource()->m_current.size;
if (pSurface->m_current.viewport.hasDestination)
return (pSurface->m_current.viewport.destination * pMonitor->m_scale).round();
if (pSurface->m_current.viewport.hasSource)
return (pSurface->m_current.viewport.source.size() * pMonitor->m_scale).round();
if (WINDOW_SIZE_MISALIGN)
return (pSurface->m_current.size * pMonitor->m_scale).round();
if (CAN_USE_WINDOW)
return (pWindow->getReportedSize() * pMonitor->m_scale).round();
return std::nullopt;
}
void IHyprRenderer::calculateUVForSurface(PHLWINDOW pWindow, SP<CWLSurfaceResource> pSurface, PHLMONITOR pMonitor, bool main, const Vector2D& projSize,
const Vector2D& projSizeUnscaled, bool fixMisalignedFSV1) {
if (!pWindow || !pWindow->m_isX11) {
static auto PEXPANDEDGES = CConfigValue<Hyprlang::INT>("render:expand_undersized_textures");
Vector2D uvTL;
Vector2D uvBR = Vector2D(1, 1);
if (pSurface->m_current.viewport.hasSource) {
// we stretch it to dest. if no dest, to 1,1
Vector2D const& bufferSize = pSurface->m_current.bufferSize;
auto const& bufferSource = pSurface->m_current.viewport.source;
2023-01-29 15:58:36 +00:00
// calculate UV for the basic src_box. Assume dest == size. Scale to dest later
uvTL = Vector2D(bufferSource.x / bufferSize.x, bufferSource.y / bufferSize.y);
uvBR = Vector2D((bufferSource.x + bufferSource.width) / bufferSize.x, (bufferSource.y + bufferSource.height) / bufferSize.y);
2022-08-28 14:32:06 +02:00
if (uvBR.x < 0.01f || uvBR.y < 0.01f) {
uvTL = Vector2D();
uvBR = Vector2D(1, 1);
2022-08-28 14:32:06 +02:00
}
}
if (projSize != Vector2D{} && fixMisalignedFSV1) {
// instead of nearest_neighbor (we will repeat / skip)
// just cut off / expand surface
const Vector2D PIXELASUV = Vector2D{1, 1} / pSurface->m_current.bufferSize;
const auto& BUFFER_SIZE = pSurface->m_current.bufferSize;
// compute MISALIGN from the adjusted UV coordinates.
const Vector2D MISALIGNMENT = (uvBR - uvTL) * BUFFER_SIZE - projSize;
if (MISALIGNMENT != Vector2D{})
uvBR -= MISALIGNMENT * PIXELASUV;
} else {
// if the surface is smaller than our viewport, extend its edges.
// this will break if later on xdg geometry is hit, but we really try
// to let the apps know to NOT add CSD. Also if source is there.
// there is no way to fix this if that's the case
const auto MONITOR_WL_SCALE = std::ceil(pMonitor->m_scale);
const bool SCALE_UNAWARE = pMonitor->m_scale != 1.f && (MONITOR_WL_SCALE == pSurface->m_current.scale || !pSurface->m_current.viewport.hasDestination);
const auto EXPECTED_SIZE = getSurfaceExpectedSize(pWindow, pSurface, pMonitor, main).value_or((projSize * pMonitor->m_scale).round());
const auto RATIO = projSize / EXPECTED_SIZE;
if (!SCALE_UNAWARE || MONITOR_WL_SCALE == 1) {
if (*PEXPANDEDGES && !SCALE_UNAWARE && (RATIO.x > 1 || RATIO.y > 1)) {
const auto FIX = RATIO.clamp(Vector2D{1, 1}, Vector2D{1000000, 1000000});
uvBR = uvBR * FIX;
}
// FIXME: probably do this for in anims on all views...
const auto SHOULD_SKIP = !pWindow || pWindow->m_animatingIn;
if (!SHOULD_SKIP && (RATIO.x < 1 || RATIO.y < 1)) {
const auto FIX = RATIO.clamp(Vector2D{0.0001, 0.0001}, Vector2D{1, 1});
uvBR = uvBR * FIX;
}
}
}
m_renderData.primarySurfaceUVTopLeft = uvTL;
m_renderData.primarySurfaceUVBottomRight = uvBR;
if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) {
// No special UV mods needed
m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1);
m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);
}
2023-01-29 13:58:47 +00:00
if (!main || !pWindow)
return;
// FIXME: this doesn't work. We always set MAXIMIZED anyways, so this doesn't need to work, but it's problematic.
// CBox geom = pWindow->m_xdgSurface->m_current.geometry;
// // Adjust UV based on the xdg_surface geometry
// if (geom.x != 0 || geom.y != 0 || geom.w != 0 || geom.h != 0) {
// const auto XPERC = geom.x / pSurface->m_current.size.x;
// const auto YPERC = geom.y / pSurface->m_current.size.y;
// const auto WPERC = (geom.x + geom.w ? geom.w : pSurface->m_current.size.x) / pSurface->m_current.size.x;
// const auto HPERC = (geom.y + geom.h ? geom.h : pSurface->m_current.size.y) / pSurface->m_current.size.y;
// const auto TOADDTL = Vector2D(XPERC * (uvBR.x - uvTL.x), YPERC * (uvBR.y - uvTL.y));
// uvBR = uvBR - Vector2D((1.0 - WPERC) * (uvBR.x - uvTL.x), (1.0 - HPERC) * (uvBR.y - uvTL.y));
// uvTL = uvTL + TOADDTL;
// }
m_renderData.primarySurfaceUVTopLeft = uvTL;
m_renderData.primarySurfaceUVBottomRight = uvBR;
if (m_renderData.primarySurfaceUVTopLeft == Vector2D() && m_renderData.primarySurfaceUVBottomRight == Vector2D(1, 1)) {
// No special UV mods needed
m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1);
m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);
}
} else {
m_renderData.primarySurfaceUVTopLeft = Vector2D(-1, -1);
m_renderData.primarySurfaceUVBottomRight = Vector2D(-1, -1);
}
}
bool IHyprRenderer::beginRender(PHLMONITOR pMonitor, CRegion& damage, eRenderMode mode, SP<IHLBuffer> buffer, SP<IFramebuffer> fb, bool simple) {
m_renderPass.clear();
m_renderMode = mode;
m_renderData.pMonitor = pMonitor;
if (simple)
setProjectionType(fb ? fb->m_size : buffer->m_texture->m_size);
else
setProjectionType(RPT_MONITOR);
if (!simple) {
const auto DRM_FORMAT = fb ? fb->m_drmFormat : pMonitor->m_output->state->state().drmFormat;
// ensure a framebuffer for the monitor exists
if (!m_renderData.pMonitor->m_offloadFB || m_renderData.pMonitor->m_offloadFB->m_size != pMonitor->m_pixelSize ||
DRM_FORMAT != m_renderData.pMonitor->m_offloadFB->m_drmFormat) {
if (!m_renderData.pMonitor->m_stencilTex || m_renderData.pMonitor->m_stencilTex->m_size != pMonitor->m_pixelSize)
m_renderData.pMonitor->m_stencilTex = createStencilTexture(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y);
m_renderData.pMonitor->m_offloadFB = createFB("offload");
m_renderData.pMonitor->m_mirrorFB = createFB("mirror");
m_renderData.pMonitor->m_mirrorSwapFB = createFB("mirrorSwap");
m_renderData.pMonitor->m_offMainFB = createFB("offMain");
m_renderData.pMonitor->m_monitorMirrorFB = createFB("monitorMirror");
m_renderData.pMonitor->m_blurFB = createFB("blur");
// add stencil before FB allocation to avoid reallocs
m_renderData.pMonitor->m_offloadFB->addStencil(m_renderData.pMonitor->m_stencilTex);
m_renderData.pMonitor->m_mirrorFB->addStencil(m_renderData.pMonitor->m_stencilTex);
m_renderData.pMonitor->m_mirrorSwapFB->addStencil(m_renderData.pMonitor->m_stencilTex);
m_renderData.pMonitor->m_offMainFB->addStencil(m_renderData.pMonitor->m_stencilTex);
m_renderData.pMonitor->m_offloadFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pMonitor->m_mirrorFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pMonitor->m_mirrorSwapFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
m_renderData.pMonitor->m_offMainFB->alloc(pMonitor->m_pixelSize.x, pMonitor->m_pixelSize.y, DRM_FORMAT);
}
}
const bool HAS_MIRROR_FB = g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB && g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->isAllocated();
const bool NEEDS_COPY_FB = needsACopyFB(g_pHyprRenderer->m_renderData.pMonitor.lock());
if (HAS_MIRROR_FB && !NEEDS_COPY_FB)
g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->release();
else if (!HAS_MIRROR_FB && NEEDS_COPY_FB && g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB)
g_pHyprRenderer->m_renderData.pMonitor->m_monitorMirrorFB->alloc(g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.x,
g_pHyprRenderer->m_renderData.pMonitor->m_pixelSize.y,
g_pHyprRenderer->m_renderData.pMonitor->m_output->state->state().drmFormat);
if (m_renderMode == RENDER_MODE_FULL_FAKE)
return beginFullFakeRenderInternal(pMonitor, damage, fb, simple);
int bufferAge = 0;
if (!buffer) {
m_currentBuffer = pMonitor->m_output->swapchain->next(&bufferAge);
if (!m_currentBuffer) {
Log::logger->log(Log::ERR, "Failed to acquire swapchain buffer for {}", pMonitor->m_name);
return false;
}
} else
m_currentBuffer = buffer;
initRender();
if (!initRenderBuffer(m_currentBuffer, pMonitor->m_output->state->state().drmFormat)) {
Log::logger->log(Log::ERR, "failed to start a render pass for output {}, no RBO could be obtained", pMonitor->m_name);
return false;
}
if (m_renderMode == RENDER_MODE_NORMAL) {
damage = pMonitor->m_damage.getBufferDamage(bufferAge);
pMonitor->m_damage.rotate();
}
const auto res = beginRenderInternal(pMonitor, damage, simple);
static bool initial = true;
if (initial) {
initAssets();
initial = false;
}
return res;
}
void IHyprRenderer::setDamage(const CRegion& damage_, std::optional<CRegion> finalDamage) {
m_renderData.damage.set(damage_);
m_renderData.finalDamage.set(finalDamage.value_or(damage_));
}
static Mat3x3 getMirrorProjection(PHLMONITORREF monitor) {
return Mat3x3::identity()
.translate(monitor->m_pixelSize / 2.0)
.transform(Math::wlTransformToHyprutils(monitor->m_transform))
.transform(Math::wlTransformToHyprutils(Math::invertTransform(monitor->m_mirrorOf->m_transform)))
.translate(-monitor->m_transformedSize / 2.0);
}
static Mat3x3 getFBProjection(PHLMONITORREF pMonitor, const Vector2D& size) {
if (pMonitor->m_transform == WL_OUTPUT_TRANSFORM_NORMAL)
return Mat3x3::identity();
const Vector2D tfmd = pMonitor->m_transform % 2 == 1 ? Vector2D{size.y, size.x} : size;
return Mat3x3::identity().translate(size / 2.0).transform(Math::wlTransformToHyprutils(pMonitor->m_transform)).translate(-tfmd / 2.0);
}
void IHyprRenderer::setProjectionType(const Vector2D& fbSize) {
m_renderData.fbSize = fbSize;
setProjectionType(RPT_FB);
}
void IHyprRenderer::setProjectionType(eRenderProjectionType projectionType) {
m_renderData.projectionType = projectionType;
switch (projectionType) {
case RPT_MONITOR: m_renderData.targetProjection = m_renderData.pMonitor->getTransformMatrix(); break;
case RPT_MIRROR: m_renderData.targetProjection = getMirrorProjection(m_renderData.pMonitor); break;
case RPT_FB: m_renderData.targetProjection = getFBProjection(m_renderData.pMonitor, m_renderData.fbSize); break;
default: UNREACHABLE();
}
}
Mat3x3 IHyprRenderer::getBoxProjection(const CBox& box, std::optional<eTransform> transform) {
return m_renderData.targetProjection.projectBox(
box, transform.value_or(Math::wlTransformToHyprutils(Math::invertTransform(!monitorTransformEnabled() ? WL_OUTPUT_TRANSFORM_NORMAL : m_renderData.pMonitor->m_transform))),
box.rot);
}
Mat3x3 IHyprRenderer::projectBoxToTarget(const CBox& box, std::optional<eTransform> transform) {
return m_renderData.pMonitor->getScaleMatrix().copy().multiply(getBoxProjection(box, transform));
}
SP<ITexture> IHyprRenderer::blurMainFramebuffer(float a, CRegion* originalDamage) {
if (!m_renderData.currentFB->getTexture()) {
Log::logger->log(Log::ERR, "BUG THIS: null fb texture while attempting to blur main fb?! (introspection off?!)");
return m_renderData.pMonitor->m_mirrorFB->getTexture(); // return something to sample from at least
}
return blurFramebuffer(m_renderData.currentFB, a, originalDamage);
}
void IHyprRenderer::preBlurForCurrentMonitor(CRegion* fakeDamage) {
const auto blurredTex = blurMainFramebuffer(1, fakeDamage);
// render onto blurFB
if (!m_renderData.pMonitor->m_blurFB)
return;
m_renderData.pMonitor->m_blurFB->alloc(m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y, m_renderData.pMonitor->m_output->state->state().drmFormat);
m_renderData.pMonitor->m_blurFB->bind();
draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{{0, 0, 0, 0}}), {});
pushMonitorTransformEnabled(true);
draw(makeUnique<CTexPassElement>(CTexPassElement::SRenderData{
.tex = blurredTex,
.box = CBox{0, 0, m_renderData.pMonitor->m_transformedSize.x, m_renderData.pMonitor->m_transformedSize.y},
.damage = *fakeDamage,
}),
*fakeDamage); // .noAA = true
popMonitorTransformEnabled();
m_renderData.currentFB->bind();
}
static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {
// might be too strict
return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||
imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22) &&
(targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||
targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG);
}
static bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {
// might be too strict
return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||
imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG) &&
(targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||
targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);
}
SCMSettings IHyprRenderer::getCMSettings(const NColorManagement::PImageDescription imageDescription, const NColorManagement::PImageDescription targetImageDescription,
SP<CWLSurfaceResource> surface, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {
const auto sdrEOTF = NTransferFunction::fromConfig();
NColorManagement::eTransferFunction srcTF;
if (m_renderData.surface.valid()) {
if (m_renderData.surface->m_colorManagement.valid()) {
if (sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22 && imageDescription->value().transferFunction == NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB)
srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22;
else
srcTF = imageDescription->value().transferFunction;
} else if (sdrEOTF == NTransferFunction::TF_SRGB)
srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_SRGB;
else if (sdrEOTF == NTransferFunction::TF_GAMMA22 || sdrEOTF == NTransferFunction::TF_FORCED_GAMMA22)
srcTF = NColorManagement::eTransferFunction::CM_TRANSFER_FUNCTION_GAMMA22;
else
srcTF = imageDescription->value().transferFunction;
} else
srcTF = imageDescription->value().transferFunction;
const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription->value(), targetImageDescription->value());
const bool needsHDRmod = !needsSDRmod && isHDR2SDR(imageDescription->value(), targetImageDescription->value());
const float maxLuminance = needsHDRmod ?
imageDescription->value().getTFMaxLuminance(-1) :
(imageDescription->value().luminances.max > 0 ? imageDescription->value().luminances.max : imageDescription->value().luminances.reference);
const auto dstMaxLuminance = targetImageDescription->value().luminances.max > 0 ? targetImageDescription->value().luminances.max : 10000;
auto matrix = imageDescription->getPrimaries()->convertMatrix(targetImageDescription->getPrimaries());
auto toXYZ = targetImageDescription->getPrimaries()->value().toXYZ();
const bool needsMod = (imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_SRGB || imageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_GAMMA22) &&
targetImageDescription->value().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ &&
((m_renderData.pMonitor->m_sdrSaturation > 0 && m_renderData.pMonitor->m_sdrSaturation != 1.0f) ||
(m_renderData.pMonitor->m_sdrBrightness > 0 && m_renderData.pMonitor->m_sdrBrightness != 1.0f));
return {
.sourceTF = srcTF,
.targetTF = targetImageDescription->value().transferFunction,
.srcTFRange = {.min = imageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),
.max = imageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)},
.dstTFRange = {.min = targetImageDescription->value().getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),
.max = targetImageDescription->value().getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)},
.srcRefLuminance = imageDescription->value().luminances.reference,
.dstRefLuminance = targetImageDescription->value().luminances.reference,
.convertMatrix = matrix.mat(),
.needsTonemap = maxLuminance >= dstMaxLuminance * 1.01,
.maxLuminance = maxLuminance * targetImageDescription->value().luminances.reference / imageDescription->value().luminances.reference,
.dstMaxLuminance = dstMaxLuminance,
.dstPrimaries2XYZ = toXYZ.mat(),
.needsSDRmod = needsMod,
.sdrSaturation = needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f,
.sdrBrightnessMultiplier = needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f,
};
}
void IHyprRenderer::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(Math::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;
if (!monitor->m_monitorMirrorFB)
monitor->m_monitorMirrorFB = createFB("monitorMirror");
const auto PFB = mirrored->m_monitorMirrorFB;
if (!PFB || !PFB->isAllocated() || !PFB->getTexture())
return;
m_renderPass.add(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}));
CTexPassElement::SRenderData data;
data.tex = PFB->getTexture();
data.box = monbox;
data.useMirrorProjection = true;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
static std::chrono::high_resolution_clock::time_point renderStart = std::chrono::high_resolution_clock::now();
static std::chrono::high_resolution_clock::time_point renderStartOverlay = std::chrono::high_resolution_clock::now();
static std::chrono::high_resolution_clock::time_point endRenderOverlay = std::chrono::high_resolution_clock::now();
static auto PDEBUGOVERLAY = CConfigValue<Hyprlang::INT>("debug:overlay");
static auto PDAMAGETRACKINGMODE = CConfigValue<Hyprlang::INT>("debug:damage_tracking");
static auto PDAMAGEBLINK = CConfigValue<Hyprlang::INT>("debug:damage_blink");
static auto PSOLDAMAGE = CConfigValue<Hyprlang::INT>("debug:render_solitary_wo_damage");
static auto PVFR = CConfigValue<Hyprlang::INT>("misc:vfr");
static int damageBlinkCleanup = 0; // because double-buffered
const float ZOOMFACTOR = pMonitor->m_cursorZoom->value();
if (pMonitor->m_pixelSize.x < 1 || pMonitor->m_pixelSize.y < 1) {
Log::logger->log(Log::ERR, "Refusing to render a monitor because of an invalid pixel size: {}", pMonitor->m_pixelSize);
return;
}
if (!*PDAMAGEBLINK)
damageBlinkCleanup = 0;
if (*PDEBUGOVERLAY == 1) {
renderStart = std::chrono::high_resolution_clock::now();
g_pDebugOverlay->frameData(pMonitor);
}
if (!g_pCompositor->m_sessionActive)
return;
if (g_pAnimationManager)
g_pAnimationManager->frameTick();
if (pMonitor->m_id == m_mostHzMonitor->m_id ||
*PVFR == 1) { // unfortunately with VFR we don't have the guarantee mostHz is going to be updated all the time, so we have to ignore that
g_pConfigManager->dispatchExecOnce(); // We exec-once when at least one monitor starts refreshing, meaning stuff has init'd
if (g_pConfigManager->m_wantsMonitorReload)
g_pConfigManager->performMonitorReload();
}
if (pMonitor->m_scheduledRecalc) {
pMonitor->m_scheduledRecalc = false;
if (pMonitor->m_activeWorkspace) // might be missing (mirror)
pMonitor->m_activeWorkspace->m_space->recalculate();
}
if (!pMonitor->m_output->needsFrame && pMonitor->m_forceFullFrames == 0)
return;
// tearing and DS first
bool shouldTear = pMonitor->updateTearing();
if (pMonitor->attemptDirectScanout()) {
2026-02-04 15:42:43 +03:00
pMonitor->m_directScanoutIsActive = true;
return;
2026-02-04 15:42:43 +03:00
} else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) {
Log::logger->log(Log::DEBUG, "Left a direct scanout.");
pMonitor->m_lastScanout.reset();
2026-02-04 15:42:43 +03:00
pMonitor->m_directScanoutIsActive = false;
// reset DRM format, but only if needed since it might modeset
if (pMonitor->m_output->state->state().drmFormat != pMonitor->m_prevDrmFormat)
pMonitor->m_output->state->setFormat(pMonitor->m_prevDrmFormat);
pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat;
}
Event::bus()->m_events.render.pre.emit(pMonitor);
const auto NOW = Time::steadyNow();
// check the damage
bool hasChanged = pMonitor->m_output->needsFrame || pMonitor->m_damage.hasChanged();
if (!hasChanged && *PDAMAGETRACKINGMODE != DAMAGE_TRACKING_NONE && pMonitor->m_forceFullFrames == 0 && damageBlinkCleanup == 0)
2023-04-09 17:59:24 +01:00
return;
if (*PDAMAGETRACKINGMODE == -1) {
Log::logger->log(Log::CRIT, "Damage tracking mode -1 ????");
return;
}
Event::bus()->m_events.render.stage.emit(RENDER_PRE);
2023-04-17 23:45:03 +01:00
pMonitor->m_renderingActive = true;
2024-02-19 18:17:32 +00:00
// we need to cleanup fading out when rendering the appropriate context
g_pCompositor->cleanupFadingOut(pMonitor->m_id);
2024-02-19 18:17:32 +00:00
// TODO: this is getting called with extents being 0,0,0,0 should it be?
// potentially can save on resources.
TRACY_GPU_ZONE("Render");
static bool zoomLock = false;
if (zoomLock && ZOOMFACTOR == 1.f) {
g_pPointerManager->unlockSoftwareAll();
zoomLock = false;
} else if (!zoomLock && ZOOMFACTOR != 1.f) {
g_pPointerManager->lockSoftwareAll();
zoomLock = true;
}
if (pMonitor == g_pCompositor->getMonitorFromCursor())
m_renderData.mouseZoomFactor = std::clamp(ZOOMFACTOR, 1.f, INFINITY);
else
m_renderData.mouseZoomFactor = 1.f;
if (pMonitor->m_zoomAnimProgress->value() != 1) {
m_renderData.mouseZoomFactor = 2.0 - pMonitor->m_zoomAnimProgress->value(); // 2x zoom -> 1x zoom
m_renderData.mouseZoomUseMouse = false;
m_renderData.useNearestNeighbor = false;
}
CRegion damage, finalDamage;
if (!beginRender(pMonitor, damage, RENDER_MODE_NORMAL)) {
Log::logger->log(Log::ERR, "renderer: couldn't beginRender()!");
return;
}
// if we have no tracking or full tracking, invalidate the entire monitor
if (*PDAMAGETRACKINGMODE == DAMAGE_TRACKING_NONE || *PDAMAGETRACKINGMODE == DAMAGE_TRACKING_MONITOR || pMonitor->m_forceFullFrames > 0 || damageBlinkCleanup > 0)
damage = {0, 0, sc<int>(pMonitor->m_transformedSize.x) * 10, sc<int>(pMonitor->m_transformedSize.y) * 10};
finalDamage = damage;
2024-02-19 18:17:32 +00:00
// update damage in renderdata as we modified it
setDamage(damage, finalDamage);
if (pMonitor->m_forceFullFrames > 0) {
pMonitor->m_forceFullFrames -= 1;
if (pMonitor->m_forceFullFrames > 10)
pMonitor->m_forceFullFrames = 0;
2024-02-19 18:17:32 +00:00
}
Event::bus()->m_events.render.stage.emit(RENDER_BEGIN);
2023-04-17 23:45:03 +01:00
bool renderCursor = true;
if (pMonitor->m_solitaryClient && (!finalDamage.empty() || *PSOLDAMAGE))
renderWindow(pMonitor->m_solitaryClient.lock(), pMonitor, NOW, false, RENDER_PASS_MAIN /* solitary = no popups */);
else if (!finalDamage.empty()) {
if (pMonitor->isMirror()) {
blend(false);
renderMirrored();
blend(true);
Event::bus()->m_events.render.stage.emit(RENDER_POST_MIRROR);
renderCursor = false;
} else {
CBox renderBox = {0, 0, sc<int>(pMonitor->m_pixelSize.x), sc<int>(pMonitor->m_pixelSize.y)};
renderWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW, renderBox);
renderLockscreen(pMonitor, NOW, renderBox);
if (pMonitor == Desktop::focusState()->monitor()) {
g_pHyprNotificationOverlay->draw(pMonitor);
g_pHyprError->draw();
}
// for drawing the debug overlay
if (pMonitor == g_pCompositor->m_monitors.front() && *PDEBUGOVERLAY == 1) {
renderStartOverlay = std::chrono::high_resolution_clock::now();
g_pDebugOverlay->draw();
endRenderOverlay = std::chrono::high_resolution_clock::now();
}
if (*PDAMAGEBLINK && damageBlinkCleanup == 0) {
CRectPassElement::SRectData data;
data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};
data.color = CHyprColor(1.0, 0.0, 1.0, 100.0 / 255.0);
m_renderPass.add(makeUnique<CRectPassElement>(data));
damageBlinkCleanup = 1;
} else if (*PDAMAGEBLINK) {
damageBlinkCleanup++;
if (damageBlinkCleanup > 3)
damageBlinkCleanup = 0;
}
}
} else if (!pMonitor->isMirror()) {
sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeWorkspace, NOW);
if (pMonitor->m_activeSpecialWorkspace)
sendFrameEventsToWorkspace(pMonitor, pMonitor->m_activeSpecialWorkspace, NOW);
}
renderCursor = renderCursor && shouldRenderCursor();
if (renderCursor) {
TRACY_GPU_ZONE("RenderCursor");
g_pPointerManager->renderSoftwareCursorsFor(pMonitor->m_self.lock(), NOW, m_renderData.damage);
2023-05-01 02:49:41 +01:00
}
2025-08-17 08:37:13 +01:00
if (pMonitor->m_dpmsBlackOpacity->value() != 0.F) {
// render the DPMS black if we are animating
CRectPassElement::SRectData data;
data.box = {0, 0, pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y};
data.color = Colors::BLACK.modifyA(pMonitor->m_dpmsBlackOpacity->value());
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
Event::bus()->m_events.render.stage.emit(RENDER_LAST_MOMENT);
2023-04-17 23:45:03 +01:00
endRender();
2023-07-20 17:47:49 +02:00
TRACY_GPU_COLLECT;
CRegion frameDamage{m_renderData.damage};
const auto TRANSFORM = Math::invertTransform(pMonitor->m_transform);
frameDamage.transform(Math::wlTransformToHyprutils(TRANSFORM), pMonitor->m_transformedSize.x, pMonitor->m_transformedSize.y);
if (*PDAMAGETRACKINGMODE == DAMAGE_TRACKING_NONE || *PDAMAGETRACKINGMODE == DAMAGE_TRACKING_MONITOR)
frameDamage.add(0, 0, sc<int>(pMonitor->m_transformedSize.x), sc<int>(pMonitor->m_transformedSize.y));
if (*PDAMAGEBLINK)
frameDamage.add(damage);
if (!pMonitor->m_mirrors.empty())
damageMirrorsWith(pMonitor, frameDamage);
2024-07-30 15:46:35 +02:00
pMonitor->m_renderingActive = false;
Event::bus()->m_events.render.stage.emit(RENDER_POST);
2023-04-17 23:45:03 +01:00
pMonitor->m_output->state->addDamage(frameDamage);
pMonitor->m_output->state->setPresentationMode(shouldTear ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE :
Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC);
if (commit)
commitPendingAndDoExplicitSync(pMonitor);
if (shouldTear)
pMonitor->m_tearingState.busy = true;
if (*PDAMAGEBLINK || *PVFR == 0 || pMonitor->m_pendingFrame)
g_pCompositor->scheduleFrameForMonitor(pMonitor, Aquamarine::IOutput::AQ_SCHEDULE_RENDER_MONITOR);
pMonitor->m_pendingFrame = false;
if (*PDEBUGOVERLAY == 1) {
const float durationUs = std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::high_resolution_clock::now() - renderStart).count() / 1000.f;
g_pDebugOverlay->renderData(pMonitor, durationUs);
if (pMonitor == g_pCompositor->m_monitors.front()) {
const float noOverlayUs = durationUs - std::chrono::duration_cast<std::chrono::nanoseconds>(endRenderOverlay - renderStartOverlay).count() / 1000.f;
g_pDebugOverlay->renderDataNoOverlay(pMonitor, noOverlayUs);
} else
g_pDebugOverlay->renderDataNoOverlay(pMonitor, durationUs);
}
}
static const hdr_output_metadata NO_HDR_METADATA = {.hdmi_metadata_type1 = hdr_metadata_infoframe{.eotf = 0}};
static hdr_output_metadata createHDRMetadata(SImageDescription settings, SP<CMonitor> monitor) {
uint8_t eotf = 0;
switch (settings.transferFunction) {
case CM_TRANSFER_FUNCTION_GAMMA22:
case CM_TRANSFER_FUNCTION_SRGB: eotf = 0; break; // used to send primaries and luminances to AQ. ignored for now
case CM_TRANSFER_FUNCTION_ST2084_PQ: eotf = 2; break;
case CM_TRANSFER_FUNCTION_EXT_LINEAR:
eotf = 2;
break; // should be Windows scRGB
// case CM_TRANSFER_FUNCTION_HLG: eotf = 3; break; TODO check display capabilities first
default: return NO_HDR_METADATA; // empty metadata for SDR
}
const auto toNits = [](uint32_t value) { return sc<uint16_t>(std::round(value)); };
const auto to16Bit = [](float value) { return sc<uint16_t>(std::round(value * 50000)); };
auto colorimetry = settings.getPrimaries();
auto luminances = settings.masteringLuminances.max > 0 ? settings.masteringLuminances :
(settings.luminances != SImageDescription::SPCLuminances{} ?
SImageDescription::SPCMasteringLuminances{.min = settings.luminances.min, .max = settings.luminances.max} :
SImageDescription::SPCMasteringLuminances{.min = monitor->minLuminance(), .max = monitor->maxLuminance(10000)});
Log::logger->log(Log::TRACE, "ColorManagement primaries {},{} {},{} {},{} {},{}", colorimetry.red.x, colorimetry.red.y, colorimetry.green.x, colorimetry.green.y,
colorimetry.blue.x, colorimetry.blue.y, colorimetry.white.x, colorimetry.white.y);
Log::logger->log(Log::TRACE, "ColorManagement min {}, max {}, cll {}, fall {}", luminances.min, luminances.max, settings.maxCLL, settings.maxFALL);
return hdr_output_metadata{
.metadata_type = 0,
.hdmi_metadata_type1 =
hdr_metadata_infoframe{
.eotf = eotf,
.metadata_type = 0,
.display_primaries =
{
{.x = to16Bit(colorimetry.red.x), .y = to16Bit(colorimetry.red.y)},
{.x = to16Bit(colorimetry.green.x), .y = to16Bit(colorimetry.green.y)},
{.x = to16Bit(colorimetry.blue.x), .y = to16Bit(colorimetry.blue.y)},
},
.white_point = {.x = to16Bit(colorimetry.white.x), .y = to16Bit(colorimetry.white.y)},
.max_display_mastering_luminance = toNits(luminances.max),
.min_display_mastering_luminance = toNits(luminances.min * 10000),
.max_cll = toNits(settings.maxCLL > 0 ? settings.maxCLL : monitor->maxCLL()),
.max_fall = toNits(settings.maxFALL > 0 ? settings.maxFALL : monitor->maxFALL()),
},
};
}
bool IHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) {
static auto PCT = CConfigValue<Hyprlang::INT>("render:send_content_type");
static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough");
static auto PAUTOHDR = CConfigValue<Hyprlang::INT>("render:cm_auto_hdr");
static auto PNONSHADER = CConfigValue<Hyprlang::INT>("render:non_shader_cm");
2025-06-23 15:33:09 +03:00
const bool configuredHDR = (pMonitor->m_cmType == NCMType::CM_HDR_EDID || pMonitor->m_cmType == NCMType::CM_HDR);
2025-06-23 15:33:09 +03:00
bool wantHDR = configuredHDR;
fix: handle fullscreen windows on special workspaces (#12851) * fix: handle fullscreen windows on special workspaces inFullscreenMode() only checked m_activeWorkspace, missing fullscreen windows on special workspaces. This caused crashes and incorrect behavior when fullscreen windows were on special workspaces. Changes: - inFullscreenMode() now checks special workspace first since it renders on top of regular workspaces - Added getFullscreenWindow() helper to safely get fullscreen window from either active or special workspace - Updated callers (shouldSkipScheduleFrameOnMouseEvent, Renderer, getFSImageDescription) to use the new helper - Reset m_aboveFullscreen for layer surfaces when opening, closing, or stealing special workspaces between monitors * test: add special workspace fullscreen detection tests Add tests for the new special workspace fullscreen handling introduced in the previous commit. The tests cover: 1. Fullscreen detection on special workspace - verifies that a window made fullscreen on a special workspace is correctly detected 2. Special workspace fullscreen precedence - verifies that when both regular and special workspaces have fullscreen windows, the special workspace window can be focused when the special workspace is opened 3. Toggle special workspace behavior - verifies that toggling the special workspace off properly hides it and returns focus to the regular workspace's fullscreen window These tests exercise the key code paths modified in the fix: - inFullscreenMode() checking special workspace first - getFullscreenWindow() helper returning correct window - Layer surface m_aboveFullscreen reset on special workspace toggle
2026-01-08 21:27:00 +00:00
const auto FS_WINDOW = pMonitor->getFullscreenWindow();
if (pMonitor->supportsHDR()) {
// HDR metadata determined by
// HDR scRGB - monitor settings
// HDR PQ surface & DS is active - surface settings
// PPASS = 0 monitor settings
// PPASS = 1
// windowed: monitor settings
// fullscreen surface: surface settings FIXME: fullscreen SDR surface passthrough - pass degamma, gamma if needed
// PPASS = 2
// windowed: monitor settings
// fullscreen SDR surface: monitor settings
// fullscreen HDR surface: surface settings
bool hdrIsHandled = false;
if (FS_WINDOW) {
const auto ROOT_SURF = FS_WINDOW->wlSurface()->resource();
const auto SURF = ROOT_SURF->findWithCM();
2025-06-23 15:33:09 +03:00
// we have a surface with image description
if (SURF && SURF->m_colorManagement.valid() && SURF->m_colorManagement->hasImageDescription()) {
const bool surfaceIsHDR = SURF->m_colorManagement->isHDR();
if (!SURF->m_colorManagement->isWindowsScRGB() && (*PPASS == 1 || ((*PPASS == 2 || !pMonitor->m_lastScanout.expired()) && surfaceIsHDR))) {
2025-06-23 15:33:09 +03:00
// passthrough
bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != FS_WINDOW || pMonitor->m_needsHDRupdate;
if (SURF->m_colorManagement->needsHdrMetadataUpdate()) {
Log::logger->log(Log::INFO, "[CM] Recreating HDR metadata for surface");
SURF->m_colorManagement->setHDRMetadata(createHDRMetadata(SURF->m_colorManagement->imageDescription(), pMonitor));
}
if (needsHdrMetadataUpdate) {
Log::logger->log(Log::INFO, "[CM] Updating HDR metadata from surface");
2025-06-23 15:33:09 +03:00
pMonitor->m_output->state->setHDRMetadata(SURF->m_colorManagement->hdrMetadata());
}
hdrIsHandled = true;
pMonitor->m_needsHDRupdate = false;
2025-06-23 15:33:09 +03:00
} else if (*PAUTOHDR && surfaceIsHDR)
wantHDR = true; // auto-hdr: hdr on
}
}
2025-06-23 15:33:09 +03:00
if (!hdrIsHandled) {
if (pMonitor->inHDR() != wantHDR) {
if (*PAUTOHDR && !(pMonitor->inHDR() && configuredHDR)) {
2025-06-23 15:33:09 +03:00
// modify or restore monitor image description for auto-hdr
// FIXME ok for now, will need some other logic if monitor image description can be modified some other way
const auto targetCM = wantHDR ? (*PAUTOHDR == 2 ? NCMType::CM_HDR_EDID : NCMType::CM_HDR) : pMonitor->m_cmType;
const auto targetSDREOTF = pMonitor->m_sdrEotf;
Log::logger->log(Log::INFO, "[CM] Auto HDR: changing monitor cm to {}", sc<uint8_t>(targetCM));
pMonitor->applyCMType(targetCM, targetSDREOTF);
pMonitor->m_previousFSWindow.reset(); // trigger CTM update
2025-06-23 15:33:09 +03:00
}
Log::logger->log(Log::INFO, wantHDR ? "[CM] Updating HDR metadata from monitor" : "[CM] Restoring SDR mode");
pMonitor->m_output->state->setHDRMetadata(wantHDR ? createHDRMetadata(pMonitor->m_imageDescription->value(), pMonitor) : NO_HDR_METADATA);
2025-06-23 15:33:09 +03:00
}
pMonitor->m_needsHDRupdate = true;
}
}
const bool needsWCG = pMonitor->wantsWideColor();
if (pMonitor->m_output->state->state().wideColorGamut != needsWCG) {
Log::logger->log(Log::TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off");
pMonitor->m_output->state->setWideColorGamut(needsWCG);
// FIXME do not trust enabled10bit, auto switch to 10bit and back if needed
if (needsWCG && !pMonitor->m_enabled10bit) {
Log::logger->log(Log::WARN, "Wide color gamut is enabled but the display is not in 10bit mode");
static bool shown = false;
if (!shown) {
g_pHyprNotificationOverlay->addNotification(I18n::i18nEngine()->localize(I18n::TXT_KEY_NOTIF_WIDE_COLOR_NOT_10B, {{"name", pMonitor->m_name}}), CHyprColor{}, 15000,
ICON_WARNING);
shown = true;
}
}
}
if (*PCT)
pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE));
if (FS_WINDOW != pMonitor->m_previousFSWindow || (!FS_WINDOW && pMonitor->m_noShaderCTM)) {
if (*PNONSHADER == CM_NS_IGNORE || !FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM() ||
(*PNONSHADER == CM_NS_ONDEMAND && pMonitor->m_lastScanout.expired() && *PPASS != 1)) {
if (pMonitor->m_noShaderCTM) {
Log::logger->log(Log::INFO, "[CM] No fullscreen CTM, restoring previous one");
pMonitor->m_noShaderCTM = false;
pMonitor->m_ctmUpdated = true;
}
} else {
const auto FS_DESC = pMonitor->getFSImageDescription();
if (FS_DESC.has_value()) {
Log::logger->log(Log::INFO, "[CM] Updating fullscreen CTM");
pMonitor->m_noShaderCTM = true;
auto conversion = FS_DESC.value()->getPrimaries()->convertMatrix(pMonitor->m_imageDescription->getPrimaries());
const auto mat = conversion.mat();
const std::array<float, 9> CTM = {
mat[0][0], mat[0][1], mat[0][2], //
mat[1][0], mat[1][1], mat[1][2], //
mat[2][0], mat[2][1], mat[2][2], //
};
pMonitor->m_output->state->setCTM(CTM);
}
}
}
if (pMonitor->m_ctmUpdated && !pMonitor->m_noShaderCTM) {
pMonitor->m_ctmUpdated = false;
pMonitor->m_output->state->setCTM(pMonitor->m_ctm);
}
pMonitor->m_previousFSWindow = FS_WINDOW;
bool ok = pMonitor->m_state.commit();
if (!ok) {
if (pMonitor->m_inFence.isValid()) {
Log::logger->log(Log::TRACE, "Monitor state commit failed, retrying without a fence");
pMonitor->m_output->state->resetExplicitFences();
ok = pMonitor->m_state.commit();
}
if (!ok) {
Log::logger->log(Log::TRACE, "Monitor state commit failed");
// rollback the buffer to avoid writing to the front buffer that is being
// displayed
pMonitor->m_output->swapchain->rollback();
pMonitor->m_damage.damageEntire();
}
}
return ok;
}
void IHyprRenderer::renderWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now, const CBox& geometry) {
Vector2D translate = {geometry.x, geometry.y};
float scale = sc<float>(geometry.width) / pMonitor->m_pixelSize.x;
2023-07-20 17:47:49 +02:00
TRACY_GPU_ZONE("RenderWorkspace");
if (!DELTALESSTHAN(sc<double>(geometry.width) / sc<double>(geometry.height), pMonitor->m_pixelSize.x / pMonitor->m_pixelSize.y, 0.01)) {
Log::logger->log(Log::ERR, "Ignoring geometry in renderWorkspace: aspect ratio mismatch");
scale = 1.f;
translate = Vector2D{};
}
renderAllClientsForWorkspace(pMonitor, pWorkspace, now, translate, scale);
}
void IHyprRenderer::sendFrameEventsToWorkspace(PHLMONITOR pMonitor, PHLWORKSPACE pWorkspace, const Time::steady_tp& now) {
for (const auto& view : Desktop::View::getViewsForWorkspace(pWorkspace)) {
if (!view->aliveAndVisible())
continue;
view->wlSurface()->resource()->frame(now);
}
}
void IHyprRenderer::setSurfaceScanoutMode(SP<CWLSurfaceResource> surface, PHLMONITOR monitor) {
if (!PROTO::linuxDma)
return;
2022-11-05 12:50:47 +00:00
PROTO::linuxDma->updateScanoutTranche(surface, monitor);
2022-11-05 12:50:47 +00:00
}
2022-03-21 17:00:17 +01:00
// taken from Sway.
// this is just too much of a spaghetti for me to understand
static void applyExclusive(CBox& usableArea, uint32_t anchor, int32_t exclusive, uint32_t exclusiveEdge, int32_t marginTop, int32_t marginRight, int32_t marginBottom,
int32_t marginLeft) {
2022-03-21 17:00:17 +01:00
if (exclusive <= 0) {
return;
}
struct {
uint32_t singular_anchor;
uint32_t anchor_triplet;
double* positive_axis;
double* negative_axis;
int margin;
2022-03-21 17:00:17 +01:00
} edges[] = {
// Top
{
.singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
.anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP,
2023-12-19 11:55:56 +00:00
.positive_axis = &usableArea.y,
.negative_axis = &usableArea.height,
.margin = marginTop,
2022-03-21 17:00:17 +01:00
},
// Bottom
{
.singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.positive_axis = nullptr,
2023-12-19 11:55:56 +00:00
.negative_axis = &usableArea.height,
.margin = marginBottom,
2022-03-21 17:00:17 +01:00
},
// Left
{
.singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT,
.anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
2023-12-19 11:55:56 +00:00
.positive_axis = &usableArea.x,
.negative_axis = &usableArea.width,
.margin = marginLeft,
2022-03-21 17:00:17 +01:00
},
// Right
{
.singular_anchor = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT,
.anchor_triplet = ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT | ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM,
.positive_axis = nullptr,
2023-12-19 11:55:56 +00:00
.negative_axis = &usableArea.width,
.margin = marginRight,
2022-03-21 17:00:17 +01:00
},
};
for (size_t i = 0; i < sizeof(edges) / sizeof(edges[0]); ++i) {
if ((exclusiveEdge == edges[i].singular_anchor || anchor == edges[i].singular_anchor || anchor == edges[i].anchor_triplet) && exclusive + edges[i].margin > 0) {
2022-03-21 17:00:17 +01:00
if (edges[i].positive_axis) {
*edges[i].positive_axis += exclusive + edges[i].margin;
}
if (edges[i].negative_axis) {
*edges[i].negative_axis -= exclusive + edges[i].margin;
}
break;
}
}
}
2022-03-19 14:37:40 +01:00
void IHyprRenderer::arrangeLayerArray(PHLMONITOR pMonitor, const std::vector<PHLLSREF>& layerSurfaces, bool exclusiveZone, CBox* usableArea) {
CBox full_area = {pMonitor->m_position.x, pMonitor->m_position.y, pMonitor->m_size.x, pMonitor->m_size.y};
2022-03-19 14:37:40 +01:00
for (auto const& ls : layerSurfaces) {
if (!ls || ls->m_fadingOut || ls->m_readyToDelete || !ls->m_layerSurface || ls->m_noProcess)
continue;
const auto PLAYER = ls->m_layerSurface;
const auto PSTATE = &PLAYER->m_current;
if (exclusiveZone != (PSTATE->exclusive > 0))
2022-03-21 17:00:17 +01:00
continue;
2022-03-19 14:37:40 +01:00
CBox bounds;
if (PSTATE->exclusive == -1)
2022-03-21 17:00:17 +01:00
bounds = full_area;
else
2022-03-21 17:00:17 +01:00
bounds = *usableArea;
2022-03-19 14:37:40 +01:00
const Vector2D OLDSIZE = {ls->m_geometry.width, ls->m_geometry.height};
CBox box = {{}, PSTATE->desiredSize};
2022-03-21 17:00:17 +01:00
// Horizontal axis
const uint32_t both_horiz = ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT | ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT;
2024-10-19 23:47:28 +01:00
if (box.width == 0)
2022-03-21 17:00:17 +01:00
box.x = bounds.x;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & both_horiz) == both_horiz)
2022-03-21 17:00:17 +01:00
box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT))
2022-03-21 17:00:17 +01:00
box.x = bounds.x;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT))
2022-03-21 17:00:17 +01:00
box.x = bounds.x + (bounds.width - box.width);
2024-10-19 23:47:28 +01:00
else
2022-03-21 17:00:17 +01:00
box.x = bounds.x + ((bounds.width / 2) - (box.width / 2));
2024-10-19 23:47:28 +01:00
2022-03-21 17:00:17 +01:00
// Vertical axis
const uint32_t both_vert = ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP | ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM;
2024-10-19 23:47:28 +01:00
if (box.height == 0)
2022-03-21 17:00:17 +01:00
box.y = bounds.y;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & both_vert) == both_vert)
2022-03-21 17:00:17 +01:00
box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP))
2022-03-21 17:00:17 +01:00
box.y = bounds.y;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM))
2022-03-21 17:00:17 +01:00
box.y = bounds.y + (bounds.height - box.height);
2024-10-19 23:47:28 +01:00
else
2022-03-21 17:00:17 +01:00
box.y = bounds.y + ((bounds.height / 2) - (box.height / 2));
2024-10-19 23:47:28 +01:00
2022-03-21 17:00:17 +01:00
// Margin
if (box.width == 0) {
box.x += PSTATE->margin.left;
box.width = bounds.width - (PSTATE->margin.left + PSTATE->margin.right);
2024-10-19 23:47:28 +01:00
} else if ((PSTATE->anchor & both_horiz) == both_horiz)
; // don't apply margins
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_LEFT))
2022-03-21 17:00:17 +01:00
box.x += PSTATE->margin.left;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_RIGHT))
2022-03-21 17:00:17 +01:00
box.x -= PSTATE->margin.right;
2024-10-19 23:47:28 +01:00
2022-03-21 17:00:17 +01:00
if (box.height == 0) {
box.y += PSTATE->margin.top;
box.height = bounds.height - (PSTATE->margin.top + PSTATE->margin.bottom);
2024-10-19 23:47:28 +01:00
} else if ((PSTATE->anchor & both_vert) == both_vert)
; // don't apply margins
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_TOP))
2022-03-21 17:00:17 +01:00
box.y += PSTATE->margin.top;
2024-10-19 23:47:28 +01:00
else if ((PSTATE->anchor & ZWLR_LAYER_SURFACE_V1_ANCHOR_BOTTOM))
2022-03-21 17:00:17 +01:00
box.y -= PSTATE->margin.bottom;
2024-10-19 23:47:28 +01:00
2022-03-21 17:00:17 +01:00
if (box.width <= 0 || box.height <= 0) {
Log::logger->log(Log::ERR, "LayerSurface {:x} has a negative/zero w/h???", rc<uintptr_t>(ls.get()));
2022-03-21 17:00:17 +01:00
continue;
}
box.round(); // fix rounding errors
ls->m_geometry = box;
applyExclusive(*usableArea, PSTATE->anchor, PSTATE->exclusive, PSTATE->exclusiveEdge, PSTATE->margin.top, PSTATE->margin.right, PSTATE->margin.bottom, PSTATE->margin.left);
2022-03-19 14:37:40 +01:00
if (Vector2D{box.width, box.height} != OLDSIZE)
ls->m_layerSurface->configure(box.size());
*ls->m_realPosition = box.pos();
*ls->m_realSize = box.size();
2022-03-19 14:37:40 +01:00
}
}
void IHyprRenderer::arrangeLayersForMonitor(const MONITORID& monitor) {
const auto PMONITOR = g_pCompositor->getMonitorFromID(monitor);
2022-03-19 14:37:40 +01:00
if (!PMONITOR || PMONITOR->m_size.x <= 0 || PMONITOR->m_size.y <= 0)
2022-03-19 14:37:40 +01:00
return;
// Reset the reserved
PMONITOR->m_reservedArea.resetType(Desktop::RESERVED_DYNAMIC_TYPE_LS);
2022-03-21 17:00:17 +01:00
const CBox ORIGINAL_USABLE_AREA = PMONITOR->logicalBoxMinusReserved();
CBox usableArea = ORIGINAL_USABLE_AREA;
for (auto& la : PMONITOR->m_layerSurfaceLayers) {
std::ranges::stable_sort(
la, [](const PHLLSREF& a, const PHLLSREF& b) { return a->m_ruleApplicator->order().valueOrDefault() > b->m_ruleApplicator->order().valueOrDefault(); });
}
for (auto const& la : PMONITOR->m_layerSurfaceLayers)
2022-03-21 17:00:17 +01:00
arrangeLayerArray(PMONITOR, la, true, &usableArea);
for (auto const& la : PMONITOR->m_layerSurfaceLayers)
2022-03-21 17:00:17 +01:00
arrangeLayerArray(PMONITOR, la, false, &usableArea);
PMONITOR->m_reservedArea.addType(Desktop::RESERVED_DYNAMIC_TYPE_LS, Desktop::CReservedArea{ORIGINAL_USABLE_AREA, usableArea});
2022-04-27 17:46:07 +02:00
2022-05-04 15:23:30 +02:00
// damage the monitor if can
2023-04-07 12:18:40 +01:00
damageMonitor(PMONITOR);
2022-05-04 15:23:30 +02:00
g_layoutManager->invalidateMonitorGeometries(PMONITOR);
2022-03-19 16:13:19 +01:00
}
void IHyprRenderer::damageSurface(SP<CWLSurfaceResource> pSurface, double x, double y, double scale) {
2022-04-14 16:43:29 +02:00
if (!pSurface)
return; // wut?
2022-03-21 16:13:43 +01:00
if (g_pCompositor->m_unsafeState)
2022-08-10 23:14:53 +02:00
return;
const auto WLSURF = Desktop::View::CWLSurface::fromResource(pSurface);
if (!WLSURF) {
Log::logger->log(Log::ERR, "BUG THIS: No CWLSurface for surface in damageSurface!!!");
return;
}
2024-04-08 15:33:02 +01:00
// hack: schedule frame events
if (!WLSURF->resource()->m_current.callbacks.empty() && pSurface->m_hlSurface) {
const auto BOX = pSurface->m_hlSurface->getSurfaceBoxGlobal();
if (BOX && !BOX->empty()) {
for (auto const& m : g_pCompositor->m_monitors) {
if (!m->m_output)
continue;
2022-03-21 16:13:43 +01:00
if (BOX->overlaps(m->logicalBox()))
g_pCompositor->scheduleFrameForMonitor(m, Aquamarine::IOutput::AQ_SCHEDULE_NEEDS_FRAME);
}
}
}
2022-07-04 17:55:33 +02:00
CRegion damageBox = WLSURF->computeDamage();
2023-07-19 20:09:49 +02:00
if (damageBox.empty())
2022-06-28 15:30:46 +02:00
return;
if (scale != 1.0)
damageBox.scale(scale);
damageBox.translate({x, y});
2023-07-19 20:09:49 +02:00
CRegion damageBoxForEach;
2022-07-28 22:15:56 +02:00
for (auto const& m : g_pCompositor->m_monitors) {
if (!m->m_output)
continue;
damageBoxForEach.set(damageBox);
damageBoxForEach.translate({-m->m_position.x, -m->m_position.y}).scale(m->m_scale);
2022-07-28 22:15:56 +02:00
m->addDamage(damageBoxForEach);
2022-03-21 16:13:43 +01:00
}
static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>("debug:log_damage");
if (*PLOGDAMAGE)
Log::logger->log(Log::DEBUG, "Damage: Surface (extents): xy: {}, {} wh: {}, {}", damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y1,
damageBox.pixman()->extents.x2 - damageBox.pixman()->extents.x1, damageBox.pixman()->extents.y2 - damageBox.pixman()->extents.y1);
2022-04-14 16:43:29 +02:00
}
2022-03-21 16:13:43 +01:00
void IHyprRenderer::damageWindow(PHLWINDOW pWindow, bool forceFull) {
if (g_pCompositor->m_unsafeState)
2022-08-10 23:14:53 +02:00
return;
CBox windowBox = pWindow->getFullWindowBoundingBox();
const auto PWINDOWWORKSPACE = pWindow->m_workspace;
if (PWINDOWWORKSPACE && PWINDOWWORKSPACE->m_renderOffset->isBeingAnimated() && !pWindow->m_pinned)
windowBox.translate(PWINDOWWORKSPACE->m_renderOffset->value());
windowBox.translate(pWindow->m_floatingOffset);
for (auto const& m : g_pCompositor->m_monitors) {
if (forceFull || shouldRenderWindow(pWindow, m)) { // only damage if window is rendered on monitor
CBox fixedDamageBox = {windowBox.x - m->m_position.x, windowBox.y - m->m_position.y, windowBox.width, windowBox.height};
fixedDamageBox.scale(m->m_scale);
m->addDamage(fixedDamageBox);
}
2022-06-29 11:21:42 +02:00
}
2022-05-05 15:09:26 +02:00
for (auto const& wd : pWindow->m_windowDecorations)
2023-06-27 13:23:53 +02:00
wd->damageEntire();
static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>("debug:log_damage");
if (*PLOGDAMAGE)
Log::logger->log(Log::DEBUG, "Damage: Window ({}): xy: {}, {} wh: {}, {}", pWindow->m_title, windowBox.x, windowBox.y, windowBox.width, windowBox.height);
2022-03-21 16:13:43 +01:00
}
void IHyprRenderer::damageMonitor(PHLMONITOR pMonitor) {
if (g_pCompositor->m_unsafeState || pMonitor->isMirror())
2022-08-10 23:14:53 +02:00
return;
CBox damageBox = {0, 0, INT16_MAX, INT16_MAX};
pMonitor->addDamage(damageBox);
2022-05-05 15:09:26 +02:00
static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>("debug:log_damage");
if (*PLOGDAMAGE)
Log::logger->log(Log::DEBUG, "Damage: Monitor {}", pMonitor->m_name);
2022-04-14 16:43:29 +02:00
}
2022-03-21 16:13:43 +01:00
void IHyprRenderer::damageBox(const CBox& box, bool skipFrameSchedule) {
if (g_pCompositor->m_unsafeState)
2022-08-10 23:14:53 +02:00
return;
for (auto const& m : g_pCompositor->m_monitors) {
2022-09-13 15:25:42 +02:00
if (m->isMirror())
continue; // don't damage mirrors traditionally
if (!skipFrameSchedule) {
2025-10-15 00:37:07 +01:00
CBox damageBox = box.copy().translate(-m->m_position).scale(m->m_scale).round();
m->addDamage(damageBox);
}
}
2022-05-05 15:09:26 +02:00
static auto PLOGDAMAGE = CConfigValue<Hyprlang::INT>("debug:log_damage");
if (*PLOGDAMAGE)
Log::logger->log(Log::DEBUG, "Damage: Box: xy: {}, {} wh: {}, {}", box.x, box.y, box.w, box.h);
2022-03-31 17:25:23 +02:00
}
void IHyprRenderer::damageBox(const int& x, const int& y, const int& w, const int& h) {
CBox box = {x, y, w, h};
damageBox(box);
}
void IHyprRenderer::damageRegion(const CRegion& rg) {
rg.forEachRect([this](const auto& RECT) { damageBox(RECT.x1, RECT.y1, RECT.x2 - RECT.x1, RECT.y2 - RECT.y1); });
}
void IHyprRenderer::damageMirrorsWith(PHLMONITOR pMonitor, const CRegion& pRegion) {
for (auto const& mirror : pMonitor->m_mirrors) {
2022-09-13 15:25:42 +02:00
// transform the damage here, so it won't get clipped by the monitor damage ring
auto monitor = mirror;
CRegion transformed{pRegion};
// we want to transform to the same box as in CHyprOpenGLImpl::renderMirrored
double scale = std::min(monitor->m_transformedSize.x / pMonitor->m_transformedSize.x, monitor->m_transformedSize.y / pMonitor->m_transformedSize.y);
CBox monbox = {0, 0, pMonitor->m_transformedSize.x * scale, pMonitor->m_transformedSize.y * scale};
monbox.x = (monitor->m_transformedSize.x - monbox.w) / 2;
monbox.y = (monitor->m_transformedSize.y - monbox.h) / 2;
transformed.scale(scale);
transformed.transform(Math::wlTransformToHyprutils(pMonitor->m_transform), pMonitor->m_pixelSize.x * scale, pMonitor->m_pixelSize.y * scale);
transformed.translate(Vector2D(monbox.x, monbox.y));
mirror->addDamage(transformed);
2023-03-04 00:48:02 +00:00
g_pCompositor->scheduleFrameForMonitor(mirror.lock(), Aquamarine::IOutput::AQ_SCHEDULE_DAMAGE);
2022-09-13 15:25:42 +02:00
}
}
void IHyprRenderer::renderDragIcon(PHLMONITOR pMonitor, const Time::steady_tp& time) {
2024-05-11 17:13:20 +01:00
PROTO::data->renderDND(pMonitor, time);
2022-04-14 16:43:29 +02:00
}
void IHyprRenderer::setCursorSurface(SP<Desktop::View::CWLSurface> surf, int hotspotX, int hotspotY, bool force) {
m_cursorHasSurface = surf && surf->resource();
m_lastCursorData.name = "";
m_lastCursorData.surf = surf;
m_lastCursorData.hotspotX = hotspotX;
m_lastCursorData.hotspotY = hotspotY;
if (m_cursorHidden && !force)
return;
g_pCursorManager->setCursorSurface(surf, {hotspotX, hotspotY});
}
void IHyprRenderer::setCursorFromName(const std::string& name, bool force) {
m_cursorHasSurface = true;
if (name == m_lastCursorData.name && !force)
return;
m_lastCursorData.name = name;
static auto getShapeOrDefault = [](std::string_view name) -> wpCursorShapeDeviceV1Shape {
const auto it = std::ranges::find(CURSOR_SHAPE_NAMES, name);
if (it == CURSOR_SHAPE_NAMES.end()) {
// clang-format off
static const auto overrites = std::unordered_map<std::string_view, wpCursorShapeDeviceV1Shape> {
{"top_side", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_N_RESIZE},
{"bottom_side", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_S_RESIZE},
{"left_side", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_W_RESIZE},
{"right_side", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_E_RESIZE},
{"top_left_corner", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NW_RESIZE},
{"bottom_left_corner", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SW_RESIZE},
{"top_right_corner", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_NE_RESIZE},
{"bottom_right_corner", WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_SE_RESIZE},
};
// clang-format on
if (overrites.contains(name))
return overrites.at(name);
return WP_CURSOR_SHAPE_DEVICE_V1_SHAPE_DEFAULT;
}
return sc<wpCursorShapeDeviceV1Shape>(std::distance(CURSOR_SHAPE_NAMES.begin(), it));
};
const auto newShape = getShapeOrDefault(name);
if (newShape != m_lastCursorData.shape) {
m_lastCursorData.shapePrevious = m_lastCursorData.shape;
m_lastCursorData.switchedTimer.reset();
}
m_lastCursorData.shape = newShape;
m_lastCursorData.surf.reset();
if (m_cursorHidden && !force)
return;
g_pCursorManager->setCursorFromName(name);
}
void IHyprRenderer::ensureCursorRenderingMode() {
static auto PINVISIBLE = CConfigValue<Hyprlang::INT>("cursor:invisible");
static auto PCURSORTIMEOUT = CConfigValue<Hyprlang::FLOAT>("cursor:inactive_timeout");
static auto PHIDEONTOUCH = CConfigValue<Hyprlang::INT>("cursor:hide_on_touch");
static auto PHIDEONTABLET = CConfigValue<Hyprlang::INT>("cursor:hide_on_tablet");
static auto PHIDEONKEY = CConfigValue<Hyprlang::INT>("cursor:hide_on_key_press");
2022-06-24 23:27:02 +02:00
if (*PCURSORTIMEOUT <= 0)
m_cursorHiddenConditions.hiddenOnTimeout = false;
if (*PHIDEONTOUCH == 0)
m_cursorHiddenConditions.hiddenOnTouch = false;
if (*PHIDEONTABLET == 0)
m_cursorHiddenConditions.hiddenOnTablet = false;
if (*PHIDEONKEY == 0)
m_cursorHiddenConditions.hiddenOnKeyboard = false;
2023-01-17 11:47:39 +01:00
if (*PCURSORTIMEOUT > 0)
m_cursorHiddenConditions.hiddenOnTimeout = *PCURSORTIMEOUT < g_pInputManager->m_lastCursorMovement.getSeconds();
2022-06-24 23:27:02 +02:00
m_cursorHiddenByCondition =
m_cursorHiddenConditions.hiddenOnTimeout || m_cursorHiddenConditions.hiddenOnTouch || m_cursorHiddenConditions.hiddenOnTablet || m_cursorHiddenConditions.hiddenOnKeyboard;
const bool HIDE = m_cursorHiddenByCondition || (*PINVISIBLE != 0);
2022-06-24 23:27:02 +02:00
if (HIDE == m_cursorHidden)
return;
if (HIDE)
Log::logger->log(Log::DEBUG, "Hiding the cursor (hl-mandated)");
else
Log::logger->log(Log::DEBUG, "Showing the cursor (hl-mandated)");
for (auto const& m : g_pCompositor->m_monitors) {
if (!g_pPointerManager->softwareLockedFor(m))
continue;
g_pPointerManager->damageCursor(m, m->shouldSkipScheduleFrameOnMouseEvent());
2022-06-24 23:27:02 +02:00
}
setCursorHidden(HIDE);
2022-06-26 13:43:32 +02:00
}
void IHyprRenderer::setCursorHidden(bool hide) {
if (hide == m_cursorHidden)
return;
m_cursorHidden = hide;
if (hide) {
g_pPointerManager->resetCursorImage();
return;
}
if (m_lastCursorData.surf.has_value())
setCursorSurface(m_lastCursorData.surf.value(), m_lastCursorData.hotspotX, m_lastCursorData.hotspotY, true);
else if (!m_lastCursorData.name.empty())
setCursorFromName(m_lastCursorData.name, true);
else
setCursorFromName("left_ptr", true);
}
bool IHyprRenderer::shouldRenderCursor() {
return !m_cursorHidden && m_cursorHasSurface;
}
std::tuple<float, float, float> IHyprRenderer::getRenderTimes(PHLMONITOR pMonitor) {
const auto POVERLAY = &g_pDebugOverlay->m_monitorOverlays[pMonitor];
float avgRenderTime = 0;
float maxRenderTime = 0;
float minRenderTime = 9999;
for (auto const& rt : POVERLAY->m_lastRenderTimes) {
maxRenderTime = std::max(rt, maxRenderTime);
minRenderTime = std::min(rt, minRenderTime);
avgRenderTime += rt;
}
avgRenderTime /= POVERLAY->m_lastRenderTimes.empty() ? 1 : POVERLAY->m_lastRenderTimes.size();
return std::make_tuple<>(avgRenderTime, maxRenderTime, minRenderTime);
}
2023-04-04 14:49:58 +01:00
static int handleCrashLoop(void* data) {
g_pHyprNotificationOverlay->addNotification("Hyprland will crash in " + std::to_string(10 - sc<int>(g_pHyprRenderer->m_crashingDistort * 2.f)) + "s.", CHyprColor(0), 5000,
2023-04-04 14:49:58 +01:00
ICON_INFO);
g_pHyprRenderer->m_crashingDistort += 0.5f;
2023-04-04 14:49:58 +01:00
if (g_pHyprRenderer->m_crashingDistort >= 5.5f)
raise(SIGABRT);
2023-04-04 14:49:58 +01:00
wl_event_source_timer_update(g_pHyprRenderer->m_crashingLoop, 1000);
2023-04-04 14:49:58 +01:00
return 1;
}
void IHyprRenderer::initiateManualCrash() {
g_pHyprNotificationOverlay->addNotification("Manual crash initiated. Farewell...", CHyprColor(0), 5000, ICON_INFO);
2023-04-04 14:49:58 +01:00
m_crashingLoop = wl_event_loop_add_timer(g_pCompositor->m_wlEventLoop, handleCrashLoop, nullptr);
wl_event_source_timer_update(m_crashingLoop, 1000);
2023-04-04 14:49:58 +01:00
m_crashingInProgress = true;
m_crashingDistort = 0.5;
2023-04-04 14:49:58 +01:00
m_globalTimer.reset();
static auto PDT = rc<Hyprlang::INT* const*>(g_pConfigManager->getConfigValuePtr("debug:damage_tracking"));
**PDT = 0;
}
const SRenderData& IHyprRenderer::renderData() {
return m_renderData;
}
SP<IRenderbuffer> IHyprRenderer::getOrCreateRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {
auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; });
if (it != m_renderbuffers.end())
return *it;
auto buf = getOrCreateRenderbufferInternal(buffer, fmt);
if (!buf->good())
return nullptr;
m_renderbuffers.emplace_back(buf);
return buf;
}
bool IHyprRenderer::beginFullFakeRender(PHLMONITOR pMonitor, CRegion& damage, SP<IFramebuffer> fb) {
return beginRender(pMonitor, damage, RENDER_MODE_FULL_FAKE, nullptr, fb, true);
}
bool IHyprRenderer::beginRenderToBuffer(PHLMONITOR pMonitor, CRegion& damage, SP<IHLBuffer> buffer, bool simple) {
return beginRender(pMonitor, damage, RENDER_MODE_TO_BUFFER, buffer, nullptr, simple);
}
void IHyprRenderer::onRenderbufferDestroy(IRenderbuffer* rb) {
std::erase_if(m_renderbuffers, [&](const auto& rbo) { return rbo.get() == rb; });
}
bool IHyprRenderer::isNvidia() {
return m_nvidia;
}
bool IHyprRenderer::isIntel() {
return m_intel;
}
bool IHyprRenderer::isSoftware() {
return m_software;
}
bool IHyprRenderer::isMgpu() {
return m_mgpu;
}
void IHyprRenderer::addWindowToRenderUnfocused(PHLWINDOW window) {
static auto PFPS = CConfigValue<Hyprlang::INT>("misc:render_unfocused_fps");
if (std::ranges::find(m_renderUnfocused, window) != m_renderUnfocused.end())
return;
m_renderUnfocused.emplace_back(window);
if (!m_renderUnfocusedTimer->armed())
m_renderUnfocusedTimer->updateTimeout(std::chrono::milliseconds(1000 / *PFPS));
}
void IHyprRenderer::makeSnapshot(PHLWINDOW pWindow) {
// we trust the window is valid.
const auto PMONITOR = pWindow->m_monitor.lock();
if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)
return;
if (!shouldRenderWindow(pWindow))
return; // ignore, window is not being rendered
Log::logger->log(Log::DEBUG, "renderer: making a snapshot of {:x}", rc<uintptr_t>(pWindow.get()));
// we need to "damage" the entire monitor
// so that we render the entire window
// this is temporary, doesn't mess with the actual damage
CRegion fakeDamage{0, 0, sc<int>(PMONITOR->m_transformedSize.x), sc<int>(PMONITOR->m_transformedSize.y)};
PHLWINDOWREF ref{pWindow};
if (!ref->m_snapshotFB)
ref->m_snapshotFB = createFB("window snapshot");
const auto PFRAMEBUFFER = ref->m_snapshotFB;
PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);
beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);
m_bRenderingSnapshot = true;
draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});
startRenderPass();
Log::logger->log(Log::DEBUG, "renderer: cleared a snapshot of {:x}", rc<uintptr_t>(pWindow.get()));
renderWindow(pWindow, PMONITOR, Time::steadyNow(), !pWindow->m_X11DoesntWantBorders, RENDER_PASS_ALL);
Log::logger->log(Log::DEBUG, "renderer: rendered a snapshot of {:x}", rc<uintptr_t>(pWindow.get()));
endRender();
Log::logger->log(Log::DEBUG, "renderer: made a snapshot of {:x}", rc<uintptr_t>(pWindow.get()));
m_bRenderingSnapshot = false;
}
void IHyprRenderer::makeSnapshot(PHLLS pLayer) {
// we trust the window is valid.
const auto PMONITOR = pLayer->m_monitor.lock();
if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)
return;
Log::logger->log(Log::DEBUG, "renderer: making a snapshot of layer {:x}", rc<uintptr_t>(pLayer.get()));
// we need to "damage" the entire monitor
// so that we render the entire window
// this is temporary, doesn't mess with the actual damage
CRegion fakeDamage{0, 0, sc<int>(PMONITOR->m_transformedSize.x), sc<int>(PMONITOR->m_transformedSize.y)};
if (!pLayer->m_snapshotFB)
pLayer->m_snapshotFB = createFB("layer snapshot");
const auto PFRAMEBUFFER = pLayer->m_snapshotFB;
PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);
beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);
m_bRenderingSnapshot = true;
draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});
startRenderPass();
Log::logger->log(Log::DEBUG, "renderer: cleared a snapshot of layer {:x}", rc<uintptr_t>(pLayer.get()));
// draw the layer
renderLayer(pLayer, PMONITOR, Time::steadyNow());
Log::logger->log(Log::DEBUG, "renderer: rendered a snapshot of layer {:x}", rc<uintptr_t>(pLayer.get()));
endRender();
Log::logger->log(Log::DEBUG, "renderer: made a snapshot of layer {:x}", rc<uintptr_t>(pLayer.get()));
m_bRenderingSnapshot = false;
}
void IHyprRenderer::makeSnapshot(WP<Desktop::View::CPopup> popup) {
// we trust the window is valid.
const auto PMONITOR = popup->getMonitor();
if (!PMONITOR || !PMONITOR->m_output || PMONITOR->m_pixelSize.x <= 0 || PMONITOR->m_pixelSize.y <= 0)
return;
if (!popup->aliveAndVisible())
return;
Log::logger->log(Log::DEBUG, "renderer: making a snapshot of {:x}", rc<uintptr_t>(popup.get()));
CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};
if (!popup->m_snapshotFB)
popup->m_snapshotFB = createFB("popup shapshot");
const auto PFRAMEBUFFER = popup->m_snapshotFB;
PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888);
beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER);
m_bRenderingSnapshot = true;
draw(makeUnique<CClearPassElement>(CClearPassElement::SClearData{CHyprColor(0, 0, 0, 0)}), {});
CSurfacePassElement::SRenderData renderdata;
2025-08-03 16:19:36 +02:00
renderdata.pos = popup->coordsGlobal();
renderdata.alpha = 1.F;
renderdata.dontRound = true; // don't round popups
renderdata.pMonitor = PMONITOR;
renderdata.squishOversized = false; // don't squish popups
renderdata.popup = true;
2025-08-03 16:19:36 +02:00
renderdata.blur = false;
popup->wlSurface()->resource()->breadthfirst(
[this, &renderdata](SP<CWLSurfaceResource> s, const Vector2D& offset, void* data) {
if (!s->m_current.texture)
return;
if (s->m_current.size.x < 1 || s->m_current.size.y < 1)
return;
renderdata.localPos = offset;
renderdata.texture = s->m_current.texture;
renderdata.surface = s;
renderdata.mainSurface = false;
m_renderPass.add(makeUnique<CSurfacePassElement>(renderdata));
renderdata.surfaceCounter++;
},
nullptr);
endRender();
m_bRenderingSnapshot = false;
}
void IHyprRenderer::renderSnapshot(PHLWINDOW pWindow) {
static auto PDIMAROUND = CConfigValue<Hyprlang::FLOAT>("decoration:dim_around");
PHLWINDOWREF ref{pWindow};
if (!ref->m_snapshotFB)
return;
const auto FBDATA = ref->m_snapshotFB;
if (!FBDATA->getTexture())
return;
const auto PMONITOR = pWindow->m_monitor.lock();
CBox windowBox;
// some mafs to figure out the correct box
// the originalClosedPos is relative to the monitor's pos
Vector2D scaleXY = Vector2D((PMONITOR->m_scale * pWindow->m_realSize->value().x / (pWindow->m_originalClosedSize.x * PMONITOR->m_scale)),
(PMONITOR->m_scale * pWindow->m_realSize->value().y / (pWindow->m_originalClosedSize.y * PMONITOR->m_scale)));
windowBox.width = PMONITOR->m_transformedSize.x * scaleXY.x;
windowBox.height = PMONITOR->m_transformedSize.y * scaleXY.y;
windowBox.x = ((pWindow->m_realPosition->value().x - PMONITOR->m_position.x) * PMONITOR->m_scale) - ((pWindow->m_originalClosedPos.x * PMONITOR->m_scale) * scaleXY.x);
windowBox.y = ((pWindow->m_realPosition->value().y - PMONITOR->m_position.y) * PMONITOR->m_scale) - ((pWindow->m_originalClosedPos.y * PMONITOR->m_scale) * scaleXY.y);
CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};
if (*PDIMAROUND && pWindow->m_ruleApplicator->dimAround().valueOrDefault()) {
CRectPassElement::SRectData data;
data.box = {0, 0, m_renderData.pMonitor->m_pixelSize.x, m_renderData.pMonitor->m_pixelSize.y};
data.color = CHyprColor(0, 0, 0, *PDIMAROUND * pWindow->m_alpha->value());
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
if (shouldBlur(pWindow)) {
CRectPassElement::SRectData data;
data.box = CBox{pWindow->m_realPosition->value(), pWindow->m_realSize->value()}.translate(-PMONITOR->m_position).scale(PMONITOR->m_scale).round();
data.color = CHyprColor{0, 0, 0, 0};
data.blur = true;
data.blurA = sqrt(pWindow->m_alpha->value()); // sqrt makes the blur fadeout more realistic.
data.round = pWindow->rounding();
data.roundingPower = pWindow->roundingPower();
data.xray = pWindow->m_ruleApplicator->xray().valueOr(false);
m_renderPass.add(makeUnique<CRectPassElement>(data));
}
CTexPassElement::SRenderData data;
data.flipEndFrame = true;
data.tex = FBDATA->getTexture();
data.box = windowBox;
data.a = pWindow->m_alpha->value();
data.damage = fakeDamage;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
void IHyprRenderer::renderSnapshot(PHLLS pLayer) {
if (!pLayer->m_snapshotFB)
return;
const auto FBDATA = pLayer->m_snapshotFB;
if (!FBDATA->getTexture())
return;
const auto PMONITOR = pLayer->m_monitor.lock();
CBox layerBox;
// some mafs to figure out the correct box
// the originalClosedPos is relative to the monitor's pos
Vector2D scaleXY = Vector2D((PMONITOR->m_scale * pLayer->m_realSize->value().x / (pLayer->m_geometry.w * PMONITOR->m_scale)),
(PMONITOR->m_scale * pLayer->m_realSize->value().y / (pLayer->m_geometry.h * PMONITOR->m_scale)));
layerBox.width = PMONITOR->m_transformedSize.x * scaleXY.x;
layerBox.height = PMONITOR->m_transformedSize.y * scaleXY.y;
layerBox.x =
((pLayer->m_realPosition->value().x - PMONITOR->m_position.x) * PMONITOR->m_scale) - (((pLayer->m_geometry.x - PMONITOR->m_position.x) * PMONITOR->m_scale) * scaleXY.x);
layerBox.y =
((pLayer->m_realPosition->value().y - PMONITOR->m_position.y) * PMONITOR->m_scale) - (((pLayer->m_geometry.y - PMONITOR->m_position.y) * PMONITOR->m_scale) * scaleXY.y);
CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};
const bool SHOULD_BLUR = shouldBlur(pLayer);
CTexPassElement::SRenderData data;
data.flipEndFrame = true;
data.tex = FBDATA->getTexture();
data.box = layerBox;
data.a = pLayer->m_alpha->value();
data.damage = fakeDamage;
data.blur = SHOULD_BLUR;
data.blurA = sqrt(pLayer->m_alpha->value()); // sqrt makes the blur fadeout more realistic.
if (SHOULD_BLUR)
data.ignoreAlpha = pLayer->m_ruleApplicator->ignoreAlpha().valueOr(0.01F) /* ignore the alpha 0 regions */;
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
void IHyprRenderer::renderSnapshot(WP<Desktop::View::CPopup> popup) {
if (!popup->m_snapshotFB)
return;
static CConfigValue PBLURIGNOREA = CConfigValue<Hyprlang::FLOAT>("decoration:blur:popups_ignorealpha");
const auto FBDATA = popup->m_snapshotFB;
if (!FBDATA->getTexture())
return;
const auto PMONITOR = popup->getMonitor();
if (!PMONITOR)
return;
CRegion fakeDamage{0, 0, PMONITOR->m_transformedSize.x, PMONITOR->m_transformedSize.y};
const bool SHOULD_BLUR = shouldBlur(popup);
CTexPassElement::SRenderData data;
data.flipEndFrame = true;
data.tex = FBDATA->getTexture();
data.box = {{}, PMONITOR->m_transformedSize};
data.a = popup->m_alpha->value();
data.damage = fakeDamage;
data.blur = SHOULD_BLUR;
data.blurA = sqrt(popup->m_alpha->value()); // sqrt makes the blur fadeout more realistic.
data.blockBlurOptimization = SHOULD_BLUR; // force no xray on this (popups never have xray)
if (SHOULD_BLUR)
data.ignoreAlpha = std::max(*PBLURIGNOREA, 0.01F); /* ignore the alpha 0 regions */
m_renderPass.add(makeUnique<CTexPassElement>(std::move(data)));
}
NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() {
// TODO
// const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present;
// const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC);
// const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
return m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF});
}
bool IHyprRenderer::shouldBlur(PHLLS ls) {
if (m_bRenderingSnapshot)
return false;
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
return *PBLUR && ls->m_ruleApplicator->blur().valueOrDefault();
}
bool IHyprRenderer::shouldBlur(PHLWINDOW w) {
if (m_bRenderingSnapshot)
return false;
static auto PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
const bool DONT_BLUR = w->m_ruleApplicator->noBlur().valueOrDefault() || w->m_ruleApplicator->RGBX().valueOrDefault() || w->opaque();
return *PBLUR && !DONT_BLUR;
}
bool IHyprRenderer::shouldBlur(WP<Desktop::View::CPopup> p) {
static CConfigValue PBLURPOPUPS = CConfigValue<Hyprlang::INT>("decoration:blur:popups");
static CConfigValue PBLUR = CConfigValue<Hyprlang::INT>("decoration:blur:enabled");
return *PBLURPOPUPS && *PBLUR;
}
SP<ITexture> IHyprRenderer::renderSplash(const std::function<SP<ITexture>(const int, const int, unsigned char* const)>& handleData, const int fontSize, const int maxWidth,
const int maxHeight) {
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 COLOR = CHyprColor(*PSPLASHCOLOR);
const auto CAIROSURFACE = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, maxWidth, maxHeight);
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);
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, 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);
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 = std::ceil((float)textW / PANGO_SCALE + fontSize / 10.f);
textH = std::ceil((float)textH / PANGO_SCALE + fontSize / 10.f);
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);
const auto smallSurf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, textW, textH);
const auto small = cairo_create(smallSurf);
cairo_set_source_surface(small, CAIROSURFACE, 0, 0);
cairo_rectangle(small, 0, 0, textW, textH);
cairo_set_operator(small, CAIRO_OPERATOR_SOURCE);
cairo_fill(small);
cairo_surface_flush(smallSurf);
auto tex = handleData(textW, textH, cairo_image_surface_get_data(smallSurf));
cairo_surface_destroy(smallSurf);
cairo_destroy(small);
cairo_surface_destroy(CAIROSURFACE);
cairo_destroy(CAIRO);
return tex;
}
bool IHyprRenderer::needsACopyFB(PHLMONITOR mon) {
return !mon->m_mirrors.empty() || Screenshare::mgr()->isOutputBeingSSd(mon);
}