diff --git a/meson.build b/meson.build index 3c1e5f9ac4be322b97081bb697c74c5937615609..0f55f354ac101a4c07a0976afad6b76640766ea7 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.49', - version: '2.67.0', + version: '2.68.0', ) # Version number diff --git a/src/include/libplacebo/shaders/colorspace.h b/src/include/libplacebo/shaders/colorspace.h index e9fed237f8d56df52c675ec18b87a60dd1a2c6ef..562ef92180ce99925f595dab422d8401bfbcac00 100644 --- a/src/include/libplacebo/shaders/colorspace.h +++ b/src/include/libplacebo/shaders/colorspace.h @@ -194,6 +194,10 @@ enum pl_tone_mapping_algorithm { // as an aditional scaling coefficient to make the image (linearly) // brighter or darker. Defaults to 1.0. PL_TONE_MAPPING_LINEAR, + + // EETF from the ITU-R Report BT.2390, a hermite spline roll-off with + // linear segment. Not configurable. + PL_TONE_MAPPING_BT_2390, }; struct pl_color_map_params { @@ -204,7 +208,7 @@ struct pl_color_map_params { // Algorithm and configuration used for tone-mapping. For non-tunable // algorithms, the `param` is ignored. If the tone mapping parameter is // left as 0.0, the tone-mapping curve's preferred default parameter will - // be used. The default algorithm is PL_TONE_MAPPING_HABLE. + // be used. The default algorithm is PL_TONE_MAPPING_BT_2390. enum pl_tone_mapping_algorithm tone_mapping_algo; float tone_mapping_param; diff --git a/src/shaders/colorspace.c b/src/shaders/colorspace.c index 722722ac60d7c540f23f7563cb8adab8f756cde1..3331f075d7d6065715e44454898c001f78745768 100644 --- a/src/shaders/colorspace.c +++ b/src/shaders/colorspace.c @@ -751,9 +751,18 @@ bool pl_shader_detect_peak(struct pl_shader *sh, return true; } +static inline float pq_delinearize(float x) +{ + x *= PL_COLOR_SDR_WHITE / 10000.0; + x = powf(x, PQ_M1); + x = (PQ_C1 + PQ_C2 * x) / (1.0 + PQ_C3 * x); + x = pow(x, PQ_M2); + return x; +} + const struct pl_color_map_params pl_color_map_default_params = { .intent = PL_INTENT_RELATIVE_COLORIMETRIC, - .tone_mapping_algo = PL_TONE_MAPPING_HABLE, + .tone_mapping_algo = PL_TONE_MAPPING_BT_2390, .desaturation_strength = 0.75, .desaturation_exponent = 1.50, .desaturation_base = 0.18, @@ -791,11 +800,13 @@ static void pl_shader_tone_map(struct pl_shader *sh, struct pl_color_space src, } } - // Rescale the input in order to bring it into a representation where - // 1.0 represents the dst_peak. This is because all of the tone mapping - // algorithms are defined in such a way that they map to the range [0.0, 1.0]. + // Rescale the input in order to bring it into a representation where 1.0 + // represents the dst_peak. This is because (almost) all of the tone + // mapping algorithms are defined in such a way that they map to the range + // [0.0, 1.0]. + bool need_norm = params->tone_mapping_algo != PL_TONE_MAPPING_BT_2390; float dst_range = dst.sig_peak * dst.sig_scale; - if (dst_range > 1.0) { + if (dst_range > 1.0 && need_norm) { GLSL("color.rgb *= 1.0 / %f; \n" "sig_peak *= 1.0 / %f; \n", dst_range, dst_range); @@ -873,6 +884,41 @@ static void pl_shader_tone_map(struct pl_shader *sh, struct pl_color_space src, GLSL("sig *= %f / sig_peak;\n", PL_DEF(param, 1.0)); break; + case PL_TONE_MAPPING_BT_2390: + // We first need to encode both sig and sig_peak into PQ space + GLSL("vec4 sig_pq = vec4(sig.rgb, sig_peak); \n" + "sig_pq *= vec4(1.0/%f); \n" + "sig_pq = pow(sig_pq, vec4(%f)); \n" + "sig_pq = (vec4(%f) + vec4(%f) * sig_pq) \n" + " / (vec4(1.0) + vec4(%f) * sig_pq); \n" + "sig_pq = pow(sig_pq, vec4(%f)); \n", + 10000 / PL_COLOR_SDR_WHITE, PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2); + // Encode both the signal and the target brightness to be relative to + // the source peak brightness, and figure out the target peak in this space + GLSL("float scale = 1.0 / sig_pq.a; \n" + "sig_pq.rgb *= vec3(scale); \n" + "float maxLum = %f * scale; \n", + pq_delinearize(dst_range)); + // Apply piece-wise hermite spline + GLSL("float ks = 1.5 * maxLum - 0.5; \n" + "vec3 tb = (sig_pq.rgb - vec3(ks)) / vec3(1.0 - ks); \n" + "vec3 tb2 = tb * tb; \n" + "vec3 tb3 = tb2 * tb; \n" + "vec3 pb = (2.0 * tb3 - 3.0 * tb2 + vec3(1.0)) * vec3(ks) + \n" + " (tb3 - 2.0 * tb2 + tb) * vec3(1.0 - ks) + \n" + " (-2.0 * tb3 + 3.0 * tb2) * vec3(maxLum); \n" + "sig = mix(pb, sig_pq.rgb, %s(lessThan(sig_pq.rgb, vec3(ks)))); \n", + sh_bvec(sh, 3)); + // Convert back from PQ space to linear light + GLSL("sig *= vec3(sig_pq.a); \n" + "sig = pow(sig, vec3(1.0/%f)); \n" + "sig = max(sig - vec3(%f), 0.0) / \n" + " (vec3(%f) - vec3(%f) * sig); \n" + "sig = pow(sig, vec3(1.0/%f)); \n" + "sig *= vec3(%f); \n", + PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, 10000.0 / PL_COLOR_SDR_WHITE); + break; + default: abort(); } @@ -895,7 +941,7 @@ static void pl_shader_tone_map(struct pl_shader *sh, struct pl_color_space src, } // Undo the normalization by `dst_peak` - if (dst_range > 1.0) + if (dst_range > 1.0 && need_norm) GLSL("color.rgb *= %f; \n", dst_range); } diff --git a/src/tests/gpu_tests.h b/src/tests/gpu_tests.h index 995fa063ad5e161ac9e59937bfee627ab5520dcd..22184ba5b37549aa44df5f9d39cd8642cce3a9e4 100644 --- a/src/tests/gpu_tests.h +++ b/src/tests/gpu_tests.h @@ -733,7 +733,7 @@ static void pl_render_tests(const struct pl_gpu *gpu) // Test HDR stuff image.color.sig_scale = 10.0; target.color.sig_scale = 2.0; - TEST_PARAMS(color_map, tone_mapping_algo, PL_TONE_MAPPING_LINEAR); + TEST_PARAMS(color_map, tone_mapping_algo, PL_TONE_MAPPING_BT_2390); TEST_PARAMS(color_map, desaturation_strength, 1); image.color.sig_scale = target.color.sig_scale = 0.0;