From c335e84230916d7d7a38288031516e8b2ec1c36b Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Sat, 10 Jun 2017 14:01:25 +0200 Subject: video: refactor HDR implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- DOCS/interface-changes.rst | 4 ++ DOCS/man/input.rst | 4 -- DOCS/man/options.rst | 7 ---- DOCS/man/vf.rst | 15 +++---- demux/demux_mkv.c | 2 +- player/command.c | 1 - video/csputils.c | 30 +++++--------- video/csputils.h | 11 ++++-- video/decode/vd_lavc.c | 2 +- video/filter/vf_format.c | 9 +++-- video/mp_image.c | 12 ++---- video/out/opengl/video.c | 40 +++---------------- video/out/opengl/video_shaders.c | 84 ++++++++++++++++++++++------------------ video/out/opengl/video_shaders.h | 3 +- 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=`` 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 - ```` - 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. + ```` + 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. ```` 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); -- cgit v1.2.3