summaryrefslogtreecommitdiffstats
path: root/src/options.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/options.c1166
1 files changed, 1166 insertions, 0 deletions
diff --git a/src/options.c b/src/options.c
new file mode 100644
index 0000000..1db53bf
--- /dev/null
+++ b/src/options.c
@@ -0,0 +1,1166 @@
+/*
+ * 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 <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+
+#include "common.h"
+#include "log.h"
+
+#include <libplacebo/options.h>
+
+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;
+}