diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 20:38:23 +0000 |
commit | ff6e3c025658a5fa1affd094f220b623e7e1b24b (patch) | |
tree | 9faab72d69c92d24e349d184f5869b9796f17e0c /demos/settings.c | |
parent | Initial commit. (diff) | |
download | libplacebo-ff6e3c025658a5fa1affd094f220b623e7e1b24b.tar.xz libplacebo-ff6e3c025658a5fa1affd094f220b623e7e1b24b.zip |
Adding upstream version 6.338.2.upstream/6.338.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | demos/settings.c | 1238 |
1 files changed, 1238 insertions, 0 deletions
diff --git a/demos/settings.c b/demos/settings.c new file mode 100644 index 0000000..e69f280 --- /dev/null +++ b/demos/settings.c @@ -0,0 +1,1238 @@ +#include <stdatomic.h> +#include <getopt.h> + +#include <libavutil/file.h> + +#include "plplay.h" + +#ifdef PL_HAVE_WIN32 +#include <shlwapi.h> +#define PL_BASENAME PathFindFileNameA +#define strdup _strdup +#else +#include <libgen.h> +#define PL_BASENAME basename +#endif + +#ifdef HAVE_NUKLEAR +#include "ui.h" + +bool parse_args(struct plplay_args *args, int argc, char *argv[]) +{ + static struct option long_options[] = { + {"verbose", no_argument, NULL, 'v'}, + {"quiet", no_argument, NULL, 'q'}, + {"preset", required_argument, NULL, 'p'}, + {"hwdec", no_argument, NULL, 'H'}, + {"window", required_argument, NULL, 'w'}, + {0} + }; + + int option; + while ((option = getopt_long(argc, argv, "vqp:Hw:", long_options, NULL)) != -1) { + switch (option) { + case 'v': + if (args->verbosity < PL_LOG_TRACE) + args->verbosity++; + break; + case 'q': + if (args->verbosity > PL_LOG_NONE) + args->verbosity--; + break; + case 'p': + if (!strcmp(optarg, "default")) { + args->preset = &pl_render_default_params; + } else if (!strcmp(optarg, "fast")) { + args->preset = &pl_render_fast_params; + } else if (!strcmp(optarg, "highquality") || !strcmp(optarg, "hq")) { + args->preset = &pl_render_high_quality_params; + } else { + fprintf(stderr, "Invalid value for -p/--preset: '%s'\n", optarg); + goto error; + } + break; + case 'H': + args->hwdec = true; + break; + case 'w': + args->window_impl = optarg; + break; + case '?': + default: + goto error; + } + } + + // Check for the required filename argument + if (optind < argc) { + args->filename = argv[optind++]; + } else { + fprintf(stderr, "Missing filename!\n"); + goto error; + } + + if (optind != argc) { + fprintf(stderr, "Superfluous argument: %s\n", argv[optind]); + goto error; + } + + return true; + +error: + fprintf(stderr, "Usage: %s [-v/--verbose] [-q/--quiet] [-p/--preset <default|fast|hq|highquality>] [--hwdec] [-w/--window <api>] <filename>\n", argv[0]); + fprintf(stderr, "Options:\n"); + fprintf(stderr, " -v, --verbose Increase verbosity\n"); + fprintf(stderr, " -q, --quiet Decrease verbosity\n"); + fprintf(stderr, " -p, --preset Set the rendering preset (default|fast|hq|highquality)\n"); + fprintf(stderr, " -H, --hwdec Enable hardware decoding\n"); + fprintf(stderr, " -w, --window Specify the windowing API\n"); + return false; +} + +static void add_hook(struct plplay *p, const struct pl_hook *hook, const char *path) +{ + if (!hook) + return; + + if (p->shader_num == p->shader_size) { + // Grow array if needed + size_t new_size = p->shader_size ? p->shader_size * 2 : 16; + void *new_hooks = realloc(p->shader_hooks, new_size * sizeof(void *)); + if (!new_hooks) + goto error; + p->shader_hooks = new_hooks; + char **new_paths = realloc(p->shader_paths, new_size * sizeof(char *)); + if (!new_paths) + goto error; + p->shader_paths = new_paths; + p->shader_size = new_size; + } + + // strip leading path + while (true) { + const char *fname = strchr(path, '/'); + if (!fname) + break; + path = fname + 1; + } + + char *path_copy = strdup(path); + if (!path_copy) + goto error; + + p->shader_hooks[p->shader_num] = hook; + p->shader_paths[p->shader_num] = path_copy; + p->shader_num++; + return; + +error: + pl_mpv_user_shader_destroy(&hook); +} + +static void auto_property_int(struct nk_context *nk, int auto_val, int min, int *val, + int max, int step, float inc_per_pixel) +{ + int value = *val; + if (!value) + value = auto_val; + + // Auto label will be delayed 1 frame + nk_property_int(nk, *val ? "" : "Auto", min, &value, max, step, inc_per_pixel); + + if (*val || value != auto_val) + *val = value; +} + +static void draw_shader_pass(struct nk_context *nk, + const struct pl_dispatch_info *info) +{ + pl_shader_info shader = info->shader; + + char label[128]; + int count = snprintf(label, sizeof(label), "%.3f/%.3f/%.3f ms: %s", + info->last / 1e6, + info->average / 1e6, + info->peak / 1e6, + shader->description); + + if (count >= sizeof(label)) { + label[sizeof(label) - 4] = '.'; + label[sizeof(label) - 3] = '.'; + label[sizeof(label) - 2] = '.'; + } + + int id = (unsigned int) (uintptr_t) info; // pointer into `struct plplay` + if (nk_tree_push_id(nk, NK_TREE_NODE, label, NK_MINIMIZED, id)) { + nk_layout_row_dynamic(nk, 32, 1); + if (nk_chart_begin(nk, NK_CHART_LINES, + info->num_samples, + 0.0f, info->peak)) + { + for (int k = 0; k < info->num_samples; k++) + nk_chart_push(nk, info->samples[k]); + nk_chart_end(nk); + } + + nk_layout_row_dynamic(nk, 24, 1); + for (int n = 0; n < shader->num_steps; n++) + nk_labelf(nk, NK_TEXT_LEFT, "%d. %s", n + 1, shader->steps[n]); + nk_tree_pop(nk); + } +} + +static void draw_timing(struct nk_context *nk, const char *label, + const struct timing *t) +{ + const double avg = t->count ? t->sum / t->count : 0.0; + const double stddev = t->count ? sqrt(t->sum2 / t->count - avg * avg) : 0.0; + nk_label(nk, label, NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.4f ± %.4f ms (%.3f ms)", + avg * 1e3, stddev * 1e3, t->peak * 1e3); +} + +static void draw_opt_data(void *priv, pl_opt_data data) +{ + struct nk_context *nk = priv; + pl_opt opt = data->opt; + if (opt->type == PL_OPT_FLOAT) { + // Print floats less verbosely than the libplacebo built-in printf + nk_labelf(nk, NK_TEXT_LEFT, "%s = %f", opt->key, *(const float *) data->value); + } else { + nk_labelf(nk, NK_TEXT_LEFT, "%s = %s", opt->key, data->text); + } +} + +static void draw_cache_line(void *priv, pl_cache_obj obj) +{ + struct nk_context *nk = priv; + nk_labelf(nk, NK_TEXT_LEFT, " - 0x%016"PRIx64": %zu bytes", obj.key, obj.size); +} + +void update_settings(struct plplay *p, const struct pl_frame *target) +{ + struct nk_context *nk = ui_get_context(p->ui); + enum nk_panel_flags win_flags = NK_WINDOW_BORDER | NK_WINDOW_MOVABLE | + NK_WINDOW_SCALABLE | NK_WINDOW_MINIMIZABLE | + NK_WINDOW_TITLE; + + ui_update_input(p->ui, p->win); + const char *dropped_file = window_get_file(p->win); + + pl_options opts = p->opts; + struct pl_render_params *par = &opts->params; + + if (nk_begin(nk, "Settings", nk_rect(100, 100, 600, 600), win_flags)) { + + if (nk_tree_push(nk, NK_TREE_NODE, "Window settings", NK_MAXIMIZED)) { + nk_layout_row_dynamic(nk, 24, 2); + + bool fullscreen = window_is_fullscreen(p->win); + p->toggle_fullscreen = nk_checkbox_label(nk, "Fullscreen", &fullscreen); + nk_property_float(nk, "Corner rounding", 0.0, &par->corner_rounding, 1.0, 0.1, 0.01); + + struct nk_colorf bg = { + par->background_color[0], + par->background_color[1], + par->background_color[2], + 1.0 - par->background_transparency, + }; + + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Background color:", NK_TEXT_LEFT); + if (nk_combo_begin_color(nk, nk_rgb_cf(bg), nk_vec2(nk_widget_width(nk), 300))) { + nk_layout_row_dynamic(nk, 200, 1); + nk_color_pick(nk, &bg, NK_RGBA); + nk_combo_end(nk); + + par->background_color[0] = bg.r; + par->background_color[1] = bg.g; + par->background_color[2] = bg.b; + par->background_transparency = 1.0 - bg.a; + } + + nk_layout_row_dynamic(nk, 24, 2); + par->blend_against_tiles = nk_check_label(nk, "Blend against tiles", par->blend_against_tiles); + nk_property_int(nk, "Tile size", 2, &par->tile_size, 256, 1, 1); + + nk_layout_row(nk, NK_DYNAMIC, 24, 3, (float[]){ 0.4, 0.3, 0.3 }); + nk_label(nk, "Tile colors:", NK_TEXT_LEFT); + for (int i = 0; i < 2; i++) { + bg = (struct nk_colorf) { + par->tile_colors[i][0], + par->tile_colors[i][1], + par->tile_colors[i][2], + }; + + if (nk_combo_begin_color(nk, nk_rgb_cf(bg), nk_vec2(nk_widget_width(nk), 300))) { + nk_layout_row_dynamic(nk, 200, 1); + nk_color_pick(nk, &bg, NK_RGB); + nk_combo_end(nk); + + par->tile_colors[i][0] = bg.r; + par->tile_colors[i][1] = bg.g; + par->tile_colors[i][2] = bg.b; + } + } + + static const char *rotations[4] = { + [PL_ROTATION_0] = "0°", + [PL_ROTATION_90] = "90°", + [PL_ROTATION_180] = "180°", + [PL_ROTATION_270] = "270°", + }; + + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Display orientation:", NK_TEXT_LEFT); + p->target_rot = nk_combo(nk, rotations, 4, p->target_rot, + 16, nk_vec2(nk_widget_width(nk), 100)); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Image scaling", NK_MAXIMIZED)) { + const struct pl_filter_config *f; + static const char *scale_none = "None (Built-in sampling)"; + static const char *pscale_none = "None (Use regular upscaler)"; + static const char *tscale_none = "None (No frame mixing)"; + #define SCALE_DESC(scaler, fallback) (par->scaler ? par->scaler->description : fallback) + + static const char *zoom_modes[ZOOM_COUNT] = { + [ZOOM_PAD] = "Pad to window", + [ZOOM_CROP] = "Crop to window", + [ZOOM_STRETCH] = "Stretch to window", + [ZOOM_FIT] = "Fit inside window", + [ZOOM_RAW] = "Unscaled (raw)", + [ZOOM_400] = "400% zoom", + [ZOOM_200] = "200% zoom", + [ZOOM_100] = "100% zoom", + [ZOOM_50] = " 50% zoom", + [ZOOM_25] = " 25% zoom", + }; + + nk_layout_row(nk, NK_DYNAMIC, 24, 2, (float[]){ 0.3, 0.7 }); + nk_label(nk, "Zoom mode:", NK_TEXT_LEFT); + int zoom = nk_combo(nk, zoom_modes, ZOOM_COUNT, p->target_zoom, 16, nk_vec2(nk_widget_width(nk), 500)); + if (zoom != p->target_zoom) { + // Image crop may change + pl_renderer_flush_cache(p->renderer); + p->target_zoom = zoom; + } + + nk_label(nk, "Upscaler:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, SCALE_DESC(upscaler, scale_none), nk_vec2(nk_widget_width(nk), 500))) { + nk_layout_row_dynamic(nk, 16, 1); + if (nk_combo_item_label(nk, scale_none, NK_TEXT_LEFT)) + par->upscaler = NULL; + for (int i = 0; i < pl_num_filter_configs; i++) { + f = pl_filter_configs[i]; + if (!f->description) + continue; + if (!(f->allowed & PL_FILTER_UPSCALING)) + continue; + if (!p->advanced_scalers && !(f->recommended & PL_FILTER_UPSCALING)) + continue; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + par->upscaler = f; + } + nk_combo_end(nk); + } + + nk_label(nk, "Downscaler:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, SCALE_DESC(downscaler, scale_none), nk_vec2(nk_widget_width(nk), 500))) { + nk_layout_row_dynamic(nk, 16, 1); + if (nk_combo_item_label(nk, scale_none, NK_TEXT_LEFT)) + par->downscaler = NULL; + for (int i = 0; i < pl_num_filter_configs; i++) { + f = pl_filter_configs[i]; + if (!f->description) + continue; + if (!(f->allowed & PL_FILTER_DOWNSCALING)) + continue; + if (!p->advanced_scalers && !(f->recommended & PL_FILTER_DOWNSCALING)) + continue; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + par->downscaler = f; + } + nk_combo_end(nk); + } + + nk_label(nk, "Plane scaler:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, SCALE_DESC(plane_upscaler, pscale_none), nk_vec2(nk_widget_width(nk), 500))) { + nk_layout_row_dynamic(nk, 16, 1); + if (nk_combo_item_label(nk, pscale_none, NK_TEXT_LEFT)) + par->downscaler = NULL; + for (int i = 0; i < pl_num_filter_configs; i++) { + f = pl_filter_configs[i]; + if (!f->description) + continue; + if (!(f->allowed & PL_FILTER_UPSCALING)) + continue; + if (!p->advanced_scalers && !(f->recommended & PL_FILTER_UPSCALING)) + continue; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + par->plane_upscaler = f; + } + nk_combo_end(nk); + } + + nk_label(nk, "Frame mixing:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, SCALE_DESC(frame_mixer, tscale_none), nk_vec2(nk_widget_width(nk), 300))) { + nk_layout_row_dynamic(nk, 16, 1); + if (nk_combo_item_label(nk, tscale_none, NK_TEXT_LEFT)) + par->frame_mixer = NULL; + for (int i = 0; i < pl_num_filter_configs; i++) { + f = pl_filter_configs[i]; + if (!f->description) + continue; + if (!(f->allowed & PL_FILTER_FRAME_MIXING)) + continue; + if (!p->advanced_scalers && !(f->recommended & PL_FILTER_FRAME_MIXING)) + continue; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + par->frame_mixer = f; + } + nk_combo_end(nk); + } + + nk_layout_row_dynamic(nk, 24, 2); + par->skip_anti_aliasing = !nk_check_label(nk, "Anti-aliasing", !par->skip_anti_aliasing); + nk_property_float(nk, "Antiringing", 0, &par->antiringing_strength, 1.0, 0.05, 0.001); + + struct pl_sigmoid_params *spar = &opts->sigmoid_params; + nk_layout_row_dynamic(nk, 24, 2); + par->sigmoid_params = nk_check_label(nk, "Sigmoidization", par->sigmoid_params) ? spar : NULL; + if (nk_button_label(nk, "Default values")) + *spar = pl_sigmoid_default_params; + nk_property_float(nk, "Sigmoid center", 0, &spar->center, 1, 0.1, 0.01); + nk_property_float(nk, "Sigmoid slope", 0, &spar->slope, 100, 1, 0.1); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Deinterlacing", NK_MINIMIZED)) { + struct pl_deinterlace_params *dpar = &opts->deinterlace_params; + nk_layout_row_dynamic(nk, 24, 2); + par->deinterlace_params = nk_check_label(nk, "Enable", par->deinterlace_params) ? dpar : NULL; + if (nk_button_label(nk, "Reset settings")) + *dpar = pl_deinterlace_default_params; + + static const char *deint_algos[PL_DEINTERLACE_ALGORITHM_COUNT] = { + [PL_DEINTERLACE_WEAVE] = "Field weaving (no-op)", + [PL_DEINTERLACE_BOB] = "Naive bob (line doubling)", + [PL_DEINTERLACE_YADIF] = "Yadif (\"yet another deinterlacing filter\")", + }; + + nk_label(nk, "Deinterlacing algorithm", NK_TEXT_LEFT); + dpar->algo = nk_combo(nk, deint_algos, PL_DEINTERLACE_ALGORITHM_COUNT, + dpar->algo, 16, nk_vec2(nk_widget_width(nk), 300)); + + switch (dpar->algo) { + case PL_DEINTERLACE_WEAVE: + case PL_DEINTERLACE_BOB: + break; + case PL_DEINTERLACE_YADIF: + nk_checkbox_label(nk, "Skip spatial check", &dpar->skip_spatial_check); + break; + default: abort(); + } + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Debanding", NK_MINIMIZED)) { + struct pl_deband_params *dpar = &opts->deband_params; + nk_layout_row_dynamic(nk, 24, 2); + par->deband_params = nk_check_label(nk, "Enable", par->deband_params) ? dpar : NULL; + if (nk_button_label(nk, "Reset settings")) + *dpar = pl_deband_default_params; + nk_property_int(nk, "Iterations", 0, &dpar->iterations, 8, 1, 0); + nk_property_float(nk, "Threshold", 0, &dpar->threshold, 256, 1, 0.5); + nk_property_float(nk, "Radius", 0, &dpar->radius, 256, 1, 0.2); + nk_property_float(nk, "Grain", 0, &dpar->grain, 512, 1, 0.5); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Distortion", NK_MINIMIZED)) { + struct pl_distort_params *dpar = &opts->distort_params; + nk_layout_row_dynamic(nk, 24, 2); + par->distort_params = nk_check_label(nk, "Enable", par->distort_params) ? dpar : NULL; + if (nk_button_label(nk, "Reset settings")) + *dpar = pl_distort_default_params; + + static const char *address_modes[PL_TEX_ADDRESS_MODE_COUNT] = { + [PL_TEX_ADDRESS_CLAMP] = "Clamp edges", + [PL_TEX_ADDRESS_REPEAT] = "Repeat edges", + [PL_TEX_ADDRESS_MIRROR] = "Mirror edges", + }; + + nk_checkbox_label(nk, "Constrain bounds", &dpar->constrain); + dpar->address_mode = nk_combo(nk, address_modes, PL_TEX_ADDRESS_MODE_COUNT, + dpar->address_mode, 16, nk_vec2(nk_widget_width(nk), 100)); + bool alpha = nk_check_label(nk, "Transparent background", dpar->alpha_mode); + dpar->alpha_mode = alpha ? PL_ALPHA_INDEPENDENT : PL_ALPHA_UNKNOWN; + nk_checkbox_label(nk, "Bicubic interpolation", &dpar->bicubic); + + struct pl_transform2x2 *tf = &dpar->transform; + nk_property_float(nk, "Scale X", -10.0, &tf->mat.m[0][0], 10.0, 0.1, 0.005); + nk_property_float(nk, "Shear X", -10.0, &tf->mat.m[0][1], 10.0, 0.1, 0.005); + nk_property_float(nk, "Shear Y", -10.0, &tf->mat.m[1][0], 10.0, 0.1, 0.005); + nk_property_float(nk, "Scale Y", -10.0, &tf->mat.m[1][1], 10.0, 0.1, 0.005); + nk_property_float(nk, "Offset X", -10.0, &tf->c[0], 10.0, 0.1, 0.005); + nk_property_float(nk, "Offset Y", -10.0, &tf->c[1], 10.0, 0.1, 0.005); + + float zoom_ref = fabsf(tf->mat.m[0][0] * tf->mat.m[1][1] - + tf->mat.m[0][1] * tf->mat.m[1][0]); + zoom_ref = logf(fmaxf(zoom_ref, 1e-4)); + float zoom = zoom_ref; + nk_property_float(nk, "log(Zoom)", -10.0, &zoom, 10.0, 0.1, 0.005); + pl_transform2x2_scale(tf, expf(zoom - zoom_ref)); + + float angle_ref = (atan2f(tf->mat.m[1][0], tf->mat.m[1][1]) - + atan2f(tf->mat.m[0][1], tf->mat.m[0][0])) / 2; + angle_ref = fmodf(angle_ref * 180/M_PI + 540, 360) - 180; + float angle = angle_ref; + nk_property_float(nk, "Rotate (°)", -200, &angle, 200, -5, -0.2); + float angle_delta = (angle - angle_ref) * M_PI / 180; + const pl_matrix2x2 rot = pl_matrix2x2_rotation(angle_delta); + pl_matrix2x2_rmul(&rot, &tf->mat); + + bool flip_ox = nk_button_label(nk, "Flip output X"); + bool flip_oy = nk_button_label(nk, "Flip output Y"); + bool flip_ix = nk_button_label(nk, "Flip input X"); + bool flip_iy = nk_button_label(nk, "Flip input Y"); + if (flip_ox ^ flip_ix) + tf->mat.m[0][0] = -tf->mat.m[0][0]; + if (flip_ox ^ flip_iy) + tf->mat.m[0][1] = -tf->mat.m[0][1]; + if (flip_oy ^ flip_ix) + tf->mat.m[1][0] = -tf->mat.m[1][0]; + if (flip_oy ^ flip_iy) + tf->mat.m[1][1] = -tf->mat.m[1][1]; + if (flip_ox) + tf->c[0] = -tf->c[0]; + if (flip_oy) + tf->c[1] = -tf->c[1]; + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Color adjustment", NK_MINIMIZED)) { + struct pl_color_adjustment *adj = &opts->color_adjustment; + nk_layout_row_dynamic(nk, 24, 2); + par->color_adjustment = nk_check_label(nk, "Enable", par->color_adjustment) ? adj : NULL; + if (nk_button_label(nk, "Default values")) + *adj = pl_color_adjustment_neutral; + nk_property_float(nk, "Brightness", -1, &adj->brightness, 1, 0.1, 0.005); + nk_property_float(nk, "Contrast", 0, &adj->contrast, 10, 0.1, 0.005); + + // Convert to (cyclical) degrees for display + int deg = roundf(adj->hue * 180.0 / M_PI); + nk_property_int(nk, "Hue (°)", -50, °, 400, 1, 1); + adj->hue = ((deg + 360) % 360) * M_PI / 180.0; + + nk_property_float(nk, "Saturation", 0, &adj->saturation, 10, 0.1, 0.005); + nk_property_float(nk, "Gamma", 0, &adj->gamma, 10, 0.1, 0.005); + + // Convert to human-friendly temperature values for display + int temp = (int) roundf(adj->temperature * 3500) + 6500; + nk_property_int(nk, "Temperature (K)", 3000, &temp, 10000, 10, 5); + adj->temperature = (temp - 6500) / 3500.0; + + struct pl_cone_params *cpar = &opts->cone_params; + nk_layout_row_dynamic(nk, 24, 2); + par->cone_params = nk_check_label(nk, "Color blindness", par->cone_params) ? cpar : NULL; + if (nk_button_label(nk, "Default values")) + *cpar = pl_vision_normal; + nk_layout_row(nk, NK_DYNAMIC, 24, 5, (float[]){ 0.25, 0.25/3, 0.25/3, 0.25/3, 0.5 }); + nk_label(nk, "Cone model:", NK_TEXT_LEFT); + unsigned int cones = cpar->cones; + nk_checkbox_flags_label(nk, "L", &cones, PL_CONE_L); + nk_checkbox_flags_label(nk, "M", &cones, PL_CONE_M); + nk_checkbox_flags_label(nk, "S", &cones, PL_CONE_S); + cpar->cones = cones; + nk_property_float(nk, "Sensitivity", 0.0, &cpar->strength, 5.0, 0.1, 0.01); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "HDR peak detection", NK_MINIMIZED)) { + struct pl_peak_detect_params *ppar = &opts->peak_detect_params; + nk_layout_row_dynamic(nk, 24, 2); + par->peak_detect_params = nk_check_label(nk, "Enable", par->peak_detect_params) ? ppar : NULL; + if (nk_button_label(nk, "Reset settings")) + *ppar = pl_peak_detect_default_params; + nk_property_float(nk, "Threshold low", 0.0, &ppar->scene_threshold_low, 20.0, 0.5, 0.005); + nk_property_float(nk, "Threshold high", 0.0, &ppar->scene_threshold_high, 20.0, 0.5, 0.005); + nk_property_float(nk, "Smoothing period", 0.0, &ppar->smoothing_period, 1000.0, 5.0, 1.0); + nk_property_float(nk, "Peak percentile", 95.0, &ppar->percentile, 100.0, 0.01, 0.001); + nk_checkbox_label(nk, "Allow 1-frame delay", &ppar->allow_delayed); + + struct pl_hdr_metadata metadata; + if (pl_renderer_get_hdr_metadata(p->renderer, &metadata)) { + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Detected max luminance:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.2f cd/m² (%.2f%% PQ)", + pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, metadata.max_pq_y), + 100.0f * metadata.max_pq_y); + nk_label(nk, "Detected avg luminance:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.2f cd/m² (%.2f%% PQ)", + pl_hdr_rescale(PL_HDR_PQ, PL_HDR_NITS, metadata.avg_pq_y), + 100.0f * metadata.avg_pq_y); + } + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Tone mapping", NK_MINIMIZED)) { + struct pl_color_map_params *cpar = &opts->color_map_params; + static const struct pl_color_map_params null_settings = {0}; + nk_layout_row_dynamic(nk, 24, 2); + par->color_map_params = nk_check_label(nk, "Enable", + par->color_map_params == cpar) ? cpar : &null_settings; + if (nk_button_label(nk, "Reset settings")) + *cpar = pl_color_map_default_params; + + nk_label(nk, "Gamut mapping function:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, cpar->gamut_mapping->description, + nk_vec2(nk_widget_width(nk), 500))) + { + nk_layout_row_dynamic(nk, 16, 1); + for (int i = 0; i < pl_num_gamut_map_functions; i++) { + const struct pl_gamut_map_function *f = pl_gamut_map_functions[i]; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + cpar->gamut_mapping = f; + } + nk_combo_end(nk); + } + + nk_label(nk, "Tone mapping function:", NK_TEXT_LEFT); + if (nk_combo_begin_label(nk, cpar->tone_mapping_function->description, + nk_vec2(nk_widget_width(nk), 500))) + { + nk_layout_row_dynamic(nk, 16, 1); + for (int i = 0; i < pl_num_tone_map_functions; i++) { + const struct pl_tone_map_function *f = pl_tone_map_functions[i]; + if (nk_combo_item_label(nk, f->description, NK_TEXT_LEFT)) + cpar->tone_mapping_function = f; + } + nk_combo_end(nk); + } + + static const char *metadata_types[PL_HDR_METADATA_TYPE_COUNT] = { + [PL_HDR_METADATA_ANY] = "Automatic selection", + [PL_HDR_METADATA_NONE] = "None (disabled)", + [PL_HDR_METADATA_HDR10] = "HDR10 (static)", + [PL_HDR_METADATA_HDR10PLUS] = "HDR10+ (MaxRGB)", + [PL_HDR_METADATA_CIE_Y] = "Luminance (CIE Y)", + }; + + nk_label(nk, "HDR metadata source:", NK_TEXT_LEFT); + cpar->metadata = nk_combo(nk, metadata_types, + PL_HDR_METADATA_TYPE_COUNT, + cpar->metadata, + 16, nk_vec2(nk_widget_width(nk), 300)); + + nk_property_float(nk, "Contrast recovery", 0.0, &cpar->contrast_recovery, 2.0, 0.05, 0.005); + nk_property_float(nk, "Contrast smoothness", 1.0, &cpar->contrast_smoothness, 32.0, 0.1, 0.005); + + nk_property_int(nk, "LUT size", 16, &cpar->lut_size, 1024, 1, 1); + nk_property_int(nk, "3DLUT size I", 7, &cpar->lut3d_size[0], 65, 1, 1); + nk_property_int(nk, "3DLUT size C", 7, &cpar->lut3d_size[1], 256, 1, 1); + nk_property_int(nk, "3DLUT size h", 7, &cpar->lut3d_size[2], 1024, 1, 1); + + nk_checkbox_label(nk, "Tricubic interpolation", &cpar->lut3d_tricubic); + nk_checkbox_label(nk, "Force full LUT", &cpar->force_tone_mapping_lut); + nk_checkbox_label(nk, "Inverse tone mapping", &cpar->inverse_tone_mapping); + nk_checkbox_label(nk, "Gamut expansion", &cpar->gamut_expansion); + nk_checkbox_label(nk, "Show clipping", &cpar->show_clipping); + nk_checkbox_label(nk, "Visualize LUT", &cpar->visualize_lut); + + if (cpar->visualize_lut) { + nk_layout_row_dynamic(nk, 24, 2); + const float huerange = 2 * M_PI; + nk_property_float(nk, "Hue", -1, &cpar->visualize_hue, huerange + 1.0, 0.1, 0.01); + nk_property_float(nk, "Theta", 0.0, &cpar->visualize_theta, M_PI_2, 0.1, 0.01); + cpar->visualize_hue = fmodf(cpar->visualize_hue + huerange, huerange); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Fine-tune constants (advanced)", NK_MINIMIZED)) { + struct pl_tone_map_constants *tc = &cpar->tone_constants; + struct pl_gamut_map_constants *gc = &cpar->gamut_constants; + nk_layout_row_dynamic(nk, 20, 2); + nk_property_float(nk, "Perceptual deadzone", 0.0, &gc->perceptual_deadzone, 1.0, 0.05, 0.001); + nk_property_float(nk, "Perceptual strength", 0.0, &gc->perceptual_strength, 1.0, 0.05, 0.001); + nk_property_float(nk, "Colorimetric gamma", 0.0, &gc->colorimetric_gamma, 10.0, 0.05, 0.001); + nk_property_float(nk, "Softclip knee", 0.0, &gc->softclip_knee, 1.0, 0.05, 0.001); + nk_property_float(nk, "Softclip desaturation", 0.0, &gc->softclip_desat, 1.0, 0.05, 0.001); + nk_property_float(nk, "Knee adaptation", 0.0, &tc->knee_adaptation, 1.0, 0.05, 0.001); + nk_property_float(nk, "Knee minimum", 0.0, &tc->knee_minimum, 0.5, 0.05, 0.001); + nk_property_float(nk, "Knee maximum", 0.5, &tc->knee_maximum, 1.0, 0.05, 0.001); + nk_property_float(nk, "Knee default", tc->knee_minimum, &tc->knee_default, tc->knee_maximum, 0.05, 0.001); + nk_property_float(nk, "BT.2390 offset", 0.5, &tc->knee_offset, 2.0, 0.05, 0.001); + nk_property_float(nk, "Spline slope tuning", 0.0, &tc->slope_tuning, 10.0, 0.05, 0.001); + nk_property_float(nk, "Spline slope offset", 0.0, &tc->slope_offset, 1.0, 0.05, 0.001); + nk_property_float(nk, "Spline contrast", 0.0, &tc->spline_contrast, 1.5, 0.05, 0.001); + nk_property_float(nk, "Reinhard contrast", 0.0, &tc->reinhard_contrast, 1.0, 0.05, 0.001); + nk_property_float(nk, "Linear knee point", 0.0, &tc->linear_knee, 1.0, 0.05, 0.001); + nk_property_float(nk, "Linear exposure", 0.0, &tc->exposure, 10.0, 0.05, 0.001); + nk_tree_pop(nk); + } + + nk_layout_row_dynamic(nk, 50, 1); + if (ui_widget_hover(nk, "Drop .cube file here...") && dropped_file) { + uint8_t *buf; + size_t size; + int ret = av_file_map(dropped_file, &buf, &size, 0, NULL); + if (ret < 0) { + fprintf(stderr, "Failed opening '%s': %s\n", dropped_file, + av_err2str(ret)); + } else { + pl_lut_free((struct pl_custom_lut **) &par->lut); + par->lut = pl_lut_parse_cube(p->log, (char *) buf, size); + av_file_unmap(buf, size); + } + } + + static const char *lut_types[] = { + [PL_LUT_UNKNOWN] = "Auto (unknown)", + [PL_LUT_NATIVE] = "Raw RGB (native)", + [PL_LUT_NORMALIZED] = "Linear RGB (normalized)", + [PL_LUT_CONVERSION] = "Gamut conversion (native)", + }; + + nk_layout_row(nk, NK_DYNAMIC, 24, 3, (float[]){ 0.2, 0.3, 0.5 }); + if (nk_button_label(nk, "Reset LUT")) { + pl_lut_free((struct pl_custom_lut **) &par->lut); + par->lut_type = PL_LUT_UNKNOWN; + } + + nk_label(nk, "LUT type:", NK_TEXT_CENTERED); + par->lut_type = nk_combo(nk, lut_types, 4, par->lut_type, + 16, nk_vec2(nk_widget_width(nk), 100)); + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Dithering", NK_MINIMIZED)) { + struct pl_dither_params *dpar = &opts->dither_params; + nk_layout_row_dynamic(nk, 24, 2); + par->dither_params = nk_check_label(nk, "Enable", par->dither_params) ? dpar : NULL; + if (nk_button_label(nk, "Reset settings")) + *dpar = pl_dither_default_params; + + static const char *dither_methods[PL_DITHER_METHOD_COUNT] = { + [PL_DITHER_BLUE_NOISE] = "Blue noise", + [PL_DITHER_ORDERED_LUT] = "Ordered (LUT)", + [PL_DITHER_ORDERED_FIXED] = "Ordered (fixed size)", + [PL_DITHER_WHITE_NOISE] = "White noise", + }; + + nk_label(nk, "Dither method:", NK_TEXT_LEFT); + dpar->method = nk_combo(nk, dither_methods, PL_DITHER_METHOD_COUNT, dpar->method, + 16, nk_vec2(nk_widget_width(nk), 100)); + + static const char *lut_sizes[8] = { + "2x2", "4x4", "8x8", "16x16", "32x32", "64x64", "128x128", "256x256", + }; + + nk_label(nk, "LUT size:", NK_TEXT_LEFT); + switch (dpar->method) { + case PL_DITHER_BLUE_NOISE: + case PL_DITHER_ORDERED_LUT: { + int size = dpar->lut_size - 1; + nk_combobox(nk, lut_sizes, 8, &size, 16, nk_vec2(nk_widget_width(nk), 200)); + dpar->lut_size = size + 1; + break; + } + case PL_DITHER_ORDERED_FIXED: + nk_label(nk, "64x64", NK_TEXT_LEFT); + break; + default: + nk_label(nk, "(N/A)", NK_TEXT_LEFT); + break; + } + + nk_checkbox_label(nk, "Temporal dithering", &dpar->temporal); + + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Error diffusion:", NK_TEXT_LEFT); + const char *name = par->error_diffusion ? par->error_diffusion->description : "(None)"; + if (nk_combo_begin_label(nk, name, nk_vec2(nk_widget_width(nk), 500))) { + nk_layout_row_dynamic(nk, 16, 1); + if (nk_combo_item_label(nk, "(None)", NK_TEXT_LEFT)) + par->error_diffusion = NULL; + for (int i = 0; i < pl_num_error_diffusion_kernels; i++) { + const struct pl_error_diffusion_kernel *k = pl_error_diffusion_kernels[i]; + if (nk_combo_item_label(nk, k->description, NK_TEXT_LEFT)) + par->error_diffusion = k; + } + nk_combo_end(nk); + } + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Output color space", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 24, 2); + nk_checkbox_label(nk, "Enable", &p->target_override); + bool reset = nk_button_label(nk, "Reset settings"); + bool reset_icc = reset; + char buf[64] = {0}; + + nk_layout_row(nk, NK_DYNAMIC, 24, 2, (float[]){ 0.3, 0.7 }); + + const char *primaries[PL_COLOR_PRIM_COUNT] = { + [PL_COLOR_PRIM_UNKNOWN] = "Auto (unknown)", + [PL_COLOR_PRIM_BT_601_525] = "ITU-R Rec. BT.601 (525-line = NTSC, SMPTE-C)", + [PL_COLOR_PRIM_BT_601_625] = "ITU-R Rec. BT.601 (625-line = PAL, SECAM)", + [PL_COLOR_PRIM_BT_709] = "ITU-R Rec. BT.709 (HD), also sRGB", + [PL_COLOR_PRIM_BT_470M] = "ITU-R Rec. BT.470 M", + [PL_COLOR_PRIM_EBU_3213] = "EBU Tech. 3213-E / JEDEC P22 phosphors", + [PL_COLOR_PRIM_BT_2020] = "ITU-R Rec. BT.2020 (UltraHD)", + [PL_COLOR_PRIM_APPLE] = "Apple RGB", + [PL_COLOR_PRIM_ADOBE] = "Adobe RGB (1998)", + [PL_COLOR_PRIM_PRO_PHOTO] = "ProPhoto RGB (ROMM)", + [PL_COLOR_PRIM_CIE_1931] = "CIE 1931 RGB primaries", + [PL_COLOR_PRIM_DCI_P3] = "DCI-P3 (Digital Cinema)", + [PL_COLOR_PRIM_DISPLAY_P3] = "DCI-P3 (Digital Cinema) with D65 white point", + [PL_COLOR_PRIM_V_GAMUT] = "Panasonic V-Gamut (VARICAM)", + [PL_COLOR_PRIM_S_GAMUT] = "Sony S-Gamut", + [PL_COLOR_PRIM_FILM_C] = "Traditional film primaries with Illuminant C", + [PL_COLOR_PRIM_ACES_AP0] = "ACES Primaries #0", + [PL_COLOR_PRIM_ACES_AP1] = "ACES Primaries #1", + }; + + if (target->color.primaries) { + snprintf(buf, sizeof(buf), "Auto (%s)", primaries[target->color.primaries]); + primaries[PL_COLOR_PRIM_UNKNOWN] = buf; + } + + nk_label(nk, "Primaries:", NK_TEXT_LEFT); + p->force_prim = nk_combo(nk, primaries, PL_COLOR_PRIM_COUNT, p->force_prim, + 16, nk_vec2(nk_widget_width(nk), 200)); + + const char *transfers[PL_COLOR_TRC_COUNT] = { + [PL_COLOR_TRC_UNKNOWN] = "Auto (unknown SDR)", + [PL_COLOR_TRC_BT_1886] = "ITU-R Rec. BT.1886 (CRT emulation + OOTF)", + [PL_COLOR_TRC_SRGB] = "IEC 61966-2-4 sRGB (CRT emulation)", + [PL_COLOR_TRC_LINEAR] = "Linear light content", + [PL_COLOR_TRC_GAMMA18] = "Pure power gamma 1.8", + [PL_COLOR_TRC_GAMMA20] = "Pure power gamma 2.0", + [PL_COLOR_TRC_GAMMA22] = "Pure power gamma 2.2", + [PL_COLOR_TRC_GAMMA24] = "Pure power gamma 2.4", + [PL_COLOR_TRC_GAMMA26] = "Pure power gamma 2.6", + [PL_COLOR_TRC_GAMMA28] = "Pure power gamma 2.8", + [PL_COLOR_TRC_PRO_PHOTO] = "ProPhoto RGB (ROMM)", + [PL_COLOR_TRC_ST428] = "Digital Cinema Distribution Master (XYZ)", + [PL_COLOR_TRC_PQ] = "ITU-R BT.2100 PQ (perceptual quantizer), aka SMPTE ST2048", + [PL_COLOR_TRC_HLG] = "ITU-R BT.2100 HLG (hybrid log-gamma), aka ARIB STD-B67", + [PL_COLOR_TRC_V_LOG] = "Panasonic V-Log (VARICAM)", + [PL_COLOR_TRC_S_LOG1] = "Sony S-Log1", + [PL_COLOR_TRC_S_LOG2] = "Sony S-Log2", + }; + + if (target->color.transfer) { + snprintf(buf, sizeof(buf), "Auto (%s)", transfers[target->color.transfer]); + transfers[PL_COLOR_TRC_UNKNOWN] = buf; + } + + nk_label(nk, "Transfer:", NK_TEXT_LEFT); + p->force_trc = nk_combo(nk, transfers, PL_COLOR_TRC_COUNT, p->force_trc, + 16, nk_vec2(nk_widget_width(nk), 200)); + + nk_layout_row_dynamic(nk, 24, 2); + nk_checkbox_label(nk, "Override HDR levels", &p->force_hdr_enable); + + // Ensure these values are always legal by going through + // pl_color_space_infer + nk_layout_row_dynamic(nk, 24, 2); + struct pl_color_space fix = target->color; + apply_csp_overrides(p, &fix); + pl_color_space_infer(&fix); + + fix.hdr.min_luma *= 1000; // better value range + nk_property_float(nk, "White point (cd/m²)", + 10.0, &fix.hdr.max_luma, 10000.0, + fix.hdr.max_luma / 100, fix.hdr.max_luma / 1000); + nk_property_float(nk, "Black point (mcd/m²)", + PL_COLOR_HDR_BLACK * 1000, &fix.hdr.min_luma, + 100.0 * 1000, 5, 2); + fix.hdr.min_luma /= 1000; + pl_color_space_infer(&fix); + p->force_hdr = fix.hdr; + + struct pl_color_repr *trepr = &p->force_repr; + nk_layout_row(nk, NK_DYNAMIC, 24, 2, (float[]){ 0.3, 0.7 }); + + const char *systems[PL_COLOR_SYSTEM_COUNT] = { + [PL_COLOR_SYSTEM_UNKNOWN] = "Auto (unknown)", + [PL_COLOR_SYSTEM_BT_601] = "ITU-R Rec. BT.601 (SD)", + [PL_COLOR_SYSTEM_BT_709] = "ITU-R Rec. BT.709 (HD)", + [PL_COLOR_SYSTEM_SMPTE_240M] = "SMPTE-240M", + [PL_COLOR_SYSTEM_BT_2020_NC] = "ITU-R Rec. BT.2020 (non-constant luminance)", + [PL_COLOR_SYSTEM_BT_2020_C] = "ITU-R Rec. BT.2020 (constant luminance)", + [PL_COLOR_SYSTEM_BT_2100_PQ] = "ITU-R Rec. BT.2100 ICtCp PQ variant", + [PL_COLOR_SYSTEM_BT_2100_HLG] = "ITU-R Rec. BT.2100 ICtCp HLG variant", + [PL_COLOR_SYSTEM_DOLBYVISION] = "Dolby Vision (invalid for output)", + [PL_COLOR_SYSTEM_YCGCO] = "YCgCo (derived from RGB)", + [PL_COLOR_SYSTEM_RGB] = "Red, Green and Blue", + [PL_COLOR_SYSTEM_XYZ] = "Digital Cinema Distribution Master (XYZ)", + }; + + if (target->repr.sys) { + snprintf(buf, sizeof(buf), "Auto (%s)", systems[target->repr.sys]); + systems[PL_COLOR_SYSTEM_UNKNOWN] = buf; + } + + nk_label(nk, "System:", NK_TEXT_LEFT); + trepr->sys = nk_combo(nk, systems, PL_COLOR_SYSTEM_COUNT, trepr->sys, + 16, nk_vec2(nk_widget_width(nk), 200)); + if (trepr->sys == PL_COLOR_SYSTEM_DOLBYVISION) + trepr->sys = PL_COLOR_SYSTEM_UNKNOWN; + + const char *levels[PL_COLOR_LEVELS_COUNT] = { + [PL_COLOR_LEVELS_UNKNOWN] = "Auto (unknown)", + [PL_COLOR_LEVELS_LIMITED] = "Limited/TV range, e.g. 16-235", + [PL_COLOR_LEVELS_FULL] = "Full/PC range, e.g. 0-255", + }; + + if (target->repr.levels) { + snprintf(buf, sizeof(buf), "Auto (%s)", levels[target->repr.levels]); + levels[PL_COLOR_LEVELS_UNKNOWN] = buf; + } + + nk_label(nk, "Levels:", NK_TEXT_LEFT); + trepr->levels = nk_combo(nk, levels, PL_COLOR_LEVELS_COUNT, trepr->levels, + 16, nk_vec2(nk_widget_width(nk), 200)); + + const char *alphas[PL_ALPHA_MODE_COUNT] = { + [PL_ALPHA_UNKNOWN] = "Auto (unknown, or no alpha)", + [PL_ALPHA_INDEPENDENT] = "Independent alpha channel", + [PL_ALPHA_PREMULTIPLIED] = "Premultiplied alpha channel", + }; + + if (target->repr.alpha) { + snprintf(buf, sizeof(buf), "Auto (%s)", alphas[target->repr.alpha]); + alphas[PL_ALPHA_UNKNOWN] = buf; + } + + nk_label(nk, "Alpha:", NK_TEXT_LEFT); + trepr->alpha = nk_combo(nk, alphas, PL_ALPHA_MODE_COUNT, trepr->alpha, + 16, nk_vec2(nk_widget_width(nk), 200)); + + const struct pl_bit_encoding *bits = &target->repr.bits; + nk_label(nk, "Bit depth:", NK_TEXT_LEFT); + auto_property_int(nk, bits->color_depth, 0, + &trepr->bits.color_depth, 16, 1, 0); + + if (bits->color_depth != bits->sample_depth) { + nk_label(nk, "Sample bit depth:", NK_TEXT_LEFT); + auto_property_int(nk, bits->sample_depth, 0, + &trepr->bits.sample_depth, 16, 1, 0); + } else { + // Adjust these two fields in unison + trepr->bits.sample_depth = trepr->bits.color_depth; + } + + if (bits->bit_shift) { + nk_label(nk, "Bit shift:", NK_TEXT_LEFT); + auto_property_int(nk, bits->bit_shift, 0, + &trepr->bits.bit_shift, 16, 1, 0); + } else { + trepr->bits.bit_shift = 0; + } + + nk_layout_row_dynamic(nk, 24, 1); + nk_checkbox_label(nk, "Forward input color space to display", &p->colorspace_hint); + + if (p->colorspace_hint && !p->force_hdr_enable) { + nk_checkbox_label(nk, "Forward dynamic brightness changes to display", + &p->colorspace_hint_dynamic); + } + + nk_layout_row_dynamic(nk, 50, 1); + if (ui_widget_hover(nk, "Drop ICC profile here...") && dropped_file) { + struct pl_icc_profile profile; + int ret = av_file_map(dropped_file, (uint8_t **) &profile.data, + &profile.len, 0, NULL); + if (ret < 0) { + fprintf(stderr, "Failed opening '%s': %s\n", dropped_file, + av_err2str(ret)); + } else { + free(p->icc_name); + pl_icc_profile_compute_signature(&profile); + pl_icc_update(p->log, &p->icc, &profile, pl_icc_params( + .force_bpc = p->force_bpc, + .max_luma = p->use_icc_luma ? 0 : PL_COLOR_SDR_WHITE, + )); + av_file_unmap((void *) profile.data, profile.len); + if (p->icc) + p->icc_name = strdup(PL_BASENAME((char *) dropped_file)); + } + } + + if (p->icc) { + nk_layout_row_dynamic(nk, 24, 2); + nk_labelf(nk, NK_TEXT_LEFT, "Loaded: %s", + p->icc_name ? p->icc_name : "(unknown)"); + reset_icc |= nk_button_label(nk, "Reset ICC"); + nk_checkbox_label(nk, "Force BPC", &p->force_bpc); + nk_checkbox_label(nk, "Use detected luminance", &p->use_icc_luma); + } + + // Apply the reset last to prevent the UI from flashing for a frame + if (reset) { + p->force_repr = (struct pl_color_repr) {0}; + p->force_prim = PL_COLOR_PRIM_UNKNOWN; + p->force_trc = PL_COLOR_TRC_UNKNOWN; + p->force_hdr = (struct pl_hdr_metadata) {0}; + p->force_hdr_enable = false; + } + + if (reset_icc && p->icc) { + pl_icc_close(&p->icc); + free(p->icc_name); + p->icc_name = NULL; + } + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Custom shaders", NK_MINIMIZED)) { + + nk_layout_row_dynamic(nk, 50, 1); + if (ui_widget_hover(nk, "Drop .hook/.glsl files here...") && dropped_file) { + uint8_t *buf; + size_t size; + int ret = av_file_map(dropped_file, &buf, &size, 0, NULL); + if (ret < 0) { + fprintf(stderr, "Failed opening '%s': %s\n", dropped_file, + av_err2str(ret)); + } else { + const struct pl_hook *hook; + hook = pl_mpv_user_shader_parse(p->win->gpu, (char *) buf, size); + av_file_unmap(buf, size); + add_hook(p, hook, dropped_file); + } + } + + const float px = 24.0; + nk_layout_row_template_begin(nk, px); + nk_layout_row_template_push_static(nk, px); + nk_layout_row_template_push_static(nk, px); + nk_layout_row_template_push_static(nk, px); + nk_layout_row_template_push_dynamic(nk); + nk_layout_row_template_end(nk); + for (int i = 0; i < p->shader_num; i++) { + + if (i == 0) { + nk_label(nk, "·", NK_TEXT_CENTERED); + } else if (nk_button_symbol(nk, NK_SYMBOL_TRIANGLE_UP)) { + const struct pl_hook *prev_hook = p->shader_hooks[i - 1]; + char *prev_path = p->shader_paths[i - 1]; + p->shader_hooks[i - 1] = p->shader_hooks[i]; + p->shader_paths[i - 1] = p->shader_paths[i]; + p->shader_hooks[i] = prev_hook; + p->shader_paths[i] = prev_path; + } + + if (i == p->shader_num - 1) { + nk_label(nk, "·", NK_TEXT_CENTERED); + } else if (nk_button_symbol(nk, NK_SYMBOL_TRIANGLE_DOWN)) { + const struct pl_hook *next_hook = p->shader_hooks[i + 1]; + char *next_path = p->shader_paths[i + 1]; + p->shader_hooks[i + 1] = p->shader_hooks[i]; + p->shader_paths[i + 1] = p->shader_paths[i]; + p->shader_hooks[i] = next_hook; + p->shader_paths[i] = next_path; + } + + if (nk_button_symbol(nk, NK_SYMBOL_X)) { + pl_mpv_user_shader_destroy(&p->shader_hooks[i]); + free(p->shader_paths[i]); + p->shader_num--; + memmove(&p->shader_hooks[i], &p->shader_hooks[i+1], + (p->shader_num - i) * sizeof(void *)); + memmove(&p->shader_paths[i], &p->shader_paths[i+1], + (p->shader_num - i) * sizeof(char *)); + if (i == p->shader_num) + break; + } + + if (p->shader_hooks[i]->num_parameters == 0) { + nk_label(nk, p->shader_paths[i], NK_TEXT_LEFT); + continue; + } + + if (nk_combo_begin_label(nk, p->shader_paths[i], nk_vec2(nk_widget_width(nk), 500))) { + nk_layout_row_dynamic(nk, 32, 1); + for (int j = 0; j < p->shader_hooks[i]->num_parameters; j++) { + const struct pl_hook_par *hp = &p->shader_hooks[i]->parameters[j]; + const char *name = hp->description ? hp->description : hp->name; + switch (hp->type) { + case PL_VAR_FLOAT: + nk_property_float(nk, name, hp->minimum.f, + &hp->data->f, hp->maximum.f, + hp->data->f / 100.0f, + hp->data->f / 1000.0f); + break; + case PL_VAR_SINT: + nk_property_int(nk, name, hp->minimum.i, + &hp->data->i, hp->maximum.i, + 1, 1.0f); + break; + case PL_VAR_UINT: { + int min = FFMIN(hp->minimum.u, INT_MAX); + int max = FFMIN(hp->maximum.u, INT_MAX); + int val = FFMIN(hp->data->u, INT_MAX); + nk_property_int(nk, name, min, &val, max, 1, 1); + hp->data->u = val; + break; + } + default: abort(); + } + } + nk_combo_end(nk); + } + } + + par->hooks = p->shader_hooks; + par->num_hooks = p->shader_num; + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Debug", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 24, 1); + nk_checkbox_label(nk, "Preserve mixing cache", &par->preserve_mixing_cache); + nk_checkbox_label(nk, "Bypass mixing cache", &par->skip_caching_single_frame); + nk_checkbox_label(nk, "Show all scaler presets", &p->advanced_scalers); + nk_checkbox_label(nk, "Disable linear scaling", &par->disable_linear_scaling); + nk_checkbox_label(nk, "Disable built-in scalers", &par->disable_builtin_scalers); + nk_checkbox_label(nk, "Correct subpixel offsets", &par->correct_subpixel_offsets); + nk_checkbox_label(nk, "Force-enable dither", &par->force_dither); + nk_checkbox_label(nk, "Disable gamma-aware dither", &par->disable_dither_gamma_correction); + nk_checkbox_label(nk, "Disable FBOs / advanced rendering", &par->disable_fbos); + nk_checkbox_label(nk, "Force low-bit depth FBOs", &par->force_low_bit_depth_fbos); + nk_checkbox_label(nk, "Disable constant hard-coding", &par->dynamic_constants); + + if (nk_check_label(nk, "Ignore Dolby Vision metadata", p->ignore_dovi) != p->ignore_dovi) { + // Flush the renderer cache on changes, since this can + // drastically alter the subjective appearance of the stream + pl_renderer_flush_cache(p->renderer); + p->ignore_dovi = !p->ignore_dovi; + } + + nk_layout_row_dynamic(nk, 24, 2); + + double prev_fps = p->fps; + bool fps_changed = nk_checkbox_label(nk, "Override display FPS", &p->fps_override); + nk_property_float(nk, "FPS", 10.0, &p->fps, 240.0, 5, 0.1); + if (fps_changed || p->fps != prev_fps) + p->stats.pts_interval = p->stats.vsync_interval = (struct timing) {0}; + + if (nk_button_label(nk, "Flush renderer cache")) + pl_renderer_flush_cache(p->renderer); + if (nk_button_label(nk, "Recreate renderer")) { + pl_renderer_destroy(&p->renderer); + p->renderer = pl_renderer_create(p->log, p->win->gpu); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Shader passes / GPU timing", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 26, 1); + nk_label(nk, "Full frames:", NK_TEXT_LEFT); + for (int i = 0; i < p->num_frame_passes; i++) + draw_shader_pass(nk, &p->frame_info[i]); + + nk_layout_row_dynamic(nk, 26, 1); + nk_label(nk, "Output blending:", NK_TEXT_LEFT); + for (int j = 0; j < MAX_BLEND_FRAMES; j++) { + for (int i = 0; i < p->num_blend_passes[j]; i++) + draw_shader_pass(nk, &p->blend_info[j][i]); + } + + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Frame statistics / CPU timing", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Current PTS:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.3f", p->stats.current_pts); + nk_label(nk, "Estimated FPS:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.3f", pl_queue_estimate_fps(p->queue)); + nk_label(nk, "Estimated vsync rate:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%.3f", pl_queue_estimate_vps(p->queue)); + nk_label(nk, "Frames rendered:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%"PRIu32, p->stats.rendered); + nk_label(nk, "Decoded frames", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%"PRIu32, atomic_load(&p->stats.decoded)); + nk_label(nk, "Dropped frames:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%"PRIu32, p->stats.dropped); + nk_label(nk, "Missed timestamps:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%"PRIu32" (%.3f ms)", + p->stats.missed, p->stats.missed_ms); + nk_label(nk, "Times stalled:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%"PRIu32" (%.3f ms)", + p->stats.stalled, p->stats.stalled_ms); + draw_timing(nk, "Acquire FBO:", &p->stats.acquire); + draw_timing(nk, "Update queue:", &p->stats.update); + draw_timing(nk, "Render frame:", &p->stats.render); + draw_timing(nk, "Draw interface:", &p->stats.draw_ui); + draw_timing(nk, "Voluntary sleep:", &p->stats.sleep); + draw_timing(nk, "Submit frame:", &p->stats.submit); + draw_timing(nk, "Swap buffers:", &p->stats.swap); + draw_timing(nk, "Vsync interval:", &p->stats.vsync_interval); + draw_timing(nk, "PTS interval:", &p->stats.pts_interval); + + if (nk_button_label(nk, "Reset statistics")) + memset(&p->stats, 0, sizeof(p->stats)); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Settings dump", NK_MINIMIZED)) { + + nk_layout_row_dynamic(nk, 24, 2); + if (nk_button_label(nk, "Copy to clipboard")) + window_set_clipboard(p->win, pl_options_save(opts)); + if (nk_button_label(nk, "Load from clipboard")) + pl_options_load(opts, window_get_clipboard(p->win)); + + nk_layout_row_dynamic(nk, 24, 1); + pl_options_iterate(opts, draw_opt_data, nk); + nk_tree_pop(nk); + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Cache statistics", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 24, 2); + nk_label(nk, "Cached objects:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%d", pl_cache_objects(p->cache)); + nk_label(nk, "Total size:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%zu", pl_cache_size(p->cache)); + nk_label(nk, "Maximum total size:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%zu", p->cache->params.max_total_size); + nk_label(nk, "Maximum object size:", NK_TEXT_LEFT); + nk_labelf(nk, NK_TEXT_LEFT, "%zu", p->cache->params.max_object_size); + + if (nk_button_label(nk, "Clear cache")) + pl_cache_reset(p->cache); + if (nk_button_label(nk, "Save cache")) { + FILE *file = fopen(p->cache_file, "wb"); + if (file) { + pl_cache_save_file(p->cache, file); + fclose(file); + } + } + + if (nk_tree_push(nk, NK_TREE_NODE, "Object list", NK_MINIMIZED)) { + nk_layout_row_dynamic(nk, 24, 1); + pl_cache_iterate(p->cache, draw_cache_line, nk); + nk_tree_pop(nk); + } + + nk_tree_pop(nk); + } + + nk_tree_pop(nk); + } + } + nk_end(nk); +} + +#else +void update_settings(struct plplay *p, const struct pl_frame *target) { } +#endif // HAVE_NUKLEAR |