/* * 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 . */ #include #include "common.h" #include "log.h" #include struct priv { pl_log log; // for pl_options_get struct pl_opt_data_t data; pl_str data_text; // for pl_options_save pl_str saved; // internally managed hooks array PL_ARRAY(const struct pl_hook *) hooks; }; static const struct pl_options_t defaults = { .params = { PL_RENDER_DEFAULTS }, .deband_params = { PL_DEBAND_DEFAULTS }, .sigmoid_params = { PL_SIGMOID_DEFAULTS }, .color_adjustment = { PL_COLOR_ADJUSTMENT_NEUTRAL }, .peak_detect_params = { PL_PEAK_DETECT_DEFAULTS }, .color_map_params = { PL_COLOR_MAP_DEFAULTS }, .dither_params = { PL_DITHER_DEFAULTS }, .icc_params = { PL_ICC_DEFAULTS }, .cone_params = { PL_CONE_NONE, 1.0 }, .deinterlace_params = { PL_DEINTERLACE_DEFAULTS }, .distort_params = { PL_DISTORT_DEFAULTS }, .upscaler = { .name = "custom", .description = "Custom upscaler", .allowed = PL_FILTER_UPSCALING, }, .downscaler = { .name = "custom", .description = "Custom downscaler", .allowed = PL_FILTER_DOWNSCALING, }, .plane_upscaler = { .name = "custom", .description = "Custom plane upscaler", .allowed = PL_FILTER_UPSCALING, }, .plane_downscaler = { .name = "custom", .description = "Custom plane downscaler", .allowed = PL_FILTER_DOWNSCALING, }, .frame_mixer = { .name = "custom", .description = "Custom frame mixer", .allowed = PL_FILTER_FRAME_MIXING, }, }; // Copies only whitelisted fields static inline void copy_filter(struct pl_filter_config *dst, const struct pl_filter_config *src) { dst->kernel = src->kernel; dst->window = src->window; dst->radius = src->radius; dst->clamp = src->clamp; dst->blur = src->blur; dst->taper = src->taper; dst->polar = src->polar; for (int i = 0; i < PL_FILTER_MAX_PARAMS; i++) { dst->params[i] = src->params[i]; dst->wparams[i] = src->wparams[i]; } } static inline void redirect_params(pl_options opts) { // Copy all non-NULL params structs into pl_options and redirect them #define REDIRECT_PARAMS(field) do \ { \ if (opts->params.field) { \ opts->field = *opts->params.field; \ opts->params.field = &opts->field; \ } \ } while (0) REDIRECT_PARAMS(deband_params); REDIRECT_PARAMS(sigmoid_params); REDIRECT_PARAMS(color_adjustment); REDIRECT_PARAMS(peak_detect_params); REDIRECT_PARAMS(color_map_params); REDIRECT_PARAMS(dither_params); REDIRECT_PARAMS(icc_params); REDIRECT_PARAMS(cone_params); REDIRECT_PARAMS(deinterlace_params); REDIRECT_PARAMS(distort_params); } void pl_options_reset(pl_options opts, const struct pl_render_params *preset) { *opts = defaults; if (preset) opts->params = *preset; redirect_params(opts); // Make a copy of all scaler configurations that aren't built-in filters struct { bool upscaler; bool downscaler; bool plane_upscaler; bool plane_downscaler; bool frame_mixer; } fixed = {0}; for (int i = 0; i < pl_num_filter_configs; i++) { const struct pl_filter_config *f = pl_filter_configs[i]; fixed.upscaler |= f == opts->params.upscaler; fixed.downscaler |= f == opts->params.downscaler; fixed.plane_upscaler |= f == opts->params.plane_upscaler; fixed.plane_downscaler |= f == opts->params.plane_downscaler; fixed.frame_mixer |= f == opts->params.frame_mixer; } #define REDIRECT_SCALER(scaler) do \ { \ if (opts->params.scaler && !fixed.scaler) { \ copy_filter(&opts->scaler, opts->params.scaler); \ opts->params.scaler = &opts->scaler; \ } \ } while (0) REDIRECT_SCALER(upscaler); REDIRECT_SCALER(downscaler); REDIRECT_SCALER(plane_upscaler); REDIRECT_SCALER(plane_downscaler); REDIRECT_SCALER(frame_mixer); } pl_options pl_options_alloc(pl_log log) { struct pl_options_t *opts = pl_zalloc_obj(NULL, opts, struct priv); struct priv *p = PL_PRIV(opts); pl_options_reset(opts, NULL); p->log = log; return opts; } void pl_options_free(pl_options *popts) { pl_free_ptr((void **) popts); } static void make_hooks_internal(pl_options opts) { struct priv *p = PL_PRIV(opts); struct pl_render_params *params = &opts->params; if (params->num_hooks && params->hooks != p->hooks.elem) { PL_ARRAY_MEMDUP(opts, p->hooks, params->hooks, params->num_hooks); params->hooks = p->hooks.elem; } } void pl_options_add_hook(pl_options opts, const struct pl_hook *hook) { struct priv *p = PL_PRIV(opts); make_hooks_internal(opts); PL_ARRAY_APPEND(opts, p->hooks, hook); opts->params.hooks = p->hooks.elem; } void pl_options_insert_hook(pl_options opts, const struct pl_hook *hook, int idx) { struct priv *p = PL_PRIV(opts); make_hooks_internal(opts); PL_ARRAY_INSERT_AT(opts, p->hooks, idx, hook); opts->params.hooks = p->hooks.elem; } void pl_options_remove_hook_at(pl_options opts, int idx) { struct priv *p = PL_PRIV(opts); make_hooks_internal(opts); PL_ARRAY_REMOVE_AT(p->hooks, idx); opts->params.hooks = p->hooks.elem; } // Options printing/parsing context typedef const struct opt_ctx_t { pl_log log; // as a convenience, only needed when parsing pl_opt opt; void *alloc; // for printing only pl_options opts; // current base ptr } *opt_ctx; struct enum_val { const char *name; unsigned val; }; struct preset { const char *name; const void *val; }; struct named { const char *name; }; typedef const struct opt_priv_t { int (*compare)(opt_ctx p, const void *a, const void *b); // optional void (*print)(opt_ctx p, pl_str *out, const void *val); // apends to `out` bool (*parse)(opt_ctx p, pl_str str, void *out_val); const struct enum_val *values; // for enums, terminated by {0} const struct preset *presets; // for preset lists, terminated by {0} const struct named * const *names; // for array-backed options, terminated by NULL // Offset and size of option in `struct pl_options_t` size_t offset; size_t size; size_t offset_params; // offset of actual struct (for params toggles) } *opt_priv; static pl_opt_data get_opt_data(opt_ctx ctx) { pl_options opts = ctx->opts; struct priv *p = PL_PRIV(opts); opt_priv priv = ctx->opt->priv; const void *val = (void *) ((uintptr_t) opts + priv->offset); p->data_text.len = 0; priv->print(ctx, &p->data_text, val); p->data = (struct pl_opt_data_t) { .opts = opts, .opt = ctx->opt, .value = val, .text = (char *) p->data_text.buf, }; return &p->data; } pl_opt_data pl_options_get(pl_options opts, const char *key) { struct priv *p = PL_PRIV(opts); pl_opt opt = pl_find_option(key); if (!opt || opt->preset) { PL_ERR(p, "Unrecognized or invalid option '%s'", key); return NULL; } return get_opt_data(&(struct opt_ctx_t) { .alloc = opts, .opts = opts, .opt = opt, }); } void pl_options_iterate(pl_options opts, void (*cb)(void *priv, pl_opt_data data), void *cb_priv) { for (pl_opt opt = pl_option_list; opt->key; opt++) { if (opt->preset) continue; struct opt_ctx_t ctx = { .alloc = opts, .opts = opts, .opt = opt, }; opt_priv priv = opt->priv; const void *val = (void *) ((uintptr_t) opts + priv->offset); const void *ref = (void *) ((uintptr_t) &defaults + priv->offset); int cmp = priv->compare ? priv->compare(&ctx, val, ref) : memcmp(val, ref, priv->size); if (cmp != 0) cb(cb_priv, get_opt_data(&ctx)); } } static void save_cb(void *priv, pl_opt_data data) { pl_opt opt = data->opt; void *alloc = data->opts; pl_str *out = priv; if (out->len) pl_str_append_raw(alloc, out, ",", 1); pl_str_append_raw(alloc, out, opt->key, strlen(opt->key)); pl_str_append_raw(alloc, out, "=", 1); pl_str_append(alloc, out, pl_str0(data->text)); } const char *pl_options_save(pl_options opts) { struct priv *p = PL_PRIV(opts); p->saved.len = 0; pl_options_iterate(opts, save_cb, &p->saved); return p->saved.len ? (char *) p->saved.buf : ""; } static bool option_set_raw(pl_options opts, pl_str k, pl_str v) { struct priv *p = PL_PRIV(opts); k = pl_str_strip(k); v = pl_str_strip(v); pl_opt opt; for (opt = pl_option_list; opt->key; opt++) { if (pl_str_equals0(k, opt->key)) goto found; } PL_ERR(p, "Unrecognized option '%.*s', in '%.*s=%.*s'", PL_STR_FMT(k), PL_STR_FMT(k), PL_STR_FMT(v)); return false; found: PL_TRACE(p, "Parsing option '%s' = '%.*s'", opt->key, PL_STR_FMT(v)); if (opt->deprecated) PL_WARN(p, "Option '%s' is deprecated", opt->key); struct opt_ctx_t ctx = { .log = p->log, .opts = opts, .opt = opt, }; opt_priv priv = opt->priv; void *val = (void *) ((uintptr_t) opts + priv->offset); return priv->parse(&ctx, v, val); } bool pl_options_set_str(pl_options opts, const char *key, const char *value) { return option_set_raw(opts, pl_str0(key), pl_str0(value)); } bool pl_options_load(pl_options opts, const char *str) { bool ret = true; pl_str rest = pl_str0(str); while (rest.len) { pl_str kv = pl_str_strip(pl_str_split_chars(rest, " ,;:\n", &rest)); if (!kv.len) continue; pl_str v, k = pl_str_split_char(kv, '=', &v); ret &= option_set_raw(opts, k, v); } return ret; } // Individual option types static void print_bool(opt_ctx p, pl_str *out, const void *ptr) { const bool *val = ptr; if (*val) { pl_str_append(p->alloc, out, pl_str0("yes")); } else { pl_str_append(p->alloc, out, pl_str0("no")); } } static bool parse_bool(opt_ctx p, pl_str str, void *out) { bool *res = out; if (pl_str_equals0(str, "yes") || pl_str_equals0(str, "y") || pl_str_equals0(str, "on") || pl_str_equals0(str, "true") || pl_str_equals0(str, "enabled") || !str.len) // accept naked option name as well { *res = true; return true; } else if (pl_str_equals0(str, "no") || pl_str_equals0(str, "n") || pl_str_equals0(str, "off") || pl_str_equals0(str, "false") || pl_str_equals0(str, "disabled")) { *res = false; return true; } PL_ERR(p, "Invalid value '%.*s' for option '%s', expected boolean", PL_STR_FMT(str), p->opt->key); return false; } static void print_int(opt_ctx p, pl_str *out, const void *ptr) { pl_opt opt = p->opt; const int *val = ptr; pl_assert(opt->min == opt->max || (*val >= opt->min && *val <= opt->max)); pl_str_append_asprintf_c(p->alloc, out, "%d", *val); } static bool parse_int(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; int val; if (!pl_str_parse_int(str, &val)) { PL_ERR(p, "Invalid value '%.*s' for option '%s', expected integer", PL_STR_FMT(str), opt->key); return false; } if (opt->min != opt->max) { if (val < opt->min || val > opt->max) { PL_ERR(p, "Value of %d out of range for option '%s': [%d, %d]", val, opt->key, (int) opt->min, (int) opt->max); return false; } } *(int *) out = val; return true; } static void print_float(opt_ctx p, pl_str *out, const void *ptr) { pl_opt opt = p->opt; const float *val = ptr; pl_assert(opt->min == opt->max || (*val >= opt->min && *val <= opt->max)); pl_str_append_asprintf_c(p->alloc, out, "%f", *val); } static bool parse_fraction(pl_str str, float *val) { pl_str denom, num = pl_str_split_char(str, '/', &denom); float n, d; bool ok = denom.buf && denom.len && pl_str_parse_float(num, &n) && pl_str_parse_float(denom, &d); if (ok) *val = n / d; return ok; } static bool parse_float(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; float val; if (!parse_fraction(str, &val) && !pl_str_parse_float(str, &val)) { PL_ERR(p, "Invalid value '%.*s' for option '%s', expected floating point " "or fraction", PL_STR_FMT(str), opt->key); return false; } switch (fpclassify(val)) { case FP_NAN: case FP_INFINITE: case FP_SUBNORMAL: PL_ERR(p, "Invalid value '%f' for option '%s', non-normal float", val, opt->key); return false; case FP_ZERO: case FP_NORMAL: break; } if (opt->min != opt->max) { if (val < opt->min || val > opt->max) { PL_ERR(p, "Value of %.3f out of range for option '%s': [%.2f, %.2f]", val, opt->key, opt->min, opt->max); return false; } } *(float *) out = val; return true; } static int compare_params(opt_ctx p, const void *pa, const void *pb) { const bool a = *(const void * const *) pa; const bool b = *(const void * const *) pb; return PL_CMP(a, b); } static void print_params(opt_ctx p, pl_str *out, const void *ptr) { const bool value = *(const void * const *) ptr; print_bool(p, out, &value); } static bool parse_params(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; opt_priv priv = opt->priv; const void **res = out; bool set; if (!parse_bool(p, str, &set)) return false; if (set) { *res = (const void *) ((uintptr_t) p->opts + priv->offset_params); } else { *res = NULL; } return true; } static void print_enum(opt_ctx p, pl_str *out, const void *ptr) { pl_opt opt = p->opt; opt_priv priv = opt->priv; const unsigned value = *(const unsigned *) ptr; for (int i = 0; priv->values[i].name; i++) { if (priv->values[i].val == value) { pl_str_append(p->alloc, out, pl_str0(priv->values[i].name)); return; } } pl_unreachable(); } static bool parse_enum(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; opt_priv priv = opt->priv; for (int i = 0; priv->values[i].name; i++) { if (pl_str_equals0(str, priv->values[i].name)) { *(unsigned *) out = priv->values[i].val; return true; } } PL_ERR(p, "Value of '%.*s' unrecognized for option '%s', valid values:", PL_STR_FMT(str), opt->key); for (int i = 0; priv->values[i].name; i++) PL_ERR(p, " %s", priv->values[i].name); return false; } static bool parse_preset(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; opt_priv priv = opt->priv; for (int i = 0; priv->presets[i].name; i++) { if (pl_str_equals0(str, priv->presets[i].name)) { if (priv->offset == offsetof(struct pl_options_t, params)) { const struct pl_render_params *preset = priv->presets[i].val; pl_assert(priv->size == sizeof(*preset)); // Redirect params structs into internal system after loading struct pl_render_params *params = out, prev = *params; *params = *preset; redirect_params(p->opts); // Re-apply excluded options params->lut = prev.lut; params->hooks = prev.hooks; params->num_hooks = prev.num_hooks; params->info_callback = prev.info_callback; params->info_priv = prev.info_priv; } else { memcpy(out, priv->presets[i].val, priv->size); } return true; } } PL_ERR(p, "Value of '%.*s' unrecognized for option '%s', valid values:", PL_STR_FMT(str), opt->key); for (int i = 0; priv->presets[i].name; i++) PL_ERR(p, " %s", priv->presets[i].name); return false; } static void print_named(opt_ctx p, pl_str *out, const void *ptr) { const struct named *value = *(const struct named **) ptr; if (value) { pl_str_append(p->alloc, out, pl_str0(value->name)); } else { pl_str_append(p->alloc, out, pl_str0("none")); } } static bool parse_named(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; opt_priv priv = opt->priv; const struct named **res = out; if (pl_str_equals0(str, "none")) { *res = NULL; return true; } for (int i = 0; priv->names[i]; i++) { if (pl_str_equals0(str, priv->names[i]->name)) { *res = priv->names[i]; return true; } } PL_ERR(p, "Value of '%.*s' unrecognized for option '%s', valid values:", PL_STR_FMT(str), opt->key); PL_ERR(p, " none"); for (int i = 0; priv->names[i]; i++) PL_ERR(p, " %s", priv->names[i]->name); return false; } static void print_scaler(opt_ctx p, pl_str *out, const void *ptr) { const struct pl_filter_config *f = *(const struct pl_filter_config **) ptr; if (f) { pl_assert(f->name); // this is either a built-in scaler or ptr to custom pl_str_append(p->alloc, out, pl_str0(f->name)); } else { pl_str_append(p->alloc, out, pl_str0("none")); } } static enum pl_filter_usage scaler_usage(pl_opt opt) { opt_priv priv = opt->priv; switch (priv->offset) { case offsetof(struct pl_options_t, params.upscaler): case offsetof(struct pl_options_t, params.plane_upscaler): case offsetof(struct pl_options_t, upscaler): case offsetof(struct pl_options_t, plane_upscaler): return PL_FILTER_UPSCALING; case offsetof(struct pl_options_t, params.downscaler): case offsetof(struct pl_options_t, params.plane_downscaler): case offsetof(struct pl_options_t, downscaler): case offsetof(struct pl_options_t, plane_downscaler): return PL_FILTER_DOWNSCALING; case offsetof(struct pl_options_t, params.frame_mixer): case offsetof(struct pl_options_t, frame_mixer): return PL_FILTER_FRAME_MIXING; } pl_unreachable(); } static bool parse_scaler(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; opt_priv priv = opt->priv; const struct pl_filter_config **res = out; if (pl_str_equals0(str, "none")) { *res = NULL; return true; } else if (pl_str_equals0(str, "custom")) { *res = (void *) ((uintptr_t) p->opts + priv->offset_params); return true; } const enum pl_filter_usage usage = scaler_usage(opt); for (int i = 0; i < pl_num_filter_configs; i++) { if (!(pl_filter_configs[i]->allowed & usage)) continue; if (pl_str_equals0(str, pl_filter_configs[i]->name)) { *res = pl_filter_configs[i]; return true; } } PL_ERR(p, "Value of '%.*s' unrecognized for option '%s', valid values:", PL_STR_FMT(str), opt->key); PL_ERR(p, " none"); PL_ERR(p, " custom"); for (int i = 0; i < pl_num_filter_configs; i++) { if (pl_filter_configs[i]->allowed & usage) PL_ERR(p, " %s", pl_filter_configs[i]->name); } return false; } static bool parse_scaler_preset(opt_ctx p, pl_str str, void *out) { pl_opt opt = p->opt; struct pl_filter_config *res = out; if (pl_str_equals0(str, "none")) { *res = (struct pl_filter_config) { .name = "custom" }; return true; } const enum pl_filter_usage usage = scaler_usage(opt); for (int i = 0; i < pl_num_filter_configs; i++) { if (!(pl_filter_configs[i]->allowed & usage)) continue; if (pl_str_equals0(str, pl_filter_configs[i]->name)) { copy_filter(res, pl_filter_configs[i]); return true; } } PL_ERR(p, "Value of '%.*s' unrecognized for option '%s', valid values:", PL_STR_FMT(str), opt->key); PL_ERR(p, " none"); for (int i = 0; i < pl_num_filter_configs; i++) { if (pl_filter_configs[i]->allowed & usage) PL_ERR(p, " %s", pl_filter_configs[i]->name); } return false; } #define OPT_BOOL(KEY, NAME, FIELD, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_BOOL, \ .priv = &(struct opt_priv_t) { \ .print = print_bool, \ .parse = parse_bool, \ .offset = offsetof(struct pl_options_t, FIELD), \ .size = sizeof(struct { \ bool dummy; \ pl_static_assert(sizeof(defaults.FIELD) == sizeof(bool)); \ }), \ }, \ __VA_ARGS__ \ } #define OPT_INT(KEY, NAME, FIELD, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_INT, \ .priv = &(struct opt_priv_t) { \ .print = print_int, \ .parse = parse_int, \ .offset = offsetof(struct pl_options_t, FIELD), \ .size = sizeof(struct { \ int dummy; \ pl_static_assert(sizeof(defaults.FIELD) == sizeof(int)); \ }), \ }, \ __VA_ARGS__ \ } #define OPT_FLOAT(KEY, NAME, FIELD, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_FLOAT, \ .priv = &(struct opt_priv_t) { \ .print = print_float, \ .parse = parse_float, \ .offset = offsetof(struct pl_options_t, FIELD), \ .size = sizeof(struct { \ float dummy; \ pl_static_assert(sizeof(defaults.FIELD) == sizeof(float)); \ }), \ }, \ __VA_ARGS__ \ } #define OPT_ENABLE_PARAMS(KEY, NAME, PARAMS, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_BOOL, \ .priv = &(struct opt_priv_t) { \ .compare = compare_params, \ .print = print_params, \ .parse = parse_params, \ .offset = offsetof(struct pl_options_t, params.PARAMS), \ .offset_params = offsetof(struct pl_options_t, PARAMS), \ .size = sizeof(struct { \ void *dummy; \ pl_static_assert(sizeof(defaults.params.PARAMS) == sizeof(void*));\ }), \ }, \ __VA_ARGS__ \ } #define OPT_ENUM(KEY, NAME, FIELD, VALUES, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_STRING, \ .priv = &(struct opt_priv_t) { \ .print = print_enum, \ .parse = parse_enum, \ .offset = offsetof(struct pl_options_t, FIELD), \ .size = sizeof(struct { \ unsigned dummy; \ pl_static_assert(sizeof(defaults.FIELD) == sizeof(unsigned)); \ }), \ .values = (struct enum_val[]) { VALUES } \ }, \ __VA_ARGS__ \ } #define OPT_PRESET(KEY, NAME, PARAMS, PRESETS, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_STRING, \ .preset = true, \ .priv = &(struct opt_priv_t) { \ .parse = parse_preset, \ .offset = offsetof(struct pl_options_t, PARAMS), \ .size = sizeof(defaults.PARAMS), \ .presets = (struct preset[]) { PRESETS }, \ }, \ __VA_ARGS__ \ } #define OPT_NAMED(KEY, NAME, FIELD, NAMES, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_STRING, \ .priv = &(struct opt_priv_t) { \ .print = print_named, \ .parse = parse_named, \ .offset = offsetof(struct pl_options_t, FIELD), \ .names = (const struct named * const * ) NAMES, \ .size = sizeof(struct { \ const struct named *dummy; \ pl_static_assert(offsetof(__typeof__(*NAMES[0]), name) == 0); \ pl_static_assert(sizeof(defaults.FIELD) == \ sizeof(const struct named *)); \ }), \ }, \ __VA_ARGS__ \ } #define OPT_SCALER(KEY, NAME, SCALER, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_STRING, \ .priv = &(struct opt_priv_t) { \ .print = print_scaler, \ .parse = parse_scaler, \ .offset = offsetof(struct pl_options_t, params.SCALER), \ .offset_params = offsetof(struct pl_options_t, SCALER), \ .size = sizeof(struct { \ const struct pl_filter_config *dummy; \ pl_static_assert(sizeof(defaults.SCALER) == \ sizeof(struct pl_filter_config)); \ }), \ }, \ __VA_ARGS__ \ } #define OPT_SCALER_PRESET(KEY, NAME, SCALER, ...) \ { \ .key = KEY, \ .name = NAME, \ .type = PL_OPT_STRING, \ .preset = true, \ .priv = &(struct opt_priv_t) { \ .parse = parse_scaler_preset, \ .offset = offsetof(struct pl_options_t, SCALER), \ .size = sizeof(struct { \ struct pl_filter_config dummy; \ pl_static_assert(sizeof(defaults.SCALER) == \ sizeof(struct pl_filter_config)); \ }), \ }, \ __VA_ARGS__ \ } #define LIST(...) __VA_ARGS__, {0} #define SCALE_OPTS(PREFIX, NAME, FIELD) \ OPT_SCALER(PREFIX, NAME, FIELD), \ OPT_SCALER_PRESET(PREFIX"_preset", NAME "preset", FIELD), \ OPT_NAMED(PREFIX"_kernel", NAME" kernel", FIELD.kernel, pl_filter_functions), \ OPT_NAMED(PREFIX"_window", NAME" window", FIELD.window, pl_filter_functions), \ OPT_FLOAT(PREFIX"_radius", NAME" radius", FIELD.radius, .min = 0.0, .max = 16.0), \ OPT_FLOAT(PREFIX"_clamp", NAME" clamping", FIELD.clamp, .max = 1.0), \ OPT_FLOAT(PREFIX"_blur", NAME" blur factor", FIELD.blur, .max = 100.0), \ OPT_FLOAT(PREFIX"_taper", NAME" taper factor", FIELD.taper, .max = 1.0), \ OPT_FLOAT(PREFIX"_antiring", NAME" antiringing", FIELD.antiring, .max = 1.0), \ OPT_FLOAT(PREFIX"_param1", NAME" parameter 1", FIELD.params[0]), \ OPT_FLOAT(PREFIX"_param2", NAME" parameter 2", FIELD.params[1]), \ OPT_FLOAT(PREFIX"_wparam1", NAME" window parameter 1", FIELD.wparams[0]), \ OPT_FLOAT(PREFIX"_wparam2", NAME" window parameter 2", FIELD.wparams[1]), \ OPT_BOOL(PREFIX"_polar", NAME" polar", FIELD.polar) const struct pl_opt_t pl_option_list[] = { OPT_PRESET("preset", "Global preset", params, LIST( {"default", &pl_render_default_params}, {"fast", &pl_render_fast_params}, {"high_quality", &pl_render_high_quality_params})), // Scalers SCALE_OPTS("upscaler", "Upscaler", upscaler), SCALE_OPTS("downscaler", "Downscaler", downscaler), SCALE_OPTS("plane_upscaler", "Plane upscaler", plane_upscaler), SCALE_OPTS("plane_downscaler", "Plane downscaler", plane_downscaler), SCALE_OPTS("frame_mixer", "Frame mixer", frame_mixer), OPT_FLOAT("antiringing_strength", "Anti-ringing strength", params.antiringing_strength, .max = 1.0), // Debanding OPT_ENABLE_PARAMS("deband", "Enable debanding", deband_params), OPT_PRESET("deband_preset", "Debanding preset", deband_params, LIST( {"default", &pl_deband_default_params})), OPT_INT("deband_iterations", "Debanding iterations", deband_params.iterations, .max = 16), OPT_FLOAT("deband_threshold", "Debanding threshold", deband_params.threshold, .max = 1000.0), OPT_FLOAT("deband_radius", "Debanding radius", deband_params.radius, .max = 1000.0), OPT_FLOAT("deband_grain", "Debanding grain", deband_params.grain, .max = 1000.0), OPT_FLOAT("deband_grain_neutral_r", "Debanding grain neutral R", deband_params.grain_neutral[0]), OPT_FLOAT("deband_grain_neutral_g", "Debanding grain neutral G", deband_params.grain_neutral[1]), OPT_FLOAT("deband_grain_neutral_b", "Debanding grain neutral B", deband_params.grain_neutral[2]), // Sigmodization OPT_ENABLE_PARAMS("sigmoid", "Enable sigmoidization", sigmoid_params), OPT_PRESET("sigmoid_preset", "Sigmoidization preset", sigmoid_params, LIST( {"default", &pl_sigmoid_default_params})), OPT_FLOAT("sigmoid_center", "Sigmoidization center", sigmoid_params.center, .max = 1.0), OPT_FLOAT("sigmoid_slope", "Sigmoidization slope", sigmoid_params.slope, .min = 1.0, .max = 20.0), // Color adjustment OPT_ENABLE_PARAMS("color_adjustment", "Enable color adjustment", color_adjustment), OPT_PRESET("color_adjustment_preset", "Color adjustment preset", color_adjustment, LIST( {"neutral", &pl_color_adjustment_neutral})), OPT_FLOAT("brightness", "Brightness boost", color_adjustment.brightness, .min = -1.0, .max = 1.0), OPT_FLOAT("contrast", "Contrast boost", color_adjustment.contrast, .max = 100.0), OPT_FLOAT("saturation", "Saturation gain", color_adjustment.saturation, .max = 100.0), OPT_FLOAT("hue", "Hue shift", color_adjustment.hue), OPT_FLOAT("gamma", "Gamma adjustment", color_adjustment.gamma, .max = 100.0), OPT_FLOAT("temperature", "Color temperature shift", color_adjustment.temperature, .min = (2500 - 6500) / 3500.0, // see `pl_white_from_temp` .max = (25000 - 6500) / 3500.0), // Peak detection OPT_ENABLE_PARAMS("peak_detect", "Enable peak detection", peak_detect_params), OPT_PRESET("peak_detect_preset", "Peak detection preset", peak_detect_params, LIST( {"default", &pl_peak_detect_default_params}, {"high_quality", &pl_peak_detect_high_quality_params})), OPT_FLOAT("peak_smoothing_period", "Peak detection smoothing coefficient", peak_detect_params.smoothing_period, .max = 1000.0), OPT_FLOAT("scene_threshold_low", "Scene change threshold low", peak_detect_params.scene_threshold_low, .max = 100.0), OPT_FLOAT("scene_threshold_high", "Scene change threshold high", peak_detect_params.scene_threshold_high, .max = 100.0), OPT_FLOAT("minimum_peak", "Minimum detected peak", peak_detect_params.minimum_peak, .max = 100.0, .deprecated = true), OPT_FLOAT("peak_percentile", "Peak detection percentile", peak_detect_params.percentile, .max = 100.0), OPT_BOOL("allow_delayed_peak", "Allow delayed peak detection", peak_detect_params.allow_delayed), // Color mapping OPT_ENABLE_PARAMS("color_map", "Enable color mapping", color_map_params), OPT_PRESET("color_map_preset", "Color mapping preset", color_map_params, LIST( {"default", &pl_color_map_default_params}, {"high_quality", &pl_color_map_high_quality_params})), OPT_NAMED("gamut_mapping", "Gamut mapping function", color_map_params.gamut_mapping, pl_gamut_map_functions), OPT_FLOAT("perceptual_deadzone", "Gamut mapping perceptual deadzone", color_map_params.gamut_constants.perceptual_deadzone, .max = 1.0f), OPT_FLOAT("perceptual_strength", "Gamut mapping perceptual strength", color_map_params.gamut_constants.perceptual_strength, .max = 1.0f), OPT_FLOAT("colorimetric_gamma", "Gamut mapping colorimetric gamma", color_map_params.gamut_constants.colorimetric_gamma, .max = 10.0f), OPT_FLOAT("softclip_knee", "Gamut mapping softclip knee point", color_map_params.gamut_constants.softclip_knee, .max = 1.0f), OPT_FLOAT("softclip_desat", "Gamut mapping softclip desaturation strength", color_map_params.gamut_constants.softclip_desat, .max = 1.0f), OPT_INT("lut3d_size_I", "Gamut 3DLUT size I", color_map_params.lut3d_size[0], .max = 1024), OPT_INT("lut3d_size_C", "Gamut 3DLUT size C", color_map_params.lut3d_size[1], .max = 1024), OPT_INT("lut3d_size_h", "Gamut 3DLUT size h", color_map_params.lut3d_size[2], .max = 1024), OPT_BOOL("lut3d_tricubic", "Gamut 3DLUT tricubic interpolation", color_map_params.lut3d_tricubic), OPT_BOOL("gamut_expansion", "Gamut expansion", color_map_params.gamut_expansion), OPT_NAMED("tone_mapping", "Tone mapping function", color_map_params.tone_mapping_function, pl_tone_map_functions), OPT_FLOAT("knee_adaptation", "Tone mapping knee point adaptation", color_map_params.tone_constants.knee_adaptation, .max = 1.0f), OPT_FLOAT("knee_minimum", "Tone mapping knee point minimum", color_map_params.tone_constants.knee_minimum, .max = 0.5f), OPT_FLOAT("knee_maximum", "Tone mapping knee point maximum", color_map_params.tone_constants.knee_maximum, .min = 0.5f, .max = 1.0f), OPT_FLOAT("knee_default", "Tone mapping knee point default", color_map_params.tone_constants.knee_default, .max = 1.0f), OPT_FLOAT("knee_offset", "BT.2390 knee point offset", color_map_params.tone_constants.knee_offset, .min = 0.5f, .max = 2.0f), OPT_FLOAT("slope_tuning", "Spline slope tuning strength", color_map_params.tone_constants.slope_tuning, .max = 10.0f), OPT_FLOAT("slope_offset", "Spline slope tuning offset", color_map_params.tone_constants.slope_offset, .max = 1.0f), OPT_FLOAT("spline_contrast", "Spline slope contrast", color_map_params.tone_constants.spline_contrast, .max = 1.5f), OPT_FLOAT("reinhard_contrast", "Reinhard contrast", color_map_params.tone_constants.reinhard_contrast, .max = 1.0f), OPT_FLOAT("linear_knee", "Tone mapping linear knee point", color_map_params.tone_constants.linear_knee, .max = 1.0f), OPT_FLOAT("exposure", "Tone mapping linear exposure", color_map_params.tone_constants.exposure, .max = 10.0f), OPT_BOOL("inverse_tone_mapping", "Inverse tone mapping", color_map_params.inverse_tone_mapping), OPT_ENUM("tone_map_metadata", "Source of HDR metadata to use", color_map_params.metadata, LIST( {"any", PL_HDR_METADATA_ANY}, {"none", PL_HDR_METADATA_NONE}, {"hdr10", PL_HDR_METADATA_HDR10}, {"hdr10plus", PL_HDR_METADATA_HDR10PLUS}, {"cie_y", PL_HDR_METADATA_CIE_Y})), OPT_INT("tone_lut_size", "Tone mapping LUT size", color_map_params.lut_size, .max = 4096), OPT_FLOAT("contrast_recovery", "HDR contrast recovery strength", color_map_params.contrast_recovery, .max = 2.0), OPT_FLOAT("contrast_smoothness", "HDR contrast recovery smoothness", color_map_params.contrast_smoothness, .min = 1.0, .max = 32.0), OPT_BOOL("force_tone_mapping_lut", "Force tone mapping LUT", color_map_params.force_tone_mapping_lut), OPT_BOOL("visualize_lut", "Visualize tone mapping LUTs", color_map_params.visualize_lut), OPT_FLOAT("visualize_lut_x0", "Visualization rect x0", color_map_params.visualize_rect.x0), OPT_FLOAT("visualize_lut_y0", "Visualization rect y0", color_map_params.visualize_rect.y0), OPT_FLOAT("visualize_lut_x1", "Visualization rect x0", color_map_params.visualize_rect.x1), OPT_FLOAT("visualize_lut_y1", "Visualization rect y0", color_map_params.visualize_rect.y1), OPT_FLOAT("visualize_hue", "Visualization hue slice", color_map_params.visualize_hue), OPT_FLOAT("visualize_theta", "Visualization rotation", color_map_params.visualize_theta), OPT_BOOL("show_clipping", "Highlight clipped pixels", color_map_params.show_clipping), OPT_FLOAT("tone_mapping_param", "Tone mapping function parameter", color_map_params.tone_mapping_param, .deprecated = true), // Dithering OPT_ENABLE_PARAMS("dither", "Enable dithering", dither_params), OPT_PRESET("dither_preset", "Dithering preset", dither_params, LIST( {"default", &pl_dither_default_params})), OPT_ENUM("dither_method", "Dither method", dither_params.method, LIST( {"blue", PL_DITHER_BLUE_NOISE}, {"ordered_lut", PL_DITHER_ORDERED_LUT}, {"ordered", PL_DITHER_ORDERED_FIXED}, {"white", PL_DITHER_WHITE_NOISE})), OPT_INT("dither_lut_size", "Dither LUT size", dither_params.lut_size, .min = 1, .max = 8), OPT_BOOL("dither_temporal", "Temporal dithering", dither_params.temporal), // ICC OPT_ENABLE_PARAMS("icc", "Enable ICC settings", icc_params, .deprecated = true), OPT_PRESET("icc_preset", "ICC preset", icc_params, LIST( {"default", &pl_icc_default_params}), .deprecated = true), OPT_ENUM("icc_intent", "ICC rendering intent", icc_params.intent, LIST( {"auto", PL_INTENT_AUTO}, {"perceptual", PL_INTENT_PERCEPTUAL}, {"relative", PL_INTENT_RELATIVE_COLORIMETRIC}, {"saturation", PL_INTENT_SATURATION}, {"absolute", PL_INTENT_ABSOLUTE_COLORIMETRIC}), .deprecated = true), OPT_INT("icc_size_r", "ICC 3DLUT size R", icc_params.size_r, .max = 256, .deprecated = true), OPT_INT("icc_size_g", "ICC 3DLUT size G", icc_params.size_g, .max = 256, .deprecated = true), OPT_INT("icc_size_b", "ICC 3DLUT size G", icc_params.size_b, .max = 256, .deprecated = true), OPT_FLOAT("icc_max_luma", "ICC profile luma override", icc_params.max_luma, .max = 10000, .deprecated = true), OPT_BOOL("icc_force_bpc", "Force ICC black point compensation", icc_params.force_bpc, .deprecated = true), // Cone distortion OPT_ENABLE_PARAMS("cone", "Enable cone distortion", cone_params), OPT_PRESET("cone_preset", "Cone distortion preset", cone_params, LIST( {"normal", &pl_vision_normal}, {"protanomaly", &pl_vision_protanomaly}, {"protanopia", &pl_vision_protanopia}, {"deuteranomaly", &pl_vision_deuteranomaly}, {"deuteranopia", &pl_vision_deuteranopia}, {"tritanomaly", &pl_vision_tritanomaly}, {"tritanopia", &pl_vision_tritanopia}, {"monochromacy", &pl_vision_monochromacy}, {"achromatopsia", &pl_vision_achromatopsia})), OPT_ENUM("cones", "Cone selection", cone_params.cones, LIST( {"none", PL_CONE_NONE}, {"l", PL_CONE_L}, {"m", PL_CONE_M}, {"s", PL_CONE_S}, {"lm", PL_CONE_LM}, {"ms", PL_CONE_MS}, {"ls", PL_CONE_LS}, {"lms", PL_CONE_LMS})), OPT_FLOAT("cone_strength", "Cone distortion gain", cone_params.strength), // Blending #define BLEND_VALUES LIST( \ {"zero", PL_BLEND_ZERO}, \ {"one", PL_BLEND_ONE}, \ {"alpha", PL_BLEND_SRC_ALPHA}, \ {"one_minus_alpha", PL_BLEND_ONE_MINUS_SRC_ALPHA}) OPT_ENABLE_PARAMS("blend", "Enable output blending", blend_params), OPT_PRESET("blend_preset", "Output blending preset", blend_params, LIST( {"alpha_overlay", &pl_alpha_overlay})), OPT_ENUM("blend_src_rgb", "Source RGB blend mode", blend_params.src_rgb, BLEND_VALUES), OPT_ENUM("blend_src_alpha", "Source alpha blend mode", blend_params.src_alpha, BLEND_VALUES), OPT_ENUM("blend_dst_rgb", "Target RGB blend mode", blend_params.dst_rgb, BLEND_VALUES), OPT_ENUM("blend_dst_alpha", "Target alpha blend mode", blend_params.dst_alpha, BLEND_VALUES), // Deinterlacing OPT_ENABLE_PARAMS("deinterlace", "Enable deinterlacing", deinterlace_params), OPT_PRESET("deinterlace_preset", "Deinterlacing preset", deinterlace_params, LIST( {"default", &pl_deinterlace_default_params})), OPT_ENUM("deinterlace_algo", "Deinterlacing algorithm", deinterlace_params.algo, LIST( {"weave", PL_DEINTERLACE_WEAVE}, {"bob", PL_DEINTERLACE_BOB}, {"yadif", PL_DEINTERLACE_YADIF})), OPT_BOOL("deinterlace_skip_spatial", "Skip spatial interlacing check", deinterlace_params.skip_spatial_check), // Distortion OPT_ENABLE_PARAMS("distort", "Enable distortion", distort_params), OPT_PRESET("distort_preset", "Distortion preset", distort_params, LIST( {"default", &pl_distort_default_params})), OPT_FLOAT("distort_scale_x", "Distortion X scale", distort_params.transform.mat.m[0][0]), OPT_FLOAT("distort_scale_y", "Distortion Y scale", distort_params.transform.mat.m[1][1]), OPT_FLOAT("distort_shear_x", "Distortion X shear", distort_params.transform.mat.m[0][1]), OPT_FLOAT("distort_shear_y", "Distortion Y shear", distort_params.transform.mat.m[1][0]), OPT_FLOAT("distort_offset_x", "Distortion X offset", distort_params.transform.c[0]), OPT_FLOAT("distort_offset_y", "Distortion Y offset", distort_params.transform.c[1]), OPT_BOOL("distort_unscaled", "Distortion unscaled", distort_params.unscaled), OPT_BOOL("distort_constrain", "Constrain distortion", distort_params.constrain), OPT_BOOL("distort_bicubic", "Distortion bicubic interpolation", distort_params.bicubic), OPT_ENUM("distort_address_mode", "Distortion texture address mode", distort_params.address_mode, LIST( {"clamp", PL_TEX_ADDRESS_CLAMP}, {"repeat", PL_TEX_ADDRESS_REPEAT}, {"mirror", PL_TEX_ADDRESS_MIRROR})), OPT_ENUM("distort_alpha_mode", "Distortion alpha blending mode", distort_params.alpha_mode, LIST( {"none", PL_ALPHA_UNKNOWN}, {"independent", PL_ALPHA_INDEPENDENT}, {"premultiplied", PL_ALPHA_PREMULTIPLIED})), // Misc renderer settings OPT_NAMED("error_diffusion", "Error diffusion kernel", params.error_diffusion, pl_error_diffusion_kernels), OPT_ENUM("lut_type", "Color mapping LUT type", params.lut_type, LIST( {"unknown", PL_LUT_UNKNOWN}, {"native", PL_LUT_NATIVE}, {"normalized", PL_LUT_NORMALIZED}, {"conversion", PL_LUT_CONVERSION})), OPT_FLOAT("background_r", "Background color R", params.background_color[0], .max = 1.0), OPT_FLOAT("background_g", "Background color G", params.background_color[1], .max = 1.0), OPT_FLOAT("background_b", "Background color B", params.background_color[2], .max = 1.0), OPT_FLOAT("background_transparency", "Background color transparency", params.background_transparency, .max = 1), OPT_BOOL("skip_target_clearing", "Skip target clearing", params.skip_target_clearing), OPT_FLOAT("corner_rounding", "Corner rounding", params.corner_rounding, .max = 1.0), OPT_BOOL("blend_against_tiles", "Blend against tiles", params.blend_against_tiles), OPT_FLOAT("tile_color_hi_r", "Bright tile R", params.tile_colors[0][0], .max = 1.0), OPT_FLOAT("tile_color_hi_g", "Bright tile G", params.tile_colors[0][1], .max = 1.0), OPT_FLOAT("tile_color_hi_b", "Bright tile B", params.tile_colors[0][2], .max = 1.0), OPT_FLOAT("tile_color_lo_r", "Dark tile R", params.tile_colors[1][0], .max = 1.0), OPT_FLOAT("tile_color_lo_g", "Dark tile G", params.tile_colors[1][1], .max = 1.0), OPT_FLOAT("tile_color_lo_b", "Dark tile B", params.tile_colors[1][2], .max = 1.0), OPT_INT("tile_size", "Tile size", params.tile_size, .min = 2, .max = 256), // Performance / quality trade-offs and debugging options OPT_BOOL("skip_anti_aliasing", "Skip anti-aliasing", params.skip_anti_aliasing), OPT_INT("lut_entries", "Scaler LUT entries", params.lut_entries, .max = 256, .deprecated = true), OPT_FLOAT("polar_cutoff", "Polar LUT cutoff", params.polar_cutoff, .max = 1.0, .deprecated = true), OPT_BOOL("preserve_mixing_cache", "Preserve mixing cache", params.preserve_mixing_cache), OPT_BOOL("skip_caching_single_frame", "Skip caching single frame", params.skip_caching_single_frame), OPT_BOOL("disable_linear_scaling", "Disable linear scaling", params.disable_linear_scaling), OPT_BOOL("disable_builtin_scalers", "Disable built-in scalers", params.disable_builtin_scalers), OPT_BOOL("correct_subpixel_offset", "Correct subpixel offsets", params.correct_subpixel_offsets), OPT_BOOL("ignore_icc_profiles", "Ignore ICC profiles", params.ignore_icc_profiles, .deprecated = true), OPT_BOOL("force_dither", "Force-enable dithering", params.force_dither), OPT_BOOL("disable_dither_gamma_correction", "Disable gamma-correct dithering", params.disable_dither_gamma_correction), OPT_BOOL("disable_fbos", "Disable FBOs", params.disable_fbos), OPT_BOOL("force_low_bit_depth_fbos", "Force 8-bit FBOs", params.force_low_bit_depth_fbos), OPT_BOOL("dynamic_constants", "Dynamic constants", params.dynamic_constants), {0}, }; const int pl_option_count = PL_ARRAY_SIZE(pl_option_list) - 1; pl_opt pl_find_option(const char *key) { for (int i = 0; i < pl_option_count; i++) { if (!strcmp(key, pl_option_list[i].key)) return &pl_option_list[i]; } return NULL; }