From 0d45b277d6c750377b336034b8adc53eae238d91 Mon Sep 17 00:00:00 2001 From: UjinT34 <41110182+UjinT34@users.noreply.github.com> Date: Fri, 22 Aug 2025 20:24:25 +0300 Subject: [PATCH] internal: Solitary clients with single subsurface & verbose solitary/tearing/DS checks (#11228) Adds more verbose checks for various conditional rendering mechanisms --- Makefile | 4 + hyprtester/src/shared.hpp | 2 +- hyprtester/src/tests/main/snap.cpp | 1 + hyprtester/src/tests/main/solitary.cpp | 76 +++++++ src/Compositor.cpp | 7 +- src/debug/HyprCtl.cpp | 107 +++++++++- src/debug/HyprCtl.hpp | 3 + src/desktop/Popup.cpp | 6 + src/desktop/Window.cpp | 34 +++- src/desktop/Window.hpp | 1 + src/helpers/Monitor.cpp | 265 +++++++++++++++++++++++-- src/helpers/Monitor.hpp | 65 ++++++ src/helpers/MonitorFrameScheduler.cpp | 2 +- src/render/Renderer.cpp | 114 +---------- src/render/Renderer.hpp | 1 - 15 files changed, 551 insertions(+), 137 deletions(-) create mode 100644 hyprtester/src/tests/main/solitary.cpp diff --git a/Makefile b/Makefile index 01d1d650..aba363a3 100644 --- a/Makefile +++ b/Makefile @@ -92,3 +92,7 @@ asan: @echo "Hyprland done" ASAN_OPTIONS="detect_odr_violation=0,log_path=asan.log" HYPRLAND_NO_CRASHREPORTER=1 ./build/Hyprland -c ~/.config/hypr/hyprland.conf + +test: + $(MAKE) debug + ./build/hyprtester/hyprtester -c hyprtester/test.conf -b ./build/Hyprland -p hyprtester/plugin/hyprtestplugin.so diff --git a/hyprtester/src/shared.hpp b/hyprtester/src/shared.hpp index 8bea4c54..43944c3c 100644 --- a/hyprtester/src/shared.hpp +++ b/hyprtester/src/shared.hpp @@ -45,7 +45,7 @@ namespace Colors { #define EXPECT_CONTAINS(haystack, needle) \ if (const auto EXPECTED = needle; !std::string{haystack}.contains(EXPECTED)) { \ - NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, EXPECTED, __FILE__, __LINE__, \ + NLog::log("{}Failed: {}{} should contain {} but doesn't. Source: {}@{}. Haystack is:\n{}", Colors::RED, Colors::RESET, #haystack, #needle, __FILE__, __LINE__, \ std::string{haystack}); \ ret = 1; \ TESTS_FAILED++; \ diff --git a/hyprtester/src/tests/main/snap.cpp b/hyprtester/src/tests/main/snap.cpp index c970c6b7..9c25de11 100644 --- a/hyprtester/src/tests/main/snap.cpp +++ b/hyprtester/src/tests/main/snap.cpp @@ -168,6 +168,7 @@ static bool test() { NLog::log("{}Reloading the config", Colors::YELLOW); OK(getFromSocket("/reload")); + OK(getFromSocket("/dispatch workspace 1")); return !ret; } diff --git a/hyprtester/src/tests/main/solitary.cpp b/hyprtester/src/tests/main/solitary.cpp new file mode 100644 index 00000000..6f4d1373 --- /dev/null +++ b/hyprtester/src/tests/main/solitary.cpp @@ -0,0 +1,76 @@ +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include +#include +#include +#include "../shared.hpp" + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define UP CUniquePointer +#define SP CSharedPointer + +static bool test() { + NLog::log("{}Testing solitary clients", Colors::GREEN); + + OK(getFromSocket("/keyword general:allow_tearing false")); + OK(getFromSocket("/keyword render:direct_scanout 0")); + OK(getFromSocket("/keyword cursor:no_hardware_cursors 1")); + NLog::log("{}Expecting blocked solitary/DS/tearing", Colors::YELLOW); + { + auto str = getFromSocket("/monitors"); + EXPECT_CONTAINS(str, "solitary: 0\n"); + EXPECT_CONTAINS(str, "solitaryBlockedBy: windowed mode,missing candidate"); + EXPECT_CONTAINS(str, "activelyTearing: false"); + EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,user settings,not supported by monitor,missing candidate"); + EXPECT_CONTAINS(str, "directScanoutTo: 0\n"); + EXPECT_CONTAINS(str, "directScanoutBlockedBy: user settings,software renders/cursors,missing candidate"); + } + + // FIXME: need a reliable client with solitary opaque surface in fullscreen. kitty doesn't work all the time + // NLog::log("{}Spawning kittyProcA", Colors::YELLOW); + // auto kittyProcA = Tests::spawnKitty(); + + // if (!kittyProcA) { + // NLog::log("{}Error: kitty did not spawn", Colors::RED); + // return false; + // } + + // OK(getFromSocket("/keyword general:allow_tearing true")); + // OK(getFromSocket("/keyword render:direct_scanout 1")); + // NLog::log("{}", getFromSocket("/clients")); + // OK(getFromSocket("/dispatch fullscreen")); + // NLog::log("{}", getFromSocket("/clients")); + // std::this_thread::sleep_for(std::chrono::milliseconds(100)); + // NLog::log("{}Expecting kitty to almost pass for solitary/DS/tearing", Colors::YELLOW); + // { + // auto str = getFromSocket("/monitors"); + // EXPECT_NOT_CONTAINS(str, "solitary: 0\n"); + // EXPECT_CONTAINS(str, "solitaryBlockedBy: null"); + // EXPECT_CONTAINS(str, "activelyTearing: false"); + // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor,window settings"); + // } + + // OK(getFromSocket("/dispatch setprop active immediate 1")); + // NLog::log("{}Expecting kitty to almost pass for tearing", Colors::YELLOW); + // { + // auto str = getFromSocket("/monitors"); + // EXPECT_CONTAINS(str, "tearingBlockedBy: next frame is not torn,not supported by monitor\n"); + // } + + // // kill all + // NLog::log("{}Killing all windows", Colors::YELLOW); + // Tests::killAllWindows(); + + NLog::log("{}Reloading the config", Colors::YELLOW); + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/src/Compositor.cpp b/src/Compositor.cpp index 23b2e366..a2733042 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -2400,8 +2400,11 @@ void CCompositor::setWindowFullscreenState(const PHLWINDOW PWINDOW, SFullscreenS // send a scanout tranche if we are entering fullscreen, and send a regular one if we aren't. // ignore if DS is disabled. - if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) - g_pHyprRenderer->setSurfaceScanoutMode(PWINDOW->m_wlSurface->resource(), EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); + if (*PDIRECTSCANOUT == 1 || (*PDIRECTSCANOUT == 2 && PWINDOW->getContentType() == CONTENT_TYPE_GAME)) { + auto surf = PWINDOW->getSolitaryResource(); + if (surf) + g_pHyprRenderer->setSurfaceScanoutMode(surf, EFFECTIVE_MODE != FSMODE_NONE ? PMONITOR->m_self.lock() : nullptr); + } g_pConfigManager->ensureVRR(PMONITOR); } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index 45aeb4f1..5af8ef80 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -1,4 +1,5 @@ #include "HyprCtl.hpp" +#include "helpers/Monitor.hpp" #include #include @@ -109,6 +110,91 @@ static std::string availableModesForOutput(PHLMONITOR pMonitor, eHyprCtlOutputFo return result; } +const std::array SOLITARY_REASONS_JSON = { + "\"UNKNOWN\"", "\"NOTIFICATION\"", "\"LOCK\"", "\"WORKSPACE\"", "\"WINDOWED\"", "\"DND\"", "\"SPECIAL\"", "\"ALPHA\"", + "\"OFFSET\"", "\"CANDIDATE\"", "\"OPAQUE\"", "\"TRANSFORM\"", "\"OVERLAYS\"", "\"FLOAT\"", "\"WORKSPACES\"", "\"SURFACES\"", +}; + +const std::array SOLITARY_REASONS_TEXT = { + "unknown reason", "notification", "session lock", "invalid workspace", "windowed mode", "dnd active", "special workspace", "alpha channel", + "workspace offset", "missing candidate", "not opaque", "surface transformations", "other overlays", "floating windows", "other workspaces", "subsurfaces", +}; + +std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { + const auto reasons = m->isSolitaryBlocked(true); + if (!reasons) + return "null"; + + std::string reasonStr = ""; + const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? SOLITARY_REASONS_JSON : SOLITARY_REASONS_TEXT; + + for (int i = 0; i < CMonitor::SC_CHECKS_COUNT; i++) { + if (reasons & (1 << i)) { + if (reasonStr != "") + reasonStr += ","; + reasonStr += TEXTS[i]; + } + } + + return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr; +} + +const std::array DS_REASONS_JSON = { + "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", +}; + +const std::array DS_REASONS_TEXT = { + "unknown reason", "user settings", "windowed mode", "content type", "monitor mirrors", "screen record/screenshot", "software renders/cursors", + "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", +}; + +std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { + const auto reasons = m->isDSBlocked(true); + if (!reasons) + return "null"; + + std::string reasonStr = ""; + const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? DS_REASONS_JSON : DS_REASONS_TEXT; + + for (int i = 0; i < CMonitor::DS_CHECKS_COUNT; i++) { + if (reasons & (1 << i)) { + if (reasonStr != "") + reasonStr += ","; + reasonStr += TEXTS[i]; + } + } + + return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr; +} + +const std::array TEARING_REASONS_JSON = { + "\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"", +}; + +const std::array TEARING_REASONS_TEXT = { + "unknown reason", "next frame is not torn", "user settings", "zoom", "not supported by monitor", "missing candidate", "window settings", +}; + +std::string CHyprCtl::getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { + const auto reasons = m->isTearingBlocked(true); + if (!reasons || (reasons == CMonitor::TC_NOT_TORN && m->m_tearingState.activelyTearing)) + return "null"; + + std::string reasonStr = ""; + const auto TEXTS = format == eHyprCtlOutputFormat::FORMAT_JSON ? TEARING_REASONS_JSON : TEARING_REASONS_TEXT; + + for (int i = 0; i < CMonitor::TC_CHECKS_COUNT; i++) { + if (reasons & (1 << i)) { + if (reasonStr != "") + reasonStr += ","; + reasonStr += TEXTS[i]; + } + } + + return format == eHyprCtlOutputFormat::FORMAT_JSON ? "[" + reasonStr + "]" : reasonStr; +} + std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { std::string result; if (!m->m_output || m->m_id == -1) @@ -146,8 +232,11 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer "dpmsStatus": {}, "vrr": {}, "solitary": "{:x}", + "solitaryBlockedBy": {}, "activelyTearing": {}, + "tearingBlockedBy": {}, "directScanoutTo": "{:x}", + "directScanoutBlockedBy": {}, "disabled": {}, "currentFormat": "{}", "mirrorOf": "{}", @@ -155,28 +244,32 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer }},)#", m->m_id, escapeJSONStrings(m->m_name), escapeJSONStrings(m->m_shortDescription), escapeJSONStrings(m->m_output->make), escapeJSONStrings(m->m_output->model), - escapeJSONStrings(m->m_output->serial), sc(m->m_pixelSize.x), m->m_pixelSize.y, sc(m->m_output->physicalSize.x), sc(m->m_output->physicalSize.y), - m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->activeWorkspaceID(), + escapeJSONStrings(m->m_output->serial), sc(m->m_pixelSize.x), sc(m->m_pixelSize.y), sc(m->m_output->physicalSize.x), + sc(m->m_output->physicalSize.y), m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->activeWorkspaceID(), (!m->m_activeWorkspace ? "" : escapeJSONStrings(m->m_activeWorkspace->m_name)), m->activeSpecialWorkspaceID(), escapeJSONStrings(m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc(m->m_reservedTopLeft.x), sc(m->m_reservedTopLeft.y), sc(m->m_reservedBottomRight.x), sc(m->m_reservedBottomRight.y), m->m_scale, sc(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "true" : "false"), (m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc(m->m_solitaryClient.get()), - (m->m_tearingState.activelyTearing ? "true" : "false"), rc(m->m_lastScanout.get()), (m->m_enabled ? "false" : "true"), - formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); + getSolitaryBlockedReason(m, format), (m->m_tearingState.activelyTearing ? "true" : "false"), getTearingBlockedReason(m, format), rc(m->m_lastScanout.get()), + getDSBlockedReason(m, format), (m->m_enabled ? "false" : "true"), formatToString(m->m_output->state->state().drmFormat), + m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); } else { result += std::format( "Monitor {} (ID {}):\n\t{}x{}@{:.5f} at {}x{}\n\tdescription: {}\n\tmake: {}\n\tmodel: {}\n\tphysical size (mm): {}x{}\n\tserial: {}\n\tactive workspace: {} ({})\n\t" "special workspace: {} ({})\n\treserved: {} {} {} {}\n\tscale: {:.2f}\n\ttransform: {}\n\tfocused: {}\n\t" - "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tactivelyTearing: {}\n\tdirectScanoutTo: {:x}\n\tdisabled: {}\n\tcurrentFormat: {}\n\tmirrorOf: " + "dpmsStatus: {}\n\tvrr: {}\n\tsolitary: {:x}\n\tsolitaryBlockedBy: {}\n\tactivelyTearing: {}\n\ttearingBlockedBy: {}\n\tdirectScanoutTo: " + "{:x}\n\tdirectScanoutBlockedBy: {}\n\tdisabled: " + "{}\n\tcurrentFormat: {}\n\tmirrorOf: " "{}\n\tavailableModes: {}\n\n", m->m_name, m->m_id, sc(m->m_pixelSize.x), sc(m->m_pixelSize.y), m->m_refreshRate, sc(m->m_position.x), sc(m->m_position.y), m->m_shortDescription, m->m_output->make, m->m_output->model, sc(m->m_output->physicalSize.x), sc(m->m_output->physicalSize.y), m->m_output->serial, m->activeWorkspaceID(), (!m->m_activeWorkspace ? "" : m->m_activeWorkspace->m_name), m->activeSpecialWorkspaceID(), (m->m_activeSpecialWorkspace ? m->m_activeSpecialWorkspace->m_name : ""), sc(m->m_reservedTopLeft.x), sc(m->m_reservedTopLeft.y), sc(m->m_reservedBottomRight.x), sc(m->m_reservedBottomRight.y), m->m_scale, sc(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync, - rc(m->m_solitaryClient.get()), m->m_tearingState.activelyTearing, rc(m->m_lastScanout.get()), !m->m_enabled, - formatToString(m->m_output->state->state().drmFormat), m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); + rc(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format), + rc(m->m_lastScanout.get()), getDSBlockedReason(m, format), !m->m_enabled, formatToString(m->m_output->state->state().drmFormat), + m->m_mirrorOf ? std::format("{}", m->m_mirrorOf->m_id) : "none", availableModesForOutput(m, format)); } return result; diff --git a/src/debug/HyprCtl.hpp b/src/debug/HyprCtl.hpp index b4f3d690..95bb65b8 100644 --- a/src/debug/HyprCtl.hpp +++ b/src/debug/HyprCtl.hpp @@ -33,6 +33,9 @@ class CHyprCtl { static std::string getWindowData(PHLWINDOW w, eHyprCtlOutputFormat format); static std::string getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat format); + static std::string getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format); + static std::string getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format); + static std::string getTearingBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format); static std::string getMonitorData(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format); private: diff --git a/src/desktop/Popup.cpp b/src/desktop/Popup.cpp index 7dc8ad53..e7a60af2 100644 --- a/src/desktop/Popup.cpp +++ b/src/desktop/Popup.cpp @@ -385,6 +385,9 @@ void CPopup::bfHelper(std::vector> const& nodes, std::functionm_children) { nodes2.push_back(c->m_self); } @@ -395,6 +398,9 @@ void CPopup::bfHelper(std::vector> const& nodes, std::function, void*)> fn, void* data) { + if (!m_self) + return; + std::vector> popups; popups.push_back(m_self); bfHelper(popups, fn, data); diff --git a/src/desktop/Window.cpp b/src/desktop/Window.cpp index c7fd6b0b..f9857438 100644 --- a/src/desktop/Window.cpp +++ b/src/desktop/Window.cpp @@ -18,6 +18,7 @@ #include "../managers/ANRManager.hpp" #include "../protocols/XDGShell.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/core/Subcompositor.hpp" #include "../protocols/ContentType.hpp" #include "../protocols/FractionalScale.hpp" #include "../xwayland/XWayland.hpp" @@ -1180,7 +1181,8 @@ bool CWindow::opaque() { if (m_isX11 && m_xwaylandSurface && m_xwaylandSurface->m_surface && m_xwaylandSurface->m_surface->m_current.texture) return m_xwaylandSurface->m_surface->m_current.texture->m_opaque; - if (!m_wlSurface->resource() || !m_wlSurface->resource()->m_current.texture) + auto solitaryResource = getSolitaryResource(); + if (!solitaryResource || !solitaryResource->m_current.texture) return false; // TODO: this is wrong @@ -1188,7 +1190,7 @@ bool CWindow::opaque() { if (EXTENTS.w >= m_xdgSurface->m_surface->m_current.bufferSize.x && EXTENTS.h >= m_xdgSurface->m_surface->m_current.bufferSize.y) return true; - return m_wlSurface->resource()->m_current.texture->m_opaque; + return solitaryResource->m_current.texture->m_opaque; } float CWindow::rounding() { @@ -1324,7 +1326,7 @@ void CWindow::onFocusAnimUpdate() { } int CWindow::popupsCount() { - if (m_isX11) + if (m_isX11 || !m_popupHead) return 0; int no = -1; @@ -1870,3 +1872,29 @@ PHLWINDOW CWindow::parent() { bool CWindow::priorityFocus() { return !m_isX11 && CAsyncDialogBox::isPriorityDialogBox(getPID()); } + +SP CWindow::getSolitaryResource() { + if (!m_wlSurface || !m_wlSurface->resource()) + return nullptr; + + auto res = m_wlSurface->resource(); + if (m_isX11) + return res; + + if (popupsCount()) + return nullptr; + + if (res->m_subsurfaces.size() == 0) + return res; + + if (res->m_subsurfaces.size() == 1) { + if (res->m_subsurfaces[0].expired() || res->m_subsurfaces[0]->m_surface.expired()) + return nullptr; + auto surf = res->m_subsurfaces[0]->m_surface.lock(); + if (!surf || surf->m_subsurfaces.size() != 0 || surf->extends() != res->extends() || !surf->m_current.texture || !surf->m_current.texture->m_opaque) + return nullptr; + return surf; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/desktop/Window.hpp b/src/desktop/Window.hpp index 7014ac2f..e58d4fb6 100644 --- a/src/desktop/Window.hpp +++ b/src/desktop/Window.hpp @@ -413,6 +413,7 @@ class CWindow { std::optional xdgDescription(); PHLWINDOW parent(); bool priorityFocus(); + SP getSolitaryResource(); CBox getWindowMainSurfaceBox() const { return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y}; diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index c24ad103..3837a1b3 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -18,6 +18,7 @@ #include "../managers/PointerManager.hpp" #include "../managers/eventLoop/EventLoopManager.hpp" #include "../protocols/core/Compositor.hpp" +#include "../protocols/core/DataDevice.hpp" #include "../render/Renderer.hpp" #include "../managers/EventManager.hpp" #include "../managers/LayoutManager.hpp" @@ -1491,33 +1492,260 @@ void CMonitor::setCTM(const Mat3x3& ctm_) { g_pCompositor->scheduleFrameForMonitor(m_self.lock(), Aquamarine::IOutput::scheduleFrameReason::AQ_SCHEDULE_NEEDS_FRAME); } -bool CMonitor::attemptDirectScanout() { - if (!m_mirrors.empty() || isMirror() || g_pHyprRenderer->m_directScanoutBlocked) - return false; // do not DS if this monitor is being mirrored. Will break the functionality. +uint16_t CMonitor::isSolitaryBlocked(bool full) { + uint16_t reasons = 0; - if (g_pPointerManager->softwareLockedFor(m_self.lock())) - return false; + if (g_pHyprNotificationOverlay->hasAny()) { + reasons |= SC_NOTIFICATION; + if (!full) + return reasons; + } + + if (g_pSessionLockManager->isSessionLocked()) { + reasons |= SC_LOCK; + if (!full) + return reasons; + } + + const auto PWORKSPACE = m_activeWorkspace; + if (!PWORKSPACE) { + reasons |= SC_WORKSPACE; + return reasons; + } + + if (!PWORKSPACE->m_hasFullscreenWindow) { + reasons |= SC_WINDOWED; + if (!full) + return reasons; + } + + if (PROTO::data->dndActive()) { + reasons |= SC_DND; + if (!full) + return reasons; + } + + if (m_activeSpecialWorkspace) { + reasons |= SC_SPECIAL; + if (!full) + return reasons; + } + + if (PWORKSPACE->m_alpha->value() != 1.f) { + reasons |= SC_ALPHA; + if (!full) + return reasons; + } + + if (PWORKSPACE->m_renderOffset->value() != Vector2D{}) { + reasons |= SC_OFFSET; + if (!full) + return reasons; + } + + const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow(); + + if (!PCANDIDATE) { + reasons |= SC_CANDIDATE; + return reasons; + } + + if (!PCANDIDATE->opaque()) { + reasons |= SC_OPAQUE; + if (!full) + return reasons; + } + + if (PCANDIDATE->m_realSize->value() != m_size || PCANDIDATE->m_realPosition->value() != m_position || PCANDIDATE->m_realPosition->isBeingAnimated() || + PCANDIDATE->m_realSize->isBeingAnimated()) { + reasons |= SC_TRANSFORM; + if (!full) + return reasons; + } + + if (!m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) { + reasons |= SC_OVERLAYS; + if (!full) + return reasons; + } + + for (auto const& topls : m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { + if (topls->m_alpha->value() != 0.f) { + reasons |= SC_OVERLAYS; + if (!full) + return reasons; + } + } + + for (auto const& w : g_pCompositor->m_windows) { + if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden()) + continue; + + if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(m_self.lock())) { + reasons |= SC_FLOAT; + if (!full) + return reasons; + } + } + + for (auto const& ws : g_pCompositor->getWorkspaces()) { + if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace || ws->m_monitor != m_self) + continue; + + reasons |= SC_WORKSPACES; + if (!full) + return reasons; + } + + // check if it did not open any subsurfaces or shit + if (!PCANDIDATE->getSolitaryResource()) + reasons |= SC_SURFACES; + + return reasons; +} + +void CMonitor::recheckSolitary() { + m_solitaryClient.reset(); // reset it, if we find one it will be set. + if (isSolitaryBlocked()) + return; + + m_solitaryClient = m_activeWorkspace->getFullscreenWindow(); +} + +uint8_t CMonitor::isTearingBlocked(bool full) { + uint8_t reasons = 0; + + static auto PTEARINGENABLED = CConfigValue("general:allow_tearing"); + + if (!m_tearingState.nextRenderTorn) { + reasons |= TC_NOT_TORN; + if (!full) + return reasons; + } + + if (!*PTEARINGENABLED) { + Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); + reasons |= TC_USER; + if (!full) + return reasons; + } + + if (g_pHyprOpenGL->m_renderData.mouseZoomFactor != 1.0) { + Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring"); + reasons |= TC_ZOOM; + if (!full) + return reasons; + } + + if (!m_tearingState.canTear) { + Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); + reasons |= TC_SUPPORT; + if (!full) + return reasons; + } + + if (m_solitaryClient.expired()) { + reasons |= TC_CANDIDATE; + return reasons; + } + + if (!m_solitaryClient->canBeTorn()) + reasons |= TC_WINDOW; + + return reasons; +} + +bool CMonitor::updateTearing() { + m_tearingState.activelyTearing = !isTearingBlocked(); + m_tearingState.nextRenderTorn = false; + return m_tearingState.activelyTearing; +} + +uint16_t CMonitor::isDSBlocked(bool full) { + uint16_t reasons = 0; + static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); + + if (*PDIRECTSCANOUT == 0) { + reasons |= DS_BLOCK_USER; + if (!full) + return reasons; + } + + if (*PDIRECTSCANOUT == 2) { + if (!m_activeWorkspace || !m_activeWorkspace->m_hasFullscreenWindow || m_activeWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN) { + reasons |= DS_BLOCK_WINDOWED; + if (!full) + return reasons; + } else if (m_activeWorkspace->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { + reasons |= DS_BLOCK_CONTENT; + if (!full) + return reasons; + } + } + + if (m_tearingState.activelyTearing) { + reasons |= DS_BLOCK_TEARING; + if (!full) + return reasons; + } + + if (!m_mirrors.empty() || isMirror()) { + reasons |= DS_BLOCK_MIRROR; + if (!full) + return reasons; + } + + if (g_pHyprRenderer->m_directScanoutBlocked) { + reasons |= DS_BLOCK_RECORD; + if (!full) + return reasons; + } + + if (g_pPointerManager->softwareLockedFor(m_self.lock())) { + reasons |= DS_BLOCK_SW; + if (!full) + return reasons; + } const auto PCANDIDATE = m_solitaryClient.lock(); + if (!PCANDIDATE) { + reasons |= DS_BLOCK_CANDIDATE; + return reasons; + } - if (!PCANDIDATE) - return false; + const auto PSURFACE = PCANDIDATE->getSolitaryResource(); + if (!PSURFACE || !PSURFACE->m_current.texture || !PSURFACE->m_current.buffer) { + reasons |= DS_BLOCK_SURFACE; + return reasons; + } - const auto PSURFACE = g_pXWaylandManager->getWindowSurface(PCANDIDATE); - - if (!PSURFACE || !PSURFACE->m_current.texture || !PSURFACE->m_current.buffer) - return false; - - if (PSURFACE->m_current.bufferSize != m_pixelSize || PSURFACE->m_current.transform != m_transform) - return false; + if (PSURFACE->m_current.bufferSize != m_pixelSize || PSURFACE->m_current.transform != m_transform) { + reasons |= DS_BLOCK_TRANSFORM; + if (!full) + return reasons; + } // we can't scanout shm buffers. const auto params = PSURFACE->m_current.buffer->dmabuf(); if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) - return false; + reasons |= DS_BLOCK_DMA; - Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {}", rc(PSURFACE.get()), - rc(PSURFACE->m_current.buffer.m_buffer.get())); + return reasons; +} + +bool CMonitor::attemptDirectScanout() { + const auto blockedReason = isDSBlocked(); + if (blockedReason) { + Debug::log(TRACE, "attemptDirectScanout: blocked by {}", blockedReason); + return false; + } + + const auto PCANDIDATE = m_solitaryClient.lock(); + const auto PSURFACE = PCANDIDATE->getSolitaryResource(); + const auto params = PSURFACE->m_current.buffer->dmabuf(); + + Debug::log(TRACE, "attemptDirectScanout: surface {:x} passed, will attempt, buffer {} fmt: {} -> {} (mod {})", rc(PSURFACE.get()), + rc(PSURFACE->m_current.buffer.m_buffer.get()), m_drmFormat, params.format, params.modifier); auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; @@ -1526,7 +1754,7 @@ bool CMonitor::attemptDirectScanout() { if (m_scanoutNeedsCursorUpdate) { if (!m_state.test()) { - Debug::log(TRACE, "attemptDirectScanout: failed basic test"); + Debug::log(TRACE, "attemptDirectScanout: failed basic test on cursor update"); return false; } @@ -1555,6 +1783,7 @@ bool CMonitor::attemptDirectScanout() { } m_output->state->setBuffer(PBUFFER); + Debug::log(TRACE, "attemptDirectScanout: setting presentation mode"); m_output->state->setPresentationMode(m_tearingState.activelyTearing ? Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_IMMEDIATE : Aquamarine::eOutputPresentationMode::AQ_OUTPUT_PRESENTATION_VSYNC); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 7614264f..75bd8750 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -212,6 +212,66 @@ class CMonitor { std::array, 4> m_layerSurfaceLayers; + // keep in sync with HyprCtl + enum eDSBlockReason : uint16_t { + DS_OK = 0, + + DS_BLOCK_UNKNOWN = (1 << 0), + DS_BLOCK_USER = (1 << 1), + DS_BLOCK_WINDOWED = (1 << 2), + DS_BLOCK_CONTENT = (1 << 3), + DS_BLOCK_MIRROR = (1 << 4), + DS_BLOCK_RECORD = (1 << 5), + DS_BLOCK_SW = (1 << 6), + DS_BLOCK_CANDIDATE = (1 << 7), + DS_BLOCK_SURFACE = (1 << 8), + DS_BLOCK_TRANSFORM = (1 << 9), + DS_BLOCK_DMA = (1 << 10), + DS_BLOCK_TEARING = (1 << 11), + DS_BLOCK_FAILED = (1 << 12), + + DS_CHECKS_COUNT = 13, + }; + + // keep in sync with HyprCtl + enum eSolitaryCheck : uint16_t { + SC_OK = 0, + + SC_UNKNOWN = (1 << 0), + SC_NOTIFICATION = (1 << 1), + SC_LOCK = (1 << 2), + SC_WORKSPACE = (1 << 3), + SC_WINDOWED = (1 << 4), + SC_DND = (1 << 5), + SC_SPECIAL = (1 << 6), + SC_ALPHA = (1 << 7), + SC_OFFSET = (1 << 8), + SC_CANDIDATE = (1 << 9), + SC_OPAQUE = (1 << 10), + SC_TRANSFORM = (1 << 11), + SC_OVERLAYS = (1 << 12), + SC_FLOAT = (1 << 13), + SC_WORKSPACES = (1 << 14), + SC_SURFACES = (1 << 15), + + SC_CHECKS_COUNT = 16, + }; + + // keep in sync with HyprCtl + enum eTearingCheck : uint8_t { + TC_OK = 0, + + TC_UNKNOWN = (1 << 0), + TC_NOT_TORN = (1 << 1), + TC_USER = (1 << 2), + TC_ZOOM = (1 << 3), + TC_SUPPORT = (1 << 4), + TC_CANDIDATE = (1 << 5), + TC_WINDOW = (1 << 6), + + TC_CHECKS_COUNT = 7, + }; + // methods void onConnect(bool noRule); void onDisconnect(bool destroy = false); @@ -236,6 +296,11 @@ class CMonitor { WORKSPACEID activeSpecialWorkspaceID(); CBox logicalBox(); void scheduleDone(); + uint16_t isSolitaryBlocked(bool full = false); + void recheckSolitary(); + uint8_t isTearingBlocked(bool full = false); + bool updateTearing(); + uint16_t isDSBlocked(bool full = false); bool attemptDirectScanout(); void setCTM(const Mat3x3& ctm); void onCursorMovedOnMonitor(); diff --git a/src/helpers/MonitorFrameScheduler.cpp b/src/helpers/MonitorFrameScheduler.cpp index 877e486f..1e8a81e5 100644 --- a/src/helpers/MonitorFrameScheduler.cpp +++ b/src/helpers/MonitorFrameScheduler.cpp @@ -80,7 +80,7 @@ void CMonitorFrameScheduler::onFrame() { if (!canRender()) return; - g_pHyprRenderer->recheckSolitaryForMonitor(m_monitor.lock()); + m_monitor->recheckSolitary(); m_monitor->m_tearingState.busy = false; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 1d152122..14bf5cac 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1188,9 +1188,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { static auto PDEBUGOVERLAY = CConfigValue("debug:overlay"); static auto PDAMAGETRACKINGMODE = CConfigValue("debug:damage_tracking"); static auto PDAMAGEBLINK = CConfigValue("debug:damage_blink"); - static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); static auto PVFR = CConfigValue("misc:vfr"); - static auto PTEARINGENABLED = CConfigValue("general:allow_tearing"); static int damageBlinkCleanup = 0; // because double-buffered @@ -1230,47 +1228,19 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { return; // tearing and DS first - bool shouldTear = false; - if (pMonitor->m_tearingState.nextRenderTorn) { - pMonitor->m_tearingState.nextRenderTorn = false; + bool shouldTear = pMonitor->updateTearing(); - if (!*PTEARINGENABLED) { - Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); - return; - } + if (pMonitor->attemptDirectScanout()) { + return; + } else if (!pMonitor->m_lastScanout.expired()) { + Debug::log(LOG, "Left a direct scanout."); + pMonitor->m_lastScanout.reset(); - if (g_pHyprOpenGL->m_renderData.mouseZoomFactor != 1.0) { - Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring"); - return; - } + // 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); - if (!pMonitor->m_tearingState.canTear) { - Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); - return; - } - - if (!pMonitor->m_solitaryClient.expired()) - shouldTear = true; - } - - pMonitor->m_tearingState.activelyTearing = shouldTear; - - if ((*PDIRECTSCANOUT == 1 || - (*PDIRECTSCANOUT == 2 && pMonitor->m_activeWorkspace && pMonitor->m_activeWorkspace->m_hasFullscreenWindow && - pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN && pMonitor->m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && - !shouldTear) { - if (pMonitor->attemptDirectScanout()) { - return; - } else if (!pMonitor->m_lastScanout.expired()) { - Debug::log(LOG, "Left a direct scanout."); - pMonitor->m_lastScanout.reset(); - - // 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; - } + pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; } EMIT_HOOK_EVENT("preRender", pMonitor); @@ -2143,70 +2113,6 @@ void CHyprRenderer::initiateManualCrash() { **PDT = 0; } -void CHyprRenderer::recheckSolitaryForMonitor(PHLMONITOR pMonitor) { - pMonitor->m_solitaryClient.reset(); // reset it, if we find one it will be set. - - if (g_pHyprNotificationOverlay->hasAny() || g_pSessionLockManager->isSessionLocked()) - return; - - const auto PWORKSPACE = pMonitor->m_activeWorkspace; - - if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PROTO::data->dndActive() || pMonitor->m_activeSpecialWorkspace || PWORKSPACE->m_alpha->value() != 1.f || - PWORKSPACE->m_renderOffset->value() != Vector2D{}) - return; - - const auto PCANDIDATE = PWORKSPACE->getFullscreenWindow(); - - if (!PCANDIDATE) - return; // ???? - - if (!PCANDIDATE->opaque()) - return; - - if (PCANDIDATE->m_realSize->value() != pMonitor->m_size || PCANDIDATE->m_realPosition->value() != pMonitor->m_position || PCANDIDATE->m_realPosition->isBeingAnimated() || - PCANDIDATE->m_realSize->isBeingAnimated()) - return; - - if (!pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_OVERLAY].empty()) - return; - - for (auto const& topls : pMonitor->m_layerSurfaceLayers[ZWLR_LAYER_SHELL_V1_LAYER_TOP]) { - if (topls->m_alpha->value() != 0.f) - return; - } - - for (auto const& w : g_pCompositor->m_windows) { - if (w == PCANDIDATE || (!w->m_isMapped && !w->m_fadingOut) || w->isHidden()) - continue; - - if (w->workspaceID() == PCANDIDATE->workspaceID() && w->m_isFloating && w->m_createdOverFullscreen && w->visibleOnMonitor(pMonitor)) - return; - } - - if (pMonitor->m_activeSpecialWorkspace) - return; - - for (auto const& ws : g_pCompositor->getWorkspaces()) { - if (ws->m_alpha->value() <= 0.F || !ws->m_isSpecialWorkspace || ws->m_monitor != pMonitor) - continue; - - return; - } - - // check if it did not open any subsurfaces or shit - int surfaceCount = 0; - if (PCANDIDATE->m_isX11) - surfaceCount = 1; - else - surfaceCount = PCANDIDATE->popupsCount() + PCANDIDATE->surfacesCount(); - - if (surfaceCount > 1) - return; - - // found one! - pMonitor->m_solitaryClient = PCANDIDATE; -} - SP CHyprRenderer::getOrCreateRenderbuffer(SP buffer, uint32_t fmt) { auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; }); diff --git a/src/render/Renderer.hpp b/src/render/Renderer.hpp index b898e37c..1e184174 100644 --- a/src/render/Renderer.hpp +++ b/src/render/Renderer.hpp @@ -69,7 +69,6 @@ class CHyprRenderer { bool fixMisalignedFSV1 = false); std::tuple getRenderTimes(PHLMONITOR pMonitor); // avg max min void renderLockscreen(PHLMONITOR pMonitor, const Time::steady_tp& now, const CBox& geometry); - void recheckSolitaryForMonitor(PHLMONITOR pMonitor); void setCursorSurface(SP surf, int hotspotX, int hotspotY, bool force = false); void setCursorFromName(const std::string& name, bool force = false); void onRenderbufferDestroy(CRenderbuffer* rb);