1
mirror of https://github.com/mpv-player/mpv synced 2024-11-18 21:16:10 +01:00

vo_gpu: simplify and correct color scale handling

The primary need for this change is the fact that the OOTF was
incorrectly scaled, due to the fact that the application of the OOTF can
itself change the required normalization peak. (Plus, an oversight in
pass_inverse_ootf meant we forgot to normalize at the end of it)

The linearize/delinearize functions still normalize the scale since it's
used in a number of places throughout gpu/video.c, but the color
management function now converts to absolute scale right away, instead
of in an awkward way inside the tone mapping branch. The OOTF functions
now work in absolute scale only.

In addition, minor changes have been made to the way normalization is
handled for tone mapping - we now divide out the dst_peak *after* peak
detection, in order to make the scale of the peak detection buffer
consistent even if the dst_peak were to (hypothetically) change
mid-stream. In theory, we could also do this for desaturation, but doing
the desaturation before tone mapping has the advantage of preserving
much more brightness than the other way around - and even mid-stream
changes are not that drastic here.

Finally, some preparation work has been done for allowing the user to
customize the `dst.sig_peak` in the future.
This commit is contained in:
Niklas Haas 2018-02-14 14:14:48 +01:00 committed by Jan Ekström
parent 5d6fb5267d
commit b9e7478760
3 changed files with 40 additions and 33 deletions

View File

@ -2411,6 +2411,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
if (gl_video_get_lut3d(p, prim_orig, trc_orig)) {
dst.primaries = prim_orig;
dst.gamma = trc_orig;
assert(dst.primaries && dst.gamma);
}
}
@ -2445,6 +2446,11 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
dst.gamma = MP_CSP_TRC_GAMMA22;
}
// For now, just infer the dst sig peak from the gamma function always.
// In theory, we could allow users to configure this or detect it from the
// ICC profile, but avoid the complexity for now.
dst.sig_peak = mp_trc_nom_peak(dst.gamma);
bool detect_peak = p->opts.compute_hdr_peak >= 0 && mp_trc_is_hdr(src.gamma);
if (detect_peak && !p->hdr_peak_ssbo) {
struct {

View File

@ -334,6 +334,10 @@ static const float SLOG_A = 0.432699,
// Linearize (expand), given a TRC as input. In essence, this is the ITU-R
// EOTF, calculated on an idealized (reference) monitor with a white point of
// MP_REF_WHITE and infinite contrast.
//
// These functions always output to a normalized scale of [0,1], for
// convenience of the video.c code that calls it. To get the values in an
// absolute scale, multiply the result by `mp_trc_nom_peak(trc)`
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
{
if (trc == MP_CSP_TRC_LINEAR)
@ -417,6 +421,8 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
// Delinearize (compress), given a TRC as output. This corresponds to the
// inverse EOTF (not the OETF) in ITU-R terminology, again assuming a
// reference monitor.
//
// Like pass_linearize, this functions ingests values on an normalized scale
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
{
if (trc == MP_CSP_TRC_LINEAR)
@ -488,15 +494,13 @@ 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.
// The extra peak parameter is used to scale the values before and after
// the OOTF, and can be inferred using mp_trc_nom_peak
void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
// Assumes absolute scale values.
static void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
{
if (light == MP_CSP_LIGHT_DISPLAY)
return;
GLSLF("// apply ootf\n");
GLSLF("color.rgb *= vec3(%f);\n", peak);
switch (light)
{
@ -521,18 +525,15 @@ void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
default:
abort();
}
GLSLF("color.rgb *= vec3(1.0/%f);\n", peak);
}
// Inverse of the function pass_ootf, for completeness' sake.
void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak)
static void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light)
{
if (light == MP_CSP_LIGHT_DISPLAY)
return;
GLSLF("// apply inverse ootf\n");
GLSLF("color.rgb *= vec3(%f);\n", peak);
switch (light)
{
@ -635,7 +636,7 @@ static void hdr_update_peak(struct gl_shader_cache *sc)
// Tone map from a known peak brightness to the range [0,1]. If ref_peak
// is 0, we will use peak detection instead
static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
float src_peak, float dst_range,
float src_peak, float dst_peak,
enum tone_mapping algo, float param, float desat)
{
GLSLF("// HDR tone mapping\n");
@ -647,18 +648,13 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
GLSLF("float sig_peak = %f;\n", src_peak);
GLSLF("float sig_avg = %f;\n", sdr_avg);
// Rescale the variables in order to bring it into a representation where
// 1.0 represents the dst_peak. This is because all of the tone mapping
// algorithms are defined in such a way that they map to the range [0.0, 1.0].
if (dst_range > 1.0) {
GLSLF("sig *= %f;\n", 1.0 / dst_range);
GLSLF("sig_peak *= %f;\n", 1.0 / dst_range);
}
// Desaturate the color using a coefficient dependent on the signal
// Do this before peak detection in order to try and reclaim as much
// dynamic range as possible.
if (desat > 0) {
float base = 0.18 * dst_peak;
GLSL(float luma = dot(dst_luma, color.rgb);)
GLSL(float coeff = max(sig - 0.18, 1e-6) / max(sig, 1e-6););
GLSLF("float coeff = max(sig - %f, 1e-6) / max(sig, 1e-6);\n", base);
GLSLF("coeff = pow(coeff, %f);\n", 10.0 / desat);
GLSL(color.rgb = mix(color.rgb, vec3(luma), coeff);)
GLSL(sig = mix(sig, luma, coeff);) // also make sure to update `sig`
@ -667,6 +663,14 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
if (detect_peak)
hdr_update_peak(sc);
// Rescale the variables in order to bring it into a representation where
// 1.0 represents the dst_peak. This is because all of the tone mapping
// algorithms are defined in such a way that they map to the range [0.0, 1.0].
if (dst_peak > 1.0) {
GLSLF("sig *= %f;\n", 1.0 / dst_peak);
GLSLF("sig_peak *= %f;\n", 1.0 / dst_peak);
}
GLSL(float sig_orig = sig;)
GLSLF("float slope = min(1.0, %f / sig_avg);\n", sdr_avg);
GLSL(sig *= slope;)
@ -728,7 +732,7 @@ static void pass_tone_map(struct gl_shader_cache *sc, bool detect_peak,
// Apply the computed scale factor to the color, linearly to prevent
// discoloration
GLSL(sig = min(sig, 1.0);)
GLSL(color.rgb *= sig / sig_orig;)
GLSL(color.rgb *= vec3(sig / sig_orig);)
}
// Map colors from one source space to another. These source spaces must be
@ -746,10 +750,6 @@ void pass_color_map(struct gl_shader_cache *sc,
{
GLSLF("// color mapping\n");
// Compute the highest encodable level
float src_range = mp_trc_nom_peak(src.gamma),
dst_range = mp_trc_nom_peak(dst.gamma);
// Some operations need access to the video's luma coefficients, so make
// them available
float rgb2xyz[3][3];
@ -763,8 +763,7 @@ void pass_color_map(struct gl_shader_cache *sc,
// operations needs it
bool need_gamma = src.gamma != dst.gamma ||
src.primaries != dst.primaries ||
src_range != dst_range ||
src.sig_peak > dst_range ||
src.sig_peak > dst.sig_peak ||
src.light != dst.light;
if (need_gamma && !is_linear) {
@ -773,8 +772,11 @@ void pass_color_map(struct gl_shader_cache *sc,
is_linear = true;
}
// Pre-scale the incoming values into an absolute scale
GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(src.gamma));
if (src.light != dst.light)
pass_ootf(sc, src.light, src_range);
pass_ootf(sc, src.light);
// Adapt to the right colorspace if necessary
if (src.primaries != dst.primaries) {
@ -791,15 +793,16 @@ void pass_color_map(struct gl_shader_cache *sc,
// Tone map to prevent clipping when the source signal peak exceeds the
// encodable range or we've reduced the gamut
if (src.sig_peak > dst_range) {
GLSLF("color.rgb *= vec3(%f);\n", src_range);
pass_tone_map(sc, detect_peak, src.sig_peak, dst_range, algo,
if (src.sig_peak > dst.sig_peak) {
pass_tone_map(sc, detect_peak, src.sig_peak, dst.sig_peak, algo,
tone_mapping_param, tone_mapping_desat);
GLSLF("color.rgb *= vec3(%f);\n", 1.0 / dst_range);
}
if (src.light != dst.light)
pass_inverse_ootf(sc, dst.light, dst_range);
pass_inverse_ootf(sc, dst.light);
// Post-scale the outgoing values from absolute scale to normalized
GLSLF("color.rgb *= vec3(%f);\n", 1.0 / mp_trc_nom_peak(dst.gamma));
// Warn for remaining out-of-gamut colors is enabled
if (gamut_warning) {

View File

@ -39,8 +39,6 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler,
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
void pass_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
void pass_inverse_ootf(struct gl_shader_cache *sc, enum mp_csp_light light, float peak);
void pass_color_map(struct gl_shader_cache *sc,
struct mp_colorspace src, struct mp_colorspace dst,