mirror of https://github.com/mpv-player/mpv
vo_gpu: correctly parametrize the HLG OOTF by the display peak
The HLG OOTF is defined as a one-parameter family of OOTFs depending on the display's peak luminance. With the preceding change to OOTF scale and handling, we no longer have any issues with outputting values in whatever signal range we need. So as a result, it's easy for us to support a tunable OOTF which may (drastically) alter the display brightness. In fact, this is also the only correct way to do it, because the HLG appearance depends strongly on the OOTF configuration. For the OOTF, we consult the mastering display's tagging (via src.sig_peak). For the inverse OOTF, we consult the output display's target peak.
This commit is contained in:
parent
b9e7478760
commit
1f881eca65
|
@ -494,8 +494,10 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the OOTF mapping from a given light type to display-referred light.
|
// Apply the OOTF mapping from a given light type to display-referred light.
|
||||||
// Assumes absolute scale values.
|
// Assumes absolute scale values. `peak` is used to tune the OOTF where
|
||||||
static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
|
// applicable (currently only HLG).
|
||||||
|
static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light,
|
||||||
|
float peak)
|
||||||
{
|
{
|
||||||
if (light == MP_CSP_LIGHT_DISPLAY)
|
if (light == MP_CSP_LIGHT_DISPLAY)
|
||||||
return;
|
return;
|
||||||
|
@ -504,12 +506,13 @@ static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
|
||||||
|
|
||||||
switch (light)
|
switch (light)
|
||||||
{
|
{
|
||||||
case MP_CSP_LIGHT_SCENE_HLG:
|
case MP_CSP_LIGHT_SCENE_HLG: {
|
||||||
// HLG OOTF from BT.2100, assuming a reference display with a
|
// HLG OOTF from BT.2100, scaled to the chosen display peak
|
||||||
// peak of 1000 cd/m² -> gamma = 1.2
|
float gamma = MPMAX(1.0, 1.2 + 0.42 * log10(peak * MP_REF_WHITE / 1000.0));
|
||||||
GLSLF("color.rgb *= vec3(%f * pow(dot(src_luma, color.rgb), 0.2));\n",
|
GLSLF("color.rgb *= vec3(%f * pow(dot(src_luma, color.rgb), %f));\n",
|
||||||
(1000 / MP_REF_WHITE) / pow(12, 1.2));
|
peak / pow(12, gamma), gamma - 1.0);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case MP_CSP_LIGHT_SCENE_709_1886:
|
case MP_CSP_LIGHT_SCENE_709_1886:
|
||||||
// This OOTF is defined by encoding the result as 709 and then decoding
|
// This OOTF is defined by encoding the result as 709 and then decoding
|
||||||
// it as 1886; although this is called 709_1886 we actually use the
|
// it as 1886; although this is called 709_1886 we actually use the
|
||||||
|
@ -528,7 +531,8 @@ static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inverse of the function pass_ootf, for completeness' sake.
|
// Inverse of the function pass_ootf, for completeness' sake.
|
||||||
static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
|
static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light,
|
||||||
|
float peak)
|
||||||
{
|
{
|
||||||
if (light == MP_CSP_LIGHT_DISPLAY)
|
if (light == MP_CSP_LIGHT_DISPLAY)
|
||||||
return;
|
return;
|
||||||
|
@ -537,10 +541,13 @@ static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light ligh
|
||||||
|
|
||||||
switch (light)
|
switch (light)
|
||||||
{
|
{
|
||||||
case MP_CSP_LIGHT_SCENE_HLG:
|
case MP_CSP_LIGHT_SCENE_HLG: {
|
||||||
GLSLF("color.rgb *= vec3(1.0/%f);\n", (1000 / MP_REF_WHITE) / pow(12, 1.2));
|
float gamma = MPMAX(1.0, 1.2 + 0.42 * log10(peak * MP_REF_WHITE / 1000.0));
|
||||||
GLSL(color.rgb /= vec3(max(1e-6, pow(dot(src_luma, color.rgb), 0.2/1.2)));)
|
GLSLF("color.rgb *= vec3(1.0/%f);\n", peak / pow(12, gamma));
|
||||||
|
GLSLF("color.rgb /= vec3(max(1e-6, pow(dot(src_luma, color.rgb), %f)));\n",
|
||||||
|
(gamma - 1.0) / gamma);
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
case MP_CSP_LIGHT_SCENE_709_1886:
|
case MP_CSP_LIGHT_SCENE_709_1886:
|
||||||
GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
|
GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));)
|
||||||
GLSL(color.rgb = mix(color.rgb * vec3(1.0/4.5),
|
GLSL(color.rgb = mix(color.rgb * vec3(1.0/4.5),
|
||||||
|
@ -758,15 +765,19 @@ void pass_color_map(struct gl_shader_cache *sc,
|
||||||
mp_get_rgb2xyz_matrix(mp_get_csp_primaries(dst.primaries), rgb2xyz);
|
mp_get_rgb2xyz_matrix(mp_get_csp_primaries(dst.primaries), rgb2xyz);
|
||||||
gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz[1]);
|
gl_sc_uniform_vec3(sc, "dst_luma", rgb2xyz[1]);
|
||||||
|
|
||||||
|
bool need_ootf = src.light != dst.light;
|
||||||
|
if (src.light == MP_CSP_LIGHT_SCENE_HLG && src.sig_peak != dst.sig_peak)
|
||||||
|
need_ootf = true;
|
||||||
|
|
||||||
// All operations from here on require linear light as a starting point,
|
// All operations from here on require linear light as a starting point,
|
||||||
// so we linearize even if src.gamma == dst.gamma when one of the other
|
// so we linearize even if src.gamma == dst.gamma when one of the other
|
||||||
// operations needs it
|
// operations needs it
|
||||||
bool need_gamma = src.gamma != dst.gamma ||
|
bool need_linear = src.gamma != dst.gamma ||
|
||||||
src.primaries != dst.primaries ||
|
src.primaries != dst.primaries ||
|
||||||
src.sig_peak > dst.sig_peak ||
|
src.sig_peak > dst.sig_peak ||
|
||||||
src.light != dst.light;
|
need_ootf;
|
||||||
|
|
||||||
if (need_gamma && !is_linear) {
|
if (need_linear && !is_linear) {
|
||||||
// We also pull it up so that 1.0 is the reference white
|
// We also pull it up so that 1.0 is the reference white
|
||||||
pass_linearize(sc, src.gamma);
|
pass_linearize(sc, src.gamma);
|
||||||
is_linear = true;
|
is_linear = true;
|
||||||
|
@ -775,8 +786,8 @@ void pass_color_map(struct gl_shader_cache *sc,
|
||||||
// Pre-scale the incoming values into an absolute scale
|
// Pre-scale the incoming values into an absolute scale
|
||||||
GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.gamma));
|
GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.gamma));
|
||||||
|
|
||||||
if (src.light != dst.light)
|
if (need_ootf)
|
||||||
pass_ootf(sc, src.light);
|
pass_ootf(sc, src.light, src.sig_peak);
|
||||||
|
|
||||||
// Adapt to the right colorspace if necessary
|
// Adapt to the right colorspace if necessary
|
||||||
if (src.primaries != dst.primaries) {
|
if (src.primaries != dst.primaries) {
|
||||||
|
@ -798,8 +809,8 @@ void pass_color_map(struct gl_shader_cache *sc,
|
||||||
tone_mapping_param, tone_mapping_desat);
|
tone_mapping_param, tone_mapping_desat);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (src.light != dst.light)
|
if (need_ootf)
|
||||||
pass_inverse_ootf(sc, dst.light);
|
pass_inverse_ootf(sc, dst.light, dst.sig_peak);
|
||||||
|
|
||||||
// Post-scale the outgoing values from absolute scale to normalized
|
// Post-scale the outgoing values from absolute scale to normalized
|
||||||
GLSLF("color.rgb *= vec3(%f);\n", 1.0 / mp_trc_nom_peak(dst.gamma));
|
GLSLF("color.rgb *= vec3(%f);\n", 1.0 / mp_trc_nom_peak(dst.gamma));
|
||||||
|
|
Loading…
Reference in New Issue