1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-11-26 19:01:44 +02:00
FFmpeg/libavfilter/vf_libplacebo.c
Niklas Haas eabc304d12 avfilter/vf_libplacebo: add SMPTE ST2094 tone-mappers
libplacebo gained these exciting new functions upstream.
2023-02-17 18:35:39 +01:00

888 lines
40 KiB
C

/*
* This file is part of FFmpeg.
*
* FFmpeg 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.
*
* FFmpeg 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 FFmpeg; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "libavutil/file.h"
#include "libavutil/opt.h"
#include "internal.h"
#include "vulkan_filter.h"
#include "scale_eval.h"
#include <libplacebo/renderer.h>
#include <libplacebo/utils/libav.h>
#include <libplacebo/vulkan.h>
enum {
TONE_MAP_AUTO,
TONE_MAP_CLIP,
TONE_MAP_ST2094_40,
TONE_MAP_ST2094_10,
TONE_MAP_BT2390,
TONE_MAP_BT2446A,
TONE_MAP_SPLINE,
TONE_MAP_REINHARD,
TONE_MAP_MOBIUS,
TONE_MAP_HABLE,
TONE_MAP_GAMMA,
TONE_MAP_LINEAR,
TONE_MAP_COUNT,
};
static const struct pl_tone_map_function * const tonemapping_funcs[TONE_MAP_COUNT] = {
[TONE_MAP_AUTO] = &pl_tone_map_auto,
[TONE_MAP_CLIP] = &pl_tone_map_clip,
#if PL_API_VER >= 246
[TONE_MAP_ST2094_40] = &pl_tone_map_st2094_40,
[TONE_MAP_ST2094_10] = &pl_tone_map_st2094_10,
#endif
[TONE_MAP_BT2390] = &pl_tone_map_bt2390,
[TONE_MAP_BT2446A] = &pl_tone_map_bt2446a,
[TONE_MAP_SPLINE] = &pl_tone_map_spline,
[TONE_MAP_REINHARD] = &pl_tone_map_reinhard,
[TONE_MAP_MOBIUS] = &pl_tone_map_mobius,
[TONE_MAP_HABLE] = &pl_tone_map_hable,
[TONE_MAP_GAMMA] = &pl_tone_map_gamma,
[TONE_MAP_LINEAR] = &pl_tone_map_linear,
};
typedef struct LibplaceboContext {
/* lavfi vulkan*/
FFVulkanContext vkctx;
/* libplacebo */
pl_log log;
pl_vulkan vulkan;
pl_gpu gpu;
pl_renderer renderer;
pl_tex tex[8];
/* settings */
char *out_format_string;
enum AVPixelFormat out_format;
char *w_expr;
char *h_expr;
AVRational target_sar;
float pad_crop_ratio;
int force_original_aspect_ratio;
int force_divisible_by;
int normalize_sar;
int apply_filmgrain;
int apply_dovi;
int colorspace;
int color_range;
int color_primaries;
int color_trc;
/* pl_render_params */
char *upscaler;
char *downscaler;
int lut_entries;
float antiringing;
int sigmoid;
int skip_aa;
float polar_cutoff;
int disable_linear;
int disable_builtin;
int force_icc_lut;
int force_dither;
int disable_fbos;
/* pl_deband_params */
int deband;
int deband_iterations;
float deband_threshold;
float deband_radius;
float deband_grain;
/* pl_color_adjustment */
float brightness;
float contrast;
float saturation;
float hue;
float gamma;
/* pl_peak_detect_params */
int peakdetect;
float smoothing;
float min_peak;
float scene_low;
float scene_high;
float overshoot;
/* pl_color_map_params */
int intent;
int gamut_mode;
int tonemapping;
float tonemapping_param;
int tonemapping_mode;
int inverse_tonemapping;
float crosstalk;
int tonemapping_lut_size;
/* for backwards compatibility */
float desat_str;
float desat_exp;
int gamut_warning;
int gamut_clipping;
/* pl_dither_params */
int dithering;
int dither_lut_size;
int dither_temporal;
/* pl_cone_params */
int cones;
float cone_str;
/* custom shaders */
char *shader_path;
void *shader_bin;
int shader_bin_len;
const struct pl_hook *hooks[2];
int num_hooks;
} LibplaceboContext;
static inline enum pl_log_level get_log_level(void)
{
int av_lev = av_log_get_level();
return av_lev >= AV_LOG_TRACE ? PL_LOG_TRACE :
av_lev >= AV_LOG_DEBUG ? PL_LOG_DEBUG :
av_lev >= AV_LOG_VERBOSE ? PL_LOG_INFO :
av_lev >= AV_LOG_WARNING ? PL_LOG_WARN :
av_lev >= AV_LOG_ERROR ? PL_LOG_ERR :
av_lev >= AV_LOG_FATAL ? PL_LOG_FATAL :
PL_LOG_NONE;
}
static void pl_av_log(void *log_ctx, enum pl_log_level level, const char *msg)
{
int av_lev;
switch (level) {
case PL_LOG_FATAL: av_lev = AV_LOG_FATAL; break;
case PL_LOG_ERR: av_lev = AV_LOG_ERROR; break;
case PL_LOG_WARN: av_lev = AV_LOG_WARNING; break;
case PL_LOG_INFO: av_lev = AV_LOG_VERBOSE; break;
case PL_LOG_DEBUG: av_lev = AV_LOG_DEBUG; break;
case PL_LOG_TRACE: av_lev = AV_LOG_TRACE; break;
default: return;
}
av_log(log_ctx, av_lev, "%s\n", msg);
}
static int parse_shader(AVFilterContext *avctx, const void *shader, size_t len)
{
LibplaceboContext *s = avctx->priv;
const struct pl_hook *hook;
hook = pl_mpv_user_shader_parse(s->gpu, shader, len);
if (!hook) {
av_log(s, AV_LOG_ERROR, "Failed parsing custom shader!\n");
return AVERROR(EINVAL);
}
s->hooks[s->num_hooks++] = hook;
return 0;
}
static int find_scaler(AVFilterContext *avctx,
const struct pl_filter_config **opt,
const char *name)
{
const struct pl_filter_preset *preset;
if (!strcmp(name, "help")) {
av_log(avctx, AV_LOG_INFO, "Available scaler presets:\n");
for (preset = pl_scale_filters; preset->name; preset++)
av_log(avctx, AV_LOG_INFO, " %s\n", preset->name);
return AVERROR_EXIT;
}
for (preset = pl_scale_filters; preset->name; preset++) {
if (!strcmp(name, preset->name)) {
*opt = preset->filter;
return 0;
}
}
av_log(avctx, AV_LOG_ERROR, "No such scaler preset '%s'.\n", name);
return AVERROR(EINVAL);
}
static void libplacebo_uninit(AVFilterContext *avctx);
static int libplacebo_init(AVFilterContext *avctx)
{
LibplaceboContext *s = avctx->priv;
/* Create libplacebo log context */
s->log = pl_log_create(PL_API_VER, pl_log_params(
.log_level = get_log_level(),
.log_cb = pl_av_log,
.log_priv = s,
));
if (!s->log)
return AVERROR(ENOMEM);
if (s->out_format_string) {
s->out_format = av_get_pix_fmt(s->out_format_string);
if (s->out_format == AV_PIX_FMT_NONE) {
av_log(avctx, AV_LOG_ERROR, "Invalid output format: %s\n",
s->out_format_string);
libplacebo_uninit(avctx);
return AVERROR(EINVAL);
}
} else {
s->out_format = AV_PIX_FMT_NONE;
}
/* Note: s->vulkan etc. are initialized later, when hwctx is available */
return 0;
}
static int init_vulkan(AVFilterContext *avctx)
{
int err = 0;
LibplaceboContext *s = avctx->priv;
const AVHWDeviceContext *avhwctx;
const AVVulkanDeviceContext *hwctx;
uint8_t *buf = NULL;
size_t buf_len;
if (!avctx->hw_device_ctx) {
av_log(s, AV_LOG_ERROR, "Missing vulkan hwdevice for vf_libplacebo.\n");
return AVERROR(EINVAL);
}
avhwctx = (AVHWDeviceContext *) avctx->hw_device_ctx->data;
if (avhwctx->type != AV_HWDEVICE_TYPE_VULKAN) {
av_log(s, AV_LOG_ERROR, "Expected vulkan hwdevice for vf_libplacebo, got %s.\n",
av_hwdevice_get_type_name(avhwctx->type));
return AVERROR(EINVAL);
}
hwctx = avhwctx->hwctx;
/* Import libavfilter vulkan context into libplacebo */
s->vulkan = pl_vulkan_import(s->log, pl_vulkan_import_params(
.instance = hwctx->inst,
.get_proc_addr = hwctx->get_proc_addr,
.phys_device = hwctx->phys_dev,
.device = hwctx->act_dev,
.extensions = hwctx->enabled_dev_extensions,
.num_extensions = hwctx->nb_enabled_dev_extensions,
.features = &hwctx->device_features,
.queue_graphics = {
.index = hwctx->queue_family_index,
.count = hwctx->nb_graphics_queues,
},
.queue_compute = {
.index = hwctx->queue_family_comp_index,
.count = hwctx->nb_comp_queues,
},
.queue_transfer = {
.index = hwctx->queue_family_tx_index,
.count = hwctx->nb_tx_queues,
},
/* This is the highest version created by hwcontext_vulkan.c */
.max_api_version = VK_API_VERSION_1_2,
));
if (!s->vulkan) {
av_log(s, AV_LOG_ERROR, "Failed importing vulkan device to libplacebo!\n");
err = AVERROR_EXTERNAL;
goto fail;
}
/* Create the renderer */
s->gpu = s->vulkan->gpu;
s->renderer = pl_renderer_create(s->log, s->gpu);
/* Parse the user shaders, if requested */
if (s->shader_bin_len)
RET(parse_shader(avctx, s->shader_bin, s->shader_bin_len));
if (s->shader_path && s->shader_path[0]) {
RET(av_file_map(s->shader_path, &buf, &buf_len, 0, s));
RET(parse_shader(avctx, buf, buf_len));
}
/* fall through */
fail:
if (buf)
av_file_unmap(buf, buf_len);
return err;
}
static void libplacebo_uninit(AVFilterContext *avctx)
{
LibplaceboContext *s = avctx->priv;
for (int i = 0; i < FF_ARRAY_ELEMS(s->tex); i++)
pl_tex_destroy(s->gpu, &s->tex[i]);
for (int i = 0; i < s->num_hooks; i++)
pl_mpv_user_shader_destroy(&s->hooks[i]);
pl_renderer_destroy(&s->renderer);
pl_vulkan_destroy(&s->vulkan);
pl_log_destroy(&s->log);
ff_vk_uninit(&s->vkctx);
s->gpu = NULL;
}
static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in)
{
int err = 0, ok;
LibplaceboContext *s = avctx->priv;
struct pl_render_params params;
enum pl_tone_map_mode tonemapping_mode = s->tonemapping_mode;
const AVPixFmtDescriptor *outdesc = av_pix_fmt_desc_get(out->format);
enum pl_gamut_mode gamut_mode = s->gamut_mode;
struct pl_frame image, target;
ok = pl_map_avframe_ex(s->gpu, &image, pl_avframe_params(
.frame = in,
.tex = s->tex,
.map_dovi = s->apply_dovi,
));
if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
ok &= pl_map_avframe_ex(s->gpu, &target, pl_avframe_params(
.frame = out,
.map_dovi = false,
));
} else {
ok &= pl_frame_recreate_from_avframe(s->gpu, &target, s->tex + 4, out);
}
if (!ok) {
err = AVERROR_EXTERNAL;
goto fail;
}
if (!s->apply_filmgrain)
image.film_grain.type = PL_FILM_GRAIN_NONE;
if (s->target_sar.num) {
float aspect = pl_rect2df_aspect(&target.crop) * av_q2d(s->target_sar);
pl_rect2df_aspect_set(&target.crop, aspect, s->pad_crop_ratio);
}
/* backwards compatibility with older API */
if (!tonemapping_mode && (s->desat_str >= 0.0f || s->desat_exp >= 0.0f)) {
float str = s->desat_str < 0.0f ? 0.9f : s->desat_str;
float exp = s->desat_exp < 0.0f ? 0.2f : s->desat_exp;
if (str >= 0.9f && exp <= 0.1f) {
tonemapping_mode = PL_TONE_MAP_RGB;
} else if (str > 0.1f) {
tonemapping_mode = PL_TONE_MAP_HYBRID;
} else {
tonemapping_mode = PL_TONE_MAP_LUMA;
}
}
if (s->gamut_warning)
gamut_mode = PL_GAMUT_WARN;
if (s->gamut_clipping)
gamut_mode = PL_GAMUT_DESATURATE;
/* Update render params */
params = (struct pl_render_params) {
PL_RENDER_DEFAULTS
.lut_entries = s->lut_entries,
.antiringing_strength = s->antiringing,
.deband_params = !s->deband ? NULL : pl_deband_params(
.iterations = s->deband_iterations,
.threshold = s->deband_threshold,
.radius = s->deband_radius,
.grain = s->deband_grain,
),
.sigmoid_params = s->sigmoid ? &pl_sigmoid_default_params : NULL,
.color_adjustment = &(struct pl_color_adjustment) {
.brightness = s->brightness,
.contrast = s->contrast,
.saturation = s->saturation,
.hue = s->hue,
.gamma = s->gamma,
},
.peak_detect_params = !s->peakdetect ? NULL : pl_peak_detect_params(
.smoothing_period = s->smoothing,
.minimum_peak = s->min_peak,
.scene_threshold_low = s->scene_low,
.scene_threshold_high = s->scene_high,
.overshoot_margin = s->overshoot,
),
.color_map_params = pl_color_map_params(
.intent = s->intent,
.gamut_mode = gamut_mode,
.tone_mapping_function = tonemapping_funcs[s->tonemapping],
.tone_mapping_param = s->tonemapping_param,
.tone_mapping_mode = tonemapping_mode,
.inverse_tone_mapping = s->inverse_tonemapping,
.tone_mapping_crosstalk = s->crosstalk,
.lut_size = s->tonemapping_lut_size,
),
.dither_params = s->dithering < 0 ? NULL : pl_dither_params(
.method = s->dithering,
.lut_size = s->dither_lut_size,
.temporal = s->dither_temporal,
),
.cone_params = !s->cones ? NULL : pl_cone_params(
.cones = s->cones,
.strength = s->cone_str,
),
.hooks = s->hooks,
.num_hooks = s->num_hooks,
.skip_anti_aliasing = s->skip_aa,
.polar_cutoff = s->polar_cutoff,
.disable_linear_scaling = s->disable_linear,
.disable_builtin_scalers = s->disable_builtin,
.force_icc_lut = s->force_icc_lut,
.force_dither = s->force_dither,
.disable_fbos = s->disable_fbos,
};
RET(find_scaler(avctx, &params.upscaler, s->upscaler));
RET(find_scaler(avctx, &params.downscaler, s->downscaler));
pl_render_image(s->renderer, &image, &target, &params);
pl_unmap_avframe(s->gpu, &image);
if (outdesc->flags & AV_PIX_FMT_FLAG_HWACCEL) {
pl_unmap_avframe(s->gpu, &target);
} else if (!pl_download_avframe(s->gpu, &target, out)) {
err = AVERROR_EXTERNAL;
goto fail;
}
/* Flush the command queues for performance */
pl_gpu_flush(s->gpu);
return 0;
fail:
pl_unmap_avframe(s->gpu, &image);
pl_unmap_avframe(s->gpu, &target);
return err;
}
static int filter_frame(AVFilterLink *link, AVFrame *in)
{
int err, changed_csp;
AVFilterContext *ctx = link->dst;
LibplaceboContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
err = AVERROR(ENOMEM);
goto fail;
}
pl_log_level_update(s->log, get_log_level());
RET(av_frame_copy_props(out, in));
out->width = outlink->w;
out->height = outlink->h;
if (s->apply_dovi && av_frame_get_side_data(in, AV_FRAME_DATA_DOVI_METADATA)) {
/* Output of dovi reshaping is always BT.2020+PQ, so infer the correct
* output colorspace defaults */
out->colorspace = AVCOL_SPC_BT2020_NCL;
out->color_primaries = AVCOL_PRI_BT2020;
out->color_trc = AVCOL_TRC_SMPTE2084;
}
if (s->colorspace >= 0)
out->colorspace = s->colorspace;
if (s->color_range >= 0)
out->color_range = s->color_range;
if (s->color_trc >= 0)
out->color_trc = s->color_trc;
if (s->color_primaries >= 0)
out->color_primaries = s->color_primaries;
changed_csp = in->colorspace != out->colorspace ||
in->color_range != out->color_range ||
in->color_trc != out->color_trc ||
in->color_primaries != out->color_primaries;
/* Strip side data if no longer relevant */
if (changed_csp) {
av_frame_remove_side_data(out, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
av_frame_remove_side_data(out, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
}
if (s->apply_dovi || changed_csp) {
av_frame_remove_side_data(out, AV_FRAME_DATA_DOVI_RPU_BUFFER);
av_frame_remove_side_data(out, AV_FRAME_DATA_DOVI_METADATA);
}
if (s->apply_filmgrain)
av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
RET(process_frames(ctx, out, in));
av_frame_free(&in);
return ff_filter_frame(outlink, out);
fail:
av_frame_free(&in);
av_frame_free(&out);
return err;
}
static int libplacebo_query_format(AVFilterContext *ctx)
{
int err;
LibplaceboContext *s = ctx->priv;
const AVPixFmtDescriptor *desc = NULL;
AVFilterFormats *infmts = NULL, *outfmts = NULL;
RET(init_vulkan(ctx));
while ((desc = av_pix_fmt_desc_next(desc))) {
enum AVPixelFormat pixfmt = av_pix_fmt_desc_get_id(desc);
#if PL_API_VER < 232
// Older libplacebo can't handle >64-bit pixel formats, so safe-guard
// this to prevent triggering an assertion
if (av_get_bits_per_pixel(desc) > 64)
continue;
#endif
if (!pl_test_pixfmt(s->gpu, pixfmt))
continue;
RET(ff_add_format(&infmts, pixfmt));
/* Filter for supported output pixel formats */
if (desc->flags & AV_PIX_FMT_FLAG_BE)
continue; /* BE formats are not supported by pl_download_avframe */
/* Mask based on user specified format */
if (s->out_format != AV_PIX_FMT_NONE) {
if (pixfmt == AV_PIX_FMT_VULKAN && av_vkfmt_from_pixfmt(s->out_format)) {
/* OK */
} else if (pixfmt == s->out_format) {
/* OK */
} else {
continue; /* Not OK */
}
}
RET(ff_add_format(&outfmts, pixfmt));
}
if (!infmts || !outfmts) {
if (s->out_format) {
av_log(s, AV_LOG_ERROR, "Invalid output format '%s'!\n",
av_get_pix_fmt_name(s->out_format));
}
err = AVERROR(EINVAL);
goto fail;
}
RET(ff_formats_ref(infmts, &ctx->inputs[0]->outcfg.formats));
RET(ff_formats_ref(outfmts, &ctx->outputs[0]->incfg.formats));
return 0;
fail:
if (infmts && !infmts->refcount)
ff_formats_unref(&infmts);
if (outfmts && !outfmts->refcount)
ff_formats_unref(&outfmts);
return err;
}
static int libplacebo_config_input(AVFilterLink *inlink)
{
AVFilterContext *avctx = inlink->dst;
LibplaceboContext *s = avctx->priv;
if (inlink->format == AV_PIX_FMT_VULKAN)
return ff_vk_filter_config_input(inlink);
/* Forward this to the vkctx for format selection */
s->vkctx.input_format = inlink->format;
return 0;
}
static int libplacebo_config_output(AVFilterLink *outlink)
{
int err;
AVFilterContext *avctx = outlink->src;
LibplaceboContext *s = avctx->priv;
AVFilterLink *inlink = outlink->src->inputs[0];
AVHWFramesContext *hwfc;
AVVulkanFramesContext *vkfc;
AVRational scale_sar;
RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink,
&outlink->w, &outlink->h));
ff_scale_adjust_dimensions(inlink, &outlink->w, &outlink->h,
s->force_original_aspect_ratio,
s->force_divisible_by);
scale_sar = (AVRational){outlink->h * inlink->w, outlink->w * inlink->h};
if (inlink->sample_aspect_ratio.num)
scale_sar = av_mul_q(scale_sar, inlink->sample_aspect_ratio);
if (s->normalize_sar) {
/* Apply all SAR during scaling, so we don't need to set the out SAR */
outlink->sample_aspect_ratio = (AVRational){ 1, 1 };
s->target_sar = scale_sar;
} else {
/* This is consistent with other scale_* filters, which only
* set the outlink SAR to be equal to the scale SAR iff the input SAR
* was set to something nonzero */
if (inlink->sample_aspect_ratio.num)
outlink->sample_aspect_ratio = scale_sar;
}
if (outlink->format != AV_PIX_FMT_VULKAN)
return 0;
s->vkctx.output_width = outlink->w;
s->vkctx.output_height = outlink->h;
/* Default to re-using the input format */
if (s->out_format == AV_PIX_FMT_NONE || s->out_format == AV_PIX_FMT_VULKAN) {
s->vkctx.output_format = s->vkctx.input_format;
} else {
s->vkctx.output_format = s->out_format;
}
RET(ff_vk_filter_config_output(outlink));
hwfc = (AVHWFramesContext *) outlink->hw_frames_ctx->data;
vkfc = hwfc->hwctx;
vkfc->usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
return 0;
fail:
return err;
}
#define OFFSET(x) offsetof(LibplaceboContext, x)
#define STATIC (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
#define DYNAMIC (STATIC | AV_OPT_FLAG_RUNTIME_PARAM)
static const AVOption libplacebo_options[] = {
{ "w", "Output video width", OFFSET(w_expr), AV_OPT_TYPE_STRING, {.str = "iw"}, .flags = STATIC },
{ "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, {.str = "ih"}, .flags = STATIC },
{ "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, .flags = STATIC },
{ "force_original_aspect_ratio", "decrease or increase w/h if necessary to keep the original AR", OFFSET(force_original_aspect_ratio), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, 2, STATIC, "force_oar" },
{ "disable", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, STATIC, "force_oar" },
{ "decrease", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, STATIC, "force_oar" },
{ "increase", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 2 }, 0, 0, STATIC, "force_oar" },
{ "force_divisible_by", "enforce that the output resolution is divisible by a defined integer when force_original_aspect_ratio is used", OFFSET(force_divisible_by), AV_OPT_TYPE_INT, { .i64 = 1 }, 1, 256, STATIC },
{ "normalize_sar", "force SAR normalization to 1:1", OFFSET(normalize_sar), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, STATIC },
{ "pad_crop_ratio", "ratio between padding and cropping when normalizing SAR (0=pad, 1=crop)", OFFSET(pad_crop_ratio), AV_OPT_TYPE_FLOAT, {.dbl=0.0}, 0.0, 1.0, DYNAMIC },
{"colorspace", "select colorspace", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_SPC_NB-1, DYNAMIC, "colorspace"},
{"auto", "keep the same colorspace", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"gbr", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_RGB}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT709}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT470BG}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"ycgco", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_YCGCO}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"bt2020nc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_NCL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"bt2020c", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_CL}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"ictcp", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_ICTCP}, INT_MIN, INT_MAX, STATIC, "colorspace"},
{"range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_RANGE_NB-1, DYNAMIC, "range"},
{"auto", "keep the same color range", 0, AV_OPT_TYPE_CONST, {.i64=-1}, 0, 0, STATIC, "range"},
{"unspecified", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED}, 0, 0, STATIC, "range"},
{"limited", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
{"tv", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
{"mpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG}, 0, 0, STATIC, "range"},
{"full", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
{"pc", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
{"jpeg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG}, 0, 0, STATIC, "range"},
{"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_PRI_NB-1, DYNAMIC, "color_primaries"},
{"auto", "keep the same color primaries", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"film", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"bt2020", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"smpte431", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"smpte432", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"jedec-p22", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"ebu3213", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213}, INT_MIN, INT_MAX, STATIC, "color_primaries"},
{"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_TRC_NB-1, DYNAMIC, "color_trc"},
{"auto", "keep the same color transfer", 0, AV_OPT_TYPE_CONST, {.i64=-1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt709", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"unknown", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_UNSPECIFIED}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt470m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt470bg", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"smpte170m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"smpte240m", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"linear", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"iec61966-2-4", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt1361e", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"iec61966-2-1", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt2020-10", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"bt2020-12", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"smpte2084", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{"arib-std-b67", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, INT_MIN, INT_MAX, STATIC, "color_trc"},
{ "upscaler", "Upscaler function", OFFSET(upscaler), AV_OPT_TYPE_STRING, {.str = "spline36"}, .flags = DYNAMIC },
{ "downscaler", "Downscaler function", OFFSET(downscaler), AV_OPT_TYPE_STRING, {.str = "mitchell"}, .flags = DYNAMIC },
{ "lut_entries", "Number of scaler LUT entries", OFFSET(lut_entries), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 256, DYNAMIC },
{ "antiringing", "Antiringing strength (for non-EWA filters)", OFFSET(antiringing), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 1.0, DYNAMIC },
{ "sigmoid", "Enable sigmoid upscaling", OFFSET(sigmoid), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
{ "apply_filmgrain", "Apply film grain metadata", OFFSET(apply_filmgrain), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
{ "apply_dolbyvision", "Apply Dolby Vision metadata", OFFSET(apply_dovi), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
{ "deband", "Enable debanding", OFFSET(deband), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "deband_iterations", "Deband iterations", OFFSET(deband_iterations), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, DYNAMIC },
{ "deband_threshold", "Deband threshold", OFFSET(deband_threshold), AV_OPT_TYPE_FLOAT, {.dbl = 4.0}, 0.0, 1024.0, DYNAMIC },
{ "deband_radius", "Deband radius", OFFSET(deband_radius), AV_OPT_TYPE_FLOAT, {.dbl = 16.0}, 0.0, 1024.0, DYNAMIC },
{ "deband_grain", "Deband grain", OFFSET(deband_grain), AV_OPT_TYPE_FLOAT, {.dbl = 6.0}, 0.0, 1024.0, DYNAMIC },
{ "brightness", "Brightness boost", OFFSET(brightness), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1.0, 1.0, DYNAMIC },
{ "contrast", "Contrast gain", OFFSET(contrast), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
{ "saturation", "Saturation gain", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
{ "hue", "Hue shift", OFFSET(hue), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -M_PI, M_PI, DYNAMIC },
{ "gamma", "Gamma adjustment", OFFSET(gamma), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
{ "peak_detect", "Enable dynamic peak detection for HDR tone-mapping", OFFSET(peakdetect), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
{ "smoothing_period", "Peak detection smoothing period", OFFSET(smoothing), AV_OPT_TYPE_FLOAT, {.dbl = 100.0}, 0.0, 1000.0, DYNAMIC },
{ "minimum_peak", "Peak detection minimum peak", OFFSET(min_peak), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 100.0, DYNAMIC },
{ "scene_threshold_low", "Scene change low threshold", OFFSET(scene_low), AV_OPT_TYPE_FLOAT, {.dbl = 5.5}, -1.0, 100.0, DYNAMIC },
{ "scene_threshold_high", "Scene change high threshold", OFFSET(scene_high), AV_OPT_TYPE_FLOAT, {.dbl = 10.0}, -1.0, 100.0, DYNAMIC },
{ "overshoot", "Tone-mapping overshoot margin", OFFSET(overshoot), AV_OPT_TYPE_FLOAT, {.dbl = 0.05}, 0.0, 1.0, DYNAMIC },
{ "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 3, DYNAMIC, "intent" },
{ "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
{ "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
{ "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
{ "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
{ "gamut_mode", "Gamut-mapping mode", OFFSET(gamut_mode), AV_OPT_TYPE_INT, {.i64 = PL_GAMUT_CLIP}, 0, PL_GAMUT_MODE_COUNT - 1, DYNAMIC, "gamut_mode" },
{ "clip", "Hard-clip gamut boundary", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_CLIP}, 0, 0, STATIC, "gamut_mode" },
{ "warn", "Highlight out-of-gamut colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_WARN}, 0, 0, STATIC, "gamut_mode" },
{ "darken", "Darken image to fit gamut", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DARKEN}, 0, 0, STATIC, "gamut_mode" },
{ "desaturate", "Colorimetrically desaturate colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DESATURATE}, 0, 0, STATIC, "gamut_mode" },
{ "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping), AV_OPT_TYPE_INT, {.i64 = TONE_MAP_AUTO}, 0, TONE_MAP_COUNT - 1, DYNAMIC, "tonemap" },
{ "auto", "Automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_AUTO}, 0, 0, STATIC, "tonemap" },
{ "clip", "No tone mapping (clip", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_CLIP}, 0, 0, STATIC, "tonemap" },
#if PL_API_VER >= 246
{ "st2094-40", "SMPTE ST 2094-40", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_ST2094_40}, 0, 0, STATIC, "tonemap" },
{ "st2094-10", "SMPTE ST 2094-10", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_ST2094_10}, 0, 0, STATIC, "tonemap" },
#endif
{ "bt.2390", "ITU-R BT.2390 EETF", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_BT2390}, 0, 0, STATIC, "tonemap" },
{ "bt.2446a", "ITU-R BT.2446 Method A", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_BT2446A}, 0, 0, STATIC, "tonemap" },
{ "spline", "Single-pivot polynomial spline", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_SPLINE}, 0, 0, STATIC, "tonemap" },
{ "reinhard", "Reinhard", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_REINHARD}, 0, 0, STATIC, "tonemap" },
{ "mobius", "Mobius", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_MOBIUS}, 0, 0, STATIC, "tonemap" },
{ "hable", "Filmic tone-mapping (Hable)", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_HABLE}, 0, 0, STATIC, "tonemap" },
{ "gamma", "Gamma function with knee", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_GAMMA}, 0, 0, STATIC, "tonemap" },
{ "linear", "Perceptually linear stretch", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_LINEAR}, 0, 0, STATIC, "tonemap" },
{ "tonemapping_param", "Tunable parameter for some tone-mapping functions", OFFSET(tonemapping_param), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 100.0, .flags = DYNAMIC },
{ "tonemapping_mode", "Tone-mapping mode", OFFSET(tonemapping_mode), AV_OPT_TYPE_INT, {.i64 = PL_TONE_MAP_AUTO}, 0, PL_TONE_MAP_MODE_COUNT - 1, DYNAMIC, "tonemap_mode" },
{ "auto", "Automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_AUTO}, 0, 0, STATIC, "tonemap_mode" },
{ "rgb", "Per-channel (RGB)", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_RGB}, 0, 0, STATIC, "tonemap_mode" },
{ "max", "Maximum component", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_MAX}, 0, 0, STATIC, "tonemap_mode" },
{ "hybrid", "Hybrid of Luma/RGB", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_HYBRID}, 0, 0, STATIC, "tonemap_mode" },
{ "luma", "Luminance", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_LUMA}, 0, 0, STATIC, "tonemap_mode" },
{ "inverse_tonemapping", "Inverse tone mapping (range expansion)", OFFSET(inverse_tonemapping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "tonemapping_crosstalk", "Crosstalk factor for tone-mapping", OFFSET(crosstalk), AV_OPT_TYPE_FLOAT, {.dbl = 0.04}, 0.0, 0.30, DYNAMIC },
{ "tonemapping_lut_size", "Tone-mapping LUT size", OFFSET(tonemapping_lut_size), AV_OPT_TYPE_INT, {.i64 = 256}, 2, 1024, DYNAMIC },
/* deprecated options for backwards compatibility, defaulting to -1 to not override the new defaults */
{ "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 1.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
{ "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 10.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
{ "gamut_warning", "Highlight out-of-gamut colors", OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
{ "gamut_clipping", "Enable colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
{ "dithering", "Dither method to use", OFFSET(dithering), AV_OPT_TYPE_INT, {.i64 = PL_DITHER_BLUE_NOISE}, -1, PL_DITHER_METHOD_COUNT - 1, DYNAMIC, "dither" },
{ "none", "Disable dithering", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, STATIC, "dither" },
{ "blue", "Blue noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_BLUE_NOISE}, 0, 0, STATIC, "dither" },
{ "ordered", "Ordered LUT", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_LUT}, 0, 0, STATIC, "dither" },
{ "ordered_fixed", "Fixed function ordered", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_FIXED}, 0, 0, STATIC, "dither" },
{ "white", "White noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_WHITE_NOISE}, 0, 0, STATIC, "dither" },
{ "dither_lut_size", "Dithering LUT size", OFFSET(dither_lut_size), AV_OPT_TYPE_INT, {.i64 = 6}, 1, 8, STATIC },
{ "dither_temporal", "Enable temporal dithering", OFFSET(dither_temporal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "cones", "Colorblindness adaptation model", OFFSET(cones), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, PL_CONE_LMS, DYNAMIC, "cone" },
{ "l", "L cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_L}, 0, 0, STATIC, "cone" },
{ "m", "M cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_M}, 0, 0, STATIC, "cone" },
{ "s", "S cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_S}, 0, 0, STATIC, "cone" },
{ "cone-strength", "Colorblindness adaptation strength", OFFSET(cone_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 10.0, DYNAMIC },
{ "custom_shader_path", "Path to custom user shader (mpv .hook format)", OFFSET(shader_path), AV_OPT_TYPE_STRING, .flags = STATIC },
{ "custom_shader_bin", "Custom user shader as binary (mpv .hook format)", OFFSET(shader_bin), AV_OPT_TYPE_BINARY, .flags = STATIC },
/* Performance/quality tradeoff options */
{ "skip_aa", "Skip anti-aliasing", OFFSET(skip_aa), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 0, DYNAMIC },
{ "polar_cutoff", "Polar LUT cutoff", OFFSET(polar_cutoff), AV_OPT_TYPE_FLOAT, {.dbl = 0}, 0.0, 1.0, DYNAMIC },
{ "disable_linear", "Disable linear scaling", OFFSET(disable_linear), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "disable_builtin", "Disable built-in scalers", OFFSET(disable_builtin), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "force_icc_lut", "Force the use of a full ICC 3DLUT for color mapping", OFFSET(force_icc_lut), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "force_dither", "Force dithering", OFFSET(force_dither), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ "disable_fbos", "Force-disable FBOs", OFFSET(disable_fbos), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
{ NULL },
};
AVFILTER_DEFINE_CLASS(libplacebo);
static const AVFilterPad libplacebo_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = &filter_frame,
.config_props = &libplacebo_config_input,
},
};
static const AVFilterPad libplacebo_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = &libplacebo_config_output,
},
};
const AVFilter ff_vf_libplacebo = {
.name = "libplacebo",
.description = NULL_IF_CONFIG_SMALL("Apply various GPU filters from libplacebo"),
.priv_size = sizeof(LibplaceboContext),
.init = &libplacebo_init,
.uninit = &libplacebo_uninit,
.process_command = &ff_filter_process_command,
FILTER_INPUTS(libplacebo_inputs),
FILTER_OUTPUTS(libplacebo_outputs),
FILTER_QUERY_FUNC(libplacebo_query_format),
.priv_class = &libplacebo_class,
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
};