summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNiklas Haas <git@haasn.xyz>2017-06-10 14:01:25 +0200
committerwm4 <wm4@nowhere>2017-06-18 20:48:23 +0200
commitc335e84230916d7d7a38288031516e8b2ec1c36b (patch)
tree009b92a90285b7fae212d82caec588dd6ef709d8
parent642e963c86040350ac8f06b9731e6126f4d55316 (diff)
downloadmpv-c335e84230916d7d7a38288031516e8b2ec1c36b.tar.bz2
mpv-c335e84230916d7d7a38288031516e8b2ec1c36b.tar.xz
video: refactor HDR implementation
List of changes: 1. Kill nom_peak, since it's a pointless non-field that stores nothing of value and is _always_ derived from ref_white anyway. 2. Kill ref_white/--target-brightness, because the only case it really existed for (PQ) actually doesn't need to be this general: According to ITU-R BT.2100, PQ *always* assumes a reference monitor with a white point of 100 cd/m². 3. Improve documentation and comments surrounding this stuff. 4. Clean up some of the code in general. Move stuff where it belongs.
-rw-r--r--DOCS/interface-changes.rst4
-rw-r--r--DOCS/man/input.rst4
-rw-r--r--DOCS/man/options.rst7
-rw-r--r--DOCS/man/vf.rst15
-rw-r--r--demux/demux_mkv.c2
-rw-r--r--player/command.c1
-rw-r--r--video/csputils.c30
-rw-r--r--video/csputils.h11
-rw-r--r--video/decode/vd_lavc.c2
-rw-r--r--video/filter/vf_format.c9
-rw-r--r--video/mp_image.c12
-rw-r--r--video/out/opengl/video.c40
-rw-r--r--video/out/opengl/video_shaders.c84
-rw-r--r--video/out/opengl/video_shaders.h3
14 files changed, 95 insertions, 129 deletions
diff --git a/DOCS/interface-changes.rst b/DOCS/interface-changes.rst
index a8340dc409..2821e80d9d 100644
--- a/DOCS/interface-changes.rst
+++ b/DOCS/interface-changes.rst
@@ -29,6 +29,10 @@ Interface changes
`--sub-ass-override=signfs` setting to `--sub-ass-override=scale`.
- change default of --video-aspect-method to "bitstream". The "hybrid"
method (old default) is deprecated.
+ - remove property "video-params/nom-peak"
+ - remove option --target-brightness
+ - replace vf_format's `peak` suboption by `sig-peak`, which is relative to
+ the reference white level instead of in cd/m^2
--- mpv 0.25.0 ---
- remove opengl-cb dxva2 dummy hwdec interop
(see git "vo_opengl: remove dxva2 dummy hwdec backend")
diff --git a/DOCS/man/input.rst b/DOCS/man/input.rst
index ed3dc2311f..e0e9cf28fd 100644
--- a/DOCS/man/input.rst
+++ b/DOCS/man/input.rst
@@ -1456,9 +1456,6 @@ Property list
``video-params/gamma``
The gamma function in use as string. (Exact values subject to change.)
- ``video-params/nom-peak``
- The video encoding's nominal peak brightness as float.
-
``video-params/sig-peak``
The video file's tagged signal peak as float.
@@ -1489,7 +1486,6 @@ Property list
"colorlevels" MPV_FORMAT_STRING
"primaries" MPV_FORMAT_STRING
"gamma" MPV_FORMAT_STRING
- "nom-peak" MPV_FORMAT_DOUBLE
"sig-peak" MPV_FORMAT_DOUBLE
"chroma-location" MPV_FORMAT_STRING
"rotate" MPV_FORMAT_INT64
diff --git a/DOCS/man/options.rst b/DOCS/man/options.rst
index 080bf73582..0be1242063 100644
--- a/DOCS/man/options.rst
+++ b/DOCS/man/options.rst
@@ -4634,13 +4634,6 @@ The following video options are currently all specific to ``--vo=opengl`` and
The user should independently guarantee this before using these signal
formats for display.
-``--target-brightness=<1..100000>``
- Specifies the display's approximate brightness in cd/m^2. When playing 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 target
display. Valid values are:
diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst
index c7f4c84d0a..90488d2429 100644
--- a/DOCS/man/vf.rst
+++ b/DOCS/man/vf.rst
@@ -367,13 +367,14 @@ Available mpv-only filters are:
:std-b67: ARIB STD-B67 (Hybrid Log-gamma) curve
:v-log: Panasonic V-Log transfer 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.
+ ``<sig-peak>``
+ Reference peak illumination for the video file, relative to the
+ signal's reference white level. This is mostly interesting for HDR, but
+ it can also be used tone map SDR content to simulate a different
+ exposure. Normally inferred from tags such as MaxCLL or mastering
+ metadata.
+
+ The default of 0.0 will default to the source's nominal peak luminance.
``<stereo-in>``
Set the stereo mode the video is assumed to be encoded in. Takes the
diff --git a/demux/demux_mkv.c b/demux/demux_mkv.c
index c3988b8bf8..da50b1ee0f 100644
--- a/demux/demux_mkv.c
+++ b/demux/demux_mkv.c
@@ -572,7 +572,7 @@ static void parse_trackcolour(struct demuxer *demuxer, struct mkv_track *track,
struct ebml_mastering_metadata *mastering = &colour->mastering_metadata;
if (mastering->n_luminance_max) {
- track->color.sig_peak = mastering->luminance_max;
+ track->color.sig_peak = mastering->luminance_max / MP_REF_WHITE;
MP_VERBOSE(demuxer, "| + HDR peak: %f\n", track->color.sig_peak);
}
}
diff --git a/player/command.c b/player/command.c
index 9839185ac5..1bb38edeb9 100644
--- a/player/command.c
+++ b/player/command.c
@@ -2695,7 +2695,6 @@ static int property_imgparams(struct mp_image_params p, int action, void *arg)
SUB_PROP_STR(m_opt_choice_str(mp_csp_prim_names, p.color.primaries))},
{"gamma",
SUB_PROP_STR(m_opt_choice_str(mp_csp_trc_names, p.color.gamma))},
- {"nom-peak", SUB_PROP_FLOAT(p.color.nom_peak)},
{"sig-peak", SUB_PROP_FLOAT(p.color.sig_peak)},
{"chroma-location",
SUB_PROP_STR(m_opt_choice_str(mp_chroma_names, p.chroma_location))},
diff --git a/video/csputils.c b/video/csputils.c
index a41d9cf8c4..768314f938 100644
--- a/video/csputils.c
+++ b/video/csputils.c
@@ -110,8 +110,6 @@ void mp_colorspace_merge(struct mp_colorspace *orig, struct mp_colorspace *new)
orig->primaries = new->primaries;
if (!orig->gamma)
orig->gamma = new->gamma;
- if (!orig->nom_peak)
- orig->nom_peak = new->nom_peak;
if (!orig->sig_peak)
orig->sig_peak = new->sig_peak;
}
@@ -449,30 +447,23 @@ struct mp_csp_primaries mp_get_csp_primaries(enum mp_csp_prim spc)
}
}
-// 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)
+// Get the nominal peak for a given colorspace, relative to the reference white
+// level. In other words, this returns the brightest encodable value that can
+// be represented by a given transfer curve.
+float mp_trc_nom_peak(enum mp_csp_trc trc)
{
switch (trc) {
- 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;
+ case MP_CSP_TRC_SMPTE_ST2084: return 10000.0 / MP_REF_WHITE;
+ case MP_CSP_TRC_ARIB_STD_B67: return 12.0;
+ case MP_CSP_TRC_V_LOG: return 46.0855;
}
- return ref_peak;
+ return 1.0;
}
bool mp_trc_is_hdr(enum mp_csp_trc trc)
{
- switch (trc) {
- case MP_CSP_TRC_SMPTE_ST2084:
- case MP_CSP_TRC_ARIB_STD_B67:
- case MP_CSP_TRC_V_LOG:
- return true;
- }
-
- return false;
+ return mp_trc_nom_peak(trc) > 1.0;
}
// Compute the RGB/XYZ matrix as described here:
@@ -798,8 +789,7 @@ 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.sig_peak == c2.sig_peak &&
- c1.nom_peak == c2.nom_peak;
+ c1.sig_peak == c2.sig_peak;
}
// Copy settings from eq into params.
diff --git a/video/csputils.h b/video/csputils.h
index 9eaafbe75d..ec7369fa67 100644
--- a/video/csputils.h
+++ b/video/csputils.h
@@ -121,10 +121,15 @@ struct mp_colorspace {
enum mp_csp_levels levels;
enum mp_csp_prim primaries;
enum mp_csp_trc gamma;
- float nom_peak; // nominal (absolute) peak. 0 = auto/unknown
- float sig_peak; // signal peak, highest value that occurs in the source
+ float sig_peak; // highest relative value in signal. 0 = unknown/auto
};
+// For many colorspace conversions, in particular those involving HDR, an
+// implicit reference white level is needed. Since this magic constant shows up
+// a lot, give it an explicit name. The value of 100 cd/m² comes from ITU-R
+// documents such as ITU-R BT.2100
+#define MP_REF_WHITE 100.0
+
// Replaces unknown values in the first struct by those of the second struct
void mp_colorspace_merge(struct mp_colorspace *orig, struct mp_colorspace *new);
@@ -230,7 +235,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_nom_peak(enum mp_csp_trc trc, float ref_peak);
+float mp_trc_nom_peak(enum mp_csp_trc trc);
bool mp_trc_is_hdr(enum mp_csp_trc trc);
/* Color conversion matrix: RGB = m * YUV + c
diff --git a/video/decode/vd_lavc.c b/video/decode/vd_lavc.c
index 209ad8c0c5..aae6937476 100644
--- a/video/decode/vd_lavc.c
+++ b/video/decode/vd_lavc.c
@@ -742,7 +742,7 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
}
#endif
- params->color.sig_peak = ctx->cached_hdr_peak;
+ params->color.sig_peak = ctx->cached_hdr_peak / MP_REF_WHITE;
params->rotate = vd->codec->rotate;
params->stereo_in = vd->codec->stereo_mode;
}
diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c
index d406d98d9b..2cb6943879 100644
--- a/video/filter/vf_format.c
+++ b/video/filter/vf_format.c
@@ -38,7 +38,7 @@ struct vf_priv_s {
int colorlevels;
int primaries;
int gamma;
- float peak;
+ float sig_peak;
int chroma_location;
int stereo_in;
int stereo_out;
@@ -95,8 +95,8 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in,
out->color.primaries = p->primaries;
if (p->gamma)
out->color.gamma = p->gamma;
- if (p->peak)
- out->color.sig_peak = p->peak;
+ if (p->sig_peak)
+ out->color.sig_peak = p->sig_peak;
if (p->chroma_location)
out->chroma_location = p->chroma_location;
if (p->stereo_in)
@@ -145,7 +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_FLOAT("sig-peak", sig_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),
@@ -154,6 +154,7 @@ static const m_option_t vf_opts_fields[] = {
OPT_INT("dh", dh, 0),
OPT_DOUBLE("dar", dar, 0),
OPT_REMOVED("outputlevels", "use the --video-output-levels global option"),
+ OPT_REMOVED("peak", "use sig-peak instead (changed value scale!)"),
{0}
};
diff --git a/video/mp_image.c b/video/mp_image.c
index bc706b8856..ae0fc02e49 100644
--- a/video/mp_image.c
+++ b/video/mp_image.c
@@ -408,7 +408,6 @@ 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.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;
@@ -531,8 +530,6 @@ char *mp_image_params_to_str_buf(char *b, size_t bs,
m_opt_choice_str(mp_csp_prim_names, p->color.primaries),
m_opt_choice_str(mp_csp_trc_names, p->color.gamma),
m_opt_choice_str(mp_csp_levels_names, p->color.levels));
- if (p->color.nom_peak)
- mp_snprintf_cat(b, bs, " NP=%f", p->color.nom_peak);
if (p->color.sig_peak)
mp_snprintf_cat(b, bs, " SP=%f", p->color.sig_peak);
mp_snprintf_cat(b, bs, " CL=%s",
@@ -687,11 +684,10 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
params->color.gamma = MP_CSP_TRC_AUTO;
}
- // Guess the nominal peak (independent of the colorspace)
- if (params->color.gamma == MP_CSP_TRC_SMPTE_ST2084) {
- if (!params->color.nom_peak)
- params->color.nom_peak = 10000; // As per the spec
- }
+ // If the signal peak is unknown, we're forced to pick the TRC's nominal
+ // range as the signal peak to prevent clipping
+ if (!params->color.sig_peak)
+ params->color.sig_peak = mp_trc_nom_peak(params->color.gamma);
}
// Copy properties and data of the AVFrame into the mp_image, without taking
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c
index 51d484d078..2cf5a41c0b 100644
--- a/video/out/opengl/video.c
+++ b/video/out/opengl/video.c
@@ -290,7 +290,6 @@ static const struct gl_video_opts gl_video_opts_def = {
.alpha_mode = ALPHA_BLEND_TILES,
.background = {0, 0, 0, 255},
.gamma = 1.0f,
- .target_brightness = 250,
.hdr_tone_mapping = TONE_MAPPING_MOBIUS,
.tone_mapping_param = NAN,
.early_flush = -1,
@@ -325,7 +324,6 @@ const struct m_sub_options gl_video_conf = {
OPT_FLAG("gamma-auto", gamma_auto, 0),
OPT_CHOICE_C("target-prim", target_prim, 0, mp_csp_prim_names),
OPT_CHOICE_C("target-trc", target_trc, 0, mp_csp_trc_names),
- OPT_INTRANGE("target-brightness", target_brightness, 0, 1, 100000),
OPT_CHOICE("hdr-tone-mapping", hdr_tone_mapping, 0,
({"clip", TONE_MAPPING_CLIP},
{"mobius", TONE_MAPPING_MOBIUS},
@@ -2053,17 +2051,11 @@ static void pass_scale_main(struct gl_video *p)
// 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) {
@@ -2095,14 +2087,14 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
// this as the default output color space.
dst.primaries = MP_CSP_PRIM_BT_709;
- if (ref.primaries == MP_CSP_PRIM_BT_601_525 ||
- ref.primaries == MP_CSP_PRIM_BT_601_625)
+ if (src.primaries == MP_CSP_PRIM_BT_601_525 ||
+ src.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.
- dst.primaries = ref.primaries;
+ dst.primaries = src.primaries;
}
}
@@ -2112,7 +2104,7 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
// altogether by default. The only exceptions to this rule apply to
// very unusual TRCs, which even hardcode technoluddites would probably
// not enjoy viewing unaltered.
- dst.gamma = ref.gamma;
+ dst.gamma = src.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
@@ -2121,30 +2113,9 @@ static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool
dst.gamma = MP_CSP_TRC_GAMMA22;
}
- // 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);
- }
-
- if (!src.sig_peak)
- src.sig_peak = src.nom_peak;
-
- MP_DBG(p, "HDR src nom: %f sig: %f, dst: %f\n",
- src.nom_peak, src.sig_peak, dst.nom_peak);
-
// Adapt from src to dst as necessary
pass_color_map(p->sc, src, dst, p->opts.hdr_tone_mapping,
- p->opts.tone_mapping_param);
+ p->opts.tone_mapping_param, p->use_linear && !osd);
if (p->use_lut_3d) {
gl_sc_uniform_tex(p->sc, "lut_3d", GL_TEXTURE_3D, p->lut_3d_texture);
@@ -3089,7 +3060,6 @@ static void check_gl_features(struct gl_video *p)
.temporal_dither_period = p->opts.temporal_dither_period,
.tex_pad_x = p->opts.tex_pad_x,
.tex_pad_y = p->opts.tex_pad_y,
- .target_brightness = p->opts.target_brightness,
.hdr_tone_mapping = p->opts.hdr_tone_mapping,
.tone_mapping_param = p->opts.tone_mapping_param,
.early_flush = p->opts.early_flush,
diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c
index 95ac712f05..4bacb12532 100644
--- a/video/out/opengl/video_shaders.c
+++ b/video/out/opengl/video_shaders.c
@@ -233,16 +233,18 @@ static const float B67_A = 0.17883277,
// Common constants for Panasonic V-Log
static const float VLOG_B = 0.00873,
VLOG_C = 0.241514,
- VLOG_D = 0.598206,
- VLOG_R = 46.085527; // nominal peak
+ VLOG_D = 0.598206;
-// Linearize (expand), given a TRC as input. This corresponds to the EOTF
-// in ITU-R terminology.
+// 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.
void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
{
if (trc == MP_CSP_TRC_LINEAR)
return;
+ GLSLF("// linearize\n");
+
// Note that this clamp may technically violate the definition of
// ITU-R BT.2100, which allows for sub-blacks and super-whites to be
// displayed on the display where such would be possible. That said, the
@@ -257,7 +259,6 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
lessThan(vec3(0.04045), color.rgb));)
break;
case MP_CSP_TRC_BT_1886:
- // We don't have an actual black point, so we assume a perfect display
GLSL(color.rgb = pow(color.rgb, vec3(2.4));)
break;
case MP_CSP_TRC_GAMMA18:
@@ -280,17 +281,15 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
" / (vec3(%f) - vec3(%f) * color.rgb);\n",
HDR_C1, HDR_C2, HDR_C3);
GLSLF("color.rgb = pow(color.rgb, vec3(1.0/%f));\n", HDR_M1);
+ // PQ's output range is 0-10000, but we need it to be relative to to
+ // MP_REF_WHITE instead, so rescale
+ GLSLF("color.rgb *= vec3(%f);\n", 10000 / MP_REF_WHITE);
break;
case MP_CSP_TRC_ARIB_STD_B67:
GLSLF("color.rgb = mix(vec3(4.0) * color.rgb * color.rgb,\n"
" exp((color.rgb - vec3(%f)) / vec3(%f)) + vec3(%f),\n"
" lessThan(vec3(0.5), color.rgb));\n",
B67_C, B67_A, B67_B);
- // Since the ARIB function's signal value of 1.0 corresponds to
- // a peak of 12.0, we need to renormalize to prevent GL textures
- // from clipping. (In general, mpv's internal conversions always
- // assume 1.0 is the maximum brightness, not the reference peak)
- GLSL(color.rgb /= vec3(12.0);)
break;
case MP_CSP_TRC_V_LOG:
GLSLF("color.rgb = mix((color.rgb - vec3(0.125)) / vec3(5.6), \n"
@@ -298,23 +297,27 @@ void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
" - vec3(%f), \n"
" lessThanEqual(vec3(0.181), color.rgb)); \n",
VLOG_D, VLOG_C, VLOG_B);
- // Same deal as with the B67 function, renormalize to texture range
- GLSLF("color.rgb /= vec3(%f);\n", VLOG_R);
- GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
break;
default:
abort();
}
+
+ // Rescale to prevent clipping on non-float textures
+ GLSLF("color.rgb /= vec3(%f);\n", mp_trc_nom_peak(trc));
}
// Delinearize (compress), given a TRC as output. This corresponds to the
-// inverse EOTF (not the OETF) in ITU-R terminology.
+// inverse EOTF (not the OETF) in ITU-R terminology, again assuming a
+// reference monitor.
void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
{
if (trc == MP_CSP_TRC_LINEAR)
return;
+ GLSLF("// delinearize\n");
GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);)
+ GLSLF("color.rgb *= vec3(%f);\n", mp_trc_nom_peak(trc));
+
switch (trc) {
case MP_CSP_TRC_SRGB:
GLSL(color.rgb = mix(color.rgb * vec3(12.92),
@@ -340,6 +343,7 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
lessThanEqual(vec3(0.001953), color.rgb));)
break;
case MP_CSP_TRC_SMPTE_ST2084:
+ GLSLF("color.rgb /= vec3(%f);\n", 10000 / MP_REF_WHITE);
GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", HDR_M1);
GLSLF("color.rgb = (vec3(%f) + vec3(%f) * color.rgb) \n"
" / (vec3(1.0) + vec3(%f) * color.rgb);\n",
@@ -347,14 +351,12 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc)
GLSLF("color.rgb = pow(color.rgb, vec3(%f));\n", HDR_M2);
break;
case MP_CSP_TRC_ARIB_STD_B67:
- GLSL(color.rgb *= vec3(12.0);)
GLSLF("color.rgb = mix(vec3(0.5) * sqrt(color.rgb),\n"
" vec3(%f) * log(color.rgb - vec3(%f)) + vec3(%f),\n"
" lessThan(vec3(1.0), color.rgb));\n",
B67_A, B67_B, B67_C);
break;
case MP_CSP_TRC_V_LOG:
- GLSLF("color.rgb *= vec3(%f);\n", VLOG_R);
GLSLF("color.rgb = mix(vec3(5.6) * color.rgb + vec3(0.125), \n"
" vec3(%f) * log(color.rgb + vec3(%f)) \n"
" + vec3(%f), \n"
@@ -429,46 +431,54 @@ static void pass_tone_map(struct gl_shader_cache *sc, float ref_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.
+// 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. If is_linear is true, we assume the input has already been
+// linearized (e.g. for linear-scaling)
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)
+ enum tone_mapping algo, float tone_mapping_param,
+ bool is_linear)
{
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);
+
// 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;
+ src_range != dst_range ||
+ src.sig_peak > dst_range;
- if (need_gamma)
+ if (need_gamma && !is_linear) {
pass_linearize(sc, src.gamma);
+ is_linear= true;
+ }
// NOTE: When src.gamma = MP_CSP_TRC_ARIB_STD_B67, we would technically
// need to apply the reference OOTF as part of the EOTF (which is what we
// implement with pass_linearize), since HLG considers OOTF to be part of
- // the display's EOTF (as opposed to the camera's OETF). But since this is
- // stupid, complicated, arbitrary, and more importantly depends on the
- // target display's signal peak (which is != the nom_peak in the case of
- // HDR displays, and mpv already has enough target-specific display
- // options), we just ignore its implementation entirely. (Plus, it doesn't
- // even really make sense with tone mapping to begin with.) But just in
- // case somebody ends up complaining about HLG looking different from a
+ // the display's EOTF (as opposed to the camera's OETF) - although arguably
+ // in our case this would be part of the ICC profile, not mpv. Either way,
+ // in case somebody ends up complaining about HLG looking different from a
// reference HLG display, this comment might be why.
- // 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);
+ // Rescale the signal to compensate for differences in the encoding range
+ // and reference white level. This is necessary because of how mpv encodes
+ // brightness in textures.
+ if (src_range != dst_range) {
+ GLSLF("// rescale value range;\n");
+ GLSLF("color.rgb *= vec3(%f);\n", src_range / dst_range);
+ }
// 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);
+ // encodable range
+ if (src.sig_peak > dst_range)
+ pass_tone_map(sc, src.sig_peak / dst_range, algo, tone_mapping_param);
// Adapt to the right colorspace if necessary
if (src.primaries != dst.primaries) {
@@ -480,7 +490,7 @@ void pass_color_map(struct gl_shader_cache *sc,
GLSL(color.rgb = cms_matrix * color.rgb;)
}
- if (need_gamma)
+ if (is_linear)
pass_delinearize(sc, dst.gamma);
}
diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h
index 3bc2f210b8..076a835754 100644
--- a/video/out/opengl/video_shaders.h
+++ b/video/out/opengl/video_shaders.h
@@ -40,7 +40,8 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc);
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);
+ enum tone_mapping algo, float tone_mapping_param,
+ bool skip_linearization);
void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts,
AVLFG *lfg);