mirror of https://github.com/mpv-player/mpv
vo_opengl: refactor HDR mechanism
Instead of doing HDR tone mapping on an ad-hoc basis inside pass_colormanage, the reference peak of an image is now part of the image params (alongside colorspace, gamma, etc.) and tone mapping is done whenever peak_src != peak_dst. To get sensible behavior when mixing HDR and SDR content and displays, target-brightness is a generic filler for "the assumed brightness of SDR content". This gets rid of the weird display_scaled hack, sets the framework for multiple HDR functions with difference reference peaks, and allows us to (in a future commit) autodetect the right source peak from the HDR metadata. (Apart from metadata, the source peak can also be controlled via vf_format. For HDR content this adjusts the overall image brightness, for SDR content it's like simulating a different exposure)
This commit is contained in:
parent
098ff4174c
commit
45c3e0f0d0
|
@ -312,6 +312,14 @@ Available filters are:
|
|||
:prophoto: ProPhoto RGB (ROMM) curve
|
||||
:st2084: SMPTE ST2084 (HDR) curve
|
||||
|
||||
``<peak>``
|
||||
Reference peak illumination for the video file. This is mostly
|
||||
interesting for HDR, but it can also be used tone map SDR content
|
||||
to a darker or brighter exposure.
|
||||
|
||||
The default of 0.0 will default to the display's reference brightness
|
||||
for SDR and the source's reference brightness for HDR.
|
||||
|
||||
``<stereo-in>``
|
||||
Set the stereo mode the video is assumed to be encoded in. Takes the
|
||||
same values as the ``--video-stereo-mode`` option.
|
||||
|
|
|
@ -1061,9 +1061,10 @@ Available video output drivers are:
|
|||
|
||||
``target-brightness=<1..100000>``
|
||||
Specifies the display's approximate brightness in cd/m^2. When playing
|
||||
HDR content, video colors will be tone mapped to this target brightness
|
||||
using the algorithm specified by ``hdr-tone-mapping``. The default of
|
||||
250 cd/m^2 corresponds to a typical consumer display.
|
||||
HDR content on a SDR display (or SDR content on an HDR display), video
|
||||
colors will be tone mapped to this target brightness using the
|
||||
algorithm specified by ``hdr-tone-mapping``. The default of 250 cd/m^2
|
||||
corresponds to a typical consumer display.
|
||||
|
||||
``hdr-tone-mapping=<value>``
|
||||
Specifies the algorithm used for tone-mapping HDR images onto the
|
||||
|
|
|
@ -38,6 +38,7 @@ struct vf_priv_s {
|
|||
int colorlevels;
|
||||
int primaries;
|
||||
int gamma;
|
||||
float peak;
|
||||
int chroma_location;
|
||||
int stereo_in;
|
||||
int stereo_out;
|
||||
|
@ -94,6 +95,8 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
|
|||
out->primaries = p->primaries;
|
||||
if (p->gamma)
|
||||
out->gamma = p->gamma;
|
||||
if (p->peak)
|
||||
out->peak = p->peak;
|
||||
if (p->chroma_location)
|
||||
out->chroma_location = p->chroma_location;
|
||||
if (p->stereo_in)
|
||||
|
@ -142,6 +145,7 @@ static const m_option_t vf_opts_fields[] = {
|
|||
OPT_CHOICE_C("colorlevels", colorlevels, 0, mp_csp_levels_names),
|
||||
OPT_CHOICE_C("primaries", primaries, 0, mp_csp_prim_names),
|
||||
OPT_CHOICE_C("gamma", gamma, 0, mp_csp_trc_names),
|
||||
OPT_FLOAT("peak", peak, 0),
|
||||
OPT_CHOICE_C("chroma-location", chroma_location, 0, mp_chroma_names),
|
||||
OPT_CHOICE_C("stereo-in", stereo_in, 0, mp_stereo3d_names),
|
||||
OPT_CHOICE_C("stereo-out", stereo_out, 0, mp_stereo3d_names),
|
||||
|
|
|
@ -568,6 +568,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1,
|
|||
p1->colorlevels == p2->colorlevels &&
|
||||
p1->primaries == p2->primaries &&
|
||||
p1->gamma == p2->gamma &&
|
||||
p1->peak == p2->peak &&
|
||||
p1->chroma_location == p2->chroma_location &&
|
||||
p1->rotate == p2->rotate &&
|
||||
p1->stereo_in == p2->stereo_in &&
|
||||
|
@ -662,6 +663,12 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
|
|||
params->primaries = MP_CSP_PRIM_AUTO;
|
||||
params->gamma = MP_CSP_TRC_AUTO;
|
||||
}
|
||||
|
||||
// Guess the reference peak (independent of the colorspace)
|
||||
if (params->gamma == MP_CSP_TRC_SMPTE_ST2084) {
|
||||
if (!params->peak)
|
||||
params->peak = 10000; // As per the spec
|
||||
}
|
||||
}
|
||||
|
||||
// Copy properties and data of the AVFrame into the mp_image, without taking
|
||||
|
|
|
@ -47,6 +47,7 @@ struct mp_image_params {
|
|||
enum mp_csp_levels colorlevels;
|
||||
enum mp_csp_prim primaries;
|
||||
enum mp_csp_trc gamma;
|
||||
float peak; // 0 = auto/unknown
|
||||
enum mp_chroma_location chroma_location;
|
||||
// The image should be rotated clockwise (0-359 degrees).
|
||||
int rotate;
|
||||
|
|
|
@ -2198,13 +2198,14 @@ static void pass_scale_main(struct gl_video *p)
|
|||
|
||||
// Adapts the colors from the given color space to the display device's native
|
||||
// gamut.
|
||||
static void pass_colormanage(struct gl_video *p, bool display_scaled,
|
||||
static void pass_colormanage(struct gl_video *p, float peak_src,
|
||||
enum mp_csp_prim prim_src,
|
||||
enum mp_csp_trc trc_src)
|
||||
{
|
||||
GLSLF("// color management\n");
|
||||
enum mp_csp_trc trc_dst = p->opts.target_trc;
|
||||
enum mp_csp_prim prim_dst = p->opts.target_prim;
|
||||
float peak_dst = p->opts.target_brightness;
|
||||
|
||||
if (p->use_lut_3d) {
|
||||
// The 3DLUT is always generated against the original source space
|
||||
|
@ -2241,21 +2242,31 @@ static void pass_colormanage(struct gl_video *p, bool display_scaled,
|
|||
if (trc_dst == MP_CSP_TRC_LINEAR || trc_dst == MP_CSP_TRC_SMPTE_ST2084)
|
||||
trc_dst = MP_CSP_TRC_GAMMA22;
|
||||
}
|
||||
if (!peak_src) {
|
||||
// If the source has no information known, it's display-referred
|
||||
// (and should be treated relative to the specified desired peak_dst)
|
||||
peak_src = peak_dst;
|
||||
}
|
||||
|
||||
bool need_gamma = trc_src != trc_dst || prim_src != prim_dst;
|
||||
// All operations from here on require linear light as a starting point,
|
||||
// so we linearize even if trc_src == trc_dst when one of the other
|
||||
// operations needs it
|
||||
bool need_gamma = trc_src != trc_dst || prim_src != prim_dst ||
|
||||
peak_src != peak_dst;
|
||||
if (need_gamma)
|
||||
pass_linearize(p->sc, trc_src);
|
||||
|
||||
// For HDR, the assumption of reference brightness = display brightness
|
||||
// is discontinued. Instead, we have to tone map the brightness to
|
||||
// the display using some algorithm.
|
||||
if (p->image_params.gamma == MP_CSP_TRC_SMPTE_ST2084 &&
|
||||
trc_dst != MP_CSP_TRC_SMPTE_ST2084 && !display_scaled)
|
||||
// Adapt and tone map for a different reference peak brightness
|
||||
if (peak_src != peak_dst)
|
||||
{
|
||||
GLSLF("// HDR tone mapping\n");
|
||||
int reference_brightness = 10000; // As per SMPTE ST.2084
|
||||
pass_tone_map(p->sc, reference_brightness, p->opts.target_brightness,
|
||||
p->opts.hdr_tone_mapping, p->opts.tone_mapping_param);
|
||||
float rel_peak = peak_src / peak_dst;
|
||||
// Normalize such that 1 is the target brightness (and values above
|
||||
// 1 are out of range)
|
||||
GLSLF("color.rgb *= vec3(%f);\n", rel_peak);
|
||||
// Tone map back down to the range [0,1]
|
||||
pass_tone_map(p->sc, rel_peak, p->opts.hdr_tone_mapping,
|
||||
p->opts.tone_mapping_param);
|
||||
}
|
||||
|
||||
// Adapt to the right colorspace if necessary
|
||||
|
@ -2268,8 +2279,14 @@ static void pass_colormanage(struct gl_video *p, bool display_scaled,
|
|||
GLSL(color.rgb = cms_matrix * color.rgb;)
|
||||
}
|
||||
|
||||
if (need_gamma)
|
||||
if (need_gamma) {
|
||||
// If the target encoding function has a fixed peak, we need to
|
||||
// un-normalize back to the encoding signal range
|
||||
if (trc_dst == MP_CSP_TRC_SMPTE_ST2084)
|
||||
GLSLF("color.rgb *= vec3(%f);\n", peak_dst / 10000);
|
||||
|
||||
pass_delinearize(p->sc, trc_dst);
|
||||
}
|
||||
|
||||
if (p->use_lut_3d) {
|
||||
gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT);
|
||||
|
@ -2410,9 +2427,12 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
|
|||
default:
|
||||
abort();
|
||||
}
|
||||
// Subtitle color management, they're assumed to be sRGB by default
|
||||
if (cms)
|
||||
pass_colormanage(p, true, MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB);
|
||||
// Subtitle color management, they're assumed to be display-referred
|
||||
// sRGB by default
|
||||
if (cms) {
|
||||
pass_colormanage(p, p->opts.target_brightness,
|
||||
MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB);
|
||||
}
|
||||
gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd));
|
||||
gl_sc_gen_shader_and_reset(p->sc);
|
||||
mpgl_osd_draw_part(p->osd, vp_w, vp_h, n);
|
||||
|
@ -2533,7 +2553,7 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo)
|
|||
GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));)
|
||||
}
|
||||
|
||||
pass_colormanage(p, false, p->image_params.primaries,
|
||||
pass_colormanage(p, p->image_params.peak, p->image_params.primaries,
|
||||
p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.gamma);
|
||||
|
||||
// Draw checkerboard pattern to indicate transparency
|
||||
|
|
|
@ -313,15 +313,10 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
|
|||
}
|
||||
}
|
||||
|
||||
// Tone map from one brightness to another
|
||||
void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
|
||||
// Tone map from a known peak brightness to the range [0,1]
|
||||
void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
enum tone_mapping algo, float param)
|
||||
{
|
||||
// First we renormalize to the output range
|
||||
float scale = peak_src / peak_dst;
|
||||
GLSLF("color.rgb *= vec3(%f);\n", scale);
|
||||
|
||||
// Then we use some algorithm to map back to [0,1]
|
||||
switch (algo) {
|
||||
case TONE_MAPPING_CLIP:
|
||||
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
|
||||
|
@ -331,7 +326,7 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
|
|||
float contrast = isnan(param) ? 0.5 : param,
|
||||
offset = (1.0 - contrast) / contrast;
|
||||
GLSLF("color.rgb = color.rgb / (color.rgb + vec3(%f));\n", offset);
|
||||
GLSLF("color.rgb *= vec3(%f);\n", (scale + offset) / scale);
|
||||
GLSLF("color.rgb *= vec3(%f);\n", (peak + offset) / peak);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -342,20 +337,20 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
|
|||
A, C*B, D*E, A, B, D*F, E/F);
|
||||
GLSLHF("}\n");
|
||||
|
||||
GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", scale);
|
||||
GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", peak);
|
||||
break;
|
||||
}
|
||||
|
||||
case TONE_MAPPING_GAMMA: {
|
||||
float gamma = isnan(param) ? 1.8 : param;
|
||||
GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n",
|
||||
scale, 1.0/gamma);
|
||||
peak, 1.0/gamma);
|
||||
break;
|
||||
}
|
||||
|
||||
case TONE_MAPPING_LINEAR: {
|
||||
float coeff = isnan(param) ? 1.0 : param;
|
||||
GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / scale);
|
||||
GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / peak);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ 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_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst,
|
||||
void pass_tone_map(struct gl_shader_cache *sc, float peak,
|
||||
enum tone_mapping algo, float param);
|
||||
|
||||
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
|
||||
|
|
Loading…
Reference in New Issue