From 45c3e0f0d0c836158ab38db53156bb6461ad7437 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Mon, 30 May 2016 19:56:58 +0200 Subject: vo_opengl: refactor HDR mechanism Instead of doing HDR tone mapping on an ad-hoc basis inside pass_colormanage, the reference peak of an image is now part of the image params (alongside colorspace, gamma, etc.) and tone mapping is done whenever peak_src != peak_dst. To get sensible behavior when mixing HDR and SDR content and displays, target-brightness is a generic filler for "the assumed brightness of SDR content". This gets rid of the weird display_scaled hack, sets the framework for multiple HDR functions with difference reference peaks, and allows us to (in a future commit) autodetect the right source peak from the HDR metadata. (Apart from metadata, the source peak can also be controlled via vf_format. For HDR content this adjusts the overall image brightness, for SDR content it's like simulating a different exposure) --- DOCS/man/vf.rst | 8 +++++++ DOCS/man/vo.rst | 7 +++--- video/filter/vf_format.c | 4 ++++ video/mp_image.c | 7 ++++++ video/mp_image.h | 1 + video/out/opengl/video.c | 50 ++++++++++++++++++++++++++++------------ video/out/opengl/video_shaders.c | 17 +++++--------- video/out/opengl/video_shaders.h | 2 +- 8 files changed, 66 insertions(+), 30 deletions(-) diff --git a/DOCS/man/vf.rst b/DOCS/man/vf.rst index 40c499cb5a..b4e4438f78 100644 --- a/DOCS/man/vf.rst +++ b/DOCS/man/vf.rst @@ -312,6 +312,14 @@ Available filters are: :prophoto: ProPhoto RGB (ROMM) curve :st2084: SMPTE ST2084 (HDR) 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. + ```` Set the stereo mode the video is assumed to be encoded in. Takes the same values as the ``--video-stereo-mode`` option. diff --git a/DOCS/man/vo.rst b/DOCS/man/vo.rst index 717c4738a9..3b16e74ae8 100644 --- a/DOCS/man/vo.rst +++ b/DOCS/man/vo.rst @@ -1061,9 +1061,10 @@ Available video output drivers are: ``target-brightness=<1..100000>`` Specifies the display's approximate brightness in cd/m^2. When playing - HDR content, 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 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 diff --git a/video/filter/vf_format.c b/video/filter/vf_format.c index ff7389cb7a..109fda4053 100644 --- a/video/filter/vf_format.c +++ b/video/filter/vf_format.c @@ -38,6 +38,7 @@ struct vf_priv_s { int colorlevels; int primaries; int gamma; + float peak; int chroma_location; int stereo_in; int stereo_out; @@ -94,6 +95,8 @@ static int reconfig(struct vf_instance *vf, struct mp_image_params *in, out->primaries = p->primaries; if (p->gamma) out->gamma = p->gamma; + if (p->peak) + out->peak = p->peak; if (p->chroma_location) out->chroma_location = p->chroma_location; if (p->stereo_in) @@ -142,6 +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_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), diff --git a/video/mp_image.c b/video/mp_image.c index ac3d4ea5e1..d5b97481e8 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -568,6 +568,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1, p1->colorlevels == p2->colorlevels && p1->primaries == p2->primaries && p1->gamma == p2->gamma && + p1->peak == p2->peak && p1->chroma_location == p2->chroma_location && p1->rotate == p2->rotate && p1->stereo_in == p2->stereo_in && @@ -662,6 +663,12 @@ void mp_image_params_guess_csp(struct mp_image_params *params) params->primaries = MP_CSP_PRIM_AUTO; params->gamma = MP_CSP_TRC_AUTO; } + + // Guess the reference peak (independent of the colorspace) + if (params->gamma == MP_CSP_TRC_SMPTE_ST2084) { + if (!params->peak) + params->peak = 10000; // As per the spec + } } // Copy properties and data of the AVFrame into the mp_image, without taking diff --git a/video/mp_image.h b/video/mp_image.h index 0a43093ecb..18d25968c0 100644 --- a/video/mp_image.h +++ b/video/mp_image.h @@ -47,6 +47,7 @@ struct mp_image_params { enum mp_csp_levels colorlevels; enum mp_csp_prim primaries; enum mp_csp_trc gamma; + float peak; // 0 = auto/unknown enum mp_chroma_location chroma_location; // The image should be rotated clockwise (0-359 degrees). int rotate; diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index 380e768126..f0a2401172 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -2198,13 +2198,14 @@ 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, bool display_scaled, +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; if (p->use_lut_3d) { // The 3DLUT is always generated against the original source space @@ -2241,21 +2242,31 @@ static void pass_colormanage(struct gl_video *p, bool display_scaled, if (trc_dst == MP_CSP_TRC_LINEAR || trc_dst == MP_CSP_TRC_SMPTE_ST2084) trc_dst = 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; + } - bool need_gamma = trc_src != trc_dst || prim_src != prim_dst; + // 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); - // For HDR, the assumption of reference brightness = display brightness - // is discontinued. Instead, we have to tone map the brightness to - // the display using some algorithm. - if (p->image_params.gamma == MP_CSP_TRC_SMPTE_ST2084 && - trc_dst != MP_CSP_TRC_SMPTE_ST2084 && !display_scaled) + // Adapt and tone map for a different reference peak brightness + if (peak_src != peak_dst) { GLSLF("// HDR tone mapping\n"); - int reference_brightness = 10000; // As per SMPTE ST.2084 - pass_tone_map(p->sc, reference_brightness, p->opts.target_brightness, - p->opts.hdr_tone_mapping, p->opts.tone_mapping_param); + 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); } // Adapt to the right colorspace if necessary @@ -2268,8 +2279,14 @@ static void pass_colormanage(struct gl_video *p, bool display_scaled, GLSL(color.rgb = cms_matrix * color.rgb;) } - if (need_gamma) + 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); + pass_delinearize(p->sc, trc_dst); + } if (p->use_lut_3d) { gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT); @@ -2410,9 +2427,12 @@ static void pass_draw_osd(struct gl_video *p, int draw_flags, double pts, default: abort(); } - // Subtitle color management, they're assumed to be sRGB by default - if (cms) - pass_colormanage(p, true, MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB); + // Subtitle color management, they're assumed to be display-referred + // sRGB by default + if (cms) { + pass_colormanage(p, p->opts.target_brightness, + MP_CSP_PRIM_BT_709, MP_CSP_TRC_SRGB); + } gl_sc_set_vao(p->sc, mpgl_osd_get_vao(p->osd)); gl_sc_gen_shader_and_reset(p->sc); mpgl_osd_draw_part(p->osd, vp_w, vp_h, n); @@ -2533,7 +2553,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, false, p->image_params.primaries, + pass_colormanage(p, p->image_params.peak, p->image_params.primaries, p->use_linear ? MP_CSP_TRC_LINEAR : p->image_params.gamma); // Draw checkerboard pattern to indicate transparency diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c index d940c415e8..1f37f4fed1 100644 --- a/video/out/opengl/video_shaders.c +++ b/video/out/opengl/video_shaders.c @@ -313,15 +313,10 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) } } -// Tone map from one brightness to another -void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst, +// 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) { - // First we renormalize to the output range - float scale = peak_src / peak_dst; - GLSLF("color.rgb *= vec3(%f);\n", scale); - - // Then we use some algorithm to map back to [0,1] switch (algo) { case TONE_MAPPING_CLIP: GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) @@ -331,7 +326,7 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst, 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", (scale + offset) / scale); + GLSLF("color.rgb *= vec3(%f);\n", (peak + offset) / peak); break; } @@ -342,20 +337,20 @@ void pass_tone_map(struct gl_shader_cache *sc, float peak_src, float peak_dst, A, C*B, D*E, A, B, D*F, E/F); GLSLHF("}\n"); - GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", scale); + GLSLF("color.rgb = hable(color.rgb) / hable(vec3(%f));\n", peak); break; } case TONE_MAPPING_GAMMA: { float gamma = isnan(param) ? 1.8 : param; GLSLF("color.rgb = pow(color.rgb / vec3(%f), vec3(%f));\n", - scale, 1.0/gamma); + 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 / scale); + GLSLF("color.rgb = vec3(%f) * color.rgb;\n", coeff / peak); break; } diff --git a/video/out/opengl/video_shaders.h b/video/out/opengl/video_shaders.h index e43efadeb4..0ee3d81fb5 100644 --- a/video/out/opengl/video_shaders.h +++ b/video/out/opengl/video_shaders.h @@ -38,7 +38,7 @@ 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_src, float peak_dst, +void pass_tone_map(struct gl_shader_cache *sc, float peak, enum tone_mapping algo, float param); void pass_sample_deband(struct gl_shader_cache *sc, struct deband_opts *opts, -- cgit v1.2.3