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.
This commit is contained in:
Niklas Haas 2017-06-10 14:01:25 +02:00 committed by wm4
parent 642e963c86
commit c335e84230
14 changed files with 94 additions and 128 deletions

View File

@ -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")

View File

@ -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

View File

@ -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:

View File

@ -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.
``<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 display's reference brightness
for SDR and the source's reference brightness for HDR.
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

View File

@ -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);
}
}

View File

@ -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))},

View File

@ -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.

View File

@ -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

View File

@ -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;
}

View File

@ -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}
};

View File

@ -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

View File

@ -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,

View File

@ -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);
}

View File

@ -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);