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_MIN_LUMINANCE 0.2
|
||||||
#define SDR_MAX_LUMINANCE 80.0
|
#define SDR_MAX_LUMINANCE 80.0
|
||||||
|
#define SDR_REF_LUMINANCE 80.0
|
||||||
#define HDR_MIN_LUMINANCE 0.005
|
#define HDR_MIN_LUMINANCE 0.005
|
||||||
#define HDR_MAX_LUMINANCE 10000.0
|
#define HDR_MAX_LUMINANCE 10000.0
|
||||||
|
#define HDR_REF_LUMINANCE 203.0
|
||||||
#define HLG_MAX_LUMINANCE 1000.0
|
#define HLG_MAX_LUMINANCE 1000.0
|
||||||
|
|
||||||
namespace NColorManagement {
|
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 findId() const;
|
||||||
uint32_t getId() const;
|
uint32_t getId() const;
|
||||||
uint32_t updateId();
|
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_DST_TF_RANGE] = glGetUniformLocation(shader.program, "dstTFRange");
|
||||||
shader.uniformLocations[SHADER_TARGET_PRIMARIES] = glGetUniformLocation(shader.program, "targetPrimaries");
|
shader.uniformLocations[SHADER_TARGET_PRIMARIES] = glGetUniformLocation(shader.program, "targetPrimaries");
|
||||||
shader.uniformLocations[SHADER_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "maxLuminance");
|
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_MAX_LUMINANCE] = glGetUniformLocation(shader.program, "dstMaxLuminance");
|
||||||
shader.uniformLocations[SHADER_DST_REF_LUMINANCE] = glGetUniformLocation(shader.program, "dstRefLuminance");
|
shader.uniformLocations[SHADER_DST_REF_LUMINANCE] = glGetUniformLocation(shader.program, "dstRefLuminance");
|
||||||
shader.uniformLocations[SHADER_SDR_SATURATION] = glGetUniformLocation(shader.program, "sdrSaturation");
|
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);
|
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,
|
void CHyprOpenGLImpl::passCMUniforms(SShader& shader, const NColorManagement::SImageDescription& imageDescription,
|
||||||
const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {
|
const NColorManagement::SImageDescription& targetImageDescription, bool modifySDR, float sdrMinLuminance, int sdrMaxLuminance) {
|
||||||
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
|
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);
|
shader.setUniformMatrix4x2fv(SHADER_TARGET_PRIMARIES, 1, false, glTargetPrimaries);
|
||||||
|
|
||||||
const bool needsSDRmod = modifySDR && isSDR2HDR(imageDescription, targetImageDescription);
|
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),
|
shader.setUniformFloat2(SHADER_SRC_TF_RANGE, imageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),
|
||||||
imageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1));
|
imageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1));
|
||||||
shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),
|
shader.setUniformFloat2(SHADER_DST_TF_RANGE, targetImageDescription.getTFMinLuminance(needsSDRmod ? sdrMinLuminance : -1),
|
||||||
targetImageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1));
|
targetImageDescription.getTFMaxLuminance(needsSDRmod ? sdrMaxLuminance : -1));
|
||||||
|
|
||||||
const float maxLuminance = imageDescription.luminances.max > 0 ? imageDescription.luminances.max : imageDescription.luminances.reference;
|
shader.setUniformFloat(SHADER_SRC_REF_LUMINANCE, imageDescription.getTFRefLuminance(-1));
|
||||||
shader.setUniformFloat(SHADER_MAX_LUMINANCE, maxLuminance * targetImageDescription.luminances.reference / imageDescription.luminances.reference);
|
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_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_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);
|
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());
|
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) {
|
if (shader == &m_shaders->m_shCM) {
|
||||||
shader->setUniformInt(SHADER_TEX_TYPE, texType);
|
shader->setUniformInt(SHADER_TEX_TYPE, texType);
|
||||||
if (data.cmBackToSRGB) {
|
if (data.cmBackToSRGB) {
|
||||||
// revert luma changes to avoid black screenshots.
|
static auto PSDREOTF = CConfigValue<Hyprlang::INT>("render:cm_sdr_eotf");
|
||||||
// this will likely not be 1:1, and might cause screenshots to be too bright, but it's better than pitch black.
|
auto chosenSdrEotf = *PSDREOTF > 0 ? NColorManagement::CM_TRANSFER_FUNCTION_GAMMA22 : NColorManagement::CM_TRANSFER_FUNCTION_SRGB;
|
||||||
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;
|
|
||||||
passCMUniforms(*shader, imageDescription, NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}, true, -1, -1);
|
passCMUniforms(*shader, imageDescription, NColorManagement::SImageDescription{.transferFunction = chosenSdrEotf}, true, -1, -1);
|
||||||
} else
|
} else
|
||||||
passCMUniforms(*shader, imageDescription);
|
passCMUniforms(*shader, imageDescription);
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ enum eShaderUniform : uint8_t {
|
||||||
SHADER_DST_TF_RANGE,
|
SHADER_DST_TF_RANGE,
|
||||||
SHADER_TARGET_PRIMARIES,
|
SHADER_TARGET_PRIMARIES,
|
||||||
SHADER_MAX_LUMINANCE,
|
SHADER_MAX_LUMINANCE,
|
||||||
|
SHADER_SRC_REF_LUMINANCE,
|
||||||
SHADER_DST_MAX_LUMINANCE,
|
SHADER_DST_MAX_LUMINANCE,
|
||||||
SHADER_DST_REF_LUMINANCE,
|
SHADER_DST_REF_LUMINANCE,
|
||||||
SHADER_SDR_SATURATION,
|
SHADER_SDR_SATURATION,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ uniform vec2 srcTFRange;
|
||||||
uniform vec2 dstTFRange;
|
uniform vec2 dstTFRange;
|
||||||
|
|
||||||
uniform float maxLuminance;
|
uniform float maxLuminance;
|
||||||
|
uniform float srcRefLuminance;
|
||||||
uniform float dstMaxLuminance;
|
uniform float dstMaxLuminance;
|
||||||
uniform float dstRefLuminance;
|
uniform float dstRefLuminance;
|
||||||
uniform float sdrSaturation;
|
uniform float sdrSaturation;
|
||||||
|
|
@ -387,24 +388,17 @@ vec4 tonemap(vec4 color, mat3 dstXYZ) {
|
||||||
PQ_INV_M1
|
PQ_INV_M1
|
||||||
) * HDR_MAX_LUMINANCE;
|
) * HDR_MAX_LUMINANCE;
|
||||||
|
|
||||||
float srcScale = maxLuminance / dstRefLuminance;
|
float linearPart = min(luminance, dstRefLuminance);
|
||||||
float dstScale = dstMaxLuminance / 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);
|
// scale src to dst reference
|
||||||
float dimming = 1.0 / clamp(minScale / dstScale, 1.0, minScale);
|
float refScale = dstRefLuminance / srcRefLuminance;
|
||||||
float refLuminance = dstRefLuminance * dimming;
|
|
||||||
|
|
||||||
float low = min(luminance * dimming, refLuminance);
|
return vec4(fromLMS * toLinear(vec4(ICtCpPQInv * ICtCp, 1.0), CM_TRANSFER_FUNCTION_ST2084_PQ).rgb * HDR_MAX_LUMINANCE * refScale, color[3]);
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat4x2 dstPrimaries) {
|
vec4 doColorManagement(vec4 pixColor, int srcTF, int dstTF, mat4x2 dstPrimaries) {
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ void main() {
|
||||||
pixColor.rgb /= sdrBrightnessMultiplier;
|
pixColor.rgb /= sdrBrightnessMultiplier;
|
||||||
}
|
}
|
||||||
pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF);
|
pixColor.rgb = convertMatrix * toLinearRGB(pixColor.rgb, sourceTF);
|
||||||
pixColor = toNit(pixColor, srcTFRange);
|
pixColor = toNit(pixColor, vec2(srcTFRange[0], srcRefLuminance));
|
||||||
pixColor = fromLinearNit(pixColor, targetTF, dstTFRange);
|
pixColor = fromLinearNit(pixColor, targetTF, dstTFRange);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue