diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index e5b9a00cf..a9388789d 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -141,12 +141,12 @@ std::string CHyprCtl::getSolitaryBlockedReason(Hyprutils::Memory::CSharedPointer const std::array DS_REASONS_JSON = { "\"UNKNOWN\"", "\"USER\"", "\"WINDOWED\"", "\"CONTENT\"", "\"MIRROR\"", "\"RECORD\"", "\"SW\"", - "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", + "\"CANDIDATE\"", "\"SURFACE\"", "\"TRANSFORM\"", "\"DMA\"", "\"TEARING\"", "\"FAILED\"", "\"CM\"", }; 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", + "missing candidate", "invalid surface", "surface transformations", "invalid buffer", "tearing", "activation failed", "color management", }; std::string CHyprCtl::getDSBlockedReason(Hyprutils::Memory::CSharedPointer m, eHyprCtlOutputFormat format) { diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index a2a6ff0d5..e5b605977 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -988,8 +988,8 @@ bool CMonitor::shouldSkipScheduleFrameOnMouseEvent() { static auto PMINRR = CConfigValue("cursor:min_refresh_rate"); // skip scheduling extra frames for fullsreen apps with vrr - const bool shouldSkip = m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN && - (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && m_output->state->state().adaptiveSync; + const bool shouldSkip = inFullscreenMode() && (*PNOBREAK == 1 || (*PNOBREAK == 2 && m_activeWorkspace->getFullscreenWindow()->getContentType() == CONTENT_TYPE_GAME)) && + m_output->state->state().adaptiveSync; // keep requested minimum refresh rate if (shouldSkip && *PMINRR && m_lastPresentationTimer.getMillis() > 1000.0f / *PMINRR) { @@ -1631,24 +1631,27 @@ uint8_t CMonitor::isTearingBlocked(bool full) { } if (!*PTEARINGENABLED) { - Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); reasons |= TC_USER; - if (!full) + if (!full) { + Debug::log(WARN, "Tearing commit requested but the master switch general:allow_tearing is off, ignoring"); 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) + if (!full) { + Debug::log(WARN, "Tearing commit requested but scale factor is not 1, ignoring"); return reasons; + } } if (!m_tearingState.canTear) { - Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); reasons |= TC_SUPPORT; - if (!full) + if (!full) { + Debug::log(WARN, "Tearing commit requested but monitor doesn't support it, ignoring"); return reasons; + } } if (m_solitaryClient.expired()) { @@ -1671,6 +1674,7 @@ bool CMonitor::updateTearing() { uint16_t CMonitor::isDSBlocked(bool full) { uint16_t reasons = 0; static auto PDIRECTSCANOUT = CConfigValue("render:direct_scanout"); + static auto PPASS = CConfigValue("render:cm_fs_passthrough"); if (*PDIRECTSCANOUT == 0) { reasons |= DS_BLOCK_USER; @@ -1734,8 +1738,14 @@ uint16_t CMonitor::isDSBlocked(bool full) { // we can't scanout shm buffers. const auto params = PSURFACE->m_current.buffer->dmabuf(); - if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) + if (!params.success || !PSURFACE->m_current.texture->m_eglImage /* dmabuf */) { reasons |= DS_BLOCK_DMA; + if (!full) + return reasons; + } + + if (!canNoShaderCM() && (!inHDR() || (PSURFACE->m_colorManagement.valid() && PSURFACE->m_colorManagement->isWindowsScRGB())) && *PPASS != 1) + reasons |= DS_BLOCK_CM; return reasons; } @@ -1935,6 +1945,52 @@ bool CMonitor::inHDR() { return m_output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2; } +bool CMonitor::inFullscreenMode() { + return m_activeWorkspace && m_activeWorkspace->m_hasFullscreenWindow && m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN; +} + +std::optional CMonitor::getFSImageDescription() { + if (!inFullscreenMode()) + return {}; + + const auto FS_WINDOW = m_activeWorkspace->getFullscreenWindow(); + if (!FS_WINDOW) + return {}; // should be unreachable + + const auto ROOT_SURF = FS_WINDOW->m_wlSurface->resource(); + const auto SURF = ROOT_SURF->findWithCM(); + return SURF ? SURF->m_colorManagement->imageDescription() : SImageDescription{}; +} + +bool CMonitor::needsCM() { + return getFSImageDescription() != m_imageDescription; +} + +// TODO support more drm properties +bool CMonitor::canNoShaderCM() { + const auto SRC_DESC = getFSImageDescription(); + if (!SRC_DESC.has_value()) + return false; + + if (SRC_DESC.value() == m_imageDescription) + return true; // no CM needed + + if (SRC_DESC->icc.fd >= 0 || m_imageDescription.icc.fd >= 0) + return false; // no ICC support + + // only primaries differ + if (SRC_DESC->transferFunction == m_imageDescription.transferFunction && SRC_DESC->transferFunctionPower == m_imageDescription.transferFunctionPower && + SRC_DESC->luminances == m_imageDescription.luminances && SRC_DESC->masteringLuminances == m_imageDescription.masteringLuminances && + SRC_DESC->maxCLL == m_imageDescription.maxCLL && SRC_DESC->maxFALL == m_imageDescription.maxFALL) + return true; + + return false; +} + +bool CMonitor::doesNoShaderCM() { + return m_noShaderCTM; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 75bd87506..5400fd974 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -229,8 +229,9 @@ class CMonitor { DS_BLOCK_DMA = (1 << 10), DS_BLOCK_TEARING = (1 << 11), DS_BLOCK_FAILED = (1 << 12), + DS_BLOCK_CM = (1 << 13), - DS_CHECKS_COUNT = 13, + DS_CHECKS_COUNT = 14, }; // keep in sync with HyprCtl @@ -273,56 +274,66 @@ class CMonitor { }; // methods - void onConnect(bool noRule); - void onDisconnect(bool destroy = false); - void applyCMType(eCMType cmType); - bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); - void addDamage(const pixman_region32_t* rg); - void addDamage(const CRegion& rg); - void addDamage(const CBox& box); - bool shouldSkipScheduleFrameOnMouseEvent(); - void setMirror(const std::string&); - bool isMirror(); - bool matchesStaticSelector(const std::string& selector) const; - float getDefaultScale(); - void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); - void changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false); - void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); - void setSpecialWorkspace(const WORKSPACEID& id); - void moveTo(const Vector2D& pos); - Vector2D middle(); - void updateMatrix(); - WORKSPACEID activeWorkspaceID(); - 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(); - void setDPMS(bool on); + void onConnect(bool noRule); + void onDisconnect(bool destroy = false); + void applyCMType(eCMType cmType); + bool applyMonitorRule(SMonitorRule* pMonitorRule, bool force = false); + void addDamage(const pixman_region32_t* rg); + void addDamage(const CRegion& rg); + void addDamage(const CBox& box); + bool shouldSkipScheduleFrameOnMouseEvent(); + void setMirror(const std::string&); + bool isMirror(); + bool matchesStaticSelector(const std::string& selector) const; + float getDefaultScale(); + void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); + void changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false); + void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); + void setSpecialWorkspace(const WORKSPACEID& id); + void moveTo(const Vector2D& pos); + Vector2D middle(); + void updateMatrix(); + WORKSPACEID activeWorkspaceID(); + 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(); + void setDPMS(bool on); - void debugLastPresentation(const std::string& message); + void debugLastPresentation(const std::string& message); - bool supportsWideColor(); - bool supportsHDR(); - float minLuminance(float defaultValue = 0); - int maxLuminance(int defaultValue = 80); - int maxAvgLuminance(int defaultValue = 80); + bool supportsWideColor(); + bool supportsHDR(); + float minLuminance(float defaultValue = 0); + int maxLuminance(int defaultValue = 80); + int maxAvgLuminance(int defaultValue = 80); - bool wantsWideColor(); - bool wantsHDR(); + bool wantsWideColor(); + bool wantsHDR(); - bool inHDR(); + bool inHDR(); + + /// Has an active workspace with a real fullscreen window + bool inFullscreenMode(); + std::optional getFSImageDescription(); + + bool needsCM(); + /// Can do CM without shader + bool canNoShaderCM(); + bool doesNoShaderCM(); bool m_enabled = false; bool m_renderingInitPassed = false; WP m_previousFSWindow; NColorManagement::SImageDescription m_imageDescription; + bool m_noShaderCTM = false; // sets drm CTM, restore needed // For the list lookup diff --git a/src/protocols/core/Compositor.cpp b/src/protocols/core/Compositor.cpp index a859148f6..484df21f9 100644 --- a/src/protocols/core/Compositor.cpp +++ b/src/protocols/core/Compositor.cpp @@ -423,6 +423,10 @@ SP CWLSurfaceResource::findFirstPreorder(std::function CWLSurfaceResource::findWithCM() { + return findFirstPreorder([this](SP surf) { return surf->m_colorManagement.valid() && surf->extends() == extends(); }); +} + std::pair, Vector2D> CWLSurfaceResource::at(const Vector2D& localCoords, bool allowsInput) { std::vector, Vector2D>> surfs; breadthfirst([&surfs](SP surf, const Vector2D& offset, void* data) { surfs.emplace_back(std::make_pair<>(surf, offset)); }, &surfs); diff --git a/src/protocols/core/Compositor.hpp b/src/protocols/core/Compositor.hpp index 5a69deb68..0eff3f085 100644 --- a/src/protocols/core/Compositor.hpp +++ b/src/protocols/core/Compositor.hpp @@ -108,6 +108,7 @@ class CWLSurfaceResource { void breadthfirst(std::function, const Vector2D&, void*)> fn, void* data); SP findFirstPreorder(std::function)> fn); + SP findWithCM(); void presentFeedback(const Time::steady_tp& when, PHLMONITOR pMonitor, bool discarded = false); void commitState(SSurfaceState& state); NColorManagement::SImageDescription getPreferredImageDescription(); diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index f4599a84f..0780adf92 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1597,17 +1597,17 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); } - const bool canPassHDRSurface = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? - m_renderData.surface->m_colorManagement->isHDR() && !m_renderData.surface->m_colorManagement->isWindowsScRGB() : - false; // windows scRGB requires CM shader - auto imageDescription = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? - m_renderData.surface->m_colorManagement->imageDescription() : - (data.cmBackToSRGB ? data.cmBackToSRGBSource->m_imageDescription : SImageDescription{}); + const bool isHDRSurface = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? m_renderData.surface->m_colorManagement->isHDR() : false; + const bool canPassHDRSurface = isHDRSurface && !m_renderData.surface->m_colorManagement->isWindowsScRGB(); // windows scRGB requires CM shader + + auto imageDescription = m_renderData.surface.valid() && m_renderData.surface->m_colorManagement.valid() ? + m_renderData.surface->m_colorManagement->imageDescription() : + (data.cmBackToSRGB ? data.cmBackToSRGBSource->m_imageDescription : SImageDescription{}); const bool skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ + || m_renderData.pMonitor->doesNoShaderCM() /* no shader needed */ || (imageDescription == m_renderData.pMonitor->m_imageDescription && !data.cmBackToSRGB) /* Source and target have the same image description */ - || ((*PPASS == 1 || (*PPASS == 2 && canPassHDRSurface)) && m_renderData.pMonitor->m_activeWorkspace && m_renderData.pMonitor->m_activeWorkspace->m_hasFullscreenWindow && - m_renderData.pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) /* Fullscreen window with pass cm enabled */; + || (((*PPASS && canPassHDRSurface) || (*PPASS == 1 && !isHDRSurface)) && m_renderData.pMonitor->inFullscreenMode()) /* Fullscreen window with pass cm enabled */; if (!skipCM && !usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) shader = &m_shaders->m_shCM; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 779a15328..7efaed0e5 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1485,43 +1485,50 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { static auto PPASS = CConfigValue("render:cm_fs_passthrough"); static auto PAUTOHDR = CConfigValue("render:cm_auto_hdr"); + static bool needsHDRupdate = false; + const bool configuredHDR = (pMonitor->m_cmType == CM_HDR_EDID || pMonitor->m_cmType == CM_HDR); bool wantHDR = configuredHDR; + const auto FS_WINDOW = pMonitor->inFullscreenMode() ? pMonitor->m_activeWorkspace->getFullscreenWindow() : nullptr; + if (pMonitor->supportsHDR()) { // HDR metadata determined by + // HDR scRGB - monitor settings + // HDR PQ surface & DS is active - surface settings // PPASS = 0 monitor settings // PPASS = 1 // windowed: monitor settings - // fullscreen surface: surface settings FIXME: fullscreen SDR surface passthrough - pass degamma, ctm, gamma if needed + // fullscreen surface: surface settings FIXME: fullscreen SDR surface passthrough - pass degamma, gamma if needed // PPASS = 2 // windowed: monitor settings // fullscreen SDR surface: monitor settings // fullscreen HDR surface: surface settings bool hdrIsHandled = false; - if (pMonitor->m_activeWorkspace && pMonitor->m_activeWorkspace->m_hasFullscreenWindow && pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) { - const auto WINDOW = pMonitor->m_activeWorkspace->getFullscreenWindow(); - const auto ROOT_SURF = WINDOW->m_wlSurface->resource(); - const auto SURF = - ROOT_SURF->findFirstPreorder([ROOT_SURF](SP surf) { return surf->m_colorManagement.valid() && surf->extends() == ROOT_SURF->extends(); }); + if (FS_WINDOW) { + const auto ROOT_SURF = FS_WINDOW->m_wlSurface->resource(); + const auto SURF = ROOT_SURF->findWithCM(); // we have a surface with image description if (SURF && SURF->m_colorManagement.valid() && SURF->m_colorManagement->hasImageDescription()) { const bool surfaceIsHDR = SURF->m_colorManagement->isHDR(); - if (*PPASS == 1 || (*PPASS == 2 && surfaceIsHDR)) { + if (!SURF->m_colorManagement->isWindowsScRGB() && (*PPASS == 1 || ((*PPASS == 2 || !pMonitor->m_lastScanout.expired()) && surfaceIsHDR))) { // passthrough - bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != WINDOW; - if (SURF->m_colorManagement->needsHdrMetadataUpdate()) + bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != FS_WINDOW || needsHDRupdate; + if (SURF->m_colorManagement->needsHdrMetadataUpdate()) { + Debug::log(INFO, "[CM] Recreating HDR metadata for surface"); SURF->m_colorManagement->setHDRMetadata(createHDRMetadata(SURF->m_colorManagement->imageDescription(), pMonitor)); - if (needsHdrMetadataUpdate) + } + if (needsHdrMetadataUpdate) { + Debug::log(INFO, "[CM] Updating HDR metadata from surface"); pMonitor->m_output->state->setHDRMetadata(SURF->m_colorManagement->hdrMetadata()); - hdrIsHandled = true; + } + hdrIsHandled = true; + needsHDRupdate = false; } else if (*PAUTOHDR && surfaceIsHDR) wantHDR = true; // auto-hdr: hdr on } - - pMonitor->m_previousFSWindow = WINDOW; } if (!hdrIsHandled) { @@ -1529,11 +1536,15 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { if (*PAUTOHDR && !(pMonitor->inHDR() && configuredHDR)) { // modify or restore monitor image description for auto-hdr // FIXME ok for now, will need some other logic if monitor image description can be modified some other way - pMonitor->applyCMType(wantHDR ? (*PAUTOHDR == 2 ? CM_HDR_EDID : CM_HDR) : pMonitor->m_cmType); + const auto targetCM = wantHDR ? (*PAUTOHDR == 2 ? CM_HDR_EDID : CM_HDR) : pMonitor->m_cmType; + Debug::log(INFO, "[CM] Auto HDR: changing monitor cm to {}", sc(targetCM)); + pMonitor->applyCMType(targetCM); + pMonitor->m_previousFSWindow.reset(); // trigger CTM update } + Debug::log(INFO, wantHDR ? "[CM] Updating HDR metadata from monitor" : "[CM] Restoring SDR mode"); pMonitor->m_output->state->setHDRMetadata(wantHDR ? createHDRMetadata(pMonitor->m_imageDescription, pMonitor) : NO_HDR_METADATA); } - pMonitor->m_previousFSWindow.reset(); + needsHDRupdate = true; } } @@ -1553,19 +1564,39 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { } } - if (*PCT) { - if (pMonitor->m_activeWorkspace && pMonitor->m_activeWorkspace->m_hasFullscreenWindow && pMonitor->m_activeWorkspace->m_fullscreenMode == FSMODE_FULLSCREEN) { - const auto WINDOW = pMonitor->m_activeWorkspace->getFullscreenWindow(); - pMonitor->m_output->state->setContentType(NContentType::toDRM(WINDOW->getContentType())); - } else - pMonitor->m_output->state->setContentType(NContentType::toDRM(CONTENT_TYPE_NONE)); + if (*PCT) + pMonitor->m_output->state->setContentType(NContentType::toDRM(FS_WINDOW ? FS_WINDOW->getContentType() : CONTENT_TYPE_NONE)); + + if (FS_WINDOW != pMonitor->m_previousFSWindow) { + if (!FS_WINDOW || !pMonitor->needsCM() || !pMonitor->canNoShaderCM()) { + if (pMonitor->m_noShaderCTM) { + Debug::log(INFO, "[CM] No fullscreen CTM, restoring previous one"); + pMonitor->m_noShaderCTM = false; + pMonitor->m_ctmUpdated = true; + } + } else { + const auto FS_DESC = pMonitor->getFSImageDescription(); + if (FS_DESC.has_value()) { + Debug::log(INFO, "[CM] Updating fullscreen CTM"); + pMonitor->m_noShaderCTM = true; + const auto mat = FS_DESC->getPrimaries().convertMatrix(pMonitor->m_imageDescription.getPrimaries()).mat(); + const std::array CTM = { + mat[0][0], mat[0][1], mat[0][2], // + mat[1][0], mat[1][1], mat[1][2], // + mat[2][0], mat[2][1], mat[2][2], // + }; + pMonitor->m_output->state->setCTM(CTM); + } + } } - if (pMonitor->m_ctmUpdated) { + if (pMonitor->m_ctmUpdated && !pMonitor->m_noShaderCTM) { pMonitor->m_ctmUpdated = false; pMonitor->m_output->state->setCTM(pMonitor->m_ctm); } + pMonitor->m_previousFSWindow = FS_WINDOW; + bool ok = pMonitor->m_state.commit(); if (!ok) { if (pMonitor->m_inFence.isValid()) {