From 204fed4d5b4aa20b5a6b5824f5d4e71ccbaf87fb Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Wed, 26 Mar 2014 23:00:09 +0100 Subject: video: Support BT.2020 constant luminance system Signed-off-by: wm4 --- video/csputils.c | 14 +++++++++++- video/csputils.h | 1 + video/mp_image.c | 4 +++- video/out/gl_video.c | 18 +++++++++++----- video/out/gl_video_shaders.glsl | 48 ++++++++++++++++++++++++++++++++++++----- 5 files changed, 73 insertions(+), 12 deletions(-) (limited to 'video') diff --git a/video/csputils.c b/video/csputils.c index 32461697c0..43dd17511a 100644 --- a/video/csputils.c +++ b/video/csputils.c @@ -42,7 +42,8 @@ const char *const mp_csp_names[MP_CSP_COUNT] = { "BT.601 (SD)", "BT.709 (HD)", "SMPTE-240M", - "BT.2020-NC (UHD)", + "BT.2020-NCL (UHD)", + "BT.2020-CL (UHD)", "RGB", "XYZ", "YCgCo", @@ -83,6 +84,7 @@ enum mp_csp avcol_spc_to_mp_csp(int avcolorspace) case AVCOL_SPC_BT470BG: return MP_CSP_BT_601; #if HAVE_AVCOL_SPC_BT2020 case AVCOL_SPC_BT2020_NCL: return MP_CSP_BT_2020_NC; + case AVCOL_SPC_BT2020_CL: return MP_CSP_BT_2020_C; #endif case AVCOL_SPC_SMPTE170M: return MP_CSP_BT_601; case AVCOL_SPC_SMPTE240M: return MP_CSP_SMPTE_240M; @@ -122,6 +124,7 @@ int mp_csp_to_avcol_spc(enum mp_csp colorspace) case MP_CSP_BT_601: return AVCOL_SPC_BT470BG; #if HAVE_AVCOL_SPC_BT2020 case MP_CSP_BT_2020_NC: return AVCOL_SPC_BT2020_NCL; + case MP_CSP_BT_2020_C: return AVCOL_SPC_BT2020_CL; #endif case MP_CSP_SMPTE_240M: return AVCOL_SPC_SMPTE240M; case MP_CSP_RGB: return AVCOL_SPC_RGB; @@ -310,6 +313,15 @@ void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float m[3][4]) case MP_CSP_BT_709: luma_coeffs(m, 0.2126, 0.7152, 0.0722); break; case MP_CSP_SMPTE_240M: luma_coeffs(m, 0.2122, 0.7013, 0.0865); break; case MP_CSP_BT_2020_NC: luma_coeffs(m, 0.2627, 0.6780, 0.0593); break; + case MP_CSP_BT_2020_C: { + // Note: This outputs into the [-0.5,0.5] range for chroma information. + // If this clips on any VO, a constant 0.5 coefficient can be added + // to the chroma channels to normalize them into [0,1]. This is not + // currently needed by anything, though. + static const float ycbcr_to_crycb[3][4] = {{0, 0, 1}, {1, 0, 0}, {0, 1, 0}}; + memcpy(m, ycbcr_to_crycb, sizeof(ycbcr_to_crycb)); + break; + } case MP_CSP_RGB: { static const float ident[3][4] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}}; memcpy(m, ident, sizeof(ident)); diff --git a/video/csputils.h b/video/csputils.h index d7d052a753..796dfd7422 100644 --- a/video/csputils.h +++ b/video/csputils.h @@ -38,6 +38,7 @@ enum mp_csp { MP_CSP_BT_709, MP_CSP_SMPTE_240M, MP_CSP_BT_2020_NC, + MP_CSP_BT_2020_C, MP_CSP_RGB, MP_CSP_XYZ, MP_CSP_YCGCO, diff --git a/video/mp_image.c b/video/mp_image.c index 1c686e5193..521b3d8e90 100644 --- a/video/mp_image.c +++ b/video/mp_image.c @@ -524,6 +524,7 @@ void mp_image_params_guess_csp(struct mp_image_params *params) if (params->colorspace != MP_CSP_BT_601 && params->colorspace != MP_CSP_BT_709 && params->colorspace != MP_CSP_BT_2020_NC && + params->colorspace != MP_CSP_BT_2020_C && params->colorspace != MP_CSP_SMPTE_240M && params->colorspace != MP_CSP_YCGCO) { @@ -539,7 +540,8 @@ void mp_image_params_guess_csp(struct mp_image_params *params) // We assume BT.709 primaries for all untagged BT.609/BT.709 // content, because it offers the minimal deviation from all three, // including both NTSC and PAL/SECAM. - if (params->colorspace == MP_CSP_BT_2020_NC) { + if (params->colorspace == MP_CSP_BT_2020_NC || + params->colorspace == MP_CSP_BT_2020_C) { params->primaries = MP_CSP_PRIM_BT_2020; } else { params->primaries = MP_CSP_PRIM_BT_709; diff --git a/video/out/gl_video.c b/video/out/gl_video.c index e50cef3686..f0981887c6 100644 --- a/video/out/gl_video.c +++ b/video/out/gl_video.c @@ -894,11 +894,12 @@ static void compile_shaders(struct gl_video *p) bool use_input_gamma = p->input_gamma != 1.0; bool use_conv_gamma = p->conv_gamma != 1.0; + bool use_const_luma = p->image_params.colorspace == MP_CSP_BT_2020_C; // Linear light scaling is only enabled when either color correction // option (3dlut or srgb) is enabled, otherwise scaling is done in the - // source space. - bool convert_to_linear_gamma = !p->is_linear_rgb && use_cms; + // source space. We also need to linearize for constant luminance systems. + bool convert_to_linear_gamma = !p->is_linear_rgb && use_cms || use_const_luma; if (p->image_format == IMGFMT_NV12 || p->image_format == IMGFMT_NV21) { shader_def(&header_conv, "USE_CONV", "CONV_NV12"); @@ -914,8 +915,11 @@ static void compile_shaders(struct gl_video *p) shader_def_opt(&header_conv, "USE_INPUT_GAMMA", use_input_gamma); shader_def_opt(&header_conv, "USE_COLORMATRIX", !p->is_rgb); shader_def_opt(&header_conv, "USE_CONV_GAMMA", use_conv_gamma); - shader_def_opt(&header_conv, "USE_LINEAR_LIGHT", convert_to_linear_gamma); - shader_def_opt(&header_conv, "USE_APPROX_GAMMA", p->opts.approx_gamma); + shader_def_opt(&header_conv, "USE_CONST_LUMA", use_const_luma); + shader_def_opt(&header_conv, "USE_LINEAR_LIGHT_APPROX", + convert_to_linear_gamma && p->opts.approx_gamma); + shader_def_opt(&header_conv, "USE_LINEAR_LIGHT_BT2020", + convert_to_linear_gamma && !p->opts.approx_gamma); if (p->opts.alpha_mode > 0 && p->has_alpha && p->plane_count > 3) shader_def(&header_conv, "USE_ALPHA_PLANE", "3"); if (p->opts.alpha_mode == 2 && p->has_alpha) @@ -926,6 +930,10 @@ static void compile_shaders(struct gl_video *p) shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d); // 3DLUT overrides SRGB shader_def_opt(&header_final, "USE_SRGB", p->opts.srgb && !p->use_lut_3d); + shader_def_opt(&header_final, "USE_CONST_LUMA_INV_APPROX", + use_const_luma && !use_cms && p->opts.approx_gamma); + shader_def_opt(&header_final, "USE_CONST_LUMA_INV_BT2020", + use_const_luma && !use_cms && !p->opts.approx_gamma); shader_def_opt(&header_final, "USE_DITHER", p->dither_texture != 0); shader_def_opt(&header_final, "USE_TEMPORAL_DITHER", p->opts.temporal_dither); @@ -1339,7 +1347,7 @@ static void init_video(struct gl_video *p, const struct mp_image_params *params) } int eq_caps = MP_CSP_EQ_CAPS_GAMMA; - if (p->is_yuv) + if (p->is_yuv && p->image_params.colorspace != MP_CSP_BT_2020_C) eq_caps |= MP_CSP_EQ_CAPS_COLORMATRIX; p->video_eq.capabilities = eq_caps; diff --git a/video/out/gl_video_shaders.glsl b/video/out/gl_video_shaders.glsl index a6ecc2eeeb..196e81e5e3 100644 --- a/video/out/gl_video_shaders.glsl +++ b/video/out/gl_video_shaders.glsl @@ -45,7 +45,7 @@ #if __VERSION__ >= 130 vec3 srgb_compand(vec3 v) { - return mix(v * 12.92, 1.055 * pow(v, vec3(1.0/2.4)) - vec3(0.055), + return mix(v * 12.92, 1.055 * pow(v, vec3(1.0/2.4)) - 0.055, lessThanEqual(vec3(0.0031308), v)); } @@ -54,6 +54,12 @@ vec3 bt2020_expand(vec3 v) return mix(v / 4.5, pow((v + vec3(0.0993))/1.0993, vec3(1/0.45)), lessThanEqual(vec3(0.08145), v)); } + +vec3 bt2020_compand(vec3 v) +{ + return mix(v * 4.5, 1.0993 * pow(v, vec3(0.45)) - vec3(0.0993), + lessThanEqual(vec3(0.0181), v)); +} #endif // Constant matrix for conversion from BT.2020 to sRGB @@ -393,13 +399,30 @@ void main() { #ifdef USE_COLORMATRIX // Conversion from Y'CbCr or other spaces to RGB color = mat3(colormatrix) * color + colormatrix[3]; - color = clamp(color, 0, 1); #endif #ifdef USE_CONV_GAMMA // Post-colormatrix converted gamma correction (eg. for MP_IMGFLAG_XYZ) color = pow(color, vec3(conv_gamma)); #endif -#ifdef USE_LINEAR_LIGHT +#ifdef USE_CONST_LUMA + // Conversion from C'rcY'cC'bc to R'Y'cB' via the BT.2020 CL system: + // C'bc = (B'-Y'c) / 1.9404 | C'bc <= 0 + // = (B'-Y'c) / 1.5816 | C'bc > 0 + // + // C'rc = (R'-Y'c) / 1.7184 | C'rc <= 0 + // = (R'-Y'c) / 0.9936 | C'rc > 0 + // + // as per the BT.2020 specification, table 4. This is a non-linear + // transformation because (constant) luminance receives non-equal + // contributions from the three different channels. + color.br = color.br * mix(vec2(1.5816, 0.9936), vec2(1.9404, 1.7184), + lessThanEqual(color.br, vec2(0))) + color.gg; +#endif +#ifdef USE_COLORMATRIX + // Clamp down here to avoid clipping CbCr details before CONST_LUMA + // has a chance to convert them. + color = clamp(color, 0, 1); +#endif // If we are scaling in linear light (SRGB or 3DLUT option enabled), we // expand our source colors before scaling. This shader currently just // assumes everything uses the BT.2020 12-bit gamma function, since the @@ -407,13 +430,18 @@ void main() { // below the rounding error threshold for both 8-bit and even 10-bit // content. It only makes a difference for 12-bit sources, so it should be // fine to use here. -#ifdef USE_APPROX_GAMMA +#ifdef USE_LINEAR_LIGHT_APPROX // We differentiate between approximate BT.2020 (gamma 1.95) ... color = pow(color, vec3(1.95)); -#else +#endif +#ifdef USE_LINEAR_LIGHT_BT2020 // ... and actual BT.2020 (two-part function) color = bt2020_expand(color); #endif +#ifdef USE_CONST_LUMA + // Calculate the green channel from the expanded RYcB + // The BT.2020 specification says Yc = 0.2627*R + 0.6780*G + 0.0593*B + color.g = (color.g - 0.2627*color.r - 0.0593*color.b)/0.6780; #endif // Image upscaling happens roughly here #ifdef USE_GAMMA_POW @@ -442,6 +470,16 @@ void main() { #ifdef USE_SRGB // Adapt and compand from the linear BT2020 source to the sRGB output color = srgb_compand(clamp(srgb_matrix * color, 0, 1)); +#endif + // If none of these options took care of companding again, we have to do + // it manually here for the previously-expanded channels. This again + // comes in two flavours, one for the approximate gamma system and one + // for the actual gamma system. +#ifdef USE_CONST_LUMA_INV_APPROX + color = pow(color, vec3(1/1.95)); +#endif +#ifdef USE_CONST_LUMA_INV_BT2020 + color = bt2020_compand(color); #endif #ifdef USE_DITHER vec2 dither_pos = gl_FragCoord.xy / dither_size; -- cgit v1.2.3