internal: Solitary clients with single subsurface & verbose solitary/tearing/DS checks (#11228)

Adds more verbose checks for various conditional rendering mechanisms
This commit is contained in:
UjinT34
2025-08-22 20:24:25 +03:00
committed by GitHub
parent e95ba5bf59
commit 0d45b277d6
15 changed files with 551 additions and 137 deletions

View File

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

View File

@@ -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++; \

View File

@@ -168,6 +168,7 @@ static bool test() {
NLog::log("{}Reloading the config", Colors::YELLOW);
OK(getFromSocket("/reload"));
OK(getFromSocket("/dispatch workspace 1"));
return !ret;
}

View File

@@ -0,0 +1,76 @@
#include "tests.hpp"
#include "../../shared.hpp"
#include "../../hyprctlCompat.hpp"
#include <thread>
#include <chrono>
#include <hyprutils/os/Process.hpp>
#include <hyprutils/memory/WeakPtr.hpp>
#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)

View File

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

View File

@@ -1,4 +1,5 @@
#include "HyprCtl.hpp"
#include "helpers/Monitor.hpp"
#include <algorithm>
#include <format>
@@ -109,6 +110,91 @@ static std::string availableModesForOutput(PHLMONITOR pMonitor, eHyprCtlOutputFo
return result;
}
const std::array<const char*, CMonitor::SC_CHECKS_COUNT> SOLITARY_REASONS_JSON = {
"\"UNKNOWN\"", "\"NOTIFICATION\"", "\"LOCK\"", "\"WORKSPACE\"", "\"WINDOWED\"", "\"DND\"", "\"SPECIAL\"", "\"ALPHA\"",
"\"OFFSET\"", "\"CANDIDATE\"", "\"OPAQUE\"", "\"TRANSFORM\"", "\"OVERLAYS\"", "\"FLOAT\"", "\"WORKSPACES\"", "\"SURFACES\"",
};
const std::array<const char*, CMonitor::SC_CHECKS_COUNT> 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<CMonitor> 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<const char*, CMonitor::DS_CHECKS_COUNT> DS_REASONS_JSON = {
"\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"",
"\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"",
};
const std::array<const char*, CMonitor::DS_CHECKS_COUNT> 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<CMonitor> 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<const char*, CMonitor::TC_CHECKS_COUNT> TEARING_REASONS_JSON = {
"\"UNKNOWN\"", "\"NOT_TORN\"", "\"USER\"", "\"ZOOM\"", "\"SUPPORT\"", "\"CANDIDATE\"", "\"WINDOW\"",
};
const std::array<const char*, CMonitor::TC_CHECKS_COUNT> 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<CMonitor> 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<CMonitor> 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<CMonitor>
"dpmsStatus": {},
"vrr": {},
"solitary": "{:x}",
"solitaryBlockedBy": {},
"activelyTearing": {},
"tearingBlockedBy": {},
"directScanoutTo": "{:x}",
"directScanoutBlockedBy": {},
"disabled": {},
"currentFormat": "{}",
"mirrorOf": "{}",
@@ -155,28 +244,32 @@ std::string CHyprCtl::getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor>
}},)#",
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<int>(m->m_pixelSize.x), m->m_pixelSize.y, sc<int>(m->m_output->physicalSize.x), sc<int>(m->m_output->physicalSize.y),
m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->activeWorkspaceID(),
escapeJSONStrings(m->m_output->serial), sc<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), sc<int>(m->m_output->physicalSize.x),
sc<int>(m->m_output->physicalSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(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<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y),
sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale, sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "true" : "false"),
(m->m_dpmsStatus ? "true" : "false"), (m->m_output->state->state().adaptiveSync ? "true" : "false"), rc<uint64_t>(m->m_solitaryClient.get()),
(m->m_tearingState.activelyTearing ? "true" : "false"), rc<uint64_t>(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<uint64_t>(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<int>(m->m_pixelSize.x), sc<int>(m->m_pixelSize.y), m->m_refreshRate, sc<int>(m->m_position.x), sc<int>(m->m_position.y), m->m_shortDescription,
m->m_output->make, m->m_output->model, sc<int>(m->m_output->physicalSize.x), sc<int>(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<int>(m->m_reservedTopLeft.x), sc<int>(m->m_reservedTopLeft.y), sc<int>(m->m_reservedBottomRight.x), sc<int>(m->m_reservedBottomRight.y), m->m_scale,
sc<int>(m->m_transform), (m == g_pCompositor->m_lastMonitor ? "yes" : "no"), sc<int>(m->m_dpmsStatus), m->m_output->state->state().adaptiveSync,
rc<uint64_t>(m->m_solitaryClient.get()), m->m_tearingState.activelyTearing, rc<uint64_t>(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<uint64_t>(m->m_solitaryClient.get()), getSolitaryBlockedReason(m, format), m->m_tearingState.activelyTearing, getTearingBlockedReason(m, format),
rc<uint64_t>(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;

View File

@@ -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<CMonitor> m, eHyprCtlOutputFormat format);
static std::string getDSBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
static std::string getTearingBlockedReason(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
static std::string getMonitorData(Hyprutils::Memory::CSharedPointer<CMonitor> m, eHyprCtlOutputFormat format);
private:

View File

@@ -385,6 +385,9 @@ void CPopup::bfHelper(std::vector<WP<CPopup>> const& nodes, std::function<void(W
nodes2.reserve(nodes.size() * 2);
for (auto const& n : nodes) {
if (!n)
continue;
for (auto const& c : n->m_children) {
nodes2.push_back(c->m_self);
}
@@ -395,6 +398,9 @@ void CPopup::bfHelper(std::vector<WP<CPopup>> const& nodes, std::function<void(W
}
void CPopup::breadthfirst(std::function<void(WP<CPopup>, void*)> fn, void* data) {
if (!m_self)
return;
std::vector<WP<CPopup>> popups;
popups.push_back(m_self);
bfHelper(popups, fn, data);

View File

@@ -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<CWLSurfaceResource> 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;
}

View File

@@ -413,6 +413,7 @@ class CWindow {
std::optional<std::string> xdgDescription();
PHLWINDOW parent();
bool priorityFocus();
SP<CWLSurfaceResource> getSolitaryResource();
CBox getWindowMainSurfaceBox() const {
return {m_realPosition->value().x, m_realPosition->value().y, m_realSize->value().x, m_realSize->value().y};

View File

@@ -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<Hyprlang::INT>("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<Hyprlang::INT>("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<uintptr_t>(PSURFACE.get()),
rc<uintptr_t>(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<uintptr_t>(PSURFACE.get()),
rc<uintptr_t>(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);

View File

@@ -212,6 +212,66 @@ class CMonitor {
std::array<std::vector<PHLLSREF>, 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();

View File

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

View File

@@ -1188,9 +1188,7 @@ void CHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) {
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 PDIRECTSCANOUT = CConfigValue<Hyprlang::INT>("render:direct_scanout");
static auto PVFR = CConfigValue<Hyprlang::INT>("misc:vfr");
static auto PTEARINGENABLED = CConfigValue<Hyprlang::INT>("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<CRenderbuffer> CHyprRenderer::getOrCreateRenderbuffer(SP<Aquamarine::IBuffer> buffer, uint32_t fmt) {
auto it = std::ranges::find_if(m_renderbuffers, [&](const auto& other) { return other->m_hlBuffer == buffer; });

View File

@@ -69,7 +69,6 @@ class CHyprRenderer {
bool fixMisalignedFSV1 = false);
std::tuple<float, float, float> 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<CWLSurface> surf, int hotspotX, int hotspotY, bool force = false);
void setCursorFromName(const std::string& name, bool force = false);
void onRenderbufferDestroy(CRenderbuffer* rb);