diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 10897cad..c24ad103 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -42,6 +42,7 @@ using namespace Hyprutils::String; using namespace Hyprutils::Utils; using namespace Hyprutils::OS; using enum NContentType::eContentType; +using namespace NColorManagement; CMonitor::CMonitor(SP output_) : m_state(this), m_output(output_) { g_pAnimationManager->createAnimation(0.f, m_specialFade, g_pConfigManager->getAnimationPropertyConfig("specialWorkspaceIn"), AVARDAMAGE_NONE); @@ -1686,6 +1687,18 @@ int CMonitor::maxAvgLuminance(int defaultValue) { (m_output->parsedEDID.hdrMetadata.has_value() ? m_output->parsedEDID.hdrMetadata->desiredMaxFrameAverageLuminance : defaultValue); } +bool CMonitor::wantsWideColor() { + return supportsWideColor() && (wantsHDR() || m_imageDescription.primariesNamed == CM_PRIMARIES_BT2020); +} + +bool CMonitor::wantsHDR() { + return supportsHDR() && inHDR(); +} + +bool CMonitor::inHDR() { + return m_output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2; +} + CMonitorState::CMonitorState(CMonitor* owner) : m_owner(owner) { ; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 09c6c49d..7614264f 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -249,6 +249,11 @@ class CMonitor { int maxLuminance(int defaultValue = 80); int maxAvgLuminance(int defaultValue = 80); + bool wantsWideColor(); + bool wantsHDR(); + + bool inHDR(); + bool m_enabled = false; bool m_renderingInitPassed = false; WP m_previousFSWindow; diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index 349a0b07..d126fea2 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -15,13 +15,13 @@ CColorManager::CColorManager(SP resource) : m_resource(resour m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_PARAMETRIC); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_PRIMARIES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_LUMINANCES); + m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); if (PROTO::colorManagement->m_debug) { m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_ICC_V2_V4); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_TF_POWER); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_SET_MASTERING_DISPLAY_PRIMARIES); m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_EXTENDED_TARGET_VOLUME); - m_resource->sendSupportedFeature(WP_COLOR_MANAGER_V1_FEATURE_WINDOWS_SCRGB); } m_resource->sendSupportedPrimariesNamed(WP_COLOR_MANAGER_V1_PRIMARIES_SRGB); @@ -170,11 +170,7 @@ CColorManager::CColorManager(SP resource) : m_resource(resour RESOURCE->m_self = RESOURCE; }); m_resource->setCreateWindowsScrgb([](CWpColorManagerV1* r, uint32_t id) { - LOGM(WARN, "New Windows scRGB description id={} (unsupported)", id); - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Windows scRGB profiles are not supported"); - return; - } + LOGM(WARN, "New Windows scRGB description id={}", id); const auto RESOURCE = PROTO::colorManagement->m_imageDescriptions.emplace_back( makeShared(makeShared(r->client(), r->version(), id), false)); @@ -261,11 +257,11 @@ CColorManagementSurface::CColorManagementSurface(SP m_client = m_resource->client(); m_resource->setDestroy([this](CWpColorManagementSurfaceV1* r) { - LOGM(TRACE, "Destroy xx cm surface {}", (uintptr_t)m_surface); + LOGM(TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); m_resource->setOnDestroy([this](CWpColorManagementSurfaceV1* r) { - LOGM(TRACE, "Destroy xx cm surface {}", (uintptr_t)m_surface); + LOGM(TRACE, "Destroy wp cm surface {}", (uintptr_t)m_surface); PROTO::colorManagement->destroyResource(this); }); @@ -340,6 +336,16 @@ bool CColorManagementSurface::needsHdrMetadataUpdate() { return m_needsNewMetadata; } +bool CColorManagementSurface::isHDR() { + return m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_HLG || isWindowsScRGB(); +} + +bool CColorManagementSurface::isWindowsScRGB() { + return m_imageDescription.windowsScRGB || + // autodetect scRGB, might be incorrect + (m_imageDescription.primariesNamed == CM_PRIMARIES_SRGB && m_imageDescription.transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR); +} + CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SP resource, SP surface_) : m_surface(surface_), m_resource(resource) { if UNLIKELY (!good()) @@ -348,13 +354,13 @@ CColorManagementFeedbackSurface::CColorManagementFeedbackSurface(SPclient(); m_resource->setDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) { - LOGM(TRACE, "Destroy xx cm feedback surface {}", (uintptr_t)m_surface); + LOGM(TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); if (m_currentPreferred.valid()) PROTO::colorManagement->destroyResource(m_currentPreferred.get()); PROTO::colorManagement->destroyResource(this); }); m_resource->setOnDestroy([this](CWpColorManagementSurfaceFeedbackV1* r) { - LOGM(TRACE, "Destroy xx cm feedback surface {}", (uintptr_t)m_surface); + LOGM(TRACE, "Destroy wp cm feedback surface {}", (uintptr_t)m_surface); if (m_currentPreferred.valid()) PROTO::colorManagement->destroyResource(m_currentPreferred.get()); PROTO::colorManagement->destroyResource(this); @@ -616,10 +622,6 @@ CColorManagementParametricCreator::CColorManagementParametricCreator(SPerror(WP_IMAGE_DESCRIPTION_CREATOR_PARAMS_V1_ERROR_ALREADY_SET, "Primaries already set"); return; } - if (!PROTO::colorManagement->m_debug) { - r->error(WP_COLOR_MANAGER_V1_ERROR_UNSUPPORTED_FEATURE, "Custom primaries aren't supported"); - return; - } m_settings.primariesNameSet = false; m_settings.primaries = SPCPRimaries{.red = {.x = r_x / 1000000.0f, .y = r_y / 1000000.0f}, .green = {.x = g_x / 1000000.0f, .y = g_y / 1000000.0f}, diff --git a/src/protocols/ColorManagement.hpp b/src/protocols/ColorManagement.hpp index 06349927..4a085b16 100644 --- a/src/protocols/ColorManagement.hpp +++ b/src/protocols/ColorManagement.hpp @@ -61,6 +61,8 @@ class CColorManagementSurface { const hdr_output_metadata& hdrMetadata(); void setHDRMetadata(const hdr_output_metadata& metadata); bool needsHdrMetadataUpdate(); + bool isHDR(); + bool isWindowsScRGB(); private: SP m_resource; diff --git a/src/protocols/types/ColorManagement.hpp b/src/protocols/types/ColorManagement.hpp index 68726a56..b7cfd37a 100644 --- a/src/protocols/types/ColorManagement.hpp +++ b/src/protocols/types/ColorManagement.hpp @@ -203,6 +203,8 @@ namespace NColorManagement { float getTFMaxLuminance(int sdrMaxLuminance = -1) const { switch (transferFunction) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: + return SDR_MAX_LUMINANCE; // assume Windows scRGB. white color range 1.0 - 125.0 maps to SDR_MAX_LUMINANCE (80) - HDR_MAX_LUMINANCE (10000) case CM_TRANSFER_FUNCTION_ST2084_PQ: return HDR_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_HLG: return HLG_MAX_LUMINANCE; case CM_TRANSFER_FUNCTION_GAMMA22: diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index e3c90989..27abe81f 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1470,7 +1470,13 @@ void CHyprOpenGLImpl::renderTexture(SP tex, const CBox& box, STextureR static std::map, std::array> primariesConversionCache; -// +static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) { + // might be too strict + return imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB && + (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ || + targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG); +} + void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) { shader.setUniformInt(SHADER_SOURCE_TF, imageDescription.transferFunction); @@ -1486,22 +1492,19 @@ void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SI }; shader.setUniformMatrix4x2fv(SHADER_TARGET_PRIMARIES, 1, false, glTargetPrimaries); - shader.setUniformFloat2(SHADER_SRC_TF_RANGE, imageDescription.getTFMinLuminance(sdrMinLuminance), imageDescription.getTFMaxLuminance(sdrMaxLuminance)); - shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription.getTFMinLuminance(sdrMinLuminance), targetImageDescription.getTFMaxLuminance(sdrMaxLuminance)); + const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription, targetImageDescription); + + shader.setUniformFloat2(SHADER_SRC_TF_RANGE, imageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + imageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); + shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1), + targetImageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1)); const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference; shader.setUniformFloat(SHADER_MAX_LUMINANCE, maxLuminance * targetImageDescription.luminances.reference / imageDescription.luminances.reference); shader.setUniformFloat(SHADER_DST_MAX_LUMINANCE, targetImageDescription.luminances.max > 0 ? targetImageDescription.luminances.max : 10000); shader.setUniformFloat(SHADER_DST_REF_LUMINANCE, targetImageDescription.luminances.reference); - shader.setUniformFloat(SHADER_SDR_SATURATION, - modifySDR && m_renderData.pMonitor->m_sdrSaturation > 0 && targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrSaturation : - 1.0f); - shader.setUniformFloat(SHADER_SDR_BRIGHTNESS, - modifySDR && m_renderData.pMonitor->m_sdrBrightness > 0 && targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ? - m_renderData.pMonitor->m_sdrBrightness : - - 1.0f); + shader.setUniformFloat(SHADER_SDR_SATURATION, needsSDRmod && m_renderData.pMonitor->m_sdrSaturation > 0 ? m_renderData.pMonitor->m_sdrSaturation : 1.0f); + shader.setUniformFloat(SHADER_SDR_BRIGHTNESS, needsSDRmod && m_renderData.pMonitor->m_sdrBrightness > 0 ? m_renderData.pMonitor->m_sdrBrightness : 1.0f); const auto cacheKey = std::make_pair(imageDescription.getId(), targetImageDescription.getId()); if (!primariesConversionCache.contains(cacheKey)) { const auto mat = imageDescription.getPrimaries().convertMatrix(targetImageDescription.getPrimaries()).mat(); @@ -1594,14 +1597,16 @@ void CHyprOpenGLImpl::renderTextureInternal(SP tex, const CBox& box, c tex->setTexParameter(GL_TEXTURE_MIN_FILTER, GL_LINEAR); } - 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 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 skipCM = !*PENABLECM || !m_cmSupported /* CM unsupported or disabled */ || (imageDescription == m_renderData.pMonitor->m_imageDescription && !data.cmBackToSRGB) /* Source and target have the same image description */ - || ((*PPASS == 1 || (*PPASS == 2 && imageDescription.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ)) && m_renderData.pMonitor->m_activeWorkspace && - m_renderData.pMonitor->m_activeWorkspace->m_hasFullscreenWindow && + || ((*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 */; if (!skipCM && !usingFinalShader && (texType == TEXTURE_RGBA || texType == TEXTURE_RGBX)) diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index b277086c..40d9d9e5 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1460,6 +1460,9 @@ static hdr_output_metadata createHDRMetadata(SImageDescription settings, S switch (settings.transferFunction) { case CM_TRANSFER_FUNCTION_SRGB: eotf = 0; break; // used to send primaries and luminances to AQ. ignored for now case CM_TRANSFER_FUNCTION_ST2084_PQ: eotf = 2; break; + case CM_TRANSFER_FUNCTION_EXT_LINEAR: + eotf = 2; + break; // should be Windows scRGB // case CM_TRANSFER_FUNCTION_HLG: eotf = 3; break; TODO check display capabilities first default: return NO_HDR_METADATA; // empty metadata for SDR } @@ -1501,7 +1504,6 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { static auto PAUTOHDR = CConfigValue("render:cm_auto_hdr"); const bool configuredHDR = (pMonitor->m_cmType == CM_HDR_EDID || pMonitor->m_cmType == CM_HDR); - const bool hdsIsActive = pMonitor->m_output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2; bool wantHDR = configuredHDR; if (pMonitor->supportsHDR()) { @@ -1524,7 +1526,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { // we have a surface with image description if (SURF && SURF->m_colorManagement.valid() && SURF->m_colorManagement->hasImageDescription()) { - const bool surfaceIsHDR = SURF->m_colorManagement->imageDescription().transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ; + const bool surfaceIsHDR = SURF->m_colorManagement->isHDR(); if (*PPASS == 1 || (*PPASS == 2 && surfaceIsHDR)) { // passthrough bool needsHdrMetadataUpdate = SURF->m_colorManagement->needsHdrMetadataUpdate() || pMonitor->m_previousFSWindow != WINDOW; @@ -1541,8 +1543,8 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { } if (!hdrIsHandled) { - if (hdsIsActive != wantHDR) { - if (*PAUTOHDR && !(hdsIsActive && configuredHDR)) { + if (pMonitor->inHDR() != wantHDR) { + 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); @@ -1553,7 +1555,7 @@ bool CHyprRenderer::commitPendingAndDoExplicitSync(PHLMONITOR pMonitor) { } } - const bool needsWCG = pMonitor->m_output->state->state().hdrMetadata.hdmi_metadata_type1.eotf == 2 || pMonitor->m_imageDescription.primariesNamed == CM_PRIMARIES_BT2020; + const bool needsWCG = pMonitor->wantsWideColor(); if (pMonitor->m_output->state->state().wideColorGamut != needsWCG) { Debug::log(TRACE, "Setting wide color gamut {}", needsWCG ? "on" : "off"); pMonitor->m_output->state->setWideColorGamut(needsWCG); diff --git a/src/render/pass/SurfacePassElement.cpp b/src/render/pass/SurfacePassElement.cpp index ea720ab1..7dd423bd 100644 --- a/src/render/pass/SurfacePassElement.cpp +++ b/src/render/pass/SurfacePassElement.cpp @@ -77,8 +77,6 @@ void CSurfacePassElement::draw(const CRegion& damage) { DELTALESSTHAN(windowBox.height, m_data.surface->m_current.bufferSize.y, 3) /* off by one-or-two */ && (!m_data.pWindow || (!m_data.pWindow->m_realSize->isBeingAnimated() && !INTERACTIVERESIZEINPROGRESS)) /* not window or not animated/resizing */; - if (m_data.surface->m_colorManagement.valid()) - Debug::log(TRACE, "FIXME: rendering surface with color management enabled, should apply necessary transformations"); g_pHyprRenderer->calculateUVForSurface(m_data.pWindow, m_data.surface, m_data.pMonitor->m_self.lock(), m_data.mainSurface, windowBox.size(), PROJSIZEUNSCALED, MISALIGNEDFSV1); auto cancelRender = false;