mirror of https://github.com/mpv-player/mpv
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:
parent
642e963c86
commit
c335e84230
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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))},
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue