summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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);