mirror of
https://github.com/hyprwm/Hyprland
synced 2026-05-11 06:38:04 +02:00
monitor: centralize solitary and scanout eligibility checks
This commit is contained in:
parent
d5b00bbc17
commit
4c42269ce6
7 changed files with 195 additions and 43 deletions
|
|
@ -1,7 +1,50 @@
|
|||
#include <xf86drm.h>
|
||||
#include <array>
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <sys/stat.h>
|
||||
#include "Drm.hpp"
|
||||
|
||||
namespace {
|
||||
using SDRMNodePair = std::array<dev_t, 2>;
|
||||
|
||||
std::optional<SDRMNodePair> 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<dev_t> 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<SDRMNodePair, bool> 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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <sys/types.h>
|
||||
|
||||
namespace DRM {
|
||||
bool sameGpu(int fd1, int fd2);
|
||||
std::optional<dev_t> devIDFromFD(int fd);
|
||||
bool sameGpu(int fd1, int fd2);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Hyprlang::INT>("render:direct_scanout");
|
||||
static auto PPASS = CConfigValue<Hyprlang::INT>("render:cm_fs_passthrough");
|
||||
static auto PNONSHADER = CConfigValue<Hyprlang::INT>("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<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;
|
||||
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<uintptr_t>(PSURFACE.get()),
|
||||
rc<uintptr_t>(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<Hyprlang::INT>("cursor:no_hardware_cursors");
|
||||
static auto PINVISIBLE = CConfigValue<Hyprlang::INT>("cursor:invisible");
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include <aquamarine/output/Output.hpp>
|
||||
#include <aquamarine/allocator/Swapchain.hpp>
|
||||
#include <hyprutils/os/FileDescriptor.hpp>
|
||||
#include <sys/types.h>
|
||||
|
||||
#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<dev_t> m_cachedAllocatorDRMDev;
|
||||
std::optional<dev_t> m_cachedCompositorDRMDev;
|
||||
int m_cachedAllocatorDRMFD = -1;
|
||||
int m_cachedCompositorDRMFD = -1;
|
||||
std::optional<bool> m_cachedSameGPU;
|
||||
|
||||
NColorManagement::PImageDescription m_imageDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{});
|
||||
bool m_noShaderCTM = false; // sets drm CTM, restore needed
|
||||
|
||||
|
|
|
|||
|
|
@ -455,7 +455,7 @@ SP<Aquamarine::IBuffer> CPointerManager::renderHWCursorBuffer(SP<CPointerManager
|
|||
options.length = 2;
|
||||
options.scanout = true;
|
||||
options.cursor = true;
|
||||
options.multigpu = !DRM::sameGpu(state->monitor->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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
32
tests/helpers/Drm.cpp
Normal file
32
tests/helpers/Drm.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#include <helpers/Drm.hpp>
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
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);
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue