From 558cfcb1d2b72492916ecbaa015444a0ca3b1be1 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Sun, 7 Feb 2021 15:35:24 +0100 Subject: [PATCH] shaders/colorspace: refactor icc/3dlut API Renamed from 3DLUT to ICC across the board, and moved to its own conditionally installed header Also, merged with the internal lcms code, since it was a bit redundant now. Mostly to make room for more LUT formats I will be adding, and since colorspace.h was a bit cluttered. --- README.md | 1 + meson.build | 2 +- src/common.h | 4 + src/include/libplacebo/renderer.h | 14 +- src/include/libplacebo/shaders/colorspace.h | 73 ---------- src/include/libplacebo/shaders/icc.h | 117 ++++++++++++++++ src/lcms.h | 28 ---- src/meson.build | 3 +- src/renderer.c | 44 +++--- src/shaders.h | 2 +- src/shaders/colorspace.c | 116 ---------------- src/{lcms.c => shaders/icc.c} | 144 +++++++++++++++++--- src/tests/gpu_tests.h | 18 +-- 13 files changed, 290 insertions(+), 276 deletions(-) create mode 100644 src/include/libplacebo/shaders/icc.h delete mode 100644 src/lcms.h rename src/{lcms.c => shaders/icc.c} (64%) diff --git a/README.md b/README.md index 0a1743da..2c565231 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ routines which libplacebo exports: - `shaders/custom.h`: Allows directly ingesting custom GLSL logic into the `pl_shader` abstraction, either as bare GLSL or in [mpv .hook format](https://mpv.io/manual/master/#options-glsl-shaders). +- `shaders/icc.h`: Shader for ICC profile based color management. - `shaders/sampling.h`: Shader routines for various algorithms that sample from images, such as debanding and scaling. diff --git a/meson.build b/meson.build index 47da7831..ebb64d87 100644 --- a/meson.build +++ b/meson.build @@ -2,7 +2,7 @@ project('libplacebo', ['c', 'cpp'], license: 'LGPL2.1+', default_options: ['c_std=c99', 'cpp_std=c++11', 'warning_level=2'], meson_version: '>=0.51', - version: '3.111.0', + version: '3.112.0', ) # Version number diff --git a/src/common.h b/src/common.h index 18aaa5e9..9efe4b54 100644 --- a/src/common.h +++ b/src/common.h @@ -62,6 +62,10 @@ #include "include/libplacebo/swapchain.h" #include "include/libplacebo/utils/upload.h" +#ifdef PL_HAVE_LCMS +#include "include/libplacebo/shaders/icc.h" +#endif + #ifdef PL_HAVE_VULKAN #include "include/libplacebo/vulkan.h" #endif diff --git a/src/include/libplacebo/renderer.h b/src/include/libplacebo/renderer.h index 5822d055..b67d27be 100644 --- a/src/include/libplacebo/renderer.h +++ b/src/include/libplacebo/renderer.h @@ -112,9 +112,9 @@ struct pl_render_params { // as NULL disables dithering. const struct pl_dither_params *dither_params; - // Configures the settings used to generate a 3DLUT, if required. If NULL, - // defaults to `&pl_3dlut_default_params`. - const struct pl_3dlut_params *lut3d_params; + // Configures the settings used to handle ICC profiles, if required. If + // NULL, defaults to `&pl_icc_default_params`. + const struct pl_icc_params *icc_params; // Configures the settings used to simulate color blindness, if desired. // If NULL, this feature is disabled. @@ -183,10 +183,10 @@ struct pl_render_params { // general-purpose ones. bool disable_builtin_scalers; - // Forces the use of a 3DLUT, even in cases where the use of one is + // Forces the use of an ICC 3DLUT, even in cases where the use of one is // unnecessary. This is slower, but may improve the quality of the gamut // reduction step, if one is performed. - bool force_3dlut; + bool force_icc_lut; // Forces the use of dithering, even when rendering to 16-bit FBOs. This is // generally pretty pointless because most 16-bit FBOs have high enough @@ -197,6 +197,10 @@ struct pl_render_params { // Completely overrides the use of FBOs, as if there were no renderable // texture format available. This disables most features. bool disable_fbos; + + // --- Deprecated aliases + const struct pl_icc_params *lut3d_params PL_DEPRECATED; // fallback for `icc_params` + bool force_3dlut PL_DEPRECATED; // fallback for `force_icc_lut` }; // This contains the default/recommended options for reasonable image quality, diff --git a/src/include/libplacebo/shaders/colorspace.h b/src/include/libplacebo/shaders/colorspace.h index e9be6533..65704aaa 100644 --- a/src/include/libplacebo/shaders/colorspace.h +++ b/src/include/libplacebo/shaders/colorspace.h @@ -21,8 +21,6 @@ // Color space transformation shaders. These all input and output a color // value (PL_SHADER_SIG_COLOR). -#include - #include #include @@ -358,75 +356,4 @@ void pl_shader_dither(struct pl_shader *sh, int new_depth, struct pl_shader_obj **dither_state, const struct pl_dither_params *params); -struct pl_3dlut_params { - // The rendering intent to use when computing the color transformation. A - // recommended value is PL_INTENT_RELATIVE_COLORIMETRIC for color-accurate - // video reproduction, or PL_INTENT_PERCEPTUAL for profiles containing - // meaningful perceptual mapping tables. - enum pl_rendering_intent intent; - - // The size of the 3DLUT to generate. If left as NULL, these individually - // default to 64, which is the recommended default for all three. - size_t size_r, size_g, size_b; -}; - -extern const struct pl_3dlut_params pl_3dlut_default_params; - -struct pl_3dlut_profile { - // The nominal, closest approximation representation of the color profile, - // as permitted by `pl_color_space` enums. This will be used as a fallback - // in the event that an ICC profile is absent, or that parsing the ICC - // profile fails. This is also that will be returned for the corresponding - // field in `pl_3dlut_result` when the ICC profile is in use. - struct pl_color_space color; - - // The ICC profile itself. (Optional) - struct pl_icc_profile profile; -}; - -struct pl_3dlut_result { - // The source color space. This is the color space that the colors should - // actually be in at the point in time that they're ingested by the 3DLUT. - // This may differ from the `pl_color_space color` specified in the - // `pl_color_profile`. Users should make sure to apply - // `pl_shader_color_map` in order to get the colors into this format before - // applying `pl_shader_3dlut`. - // - // Note: `pl_shader_color_map` is a no-op when the source and destination - // color spaces are the same, so this can safely be used without disturbing - // the colors in the event that an ICC profile is actually in use. - struct pl_color_space src_color; - - // The destination color space. This is the color space that the colors - // will (nominally) be in at the time they exit the 3DLUT. - struct pl_color_space dst_color; -}; - -#ifdef PL_HAVE_LCMS - -// Updates/generates a 3DLUT. Returns success. If true, `out` will be updated -// to a struct describing the color space chosen for the input and output of -// the 3DLUT. (See `pl_color_profile`) -// If `params` is NULL, it defaults to &pl_3dlut_default_params. -// -// Note: This function must always be called before `pl_shader_3dlut`, on the -// same `pl_shader` object, The only reason it's separate from `pl_shader_3dlut` -// is to give users a chance to adapt the input colors to the color space -// chosen by the 3DLUT before applying it. -bool pl_3dlut_update(struct pl_shader *sh, - const struct pl_3dlut_profile *src, - const struct pl_3dlut_profile *dst, - struct pl_shader_obj **lut3d, - struct pl_3dlut_result *out, - const struct pl_3dlut_params *params); - -// Actually applies a 3DLUT as generated by `pl_3dlut_update`. The reason this -// is separated from `pl_3dlut_update` is so that the user has the chance to -// correctly map the colors into the specified `src_color` space. This should -// be called only on the `pl_shader_obj` previously updated by -// `pl_3dlut_update`, and only when that function returned true. -void pl_3dlut_apply(struct pl_shader *sh, struct pl_shader_obj **lut3d); - -#endif // PL_HAVE_LCMS - #endif // LIBPLACEBO_SHADERS_COLORSPACE_H_ diff --git a/src/include/libplacebo/shaders/icc.h b/src/include/libplacebo/shaders/icc.h new file mode 100644 index 00000000..a4ad40a8 --- /dev/null +++ b/src/include/libplacebo/shaders/icc.h @@ -0,0 +1,117 @@ +/* + * This file is part of libplacebo. + * + * libplacebo is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * libplacebo is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with libplacebo. If not, see . + */ + +#ifndef LIBPLACEBO_SHADERS_ICC_H_ +#define LIBPLACEBO_SHADERS_ICC_H_ + +// Functions for generating and applying ICC-derived 3DLUTs + +#include +#include + +// ICC profiles + +struct pl_icc_params { + // The rendering intent to use when computing the color transformation. A + // recommended value is PL_INTENT_RELATIVE_COLORIMETRIC for color-accurate + // video reproduction, or PL_INTENT_PERCEPTUAL for profiles containing + // meaningful perceptual mapping tables. + enum pl_rendering_intent intent; + + // The size of the 3DLUT to generate. If left as NULL, these individually + // default to 64, which is the recommended default for all three. + size_t size_r, size_g, size_b; +}; + +extern const struct pl_icc_params pl_icc_default_params; + +struct pl_icc_color_space { + // The nominal, closest approximation representation of the color profile, + // as permitted by `pl_color_space` enums. This will be used as a fallback + // in the event that an ICC profile is absent, or that parsing the ICC + // profile fails. This is also that will be returned for the corresponding + // field in `pl_icc_result` when the ICC profile is in use. + struct pl_color_space color; + + // The ICC profile itself. (Optional) + struct pl_icc_profile profile; +}; + +struct pl_icc_result { + // The source color space. This is the color space that the colors should + // actually be in at the point in time that they're ingested by the 3DLUT. + // This may differ from the `pl_color_space color` specified in the + // `pl_icc_color_space`. Users should make sure to apply + // `pl_shader_color_map` in order to get the colors into this format before + // applying `pl_icc_apply`. + // + // Note: `pl_shader_color_map` is a no-op when the source and destination + // color spaces are the same, so this can safely be used without disturbing + // the colors in the event that an ICC profile is actually in use. + struct pl_color_space src_color; + + // The destination color space. This is the color space that the colors + // will (nominally) be in at the time they exit the 3DLUT. + struct pl_color_space dst_color; +}; + +// Updates/generates a 3DLUT based on ICC profiles. Returns success. If true, +// `out` will be updated to a struct describing the color space chosen for the +// input and output of the 3DLUT. (See `pl_icc_color_space`) If `params` is +// NULL, it defaults to &pl_icc_default_params. +// +// Note: This function must always be called before `pl_icc_apply`, on the +// same `pl_shader` object, The only reason it's separate from `pl_icc_apply` +// is to give users a chance to adapt the input colors to the color space +// chosen by the ICC profile before applying it. +bool pl_icc_update(struct pl_shader *sh, + const struct pl_icc_color_space *src, + const struct pl_icc_color_space *dst, + struct pl_shader_obj **icc, + struct pl_icc_result *out, + const struct pl_icc_params *params); + +// Actually applies a 3DLUT as generated by `pl_icc_update`. The reason this is +// separated from `pl_icc_update` is so that the user has the chance to +// correctly map the colors into the specified `src_color` space. This should +// be called only on the `pl_shader_obj` previously updated by `pl_icc_update`, +// and only when that function returned true. +void pl_icc_apply(struct pl_shader *sh, struct pl_shader_obj **icc); + +// Backwards compatibility aliases +#define pl_3dlut_params pl_icc_params +#define pl_3dlut_default_params pl_icc_default_params +#define pl_3dlut_profile pl_icc_color_space +#define pl_3dlut_result pl_icc_result + +static PL_DEPRECATED inline bool pl_3dlut_update(struct pl_shader *sh, + const struct pl_icc_color_space *src, + const struct pl_icc_color_space *dst, + struct pl_shader_obj **lut3d, + struct pl_icc_result *out, + const struct pl_icc_params *params) +{ + return pl_icc_update(sh, src, dst, lut3d, out, params); +} + +static PL_DEPRECATED inline void pl_3dlut_apply(struct pl_shader *sh, + struct pl_shader_obj **lut3d) +{ + return pl_icc_apply(sh, lut3d); +} + +#endif // LIBPLACEBO_SHADERS_ICC_H_ diff --git a/src/lcms.h b/src/lcms.h deleted file mode 100644 index 5f9f324d..00000000 --- a/src/lcms.h +++ /dev/null @@ -1,28 +0,0 @@ -/* - * This file is part of libplacebo. - * - * libplacebo is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * libplacebo is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with libplacebo. If not, see . - */ - -#pragma once - -#include "common.h" - -// Compute a transformation from one color profile to another, and fill the -// provided array by the resulting 3DLUT. The array must have room for four -// components per sample. -bool pl_lcms_compute_lut(struct pl_context *ctx, enum pl_rendering_intent intent, - struct pl_3dlut_profile src, struct pl_3dlut_profile dst, - float *out_data, int s_r, int s_g, int s_b, - struct pl_3dlut_result *out); diff --git a/src/meson.build b/src/meson.build index c3758657..71ff379a 100644 --- a/src/meson.build +++ b/src/meson.build @@ -213,7 +213,8 @@ components = [ { 'name': 'lcms', 'deps': dependency('lcms2', version: '>=2.6', required: get_option('lcms')), - 'srcs': 'lcms.c', + 'srcs': 'shaders/icc.c', + 'headers': 'shaders/icc.h', }, { 'name': 'glslang', 'deps': glslang_combined, diff --git a/src/renderer.c b/src/renderer.c index e5c3b9d6..5aa8f388 100644 --- a/src/renderer.c +++ b/src/renderer.c @@ -52,7 +52,7 @@ struct pl_renderer { bool disable_linear_sdr; // disable linear scaling for SDR signals bool disable_blending; // disable blending for the target/fbofmt bool disable_overlay; // disable rendering overlays - bool disable_3dlut; // disable usage of a 3DLUT + bool disable_icc; // disable usage of ICC profiles bool disable_peak_detect; // disable peak detection shader bool disable_grain; // disable AV1 grain code bool disable_hooks; // disable user hooks / custom shaders @@ -61,7 +61,7 @@ struct pl_renderer { // Shader resource objects and intermediate textures (FBOs) struct pl_shader_obj *peak_detect_state; struct pl_shader_obj *dither_state; - struct pl_shader_obj *lut3d_state; + struct pl_shader_obj *icc_state; struct pl_shader_obj *grain_state[4]; PL_ARRAY(const struct pl_tex *) fbos; struct sampler sampler_main; @@ -166,7 +166,7 @@ void pl_renderer_destroy(struct pl_renderer **p_rr) // Free all shader resource objects pl_shader_obj_destroy(&rr->peak_detect_state); pl_shader_obj_destroy(&rr->dither_state); - pl_shader_obj_destroy(&rr->lut3d_state); + pl_shader_obj_destroy(&rr->icc_state); for (int i = 0; i < PL_ARRAY_SIZE(rr->grain_state); i++) pl_shader_obj_destroy(&rr->grain_state[i]); @@ -1418,38 +1418,38 @@ static bool pass_output_target(struct pl_renderer *rr, struct pass_state *pass, bool need_icc = (image->profile.data || target->profile.data) && !pl_icc_profile_equal(&image->profile, &target->profile); - bool use_3dlut = need_icc || params->force_3dlut; - if (rr->disable_3dlut) - use_3dlut = false; + bool use_icc = need_icc || params->force_icc_lut || params->force_3dlut; + if (rr->disable_icc) + use_icc = false; #ifdef PL_HAVE_LCMS - if (use_3dlut) { - struct pl_3dlut_profile src = { + if (use_icc) { + struct pl_icc_color_space src = { .color = ref_csp, .profile = image->profile, }; - struct pl_3dlut_profile dst = { + struct pl_icc_color_space dst = { .color = target->color, .profile = target->profile, }; - struct pl_3dlut_result res; - bool ok = pl_3dlut_update(sh, &src, &dst, &rr->lut3d_state, &res, - params->lut3d_params); + struct pl_icc_result res; + bool ok = pl_icc_update(sh, &src, &dst, &rr->icc_state, &res, + PL_DEF(params->icc_params, params->lut3d_params)); if (!ok) { - rr->disable_3dlut = true; - use_3dlut = false; + rr->disable_icc = true; + use_icc = false; goto fallback; } - // current -> 3DLUT in + // current -> ICC in pl_shader_color_map(sh, params->color_map_params, ref_csp, res.src_color, &rr->peak_detect_state, prelinearized); - // 3DLUT in -> 3DLUT out - pl_3dlut_apply(sh, &rr->lut3d_state); - // 3DLUT out -> target + // ICC in -> ICC out + pl_icc_apply(sh, &rr->icc_state); + // ICC out -> target pl_shader_color_map(sh, params->color_map_params, res.dst_color, target->color, NULL, false); } @@ -1458,16 +1458,16 @@ static bool pass_output_target(struct pl_renderer *rr, struct pass_state *pass, #else // !PL_HAVE_LCMS - if (use_3dlut) { + if (use_icc) { PL_WARN(rr, "An ICC profile was set, but libplacebo is built without " "support for LittleCMS! Disabling.."); - rr->disable_3dlut = true; - use_3dlut = false; + rr->disable_icc = true; + use_icc = false; } #endif - if (!use_3dlut) { + if (!use_icc) { // current -> target pl_shader_color_map(sh, params->color_map_params, ref_csp, target->color, &rr->peak_detect_state, prelinearized); diff --git a/src/shaders.h b/src/shaders.h index d31d5c0f..66ad8b6f 100644 --- a/src/shaders.h +++ b/src/shaders.h @@ -144,7 +144,7 @@ enum pl_shader_obj_type { PL_SHADER_OBJ_PEAK_DETECT, PL_SHADER_OBJ_SAMPLER, PL_SHADER_OBJ_DITHER, - PL_SHADER_OBJ_3DLUT, + PL_SHADER_OBJ_ICC, PL_SHADER_OBJ_LUT, PL_SHADER_OBJ_AV1_GRAIN, }; diff --git a/src/shaders/colorspace.c b/src/shaders/colorspace.c index e5db02ae..84a0cfbc 100644 --- a/src/shaders/colorspace.c +++ b/src/shaders/colorspace.c @@ -1385,119 +1385,3 @@ const struct pl_dither_params pl_dither_default_params = { .lut_size = 6, .temporal = false, // commonly flickers on LCDs }; - -#ifdef PL_HAVE_LCMS - -#include "lcms.h" - -struct sh_3dlut_obj { - struct pl_context *ctx; - enum pl_rendering_intent intent; - struct pl_3dlut_profile src, dst; - struct pl_3dlut_result result; - struct pl_shader_obj *lut_obj; - bool updated; // to detect misuse of the API - bool ok; - ident_t lut; -}; - -static void sh_3dlut_uninit(const struct pl_gpu *gpu, void *ptr) -{ - struct sh_3dlut_obj *obj = ptr; - pl_shader_obj_destroy(&obj->lut_obj); - *obj = (struct sh_3dlut_obj) {0}; -} - -static void fill_3dlut(void *data, const struct sh_lut_params *params) -{ - struct sh_3dlut_obj *obj = params->priv; - struct pl_context *ctx = obj->ctx; - - pl_assert(params->comps == 4); - obj->ok = pl_lcms_compute_lut(ctx, obj->intent, obj->src, obj->dst, data, - params->width, params->height, params->depth, - &obj->result); - if (!obj->ok) - pl_err(ctx, "Failed computing 3DLUT!"); -} - -static bool color_profile_eq(const struct pl_3dlut_profile *a, - const struct pl_3dlut_profile *b) -{ - return pl_icc_profile_equal(&a->profile, &b->profile) && - pl_color_space_equal(&a->color, &b->color); -} - -bool pl_3dlut_update(struct pl_shader *sh, - const struct pl_3dlut_profile *src, - const struct pl_3dlut_profile *dst, - struct pl_shader_obj **lut3d, struct pl_3dlut_result *out, - const struct pl_3dlut_params *params) -{ - params = PL_DEF(params, &pl_3dlut_default_params); - size_t s_r = PL_DEF(params->size_r, 64), - s_g = PL_DEF(params->size_g, 64), - s_b = PL_DEF(params->size_b, 64); - - struct sh_3dlut_obj *obj; - obj = SH_OBJ(sh, lut3d, PL_SHADER_OBJ_3DLUT, - struct sh_3dlut_obj, sh_3dlut_uninit); - if (!obj) - return false; - - bool changed = !color_profile_eq(&obj->src, src) || - !color_profile_eq(&obj->dst, dst) || - obj->intent != params->intent; - - // Update the object, since we need this information from `fill_3dlut` - obj->ctx = sh->ctx; - obj->intent = params->intent; - obj->src = *src; - obj->dst = *dst; - obj->lut = sh_lut(sh, &(struct sh_lut_params) { - .object = &obj->lut_obj, - .type = PL_VAR_FLOAT, - .width = s_r, - .height = s_g, - .depth = s_b, - .comps = 4, - .linear = true, - .update = changed, - .fill = fill_3dlut, - .priv = obj, - }); - if (!obj->lut || !obj->ok) - return false; - - obj->updated = true; - *out = obj->result; - return true; -} - -void pl_3dlut_apply(struct pl_shader *sh, struct pl_shader_obj **lut3d) -{ - if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0)) - return; - - struct sh_3dlut_obj *obj; - obj = SH_OBJ(sh, lut3d, PL_SHADER_OBJ_3DLUT, - struct sh_3dlut_obj, sh_3dlut_uninit); - if (!obj || !obj->lut || !obj->updated || !obj->ok) { - SH_FAIL(sh, "pl_shader_3dlut called without prior pl_3dlut_update?"); - return; - } - - GLSL("// pl_shader_3dlut\n"); - GLSL("color.rgb = %s(color.rgb).rgb;\n", obj->lut); - - obj->updated = false; -} - -#endif // PL_HAVE_LCMS - -const struct pl_3dlut_params pl_3dlut_default_params = { - .intent = PL_INTENT_RELATIVE_COLORIMETRIC, - .size_r = 64, - .size_g = 64, - .size_b = 64, -}; diff --git a/src/lcms.c b/src/shaders/icc.c similarity index 64% rename from src/lcms.c rename to src/shaders/icc.c index a6b699e6..61c8a9ef 100644 --- a/src/lcms.c +++ b/src/shaders/icc.c @@ -18,19 +18,18 @@ #include #include -#include "context.h" -#include "lcms.h" +#include "shaders.h" static cmsHPROFILE get_profile(struct pl_context *ctx, cmsContext cms, - struct pl_3dlut_profile prof, cmsHPROFILE dstp, + struct pl_icc_color_space iccsp, cmsHPROFILE dstp, struct pl_color_space *csp) { - *csp = prof.color; + *csp = iccsp.color; - if (prof.profile.data) { + if (iccsp.profile.data) { pl_info(ctx, "Opening ICC profile.."); - cmsHPROFILE ret = cmsOpenProfileFromMemTHR(cms, prof.profile.data, - prof.profile.len); + cmsHPROFILE ret = cmsOpenProfileFromMemTHR(cms, iccsp.profile.data, + iccsp.profile.len); if (ret) return ret; pl_err(ctx, "Failed opening ICC profile, falling back to color struct"); @@ -160,23 +159,38 @@ static void error_callback(cmsContext cms, cmsUInt32Number code, pl_err(ctx, "lcms2: [%d] %s", (int) code, msg); } -bool pl_lcms_compute_lut(struct pl_context *ctx, enum pl_rendering_intent intent, - struct pl_3dlut_profile src, struct pl_3dlut_profile dst, - float *out_data, int s_r, int s_g, int s_b, - struct pl_3dlut_result *out) +struct sh_icc_obj { + struct pl_context *ctx; + enum pl_rendering_intent intent; + struct pl_icc_color_space src, dst; + struct pl_icc_result result; + struct pl_shader_obj *lut_obj; + bool updated; // to detect misuse of the API + bool ok; + ident_t lut; +}; + +static void fill_icc(void *datap, const struct sh_lut_params *params) { - bool ret = false; + struct sh_icc_obj *obj = params->priv; + struct pl_context *ctx = obj->ctx; + pl_assert(params->comps == 4); + float *data = datap; + cmsHPROFILE srcp = NULL, dstp = NULL; cmsHTRANSFORM trafo = NULL; uint16_t *tmp = NULL; + obj->ok = false; cmsContext cms = cmsCreateContext(NULL, ctx); - if (!cms) + if (!cms) { + pl_err(ctx, "Failed creating LittleCMS context!"); goto error; + } cmsSetLogErrorHandlerTHR(cms, error_callback); - dstp = get_profile(ctx, cms, dst, NULL, &out->dst_color); - srcp = get_profile(ctx, cms, src, dstp, &out->src_color); + dstp = get_profile(ctx, cms, obj->dst, NULL, &obj->result.dst_color); + srcp = get_profile(ctx, cms, obj->src, dstp, &obj->result.src_color); if (!srcp || !dstp) goto error; @@ -184,10 +198,13 @@ bool pl_lcms_compute_lut(struct pl_context *ctx, enum pl_rendering_intent intent cmsFLAGS_NOCACHE; trafo = cmsCreateTransformTHR(cms, srcp, TYPE_RGB_16, dstp, TYPE_RGBA_FLT, - intent, flags); - if (!trafo) + obj->intent, flags); + if (!trafo) { + pl_err(ctx, "Failed creating CMS transform!"); goto error; + } + int s_r = params->width, s_g = params->height, s_b = params->depth; pl_assert(s_r > 1 && s_g > 1 && s_b > 1); tmp = pl_alloc(NULL, s_r * 3 * sizeof(uint16_t)); @@ -202,11 +219,11 @@ bool pl_lcms_compute_lut(struct pl_context *ctx, enum pl_rendering_intent intent // Transform this line into the right output position size_t offset = (b * s_g + g) * s_r * 4; - cmsDoTransform(trafo, tmp, out_data + offset, s_r); + cmsDoTransform(trafo, tmp, data + offset, s_r); } } - ret = true; + obj->ok = true; // fall through error: @@ -220,5 +237,92 @@ bool pl_lcms_compute_lut(struct pl_context *ctx, enum pl_rendering_intent intent cmsDeleteContext(cms); pl_free_ptr(&tmp); - return ret; } + +static void sh_icc_uninit(const struct pl_gpu *gpu, void *ptr) +{ + struct sh_icc_obj *obj = ptr; + pl_shader_obj_destroy(&obj->lut_obj); + *obj = (struct sh_icc_obj) {0}; +} + +static bool icc_csp_eq(const struct pl_icc_color_space *a, + const struct pl_icc_color_space *b) +{ + return pl_icc_profile_equal(&a->profile, &b->profile) && + pl_color_space_equal(&a->color, &b->color); +} + +bool pl_icc_update(struct pl_shader *sh, + const struct pl_icc_color_space *src, + const struct pl_icc_color_space *dst, + struct pl_shader_obj **icc, + struct pl_icc_result *out, + const struct pl_icc_params *params) +{ + params = PL_DEF(params, &pl_icc_default_params); + size_t s_r = PL_DEF(params->size_r, 64), + s_g = PL_DEF(params->size_g, 64), + s_b = PL_DEF(params->size_b, 64); + + struct sh_icc_obj *obj; + obj = SH_OBJ(sh, icc, PL_SHADER_OBJ_ICC, + struct sh_icc_obj, sh_icc_uninit); + if (!obj) + return false; + + bool changed = !icc_csp_eq(&obj->src, src) || + !icc_csp_eq(&obj->dst, dst) || + obj->intent != params->intent; + + // Update the object, since we need this information from `fill_icc` + obj->ctx = sh->ctx; + obj->intent = params->intent; + obj->src = *src; + obj->dst = *dst; + obj->lut = sh_lut(sh, &(struct sh_lut_params) { + .object = &obj->lut_obj, + .type = PL_VAR_FLOAT, + .width = s_r, + .height = s_g, + .depth = s_b, + .comps = 4, + .linear = true, + .update = changed, + .fill = fill_icc, + .priv = obj, + }); + if (!obj->lut || !obj->ok) + return false; + + obj->updated = true; + *out = obj->result; + return true; +} + +void pl_icc_apply(struct pl_shader *sh, struct pl_shader_obj **icc) +{ + if (!sh_require(sh, PL_SHADER_SIG_COLOR, 0, 0)) + return; + + struct sh_icc_obj *obj; + obj = SH_OBJ(sh, icc, PL_SHADER_OBJ_ICC, + struct sh_icc_obj, sh_icc_uninit); + if (!obj || !obj->lut || !obj->updated || !obj->ok) { + SH_FAIL(sh, "pl_icc_apply called without prior pl_icc_update?"); + return; + } + + GLSL("// pl_icc_apply \n" + "color.rgb = %s(color.rgb).rgb; \n", + obj->lut); + + obj->updated = false; +} + +const struct pl_icc_params pl_icc_default_params = { + .intent = PL_INTENT_RELATIVE_COLORIMETRIC, + .size_r = 64, + .size_g = 64, + .size_b = 64, +}; diff --git a/src/tests/gpu_tests.h b/src/tests/gpu_tests.h index 7105bb85..b789aa26 100644 --- a/src/tests/gpu_tests.h +++ b/src/tests/gpu_tests.h @@ -521,17 +521,17 @@ static void pl_shader_tests(const struct pl_gpu *gpu) pl_shader_obj_destroy(&peak_state); #ifdef PL_HAVE_LCMS - // Test the use of 3DLUTs if available + // Test the use of ICC profiles if available sh = pl_dispatch_begin(dp); pl_shader_sample_nearest(sh, &(struct pl_sample_src) { .tex = src }); - struct pl_shader_obj *lut3d = NULL; - struct pl_3dlut_profile src_color = { .color = pl_color_space_bt709 }; - struct pl_3dlut_profile dst_color = { .color = pl_color_space_srgb }; - struct pl_3dlut_result out; + struct pl_shader_obj *icc = NULL; + struct pl_icc_color_space src_color = { .color = pl_color_space_bt709 }; + struct pl_icc_color_space dst_color = { .color = pl_color_space_srgb }; + struct pl_icc_result out; - if (pl_3dlut_update(sh, &src_color, &dst_color, &lut3d, &out, NULL)) { - pl_3dlut_apply(sh, &lut3d); + if (pl_icc_update(sh, &src_color, &dst_color, &icc, &out, NULL)) { + pl_icc_apply(sh, &icc); REQUIRE(pl_dispatch_finish(dp, &(struct pl_dispatch_params) { .shader = &sh, .target = fbo, @@ -539,7 +539,7 @@ static void pl_shader_tests(const struct pl_gpu *gpu) } pl_dispatch_abort(dp, &sh); - pl_shader_obj_destroy(&lut3d); + pl_shader_obj_destroy(&icc); #endif // Test AV1 grain synthesis @@ -912,7 +912,7 @@ static void pl_render_tests(const struct pl_gpu *gpu) REQUIRE(pl_render_image(rr, &image, &target, ¶ms)); params = pl_render_default_params; - params.force_3dlut = true; + params.force_icc_lut = true; REQUIRE(pl_render_image(rr, &image, &target, ¶ms)); params = pl_render_default_params;