From 4c42269ce6c44af40ea63f048d67dc0b898c8213 Mon Sep 17 00:00:00 2001 From: Pppp1116 Date: Fri, 3 Apr 2026 13:26:49 +0100 Subject: [PATCH] monitor: centralize solitary and scanout eligibility checks --- src/helpers/Drm.cpp | 49 ++++++++++++++ src/helpers/Drm.hpp | 6 +- src/helpers/Monitor.cpp | 113 ++++++++++++++++++++++++-------- src/helpers/Monitor.hpp | 9 +++ src/managers/PointerManager.cpp | 2 +- src/render/Renderer.cpp | 27 ++++---- tests/helpers/Drm.cpp | 32 +++++++++ 7 files changed, 195 insertions(+), 43 deletions(-) create mode 100644 tests/helpers/Drm.cpp diff --git a/src/helpers/Drm.cpp b/src/helpers/Drm.cpp index 207b5e3d4..f91c5692a 100644 --- a/src/helpers/Drm.cpp +++ b/src/helpers/Drm.cpp @@ -1,7 +1,50 @@ #include +#include +#include +#include +#include +#include #include "Drm.hpp" +namespace { + using SDRMNodePair = std::array; + + std::optional getDrmNodePair(int fd1, int fd2) { + const auto DEVA = DRM::devIDFromFD(fd1); + const auto DEVB = DRM::devIDFromFD(fd2); + if (!DEVA || !DEVB) + return std::nullopt; + + SDRMNodePair pair = {*DEVA, *DEVB}; + if (pair[0] > pair[1]) + std::swap(pair[0], pair[1]); + + return pair; + } +} + +std::optional DRM::devIDFromFD(int fd) { + struct stat stat = {}; + if (fstat(fd, &stat) != 0 || !S_ISCHR(stat.st_mode)) + return std::nullopt; + + return stat.st_rdev; +} + bool DRM::sameGpu(int fd1, int fd2) { + if (fd1 >= 0 && fd1 == fd2) + return true; + + static std::mutex cacheMutex; + static std::map sameGpuCache; + + const auto NODEPAIR = getDrmNodePair(fd1, fd2); + if (NODEPAIR) { + std::scoped_lock lock(cacheMutex); + if (const auto it = sameGpuCache.find(*NODEPAIR); it != sameGpuCache.end()) + return it->second; + } + drmDevice* devA = nullptr; drmDevice* devB = nullptr; @@ -16,5 +59,11 @@ bool DRM::sameGpu(int fd1, int fd2) { drmFreeDevice(&devA); drmFreeDevice(&devB); + + if (NODEPAIR) { + std::scoped_lock lock(cacheMutex); + sameGpuCache[*NODEPAIR] = same; + } + return same; } diff --git a/src/helpers/Drm.hpp b/src/helpers/Drm.hpp index bc56b1eec..755ee0502 100644 --- a/src/helpers/Drm.hpp +++ b/src/helpers/Drm.hpp @@ -1,5 +1,9 @@ #pragma once +#include +#include + namespace DRM { - bool sameGpu(int fd1, int fd2); + std::optional devIDFromFD(int fd); + bool sameGpu(int fd1, int fd2); } diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 33c2151c9..399510282 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -1661,7 +1661,25 @@ void CMonitor::setCTM(const Mat3x3& ctm_) { } uint32_t CMonitor::isSolitaryBlocked(bool full) { - uint32_t reasons = 0; + uint32_t reasons = 0; + + const auto PWORKSPACE = m_activeWorkspace; + if (!PWORKSPACE) { + reasons |= SC_WORKSPACE; + return reasons; + } + + if (!PWORKSPACE->m_hasFullscreenWindow) { + reasons |= SC_WINDOWED; + if (!full) + return reasons; + } + + if (m_activeSpecialWorkspace) { + reasons |= SC_SPECIAL; + if (!full) + return reasons; + } if (g_pHyprNotificationOverlay->hasAny()) { reasons |= SC_NOTIFICATION; @@ -1681,30 +1699,12 @@ uint32_t CMonitor::isSolitaryBlocked(bool 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) @@ -1780,10 +1780,15 @@ uint32_t CMonitor::isSolitaryBlocked(bool full) { void CMonitor::recheckSolitary() { m_solitaryClient.reset(); // reset it, if we find one it will be set. + + const auto PWORKSPACE = m_activeWorkspace; + if (!PWORKSPACE) + return; + if (isSolitaryBlocked()) return; - m_solitaryClient = m_activeWorkspace->getFullscreenWindow(); + m_solitaryClient = PWORKSPACE->getFullscreenWindow(); } uint8_t CMonitor::isTearingBlocked(bool full) { @@ -1847,6 +1852,15 @@ uint16_t CMonitor::isDSBlocked(bool full) { static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); static auto PPASS = CConfigValue("render:cm_fs_passthrough"); static auto PNONSHADER = CConfigValue("render:non_shader_cm"); + const auto PWORKSPACE = m_activeWorkspace; + + // Fast reject for the hot render path; full=true callers still collect + // the remaining blockers for hyprctl/debug output below. + if (!canAttemptDirectScanoutFast()) { + reasons |= DS_BLOCK_CANDIDATE; + if (!full) + return reasons; + } if (*PDIRECTSCANOUT == 0) { reasons |= DS_BLOCK_USER; @@ -1855,11 +1869,11 @@ uint16_t CMonitor::isDSBlocked(bool full) { } if (*PDIRECTSCANOUT == 2) { - if (!m_activeWorkspace || !m_activeWorkspace->m_hasFullscreenWindow || m_activeWorkspace->m_fullscreenMode != FSMODE_FULLSCREEN) { + if (!PWORKSPACE || !PWORKSPACE->m_hasFullscreenWindow || PWORKSPACE->m_fullscreenMode != FSMODE_FULLSCREEN) { reasons |= DS_BLOCK_WINDOWED; if (!full) return reasons; - } else if (m_activeWorkspace->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { + } else if (PWORKSPACE->getFullscreenWindow()->getContentType() != CONTENT_TYPE_GAME) { reasons |= DS_BLOCK_CONTENT; if (!full) return reasons; @@ -1930,12 +1944,7 @@ bool CMonitor::attemptDirectScanout() { const auto PCANDIDATE = m_solitaryClient.lock(); const auto PSURFACE = PCANDIDATE->getSolitaryResource(); - const auto params = PSURFACE->m_current.buffer->dmabuf(); - - Log::logger->log(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; + auto PBUFFER = PSURFACE->m_current.buffer.m_buffer; // #TODO this entire bit needs figuring out, vrr goes down the drain without it if (PBUFFER == m_output->state->state().buffer && *PSAME) { @@ -1963,6 +1972,11 @@ bool CMonitor::attemptDirectScanout() { return true; } + const auto params = PSURFACE->m_current.buffer->dmabuf(); + + Log::logger->log(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); + // FIXME: make sure the buffer actually follows the available scanout dmabuf formats // and comes from the appropriate device. This may implode on multi-gpu!! @@ -1990,7 +2004,7 @@ bool CMonitor::attemptDirectScanout() { m_output->state->addDamage(PSURFACE->m_current.accumulateBufferDamage()); // multigpu needs a fence to trigger fence syncing blits and also committing with the recreated dgpu fence - if (!DRM::sameGpu(m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd) && g_pHyprRenderer->explicitSyncSupported()) { + if (g_pHyprRenderer->explicitSyncSupported() && isMultiGPU()) { auto sync = g_pHyprRenderer->createSyncFDManager(); if (sync->fd().isValid()) { @@ -2032,6 +2046,47 @@ bool CMonitor::attemptDirectScanout() { return true; } +bool CMonitor::canAttemptDirectScanoutFast() const { + return !m_solitaryClient.expired() || !m_lastScanout.expired() || m_directScanoutIsActive; +} + +bool CMonitor::isMultiGPU() { + if (!m_output || !g_pCompositor) + return false; + + const auto PREFERREDALLOCATOR = m_output->getBackend()->preferredAllocator(); + const int allocatorFD = PREFERREDALLOCATOR ? PREFERREDALLOCATOR->drmFD() : -1; + const int compositorFD = g_pCompositor->m_drm.fd; + + if (allocatorFD < 0 || compositorFD < 0) { + m_cachedAllocatorDRMDev.reset(); + m_cachedCompositorDRMDev.reset(); + m_cachedAllocatorDRMFD = allocatorFD; + m_cachedCompositorDRMFD = compositorFD; + m_cachedSameGPU = true; + return false; + } + + const auto allocatorDev = DRM::devIDFromFD(allocatorFD); + const auto compositorDev = DRM::devIDFromFD(compositorFD); + + // AQ can reopen DRM nodes for refcounting, so raw fd numbers are not a stable cache key. + const bool useDeviceIDCache = allocatorDev.has_value() && compositorDev.has_value(); + const bool cacheStale = !m_cachedSameGPU || + (useDeviceIDCache ? m_cachedAllocatorDRMDev != allocatorDev || m_cachedCompositorDRMDev != compositorDev : + m_cachedAllocatorDRMFD != allocatorFD || m_cachedCompositorDRMFD != compositorFD); + + if (cacheStale) { + m_cachedAllocatorDRMDev = allocatorDev; + m_cachedCompositorDRMDev = compositorDev; + m_cachedAllocatorDRMFD = allocatorFD; + m_cachedCompositorDRMFD = compositorFD; + m_cachedSameGPU = DRM::sameGpu(allocatorFD, compositorFD); + } + + return !*m_cachedSameGPU; +} + bool CMonitor::shouldUseSoftwareCursors() { static auto PNOHW = CConfigValue("cursor:no_hardware_cursors"); static auto PINVISIBLE = CConfigValue("cursor:invisible"); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index bd4f043b0..12a5de6b2 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "../helpers/TransferFunction.hpp" #include "../config/shared/monitor/MonitorRule.hpp" @@ -323,6 +324,8 @@ class CMonitor { bool updateTearing(); uint16_t isDSBlocked(bool full = false); bool attemptDirectScanout(); + bool canAttemptDirectScanoutFast() const; + bool isMultiGPU(); void setCTM(const Mat3x3& ctm); void onCursorMovedOnMonitor(); void setDPMS(bool on); @@ -370,6 +373,12 @@ class CMonitor { PHLWINDOWREF m_previousFSWindow; bool m_needsHDRupdate = false; + std::optional m_cachedAllocatorDRMDev; + std::optional m_cachedCompositorDRMDev; + int m_cachedAllocatorDRMFD = -1; + int m_cachedCompositorDRMFD = -1; + std::optional m_cachedSameGPU; + NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); bool m_noShaderCTM = false; // sets drm CTM, restore needed diff --git a/src/managers/PointerManager.cpp b/src/managers/PointerManager.cpp index 1122a4439..8d3c83f03 100644 --- a/src/managers/PointerManager.cpp +++ b/src/managers/PointerManager.cpp @@ -455,7 +455,7 @@ SP CPointerManager::renderHWCursorBuffer(SPmonitor->m_output->getBackend()->preferredAllocator()->drmFD(), g_pCompositor->m_drm.fd); + options.multigpu = state->monitor->isMultiGPU(); // We do not set the format (unless shm). If it's unset (DRM_FORMAT_INVALID) then the swapchain will pick for us, // but if it's set, we don't wanna change it. if (shouldUseCpuBuffer) diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index a9643df0d..b6f4acb76 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1901,21 +1901,24 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { return; // tearing and DS first - bool shouldTear = pMonitor->updateTearing(); + bool shouldTear = pMonitor->updateTearing(); + const bool canAttemptDirectScanout = pMonitor->canAttemptDirectScanoutFast(); - if (pMonitor->attemptDirectScanout()) { - pMonitor->m_directScanoutIsActive = true; - return; - } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { - Log::logger->log(Log::DEBUG, "Left a direct scanout."); - pMonitor->m_lastScanout.reset(); - pMonitor->m_directScanoutIsActive = false; + if (canAttemptDirectScanout) { + if (pMonitor->attemptDirectScanout()) { + pMonitor->m_directScanoutIsActive = true; + return; + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) { + Log::logger->log(Log::DEBUG, "Left a direct scanout."); + pMonitor->m_lastScanout.reset(); + 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); + // 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; + } } Event::bus()->m_events.render.pre.emit(pMonitor); diff --git a/tests/helpers/Drm.cpp b/tests/helpers/Drm.cpp new file mode 100644 index 000000000..6ebc23675 --- /dev/null +++ b/tests/helpers/Drm.cpp @@ -0,0 +1,32 @@ +#include + +#include + +#include +#include +#include + +TEST(Helpers, drmDevIDFromFDCharacterDevice) { + const int FD = open("/dev/null", O_RDONLY | O_CLOEXEC); + ASSERT_GE(FD, 0); + + struct stat stat = {}; + ASSERT_EQ(fstat(FD, &stat), 0); + + const auto devID = DRM::devIDFromFD(FD); + EXPECT_TRUE(devID.has_value()); + EXPECT_EQ(*devID, stat.st_rdev); + + close(FD); +} + +TEST(Helpers, drmDevIDFromFDRejectsRegularFiles) { + char path[] = "/tmp/hyprland-drm-testXXXXXX"; + const int FD = mkstemp(path); + ASSERT_GE(FD, 0); + + EXPECT_FALSE(DRM::devIDFromFD(FD).has_value()); + + close(FD); + unlink(path); +}