summaryrefslogtreecommitdiffstats
path: root/video/out/opengl/video.c
diff options
context:
space:
mode:
authorNiklas Haas <git@nand.wakku.to>2016-06-29 09:28:17 +0200
committerwm4 <wm4@nowhere>2016-07-03 19:42:52 +0200
commit923e3c7b20f0a238062b0ac538a751c6c363a8cb (patch)
treed86988c4fe3a603df877e7cf91216ccc20b09a27 /video/out/opengl/video.c
parentd81fb97f4587f73f62a760b99f686139f9b8d966 (diff)
downloadmpv-923e3c7b20f0a238062b0ac538a751c6c363a8cb.tar.bz2
mpv-923e3c7b20f0a238062b0ac538a751c6c363a8cb.tar.xz
vo_opengl: generalize HDR tone mapping mechanism
This involves multiple changes: 1. Brightness metadata is split into nominal peak and signal peak. For a quick and dirty explanation: nominal peak is the brightest value that your color space can represent (i.e. the brightness of an encoded 1.0), and signal peak is the brightest value that actually occurs in the video (i.e. the brightest thing that's displayed). 2. vo_opengl uses a new decision logic to figure out the right nom_peak and sig_peak for all situations. It also does a better job of picking the right target gamut/colorspace to use for the OSD. (Which still is and still should be treated as sRGB). This change in logic also fixes #3293 en passant. 3. Since it was growing rapidly, the logic for auto-guessing / inferring the right colorimetry configuration (in pass_colormanage) was split from the logic for actually performing the adaptation (now pass_color_map). Right now, the new logic doesn't do a whole lot since HDR metadata is still ignored (but not for long).
Diffstat (limited to 'video/out/opengl/video.c')
-rw-r--r--video/out/opengl/video.c130
1 files changed, 61 insertions, 69 deletions
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 59dd64cb65..a4cc6cfac8 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -2158,19 +2158,29 @@ 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, 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;
+// Adapts the colors to the right output color space. (Final pass during
+// rendering)
+// If OSD is true, ignore any changes that may have been made to the video
+// by previous passes (i.e. linear scaling)
+static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool osd)
+{
+ struct mp_colorspace ref = src;
+
+ if (p->use_linear && !osd)
+ src.gamma = MP_CSP_TRC_LINEAR;
+
+ // Figure out the target color space from the options, or auto-guess if
+ // none were set
+ struct mp_colorspace dst = {
+ .gamma = p->opts.target_trc,
+ .primaries = p->opts.target_prim,
+ .nom_peak = mp_csp_trc_nom_peak(p->opts.target_trc, p->opts.target_brightness),
+ };
if (p->use_lut_3d) {
- // The 3DLUT is always generated against the original source space
+ // The 3DLUT is always generated against the video's original source
+ // space, *not* the reference space. (To avoid having to regenerate
+ // the 3DLUT for the OSD on every frame)
enum mp_csp_prim prim_orig = p->image_params.color.primaries;
enum mp_csp_trc trc_orig = p->image_params.color.gamma;
@@ -2186,87 +2196,66 @@ static void pass_colormanage(struct gl_video *p, float peak_src,
}
if (gl_video_get_lut3d(p, prim_orig, trc_orig)) {
- prim_dst = prim_orig;
- trc_dst = trc_orig;
+ dst.primaries = prim_orig;
+ dst.gamma = trc_orig;
}
}
- if (prim_dst == MP_CSP_PRIM_AUTO) {
+ if (dst.primaries == MP_CSP_PRIM_AUTO) {
// The vast majority of people are on sRGB or BT.709 displays, so pick
// this as the default output color space.
- prim_dst = MP_CSP_PRIM_BT_709;
+ dst.primaries = MP_CSP_PRIM_BT_709;
- if (p->image_params.color.primaries == MP_CSP_PRIM_BT_601_525 ||
- p->image_params.color.primaries == MP_CSP_PRIM_BT_601_625)
+ if (ref.primaries == MP_CSP_PRIM_BT_601_525 ||
+ ref.primaries == MP_CSP_PRIM_BT_601_625)
{
// Since we auto-pick BT.601 and BT.709 based on the dimensions,
// combined with the fact that they're very similar to begin with,
// and to avoid confusing the average user, just don't adapt BT.601
// content automatically at all.
- prim_dst = p->image_params.color.primaries;
+ dst.primaries = ref.gamma;
}
}
- if (trc_dst == MP_CSP_TRC_AUTO) {
+ if (dst.gamma == MP_CSP_TRC_AUTO) {
// Most people seem to complain when the image is darker or brighter
// than what they're "used to", so just avoid changing the gamma
// altogether by default. The only exceptions to this rule apply to
// very unusual TRCs, which even hardcode technoluddites would probably
// not enjoy viewing unaltered.
- trc_dst = p->image_params.color.gamma;
+ dst.gamma = ref.gamma;
// Avoid outputting linear light or HDR content "by default". For these
// just pick gamma 2.2 as a default, since it's a good estimate for
// the response of typical displays
- if (trc_dst == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(trc_dst))
- trc_dst = MP_CSP_TRC_GAMMA22;
+ if (dst.gamma == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(dst.gamma))
+ dst.gamma = 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 * mp_csp_trc_rel_peak(p->image_params.color.gamma);
- }
-
- // 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);
-
- // Adapt and tone map for a different reference peak brightness
- if (peak_src != peak_dst)
- {
- GLSLF("// HDR tone mapping\n");
- 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);
+ // For the src peaks, the correct brightness metadata may be present for
+ // sig_peak, nom_peak, both, or neither. To handle everything in a generic
+ // way, it's important to never automatically infer a sig_peak that is
+ // below the nom_peak (since we don't know what bits the image contains,
+ // doing so would potentially badly clip). The only time in which this
+ // may be the case is when the mastering metadata explicitly says so, i.e.
+ // the sig_peak was already set. So to simplify the logic as much as
+ // possible, make sure the nom_peak is present and correct first, and just
+ // set sig_peak = nom_peak if missing.
+ if (!src.nom_peak) {
+ // For display-referred colorspaces, we treat it as relative to
+ // target_brightness
+ src.nom_peak = mp_csp_trc_nom_peak(src.gamma, p->opts.target_brightness);
}
- // Adapt to the right colorspace if necessary
- if (prim_src != prim_dst) {
- struct mp_csp_primaries csp_src = mp_get_csp_primaries(prim_src),
- csp_dst = mp_get_csp_primaries(prim_dst);
- float m[3][3] = {{0}};
- mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m);
- gl_sc_uniform_mat3(p->sc, "cms_matrix", true, &m[0][0]);
- GLSL(color.rgb = cms_matrix * color.rgb;)
- }
+ if (!src.sig_peak)
+ src.sig_peak = src.nom_peak;
- 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);
+ MP_DBG(p, "HDR src nom: %f sig: %f, dst: %f\n",
+ src.nom_peak, src.sig_peak, dst.nom_peak);
- pass_delinearize(p->sc, trc_dst);
- }
+ // Adapt from src to dst as necessary
+ pass_color_map(p->sc, src, dst, p->opts.hdr_tone_mapping,
+ p->opts.tone_mapping_param);
if (p->use_lut_3d) {
gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT);
@@ -2407,11 +2396,15 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts,
default:
abort();
}
- // Subtitle color management, they're assumed to be display-referred
- // sRGB by default
+ // When subtitles need to be color managed, assume they're in sRGB
+ // (for lack of anything saner to do)
if (cms) {
- pass_colormanage(p, p->opts.target_brightness,
- MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB);
+ static const struct mp_colorspace csp_srgb = {
+ .primaries = MP_CSP_PRIM_BT_709,
+ .gamma = MP_CSP_TRC_SRGB,
+ };
+
+ pass_colormanage(p, csp_srgb, true);
}
gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd));
gl_sc_gen_shader_and_reset(p->sc);
@@ -2542,8 +2535,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, p->image_params.color.peak, p->image_params.color.primaries,
- p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.color.gamma);
+ pass_colormanage(p, p->image_params.color, false);
// Draw checkerboard pattern to indicate transparency
if (p->has_alpha && p->opts.alpha_mode == ALPHA_BLEND_TILES) {