video: Add support for non-BT.709 primaries

This add support for reading primary information from lavc, categorized
into BT.601-525, BT.601-625, BT.709 and BT.2020; and passes it on to the
vo. In vo_opengl, we always generate the 3dlut against the wider BT.2020
and transform our source into this colorspace in the shader.
This commit is contained in:
Niklas Haas
2014-03-26 01:46:38 +01:00
committed by wm4
parent 86d3d11a68
commit 70f50ddc5e
9 changed files with 194 additions and 18 deletions

View File

@@ -362,6 +362,12 @@ Available video output drivers are:
LittleCMS 2. If both ``srgb`` and ``icc-profile`` are present, the LittleCMS 2. If both ``srgb`` and ``icc-profile`` are present, the
latter takes precedence, as they are somewhat redundant. latter takes precedence, as they are somewhat redundant.
Note: When playing back BT.2020 content with this option enabled, out
of gamut colors will be numerically clipped, which can potentially
change the hue and/or luminance. If this is not desired, it is
recommended to use ``icc-profile`` with an sRGB ICC profile instead,
when playing back wide-gamut BT.2020 content.
``pbo`` ``pbo``
Enable use of PBOs. This is slightly faster, but can sometimes lead to Enable use of PBOs. This is slightly faster, but can sometimes lead to
sporadic and temporary image corruption (in theory, because reupload sporadic and temporary image corruption (in theory, because reupload
@@ -521,7 +527,7 @@ Available video output drivers are:
``3dlut-size=<r>x<g>x<b>`` ``3dlut-size=<r>x<g>x<b>``
Size of the 3D LUT generated from the ICC profile in each dimension. Size of the 3D LUT generated from the ICC profile in each dimension.
Default is 128x256x64. Default is 128x256x64.
Sizes must be a power of two, and 256 at most. Sizes must be a power of two, and 512 at most.
``alpha=<blend|yes|no>`` ``alpha=<blend|yes|no>``
Decides what to do if the input has an alpha component (default: blend). Decides what to do if the input has an alpha component (default: blend).

View File

@@ -42,7 +42,7 @@ const char *const mp_csp_names[MP_CSP_COUNT] = {
"BT.601 (SD)", "BT.601 (SD)",
"BT.709 (HD)", "BT.709 (HD)",
"SMPTE-240M", "SMPTE-240M",
"BT.2020 (NC)", "BT.2020-NC (UHD)",
"RGB", "RGB",
"XYZ", "XYZ",
"YCgCo", "YCgCo",
@@ -54,6 +54,14 @@ const char *const mp_csp_levels_names[MP_CSP_LEVELS_COUNT] = {
"PC", "PC",
}; };
const char *const mp_csp_prim_names[MP_CSP_PRIM_COUNT] = {
"Autoselect",
"BT.601 (525-line SD)",
"BT.601 (625-line SD)",
"BT.709 (HD)",
"BT.2020 (UHD)",
};
const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = { const char *const mp_csp_equalizer_names[MP_CSP_EQ_COUNT] = {
"brightness", "brightness",
"contrast", "contrast",
@@ -93,6 +101,20 @@ enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange)
} }
} }
enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri)
{
switch (avpri) {
case AVCOL_PRI_SMPTE240M: // Same as below
case AVCOL_PRI_SMPTE170M: return MP_CSP_PRIM_BT_601_525;
case AVCOL_PRI_BT470BG: return MP_CSP_PRIM_BT_601_625;
case AVCOL_PRI_BT709: return MP_CSP_PRIM_BT_709;
#if HAVE_AVCOL_SPC_BT2020
case AVCOL_PRI_BT2020: return MP_CSP_PRIM_BT_2020;
#endif
default: return MP_CSP_PRIM_AUTO;
}
}
int mp_csp_to_avcol_spc(enum mp_csp colorspace) int mp_csp_to_avcol_spc(enum mp_csp colorspace)
{ {
switch (colorspace) { switch (colorspace) {
@@ -117,6 +139,19 @@ int mp_csp_levels_to_avcol_range(enum mp_csp_levels range)
} }
} }
int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim)
{
switch (prim) {
case MP_CSP_PRIM_BT_601_525: return AVCOL_PRI_SMPTE170M;
case MP_CSP_PRIM_BT_601_625: return AVCOL_PRI_BT470BG;
case MP_CSP_PRIM_BT_709: return AVCOL_PRI_BT709;
#if HAVE_AVCOL_SPC_BT2020
case MP_CSP_PRIM_BT_2020: return AVCOL_PRI_BT2020;
#endif
default: return AVCOL_PRI_UNSPECIFIED;
}
}
enum mp_csp mp_csp_guess_colorspace(int width, int height) enum mp_csp mp_csp_guess_colorspace(int width, int height)
{ {
return width >= 1280 || height > 576 ? MP_CSP_BT_709 : MP_CSP_BT_601; return width >= 1280 || height > 576 ? MP_CSP_BT_709 : MP_CSP_BT_601;
@@ -208,6 +243,53 @@ static void luma_coeffs(float m[3][4], float lr, float lg, float lb)
// Constant coefficients (m[x][3]) not set here // Constant coefficients (m[x][3]) not set here
} }
/**
* Get the coefficients of the source -> bt2020 gamut mapping matrix,
* given an identifier describing the source gamut primaries.
*/
void mp_get_cms_matrix(enum mp_csp_prim source, float m[3][3])
{
// Conversion matrices to BT.2020 primaries
// These were computed using: http://lpaste.net/101796
switch (source) {
case MP_CSP_PRIM_BT_601_525: {
static const float from_525[3][3] = {
{0.5952542, 0.3493139, 0.0554319},
{0.0812437, 0.8915033, 0.0272530},
{0.0155123, 0.0819116, 0.9025760},
};
memcpy(m, from_525, sizeof(from_525));
break;
}
case MP_CSP_PRIM_BT_601_625: {
static const float from_625[3][3] = {
{0.6550368, 0.3021610, 0.0428023},
{0.0721406, 0.9166311, 0.0112283},
{0.0171134, 0.0978535, 0.8850332},
};
memcpy(m, from_625, sizeof(from_625));
break;
}
case MP_CSP_PRIM_BT_709: {
static const float from_709[3][3] = {
{0.6274039, 0.3292830, 0.0433131},
{0.0690973, 0.9195404, 0.0113623},
{0.0163914, 0.0880133, 0.8955953},
};
memcpy(m, from_709, sizeof(from_709));
break;
}
case MP_CSP_PRIM_BT_2020:
default: {
// No conversion necessary. This matrix should not even be used
// in this case, but assign it to the identity just to be safe.
static const float ident[3][3] = {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
memcpy(m, ident, sizeof(ident));
break;
}
}
}
/** /**
* \brief get the coefficients of the yuv -> rgb conversion matrix * \brief get the coefficients of the yuv -> rgb conversion matrix
* \param params struct specifying the properties of the conversion like * \param params struct specifying the properties of the conversion like

View File

@@ -57,6 +57,18 @@ enum mp_csp_levels {
// Any enum mp_csp_levels value is a valid index (except MP_CSP_LEVELS_COUNT) // Any enum mp_csp_levels value is a valid index (except MP_CSP_LEVELS_COUNT)
extern const char *const mp_csp_levels_names[MP_CSP_LEVELS_COUNT]; extern const char *const mp_csp_levels_names[MP_CSP_LEVELS_COUNT];
enum mp_csp_prim {
MP_CSP_PRIM_AUTO,
MP_CSP_PRIM_BT_601_525,
MP_CSP_PRIM_BT_601_625,
MP_CSP_PRIM_BT_709,
MP_CSP_PRIM_BT_2020,
MP_CSP_PRIM_COUNT
};
// Any enum mp_csp_prim value is a valid index (except MP_CSP_PRIM_COUNT)
extern const char *const mp_csp_prim_names[MP_CSP_PRIM_COUNT];
struct mp_csp_details { struct mp_csp_details {
enum mp_csp format; enum mp_csp format;
enum mp_csp_levels levels_in; // encoded video enum mp_csp_levels levels_in; // encoded video
@@ -141,10 +153,14 @@ enum mp_csp avcol_spc_to_mp_csp(int avcolorspace);
enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange); enum mp_csp_levels avcol_range_to_mp_csp_levels(int avrange);
enum mp_csp_prim avcol_pri_to_mp_csp_prim(int avpri);
int mp_csp_to_avcol_spc(enum mp_csp colorspace); int mp_csp_to_avcol_spc(enum mp_csp colorspace);
int mp_csp_levels_to_avcol_range(enum mp_csp_levels range); int mp_csp_levels_to_avcol_range(enum mp_csp_levels range);
int mp_csp_prim_to_avcol_pri(enum mp_csp_prim prim);
enum mp_csp mp_csp_guess_colorspace(int width, int height); enum mp_csp mp_csp_guess_colorspace(int width, int height);
enum mp_chroma_location avchroma_location_to_mp(int avloc); enum mp_chroma_location avchroma_location_to_mp(int avloc);
@@ -160,6 +176,8 @@ void mp_gen_gamma_map(unsigned char *map, int size, float gamma);
#define COL_U 1 #define COL_U 1
#define COL_V 2 #define COL_V 2
#define COL_C 3 #define COL_C 3
void mp_get_cms_matrix(enum mp_csp_prim source, float m[3][3]);
void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float yuv2rgb[3][4]); void mp_get_yuv2rgb_coeffs(struct mp_csp_params *params, float yuv2rgb[3][4]);
void mp_gen_yuv2rgb_map(struct mp_csp_params *params, uint8_t *map, int size); void mp_gen_yuv2rgb_map(struct mp_csp_params *params, uint8_t *map, int size);

View File

@@ -477,6 +477,7 @@ static void update_image_params(struct dec_video *vd, AVFrame *frame,
.d_h = d_h, .d_h = d_h,
.colorspace = avcol_spc_to_mp_csp(ctx->avctx->colorspace), .colorspace = avcol_spc_to_mp_csp(ctx->avctx->colorspace),
.colorlevels = avcol_range_to_mp_csp_levels(ctx->avctx->color_range), .colorlevels = avcol_range_to_mp_csp_levels(ctx->avctx->color_range),
.primaries = avcol_pri_to_mp_csp_prim(ctx->avctx->color_primaries),
.chroma_location = .chroma_location =
avchroma_location_to_mp(ctx->avctx->chroma_sample_location), avchroma_location_to_mp(ctx->avctx->chroma_sample_location),
.rotate = vd->header->video->rotate, .rotate = vd->header->video->rotate,

View File

@@ -371,6 +371,7 @@ void mp_image_copy_attributes(struct mp_image *dst, struct mp_image *src)
if ((dst->flags & MP_IMGFLAG_YUV) == (src->flags & MP_IMGFLAG_YUV)) { if ((dst->flags & MP_IMGFLAG_YUV) == (src->flags & MP_IMGFLAG_YUV)) {
dst->params.colorspace = src->params.colorspace; dst->params.colorspace = src->params.colorspace;
dst->params.colorlevels = src->params.colorlevels; dst->params.colorlevels = src->params.colorlevels;
dst->params.primaries = src->params.primaries;
dst->params.chroma_location = src->params.chroma_location; dst->params.chroma_location = src->params.chroma_location;
} }
if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) { if ((dst->fmt.flags & MP_IMGFLAG_PAL) && (src->fmt.flags & MP_IMGFLAG_PAL)) {
@@ -486,6 +487,7 @@ bool mp_image_params_equal(const struct mp_image_params *p1,
p1->colorspace == p2->colorspace && p1->colorspace == p2->colorspace &&
p1->colorlevels == p2->colorlevels && p1->colorlevels == p2->colorlevels &&
p1->outputlevels == p2->outputlevels && p1->outputlevels == p2->outputlevels &&
p1->primaries == p2->primaries &&
p1->chroma_location == p2->chroma_location && p1->chroma_location == p2->chroma_location &&
p1->rotate == p2->rotate; p1->rotate == p2->rotate;
} }
@@ -533,16 +535,43 @@ void mp_image_params_guess_csp(struct mp_image_params *params)
params->colorspace = mp_csp_guess_colorspace(params->w, params->h); params->colorspace = mp_csp_guess_colorspace(params->w, params->h);
if (params->colorlevels == MP_CSP_LEVELS_AUTO) if (params->colorlevels == MP_CSP_LEVELS_AUTO)
params->colorlevels = MP_CSP_LEVELS_TV; params->colorlevels = MP_CSP_LEVELS_TV;
if (params->primaries == MP_CSP_PRIM_AUTO) {
// 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) {
params->primaries = MP_CSP_PRIM_BT_2020;
} else {
params->primaries = MP_CSP_PRIM_BT_709;
}
}
} else if (fmt.flags & MP_IMGFLAG_RGB) { } else if (fmt.flags & MP_IMGFLAG_RGB) {
params->colorspace = MP_CSP_RGB; params->colorspace = MP_CSP_RGB;
params->colorlevels = MP_CSP_LEVELS_PC; params->colorlevels = MP_CSP_LEVELS_PC;
// The majority of RGB content is either sRGB or (rarely) some other
// color space which we don't even handle, like AdobeRGB or
// ProPhotoRGB. The only reasonable thing we can do is assume it's
// sRGB and hope for the best, which should usually just work out fine.
// Note: sRGB primaries = BT.709 primaries
if (params->primaries == MP_CSP_PRIM_AUTO)
params->primaries = MP_CSP_PRIM_BT_709;
} else if (fmt.flags & MP_IMGFLAG_XYZ) { } else if (fmt.flags & MP_IMGFLAG_XYZ) {
params->colorspace = MP_CSP_XYZ; params->colorspace = MP_CSP_XYZ;
params->colorlevels = MP_CSP_LEVELS_PC; params->colorlevels = MP_CSP_LEVELS_PC;
// The default XYZ matrix converts it to BT.709 color space
// since that's the most likely scenario. Proper VOs should ignore
// this field as well as the matrix and treat XYZ input as absolute,
// but for VOs which use the matrix (and hence, consult this field)
// this is the correct parameter.
if (params->primaries == MP_CSP_PRIM_AUTO)
params->primaries = MP_CSP_PRIM_BT_709;
} else { } else {
// We have no clue. // We have no clue.
params->colorspace = MP_CSP_AUTO; params->colorspace = MP_CSP_AUTO;
params->colorlevels = MP_CSP_LEVELS_AUTO; params->colorlevels = MP_CSP_LEVELS_AUTO;
params->primaries = MP_CSP_PRIM_AUTO;
} }
} }

View File

@@ -47,6 +47,7 @@ struct mp_image_params {
int d_w, d_h; // define display aspect ratio (never 0/0) int d_w, d_h; // define display aspect ratio (never 0/0)
enum mp_csp colorspace; enum mp_csp colorspace;
enum mp_csp_levels colorlevels; enum mp_csp_levels colorlevels;
enum mp_csp_prim primaries;
enum mp_chroma_location chroma_location; enum mp_chroma_location chroma_location;
// The image should be converted to these levels. Unlike colorlevels, it // The image should be converted to these levels. Unlike colorlevels, it
// does not describe the current state of the image. (Somewhat similar to // does not describe the current state of the image. (Somewhat similar to

View File

@@ -46,7 +46,7 @@ static bool parse_3dlut_size(const char *arg, int *p1, int *p2, int *p3)
return false; return false;
for (int n = 0; n < 3; n++) { for (int n = 0; n < 3; n++) {
int s = ((int[]) { *p1, *p2, *p3 })[n]; int s = ((int[]) { *p1, *p2, *p3 })[n];
if (s < 2 || s > 256 || ((s - 1) & s)) if (s < 2 || s > 512 || ((s - 1) & s))
return false; return false;
} }
return true; return true;
@@ -135,8 +135,8 @@ struct lut3d *mp_load_icc(struct mp_icc_opts *opts, struct mp_log *log,
char *cache_info = char *cache_info =
// Gamma is included in the header to help uniquely identify it, // Gamma is included in the header to help uniquely identify it,
// because we may change the parameter in the future or make it // because we may change the parameter in the future or make it
// customizable. // customizable, same for the primaries.
talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d, gamma=2.4", talloc_asprintf(tmp, "intent=%d, size=%dx%dx%d, gamma=2.4, prim=bt2020\n",
opts->intent, s_r, s_g, s_b); opts->intent, s_r, s_g, s_b);
// check cache // check cache
@@ -165,17 +165,19 @@ struct lut3d *mp_load_icc(struct mp_icc_opts *opts, struct mp_log *log,
if (!profile) if (!profile)
goto error_exit; goto error_exit;
cmsCIExyY d65 = {0.3127, 0.3290, 1.0}; // We always generate the 3DLUT against BT.2020, and transform into this
static const cmsCIExyYTRIPLE bt709prim = { // space inside the shader if the source differs.
.Red = {0.64, 0.33, 1.0}, static const cmsCIExyY d65 = {0.3127, 0.3290, 1.0};
.Green = {0.30, 0.60, 1.0}, static const cmsCIExyYTRIPLE bt2020prim = {
.Blue = {0.15, 0.06, 1.0}, .Red = {0.708, 0.292, 1.0},
.Green = {0.170, 0.797, 1.0},
.Blue = {0.131, 0.046, 1.0},
}; };
// 2.4 is arbitrarily used as a gamma compression factor for the 3DLUT, // 2.4 is arbitrarily used as a gamma compression factor for the 3DLUT,
// reducing artifacts due to rounding errors on wide gamut profiles // reducing artifacts due to rounding errors on wide gamut profiles
cmsToneCurve *tonecurve = cmsBuildGamma(cms, 2.4); cmsToneCurve *tonecurve = cmsBuildGamma(cms, 2.4);
cmsHPROFILE vid_profile = cmsCreateRGBProfileTHR(cms, &d65, &bt709prim, cmsHPROFILE vid_profile = cmsCreateRGBProfileTHR(cms, &d65, &bt2020prim,
(cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve}); (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve});
cmsFreeToneCurve(tonecurve); cmsFreeToneCurve(tonecurve);
cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_profile, TYPE_RGB_16, cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_profile, TYPE_RGB_16,

View File

@@ -639,6 +639,13 @@ static void update_uniforms(struct gl_video *p, GLuint program)
gl->Uniform1i(gl->GetUniformLocation(program, "lut_3d"), TEXUNIT_3DLUT); gl->Uniform1i(gl->GetUniformLocation(program, "lut_3d"), TEXUNIT_3DLUT);
loc = gl->GetUniformLocation(program, "cms_matrix");
if (loc >= 0) {
float cms_matrix[3][3] = {{0}};
mp_get_cms_matrix(p->image_params.primaries, cms_matrix);
gl->UniformMatrix3fv(loc, 1, GL_TRUE, &cms_matrix[0][0]);
}
for (int n = 0; n < 2; n++) { for (int n = 0; n < 2; n++) {
const char *lut = p->scalers[n].lut_name; const char *lut = p->scalers[n].lut_name;
if (lut) if (lut)
@@ -840,6 +847,9 @@ static void compile_shaders(struct gl_video *p)
char *header = talloc_asprintf(tmp, "#version %d\n%s%s", gl->glsl_version, char *header = talloc_asprintf(tmp, "#version %d\n%s%s", gl->glsl_version,
shader_prelude, PRELUDE_END); shader_prelude, PRELUDE_END);
bool use_cms = p->opts.srgb || p->use_lut_3d;
bool use_cms_matrix = use_cms && (p->image_params.primaries != MP_CSP_PRIM_BT_2020);
if (p->gl_target == GL_TEXTURE_RECTANGLE) { if (p->gl_target == GL_TEXTURE_RECTANGLE) {
shader_def(&header, "VIDEO_SAMPLER", "sampler2DRect"); shader_def(&header, "VIDEO_SAMPLER", "sampler2DRect");
shader_def_opt(&header, "USE_RECTANGLE", true); shader_def_opt(&header, "USE_RECTANGLE", true);
@@ -852,8 +862,8 @@ static void compile_shaders(struct gl_video *p)
shader_def_opt(&header, "USE_ALPHA", p->has_alpha); shader_def_opt(&header, "USE_ALPHA", p->has_alpha);
char *header_osd = talloc_strdup(tmp, header); char *header_osd = talloc_strdup(tmp, header);
shader_def_opt(&header_osd, "USE_OSD_LINEAR_CONV", p->opts.srgb || shader_def_opt(&header_osd, "USE_OSD_LINEAR_CONV", use_cms);
p->use_lut_3d); shader_def_opt(&header_osd, "USE_OSD_CMS_MATRIX", use_cms_matrix);
shader_def_opt(&header_osd, "USE_OSD_3DLUT", p->use_lut_3d); shader_def_opt(&header_osd, "USE_OSD_3DLUT", p->use_lut_3d);
// 3DLUT overrides SRGB // 3DLUT overrides SRGB
shader_def_opt(&header_osd, "USE_OSD_SRGB", !p->use_lut_3d && p->opts.srgb); shader_def_opt(&header_osd, "USE_OSD_SRGB", !p->use_lut_3d && p->opts.srgb);
@@ -888,7 +898,7 @@ static void compile_shaders(struct gl_video *p)
// Linear light scaling is only enabled when either color correction // Linear light scaling is only enabled when either color correction
// option (3dlut or srgb) is enabled, otherwise scaling is done in the // option (3dlut or srgb) is enabled, otherwise scaling is done in the
// source space. // source space.
bool convert_to_linear_gamma = !p->is_linear_rgb && (p->opts.srgb || p->use_lut_3d); bool convert_to_linear_gamma = !p->is_linear_rgb && use_cms;
if (p->image_format == IMGFMT_NV12 || p->image_format == IMGFMT_NV21) { if (p->image_format == IMGFMT_NV12 || p->image_format == IMGFMT_NV21) {
shader_def(&header_conv, "USE_CONV", "CONV_NV12"); shader_def(&header_conv, "USE_CONV", "CONV_NV12");
@@ -912,6 +922,7 @@ static void compile_shaders(struct gl_video *p)
shader_def(&header_conv, "USE_ALPHA_BLEND", "1"); shader_def(&header_conv, "USE_ALPHA_BLEND", "1");
shader_def_opt(&header_final, "USE_GAMMA_POW", p->opts.gamma > 0); shader_def_opt(&header_final, "USE_GAMMA_POW", p->opts.gamma > 0);
shader_def_opt(&header_final, "USE_CMS_MATRIX", use_cms_matrix);
shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d); shader_def_opt(&header_final, "USE_3DLUT", p->use_lut_3d);
// 3DLUT overrides SRGB // 3DLUT overrides SRGB
shader_def_opt(&header_final, "USE_SRGB", p->opts.srgb && !p->use_lut_3d); shader_def_opt(&header_final, "USE_SRGB", p->opts.srgb && !p->use_lut_3d);

View File

@@ -56,6 +56,13 @@ vec3 bt2020_expand(vec3 v)
} }
#endif #endif
// Constant matrix for conversion from BT.2020 to sRGB
const mat3 srgb_matrix = mat3(
1.6604910, -0.1245505, -0.0181508,
-0.5876411, 1.1328999, -0.1005789,
-0.0728499, -0.0083494, 1.1187297
);
#!section vertex_all #!section vertex_all
#if __VERSION__ < 130 #if __VERSION__ < 130
@@ -66,6 +73,7 @@ vec3 bt2020_expand(vec3 v)
uniform mat3 transform; uniform mat3 transform;
uniform sampler3D lut_3d; uniform sampler3D lut_3d;
uniform mat3 cms_matrix; // transformation from file's gamut to bt.2020
in vec2 vertex_position; in vec2 vertex_position;
in vec4 vertex_color; in vec4 vertex_color;
@@ -89,12 +97,17 @@ void main() {
// NOTE: This always applies the true BT2020, maybe we need to use // NOTE: This always applies the true BT2020, maybe we need to use
// approx-gamma here too? // approx-gamma here too?
#endif #endif
#ifdef USE_OSD_CMS_MATRIX
// Convert to the right target gamut first (to BT.709 for sRGB,
// and to BT.2020 for 3DLUT).
color.rgb = clamp(cms_matrix * color.rgb, 0, 1);
#endif
#ifdef USE_OSD_3DLUT #ifdef USE_OSD_3DLUT
color.rgb = pow(color.rgb, vec3(1/2.4)); // linear -> 2.4 3DLUT space color.rgb = pow(color.rgb, vec3(1/2.4)); // linear -> 2.4 3DLUT space
color = vec4(texture3D(lut_3d, color.rgb).rgb, color.a); color = vec4(texture3D(lut_3d, color.rgb).rgb, color.a);
#endif #endif
#ifdef USE_OSD_SRGB #ifdef USE_OSD_SRGB
color.rgb = srgb_compand(color.rgb); color.rgb = srgb_compand(clamp(srgb_matrix * color.rgb, 0, 1));
#endif #endif
texcoord = vertex_texcoord; texcoord = vertex_texcoord;
@@ -136,6 +149,7 @@ uniform sampler2D lut_l_2d;
uniform sampler3D lut_3d; uniform sampler3D lut_3d;
uniform sampler2D dither; uniform sampler2D dither;
uniform mat4x3 colormatrix; uniform mat4x3 colormatrix;
uniform mat3 cms_matrix;
uniform mat2 dither_trafo; uniform mat2 dither_trafo;
uniform vec3 inv_gamma; uniform vec3 inv_gamma;
uniform float input_gamma; uniform float input_gamma;
@@ -406,16 +420,28 @@ void main() {
// User-defined gamma correction factor (via the gamma sub-option) // User-defined gamma correction factor (via the gamma sub-option)
color = pow(color, inv_gamma); color = pow(color, inv_gamma);
#endif #endif
#ifdef USE_CMS_MATRIX
// Convert to the right target gamut first (to BT.709 for sRGB,
// and to BT.2020 for 3DLUT).
color = cms_matrix * color;
#endif
#ifdef USE_3DLUT #ifdef USE_3DLUT
// For the 3DLUT we are arbitrarily using 2.4 as input gamma to reduce // For the 3DLUT we are arbitrarily using 2.4 as input gamma to reduce
// the amount of rounding errors, so we pull up to that space first and // the amount of rounding errors, so we pull up to that space first and
// then pass it through the 3D texture. // then pass it through the 3D texture.
color = pow(color, vec3(1/2.4)); //
// The value is clamped to [0,1] first because the gamma function is not
// well-defined outside it. This should not be a problem because the 3dlut
// is not defined for values outside its boundaries either way, and no
// media can possibly exceed its BT.2020 source gamut either way due to
// that being the biggest taggable color space. This is just to avoid
// numerical quirks like -1e-30 turning into NaN.
color = pow(clamp(color, 0, 1), vec3(1/2.4));
color = texture3D(lut_3d, color).rgb; color = texture3D(lut_3d, color).rgb;
#endif #endif
#ifdef USE_SRGB #ifdef USE_SRGB
// Compand from the linear scaling gamma to the sRGB output gamma // Adapt and compand from the linear BT2020 source to the sRGB output
color = srgb_compand(color.rgb); color = srgb_compand(clamp(srgb_matrix * color, 0, 1));
#endif #endif
#ifdef USE_DITHER #ifdef USE_DITHER
vec2 dither_pos = gl_FragCoord.xy / dither_size; vec2 dither_pos = gl_FragCoord.xy / dither_size;