summaryrefslogtreecommitdiff
path: root/src/render
diff options
context:
space:
mode:
authorDominick DiMaggio <ddimaggio324@gmail.com>2025-12-07 12:58:49 -0500
committerGitHub <noreply@github.com>2025-12-07 17:58:49 +0000
commit532ca053d6d7ca4afd0b7980b91fcb5bb09da552 (patch)
treefe5c4681b19857131042c9b396071f9fe254f812 /src/render
parentca99e8228cd3b2fa062d74f16c0531f217d722b2 (diff)
renderer/cm: higher-quality tonemapping (#12204)
Diffstat (limited to 'src/render')
-rw-r--r--src/render/OpenGL.cpp28
-rw-r--r--src/render/Shader.hpp1
-rw-r--r--src/render/shaders/glsl/CM.glsl26
-rw-r--r--src/render/shaders/glsl/blurprepare.frag2
4 files changed, 32 insertions, 25 deletions
diff --git a/src/render/OpenGL.cpp b/src/render/OpenGL.cpp
index 19c07923..2f1bccb2 100644
--- a/src/render/OpenGL.cpp
+++ b/src/render/OpenGL.cpp
@@ -971,6 +971,7 @@ static void getCMShaderUniforms(SShader& shader) {
shader.uniformLocations[SHADER_DST_TF_RANGE] = glGetUniformLocation(shader.program, "dstTFRange");
shader.uniformLocations[SHADER_TARGET_PRIMARIES] = glGetUniformLocation(shader.program, "targetPrimaries");
shader.uniformLocations[SHADER_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "maxLuminance");
+ shader.uniformLocations[SHADER_SRC_REF_LUMINANCE] = glGetUniformLocation(shader.program, "srcRefLuminance");
shader.uniformLocations[SHADER_DST_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "dstMaxLuminance");
shader.uniformLocations[SHADER_DST_REF_LUMINANCE] = glGetUniformLocation(shader.program, "dstRefLuminance");
shader.uniformLocations[SHADER_SDR_SATURATION] = glGetUniformLocation(shader.program, "sdrSaturation");
@@ -1560,6 +1561,14 @@ static bool isSDR2HDR(const NColorManagement::SImageDescription& imageDescriptio
targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG);
}
+static bool isHDR2SDR(const NColorManagement::SImageDescription& imageDescription, const NColorManagement::SImageDescription& targetImageDescription) {
+ // might be too strict
+ return (imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_ST2084_PQ ||
+ imageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_HLG) &&
+ (targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_SRGB ||
+ targetImageDescription.transferFunction == NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22);
+}
+
void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SImageDescription& imageDescription,
const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
@@ -1585,16 +1594,22 @@ void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SI
shader.setUniformMatrix4x2fv(SHADER_TARGET_PRIMARIES, 1, false, glTargetPrimaries);
const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription, targetImageDescription);
+ const bool needsHDRmod = !needsSDRmod && isHDR2SDR(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_SRC_REF_LUMINANCE, imageDescription.getTFRefLuminance(-1));
+ shader.setUniformFloat(SHADER_DST_REF_LUMINANCE, targetImageDescription.getTFRefLuminance(-1));
+
+ const float maxLuminance =
+ needsHDRmod ? imageDescription.getTFMaxLuminance(-1) : (imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference);
+ shader.setUniformFloat(SHADER_MAX_LUMINANCE,
+ maxLuminance * targetImageDescription.luminances.reference /
+ (needsHDRmod ? imageDescription.getTFRefLuminance(-1) : 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, 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());
@@ -1710,11 +1725,8 @@ void CHyprOpenGLImpl::renderTextureInternal(SP<CTexture> tex, const CBox& box, c
if (shader == &m_shaders->m_shCM) {
shader->setUniformInt(SHADER_TEX_TYPE, texType);
if (data.cmBackToSRGB) {
- // revert luma changes to avoid black screenshots.
- // this will likely not be 1:1, and might cause screenshots to be too bright, but it's better than pitch black.
- imageDescription.luminances = {};
- static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
- auto chosenSdrEotf = *PSDREOTF > 0 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
+ static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
+ auto chosenSdrEotf = *PSDREOTF > 0 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
passCMUniforms(*shader, imageDescription, NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}, true, -1, -1);
} else
passCMUniforms(*shader, imageDescription);
diff --git a/src/render/Shader.hpp b/src/render/Shader.hpp
index 4f545642..50ff5889 100644
--- a/src/render/Shader.hpp
+++ b/src/render/Shader.hpp
@@ -16,6 +16,7 @@ enum eShaderUniform : uint8_t {
SHADER_DST_TF_RANGE,
SHADER_TARGET_PRIMARIES,
SHADER_MAX_LUMINANCE,
+ SHADER_SRC_REF_LUMINANCE,
SHADER_DST_MAX_LUMINANCE,
SHADER_DST_REF_LUMINANCE,
SHADER_SDR_SATURATION,
diff --git a/src/render/shaders/glsl/CM.glsl b/src/render/shaders/glsl/CM.glsl
index 0e79aab0..362d7cfb 100644
--- a/src/render/shaders/glsl/CM.glsl
+++ b/src/render/shaders/glsl/CM.glsl
@@ -2,6 +2,7 @@ uniform vec2 srcTFRange;
uniform vec2 dstTFRange;
uniform float maxLuminance;
+uniform float srcRefLuminance;
uniform float dstMaxLuminance;
uniform float dstRefLuminance;
uniform float sdrSaturation;
@@ -387,24 +388,17 @@ vec4 tonemap(vec4 color, mat3 dstXYZ) {
PQ_INV_M1
) * HDR_MAX_LUMINANCE;
- float srcScale = maxLuminance / dstRefLuminance;
- float dstScale = dstMaxLuminance / dstRefLuminance;
+ 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 minScale = min(srcScale, 1.5);
- float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale);
- float refLuminance = dstRefLuminance * dimming;
+ // scale src to dst reference
+ float refScale = dstRefLuminance / srcRefLuminance;
- float low = min(luminance * dimming, refLuminance);
- float highlight = clamp((luminance / dstRefLuminance - 1.0) / (srcScale - 1.0), 0.0, 1.0);
- float high = log(highlight * (M_E - 1.0) + 1.0) * (dstMaxLuminance - refLuminance);
- luminance = low + high;
-
- E = pow(clamp(ICtCp[0], 0.0, 1.0), PQ_M1);
- ICtCp[0] = pow(
- (PQ_C1 + PQ_C2 * E) / (1.0 + PQ_C3 * E),
- PQ_M2
- ) / HDR_MAX_LUMINANCE;
- return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE, color[3]);
+ return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]);
}
vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat4x2 dstPrimaries) {
diff --git a/src/render/shaders/glsl/blurprepare.frag b/src/render/shaders/glsl/blurprepare.frag
index 548289c3..6b9809f8 100644
--- a/src/render/shaders/glsl/blurprepare.frag
+++ b/src/render/shaders/glsl/blurprepare.frag
@@ -28,7 +28,7 @@ void main() {
pixColor.rgb /= sdrBrightnessMultiplier;
}
pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF);
- pixColor = toNit(pixColor, srcTFRange);
+ pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance));
pixColor = fromLinearNit(pixColor, targetTF, dstTFRange);
}