From 258cf937bfdf56f188bf027f857866cf396d8f6b Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Fri, 17 Feb 2023 12:58:03 +0100 Subject: [PATCH] shaders/colorspace: add tone mapping visualization Infinitely useful during development, no need to constantly NIH this patch or re-invent it ad-hoc. --- demos/plplay.c | 1 + meson.build | 1 + src/include/libplacebo/shaders/colorspace.h | 3 + src/shaders/colorspace.c | 94 +++++++++++++++++++++ src/tests/gpu_tests.h | 1 + 5 files changed, 100 insertions(+) diff --git a/demos/plplay.c b/demos/plplay.c index d1abc4dd..2790de4b 100644 --- a/demos/plplay.c +++ b/demos/plplay.c @@ -1085,6 +1085,7 @@ static void update_settings(struct plplay *p) nk_property_float(nk, "Crosstalk", 0.0, &cpar->tone_mapping_crosstalk, 0.30, 0.01, 0.001); nk_checkbox_label(nk, "Inverse tone mapping", &cpar->inverse_tone_mapping); nk_checkbox_label(nk, "Force full LUT", &cpar->force_tone_mapping_lut); + nk_checkbox_label(nk, "Visualize LUT", &cpar->visualize_lut); nk_layout_row_dynamic(nk, 50, 1); if (ui_widget_hover(nk, "Drop .cube file here...") && dropped_file) { diff --git a/meson.build b/meson.build index f7636116..124a7a5c 100644 --- a/meson.build +++ b/meson.build @@ -12,6 +12,7 @@ project('libplacebo', ['c', 'cpp'], 5, # API version { + '247': 'add pl_color_map_params.visualize_lut', '246': 'add `pl_tone_map_st2094_10` and `pl_tone_map_st2094_40`', '245': 'add `pl_tone_map_params.hdr`', '244': 'add `pl_map_hdr_metadata`', diff --git a/src/include/libplacebo/shaders/colorspace.h b/src/include/libplacebo/shaders/colorspace.h index 434af26a..54b988f3 100644 --- a/src/include/libplacebo/shaders/colorspace.h +++ b/src/include/libplacebo/shaders/colorspace.h @@ -284,6 +284,9 @@ struct pl_color_map_params { // faster pure GLSL replacements (e.g. clip). bool force_tone_mapping_lut; + // Visualize the tone-mapping curve / LUT. (PQ-PQ graph) + bool visualize_lut; + // --- Deprecated fields enum pl_tone_mapping_algorithm tone_mapping_algo PL_DEPRECATED; float desaturation_strength PL_DEPRECATED; diff --git a/src/shaders/colorspace.c b/src/shaders/colorspace.c index dffdad0d..e174c679 100644 --- a/src/shaders/colorspace.c +++ b/src/shaders/colorspace.c @@ -1246,6 +1246,95 @@ static void fill_lut(void *data, const struct sh_lut_params *params) } } +static inline void visualize_tone_map(pl_shader sh, ident_t fun, + float xmin, float xmax, + float ymin, float ymax, + bool dynamic_peak) +{ + ident_t pos = sh_attr_vec2(sh, "screenpos", &(struct pl_rect2df) { + .x0 = 0.0f, .x1 = 1.0f, + .y0 = 1.0f, .y1 = 0.0f, + }); + + GLSL("// Visualize tone mapping \n" + "{ \n" + "float xmin = %s; \n" + "float xmax = %s; \n" + "float ymin = %s; \n" + "float ymax = %s; \n", + SH_FLOAT(pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, xmin)), + SH_FLOAT(pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, xmax)), + SH_FLOAT(pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ymin)), + SH_FLOAT(pl_hdr_rescale(PL_HDR_NORM, PL_HDR_PQ, ymax))); + + if (dynamic_peak) { + GLSL("vec2 avg = average.xy; \n" + "avg *= vec2(%f); \n" + "avg = pow(max(avg, 0.0), vec2(%f)); \n" + "avg = (vec2(%f) + vec2(%f) * avg) \n" + " / (vec2(1.0) + vec2(%f) * avg); \n" + "avg = pow(avg, vec2(%f)); \n" + "xmax = avg.y; \n", + PL_COLOR_SDR_WHITE / 10000.0, + PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2); + } + + GLSL("vec2 pos = %s; \n" + "vec3 viz = color.rgb; \n" + // PQ EOTF + "float vv = pos.x; \n" + "vv = pow(max(vv, 0.0), 1.0/%f); \n" + "vv = max(vv - %f, 0.0) / (%f - %f * vv); \n" + "vv = pow(vv, 1.0 / %f); \n" + "vv *= %f; \n" + // Apply tone-mapping function + "vv = %s(vv); \n" + // PQ OETF + "vv *= %f; \n" + "vv = pow(max(vv, 0.0), %f); \n" + "vv = (%f + %f * vv) / (1.0 + %f * vv); \n" + "vv = pow(vv, %f); \n" + // Color based on region + "if (pos.x < xmin || pos.x > xmax) { \n" // outside source + " viz = vec3(0.0); \n" + "} else if (pos.y < ymin || pos.y > ymax) {\n" // outside target + " if (pos.y < xmin || pos.y > xmax) { \n" // and also source + " viz = vec3(0.1, 0.1, 0.5); \n" + " } else { \n" + " viz = vec3(0.4, 0.1, 0.1); \n" // but inside source + " } \n" + "} else { \n" // inside domain + " if (abs(pos.x - pos.y) < 1e-3) { \n" // main diagonal + " viz = vec3(0.2); \n" + " } else if (pos.y < vv) { \n" // inside function + " viz = vec3(0.3, 1.0, 0.1); \n" + " if (vv > pos.x && pos.y > pos.x) \n" // output brighter than input + " viz.r = 0.7; \n" + " } else { \n" // outside function + " if (vv < pos.x && pos.y < pos.x) \n" // output darker than input + " viz = vec3(0.0, 0.05, 0.1); \n" + " } \n" + " if (pos.y > xmax) { \n" // inverse tone-mapping region + " vec3 hi = vec3(0.2, 0.5, 0.8); \n" + " viz = mix(viz, hi, 0.5); \n" + " } \n", + pos, + PQ_M2, PQ_C1, PQ_C2, PQ_C3, PQ_M1, + 10000.0 / PL_COLOR_SDR_WHITE, + fun, + PL_COLOR_SDR_WHITE / 10000.0, + PQ_M1, PQ_C1, PQ_C2, PQ_C3, PQ_M2); + + if (dynamic_peak) { + GLSL(" if (abs(pos.x - avg.x) < 1e-3) \n" // source avg brightness + " viz = vec3(0.5); \n"); + } + + GLSL("} \n" + "color.rgb = mix(color.rgb, viz, 0.5); \n" + "} \n"); +} + static void tone_map(pl_shader sh, const struct pl_color_space *src, const struct pl_color_space *dst, @@ -1585,6 +1674,11 @@ static void tone_map(pl_shader sh, "color.rgb = (color.rgb - vec3(ct)) / ct_scale; \n", ct); + if (params->visualize_lut) { + visualize_tone_map(sh, "tone_map", src_min, src_max, dst_min, dst_max, + dynamic_peak); + } + GLSL("#undef tone_map \n"); } diff --git a/src/tests/gpu_tests.h b/src/tests/gpu_tests.h index fb98d68b..79cf3194 100644 --- a/src/tests/gpu_tests.h +++ b/src/tests/gpu_tests.h @@ -1121,6 +1121,7 @@ static void pl_render_tests(pl_gpu gpu) image.color = pl_color_space_hdr10; TEST_PARAMS(color_map, tone_mapping_mode, PL_TONE_MAP_MODE_COUNT - 1); TEST_PARAMS(color_map, gamut_mode, PL_GAMUT_MODE_COUNT - 1); + TEST_PARAMS(color_map, visualize_lut, true); // Test inverse tone-mapping and pure BPC image.color.hdr.max_luma = 1000;