[FFmpeg-devel] [PATCH v2] lavfi: add a libplacebo filter

Dennis Mungai dmngaie at gmail.com
Mon Nov 15 20:21:16 EET 2021


On Fri, 12 Nov 2021 at 13:59, Niklas Haas <ffmpeg at haasn.xyz> wrote:

> From: Niklas Haas <git at haasn.dev>
>
> This filter conceptually maps the libplacebo `pl_renderer` API into
> libavfilter, which is a high-level image rendering API designed to work
> with an RGB pipeline internally. As such, there's no way to avoid e.g.
> chroma interpolation with this filter, although new versions of
> libplacebo support outputting back to subsampled YCbCr after processing
> is done.
>
> That being said, `pl_renderer` supports automatic integration of the
> majority of libplacebo's shaders, ranging from debanding to tone
> mapping, and also supports loading custom mpv-style user shaders, making
> this API a natural candidate for getting a lot of functionality out of
> relatively little code.
>
> In the future, I may approach this problem either by rewriting this
> filter to also support a non-renderer codepath, or by upgrading
> libplacebo's renderer to support a full YCbCr pipeline.
>
> This unfortunately requires a very new version of libplacebo (unreleased
> at time of writing) for timeline semaphore support. But the amount of
> boilerplate needed to hack in backwards compatibility would have been
> very unreasonable.
> ---
>  configure                   |   3 +
>  libavfilter/Makefile        |   1 +
>  libavfilter/allfilters.c    |   1 +
>  libavfilter/vf_libplacebo.c | 717 ++++++++++++++++++++++++++++++++++++
>  4 files changed, 722 insertions(+)
>  create mode 100644 libavfilter/vf_libplacebo.c
>
> diff --git a/configure b/configure
> index eb451d2782..891824757b 100755
> --- a/configure
> +++ b/configure
> @@ -1827,6 +1827,7 @@ EXTERNAL_LIBRARY_LIST="
>      libopenmpt
>      libopenvino
>      libopus
> +    libplacebo
>      libpulse
>      librabbitmq
>      librav1e
> @@ -3618,6 +3619,7 @@ interlace_filter_deps="gpl"
>  kerndeint_filter_deps="gpl"
>  ladspa_filter_deps="ladspa libdl"
>  lensfun_filter_deps="liblensfun version3"
> +libplacebo_filter_deps="libplacebo vulkan libglslang"
>  lv2_filter_deps="lv2"
>  mcdeint_filter_deps="avcodec gpl"
>  metadata_filter_deps="avformat"
> @@ -6493,6 +6495,7 @@ enabled libopus           && {
>          require_pkg_config libopus opus opus_multistream.h
> opus_multistream_surround_encoder_create
>      }
>  }
> +enabled libplacebo        && require_pkg_config libplacebo "libplacebo >=
> 4.173.0" libplacebo/vulkan.h pl_vulkan_create
>  enabled libpulse          && require_pkg_config libpulse libpulse
> pulse/pulseaudio.h pa_context_new
>  enabled librabbitmq       && require_pkg_config librabbitmq "librabbitmq
> >= 0.7.1" amqp.h amqp_new_connection
>  enabled librav1e          && require_pkg_config librav1e "rav1e >= 0.4.0"
> rav1e.h rav1e_context_new
> diff --git a/libavfilter/Makefile b/libavfilter/Makefile
> index 552bd4e286..e2059766b0 100644
> --- a/libavfilter/Makefile
> +++ b/libavfilter/Makefile
> @@ -323,6 +323,7 @@ OBJS-$(CONFIG_LAGFUN_FILTER)                 +=
> vf_lagfun.o
>  OBJS-$(CONFIG_LATENCY_FILTER)                += f_latency.o
>  OBJS-$(CONFIG_LENSCORRECTION_FILTER)         += vf_lenscorrection.o
>  OBJS-$(CONFIG_LENSFUN_FILTER)                += vf_lensfun.o
> +OBJS-$(CONFIG_LIBPLACEBO_FILTER)             += vf_libplacebo.o vulkan.o
>  OBJS-$(CONFIG_LIBVMAF_FILTER)                += vf_libvmaf.o framesync.o
>  OBJS-$(CONFIG_LIMITDIFF_FILTER)              += vf_limitdiff.o framesync.o
>  OBJS-$(CONFIG_LIMITER_FILTER)                += vf_limiter.o
> diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
> index 667b6fc246..be94249024 100644
> --- a/libavfilter/allfilters.c
> +++ b/libavfilter/allfilters.c
> @@ -308,6 +308,7 @@ extern const AVFilter ff_vf_lagfun;
>  extern const AVFilter ff_vf_latency;
>  extern const AVFilter ff_vf_lenscorrection;
>  extern const AVFilter ff_vf_lensfun;
> +extern const AVFilter ff_vf_libplacebo;
>  extern const AVFilter ff_vf_libvmaf;
>  extern const AVFilter ff_vf_limitdiff;
>  extern const AVFilter ff_vf_limiter;
> diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c
> new file mode 100644
> index 0000000000..3b48674d1a
> --- /dev/null
> +++ b/libavfilter/vf_libplacebo.c
> @@ -0,0 +1,717 @@
> +/*
> + * 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.h"
> +#include "scale_eval.h"
> +
> +#include <libplacebo/renderer.h>
> +#include <libplacebo/utils/libav.h>
> +#include <libplacebo/vulkan.h>
> +
> +typedef struct LibplaceboContext {
> +    /* lavfi vulkan*/
> +    FFVulkanContext vkctx;
> +    int initialized;
> +
> +    /* libplacebo */
> +    pl_log log;
> +    pl_vulkan vulkan;
> +    pl_gpu gpu;
> +    pl_renderer renderer;
> +
> +    /* settings */
> +    char *out_format_string;
> +    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 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_3dlut;
> +    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 tonemapping;
> +    float tonemapping_param;
> +    float desat_str;
> +    float desat_exp;
> +    float desat_base;
> +    float max_boost;
> +    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 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 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 = PL_LOG_DEBUG,
> +        .log_cb = pl_av_log,
> +        .log_priv = s,
> +    ));
> +
> +    if (!s->log)
> +        return AVERROR(ENOMEM);
> +
> +    /* 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 AVVulkanDeviceContext *hwctx = s->vkctx.hwctx;
> +    uint8_t *buf = NULL;
> +    size_t buf_len;
> +
> +    /* 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);
> +    s->initialized =  1;
> +    return err;
> +}
> +
> +static void libplacebo_uninit(AVFilterContext *avctx)
> +{
> +    LibplaceboContext *s = avctx->priv;
> +
> +    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_filter_uninit(avctx);
> +    s->initialized = 0;
> +    s->gpu = NULL;
> +}
> +
> +static int wrap_vkframe(pl_gpu gpu, const AVFrame *frame, int plane,
> pl_tex *tex)
> +{
> +    AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
> +    const AVHWFramesContext *hwfc = (AVHWFramesContext *)
> frame->hw_frames_ctx->data;
> +    const AVVulkanFramesContext *vkfc = hwfc->hwctx;
> +    const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format);
> +    const VkFormat *vk_fmt = av_vkfmt_from_pixfmt(hwfc->sw_format);
> +    const int chroma = plane == 1 || plane == 2;
> +
> +    *tex = pl_vulkan_wrap(gpu, pl_vulkan_wrap_params(
> +        .image = vkf->img[plane],
> +        .format = vk_fmt[plane],
> +        .width = AV_CEIL_RSHIFT(frame->width, chroma ?
> desc->log2_chroma_w : 0),
> +        .height = AV_CEIL_RSHIFT(frame->height, chroma ?
> desc->log2_chroma_h : 0),
> +        .usage = vkfc->usage,
> +    ));
> +
> +    if (!*tex)
> +        return AVERROR(ENOMEM);
> +
> +    pl_vulkan_release(gpu, *tex, vkf->layout[plane], (pl_vulkan_sem) {
> +        .sem = vkf->sem[plane],
> +        .value = vkf->sem_value[plane]
> +    });
> +    return 0;
> +}
> +
> +static int unwrap_vkframe(pl_gpu gpu, AVFrame *frame, int plane, pl_tex
> *tex)
> +{
> +    AVVkFrame *vkf = (AVVkFrame *) frame->data[0];
> +    uint64_t sem_value = ++vkf->sem_value[plane];
> +    int ok = pl_vulkan_hold_raw(gpu, *tex, &vkf->layout[plane],
> +                                (pl_vulkan_sem) { vkf->sem[plane],
> sem_value });
> +    vkf->access[plane] = 0;
> +    return ok ? 0 : AVERROR_EXTERNAL;
> +}
> +
> +static void set_sample_depth(struct pl_frame *out_frame, const AVFrame
> *frame)
> +{
> +    const AVHWFramesContext *hwfc = (AVHWFramesContext *)
> frame->hw_frames_ctx->data;
> +    pl_fmt fmt = out_frame->planes[0].texture->params.format;
> +    struct pl_bit_encoding *bits = &out_frame->repr.bits;
> +    bits->sample_depth = fmt->component_depth[0];
> +
> +    switch (hwfc->sw_format) {
> +    case AV_PIX_FMT_P010: bits->bit_shift = 6; break;
> +    default: break;
> +    }
> +}
> +
> +static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame
> *in)
> +{
> +    int err = 0;
> +    LibplaceboContext *s = avctx->priv;
> +    struct pl_render_params params;
> +    struct pl_frame image, target;
> +    pl_frame_from_avframe(&image, in);
> +    pl_frame_from_avframe(&target, out);
> +
> +    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);
> +    }
> +
> +    /* 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,
> +            .tone_mapping_algo = s->tonemapping,
> +            .tone_mapping_param = s->tonemapping_param,
> +            .desaturation_strength = s->desat_str,
> +            .desaturation_exponent = s->desat_exp,
> +            .desaturation_base = s->desat_base,
> +            .max_boost = s->max_boost,
> +            .gamut_warning = s->gamut_warning,
> +            .gamut_clipping = s->gamut_clipping,
> +        ),
> +
> +        .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_3dlut = s->force_3dlut,
> +        .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));
> +
> +    /* Ideally, we would persistently wrap all of these AVVkFrames into
> pl_tex
> +     * objects, but for now we'll just create and destroy a wrapper per
> frame.
> +     * Note that doing it this way is suboptimal, since it results in the
> +     * creation and destruction of a VkSampler and VkFramebuffer per
> frame.
> +     *
> +     * FIXME: Can we do better? */
> +    for (int i = 0; i < image.num_planes; i++)
> +        RET(wrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
> +    for (int i = 0; i < target.num_planes; i++)
> +        RET(wrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
> +
> +    /* Since we-re mapping vkframes manually, the pl_frame helpers don't
> know
> +     * about the mismatch between the sample format and the color depth.
> */
> +    set_sample_depth(&image, in);
> +    set_sample_depth(&target, out);
> +
> +    pl_render_image(s->renderer, &image, &target, &params);
> +
> +    for (int i = 0; i < image.num_planes; i++)
> +        RET(unwrap_vkframe(s->gpu, in, i, &image.planes[i].texture));
> +    for (int i = 0; i < target.num_planes; i++)
> +        RET(unwrap_vkframe(s->gpu, out, i, &target.planes[i].texture));
> +
> +    /* Flush the command queues for performance */
> +    pl_gpu_flush(s->gpu);
> +
> +    /* fall through */
> +fail:
> +    for (int i = 0; i < image.num_planes; i++)
> +        pl_tex_destroy(s->gpu, &image.planes[i].texture);
> +    for (int i = 0; i < target.num_planes; i++)
> +        pl_tex_destroy(s->gpu, &target.planes[i].texture);
> +    return err;
> +}
> +
> +static int filter_frame(AVFilterLink *link, AVFrame *in)
> +{
> +    int err;
> +    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;
> +    }
> +
> +    if (!s->initialized)
> +        RET(init_vulkan(ctx));
> +
> +    RET(av_frame_copy_props(out, in));
> +    out->width = outlink->w;
> +    out->height = outlink->h;
> +
> +    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;
> +
> +    RET(process_frames(ctx, out, in));
> +
> +    if (s->apply_filmgrain)
> +        av_frame_remove_side_data(out, AV_FRAME_DATA_FILM_GRAIN_PARAMS);
> +
> +    av_frame_free(&in);
> +
> +    return ff_filter_frame(outlink, out);
> +
> +fail:
> +    av_frame_free(&in);
> +    av_frame_free(&out);
> +    return err;
> +}
> +
> +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;
> +    int *out_w = &s->vkctx.output_width;
> +    int *out_h = &s->vkctx.output_height;
> +
> +    RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink,
> +                                 out_w, out_h));
> +
> +    ff_scale_adjust_dimensions(inlink, out_w, out_h,
> +                               s->force_original_aspect_ratio,
> +                               s->force_divisible_by);
> +
> +    scale_sar = (AVRational){outlink->h * inlink->w, *out_w * *out_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 */
> +        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 (s->out_format_string) {
> +        s->vkctx.output_format = av_get_pix_fmt(s->out_format_string);
> +        if (s->vkctx.output_format == AV_PIX_FMT_NONE) {
> +            av_log(avctx, AV_LOG_ERROR, "Invalid output format.\n");
> +            return AVERROR(EINVAL);
> +        }
> +    } else {
> +        /* Default to re-using the input format */
> +        s->vkctx.output_format = s->vkctx.input_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 = 1}, 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 },
> +
> +    { "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" },
> +    { "tonemapping", "Tone-mapping algorithm", OFFSET(tonemapping),
> AV_OPT_TYPE_INT, {.i64 = PL_TONE_MAPPING_BT_2390}, 0,
> PL_TONE_MAPPING_ALGORITHM_COUNT - 1, DYNAMIC, "tonemap" },
> +        { "clip", "Hard-clipping", 0, AV_OPT_TYPE_CONST, {.i64 =
> PL_TONE_MAPPING_CLIP}, 0, 0, STATIC, "tonemap" },
> +        { "mobius", "Mobius tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 =
> PL_TONE_MAPPING_MOBIUS}, 0, 0, STATIC, "tonemap" },
> +        { "reinhard", "Reinhard tone-mapping", 0, AV_OPT_TYPE_CONST,
> {.i64 = PL_TONE_MAPPING_REINHARD}, 0, 0, STATIC, "tonemap" },
> +        { "hable", "Hable/Filmic tone-mapping", 0, AV_OPT_TYPE_CONST,
> {.i64 = PL_TONE_MAPPING_HABLE}, 0, 0, STATIC, "tonemap" },
> +        { "gamma", "Gamma tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 =
> PL_TONE_MAPPING_GAMMA}, 0, 0, STATIC, "tonemap" },
> +        { "linear", "Linear tone-mapping", 0, AV_OPT_TYPE_CONST, {.i64 =
> PL_TONE_MAPPING_LINEAR}, 0, 0, STATIC, "tonemap" },
> +        { "bt.2390", "ITU-R BT.2390 tone-mapping", 0, AV_OPT_TYPE_CONST,
> {.i64 = PL_TONE_MAPPING_BT_2390}, 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 },
> +    { "desaturation_strength", "Desaturation strength",
> OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.90}, 0.0, 1.0, DYNAMIC },
> +    { "desaturation_exponent", "Desaturation exponent",
> OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = 0.2}, 0.0, 10.0, DYNAMIC },
> +    { "desaturation_base", "Desaturation base", OFFSET(desat_base),
> AV_OPT_TYPE_FLOAT, {.dbl = 0.18}, 0.0, 10.0, DYNAMIC },
> +    { "max_boost", "Tone-mapping maximum boost", OFFSET(max_boost),
> AV_OPT_TYPE_FLOAT, {.dbl = 1.0}, 1.0, 10.0, DYNAMIC },
> +    { "gamut_warning", "Highlight out-of-gamut colors",
> OFFSET(gamut_warning), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DYNAMIC },
> +    { "gamut_clipping", "Enable colorimetric gamut clipping",
> OFFSET(gamut_clipping), AV_OPT_TYPE_BOOL, {.i64 = 1}, 0, 1, DYNAMIC },
> +
> +    { "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, {.i64 = 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_3dlut", "Force the use of a full 3DLUT",
> OFFSET(force_3dlut), 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 = &ff_vk_filter_config_input,
> +    },
> +};
> +
> +static const AVFilterPad libplacebo_outputs[] = {
> +    {
> +        .name         = "default",
> +        .type         = AVMEDIA_TYPE_VIDEO,
> +        .config_props = &libplacebo_config_output,
> +    },
> +};
> +
> +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_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
> +    .priv_class     = &libplacebo_class,
> +    .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
> +};
> --
> 2.33.1
>
>
>
Hello.

Were you able to build FFmpeg with this filter enabled?
So far, building this package does not generate any pkgconfig file in the
configured prefix, and FFmpeg's ./configure cannot detect it.
I filed the issue on libplacebo's project page, with more details:
https://github.com/haasn/libplacebo/issues/110

Warm regards,

Dennis.


More information about the ffmpeg-devel mailing list