From f4ce197905cc04b747fd154e9681e2e99bbac5d6 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Mon, 13 Apr 2026 20:01:34 +0300 Subject: [PATCH 01/19] fix fp16 blur with invalidation --- src/render/OpenGL.cpp | 2 ++ src/render/gl/GLFramebuffer.cpp | 11 +++++++++++ src/render/gl/GLFramebuffer.hpp | 6 +++++- src/render/shaders/glsl/blur1.glsl | 8 ++++---- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 6bb04cfae..97e3fd7af 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1660,6 +1660,7 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or static auto PBLEND = CConfigValue("render:use_shader_blur_blend"); PMIRRORSWAPFB->bind(); + GLFB(PMIRRORSWAPFB)->clearAfterInvalidation(); glActiveTexture(GL_TEXTURE0); @@ -1754,6 +1755,7 @@ SP CHyprOpenGLImpl::blurFramebufferWithDamage(float a, CRegion* or // draw the things. // first draw is swap -> mirr PMIRRORFB->bind(); + GLFB(PMIRRORFB)->clearAfterInvalidation(); PMIRRORSWAPFB->getTexture()->bind(); // damage region will be scaled, make a temp diff --git a/src/render/gl/GLFramebuffer.cpp b/src/render/gl/GLFramebuffer.cpp index d8b198c3e..196f33275 100644 --- a/src/render/gl/GLFramebuffer.cpp +++ b/src/render/gl/GLFramebuffer.cpp @@ -171,4 +171,15 @@ void CGLFramebuffer::invalidate(const std::vector& attachments) { return; glInvalidateFramebuffer(GL_FRAMEBUFFER, attachments.size(), attachments.data()); + m_cleared = false; +} + +void CGLFramebuffer::clearAfterInvalidation() { + if (m_cleared) + return; + + m_cleared = true; + glClearColor(0, 0, 0, 0); + g_pHyprOpenGL->scissor(nullptr); + glClear(GL_COLOR_BUFFER_BIT); } diff --git a/src/render/gl/GLFramebuffer.hpp b/src/render/gl/GLFramebuffer.hpp index 1be5d0e02..34051656f 100644 --- a/src/render/gl/GLFramebuffer.hpp +++ b/src/render/gl/GLFramebuffer.hpp @@ -21,11 +21,15 @@ namespace Render::GL { GLuint getFBID(); void invalidate(const std::vector& attachments); + // clear at most once per invalidate() + void clearAfterInvalidation(); + protected: bool internalAlloc(int w, int h, DRMFormat format = DRM_FORMAT_ARGB8888) override; private: - GLuint m_fb = -1; + GLuint m_fb = -1; + bool m_cleared = false; friend class CGLRenderbuffer; }; diff --git a/src/render/shaders/glsl/blur1.glsl b/src/render/shaders/glsl/blur1.glsl index 86a37d88a..8d9e39aec 100644 --- a/src/render/shaders/glsl/blur1.glsl +++ b/src/render/shaders/glsl/blur1.glsl @@ -104,10 +104,10 @@ vec4 blur1(vec2 v_texcoord, sampler2D tex, float radius, vec2 halfpixel, int pas // That garbage maps to 0.0-1.0 range with UINT8 buffer and doesn't have any significant impact on the end result. // FP16 garbage maps to -65,504 - 65,504 and defines the end result. Clamp it here to 0.0 - 1.0 to get the same quality outcome as with UINT8. // Rerendering an undamaged area to get some insignificant color accuracy increase on blur edges isn't worth it. - sum += clamp(texture(tex, uv - halfpixel.xy * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv + halfpixel.xy * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); - sum += clamp(texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius), 0.0, 1.0); + sum += texture(tex, uv - halfpixel.xy * radius); + sum += texture(tex, uv + halfpixel.xy * radius); + sum += texture(tex, uv + vec2(halfpixel.x, -halfpixel.y) * radius); + sum += texture(tex, uv - vec2(halfpixel.x, -halfpixel.y) * radius); vec4 color = sum / 8.0; From 173c13636e21b708550657723c0325ad6b5bd635 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Mon, 13 Apr 2026 20:02:02 +0300 Subject: [PATCH 02/19] restore tonemap code --- src/render/shaders/glsl/tonemap.glsl | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/render/shaders/glsl/tonemap.glsl b/src/render/shaders/glsl/tonemap.glsl index dcce45a67..a2db7e6e1 100644 --- a/src/render/shaders/glsl/tonemap.glsl +++ b/src/render/shaders/glsl/tonemap.glsl @@ -50,21 +50,16 @@ vec4 tonemap(vec4 color, mat3 dstXYZ, float maxLuminance, float dstMaxLuminance, float E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_INV_M2); float luminance = pow((max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1) * HDR_MAX_LUMINANCE; - float linearPart = min(luminance, dstRefLuminance); - float luminanceAboveRef = max(luminance - dstRefLuminance, 0.0); - float maxExcessLuminance = max(maxLuminance - dstRefLuminance, 1.0); - float shoulder = log((luminanceAboveRef / maxExcessLuminance + 1.0) * (M_E - 1.0)); - float mappedHigh = shoulder * (dstMaxLuminance - dstRefLuminance); - float newLum = clamp(linearPart + mappedHigh, 0.0, dstMaxLuminance); + float luminanceRatio = max(luminance / dstRefLuminance, 0.0); + float srcScale = maxLuminance / dstRefLuminance; + float dstScale = dstMaxLuminance / dstRefLuminance; + float v = (dstScale * (1.0 + srcScale) - srcScale) / pow(srcScale, 2.0); + float newLuminance = (luminanceRatio * (1.0 + luminanceRatio * v) / (1.0 + luminanceRatio)) * dstRefLuminance; - // scale src to dst reference - float refScale = dstRefLuminance / srcRefLuminance; + E = pow(clamp(newLuminance / HDR_MAX_LUMINANCE, 0.0, 1.0), PQ_M1); + ICtCp[0] = pow((PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), PQ_M2); - // kind of works but doesn't use newLum at all - return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]); - // breaks with overriden monitor luminances. might be caused by incorrect imput values - // @gulafaran - // vec3 outRGB = fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb; - // outRGB *= (newLum / max(luminance, 0.0001)); // actually apply the tone mapping - // return vec4(clamp(outRGB * HDR_MAX_LUMINANCE * refScale, 0.0, dstMaxLuminance), color[3]); + color = vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]); + + return clamp(color, 0.0, dstMaxLuminance); } From afeb437606600b2ca8f0645ecc974a518a86f07a Mon Sep 17 00:00:00 2001 From: Vaxry Date: Fri, 24 Apr 2026 16:25:47 +0100 Subject: [PATCH 03/19] fix fade outs --- src/render/Renderer.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index b5a32a2b9..f0840802f 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3002,6 +3002,7 @@ void IHyprRenderer::makeSnapshot(PHLWINDOW pWindow) { const auto PFRAMEBUFFER = ref->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); @@ -3043,6 +3044,7 @@ void IHyprRenderer::makeSnapshot(PHLLS pLayer) { const auto PFRAMEBUFFER = pLayer->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); @@ -3085,6 +3087,7 @@ void IHyprRenderer::makeSnapshot(WP popup) { const auto PFRAMEBUFFER = popup->m_snapshotFB; PFRAMEBUFFER->alloc(PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y, DRM_FORMAT_ABGR8888); + PFRAMEBUFFER->setImageDescription(workBufferImageDescription()); beginFullFakeRender(PMONITOR, fakeDamage, PFRAMEBUFFER); From 158f58098310e6c4b4c664070326259451098faa Mon Sep 17 00:00:00 2001 From: Vaxry Date: Fri, 24 Apr 2026 17:30:50 +0100 Subject: [PATCH 04/19] add sdr compat for unaware surfaces --- src/render/OpenGL.cpp | 8 ++++++++ src/render/Renderer.cpp | 3 +++ src/render/ShaderLoader.cpp | 13 ++++++++++--- src/render/ShaderLoader.hpp | 21 +++++++++++---------- src/render/shaders/glsl/cm_helpers.glsl | 12 +++++++++++- src/render/shaders/glsl/defines.h | 21 +++++++++++---------- 6 files changed, 54 insertions(+), 24 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 97e3fd7af..64aa4eb03 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1349,6 +1349,14 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture || !SOURCE_IMAGE_DESCRIPTION->needsCM(TARGET_IMAGE_DESCRIPTION) /* Source and target have matching image descriptions */ ; + const auto sourceTF = SOURCE_IMAGE_DESCRIPTION->value().transferFunction; + const bool sourceIsUnmanagedSDRSurface = surface.valid() && !surface->m_colorManagement.valid() && + (sourceTF == CM_TRANSFER_FUNCTION_SRGB || sourceTF == CM_TRANSFER_FUNCTION_GAMMA22 || sourceTF == CM_TRANSFER_FUNCTION_EXT_SRGB || sourceTF == CM_TRANSFER_FUNCTION_BT1886); + const bool targetIsLinearWorkBuffer = TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR; + + if (!skipCM && !data.finalMonitorCM && !data.cmBackToSRGB && sourceIsUnmanagedSDRSurface && targetIsLinearWorkBuffer) + shaderFeatures |= SH_FEAT_SDR_PREMUL_COMPAT; + if (g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB()) Log::logger->log(Log::TRACE, "CM: render to FB skip={} {} -> {}", skipCM, SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index f0840802f..4bd4ed6db 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3275,6 +3275,9 @@ NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() // const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC); // const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; + if (!m_renderData.pMonitor) + return LINEAR_IMAGE_DESCRIPTION; + return m_renderData.pMonitor->useFP16() ? LINEAR_IMAGE_DESCRIPTION : m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp index 50ab61d04..8f711abde 100644 --- a/src/render/ShaderLoader.cpp +++ b/src/render/ShaderLoader.cpp @@ -67,10 +67,17 @@ void CShaderLoader::include(const std::string& filename) { std::string CShaderLoader::getDefines(ShaderFeatureFlags features) { std::string res = ""; std::map defines = { - {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, - {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, - {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, + {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, + {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, + {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, + {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, + {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, + {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, + {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, + {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, + {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, {"USE_MIRROR", features & SH_FEAT_MIRROR ? "1" : "0"}, + {"USE_SDR_PREMUL_COMPAT", features & SH_FEAT_SDR_PREMUL_COMPAT ? "1" : "0"}, }; for (const auto& [name, value] : defines) { res += std::format("#define {} {}\n", name, value); diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp index 58184659c..faf053ba2 100644 --- a/src/render/ShaderLoader.hpp +++ b/src/render/ShaderLoader.hpp @@ -11,16 +11,17 @@ namespace Render { enum ePreparedFragmentShaderFeature : uint16_t { SH_FEAT_UNKNOWN = 0, // all features just in case - SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling - SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling - SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint - SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 - SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM - SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 - SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) - SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend - SH_FEAT_ICC = (1 << 8), // - SH_FEAT_MIRROR = (1 << 9), // condition: mirror or screenshare + SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling + SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling + SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint + SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 + SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM + SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 + SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) + SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend + SH_FEAT_ICC = (1 << 8), // + SH_FEAT_MIRROR = (1 << 9), // condition: mirror or screenshare + SH_FEAT_SDR_PREMUL_COMPAT = (1 << 10), // condition: unmanaged SDR surfaces drawn into linear workbuffer // uniforms: targetPrimariesXYZ; condition: SH_FEAT_TONEMAP || SH_FEAT_SDR_MOD }; diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl index 0d8c68e93..b00767dc8 100644 --- a/src/render/shaders/glsl/cm_helpers.glsl +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -224,15 +224,25 @@ vec4 #endif #endif ) { +#if USE_SDR_PREMUL_COMPAT + // Compatibility path for unmanaged SDR translucent surfaces rendered into a linear workbuffer. + // Keep premultiplication during source TF decoding to avoid low-alpha hue/opacity drift. + pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); +#else pixColor.rgb /= max(pixColor.a, 0.001); pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); +#endif #if USE_ICC pixColor.rgb = applyIcc3DLut(pixColor.rgb, iccLut3D, iccLutSize); pixColor.rgb *= pixColor.a; #else pixColor.rgb = convertMatrix * pixColor.rgb; +#if USE_SDR_PREMUL_COMPAT + pixColor.rgb = pixColor.rgb * (srcTFRange[1] - srcTFRange[0]) + srcTFRange[0] * pixColor.a; +#else pixColor = toNit(pixColor, srcTFRange); pixColor.rgb *= pixColor.a; +#endif #if USE_TONEMAP pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); #endif @@ -258,4 +268,4 @@ vec4 #endif } -#endif \ No newline at end of file +#endif diff --git a/src/render/shaders/glsl/defines.h b/src/render/shaders/glsl/defines.h index a04e0250b..d38eeffd9 100644 --- a/src/render/shaders/glsl/defines.h +++ b/src/render/shaders/glsl/defines.h @@ -2,13 +2,14 @@ // Values here are only for highlighting and static checking // 1 assumes that a shader either supports this feature or doesn't use any code supporting it // 0 assumes that shader uses some code supporting the feature but will never have this feature enabled -#define USE_RGBA 1 -#define USE_DISCARD 1 -#define USE_TINT 1 -#define USE_ROUNDING 1 -#define USE_CM 1 -#define USE_TONEMAP 1 -#define USE_SDR_MOD 1 -#define USE_BLUR 1 -#define USE_ICC 0 -#define USE_MIRROR 0 +#define USE_RGBA 1 +#define USE_DISCARD 1 +#define USE_TINT 1 +#define USE_ROUNDING 1 +#define USE_CM 1 +#define USE_TONEMAP 1 +#define USE_SDR_MOD 1 +#define USE_BLUR 1 +#define USE_ICC 0 +#define USE_MIRROR 0 +#define USE_SDR_PREMUL_COMPAT 1 From 19175706eb9d55b9b363341312c3cf8a3096458b Mon Sep 17 00:00:00 2001 From: Vaxry Date: Fri, 24 Apr 2026 19:48:46 +0100 Subject: [PATCH 05/19] use a better work space, not linear, only for sdr --- src/helpers/Monitor.cpp | 22 +++++++++++++++- src/helpers/Monitor.hpp | 9 ++++--- src/helpers/MonitorResources.cpp | 18 ++++++------- src/managers/screenshare/ScreenshareFrame.cpp | 9 +++---- src/render/OpenGL.cpp | 16 +++--------- src/render/OpenGL.hpp | 1 - src/render/Renderer.cpp | 9 +------ src/render/ShaderLoader.cpp | 13 +++------- src/render/ShaderLoader.hpp | 21 ++++++++-------- src/render/gl/GLElementRenderer.cpp | 25 +++++++++---------- src/render/pass/TexPassElement.hpp | 1 - src/render/shaders/glsl/cm_helpers.glsl | 10 -------- src/render/shaders/glsl/defines.h | 21 ++++++++-------- 13 files changed, 77 insertions(+), 98 deletions(-) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index a5195a94f..528cb84e2 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2549,9 +2549,29 @@ bool CMonitor::useFP16() { return shouldUse; } +PImageDescription CMonitor::workBufferImageDescription() { + if (!useFP16()) + return m_imageDescription; + + const auto& value = m_imageDescription->value(); + + const bool isHDRLikeTF = + value.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || value.transferFunction == CM_TRANSFER_FUNCTION_HLG || value.transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR; + + if (isHDRLikeTF || value.windowsScRGB) + return LINEAR_IMAGE_DESCRIPTION; + + return CImageDescription::from(SImageDescription{ + .transferFunction = chooseTF(m_sdrEotf), + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorPrimaries::BT2020, + }); +} + WP CMonitor::resources() { const auto DRM_FORMAT = useFP16() ? DRM_FORMAT_ABGR16161616F : m_output->state->state().drmFormat; - const auto DESC = useFP16() ? LINEAR_IMAGE_DESCRIPTION : m_imageDescription; + const auto DESC = workBufferImageDescription(); if (!m_resources || m_resources->m_drmFormat != DRM_FORMAT || m_resources->m_size != m_pixelSize) m_resources = makeUnique(m_self, DRM_FORMAT, m_pixelSize, DESC); diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 59a64237e..b4882fe66 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -393,10 +393,11 @@ class CMonitor { return m_position == rhs.m_position && m_size == rhs.m_size && m_name == rhs.m_name; } - bool needsACopyFB(); - bool needsUnmodifiedCopy(); - bool useFP16(); - WP resources(); + bool needsACopyFB(); + bool needsUnmodifiedCopy(); + bool useFP16(); + NColorManagement::PImageDescription workBufferImageDescription(); + WP resources(); private: void updateMatrix(); diff --git a/src/helpers/MonitorResources.cpp b/src/helpers/MonitorResources.cpp index 8e7723c23..f54aa9dc4 100644 --- a/src/helpers/MonitorResources.cpp +++ b/src/helpers/MonitorResources.cpp @@ -32,7 +32,7 @@ void CMonitorResources::setImageDescription(NColorManagement::PImageDescription for (const auto& res : m_workBuffers) res.buffer->setImageDescription(imageDescription); if (m_monitorMirrorFB) - m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + m_monitorMirrorFB->setImageDescription(getMirrorTexImageDescription()); if (m_mirrorTex) m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); } @@ -78,8 +78,8 @@ SP CMonitorResources::mirrorFB() { m_monitorMirrorFB = g_pHyprRenderer->createFB(std::format("Monitor {} mirror FB", m_monitor->m_name)); if (!m_monitorMirrorFB->isAllocated()) { - m_monitorMirrorFB->alloc(m_size.x, m_size.y, DRM_FORMAT_XRGB8888); - m_monitorMirrorFB->setImageDescription(NColorManagement::getDefaultImageDescription()); + m_monitorMirrorFB->alloc(m_size.x, m_size.y, m_monitor->m_activeMonitorRule.m_enable10bit ? DRM_FORMAT_XRGB2101010 : DRM_FORMAT_XRGB8888); + m_monitorMirrorFB->setImageDescription(getMirrorTexImageDescription()); } return m_monitorMirrorFB; @@ -90,20 +90,16 @@ SP CMonitorResources::getMirrorTexture() { } NColorManagement::PImageDescription CMonitorResources::getMirrorTexImageDescription() { - return CImageDescription::from(SImageDescription{ - .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_SRGB, - .primariesNameSet = m_imageDescription->value().primariesNameSet, - .primariesNamed = m_imageDescription->value().primariesNamed, - .primaries = m_imageDescription->value().primaries, - .luminances = {.min = SDR_MIN_LUMINANCE, .max = 80, .reference = 80}, - }); + if (!m_monitor) + return DEFAULT_SRGB_IMAGE_DESCRIPTION; + return m_monitor->workBufferImageDescription(); } void CMonitorResources::enableMirror() { if (m_mirrorTex) return; m_mirrorTex = g_pHyprRenderer->createTexture(); - m_mirrorTex->allocate({m_size.x, m_size.y}, DRM_FORMAT_XRGB8888); + m_mirrorTex->allocate({m_size.x, m_size.y}, m_monitor->m_activeMonitorRule.m_enable10bit ? DRM_FORMAT_XRGB2101010 : DRM_FORMAT_XRGB8888); m_mirrorTex->m_imageDescription = getMirrorTexImageDescription(); m_monitor->m_blurFBDirty = true; } diff --git a/src/managers/screenshare/ScreenshareFrame.cpp b/src/managers/screenshare/ScreenshareFrame.cpp index 7a596d0c6..4cc51335e 100644 --- a/src/managers/screenshare/ScreenshareFrame.cpp +++ b/src/managers/screenshare/ScreenshareFrame.cpp @@ -196,11 +196,10 @@ void CScreenshareFrame::renderMonitor() { g_pHyprRenderer->startRenderPass(); g_pHyprRenderer->draw( CTexPassElement::SRenderData{ - .tex = TEXTURE, - .box = monbox, - .flipEndFrame = true, - .cmBackToSRGB = !IS_CM_AWARE, - .cmBackToSRGBSource = !IS_CM_AWARE ? PMONITOR : nullptr, + .tex = TEXTURE, + .box = monbox, + .flipEndFrame = true, + .cmBackToSRGB = !IS_CM_AWARE, }, {0, 0, PMONITOR->m_pixelSize.x, PMONITOR->m_pixelSize.y}); g_pHyprRenderer->m_renderData.renderModif.enabled = OLD; diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 64aa4eb03..76f38ce04 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1298,7 +1298,7 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture shaderFeatures &= ~SH_FEAT_RGBA; const auto surface = g_pHyprRenderer->m_renderData.surface; - const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->workBufferImageDescription(); + const auto WORK_BUFFER_IMAGE_DESCRIPTION = g_pHyprRenderer->m_renderData.pMonitor->workBufferImageDescription(); // chosenSdrEotf contains the valid eotf for this display @@ -1311,7 +1311,7 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture return CImageDescription::from(surface->m_colorManagement->imageDescription()); if (data.cmBackToSRGB) - return g_pHyprRenderer->m_renderData.pMonitor->m_imageDescription; + return tex->m_imageDescription ? tex->m_imageDescription : WORK_BUFFER_IMAGE_DESCRIPTION; // otherwise, if we are CM'ing back into source, use chosen, because that's what our work buffer is in // the same applies to the final monitor CM @@ -1349,14 +1349,6 @@ WP CHyprOpenGLImpl::renderToFBInternal(SP tex, const STexture || !SOURCE_IMAGE_DESCRIPTION->needsCM(TARGET_IMAGE_DESCRIPTION) /* Source and target have matching image descriptions */ ; - const auto sourceTF = SOURCE_IMAGE_DESCRIPTION->value().transferFunction; - const bool sourceIsUnmanagedSDRSurface = surface.valid() && !surface->m_colorManagement.valid() && - (sourceTF == CM_TRANSFER_FUNCTION_SRGB || sourceTF == CM_TRANSFER_FUNCTION_GAMMA22 || sourceTF == CM_TRANSFER_FUNCTION_EXT_SRGB || sourceTF == CM_TRANSFER_FUNCTION_BT1886); - const bool targetIsLinearWorkBuffer = TARGET_IMAGE_DESCRIPTION->value().transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR; - - if (!skipCM && !data.finalMonitorCM && !data.cmBackToSRGB && sourceIsUnmanagedSDRSurface && targetIsLinearWorkBuffer) - shaderFeatures |= SH_FEAT_SDR_PREMUL_COMPAT; - if (g_pHyprRenderer->m_renderData.pMonitor->needsACopyFB()) Log::logger->log(Log::TRACE, "CM: render to FB skip={} {} -> {}", skipCM, SOURCE_IMAGE_DESCRIPTION->value(), TARGET_IMAGE_DESCRIPTION->value()); @@ -2395,7 +2387,8 @@ void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { Log::logger->log(Log::ERR, "Invalid source texture for mirror"); return; } - auto guard = g_pHyprRenderer->bindTempFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB()); + auto fb = g_pHyprRenderer->m_renderData.pMonitor->resources()->mirrorFB(); + auto guard = g_pHyprRenderer->bindTempFB(fb); Log::logger->log(Log::TRACE, "CM: saveBufferForMirror {} -> {}", TEX->m_imageDescription->value(), g_pHyprRenderer->m_renderData.currentFB->imageDescription()->value()); @@ -2408,7 +2401,6 @@ void CHyprOpenGLImpl::saveBufferForMirror(const CBox& box) { .round = 0, .discardActive = false, .allowCustomUV = false, - .cmBackToSRGB = true, }); blend(true); diff --git a/src/render/OpenGL.hpp b/src/render/OpenGL.hpp index 82b8f0548..2434c0edc 100644 --- a/src/render/OpenGL.hpp +++ b/src/render/OpenGL.hpp @@ -174,7 +174,6 @@ namespace Render::GL { GLenum wrapX = GL_CLAMP_TO_EDGE, wrapY = GL_CLAMP_TO_EDGE; bool cmBackToSRGB = false; bool finalMonitorCM = false; - SP cmBackToSRGBSource; uint32_t discardMode = DISCARD_OPAQUE; float discardOpacity = 0.f; diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 4bd4ed6db..20282315b 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -3270,17 +3270,10 @@ void IHyprRenderer::renderSnapshot(WP popup) { } NColorManagement::PImageDescription IHyprRenderer::workBufferImageDescription() { - // TODO - // const bool IS_MONITOR_ICC = m_renderData.pMonitor->m_imageDescription.valid() && m_renderData.pMonitor->m_imageDescription->value().icc.present; - // const auto sdrEOTF = NTransferFunction::fromConfig(IS_MONITOR_ICC); - // const auto CHOSEN_SDR_EOTF = sdrEOTF != NTransferFunction::TF_SRGB ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB; - if (!m_renderData.pMonitor) return LINEAR_IMAGE_DESCRIPTION; - return m_renderData.pMonitor->useFP16() ? - LINEAR_IMAGE_DESCRIPTION : - m_renderData.pMonitor->m_imageDescription; //CImageDescription::from(NColorManagement::SImageDescription{.transferFunction = CHOSEN_SDR_EOTF}); + return m_renderData.pMonitor->workBufferImageDescription(); } bool IHyprRenderer::shouldBlur(PHLLS ls) { diff --git a/src/render/ShaderLoader.cpp b/src/render/ShaderLoader.cpp index 8f711abde..50ab61d04 100644 --- a/src/render/ShaderLoader.cpp +++ b/src/render/ShaderLoader.cpp @@ -67,17 +67,10 @@ void CShaderLoader::include(const std::string& filename) { std::string CShaderLoader::getDefines(ShaderFeatureFlags features) { std::string res = ""; std::map defines = { - {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, - {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, - {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, - {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, - {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, - {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, - {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, - {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, - {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, + {"USE_RGBA", features & SH_FEAT_RGBA ? "1" : "0"}, {"USE_DISCARD", features & SH_FEAT_DISCARD ? "1" : "0"}, {"USE_TINT", features & SH_FEAT_TINT ? "1" : "0"}, + {"USE_ROUNDING", features & SH_FEAT_ROUNDING ? "1" : "0"}, {"USE_CM", features & SH_FEAT_CM ? "1" : "0"}, {"USE_TONEMAP", features & SH_FEAT_TONEMAP ? "1" : "0"}, + {"USE_SDR_MOD", features & SH_FEAT_SDR_MOD ? "1" : "0"}, {"USE_BLUR", features & SH_FEAT_BLUR ? "1" : "0"}, {"USE_ICC", features & SH_FEAT_ICC ? "1" : "0"}, {"USE_MIRROR", features & SH_FEAT_MIRROR ? "1" : "0"}, - {"USE_SDR_PREMUL_COMPAT", features & SH_FEAT_SDR_PREMUL_COMPAT ? "1" : "0"}, }; for (const auto& [name, value] : defines) { res += std::format("#define {} {}\n", name, value); diff --git a/src/render/ShaderLoader.hpp b/src/render/ShaderLoader.hpp index faf053ba2..58184659c 100644 --- a/src/render/ShaderLoader.hpp +++ b/src/render/ShaderLoader.hpp @@ -11,17 +11,16 @@ namespace Render { enum ePreparedFragmentShaderFeature : uint16_t { SH_FEAT_UNKNOWN = 0, // all features just in case - SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling - SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling - SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint - SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 - SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM - SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 - SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) - SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend - SH_FEAT_ICC = (1 << 8), // - SH_FEAT_MIRROR = (1 << 9), // condition: mirror or screenshare - SH_FEAT_SDR_PREMUL_COMPAT = (1 << 10), // condition: unmanaged SDR surfaces drawn into linear workbuffer + SH_FEAT_RGBA = (1 << 0), // RGBA/RGBX texture sampling + SH_FEAT_DISCARD = (1 << 1), // RGBA/RGBX texture sampling + SH_FEAT_TINT = (1 << 2), // uniforms: tint; condition: applyTint + SH_FEAT_ROUNDING = (1 << 3), // uniforms: radius, roundingPower, topLeft, fullSize; condition: radius > 0 + SH_FEAT_CM = (1 << 4), // uniforms: srcTFRange, dstTFRange, srcRefLuminance, convertMatrix; condition: !skipCM + SH_FEAT_TONEMAP = (1 << 5), // uniforms: maxLuminance, dstMaxLuminance, dstRefLuminance; condition: maxLuminance < dstMaxLuminance * 1.01 + SH_FEAT_SDR_MOD = (1 << 6), // uniforms: sdrSaturation, sdrBrightnessMultiplier; condition: SDR <-> HDR && (sdrSaturation != 1 || sdrBrightnessMultiplier != 1) + SH_FEAT_BLUR = (1 << 7), // condition: render:use_shader_blur_blend + SH_FEAT_ICC = (1 << 8), // + SH_FEAT_MIRROR = (1 << 9), // condition: mirror or screenshare // uniforms: targetPrimariesXYZ; condition: SH_FEAT_TONEMAP || SH_FEAT_SDR_MOD }; diff --git a/src/render/gl/GLElementRenderer.cpp b/src/render/gl/GLElementRenderer.cpp index dbf62cb8f..c20fffc08 100644 --- a/src/render/gl/GLElementRenderer.cpp +++ b/src/render/gl/GLElementRenderer.cpp @@ -114,19 +114,18 @@ void CGLElementRenderer::draw(WP element, const CRegion& damage .blurredBG = m_data.blurredBG, // common settings - .damage = m_data.damage.empty() ? &damage : &m_data.damage, - .surface = m_data.surface, - .a = m_data.a, - .round = m_data.round, - .roundingPower = m_data.roundingPower, - .discardActive = m_data.discardActive, - .allowCustomUV = m_data.allowCustomUV, - .cmBackToSRGB = m_data.cmBackToSRGB, - .cmBackToSRGBSource = m_data.cmBackToSRGBSource, - .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, - .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, - .clipRegion = m_data.clipRegion, - .currentLS = m_data.currentLS, + .damage = m_data.damage.empty() ? &damage : &m_data.damage, + .surface = m_data.surface, + .a = m_data.a, + .round = m_data.round, + .roundingPower = m_data.roundingPower, + .discardActive = m_data.discardActive, + .allowCustomUV = m_data.allowCustomUV, + .cmBackToSRGB = m_data.cmBackToSRGB, + .discardMode = m_data.ignoreAlpha.has_value() ? sc(DISCARD_ALPHA) : m_data.discardMode, + .discardOpacity = m_data.ignoreAlpha.has_value() ? *m_data.ignoreAlpha : m_data.discardOpacity, + .clipRegion = m_data.clipRegion, + .currentLS = m_data.currentLS, .primarySurfaceUVTopLeft = g_pHyprRenderer->m_renderData.primarySurfaceUVTopLeft, .primarySurfaceUVBottomRight = g_pHyprRenderer->m_renderData.primarySurfaceUVBottomRight, diff --git a/src/render/pass/TexPassElement.hpp b/src/render/pass/TexPassElement.hpp index 6c0cfab11..413f98753 100644 --- a/src/render/pass/TexPassElement.hpp +++ b/src/render/pass/TexPassElement.hpp @@ -31,7 +31,6 @@ class CTexPassElement : public IPassElement { std::optional ignoreAlpha; std::optional blockBlurOptimization; bool cmBackToSRGB = false; - SP cmBackToSRGBSource; bool discardActive = false; bool allowCustomUV = false; diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl index b00767dc8..0c7d3d510 100644 --- a/src/render/shaders/glsl/cm_helpers.glsl +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -224,25 +224,15 @@ vec4 #endif #endif ) { -#if USE_SDR_PREMUL_COMPAT - // Compatibility path for unmanaged SDR translucent surfaces rendered into a linear workbuffer. - // Keep premultiplication during source TF decoding to avoid low-alpha hue/opacity drift. - pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); -#else pixColor.rgb /= max(pixColor.a, 0.001); pixColor.rgb = toLinearRGB(pixColor.rgb, srcTF); -#endif #if USE_ICC pixColor.rgb = applyIcc3DLut(pixColor.rgb, iccLut3D, iccLutSize); pixColor.rgb *= pixColor.a; #else pixColor.rgb = convertMatrix * pixColor.rgb; -#if USE_SDR_PREMUL_COMPAT - pixColor.rgb = pixColor.rgb * (srcTFRange[1] - srcTFRange[0]) + srcTFRange[0] * pixColor.a; -#else pixColor = toNit(pixColor, srcTFRange); pixColor.rgb *= pixColor.a; -#endif #if USE_TONEMAP pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); #endif diff --git a/src/render/shaders/glsl/defines.h b/src/render/shaders/glsl/defines.h index d38eeffd9..a04e0250b 100644 --- a/src/render/shaders/glsl/defines.h +++ b/src/render/shaders/glsl/defines.h @@ -2,14 +2,13 @@ // Values here are only for highlighting and static checking // 1 assumes that a shader either supports this feature or doesn't use any code supporting it // 0 assumes that shader uses some code supporting the feature but will never have this feature enabled -#define USE_RGBA 1 -#define USE_DISCARD 1 -#define USE_TINT 1 -#define USE_ROUNDING 1 -#define USE_CM 1 -#define USE_TONEMAP 1 -#define USE_SDR_MOD 1 -#define USE_BLUR 1 -#define USE_ICC 0 -#define USE_MIRROR 0 -#define USE_SDR_PREMUL_COMPAT 1 +#define USE_RGBA 1 +#define USE_DISCARD 1 +#define USE_TINT 1 +#define USE_ROUNDING 1 +#define USE_CM 1 +#define USE_TONEMAP 1 +#define USE_SDR_MOD 1 +#define USE_BLUR 1 +#define USE_ICC 0 +#define USE_MIRROR 0 From 3f77bf25d10548bd6dd282e3c5b5733db7e374e1 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 09:54:15 +0300 Subject: [PATCH 06/19] fix hl tf -> wp tf --- src/helpers/cm/ColorManagement.hpp | 11 +++++++++-- src/protocols/ColorManagement.cpp | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/helpers/cm/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp index 183a8544d..e970ff592 100644 --- a/src/helpers/cm/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -5,6 +5,7 @@ #include #include "../../helpers/memory/Memory.hpp" #include "../../helpers/math/Math.hpp" +#include "../../debug/log/Logger.hpp" #include #include @@ -45,6 +46,7 @@ namespace NColorManagement { }; enum eTransferFunction : uint8_t { + CM_TRANSFER_FUNCTION_LINEAR = 0, CM_TRANSFER_FUNCTION_BT1886 = 1, CM_TRANSFER_FUNCTION_GAMMA22 = 2, CM_TRANSFER_FUNCTION_GAMMA28 = 3, @@ -68,9 +70,14 @@ namespace NColorManagement { inline ePrimaries convertPrimaries(wpColorManagerV1Primaries primaries) { return sc(primaries); } - inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf) { + inline wpColorManagerV1TransferFunction convertTransferFunction(eTransferFunction tf, bool useV1SRGB = true) { switch (tf) { - case CM_TRANSFER_FUNCTION_SRGB: return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; + case CM_TRANSFER_FUNCTION_LINEAR: + Log::logger->log(Log::TRACE, + "CM_TRANSFER_FUNCTION_LINEAR is internal and buffers with this TF shouldn't go outside. Returning " + "WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR for preferred description instead"); + return WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_EXT_LINEAR; + case CM_TRANSFER_FUNCTION_SRGB: return useV1SRGB ? WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_SRGB : WP_COLOR_MANAGER_V1_TRANSFER_FUNCTION_COMPOUND_POWER_2_4; default: return sc(tf); } } diff --git a/src/protocols/ColorManagement.cpp b/src/protocols/ColorManagement.cpp index fce91691b..a704d4d7a 100644 --- a/src/protocols/ColorManagement.cpp +++ b/src/protocols/ColorManagement.cpp @@ -807,7 +807,7 @@ CColorManagementImageDescriptionInfo::CColorManagementImageDescriptionInfo(SPsendPrimariesNamed(m_settings.primariesNamed); - m_resource->sendTfNamed(m_settings.transferFunction); + m_resource->sendTfNamed(convertTransferFunction(m_settings.transferFunction, m_resource->version() == 1)); if (m_settings.transferFunctionPower != 1.0f) m_resource->sendTfPower(std::round(m_settings.transferFunctionPower * 10000)); From e249b381bf4980c74426b7d6c18b2fc46f1cfefd Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 10:59:07 +0300 Subject: [PATCH 07/19] cache workbuffer descriptions --- src/helpers/Monitor.cpp | 26 ++++++++++++++++++-------- src/helpers/Monitor.hpp | 3 +++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 528cb84e2..af79a4e3b 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2558,15 +2558,25 @@ PImageDescription CMonitor::workBufferImageDescription() { const bool isHDRLikeTF = value.transferFunction == CM_TRANSFER_FUNCTION_ST2084_PQ || value.transferFunction == CM_TRANSFER_FUNCTION_HLG || value.transferFunction == CM_TRANSFER_FUNCTION_EXT_LINEAR; - if (isHDRLikeTF || value.windowsScRGB) - return LINEAR_IMAGE_DESCRIPTION; + const auto& cached = m_cachedInternalDescription->value(); - return CImageDescription::from(SImageDescription{ - .transferFunction = chooseTF(m_sdrEotf), - .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorPrimaries::BT2020, - }); + // HDR + if (isHDRLikeTF || value.windowsScRGB) { + if (cached.primariesNamed != NColorManagement::CM_PRIMARIES_SRGB || cached.luminances != value.luminances) + m_cachedInternalDescription = LINEAR_IMAGE_DESCRIPTION->with(value.luminances); + return m_cachedInternalDescription; + } + + // SDR + if (cached.primariesNamed != NColorManagement::CM_PRIMARIES_BT2020 || cached.transferFunction != chooseTF(m_sdrEotf)) + m_cachedInternalDescription = CImageDescription::from(SImageDescription{ + .transferFunction = chooseTF(m_sdrEotf), + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, + .primaries = NColorPrimaries::BT2020, + }); + + return m_cachedInternalDescription; } WP CMonitor::resources() { diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index b4882fe66..478d3fedb 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -415,6 +415,9 @@ class CMonitor { // Resources UP m_resources; + // cached should contain one of predefined descriptions: BT2020 primaries with variable TF and SDR luminances or SRGB primaries with linear TF and variable luminances. + // avoids lookup for an id when ::from is used + NColorManagement::PImageDescription m_cachedInternalDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); struct { CHyprSignalListener frame; From cc584ff16454f4a1d82f70c6546becaf9c91e0cb Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 10:59:59 +0300 Subject: [PATCH 08/19] linear not normalised WIP --- src/helpers/cm/ColorManagement.hpp | 12 ++++++++++++ src/render/shaders/glsl/cm_helpers.glsl | 9 +++++++-- src/render/shaders/glsl/constants.h | 1 + 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/helpers/cm/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp index e970ff592..ecde9396d 100644 --- a/src/helpers/cm/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -89,6 +89,7 @@ namespace NColorManagement { } inline std::string tfToString(eTransferFunction tf) { switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return "TF:INTERNAL LINEAR NOT NORMALISED"; case CM_TRANSFER_FUNCTION_BT1886: return "TF:BT1886"; case CM_TRANSFER_FUNCTION_GAMMA22: return "TF:GAMMA22"; case CM_TRANSFER_FUNCTION_GAMMA28: return "TF:GAMMA28"; @@ -400,6 +401,17 @@ namespace NColorManagement { .primaries = NColorPrimaries::BT709, .luminances = {.min = 0, .max = 10000, .reference = 80}, }); + + // For internal use only + // not normalised to 0.0 - 1.0 + // luminance values should be set to default SDR settings in SDR mode and to output settings in HDR mode + // keep srgb primaries to avoid conversions for image exports + static const auto LINEAR_NN_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ + .transferFunction = NColorManagement::CM_TRANSFER_FUNCTION_LINEAR, + .primariesNameSet = true, + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, + }); } template diff --git a/src/render/shaders/glsl/cm_helpers.glsl b/src/render/shaders/glsl/cm_helpers.glsl index 0c7d3d510..ae4b63ae8 100644 --- a/src/render/shaders/glsl/cm_helpers.glsl +++ b/src/render/shaders/glsl/cm_helpers.glsl @@ -127,6 +127,7 @@ vec3 tfST240(vec3 color) { vec3 toLinearRGB(vec3 color, int tf) { switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return color; case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); case CM_TRANSFER_FUNCTION_GAMMA22: return pow(max(color, vec3(0.0)), vec3(2.2)); @@ -179,7 +180,7 @@ vec3 fromLinearRGB(vec3 color, int tf) { } vec4 fromLinear(vec4 color, int tf) { - if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR) + if (tf == CM_TRANSFER_FUNCTION_EXT_LINEAR || tf == CM_TRANSFER_FUNCTION_LINEAR) return color; color.rgb /= max(color.a, 0.001); @@ -189,6 +190,9 @@ vec4 fromLinear(vec4 color, int tf) { } vec4 fromLinearNit(vec4 color, int tf, vec2 range) { + if (tf == CM_TRANSFER_FUNCTION_LINEAR) + return color; + color.rgb = (color.rgb - range[0] * color.a) / (range[1] - range[0]); // @gulafaran color.rgb /= max(color.a, 0.001); color.rgb = fromLinearRGB(color.rgb, tf); @@ -231,7 +235,8 @@ vec4 pixColor.rgb *= pixColor.a; #else pixColor.rgb = convertMatrix * pixColor.rgb; - pixColor = toNit(pixColor, srcTFRange); + if (srcTF != CM_TRANSFER_FUNCTION_LINEAR) + pixColor = toNit(pixColor, srcTFRange); pixColor.rgb *= pixColor.a; #if USE_TONEMAP pixColor = tonemap(pixColor, dstxyz, maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance); diff --git a/src/render/shaders/glsl/constants.h b/src/render/shaders/glsl/constants.h index bbab5284b..db8b2cc1d 100644 --- a/src/render/shaders/glsl/constants.h +++ b/src/render/shaders/glsl/constants.h @@ -1,6 +1,7 @@ #ifndef CONSTANTS_H #define CONSTANTS_H //enum eTransferFunction +#define CM_TRANSFER_FUNCTION_LINEAR 0 // not normalised #define CM_TRANSFER_FUNCTION_BT1886 1 #define CM_TRANSFER_FUNCTION_GAMMA22 2 #define CM_TRANSFER_FUNCTION_GAMMA28 3 From 9aa61767bb7652fd518cc87f3be789fea2b0cb11 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 11:42:17 +0300 Subject: [PATCH 09/19] clear offload fb --- src/config/values/ConfigValues.cpp | 2 +- src/render/OpenGL.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config/values/ConfigValues.cpp b/src/config/values/ConfigValues.cpp index 0214ba86c..ac213a421 100644 --- a/src/config/values/ConfigValues.cpp +++ b/src/config/values/ConfigValues.cpp @@ -603,7 +603,7 @@ std::vector> Values::getConfigValues() { MS("debug:fifo_pending_workaround", "Fifo workaround for empty pending list", false), MS("debug:render_solitary_wo_damage", "Render solitary window with empty damage", false), MS("debug:vfr", "controls the VFR status of Hyprland. Do not turn off unless debugging", true), - MS("debug:invalidate_fp16", "allow fp16 buffer invalidation.", 2, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), + MS("debug:invalidate_fp16", "allow fp16 buffer invalidation.", 1, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), /* * layout: diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 76f38ce04..766df64bd 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -758,6 +758,7 @@ void CHyprOpenGLImpl::begin(PHLMONITOR pMonitor, const CRegion& damage_, SPbindFB(g_pHyprRenderer->m_renderData.pMonitor->resources()->getUnusedWorkBuffer()); m_offloadedFramebuffer = true; + GLFB(g_pHyprRenderer->m_renderData.currentFB)->clearAfterInvalidation(); g_pHyprRenderer->m_renderData.mainFB = g_pHyprRenderer->m_renderData.currentFB; g_pHyprRenderer->m_renderData.outFB = fb ? fb : dc(g_pHyprRenderer.get())->m_currentRenderbuffer->getFB(); From 1f07a344fc1b0f00ba7e396d0be6320ce83a4391 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 15:06:06 +0300 Subject: [PATCH 10/19] CM math --- src/helpers/cm/ColorManagement.cpp | 317 ++++++++++++++++++++++++++++- src/helpers/cm/ColorManagement.hpp | 29 ++- 2 files changed, 343 insertions(+), 3 deletions(-) diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp index 5e4725d40..4a41cc0c9 100644 --- a/src/helpers/cm/ColorManagement.cpp +++ b/src/helpers/cm/ColorManagement.cpp @@ -1,6 +1,9 @@ #include "ColorManagement.hpp" #include "../../macros.hpp" -#include "helpers/TransferFunction.hpp" +#include "../Color.hpp" +#include "../TransferFunction.hpp" +#include "../../render/Renderer.hpp" +#include "render/types.hpp" #include #include #include @@ -208,6 +211,318 @@ Mat3x3 NColorManagement::adaptBradford(Hyprgraphics::CColor::xy srcW, Hyprgraphi return result; } +// sRGB constants +#define SRGB_POW 2.4 +#define SRGB_CUT 0.0031308 +#define SRGB_SCALE 12.92 +#define SRGB_ALPHA 1.055 + +#define BT1886_POW (1.0 / 0.45) +#define BT1886_CUT 0.018053968510807 +#define BT1886_SCALE 4.5 +#define BT1886_ALPHA (1.0 + 5.5 * BT1886_CUT) + +// See http://car.france3.mars.free.fr/HD/INA-%2026%20jan%2006/SMPTE%20normes%20et%20confs/s240m.pdf +#define ST240_POW (1.0 / 0.45) +#define ST240_CUT 0.0228 +#define ST240_SCALE 4.0 +#define ST240_ALPHA 1.1115 + +#define ST428_POW 2.6 +#define ST428_SCALE (52.37 / 48.0) + +// PQ constants +#define PQ_M1 0.1593017578125 +#define PQ_M2 78.84375 +#define PQ_INV_M1 (1.0 / PQ_M1) +#define PQ_INV_M2 (1.0 / PQ_M2) +#define PQ_C1 0.8359375 +#define PQ_C2 18.8515625 +#define PQ_C3 18.6875 + +// HLG constants +#define HLG_D_CUT (1.0 / 12.0) +#define HLG_E_CUT 0.5 +#define HLG_A 0.17883277 +#define HLG_B 0.28466892 +#define HLG_C 0.55991073 + +static inline int sign(double value) { + return value < 0 ? -1 : 1; +} + +// The primary source for these transfer functions is https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.1361-0-199802-W!!PDF-E.pdf +static RGBAColor tfInvPQ(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const double E = pow(std::clamp(color.v[i], 0.0, 1.0), PQ_INV_M2); + color.v[i] = pow((std::max(E - PQ_C1, 0.0)) / (PQ_C2 - PQ_C3 * E), PQ_INV_M1); + } + return color; +} + +static RGBAColor tfInvHLG(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= HLG_E_CUT; + color.v[i] = isLow ? color.v[i] * color.v[i] / 3.0 : (exp((color.v[i] - HLG_C) / HLG_A) + HLG_B) / 12.0; + } + return color; +} + +// Many transfer functions (including sRGB) follow the same pattern: a linear +// segment for small values and a power function for larger values. The +// following function implements this pattern from which sRGB, BT.1886, and +// others can be derived by plugging in the right constants. +static RGBAColor tfInvLinPow(RGBAColor color, float gamma, float thres, float scale, float alpha) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= thres * scale; + color.v[i] = isLow ? color.v[i] / scale : pow((color.v[i] + alpha - 1.0) / alpha, gamma); + } + return color; +} + +static RGBAColor tfInvSRGB(RGBAColor color) { + return tfInvLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +static RGBAColor tfInvExtSRGB(RGBAColor color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + const auto absColor = tfInvSRGB({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfInvBT1886(RGBAColor color) { + return tfInvLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +static RGBAColor tfInvXVYCC(RGBAColor color) { + // The inverse transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + const auto absColor = tfInvBT1886({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfInvST240(RGBAColor color) { + return tfInvLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +// Forward transfer functions corresponding to the inverse functions above. +static RGBAColor tfPQ(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const double E = pow(std::clamp(color.v[i], 0.0, 1.0), PQ_M1); + color.v[i] = pow((PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E), PQ_M2); + } + return color; +} + +static RGBAColor tfHLG(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= HLG_D_CUT; + color.v[i] = isLow ? sqrt(std::max(color.v[i], 0.0) * 3.0) : HLG_A * log(std::max(12.0 * color.v[i] - HLG_B, 0.0001)) + HLG_C; + } + return color; +} + +static RGBAColor tfLinPow(RGBAColor color, float gamma, float thres, float scale, float alpha) { + for (uint i = 0; i <= 2; i++) { + const bool isLow = color.v[i] <= thres; + color.v[i] = isLow ? color.v[i] * scale : pow(color.v[i], 1.0 / gamma) * alpha - (alpha - 1.0); + } + return color; +} + +static RGBAColor tfSRGB(RGBAColor color) { + return tfLinPow(color, SRGB_POW, SRGB_CUT, SRGB_SCALE, SRGB_ALPHA); +} + +static RGBAColor tfExtSRGB(RGBAColor color) { + // EXT sRGB is the sRGB transfer function mirrored around 0. + const auto absColor = tfSRGB({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfBT1886(RGBAColor color) { + return tfLinPow(color, BT1886_POW, BT1886_CUT, BT1886_SCALE, BT1886_ALPHA); +} + +static RGBAColor tfXVYCC(RGBAColor color) { + // The transfer function for XVYCC is the BT1886 transfer function mirrored around 0, + // same as what EXT sRGB is to sRGB. + const auto absColor = tfBT1886({{.r = abs(color.c.r), .g = abs(color.c.g), .b = abs(color.c.b), .a = color.c.a}}); + return {{ + .r = absColor.c.r * sign(color.c.r), + .g = absColor.c.g * sign(color.c.g), + .b = absColor.c.b * sign(color.c.b), + .a = absColor.c.a, + }}; +} + +static RGBAColor tfST240(RGBAColor color) { + return tfLinPow(color, ST240_POW, ST240_CUT, ST240_SCALE, ST240_ALPHA); +} + +static RGBAColor tfST428(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0), ST428_POW) * ST428_SCALE; + } + return color; +} + +static RGBAColor tfInvST428(RGBAColor color) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0) / ST428_SCALE, 1.0 / ST428_POW); + } + return color; +} + +static RGBAColor tfGamma(RGBAColor color, float gamma) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = pow(std::max(color.v[i], 0.0), gamma); + } + return color; +} + +static RGBAColor tfLog(RGBAColor color, float mult) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] <= 0 ? 0.0 : exp((color.v[i] - 1.0) * mult * std::numbers::ln10); + } + return color; +} + +static RGBAColor tfInvLog(RGBAColor color, float mult, float min) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] <= min ? 0.0 : 1.0 + log(color.v[i]) / std::numbers::ln10 / mult; + } + return color; +} + +static RGBAColor toLinearRGB(RGBAColor color, eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_LINEAR: return color; + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfInvPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return tfGamma(color, 2.2); + case CM_TRANSFER_FUNCTION_GAMMA28: return tfGamma(color, 2.8); + case CM_TRANSFER_FUNCTION_HLG: return tfInvHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfInvExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfInvBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfInvST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return tfLog(color, 2.0); + case CM_TRANSFER_FUNCTION_LOG_316: return tfLog(color, 2.5); + case CM_TRANSFER_FUNCTION_XVYCC: return tfInvXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return tfST428(color); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfInvSRGB(color); + } +} + +static RGBAColor fromLinearRGB(RGBAColor color, eTransferFunction tf) { + switch (tf) { + case CM_TRANSFER_FUNCTION_EXT_LINEAR: return color; + case CM_TRANSFER_FUNCTION_ST2084_PQ: return tfPQ(color); + case CM_TRANSFER_FUNCTION_GAMMA22: return tfGamma(color, 1.0 / 2.2); + case CM_TRANSFER_FUNCTION_GAMMA28: return tfGamma(color, 1.0 / 2.8); + case CM_TRANSFER_FUNCTION_HLG: return tfHLG(color); + case CM_TRANSFER_FUNCTION_EXT_SRGB: return tfExtSRGB(color); + case CM_TRANSFER_FUNCTION_BT1886: return tfBT1886(color); + case CM_TRANSFER_FUNCTION_ST240: return tfST240(color); + case CM_TRANSFER_FUNCTION_LOG_100: return tfInvLog(color, 2.0, 0.01); + case CM_TRANSFER_FUNCTION_LOG_316: return tfInvLog(color, 2.5, sqrt(10.0) / 1000.0); + case CM_TRANSFER_FUNCTION_XVYCC: return tfXVYCC(color); + case CM_TRANSFER_FUNCTION_ST428: return tfInvST428(color); + case CM_TRANSFER_FUNCTION_SRGB: + default: return tfSRGB(color); + } +} + +static RGBAColor toNit(RGBAColor color, Render::STFRange range) { + for (uint i = 0; i <= 2; i++) { + color.v[i] = color.v[i] * (range.max - range.min) + range.min; + } + return color; +} + +static RGBAColor fromLinearNit(RGBAColor color, eTransferFunction tf, Render::STFRange range) { + if (tf == CM_TRANSFER_FUNCTION_LINEAR) + return color; + + for (uint i = 0; i <= 2; i++) { + color.v[i] = (color.v[i] - range.min * color.c.a) / (range.max - range.min); + } + color /= std::max(color.c.a, 0.001); + color = fromLinearRGB(color, tf); + color *= color.c.a; + return color; +} + +static RGBAColor saturate(RGBAColor color, std::array, 3> primaries, float saturation) { + if (saturation == 1.0) + return color; + + std::array colorArr = {color.v[0], color.v[1], color.v[2]}; + std::array brightness = {primaries[1][0], primaries[1][1], primaries[1][2]}; + const auto Y = std::inner_product(colorArr.begin(), colorArr.end(), brightness.begin(), 0.0); + for (uint i = 0; i <= 2; i++) { + color.v[i] = (color.v[i] * saturation) + (Y * (1.0 - saturation)); + } + return color; +} + +static RGBAColor tonemap(RGBAColor color, std::array, 3> dstXYZ, float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance) { + // TODO source color is expected to be in sRGB colorspace and tonamepping shouldn't be needed + return color; +} + +RGBAColor NColorManagement::convertColor(RGBAColor color, PImageDescription srcDesc, PImageDescription dstDesc) { + const auto settings = + g_pHyprRenderer->getCMSettings(srcDesc, dstDesc, nullptr, true, g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_sdrMinLuminance : -1, + g_pHyprRenderer->m_renderData.pMonitor ? g_pHyprRenderer->m_renderData.pMonitor->m_sdrMaxLuminance : -1); + + color /= std::max(color.c.a, 0.001); + color = toLinearRGB(color, srcDesc->value().transferFunction); + if (dstDesc->value().icc.present) { + // color.rgb = applyIcc3DLut(color.rgb, iccLut3D, iccLutSize); + color *= color.c.a; + } else { + const auto convertMatrix = srcDesc->getPrimaries()->convertMatrix(dstDesc->getPrimaries()); + const auto converted = convertMatrix * Hyprgraphics::CColor::XYZ{.x = color.c.r, .y = color.c.g, .z = color.c.b}; + color.c.r = converted.x; + color.c.g = converted.y; + color.c.b = converted.z; + if (srcDesc->value().transferFunction != CM_TRANSFER_FUNCTION_LINEAR) + color = toNit(color, settings.srcTFRange); + color *= color.c.a; + if (settings.needsTonemap) + color = tonemap(color, settings.dstPrimaries2XYZ, settings.maxLuminance, settings.dstMaxLuminance, settings.dstRefLuminance, settings.srcRefLuminance); + + color = fromLinearNit(color, dstDesc->value().transferFunction, settings.dstTFRange); + if (settings.needsSDRmod) { + color = saturate(color, settings.dstPrimaries2XYZ, settings.sdrSaturation); + color *= settings.sdrBrightnessMultiplier; + } + } + return color; +} + +CHyprColor NColorManagement::convertColor(const CHyprColor& color, PImageDescription srcDesc, PImageDescription dstDesc) { + const auto& converted = convertColor(RGBAColor{{.r = color.r, .g = color.g, .b = color.b, .a = color.a}}, srcDesc, dstDesc); + return CHyprColor(converted.c.r, converted.c.g, converted.c.b, converted.c.a); +} + PImageDescription NColorManagement::getDefaultImageDescription() { const auto TF = fromConfig(); switch (TF) { diff --git a/src/helpers/cm/ColorManagement.hpp b/src/helpers/cm/ColorManagement.hpp index ecde9396d..c8d1cfcba 100644 --- a/src/helpers/cm/ColorManagement.hpp +++ b/src/helpers/cm/ColorManagement.hpp @@ -3,8 +3,9 @@ #include "color-management-v1.hpp" #include #include -#include "../../helpers/memory/Memory.hpp" -#include "../../helpers/math/Math.hpp" +#include "../memory/Memory.hpp" +#include "../math/Math.hpp" +#include "../Color.hpp" #include "../../debug/log/Logger.hpp" #include @@ -357,8 +358,32 @@ namespace NColorManagement { SImageDescription m_imageDescription; }; + union RGBAColor { + struct { + double r = 0, g = 0, b = 0, a = 0; + } c; + double v[4]; + + RGBAColor& operator*=(double value) { + c.r *= value; + c.g *= value; + c.b *= value; + return *this; + } + + RGBAColor& operator/=(double value) { + c.r /= value; + c.g /= value; + c.b /= value; + return *this; + } + }; + using PImageDescription = WP; + RGBAColor convertColor(RGBAColor color, PImageDescription srcDesc, PImageDescription dstDesc); + CHyprColor convertColor(const CHyprColor& color, PImageDescription srcDesc, PImageDescription dstDesc); + PImageDescription getDefaultImageDescription(); static const auto DEFAULT_GAMMA22_IMAGE_DESCRIPTION = CImageDescription::from(SImageDescription{ From 573440b6fde473ca8efeaa6ffaf0f2463a64a73c Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 18:28:56 +0300 Subject: [PATCH 11/19] fix includes --- src/helpers/cm/ColorManagement.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/helpers/cm/ColorManagement.cpp b/src/helpers/cm/ColorManagement.cpp index 4a41cc0c9..8428f29ef 100644 --- a/src/helpers/cm/ColorManagement.cpp +++ b/src/helpers/cm/ColorManagement.cpp @@ -7,6 +7,7 @@ #include #include #include +#include using namespace NColorManagement; using namespace NTransferFunction; From acdcbaa79663f5ab3382da736e8a425418de525a Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 19:03:25 +0300 Subject: [PATCH 12/19] use precalculated cm for shadows --- src/render/OpenGL.cpp | 10 ++--- src/render/Shader.cpp | 1 + src/render/Shader.hpp | 1 + src/render/shaders/glsl/shadow.frag | 34 +---------------- src/render/shaders/glsl/shadow.glsl | 58 ++++------------------------- 5 files changed, 15 insertions(+), 89 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 766df64bd..38fe3325b 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -2243,14 +2243,12 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun blend(true); - const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; - const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); - auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD) | globalFeatures())); - if (!skipCM) - passCMUniforms(shader, getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + const auto converted = convertColor(col, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(range + round, range + round); const auto BOTTOMRIGHT = Vector2D(newBox.width - (range + round), newBox.height - (range + round)); diff --git a/src/render/Shader.cpp b/src/render/Shader.cpp index 3359de660..8a7b8ba9d 100644 --- a/src/render/Shader.cpp +++ b/src/render/Shader.cpp @@ -126,6 +126,7 @@ void CShader::getUniformLocations() { m_uniformLocations[SHADER_PROJ] = getUniform("proj"); m_uniformLocations[SHADER_COLOR] = getUniform("color"); + m_uniformLocations[SHADER_COLOR_SRGB] = getUniform("colorSRGB"); m_uniformLocations[SHADER_ALPHA_MATTE] = getUniform("texMatte"); m_uniformLocations[SHADER_TEX_TYPE] = getUniform("texType"); diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp index 84a4014cd..cf15c8059 100644 --- a/src/render/Shader.hpp +++ b/src/render/Shader.hpp @@ -7,6 +7,7 @@ enum eShaderUniform : uint8_t { SHADER_PROJ = 0, SHADER_COLOR, + SHADER_COLOR_SRGB, SHADER_ALPHA_MATTE, SHADER_TEX_TYPE, SHADER_SOURCE_TF, diff --git a/src/render/shaders/glsl/shadow.frag b/src/render/shaders/glsl/shadow.frag index e91e9a140..eb6f31749 100644 --- a/src/render/shaders/glsl/shadow.frag +++ b/src/render/shaders/glsl/shadow.frag @@ -8,10 +8,7 @@ precision highp float; in vec4 v_color; in vec2 v_texcoord; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat3 targetPrimariesXYZ; - +uniform vec4 colorSRGB; uniform vec2 topLeft; uniform vec2 bottomRight; uniform vec2 windowTopLeft; @@ -23,11 +20,6 @@ uniform float range; uniform float shadowPower; uniform float thick; -#if USE_CM -#include "cm_helpers.glsl" -#include "CM.glsl" -#endif - #include "shadow.glsl" layout(location = 0) out vec4 fragColor; @@ -41,29 +33,7 @@ void main() { #else fragColor = #endif - getShadow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight, windowTopLeft, windowBottomRight, thick -#if USE_CM - , - sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif -#endif - ); + getShadow(pixColor, colorSRGB, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight, windowTopLeft, windowBottomRight, thick); #if USE_MIRROR fragColor = pixColors[0]; mirrorColor = pixColors[1]; diff --git a/src/render/shaders/glsl/shadow.glsl b/src/render/shaders/glsl/shadow.glsl index fdfa697c6..ba68bc49f 100644 --- a/src/render/shaders/glsl/shadow.glsl +++ b/src/render/shaders/glsl/shadow.glsl @@ -5,7 +5,6 @@ #ifndef SHADOW_GLSL #define SHADOW_GLSL -#include "cm_helpers.glsl" #include "rounding.glsl" float pixAlphaRoundedDistance(float distanceToCorner, float radius, float range, float shadowPower) { @@ -54,30 +53,8 @@ vec4[2] #else vec4 #endif - getShadow(vec4 pixColor, vec2 v_texcoord, float borderRadius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight, - vec2 windowTopLeft, vec2 windowBottomRight, float windowRadius -#if USE_CM - , - int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange -#if USE_ICC - , - highp sampler3D iccLut3D, float iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - mat3 targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance -#endif -#if USE_SDR_MOD - , - float sdrSaturation, float sdrBrightnessMultiplier -#endif -#endif -#endif - ) { + getShadow(vec4 pixColor, vec4 colorSRGB, vec2 v_texcoord, float borderRadius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float shadowPower, vec2 bottomRight, + vec2 windowTopLeft, vec2 windowBottomRight, float windowRadius) { float radius = range + borderRadius; float originalAlpha = pixColor[3]; @@ -144,33 +121,12 @@ vec4 // premultiply pixColor.rgb *= pixColor[3]; -#if USE_CM -#if USE_MIRROR - vec4[2] pixColors = -#else - pixColor = -#endif - doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif - ); -#endif #if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = colorSRGB; + pixColors[1].a = pixColor.a; + pixColors[1].rgb *= pixColors[1].a; return pixColors; #else return pixColor; From 1b441e97f8aa31c57fb2fc22ce994397db4594ee Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 19:41:51 +0300 Subject: [PATCH 13/19] inner glow cm --- src/render/OpenGL.cpp | 10 ++--- src/render/shaders/glsl/inner_glow.frag | 41 +++++------------- src/render/shaders/glsl/inner_glow.glsl | 55 ++++--------------------- 3 files changed, 24 insertions(+), 82 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 38fe3325b..17fa0b714 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -2335,14 +2335,12 @@ void CHyprOpenGLImpl::renderInnerGlow(const CBox& box, int round, float rounding blend(true); - const bool IS_ICC = g_pHyprRenderer->workBufferImageDescription()->value().icc.present; - const bool skipCM = !m_cmSupported || !g_pHyprRenderer->workBufferImageDescription()->needsCM(getDefaultImageDescription()); - auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, skipCM ? 0 : SH_FEAT_CM | (IS_ICC ? SH_FEAT_ICC : SH_FEAT_TONEMAP | SH_FEAT_SDR_MOD))); - if (!skipCM) - passCMUniforms(shader, getDefaultImageDescription()); + auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - shader->setUniformFloat4(SHADER_COLOR, col.r, col.g, col.b, col.a * a); + const auto converted = convertColor(col, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); const auto TOPLEFT = Vector2D(round, round); const auto BOTTOMRIGHT = Vector2D(newBox.width - round, newBox.height - round); diff --git a/src/render/shaders/glsl/inner_glow.frag b/src/render/shaders/glsl/inner_glow.frag index 2fc405c06..63369e0ac 100644 --- a/src/render/shaders/glsl/inner_glow.frag +++ b/src/render/shaders/glsl/inner_glow.frag @@ -8,10 +8,7 @@ precision highp float; in vec4 v_color; in vec2 v_texcoord; -uniform int sourceTF; // eTransferFunction -uniform int targetTF; // eTransferFunction -uniform mat3 targetPrimariesXYZ; - +uniform vec4 colorSRGB; uniform vec2 topLeft; uniform vec2 bottomRight; uniform vec2 fullSize; @@ -20,38 +17,22 @@ uniform float roundingPower; uniform float range; uniform float shadowPower; -#if USE_CM -#include "cm_helpers.glsl" -#include "CM.glsl" -#endif - #include "inner_glow.glsl" layout(location = 0) out vec4 fragColor; +#if USE_MIRROR +layout(location = 1) out vec4 mirrorColor; +#endif void main() { vec4 pixColor = v_color; - - fragColor = getInnerGlow(pixColor, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight -#if USE_CM - , - sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize +#if USE_MIRROR + vec4[2] pixColors = #else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ + fragColor = #endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance + getInnerGlow(pixColor, colorSRGB, v_texcoord, radius, roundingPower, topLeft, fullSize, range, shadowPower, bottomRight); +#if USE_MIRROR + fragColor = pixColors[0]; + mirrorColor = pixColors[1]; #endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif -#endif - ); } diff --git a/src/render/shaders/glsl/inner_glow.glsl b/src/render/shaders/glsl/inner_glow.glsl index b0d55b194..307c3648a 100644 --- a/src/render/shaders/glsl/inner_glow.glsl +++ b/src/render/shaders/glsl/inner_glow.glsl @@ -5,8 +5,6 @@ #ifndef INNER_GLOW_GLSL #define INNER_GLOW_GLSL -#include "cm_helpers.glsl" - float innerGlowAlpha(float distFromEdge, float range, float glowPower) { if (distFromEdge >= range) return 0.0; @@ -26,29 +24,7 @@ float innerGlowSmin(float a, float b, float k) { return min(a, b) - h * h * h * k * (1.0 / 6.0); } -vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, vec2 bottomRight -#if USE_CM - , - int sourceTF, int targetTF, mat3 convertMatrix, vec2 srcTFRange, vec2 dstTFRange -#if USE_ICC - , - highp sampler3D iccLut3D, float iccLutSize -#else -#if USE_TONEMAP || USE_SDR_MOD - , - mat3 targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - float maxLuminance, float dstMaxLuminance, float dstRefLuminance, float srcRefLuminance -#endif -#if USE_SDR_MOD - , - float sdrSaturation, float sdrBrightnessMultiplier -#endif -#endif -#endif -) { +vec4 getInnerGlow(vec4 pixColor, vec4 colorSRGB, vec2 v_texcoord, float radius, float roundingPower, vec2 topLeft, vec2 fullSize, float range, float glowPower, vec2 bottomRight) { vec2 pixCoord = fullSize * v_texcoord; // clip to the rounded rectangle shape using actual SDF @@ -79,28 +55,15 @@ vec4 getInnerGlow(vec4 pixColor, vec2 v_texcoord, float radius, float roundingPo // premultiply pixColor.rgb *= pixColor[3]; -#if USE_CM - pixColor = doColorManagement(pixColor, sourceTF, targetTF, convertMatrix, srcTFRange, dstTFRange -#if USE_ICC - , - iccLut3D, iccLutSize +#if USE_MIRROR + vec4[2] pixColors; + pixColors[0] = pixColor; + pixColors[1] = colorSRGB; + pixColors[1].a = pixColor.a; + pixColors[1].rgb *= pixColors[1].a; + return pixColors; #else -#if USE_TONEMAP || USE_SDR_MOD - , - targetPrimariesXYZ -#endif -#if USE_TONEMAP - , - maxLuminance, dstMaxLuminance, dstRefLuminance, srcRefLuminance -#endif -#if USE_SDR_MOD - , - sdrSaturation, sdrBrightnessMultiplier -#endif -#endif - ); -#endif - return pixColor; +#endif } #endif From f71905e13bcf8caef393fc2ae4dea697a73dcd86 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sun, 26 Apr 2026 19:52:15 +0300 Subject: [PATCH 14/19] quad cm --- src/render/OpenGL.cpp | 6 +++++- src/render/shaders/glsl/quad.frag | 7 ++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 17fa0b714..1edd0a79c 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -34,6 +34,7 @@ #include "../managers/screenshare/ScreenshareManager.hpp" #include "../notification/NotificationOverlay.hpp" #include "errorOverlay/Overlay.hpp" +#include "helpers/Color.hpp" #include "macros.hpp" #include "pass/TexPassElement.hpp" #include "pass/RectPassElement.hpp" @@ -1079,7 +1080,10 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); // premultiply the color as well as we don't work with straight alpha - shader->setUniformFloat4(SHADER_COLOR, col.r * col.a, col.g * col.a, col.b * col.a, col.a); + const auto premultiplied = CHyprColor(col.r * col.a, col.g * col.a, col.b * col.a, col.a); + const auto converted = convertColor(premultiplied, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a); + shader->setUniformFloat4(SHADER_COLOR_SRGB, premultiplied.r, premultiplied.g, premultiplied.b, premultiplied.a); CBox transformedBox = box; transformedBox.transform(Math::wlTransformToHyprutils(Math::invertTransform(m_renderData.pMonitor->m_transform)), m_renderData.pMonitor->m_transformedSize.x, diff --git a/src/render/shaders/glsl/quad.frag b/src/render/shaders/glsl/quad.frag index dc1945afa..46d42a8c7 100644 --- a/src/render/shaders/glsl/quad.frag +++ b/src/render/shaders/glsl/quad.frag @@ -7,6 +7,7 @@ precision highp float; in vec4 v_color; +uniform vec4 colorSRGB; #if USE_ROUNDING uniform float radius; uniform float roundingPower; @@ -28,6 +29,10 @@ void main() { fragColor = pixColor; #if USE_MIRROR - mirrorColor = fragColor; +#if USE_ROUNDING + mirrorColor = rounding(colorSRGB, radius, roundingPower, topLeft, fullSize); +#else + mirrorColor = colorSRGB; +#endif #endif } From b08eefd4e22d2d770fd0a72aef1424a9e112bc46 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Tue, 28 Apr 2026 21:57:46 +0300 Subject: [PATCH 15/19] fix unmodified copy --- src/config/values/ConfigValues.cpp | 2 ++ src/helpers/Monitor.cpp | 13 ++++++++----- src/helpers/Monitor.hpp | 2 +- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/config/values/ConfigValues.cpp b/src/config/values/ConfigValues.cpp index ac213a421..260425cf1 100644 --- a/src/config/values/ConfigValues.cpp +++ b/src/config/values/ConfigValues.cpp @@ -539,6 +539,8 @@ std::vector> Values::getConfigValues() { {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), MS("render:non_shader_cm_interop", "non_shader_cm interaction with ctm proto (hyprsunset and similar).", 2, {.min = 0, .max = 2, .map = OptionMap{{"disable", 0}, {"enable", 1}, {"auto", 2}}}), + MS("render:fp16_sdr_tf", "Internal workbuffer transfer function for fp16 in SDR mode", 0, + {.min = 0, .max = 2, .map = OptionMap{{"default", 0}, {"linear", 1}, {"monitor", 2}}}), /* * cursor: diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index af79a4e3b..4f4e9a7b4 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2550,6 +2550,8 @@ bool CMonitor::useFP16() { } PImageDescription CMonitor::workBufferImageDescription() { + static const auto PFP16TF = CConfigValue("render:fp16_sdr_tf"); + if (!useFP16()) return m_imageDescription; @@ -2561,19 +2563,20 @@ PImageDescription CMonitor::workBufferImageDescription() { const auto& cached = m_cachedInternalDescription->value(); // HDR - if (isHDRLikeTF || value.windowsScRGB) { - if (cached.primariesNamed != NColorManagement::CM_PRIMARIES_SRGB || cached.luminances != value.luminances) + if (isHDRLikeTF || value.windowsScRGB || *PFP16TF != 2) { + if (cached.transferFunction != LINEAR_IMAGE_DESCRIPTION->value().transferFunction || cached.luminances != value.luminances) m_cachedInternalDescription = LINEAR_IMAGE_DESCRIPTION->with(value.luminances); return m_cachedInternalDescription; } // SDR - if (cached.primariesNamed != NColorManagement::CM_PRIMARIES_BT2020 || cached.transferFunction != chooseTF(m_sdrEotf)) + if (cached.transferFunction != chooseTF(m_sdrEotf)) m_cachedInternalDescription = CImageDescription::from(SImageDescription{ .transferFunction = chooseTF(m_sdrEotf), .primariesNameSet = true, - .primariesNamed = NColorManagement::CM_PRIMARIES_BT2020, - .primaries = NColorPrimaries::BT2020, + // render:keep_unmodified_copy and other conditions that trigger MRT for screen sharing expect a work buffer with sRGB primaries + .primariesNamed = NColorManagement::CM_PRIMARIES_SRGB, + .primaries = NColorPrimaries::BT709, }); return m_cachedInternalDescription; diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 478d3fedb..8d26be45b 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -415,7 +415,7 @@ class CMonitor { // Resources UP m_resources; - // cached should contain one of predefined descriptions: BT2020 primaries with variable TF and SDR luminances or SRGB primaries with linear TF and variable luminances. + // cached should contain one of predefined descriptions for FP16: sRGB primaries with either linear TF by default and in HDR mode or monitor's TF in SDR with render:fp16_sdr_tf = 2 // avoids lookup for an id when ::from is used NColorManagement::PImageDescription m_cachedInternalDescription = NColorManagement::CImageDescription::from(NColorManagement::SImageDescription{}); From adc6704ca9cec29622a69209ad06496d7ee56644 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sat, 2 May 2026 15:22:43 +0300 Subject: [PATCH 16/19] codegen --- meta/hl.meta.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/meta/hl.meta.lua b/meta/hl.meta.lua index e0fd096ae..a95da71c7 100644 --- a/meta/hl.meta.lua +++ b/meta/hl.meta.lua @@ -348,6 +348,7 @@ ---| "render.ctm_animation" ---| "render.direct_scanout" ---| "render.expand_undersized_textures" +---| "render.fp16_sdr_tf" ---| "render.icc_vcgt_enabled" ---| "render.keep_unmodified_copy" ---| "render.new_render_scheduling" @@ -1178,6 +1179,7 @@ hl = {} ---@field ['render.ctm_animation'] integer|boolean ---@field ['render.direct_scanout'] integer|boolean ---@field ['render.expand_undersized_textures'] boolean +---@field ['render.fp16_sdr_tf'] integer|boolean ---@field ['render.icc_vcgt_enabled'] boolean ---@field ['render.keep_unmodified_copy'] integer|boolean ---@field ['render.new_render_scheduling'] boolean From bcbe5dbfad70ac03fb72224e8a8bb23c28d6baaf Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sat, 2 May 2026 15:23:04 +0300 Subject: [PATCH 17/19] cache color CM --- src/render/OpenGL.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp index 1edd0a79c..0a53442ef 100644 --- a/src/render/OpenGL.cpp +++ b/src/render/OpenGL.cpp @@ -1064,6 +1064,19 @@ void CHyprOpenGLImpl::renderRectWithBlurInternal(const CBox& box, const CHyprCol renderRectWithDamageInternal(box, col, data); } +using ColorConversionKey = std::tuple; +static std::map colorConversionCache; + +static CHyprColor getConvertedColor(const CHyprColor& color) { + const auto targetId = g_pHyprRenderer->workBufferImageDescription()->id(); + const ColorConversionKey key = {color.r, color.g, color.b, color.a, targetId}; + if (colorConversionCache.contains(key)) + return colorConversionCache[key]; + const auto converted = convertColor(color, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + colorConversionCache[key] = converted; + return converted; +} + void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprColor& col, const SRectRenderData& data) { auto& m_renderData = g_pHyprRenderer->m_renderData; RASSERT((box.width > 0 && box.height > 0), "Tried to render rect with width/height < 0!"); @@ -1081,7 +1094,7 @@ void CHyprOpenGLImpl::renderRectWithDamageInternal(const CBox& box, const CHyprC // premultiply the color as well as we don't work with straight alpha const auto premultiplied = CHyprColor(col.r * col.a, col.g * col.a, col.b * col.a, col.a); - const auto converted = convertColor(premultiplied, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + const auto converted = getConvertedColor(premultiplied); shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a); shader->setUniformFloat4(SHADER_COLOR_SRGB, premultiplied.r, premultiplied.g, premultiplied.b, premultiplied.a); @@ -2250,7 +2263,7 @@ void CHyprOpenGLImpl::renderRoundedShadow(const CBox& box, int round, float roun auto shader = useShader(getShaderVariant(SH_FRAG_SHADOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - const auto converted = convertColor(col, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + const auto converted = getConvertedColor(col); shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); @@ -2342,7 +2355,7 @@ void CHyprOpenGLImpl::renderInnerGlow(const CBox& box, int round, float rounding auto shader = useShader(getShaderVariant(SH_FRAG_INNER_GLOW, globalFeatures())); shader->setUniformMatrix3fv(SHADER_PROJ, 1, GL_TRUE, glMatrix.getMatrix()); - const auto converted = convertColor(col, DEFAULT_SRGB_IMAGE_DESCRIPTION, g_pHyprRenderer->workBufferImageDescription()); + const auto converted = getConvertedColor(col); shader->setUniformFloat4(SHADER_COLOR, converted.r, converted.g, converted.b, converted.a * a); shader->setUniformFloat4(SHADER_COLOR_SRGB, col.r, col.g, col.b, col.a * a); From 60abf28fc9f2ec8d9103b4cf0943ec99ea241d18 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sat, 2 May 2026 17:41:37 +0300 Subject: [PATCH 18/19] mark blur dirty when CM/DS changes --- src/helpers/Monitor.cpp | 15 +++++++++++++++ src/helpers/Monitor.hpp | 1 + src/render/Renderer.cpp | 14 ++------------ 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index 4f4e9a7b4..a9763685b 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -620,6 +620,7 @@ void CMonitor::applyCMType(NCMType::eCMType cmType, NTransferFunction::eTF cmSdr if (oldImageDescription != m_imageDescription) { if (PROTO::colorManagement) PROTO::colorManagement->onMonitorImageDescriptionChanged(m_self); + m_blurFBDirty = true; } } @@ -2063,6 +2064,20 @@ bool CMonitor::attemptDirectScanout() { return true; } +void CMonitor::handleDSleave() { + Log::logger->log(Log::DEBUG, "Left a direct scanout."); + m_lastScanout.reset(); + m_previousFSWindow.reset(); // recalc fs settings + m_directScanoutIsActive = false; + + // reset DRM format, but only if needed since it might modeset + if (m_output->state->state().drmFormat != m_prevDrmFormat) + m_output->state->setFormat(m_prevDrmFormat); + + m_drmFormat = m_prevDrmFormat; + m_blurFBDirty = true; +} + bool CMonitor::canAttemptDirectScanoutFast() const { return !m_solitaryClient.expired() || !m_lastScanout.expired() || m_directScanoutIsActive; } diff --git a/src/helpers/Monitor.hpp b/src/helpers/Monitor.hpp index 8d26be45b..4758481f4 100644 --- a/src/helpers/Monitor.hpp +++ b/src/helpers/Monitor.hpp @@ -326,6 +326,7 @@ class CMonitor { bool updateTearing(); uint16_t isDSBlocked(bool full = false); bool attemptDirectScanout(); + void handleDSleave(); bool canAttemptDirectScanoutFast() const; bool isMultiGPU(); void setCTM(const Mat3x3& ctm); diff --git a/src/render/Renderer.cpp b/src/render/Renderer.cpp index 20282315b..8cda1437b 100644 --- a/src/render/Renderer.cpp +++ b/src/render/Renderer.cpp @@ -1974,18 +1974,8 @@ void IHyprRenderer::renderMonitor(PHLMONITOR pMonitor, bool commit) { } handleFullscreenSettings(pMonitor); 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_previousFSWindow.reset(); // recalc fs settings - 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); - - pMonitor->m_drmFormat = pMonitor->m_prevDrmFormat; - } + } else if (!pMonitor->m_lastScanout.expired() || pMonitor->m_directScanoutIsActive) + pMonitor->handleDSleave(); } Event::bus()->m_events.render.pre.emit(pMonitor); From 5cfa6fcf9f97970ea62048403c000038bd4dadd0 Mon Sep 17 00:00:00 2001 From: UjinT34 Date: Sat, 2 May 2026 17:43:53 +0300 Subject: [PATCH 19/19] avoid icc workbuffer --- src/helpers/Monitor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/helpers/Monitor.cpp b/src/helpers/Monitor.cpp index a9763685b..e040d0b3a 100644 --- a/src/helpers/Monitor.cpp +++ b/src/helpers/Monitor.cpp @@ -2567,7 +2567,7 @@ bool CMonitor::useFP16() { PImageDescription CMonitor::workBufferImageDescription() { static const auto PFP16TF = CConfigValue("render:fp16_sdr_tf"); - if (!useFP16()) + if (!useFP16() && !m_imageDescription->value().icc.present) return m_imageDescription; const auto& value = m_imageDescription->value();