diff options
author | Niklas Haas <git@nand.wakku.to> | 2016-02-13 15:33:00 +0100 |
---|---|---|
committer | wm4 <wm4@nowhere> | 2016-04-01 10:27:27 +0200 |
commit | 2dcf18c0c01282f0f0e72423038a78c1fc938b02 (patch) | |
tree | 2ca8bae135f1c29f6847e68861f8bb3461e8c8c1 /video/out | |
parent | 0d746522325923ff5926f3a3cd0024b679a8199f (diff) |
vo_opengl: generate 3DLUT against source and use full BT.1886
This commit refactors the 3DLUT loading mechanism to build the 3DLUT
against the original source characteristics of the file. This allows us,
among other things, to use a real BT.1886 profile for the source. This
also allows us to actually use perceptual mappings. Finally, this
reduces errors on standard gamut displays (where the previous 3DLUT
target of BT.2020 was unreasonably wide).
This also improves the overall accuracy of the 3DLUT due to eliminating
rounding errors where possible, and allows for more accurate use of
LUT-based ICC profiles.
The current code is somewhat more ugly than necessary, because the idea
was to implement this commit in a working state first, and then maybe
refactor the profile loading mechanism in a later commit.
Fixes #2815.
Diffstat (limited to 'video/out')
-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; |