diff options
-rw-r--r-- | video/out/opengl/lcms.c | 123 | ||||
-rw-r--r-- | video/out/opengl/lcms.h | 6 | ||||
-rw-r--r-- | video/out/opengl/video.c | 72 | ||||
-rw-r--r-- | video/out/opengl/video.h | 6 | ||||
-rw-r--r-- | video/out/opengl/video_shaders.c | 5 | ||||
-rw-r--r-- | video/out/vo_opengl.c | 24 | ||||
-rw-r--r-- | video/out/vo_opengl_cb.c | 2 |
7 files changed, 162 insertions, 76 deletions
diff --git a/video/out/opengl/lcms.c b/video/out/opengl/lcms.c index c956127a75..8f6b40830a 100644 --- a/video/out/opengl/lcms.c +++ b/video/out/opengl/lcms.c @@ -44,6 +44,8 @@ struct gl_lcms { size_t icc_size; char *icc_path; bool changed; + enum mp_csp_prim prev_prim; + enum mp_csp_trc prev_trc; struct mp_log *log; struct mpv_global *global; @@ -166,16 +168,88 @@ void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile) p->icc_size = profile->len; } -// Return and _reset_ whether the lookul table has changed since the last call. -// If it has changed, gl_lcms_get_lut3d() should be called. -bool gl_lcms_has_changed(struct gl_lcms *p) +// Return and _reset_ whether the profile or config has changed since the last +// call. If it has changed, gl_lcms_get_lut3d() should be called. +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) { - bool change = p->changed; + bool change = p->changed || p->prev_prim != prim || p->prev_trc != trc; p->changed = false; + p->prev_prim = prim; + p->prev_trc = trc; return change; } -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) +static cmsHPROFILE get_vid_profile(cmsContext cms, cmsHPROFILE disp_profile, + enum mp_csp_prim prim, enum mp_csp_trc trc) +{ + // The input profile for the transformation is dependent on the video + // primaries and transfer characteristics + struct mp_csp_primaries csp = mp_get_csp_primaries(prim); + cmsCIExyY wp_xyY = {csp.white.x, csp.white.y, 1.0}; + cmsCIExyYTRIPLE prim_xyY = { + .Red = {csp.red.x, csp.red.y, 1.0}, + .Green = {csp.green.x, csp.green.y, 1.0}, + .Blue = {csp.blue.x, csp.blue.y, 1.0}, + }; + + cmsToneCurve *tonecurve = NULL; + switch (trc) { + case MP_CSP_TRC_LINEAR: tonecurve = cmsBuildGamma(cms, 1.0); break; + case MP_CSP_TRC_GAMMA18: tonecurve = cmsBuildGamma(cms, 1.8); break; + case MP_CSP_TRC_GAMMA22: tonecurve = cmsBuildGamma(cms, 2.2); break; + case MP_CSP_TRC_GAMMA28: tonecurve = cmsBuildGamma(cms, 2.8); break; + + case MP_CSP_TRC_SRGB: + // Values copied from Little-CMS + tonecurve = cmsBuildParametricToneCurve(cms, 4, + (double[5]){2.40, 1/1.055, 0.055/1.055, 1/12.92, 0.04045}); + break; + + case MP_CSP_TRC_PRO_PHOTO: + tonecurve = cmsBuildParametricToneCurve(cms, 4, + (double[5]){1.8, 1.0, 0.0, 1/16.0, 0.03125}); + break; + + case MP_CSP_TRC_BT_1886: { + // To build an appropriate BT.1886 transformation we need access to + // the display's black point, so we use the reverse mappings + cmsHPROFILE xyz_profile = cmsCreateXYZProfileTHR(cms); + cmsHTRANSFORM rgb2xyz = cmsCreateTransformTHR(cms, + disp_profile, TYPE_RGB_16, xyz_profile, TYPE_XYZ_DBL, + INTENT_RELATIVE_COLORIMETRIC, 0); + cmsCloseProfile(xyz_profile); + if (!rgb2xyz) + return false; + + uint64_t black[3] = {0}; + cmsCIEXYZ disp_black; + cmsDoTransform(rgb2xyz, black, &disp_black, 1); + + // Build the parametric BT.1886 transfer curve + const double gamma = 2.40; + double binv = pow(disp_black.Y, 1.0/gamma); + tonecurve = cmsBuildParametricToneCurve(cms, 6, + (double[4]){gamma, 1.0 - binv, binv, 0.0}); + break; + } + + default: + abort(); + } + + if (!tonecurve) + return false; + + cmsHPROFILE *vid_profile = cmsCreateRGBProfileTHR(cms, &wp_xyY, &prim_xyY, + (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve}); + cmsFreeToneCurve(tonecurve); + + return vid_profile; +} + +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, + enum mp_csp_prim prim, enum mp_csp_trc trc) { int s_r, s_g, s_b; bool result = false; @@ -197,8 +271,8 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) // because we may change the parameter in the future or make it // customizable, same for the primaries. char *cache_info = talloc_asprintf(tmp, - "ver=1.1, intent=%d, size=%dx%dx%d, gamma=2.4, prim=bt2020\n", - p->opts.intent, s_r, s_g, s_b); + "ver=1.2, intent=%d, size=%dx%dx%d, prim=%d, trc=%d\n", + p->opts.intent, s_r, s_g, s_b, prim, trc); uint8_t hash[32]; struct AVSHA *sha = av_sha_alloc(); @@ -242,23 +316,12 @@ bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d) if (!profile) goto error_exit; - // We always generate the 3DLUT against BT.2020, and transform into this - // space inside the shader if the source differs. - struct mp_csp_primaries csp = mp_get_csp_primaries(MP_CSP_PRIM_BT_2020); - - cmsCIExyY wp = {csp.white.x, csp.white.y, 1.0}; - cmsCIExyYTRIPLE prim = { - .Red = {csp.red.x, csp.red.y, 1.0}, - .Green = {csp.green.x, csp.green.y, 1.0}, - .Blue = {csp.blue.x, csp.blue.y, 1.0}, - }; + cmsHPROFILE vid_profile = get_vid_profile(cms, profile, prim, trc); + if (!vid_profile) { + cmsCloseProfile(profile); + goto error_exit; + } - // 2.4 is arbitrarily used as a gamma compression factor for the 3DLUT, - // reducing artifacts due to rounding errors on wide gamut profiles - cmsToneCurve *tonecurve = cmsBuildGamma(cms, 2.4); - cmsHPROFILE vid_profile = cmsCreateRGBProfileTHR(cms, &wp, &prim, - (cmsToneCurve*[3]){tonecurve, tonecurve, tonecurve}); - cmsFreeToneCurve(tonecurve); cmsHTRANSFORM trafo = cmsCreateTransformTHR(cms, vid_profile, TYPE_RGB_16, profile, TYPE_RGB_16, p->opts.intent, @@ -333,7 +396,17 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, void gl_lcms_set_options(struct gl_lcms *p, struct mp_icc_opts *opts) { } void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile) { } -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **x) { return false; } -bool gl_lcms_has_changed(struct gl_lcms *p) { return false; } + +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) +{ + return false; +} + +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **result_lut3d, + enum mp_csp_prim prim, enum mp_csp_trc trc) +{ + return false; +} #endif diff --git a/video/out/opengl/lcms.h b/video/out/opengl/lcms.h index 5ad08b7d64..ee2a48b59c 100644 --- a/video/out/opengl/lcms.h +++ b/video/out/opengl/lcms.h @@ -24,7 +24,9 @@ struct gl_lcms *gl_lcms_init(void *talloc_ctx, struct mp_log *log, struct mpv_global *global); void gl_lcms_set_options(struct gl_lcms *p, struct mp_icc_opts *opts); void gl_lcms_set_memory_profile(struct gl_lcms *p, bstr *profile); -bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **); -bool gl_lcms_has_changed(struct gl_lcms *p); +bool gl_lcms_get_lut3d(struct gl_lcms *p, struct lut3d **, + enum mp_csp_prim prim, enum mp_csp_trc trc); +bool gl_lcms_has_changed(struct gl_lcms *p, enum mp_csp_prim prim, + enum mp_csp_trc trc); #endif diff --git a/video/out/opengl/video.c b/video/out/opengl/video.c index e9bafc0d3f..c2d5fc211d 100644 --- a/video/out/opengl/video.c +++ b/video/out/opengl/video.c @@ -148,6 +148,7 @@ struct gl_video { struct mpv_global *global; struct mp_log *log; struct gl_video_opts opts; + struct gl_lcms *cms; bool gl_debug; int texture_16bit_depth; // actual bits available in 16 bit textures @@ -693,21 +694,31 @@ static void uninit_rendering(struct gl_video *p) gl_video_reset_surfaces(p); } -void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d) +void gl_video_update_profile(struct gl_video *p) +{ + if (p->use_lut_3d) + return; + + p->use_lut_3d = true; + check_gl_features(p); + + reinit_rendering(p); +} + +static bool gl_video_get_lut3d(struct gl_video *p, enum mp_csp_prim prim, + enum mp_csp_trc trc) { GL *gl = p->gl; - if (!lut3d) { - if (p->use_lut_3d) { - p->use_lut_3d = false; - reinit_rendering(p); - } - return; - } + if (!p->cms || !p->use_lut_3d) + return false; - if (!(gl->mpgl_caps & MPGL_CAP_3D_TEX) || gl->es) { - MP_ERR(p, "16 bit fixed point 3D textures not available.\n"); - return; + if (!gl_lcms_has_changed(p->cms, prim, trc)) + return true; + + struct lut3d *lut3d = NULL; + if (!gl_lcms_get_lut3d(p->cms, &lut3d, prim, trc) || !lut3d) { + return false; } if (!p->lut_3d_texture) @@ -724,12 +735,9 @@ void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d) gl->TexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); gl->ActiveTexture(GL_TEXTURE0); - p->use_lut_3d = true; - check_gl_features(p); - debug_check_gl(p, "after 3d lut creation"); - reinit_rendering(p); + return true; } // Fill an img_tex struct from an FBO + some metadata @@ -1868,10 +1876,16 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, enum mp_csp_prim prim_dst = p->opts.target_prim; if (p->use_lut_3d) { - // The 3DLUT is hard-coded against BT.2020's gamut during creation, and - // we never want to adjust its output (so treat it as linear) - prim_dst = MP_CSP_PRIM_BT_2020; - trc_dst = MP_CSP_TRC_LINEAR; + // The 3DLUT is always generated against the original source space + enum mp_csp_prim prim_orig = p->image_params.primaries; + enum mp_csp_trc trc_orig = p->image_params.gamma; + + if (gl_video_get_lut3d(p, prim_orig, trc_orig)) { + prim_dst = prim_orig; + trc_dst = trc_orig; + } else { + p->use_lut_3d = false; + } } if (prim_dst == MP_CSP_PRIM_AUTO) @@ -1885,10 +1899,10 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, trc_dst = MP_CSP_TRC_GAMMA22; } - bool need_cms = prim_src != prim_dst || p->use_lut_3d; - bool need_gamma = trc_src != trc_dst || need_cms; + bool need_gamma = trc_src != trc_dst || prim_src != prim_dst; if (need_gamma) pass_linearize(p->sc, trc_src); + // Adapt to the right colorspace if necessary if (prim_src != prim_dst) { struct mp_csp_primaries csp_src = mp_get_csp_primaries(prim_src), @@ -1898,16 +1912,14 @@ static void pass_colormanage(struct gl_video *p, enum mp_csp_prim prim_src, gl_sc_uniform_mat3(p->sc, "cms_matrix", true, &m[0][0]); GLSL(color.rgb = cms_matrix * color.rgb;) } + + if (need_gamma) + pass_delinearize(p->sc, trc_dst); + if (p->use_lut_3d) { gl_sc_uniform_sampler(p->sc, "lut_3d", GL_TEXTURE_3D, TEXUNIT_3DLUT); - // For the 3DLUT we are arbitrarily using 2.4 as input gamma to reduce - // the severity of quantization errors. - GLSL(color.rgb = clamp(color.rgb, 0.0, 1.0);) - GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) GLSL(color.rgb = texture3D(lut_3d, color.rgb).rgb;) } - if (need_gamma) - pass_delinearize(p->sc, trc_dst); } static void pass_dither(struct gl_video *p) @@ -2681,7 +2693,7 @@ static void check_gl_features(struct gl_video *p) // GLES3 doesn't provide filtered 16 bit integer textures // GLES2 doesn't even provide 3D textures - if (p->use_lut_3d && !(have_3d_tex && have_float_tex)) { + if (p->use_lut_3d && (!have_3d_tex || gl->es)) { p->use_lut_3d = false; MP_WARN(p, "Disabling color management (GLES unsupported).\n"); } @@ -3001,7 +3013,8 @@ void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd) recreate_osd(p); } -struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g) +struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g, + struct gl_lcms *cms) { if (gl->version < 210 && gl->es < 200) { mp_err(log, "At least OpenGL 2.1 or OpenGL ES 2.0 required.\n"); @@ -3013,6 +3026,7 @@ struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g) .gl = gl, .global = g, .log = log, + .cms = cms, .opts = gl_video_opts_def, .gl_target = GL_TEXTURE_2D, .texture_16bit_depth = 16, diff --git a/video/out/opengl/video.h b/video/out/opengl/video.h index 23b6c86cb1..4f9d497997 100644 --- a/video/out/opengl/video.h +++ b/video/out/opengl/video.h @@ -24,6 +24,7 @@ #include "sub/osd.h" #include "common.h" #include "utils.h" +#include "lcms.h" #include "video/out/filter_kernels.h" // Texture units 0-5 are used by the video, and for free use by the passes @@ -125,14 +126,15 @@ extern const struct gl_video_opts gl_video_opts_def; struct gl_video; struct vo_frame; -struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g); +struct gl_video *gl_video_init(GL *gl, struct mp_log *log, struct mpv_global *g, + struct gl_lcms *cms); void gl_video_uninit(struct gl_video *p); void gl_video_set_osd_source(struct gl_video *p, struct osd_state *osd); void gl_video_set_options(struct gl_video *p, struct gl_video_opts *opts); bool gl_video_check_format(struct gl_video *p, int mp_format); void gl_video_config(struct gl_video *p, struct mp_image_params *params); void gl_video_set_output_depth(struct gl_video *p, int r, int g, int b); -void gl_video_set_lut3d(struct gl_video *p, struct lut3d *lut3d); +void gl_video_update_profile(struct gl_video *p); void gl_video_render_frame(struct gl_video *p, struct vo_frame *frame, int fbo); void gl_video_resize(struct gl_video *p, int vp_w, int vp_h, struct mp_rect *src, struct mp_rect *dst, diff --git a/video/out/opengl/video_shaders.c b/video/out/opengl/video_shaders.c index 62feb47738..bea1bbf325 100644 --- a/video/out/opengl/video_shaders.c +++ b/video/out/opengl/video_shaders.c @@ -250,7 +250,8 @@ 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: - GLSL(color.rgb = pow(color.rgb, vec3(1.961));) + // 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: GLSL(color.rgb = pow(color.rgb, vec3(1.8));) @@ -284,7 +285,7 @@ void pass_delinearize(struct gl_shader_cache *sc, enum mp_csp_trc trc) lessThanEqual(vec3(0.0031308), color.rgb));) break; case MP_CSP_TRC_BT_1886: - GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.961));) + GLSL(color.rgb = pow(color.rgb, vec3(1.0/2.4));) break; case MP_CSP_TRC_GAMMA18: GLSL(color.rgb = pow(color.rgb, vec3(1.0/1.8));) diff --git a/video/out/vo_opengl.c b/video/out/vo_opengl.c index 7f4f13f882..dfef6ec500 100644 --- a/video/out/vo_opengl.c +++ b/video/out/vo_opengl.c @@ -217,7 +217,7 @@ static void call_request_hwdec_api(struct mp_hwdec_info *info, vo_control(vo, VOCTRL_LOAD_HWDEC_API, (void *)api_name); } -static bool get_and_update_icc_profile(struct gl_priv *p, int *events) +static void get_and_update_icc_profile(struct gl_priv *p, int *events) { bool has_profile = p->icc_opts->profile && p->icc_opts->profile[0]; if (p->icc_opts->profile_auto && !has_profile) { @@ -233,17 +233,12 @@ static bool get_and_update_icc_profile(struct gl_priv *p, int *events) } gl_lcms_set_memory_profile(p->cms, &icc); + has_profile = true; } } - struct lut3d *lut3d = NULL; - if (!gl_lcms_has_changed(p->cms)) - return true; - if (gl_lcms_get_lut3d(p->cms, &lut3d) && !lut3d) - return false; - gl_video_set_lut3d(p->renderer, lut3d); - talloc_free(lut3d); - return true; + if (has_profile) + gl_video_update_profile(p->renderer); } static void get_and_update_ambient_lighting(struct gl_priv *p, int *events) @@ -416,19 +411,18 @@ static int preinit(struct vo *vo) MP_VERBOSE(vo, "swap_control extension missing.\n"); } - p->renderer = gl_video_init(p->gl, vo->log, vo->global); + p->cms = gl_lcms_init(p, vo->log, vo->global); + if (!p->cms) + goto err_out; + p->renderer = gl_video_init(p->gl, vo->log, vo->global, p->cms); if (!p->renderer) goto err_out; gl_video_set_osd_source(p->renderer, vo->osd); gl_video_set_options(p->renderer, p->renderer_opts); gl_video_configure_queue(p->renderer, vo); - p->cms = gl_lcms_init(p, vo->log, vo->global); - if (!p->cms) - goto err_out; gl_lcms_set_options(p->cms, p->icc_opts); - if (!get_and_update_icc_profile(p, &(int){0})) - goto err_out; + get_and_update_icc_profile(p, &(int){0}); p->hwdec_info.load_api = call_request_hwdec_api; p->hwdec_info.load_api_ctx = vo; diff --git a/video/out/vo_opengl_cb.c b/video/out/vo_opengl_cb.c index 7accfc1a92..40930fbcae 100644 --- a/video/out/vo_opengl_cb.c +++ b/video/out/vo_opengl_cb.c @@ -176,7 +176,7 @@ int mpv_opengl_cb_init_gl(struct mpv_opengl_cb_context *ctx, const char *exts, mpgl_load_functions2(ctx->gl, get_proc_address, get_proc_address_ctx, exts, ctx->log); - ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->global); + ctx->renderer = gl_video_init(ctx->gl, ctx->log, ctx->global, NULL); if (!ctx->renderer) return MPV_ERROR_UNSUPPORTED; |