mirror of
https://github.com/hyprwm/Hyprland
synced 2025-12-20 02:30:03 +01:00
renderer/cm: higher-quality tonemapping (#12204)
This commit is contained in:
parent
ca99e8228c
commit
532ca053d6
5 changed files with 53 additions and 25 deletions
|
|
@ -6,8 +6,10 @@
|
|||
|
||||
#define SDR_MIN_LUMINANCE 0.2
|
||||
#define SDR_MAX_LUMINANCE 80.0
|
||||
#define SDR_REF_LUMINANCE 80.0
|
||||
#define HDR_MIN_LUMINANCE 0.005
|
||||
#define HDR_MAX_LUMINANCE 10000.0
|
||||
#define HDR_REF_LUMINANCE 203.0
|
||||
#define HLG_MAX_LUMINANCE 1000.0
|
||||
|
||||
namespace NColorManagement {
|
||||
|
|
@ -229,6 +231,25 @@ namespace NColorManagement {
|
|||
}
|
||||
};
|
||||
|
||||
float getTFRefLuminance(int sdrRefLuminance = -1) const {
|
||||
switch (transferFunction) {
|
||||
case CM_TRANSFER_FUNCTION_EXT_LINEAR:
|
||||
case CM_TRANSFER_FUNCTION_ST2084_PQ:
|
||||
case CM_TRANSFER_FUNCTION_HLG: return HDR_REF_LUMINANCE;
|
||||
case CM_TRANSFER_FUNCTION_GAMMA22:
|
||||
case CM_TRANSFER_FUNCTION_GAMMA28:
|
||||
case CM_TRANSFER_FUNCTION_BT1886:
|
||||
case CM_TRANSFER_FUNCTION_ST240:
|
||||
case CM_TRANSFER_FUNCTION_LOG_100:
|
||||
case CM_TRANSFER_FUNCTION_LOG_316:
|
||||
case CM_TRANSFER_FUNCTION_XVYCC:
|
||||
case CM_TRANSFER_FUNCTION_EXT_SRGB:
|
||||
case CM_TRANSFER_FUNCTION_ST428:
|
||||
case CM_TRANSFER_FUNCTION_SRGB:
|
||||
default: return sdrRefLuminance >= 0 ? sdrRefLuminance : SDR_REF_LUMINANCE;
|
||||
}
|
||||
};
|
||||
|
||||
uint32_t findId() const;
|
||||
uint32_t getId() const;
|
||||
uint32_t updateId();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue