summaryrefslogtreecommitdiffstats
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
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).
-rw-r--r--video/csputils.c18
-rw-r--r--video/csputils.h5
-rw-r--r--video/decode/lavc.h3
-rw-r--r--video/filter/vf_format.c2
-rw-r--r--video/mp_image.c7
-rw-r--r--video/out/opengl/video.c130
-rw-r--r--video/out/opengl/video_shaders.c57
-rw-r--r--video/out/opengl/video_shaders.h5
8 files changed, 136 insertions, 91 deletions
diff --git a/video/csputils.c b/video/csputils.c
index 4c9cfbeebd..8ed3b289b1 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -437,17 +437,18 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc)
}
}
-// Get the relative peak of a transfer curve, that is: (source reference /
-// display reference), or 0 if there is none (i.e. source has an absolute peak)
-float mp_csp_trc_rel_peak(enum mp_csp_trc trc)
+// Get the nominal peak for a given colorspace, based on a known reference peak
+// (i.e. the display of a reference white illuminant. This may or may not
+// be the actual signal peak)
+float mp_csp_trc_nom_peak(enum mp_csp_trc trc, float ref_peak)
{
switch (trc) {
- case MP_CSP_TRC_SMPTE_ST2084: return 0.0; // This has a fixed peak
- case MP_CSP_TRC_ARIB_STD_B67: return 12.0;
- case MP_CSP_TRC_V_LOG: return 46.0855;
+ case MP_CSP_TRC_SMPTE_ST2084: return 10000; // fixed peak
+ case MP_CSP_TRC_ARIB_STD_B67: return 12.0 * ref_peak;
+ case MP_CSP_TRC_V_LOG: return 46.0855 * ref_peak;
}
- return 1.0;
+ return ref_peak;
}
bool mp_trc_is_hdr(enum mp_csp_trc trc)
@@ -781,7 +782,8 @@ bool mp_colorspace_equal(struct mp_colorspace c1, struct mp_colorspace c2)
c1.levels == c2.levels &&
c1.primaries == c2.primaries &&
c1.gamma == c2.gamma &&
- c1.peak == c2.peak;
+ c1.sig_peak == c2.sig_peak &&
+ c1.nom_peak == c2.nom_peak;
}
// Copy settings from eq into params.
diff --git a/video/csputils.h b/video/csputils.h
index 0743f2bee5..0406ddf35f 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -121,7 +121,8 @@ struct mp_colorspace {
enum mp_csp_levels levels;
enum mp_csp_prim primaries;
enum mp_csp_trc gamma;
- float peak; // 0 = auto/unknown
+ float nom_peak; // nominal (absolute) peak. 0 = auto/unknown
+ float sig_peak; // signal peak, highest value that occurs in the source
};
struct mp_csp_params {
@@ -226,7 +227,7 @@ int mp_chroma_location_to_av(enum mp_chroma_location mploc);
void mp_get_chroma_location(enum mp_chroma_location loc, int *x, int *y);
struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim csp);
-float mp_csp_trc_rel_peak(enum mp_csp_trc trc);
+float mp_csp_trc_nom_peak(enum mp_csp_trc trc, float ref_peak);
bool mp_trc_is_hdr(enum mp_csp_trc trc);
/* Color conversion matrix: RGB = m * YUV + c
diff --git a/video/decode/lavc.h b/video/decode/lavc.h
index 689222d872..e76dff50bc 100644
--- a/video/decode/lavc.h
+++ b/video/decode/lavc.h
@@ -25,6 +25,9 @@ typedef struct lavc_ctx {
bool hwdec_failed;
bool hwdec_notified;
+ // For HDR side-data caching
+ int cached_hdr_peak;
+
struct mp_image **delay_queue;
int num_delay_queue;
int max_delay_queue;
diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c
index 36388e6288..d406d98d9b 100644
--- a/video/filter/vf_format.c
+++ b/video/filter/vf_format.c
@@ -96,7 +96,7 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
if (p->gamma)
out->color.gamma = p->gamma;
if (p->peak)
- out->color.peak = p->peak;
+ out->color.sig_peak = p->peak;
if (p->chroma_location)
out->chroma_location = p->chroma_location;
if (p->stereo_in)
diff --git a/video/mp_image.c b/video/mp_image.c
index 286e40bf62..a4ce6d1cc5 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -395,7 +395,8 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
}
dst->params.color.primaries = src->params.color.primaries;
dst->params.color.gamma = src->params.color.gamma;
- dst->params.color.peak = src->params.color.peak;
+ dst->params.color.nom_peak = src->params.color.nom_peak;
+ dst->params.color.sig_peak = src->params.color.sig_peak;
if ((dst->fmt.flags & MP_IMGFLAG_YUV) == (src->fmt.flags & MP_IMGFLAG_YUV)) {
dst->params.color.space = src->params.color.space;
dst->params.color.levels = src->params.color.levels;
@@ -668,8 +669,8 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
// Guess the nominal peak (independent of the colorspace)
if (params->color.gamma == MP_CSP_TRC_SMPTE_ST2084) {
- if (!params->color.peak)
- params->color.peak = 10000; // As per the spec
+ if (!params->color.nom_peak)
+ params->color.nom_peak = 10000; // As per the spec
}
}
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) {
diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c
index 7b736f1d5d..eded7d59c2 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/opengl/video_shaders.c
@@ -361,9 +361,11 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
}
// 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)
+static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak,
+ enum tone_mapping algo, float param)
{
+ GLSLF("// HDR tone mapping\n");
+
switch (algo) {
case TONE_MAPPING_CLIP:
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
@@ -373,7 +375,7 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
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", (peak + offset) / peak);
+ GLSLF("color.rgb *= vec3(%f);\n", (ref_peak + offset) / ref_peak);
break;
}
@@ -384,20 +386,20 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
A, C*B, D*E, A, B, D*F, E/F);
GLSLHF("}\n");
- GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", peak);
+ GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", ref_peak);
break;
}
case TONE_MAPPING_GAMMA: {
float gamma = isnan(param) ? 1.8 : param;
GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n",
- peak, 1.0/gamma);
+ ref_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 / peak);
+ GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / ref_peak);
break;
}
@@ -406,6 +408,49 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak,
}
}
+// Map colors from one source space to another. These source spaces
+// must be known (i.e. not MP_CSP_*_AUTO), as this function won't perform
+// any auto-guessing.
+void pass_color_map(struct gl_shader_cache *sc,
+ struct mp_colorspace src, struct mp_colorspace dst,
+ enum tone_mapping algo, float tone_mapping_param)
+{
+ GLSLF("// color mapping\n");
+
+ // 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
+ // operations needs it
+ bool need_gamma = src.gamma != dst.gamma ||
+ src.primaries != dst.primaries ||
+ src.nom_peak != dst.nom_peak ||
+ src.sig_peak > dst.nom_peak;
+
+ if (need_gamma)
+ pass_linearize(sc, src.gamma);
+
+ // Stretch the signal value to renormalize to the dst nominal peak
+ if (src.nom_peak != dst.nom_peak)
+ GLSLF("color.rgb *= vec3(%f);\n", src.nom_peak / dst.nom_peak);
+
+ // Tone map to prevent clipping when the source signal peak exceeds the
+ // encodable range.
+ if (src.sig_peak > dst.nom_peak)
+ pass_tone_map(sc, src.sig_peak / dst.nom_peak, algo, tone_mapping_param);
+
+ // Adapt to the right colorspace if necessary
+ if (src.primaries != dst.primaries) {
+ struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries),
+ csp_dst = mp_get_csp_primaries(dst.primaries);
+ float m[3][3] = {{0}};
+ mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m);
+ gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]);
+ GLSL(color.rgb = cms_matrix * color.rgb;)
+ }
+
+ if (need_gamma)
+ pass_delinearize(sc, dst.gamma);
+}
+
// Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post.
// Obtain random numbers by calling rand(h), followed by h = permute(h) to
// update the state. Assumes the texture was hooked.
diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h
index 0ee3d81fb5..3bc2f210b8 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/opengl/video_shaders.h
@@ -38,8 +38,9 @@ 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,
- enum tone_mapping algo, float param);
+void pass_color_map(struct gl_shader_cache *sc,
+ struct mp_colorspace src, struct mp_colorspace dst,
+ enum tone_mapping algo, float tone_mapping_param);
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
AVLFG *lfg);