diff options
Diffstat (limited to 'video/out')
-rw-r--r-- | video/out/opengl/video.c | 130 | ||||
-rw-r--r-- | video/out/opengl/video_shaders.c | 57 | ||||
-rw-r--r-- | video/out/opengl/video_shaders.h | 5 |
3 files changed, 115 insertions, 77 deletions
diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index 59dd64cb65..a4cc6cfac8 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -2158,19 +2158,29 @@ static void pass_scale_main(struct gl_video *p) } } -// Adapts the colors from the given color space to the display device's native -// gamut. -static void pass_colormanage(struct gl_video *p, float peak_src, - enum mp_csp_prim prim_src, - enum mp_csp_trc trc_src) -{ - GLSLF("// color management\n"); - enum mp_csp_trc trc_dst = p->opts.target_trc; - enum mp_csp_prim prim_dst = p->opts.target_prim; - float peak_dst = p->opts.target_brightness; +// Adapts the colors to the right output color space. (Final pass during +// rendering) +// If OSD is true, ignore any changes that may have been made to the video +// by previous passes (i.e. linear scaling) +static void pass_colormanage(struct gl_video *p, struct mp_colorspace src, bool osd) +{ + struct mp_colorspace ref = src; + + if (p->use_linear && !osd) + src.gamma = MP_CSP_TRC_LINEAR; + + // Figure out the target color space from the options, or auto-guess if + // none were set + struct mp_colorspace dst = { + .gamma = p->opts.target_trc, + .primaries = p->opts.target_prim, + .nom_peak = mp_csp_trc_nom_peak(p->opts.target_trc, p->opts.target_brightness), + }; if (p->use_lut_3d) { - // The 3DLUT is always generated against the original source space + // The 3DLUT is always generated against the video's original source + // space, *not* the reference space. (To avoid having to regenerate + // the 3DLUT for the OSD on every frame) enum mp_csp_prim prim_orig = p->image_params.color.primaries; enum mp_csp_trc trc_orig = p->image_params.color.gamma; @@ -2186,87 +2196,66 @@ static void pass_colormanage(struct gl_video *p, float peak_src, } if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { - prim_dst = prim_orig; - trc_dst = trc_orig; + dst.primaries = prim_orig; + dst.gamma = trc_orig; } } - if (prim_dst == MP_CSP_PRIM_AUTO) { + if (dst.primaries == MP_CSP_PRIM_AUTO) { // The vast majority of people are on sRGB or BT.709 displays, so pick // this as the default output color space. - prim_dst = MP_CSP_PRIM_BT_709; + dst.primaries = MP_CSP_PRIM_BT_709; - if (p->image_params.color.primaries == MP_CSP_PRIM_BT_601_525 || - p->image_params.color.primaries == MP_CSP_PRIM_BT_601_625) + if (ref.primaries == MP_CSP_PRIM_BT_601_525 || + ref.primaries == MP_CSP_PRIM_BT_601_625) { // Since we auto-pick BT.601 and BT.709 based on the dimensions, // combined with the fact that they're very similar to begin with, // and to avoid confusing the average user, just don't adapt BT.601 // content automatically at all. - prim_dst = p->image_params.color.primaries; + dst.primaries = ref.gamma; } } - if (trc_dst == MP_CSP_TRC_AUTO) { + if (dst.gamma == MP_CSP_TRC_AUTO) { // Most people seem to complain when the image is darker or brighter // than what they're "used to", so just avoid changing the gamma // altogether by default. The only exceptions to this rule apply to // very unusual TRCs, which even hardcode technoluddites would probably // not enjoy viewing unaltered. - trc_dst = p->image_params.color.gamma; + dst.gamma = ref.gamma; // Avoid outputting linear light or HDR content "by default". For these // just pick gamma 2.2 as a default, since it's a good estimate for // the response of typical displays - if (trc_dst == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(trc_dst)) - trc_dst = MP_CSP_TRC_GAMMA22; + if (dst.gamma == MP_CSP_TRC_LINEAR || mp_trc_is_hdr(dst.gamma)) + dst.gamma = MP_CSP_TRC_GAMMA22; } - if (!peak_src) { - // If the source has no information known, it's display-referred - // (and should be treated relative to the specified desired peak_dst) - peak_src = peak_dst * mp_csp_trc_rel_peak(p->image_params.color.gamma); - } - - // All operations from here on require linear light as a starting point, - // so we linearize even if trc_src == trc_dst when one of the other - // operations needs it - bool need_gamma = trc_src != trc_dst || prim_src != prim_dst || - peak_src != peak_dst; - if (need_gamma) - pass_linearize(p->sc, trc_src); - - // Adapt and tone map for a different reference peak brightness - if (peak_src != peak_dst) - { - GLSLF("// HDR tone mapping\n"); - float rel_peak = peak_src / peak_dst; - // Normalize such that 1 is the target brightness (and values above - // 1 are out of range) - GLSLF("color.rgb *= vec3(%f);\n", rel_peak); - // Tone map back down to the range [0,1] - pass_tone_map(p->sc, rel_peak, p->opts.hdr_tone_mapping, - p->opts.tone_mapping_param); + // For the src peaks, the correct brightness metadata may be present for + // sig_peak, nom_peak, both, or neither. To handle everything in a generic + // way, it's important to never automatically infer a sig_peak that is + // below the nom_peak (since we don't know what bits the image contains, + // doing so would potentially badly clip). The only time in which this + // may be the case is when the mastering metadata explicitly says so, i.e. + // the sig_peak was already set. So to simplify the logic as much as + // possible, make sure the nom_peak is present and correct first, and just + // set sig_peak = nom_peak if missing. + if (!src.nom_peak) { + // For display-referred colorspaces, we treat it as relative to + // target_brightness + src.nom_peak = mp_csp_trc_nom_peak(src.gamma, p->opts.target_brightness); } - // Adapt to the right colorspace if necessary - if (prim_src != prim_dst) { - struct mp_csp_primaries csp_src = mp_get_csp_primaries(prim_src), - csp_dst = mp_get_csp_primaries(prim_dst); - float m[3][3] = {{0}}; - mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m); - gl_sc_uniform_mat3(p->sc, "cms_matrix", true, &m[0][0]); - GLSL(color.rgb = cms_matrix * color.rgb;) - } + if (!src.sig_peak) + src.sig_peak = src.nom_peak; - if (need_gamma) { - // If the target encoding function has a fixed peak, we need to - // un-normalize back to the encoding signal range - if (trc_dst == MP_CSP_TRC_SMPTE_ST2084) - GLSLF("color.rgb *= vec3(%f);\n", peak_dst / 10000); + MP_DBG(p, "HDR src nom: %f sig: %f, dst: %f\n", + src.nom_peak, src.sig_peak, dst.nom_peak); - pass_delinearize(p->sc, trc_dst); - } + // Adapt from src to dst as necessary + pass_color_map(p->sc, src, dst, p->opts.hdr_tone_mapping, + p->opts.tone_mapping_param); if (p->use_lut_3d) { gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT); @@ -2407,11 +2396,15 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts, default: abort(); } - // Subtitle color management, they're assumed to be display-referred - // sRGB by default + // When subtitles need to be color managed, assume they're in sRGB + // (for lack of anything saner to do) if (cms) { - pass_colormanage(p, p->opts.target_brightness, - MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB); + static const struct mp_colorspace csp_srgb = { + .primaries = MP_CSP_PRIM_BT_709, + .gamma = MP_CSP_TRC_SRGB, + }; + + pass_colormanage(p, csp_srgb, true); } gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd)); gl_sc_gen_shader_and_reset(p->sc); @@ -2542,8 +2535,7 @@ static void pass_draw_to_screen(struct gl_video *p, int fbo) GLSL(color.rgb = pow(color.rgb, vec3(user_gamma));) } - pass_colormanage(p, p->image_params.color.peak, p->image_params.color.primaries, - p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.color.gamma); + pass_colormanage(p, p->image_params.color, false); // Draw checkerboard pattern to indicate transparency if (p->has_alpha && p->opts.alpha_mode == ALPHA_BLEND_TILES) { diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c index 7b736f1d5d..eded7d59c2 100644 --- a/video/out/opengl/video_shaders.c +++ b/video/out/opengl/video_shaders.c @@ -361,9 +361,11 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) } // Tone map from a known peak brightness to the range [0,1] -void pass_tone_map(struct gl_shader_cache *sc, float peak, - enum tone_mapping algo, float param) +static void pass_tone_map(struct gl_shader_cache *sc, float ref_peak, + enum tone_mapping algo, float param) { + GLSLF("// HDR tone mapping\n"); + switch (algo) { case TONE_MAPPING_CLIP: GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) @@ -373,7 +375,7 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak, float contrast = isnan(param) ? 0.5 : param, offset = (1.0 - contrast) / contrast; GLSLF("color.rgb = color.rgb / (color.rgb + vec3(%f));\n", offset); - GLSLF("color.rgb *= vec3(%f);\n", (peak + offset) / peak); + GLSLF("color.rgb *= vec3(%f);\n", (ref_peak + offset) / ref_peak); break; } @@ -384,20 +386,20 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak, A, C*B, D*E, A, B, D*F, E/F); GLSLHF("}\n"); - GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", peak); + GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", ref_peak); break; } case TONE_MAPPING_GAMMA: { float gamma = isnan(param) ? 1.8 : param; GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n", - peak, 1.0/gamma); + ref_peak, 1.0/gamma); break; } case TONE_MAPPING_LINEAR: { float coeff = isnan(param) ? 1.0 : param; - GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / peak); + GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / ref_peak); break; } @@ -406,6 +408,49 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak, } } +// Map colors from one source space to another. These source spaces +// must be known (i.e. not MP_CSP_*_AUTO), as this function won't perform +// any auto-guessing. +void pass_color_map(struct gl_shader_cache *sc, + struct mp_colorspace src, struct mp_colorspace dst, + enum tone_mapping algo, float tone_mapping_param) +{ + GLSLF("// color mapping\n"); + + // All operations from here on require linear light as a starting point, + // so we linearize even if src.gamma == dst.gamma when one of the other + // operations needs it + bool need_gamma = src.gamma != dst.gamma || + src.primaries != dst.primaries || + src.nom_peak != dst.nom_peak || + src.sig_peak > dst.nom_peak; + + if (need_gamma) + pass_linearize(sc, src.gamma); + + // Stretch the signal value to renormalize to the dst nominal peak + if (src.nom_peak != dst.nom_peak) + GLSLF("color.rgb *= vec3(%f);\n", src.nom_peak / dst.nom_peak); + + // Tone map to prevent clipping when the source signal peak exceeds the + // encodable range. + if (src.sig_peak > dst.nom_peak) + pass_tone_map(sc, src.sig_peak / dst.nom_peak, algo, tone_mapping_param); + + // Adapt to the right colorspace if necessary + if (src.primaries != dst.primaries) { + struct mp_csp_primaries csp_src = mp_get_csp_primaries(src.primaries), + csp_dst = mp_get_csp_primaries(dst.primaries); + float m[3][3] = {{0}}; + mp_get_cms_matrix(csp_src, csp_dst, MP_INTENT_RELATIVE_COLORIMETRIC, m); + gl_sc_uniform_mat3(sc, "cms_matrix", true, &m[0][0]); + GLSL(color.rgb = cms_matrix * color.rgb;) + } + + if (need_gamma) + pass_delinearize(sc, dst.gamma); +} + // Wide usage friendly PRNG, shamelessly stolen from a GLSL tricks forum post. // Obtain random numbers by calling rand(h), followed by h = permute(h) to // update the state. Assumes the texture was hooked. diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h index 0ee3d81fb5..3bc2f210b8 100644 --- a/video/out/opengl/video_shaders.h +++ b/video/out/opengl/video_shaders.h @@ -38,8 +38,9 @@ void pass_sample_oversample(struct gl_shader_cache *sc, struct scaler *scaler, void pass_linearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc); -void pass_tone_map(struct gl_shader_cache *sc, float peak, - enum tone_mapping algo, float param); +void pass_color_map(struct gl_shader_cache *sc, + struct mp_colorspace src, struct mp_colorspace dst, + enum tone_mapping algo, float tone_mapping_param); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, AVLFG *lfg); |