• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * This file is part of FFmpeg.
3  *
4  * FFmpeg is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * FFmpeg is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with FFmpeg; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17  */
18 
19 #include "libavutil/file.h"
20 #include "libavutil/opt.h"
21 #include "internal.h"
22 #include "vulkan_filter.h"
23 #include "scale_eval.h"
24 
25 #include <libplacebo/renderer.h>
26 #include <libplacebo/utils/libav.h>
27 #include <libplacebo/vulkan.h>
28 
29 enum {
30     TONE_MAP_AUTO,
31     TONE_MAP_CLIP,
32     TONE_MAP_BT2390,
33     TONE_MAP_BT2446A,
34     TONE_MAP_SPLINE,
35     TONE_MAP_REINHARD,
36     TONE_MAP_MOBIUS,
37     TONE_MAP_HABLE,
38     TONE_MAP_GAMMA,
39     TONE_MAP_LINEAR,
40     TONE_MAP_COUNT,
41 };
42 
43 static const struct pl_tone_map_function * const tonemapping_funcs[TONE_MAP_COUNT] = {
44     [TONE_MAP_AUTO]     = &pl_tone_map_auto,
45     [TONE_MAP_CLIP]     = &pl_tone_map_clip,
46     [TONE_MAP_BT2390]   = &pl_tone_map_bt2390,
47     [TONE_MAP_BT2446A]  = &pl_tone_map_bt2446a,
48     [TONE_MAP_SPLINE]   = &pl_tone_map_spline,
49     [TONE_MAP_REINHARD] = &pl_tone_map_reinhard,
50     [TONE_MAP_MOBIUS]   = &pl_tone_map_mobius,
51     [TONE_MAP_HABLE]    = &pl_tone_map_hable,
52     [TONE_MAP_GAMMA]    = &pl_tone_map_gamma,
53     [TONE_MAP_LINEAR]   = &pl_tone_map_linear,
54 };
55 
56 typedef struct LibplaceboContext {
57     /* lavfi vulkan*/
58     FFVulkanContext vkctx;
59     int initialized;
60 
61     /* libplacebo */
62     pl_log log;
63     pl_vulkan vulkan;
64     pl_gpu gpu;
65     pl_renderer renderer;
66 
67     /* settings */
68     char *out_format_string;
69     char *w_expr;
70     char *h_expr;
71     AVRational target_sar;
72     float pad_crop_ratio;
73     int force_original_aspect_ratio;
74     int force_divisible_by;
75     int normalize_sar;
76     int apply_filmgrain;
77     int apply_dovi;
78     int colorspace;
79     int color_range;
80     int color_primaries;
81     int color_trc;
82 
83     /* pl_render_params */
84     char *upscaler;
85     char *downscaler;
86     int lut_entries;
87     float antiringing;
88     int sigmoid;
89     int skip_aa;
90     float polar_cutoff;
91     int disable_linear;
92     int disable_builtin;
93     int force_icc_lut;
94     int force_dither;
95     int disable_fbos;
96 
97     /* pl_deband_params */
98     int deband;
99     int deband_iterations;
100     float deband_threshold;
101     float deband_radius;
102     float deband_grain;
103 
104     /* pl_color_adjustment */
105     float brightness;
106     float contrast;
107     float saturation;
108     float hue;
109     float gamma;
110 
111     /* pl_peak_detect_params */
112     int peakdetect;
113     float smoothing;
114     float min_peak;
115     float scene_low;
116     float scene_high;
117     float overshoot;
118 
119     /* pl_color_map_params */
120     int intent;
121     int gamut_mode;
122     int tonemapping;
123     float tonemapping_param;
124     int tonemapping_mode;
125     int inverse_tonemapping;
126     float crosstalk;
127     int tonemapping_lut_size;
128     /* for backwards compatibility */
129     float desat_str;
130     float desat_exp;
131     int gamut_warning;
132     int gamut_clipping;
133 
134      /* pl_dither_params */
135     int dithering;
136     int dither_lut_size;
137     int dither_temporal;
138 
139     /* pl_cone_params */
140     int cones;
141     float cone_str;
142 
143     /* custom shaders */
144     char *shader_path;
145     void *shader_bin;
146     int shader_bin_len;
147     const struct pl_hook *hooks[2];
148     int num_hooks;
149 } LibplaceboContext;
150 
get_log_level(void)151 static inline enum pl_log_level get_log_level(void)
152 {
153     int av_lev = av_log_get_level();
154     return av_lev >= AV_LOG_TRACE   ? PL_LOG_TRACE :
155            av_lev >= AV_LOG_DEBUG   ? PL_LOG_DEBUG :
156            av_lev >= AV_LOG_VERBOSE ? PL_LOG_INFO :
157            av_lev >= AV_LOG_WARNING ? PL_LOG_WARN :
158            av_lev >= AV_LOG_ERROR   ? PL_LOG_ERR :
159            av_lev >= AV_LOG_FATAL   ? PL_LOG_FATAL :
160                                       PL_LOG_NONE;
161 }
162 
pl_av_log(void * log_ctx,enum pl_log_level level,const char * msg)163 static void pl_av_log(void *log_ctx, enum pl_log_level level, const char *msg)
164 {
165     int av_lev;
166 
167     switch (level) {
168     case PL_LOG_FATAL:  av_lev = AV_LOG_FATAL;   break;
169     case PL_LOG_ERR:    av_lev = AV_LOG_ERROR;   break;
170     case PL_LOG_WARN:   av_lev = AV_LOG_WARNING; break;
171     case PL_LOG_INFO:   av_lev = AV_LOG_VERBOSE; break;
172     case PL_LOG_DEBUG:  av_lev = AV_LOG_DEBUG;   break;
173     case PL_LOG_TRACE:  av_lev = AV_LOG_TRACE;   break;
174     default: return;
175     }
176 
177     av_log(log_ctx, av_lev, "%s\n", msg);
178 }
179 
parse_shader(AVFilterContext * avctx,const void * shader,size_t len)180 static int parse_shader(AVFilterContext *avctx, const void *shader, size_t len)
181 {
182     LibplaceboContext *s = avctx->priv;
183     const struct pl_hook *hook;
184 
185     hook = pl_mpv_user_shader_parse(s->gpu, shader, len);
186     if (!hook) {
187         av_log(s, AV_LOG_ERROR, "Failed parsing custom shader!\n");
188         return AVERROR(EINVAL);
189     }
190 
191     s->hooks[s->num_hooks++] = hook;
192     return 0;
193 }
194 
find_scaler(AVFilterContext * avctx,const struct pl_filter_config ** opt,const char * name)195 static int find_scaler(AVFilterContext *avctx,
196                        const struct pl_filter_config **opt,
197                        const char *name)
198 {
199     const struct pl_filter_preset *preset;
200     if (!strcmp(name, "help")) {
201         av_log(avctx, AV_LOG_INFO, "Available scaler presets:\n");
202         for (preset = pl_scale_filters; preset->name; preset++)
203             av_log(avctx, AV_LOG_INFO, "    %s\n", preset->name);
204         return AVERROR_EXIT;
205     }
206 
207     for (preset = pl_scale_filters; preset->name; preset++) {
208         if (!strcmp(name, preset->name)) {
209             *opt = preset->filter;
210             return 0;
211         }
212     }
213 
214     av_log(avctx, AV_LOG_ERROR, "No such scaler preset '%s'.\n", name);
215     return AVERROR(EINVAL);
216 }
217 
libplacebo_init(AVFilterContext * avctx)218 static int libplacebo_init(AVFilterContext *avctx)
219 {
220     LibplaceboContext *s = avctx->priv;
221 
222     /* Create libplacebo log context */
223     s->log = pl_log_create(PL_API_VER, pl_log_params(
224         .log_level = get_log_level(),
225         .log_cb = pl_av_log,
226         .log_priv = s,
227     ));
228 
229     if (!s->log)
230         return AVERROR(ENOMEM);
231 
232     /* Note: s->vulkan etc. are initialized later, when hwctx is available */
233     return 0;
234 }
235 
init_vulkan(AVFilterContext * avctx)236 static int init_vulkan(AVFilterContext *avctx)
237 {
238     int err = 0;
239     LibplaceboContext *s = avctx->priv;
240     const AVVulkanDeviceContext *hwctx = s->vkctx.hwctx;
241     uint8_t *buf = NULL;
242     size_t buf_len;
243 
244     /* Import libavfilter vulkan context into libplacebo */
245     s->vulkan = pl_vulkan_import(s->log, pl_vulkan_import_params(
246         .instance       = hwctx->inst,
247         .get_proc_addr  = hwctx->get_proc_addr,
248         .phys_device    = hwctx->phys_dev,
249         .device         = hwctx->act_dev,
250         .extensions     = hwctx->enabled_dev_extensions,
251         .num_extensions = hwctx->nb_enabled_dev_extensions,
252         .features       = &hwctx->device_features,
253         .queue_graphics = {
254             .index = hwctx->queue_family_index,
255             .count = hwctx->nb_graphics_queues,
256         },
257         .queue_compute = {
258             .index = hwctx->queue_family_comp_index,
259             .count = hwctx->nb_comp_queues,
260         },
261         .queue_transfer = {
262             .index = hwctx->queue_family_tx_index,
263             .count = hwctx->nb_tx_queues,
264         },
265         /* This is the highest version created by hwcontext_vulkan.c */
266         .max_api_version = VK_API_VERSION_1_2,
267     ));
268 
269     if (!s->vulkan) {
270         av_log(s, AV_LOG_ERROR, "Failed importing vulkan device to libplacebo!\n");
271         err = AVERROR_EXTERNAL;
272         goto fail;
273     }
274 
275     /* Create the renderer */
276     s->gpu = s->vulkan->gpu;
277     s->renderer = pl_renderer_create(s->log, s->gpu);
278 
279     /* Parse the user shaders, if requested */
280     if (s->shader_bin_len)
281         RET(parse_shader(avctx, s->shader_bin, s->shader_bin_len));
282 
283     if (s->shader_path && s->shader_path[0]) {
284         RET(av_file_map(s->shader_path, &buf, &buf_len, 0, s));
285         RET(parse_shader(avctx, buf, buf_len));
286     }
287 
288     /* fall through */
289 fail:
290     if (buf)
291         av_file_unmap(buf, buf_len);
292     s->initialized =  1;
293     return err;
294 }
295 
libplacebo_uninit(AVFilterContext * avctx)296 static void libplacebo_uninit(AVFilterContext *avctx)
297 {
298     LibplaceboContext *s = avctx->priv;
299 
300     for (int i = 0; i < s->num_hooks; i++)
301         pl_mpv_user_shader_destroy(&s->hooks[i]);
302     pl_renderer_destroy(&s->renderer);
303     pl_vulkan_destroy(&s->vulkan);
304     pl_log_destroy(&s->log);
305     ff_vk_uninit(&s->vkctx);
306     s->initialized = 0;
307     s->gpu = NULL;
308 }
309 
process_frames(AVFilterContext * avctx,AVFrame * out,AVFrame * in)310 static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in)
311 {
312     int err = 0, ok;
313     LibplaceboContext *s = avctx->priv;
314     struct pl_render_params params;
315     enum pl_tone_map_mode tonemapping_mode = s->tonemapping_mode;
316     enum pl_gamut_mode gamut_mode = s->gamut_mode;
317     struct pl_frame image, target;
318     ok = pl_map_avframe_ex(s->gpu, &image, pl_avframe_params(
319         .frame    = in,
320         .map_dovi = s->apply_dovi,
321     ));
322 
323     ok &= pl_map_avframe_ex(s->gpu, &target, pl_avframe_params(
324         .frame    = out,
325         .map_dovi = false,
326     ));
327 
328     if (!ok) {
329         err = AVERROR_EXTERNAL;
330         goto fail;
331     }
332 
333     if (!s->apply_filmgrain)
334         image.film_grain.type = PL_FILM_GRAIN_NONE;
335 
336     if (s->target_sar.num) {
337         float aspect = pl_rect2df_aspect(&target.crop) * av_q2d(s->target_sar);
338         pl_rect2df_aspect_set(&target.crop, aspect, s->pad_crop_ratio);
339     }
340 
341     /* backwards compatibility with older API */
342     if (!tonemapping_mode && (s->desat_str >= 0.0f || s->desat_exp >= 0.0f)) {
343         float str = s->desat_str < 0.0f ? 0.9f : s->desat_str;
344         float exp = s->desat_exp < 0.0f ? 0.2f : s->desat_exp;
345         if (str >= 0.9f && exp <= 0.1f) {
346             tonemapping_mode = PL_TONE_MAP_RGB;
347         } else if (str > 0.1f) {
348             tonemapping_mode = PL_TONE_MAP_HYBRID;
349         } else {
350             tonemapping_mode = PL_TONE_MAP_LUMA;
351         }
352     }
353 
354     if (s->gamut_warning)
355         gamut_mode = PL_GAMUT_WARN;
356     if (s->gamut_clipping)
357         gamut_mode = PL_GAMUT_DESATURATE;
358 
359     /* Update render params */
360     params = (struct pl_render_params) {
361         PL_RENDER_DEFAULTS
362         .lut_entries = s->lut_entries,
363         .antiringing_strength = s->antiringing,
364 
365         .deband_params = !s->deband ? NULL : pl_deband_params(
366             .iterations = s->deband_iterations,
367             .threshold = s->deband_threshold,
368             .radius = s->deband_radius,
369             .grain = s->deband_grain,
370         ),
371 
372         .sigmoid_params = s->sigmoid ? &pl_sigmoid_default_params : NULL,
373 
374         .color_adjustment = &(struct pl_color_adjustment) {
375             .brightness = s->brightness,
376             .contrast = s->contrast,
377             .saturation = s->saturation,
378             .hue = s->hue,
379             .gamma = s->gamma,
380         },
381 
382         .peak_detect_params = !s->peakdetect ? NULL : pl_peak_detect_params(
383             .smoothing_period = s->smoothing,
384             .minimum_peak = s->min_peak,
385             .scene_threshold_low = s->scene_low,
386             .scene_threshold_high = s->scene_high,
387             .overshoot_margin = s->overshoot,
388         ),
389 
390         .color_map_params = pl_color_map_params(
391             .intent = s->intent,
392             .gamut_mode = gamut_mode,
393             .tone_mapping_function = tonemapping_funcs[s->tonemapping],
394             .tone_mapping_param = s->tonemapping_param,
395             .tone_mapping_mode = tonemapping_mode,
396             .inverse_tone_mapping = s->inverse_tonemapping,
397             .tone_mapping_crosstalk = s->crosstalk,
398             .lut_size = s->tonemapping_lut_size,
399         ),
400 
401         .dither_params = s->dithering < 0 ? NULL : pl_dither_params(
402             .method = s->dithering,
403             .lut_size = s->dither_lut_size,
404             .temporal = s->dither_temporal,
405         ),
406 
407         .cone_params = !s->cones ? NULL : pl_cone_params(
408             .cones = s->cones,
409             .strength = s->cone_str,
410         ),
411 
412         .hooks = s->hooks,
413         .num_hooks = s->num_hooks,
414 
415         .skip_anti_aliasing = s->skip_aa,
416         .polar_cutoff = s->polar_cutoff,
417         .disable_linear_scaling = s->disable_linear,
418         .disable_builtin_scalers = s->disable_builtin,
419         .force_icc_lut = s->force_icc_lut,
420         .force_dither = s->force_dither,
421         .disable_fbos = s->disable_fbos,
422     };
423 
424     RET(find_scaler(avctx, &params.upscaler, s->upscaler));
425     RET(find_scaler(avctx, &params.downscaler, s->downscaler));
426 
427     pl_render_image(s->renderer, &image, &target, &params);
428     pl_unmap_avframe(s->gpu, &image);
429     pl_unmap_avframe(s->gpu, &target);
430 
431     /* Flush the command queues for performance */
432     pl_gpu_flush(s->gpu);
433     return 0;
434 
435 fail:
436     pl_unmap_avframe(s->gpu, &image);
437     pl_unmap_avframe(s->gpu, &target);
438     return err;
439 }
440 
filter_frame(AVFilterLink * link,AVFrame * in)441 static int filter_frame(AVFilterLink *link, AVFrame *in)
442 {
443     int err, changed_csp;
444     AVFilterContext *ctx = link->dst;
445     LibplaceboContext *s = ctx->priv;
446     AVFilterLink *outlink = ctx->outputs[0];
447 
448     AVFrame *out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
449     if (!out) {
450         err = AVERROR(ENOMEM);
451         goto fail;
452     }
453 
454     pl_log_level_update(s->log, get_log_level());
455     if (!s->initialized)
456         RET(init_vulkan(ctx));
457 
458     RET(av_frame_copy_props(out, in));
459     out->width = outlink->w;
460     out->height = outlink->h;
461 
462     if (s->apply_dovi && av_frame_get_side_data(in, AV_FRAME_DATA_DOVI_METADATA)) {
463         /* Output of dovi reshaping is always BT.2020+PQ, so infer the correct
464          * output colorspace defaults */
465         out->colorspace = AVCOL_SPC_BT2020_NCL;
466         out->color_primaries = AVCOL_PRI_BT2020;
467         out->color_trc = AVCOL_TRC_SMPTE2084;
468     }
469 
470     if (s->colorspace >= 0)
471         out->colorspace = s->colorspace;
472     if (s->color_range >= 0)
473         out->color_range = s->color_range;
474     if (s->color_trc >= 0)
475         out->color_trc = s->color_trc;
476     if (s->color_primaries >= 0)
477         out->color_primaries = s->color_primaries;
478 
479     changed_csp = in->colorspace      != out->colorspace     ||
480                   in->color_range     != out->color_range    ||
481                   in->color_trc       != out->color_trc      ||
482                   in->color_primaries != out->color_primaries;
483 
484     /* Strip side data if no longer relevant */
485     if (changed_csp) {
486         av_frame_remove_side_data(out, AV_FRAME_DATA_MASTERING_DISPLAY_METADATA);
487         av_frame_remove_side_data(out, AV_FRAME_DATA_CONTENT_LIGHT_LEVEL);
488     }
489     if (s->apply_dovi || changed_csp) {
490         av_frame_remove_side_data(out, AV_FRAME_DATA_DOVI_RPU_BUFFER);
491         av_frame_remove_side_data(out, AV_FRAME_DATA_DOVI_METADATA);
492     }
493     if (s->apply_filmgrain)
494         av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
495 
496     RET(process_frames(ctx, out, in));
497 
498     av_frame_free(&in);
499 
500     return ff_filter_frame(outlink, out);
501 
502 fail:
503     av_frame_free(&in);
504     av_frame_free(&out);
505     return err;
506 }
507 
libplacebo_config_output(AVFilterLink * outlink)508 static int libplacebo_config_output(AVFilterLink *outlink)
509 {
510     int err;
511     AVFilterContext *avctx = outlink->src;
512     LibplaceboContext *s   = avctx->priv;
513     AVFilterLink *inlink   = outlink->src->inputs[0];
514     AVHWFramesContext *hwfc;
515     AVVulkanFramesContext *vkfc;
516     AVRational scale_sar;
517     int *out_w = &s->vkctx.output_width;
518     int *out_h = &s->vkctx.output_height;
519 
520     RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink,
521                                  out_w, out_h));
522 
523     ff_scale_adjust_dimensions(inlink, out_w, out_h,
524                                s->force_original_aspect_ratio,
525                                s->force_divisible_by);
526 
527     scale_sar = (AVRational){outlink->h * inlink->w, *out_w * *out_h};
528     if (inlink->sample_aspect_ratio.num)
529         scale_sar = av_mul_q(scale_sar, inlink->sample_aspect_ratio);
530 
531     if (s->normalize_sar) {
532         /* Apply all SAR during scaling, so we don't need to set the out SAR */
533         s->target_sar = scale_sar;
534     } else {
535         /* This is consistent with other scale_* filters, which only
536          * set the outlink SAR to be equal to the scale SAR iff the input SAR
537          * was set to something nonzero */
538         if (inlink->sample_aspect_ratio.num)
539             outlink->sample_aspect_ratio = scale_sar;
540     }
541 
542     if (s->out_format_string) {
543         s->vkctx.output_format = av_get_pix_fmt(s->out_format_string);
544         if (s->vkctx.output_format == AV_PIX_FMT_NONE) {
545             av_log(avctx, AV_LOG_ERROR, "Invalid output format.\n");
546             return AVERROR(EINVAL);
547         }
548     } else {
549         /* Default to re-using the input format */
550         s->vkctx.output_format = s->vkctx.input_format;
551     }
552 
553     RET(ff_vk_filter_config_output(outlink));
554     hwfc = (AVHWFramesContext *) outlink->hw_frames_ctx->data;
555     vkfc = hwfc->hwctx;
556     vkfc->usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
557 
558     return 0;
559 
560 fail:
561     return err;
562 }
563 
564 #define OFFSET(x) offsetof(LibplaceboContext, x)
565 #define STATIC (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
566 #define DYNAMIC (STATIC | AV_OPT_FLAG_RUNTIME_PARAM)
567 
568 static const AVOption libplacebo_options[] = {
569     { "w", "Output video width",  OFFSET(w_expr), AV_OPT_TYPE_STRING, {.str = "iw"}, .flags = STATIC },
570     { "h", "Output video height", OFFSET(h_expr), AV_OPT_TYPE_STRING, {.str = "ih"}, .flags = STATIC },
571     { "format", "Output video format", OFFSET(out_format_string), AV_OPT_TYPE_STRING, .flags = STATIC },
572     { "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" },
573         { "disable",  NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 0 }, 0, 0, STATIC, "force_oar" },
574         { "decrease", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 1 }, 0, 0, STATIC, "force_oar" },
575         { "increase", NULL, 0, AV_OPT_TYPE_CONST, {.i64 = 2 }, 0, 0, STATIC, "force_oar" },
576     { "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 },
577     { "normalize_sar", "force SAR normalization to 1:1", OFFSET(normalize_sar), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, STATIC },
578     { "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 },
579 
580     {"colorspace", "select colorspace", OFFSET(colorspace), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_SPC_NB-1, DYNAMIC, "colorspace"},
581     {"auto", "keep the same colorspace",  0, AV_OPT_TYPE_CONST, {.i64=-1},                          INT_MIN, INT_MAX, STATIC, "colorspace"},
582     {"gbr",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_RGB},               INT_MIN, INT_MAX, STATIC, "colorspace"},
583     {"bt709",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT709},             INT_MIN, INT_MAX, STATIC, "colorspace"},
584     {"unknown",                    NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_UNSPECIFIED},       INT_MIN, INT_MAX, STATIC, "colorspace"},
585     {"bt470bg",                    NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT470BG},           INT_MIN, INT_MAX, STATIC, "colorspace"},
586     {"smpte170m",                  NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE170M},         INT_MIN, INT_MAX, STATIC, "colorspace"},
587     {"smpte240m",                  NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_SMPTE240M},         INT_MIN, INT_MAX, STATIC, "colorspace"},
588     {"ycgco",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_YCGCO},             INT_MIN, INT_MAX, STATIC, "colorspace"},
589     {"bt2020nc",                   NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_NCL},        INT_MIN, INT_MAX, STATIC, "colorspace"},
590     {"bt2020c",                    NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_BT2020_CL},         INT_MIN, INT_MAX, STATIC, "colorspace"},
591     {"ictcp",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_SPC_ICTCP},             INT_MIN, INT_MAX, STATIC, "colorspace"},
592 
593     {"range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_RANGE_NB-1, DYNAMIC, "range"},
594     {"auto",  "keep the same color range",   0, AV_OPT_TYPE_CONST, {.i64=-1},                       0, 0, STATIC, "range"},
595     {"unspecified",                  NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED},  0, 0, STATIC, "range"},
596     {"unknown",                      NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_UNSPECIFIED},  0, 0, STATIC, "range"},
597     {"limited",                      NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG},         0, 0, STATIC, "range"},
598     {"tv",                           NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG},         0, 0, STATIC, "range"},
599     {"mpeg",                         NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_MPEG},         0, 0, STATIC, "range"},
600     {"full",                         NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG},         0, 0, STATIC, "range"},
601     {"pc",                           NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG},         0, 0, STATIC, "range"},
602     {"jpeg",                         NULL,   0, AV_OPT_TYPE_CONST, {.i64=AVCOL_RANGE_JPEG},         0, 0, STATIC, "range"},
603 
604     {"color_primaries", "select color primaries", OFFSET(color_primaries), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_PRI_NB-1, DYNAMIC, "color_primaries"},
605     {"auto", "keep the same color primaries",  0, AV_OPT_TYPE_CONST, {.i64=-1},                     INT_MIN, INT_MAX, STATIC, "color_primaries"},
606     {"bt709",                           NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT709},        INT_MIN, INT_MAX, STATIC, "color_primaries"},
607     {"unknown",                         NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_UNSPECIFIED},  INT_MIN, INT_MAX, STATIC, "color_primaries"},
608     {"bt470m",                          NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470M},       INT_MIN, INT_MAX, STATIC, "color_primaries"},
609     {"bt470bg",                         NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT470BG},      INT_MIN, INT_MAX, STATIC, "color_primaries"},
610     {"smpte170m",                       NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE170M},    INT_MIN, INT_MAX, STATIC, "color_primaries"},
611     {"smpte240m",                       NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE240M},    INT_MIN, INT_MAX, STATIC, "color_primaries"},
612     {"film",                            NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_FILM},         INT_MIN, INT_MAX, STATIC, "color_primaries"},
613     {"bt2020",                          NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_BT2020},       INT_MIN, INT_MAX, STATIC, "color_primaries"},
614     {"smpte428",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE428},     INT_MIN, INT_MAX, STATIC, "color_primaries"},
615     {"smpte431",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE431},     INT_MIN, INT_MAX, STATIC, "color_primaries"},
616     {"smpte432",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_SMPTE432},     INT_MIN, INT_MAX, STATIC, "color_primaries"},
617     {"jedec-p22",                       NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_JEDEC_P22},    INT_MIN, INT_MAX, STATIC, "color_primaries"},
618     {"ebu3213",                         NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_PRI_EBU3213},      INT_MIN, INT_MAX, STATIC, "color_primaries"},
619 
620     {"color_trc", "select color transfer", OFFSET(color_trc), AV_OPT_TYPE_INT, {.i64=-1}, -1, AVCOL_TRC_NB-1, DYNAMIC, "color_trc"},
621     {"auto", "keep the same color transfer",  0, AV_OPT_TYPE_CONST, {.i64=-1},                     INT_MIN, INT_MAX, STATIC, "color_trc"},
622     {"bt709",                          NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT709},        INT_MIN, INT_MAX, STATIC, "color_trc"},
623     {"unknown",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_UNSPECIFIED},  INT_MIN, INT_MAX, STATIC, "color_trc"},
624     {"bt470m",                         NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA22},      INT_MIN, INT_MAX, STATIC, "color_trc"},
625     {"bt470bg",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_GAMMA28},      INT_MIN, INT_MAX, STATIC, "color_trc"},
626     {"smpte170m",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE170M},    INT_MIN, INT_MAX, STATIC, "color_trc"},
627     {"smpte240m",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE240M},    INT_MIN, INT_MAX, STATIC, "color_trc"},
628     {"linear",                         NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LINEAR},       INT_MIN, INT_MAX, STATIC, "color_trc"},
629     {"iec61966-2-4",                   NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_4}, INT_MIN, INT_MAX, STATIC, "color_trc"},
630     {"bt1361e",                        NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT1361_ECG},   INT_MIN, INT_MAX, STATIC, "color_trc"},
631     {"iec61966-2-1",                   NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_IEC61966_2_1}, INT_MIN, INT_MAX, STATIC, "color_trc"},
632     {"bt2020-10",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_10},    INT_MIN, INT_MAX, STATIC, "color_trc"},
633     {"bt2020-12",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_BT2020_12},    INT_MIN, INT_MAX, STATIC, "color_trc"},
634     {"smpte2084",                      NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE2084},    INT_MIN, INT_MAX, STATIC, "color_trc"},
635     {"arib-std-b67",                   NULL,  0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_ARIB_STD_B67}, INT_MIN, INT_MAX, STATIC, "color_trc"},
636 
637     { "upscaler", "Upscaler function", OFFSET(upscaler), AV_OPT_TYPE_STRING, {.str = "spline36"}, .flags = DYNAMIC },
638     { "downscaler", "Downscaler function", OFFSET(downscaler), AV_OPT_TYPE_STRING, {.str = "mitchell"}, .flags = DYNAMIC },
639     { "lut_entries", "Number of scaler LUT entries", OFFSET(lut_entries), AV_OPT_TYPE_INT, {.i64 = 0}, 0, 256, DYNAMIC },
640     { "antiringing", "Antiringing strength (for non-EWA filters)", OFFSET(antiringing), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 1.0, DYNAMIC },
641     { "sigmoid", "Enable sigmoid upscaling", OFFSET(sigmoid), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
642     { "apply_filmgrain", "Apply film grain metadata", OFFSET(apply_filmgrain), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
643     { "apply_dolbyvision", "Apply Dolby Vision metadata", OFFSET(apply_dovi), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
644 
645     { "deband", "Enable debanding", OFFSET(deband), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
646     { "deband_iterations", "Deband iterations", OFFSET(deband_iterations), AV_OPT_TYPE_INT, {.i64 = 1}, 0, 16, DYNAMIC },
647     { "deband_threshold", "Deband threshold", OFFSET(deband_threshold), AV_OPT_TYPE_FLOAT, {.dbl = 4.0}, 0.0, 1024.0, DYNAMIC },
648     { "deband_radius", "Deband radius", OFFSET(deband_radius), AV_OPT_TYPE_FLOAT, {.dbl = 16.0}, 0.0, 1024.0, DYNAMIC },
649     { "deband_grain", "Deband grain", OFFSET(deband_grain), AV_OPT_TYPE_FLOAT, {.dbl = 6.0}, 0.0, 1024.0, DYNAMIC },
650 
651     { "brightness", "Brightness boost", OFFSET(brightness), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -1.0, 1.0, DYNAMIC },
652     { "contrast", "Contrast gain", OFFSET(contrast), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
653     { "saturation", "Saturation gain", OFFSET(saturation), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
654     { "hue", "Hue shift", OFFSET(hue), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, -M_PI, M_PI, DYNAMIC },
655     { "gamma", "Gamma adjustment", OFFSET(gamma), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 16.0, DYNAMIC },
656 
657     { "peak_detect", "Enable dynamic peak detection for HDR tone-mapping", OFFSET(peakdetect), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
658     { "smoothing_period", "Peak detection smoothing period", OFFSET(smoothing), AV_OPT_TYPE_FLOAT, {.dbl = 100.0}, 0.0, 1000.0, DYNAMIC },
659     { "minimum_peak", "Peak detection minimum peak", OFFSET(min_peak), AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 0.0, 100.0, DYNAMIC },
660     { "scene_threshold_low", "Scene change low threshold", OFFSET(scene_low), AV_OPT_TYPE_FLOAT, {.dbl = 5.5}, -1.0, 100.0, DYNAMIC },
661     { "scene_threshold_high", "Scene change high threshold", OFFSET(scene_high), AV_OPT_TYPE_FLOAT, {.dbl = 10.0}, -1.0, 100.0, DYNAMIC },
662     { "overshoot", "Tone-mapping overshoot margin", OFFSET(overshoot), AV_OPT_TYPE_FLOAT, {.dbl = 0.05}, 0.0, 1.0, DYNAMIC },
663 
664     { "intent", "Rendering intent", OFFSET(intent), AV_OPT_TYPE_INT, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 3, DYNAMIC, "intent" },
665         { "perceptual", "Perceptual", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_PERCEPTUAL}, 0, 0, STATIC, "intent" },
666         { "relative", "Relative colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_RELATIVE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
667         { "absolute", "Absolute colorimetric", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_ABSOLUTE_COLORIMETRIC}, 0, 0, STATIC, "intent" },
668         { "saturation", "Saturation mapping", 0, AV_OPT_TYPE_CONST, {.i64 = PL_INTENT_SATURATION}, 0, 0, STATIC, "intent" },
669     { "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" },
670         { "clip", "Hard-clip gamut boundary", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_CLIP}, 0, 0, STATIC, "gamut_mode" },
671         { "warn", "Highlight out-of-gamut colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_WARN}, 0, 0, STATIC, "gamut_mode" },
672         { "darken", "Darken image to fit gamut", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DARKEN}, 0, 0, STATIC, "gamut_mode" },
673         { "desaturate", "Colorimetrically desaturate colors", 0, AV_OPT_TYPE_CONST, {.i64 = PL_GAMUT_DESATURATE}, 0, 0, STATIC, "gamut_mode" },
674     { "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping), AV_OPT_TYPE_INT, {.i64 = TONE_MAP_AUTO}, 0, TONE_MAP_COUNT - 1, DYNAMIC, "tonemap" },
675         { "auto", "Automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_AUTO}, 0, 0, STATIC, "tonemap" },
676         { "clip", "No tone mapping (clip", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_CLIP}, 0, 0, STATIC, "tonemap" },
677         { "bt.2390", "ITU-R BT.2390 EETF", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_BT2390}, 0, 0, STATIC, "tonemap" },
678         { "bt.2446a", "ITU-R BT.2446 Method A", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_BT2446A}, 0, 0, STATIC, "tonemap" },
679         { "spline", "Single-pivot polynomial spline", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_SPLINE}, 0, 0, STATIC, "tonemap" },
680         { "reinhard", "Reinhard", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_REINHARD}, 0, 0, STATIC, "tonemap" },
681         { "mobius", "Mobius", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_MOBIUS}, 0, 0, STATIC, "tonemap" },
682         { "hable", "Filmic tone-mapping (Hable)", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_HABLE}, 0, 0, STATIC, "tonemap" },
683         { "gamma", "Gamma function with knee", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_GAMMA}, 0, 0, STATIC, "tonemap" },
684         { "linear", "Perceptually linear stretch", 0, AV_OPT_TYPE_CONST, {.i64 = TONE_MAP_LINEAR}, 0, 0, STATIC, "tonemap" },
685     { "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 },
686     { "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" },
687         { "auto", "Automatic selection", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_AUTO}, 0, 0, STATIC, "tonemap_mode" },
688         { "rgb", "Per-channel (RGB)", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_RGB}, 0, 0, STATIC, "tonemap_mode" },
689         { "max", "Maximum component", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_MAX}, 0, 0, STATIC, "tonemap_mode" },
690         { "hybrid", "Hybrid of Luma/RGB", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_HYBRID}, 0, 0, STATIC, "tonemap_mode" },
691         { "luma", "Luminance", 0, AV_OPT_TYPE_CONST, {.i64 = PL_TONE_MAP_LUMA}, 0, 0, STATIC, "tonemap_mode" },
692     { "inverse_tonemapping", "Inverse tone mapping (range expansion)", OFFSET(inverse_tonemapping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
693     { "tonemapping_crosstalk", "Crosstalk factor for tone-mapping", OFFSET(crosstalk), AV_OPT_TYPE_FLOAT, {.dbl = 0.04}, 0.0, 0.30, DYNAMIC },
694     { "tonemapping_lut_size", "Tone-mapping LUT size", OFFSET(tonemapping_lut_size), AV_OPT_TYPE_INT, {.i64 = 256}, 2, 1024, DYNAMIC },
695     /* deprecated options for backwards compatibility, defaulting to -1 to not override the new defaults */
696     { "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 1.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
697     { "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = -1.0}, -1.0, 10.0, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
698     { "gamut_warning", "Highlight out-of-gamut colors", OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
699     { "gamut_clipping", "Enable colorimetric gamut clipping", OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC | AV_OPT_FLAG_DEPRECATED },
700 
701     { "dithering", "Dither method to use", OFFSET(dithering), AV_OPT_TYPE_INT, {.i64 = PL_DITHER_BLUE_NOISE}, -1, PL_DITHER_METHOD_COUNT - 1, DYNAMIC, "dither" },
702         { "none", "Disable dithering", 0, AV_OPT_TYPE_CONST, {.i64 = -1}, 0, 0, STATIC, "dither" },
703         { "blue", "Blue noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_BLUE_NOISE}, 0, 0, STATIC, "dither" },
704         { "ordered", "Ordered LUT", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_LUT}, 0, 0, STATIC, "dither" },
705         { "ordered_fixed", "Fixed function ordered", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_ORDERED_FIXED}, 0, 0, STATIC, "dither" },
706         { "white", "White noise", 0, AV_OPT_TYPE_CONST, {.i64 = PL_DITHER_WHITE_NOISE}, 0, 0, STATIC, "dither" },
707     { "dither_lut_size", "Dithering LUT size", OFFSET(dither_lut_size), AV_OPT_TYPE_INT, {.i64 = 6}, 1, 8, STATIC },
708     { "dither_temporal", "Enable temporal dithering", OFFSET(dither_temporal), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
709 
710     { "cones", "Colorblindness adaptation model", OFFSET(cones), AV_OPT_TYPE_FLAGS, {.i64 = 0}, 0, PL_CONE_LMS, DYNAMIC, "cone" },
711         { "l", "L cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_L}, 0, 0, STATIC, "cone" },
712         { "m", "M cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_M}, 0, 0, STATIC, "cone" },
713         { "s", "S cone", 0, AV_OPT_TYPE_CONST, {.i64 = PL_CONE_S}, 0, 0, STATIC, "cone" },
714     { "cone-strength", "Colorblindness adaptation strength", OFFSET(cone_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.0}, 0.0, 10.0, DYNAMIC },
715 
716     { "custom_shader_path", "Path to custom user shader (mpv .hook format)", OFFSET(shader_path), AV_OPT_TYPE_STRING, .flags = STATIC },
717     { "custom_shader_bin", "Custom user shader as binary (mpv .hook format)", OFFSET(shader_bin), AV_OPT_TYPE_BINARY, .flags = STATIC },
718 
719     /* Performance/quality tradeoff options */
720     { "skip_aa", "Skip anti-aliasing", OFFSET(skip_aa), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 0, DYNAMIC },
721     { "polar_cutoff", "Polar LUT cutoff", OFFSET(polar_cutoff), AV_OPT_TYPE_FLOAT, {.dbl = 0}, 0.0, 1.0, DYNAMIC },
722     { "disable_linear", "Disable linear scaling", OFFSET(disable_linear), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
723     { "disable_builtin", "Disable built-in scalers", OFFSET(disable_builtin), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
724     { "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 },
725     { "force_dither", "Force dithering", OFFSET(force_dither), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
726     { "disable_fbos", "Force-disable FBOs", OFFSET(disable_fbos), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
727     { NULL },
728 };
729 
730 AVFILTER_DEFINE_CLASS(libplacebo);
731 
732 static const AVFilterPad libplacebo_inputs[] = {
733     {
734         .name         = "default",
735         .type         = AVMEDIA_TYPE_VIDEO,
736         .filter_frame = &filter_frame,
737         .config_props = &ff_vk_filter_config_input,
738     },
739 };
740 
741 static const AVFilterPad libplacebo_outputs[] = {
742     {
743         .name         = "default",
744         .type         = AVMEDIA_TYPE_VIDEO,
745         .config_props = &libplacebo_config_output,
746     },
747 };
748 
749 const AVFilter ff_vf_libplacebo = {
750     .name           = "libplacebo",
751     .description    = NULL_IF_CONFIG_SMALL("Apply various GPU filters from libplacebo"),
752     .priv_size      = sizeof(LibplaceboContext),
753     .init           = &libplacebo_init,
754     .uninit         = &libplacebo_uninit,
755     .process_command = &ff_filter_process_command,
756     FILTER_INPUTS(libplacebo_inputs),
757     FILTER_OUTPUTS(libplacebo_outputs),
758     FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
759     .priv_class     = &libplacebo_class,
760     .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
761 };
762