From patchwork Wed Nov 25 11:50:33 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 24030 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id C268244AD38 for ; Wed, 25 Nov 2020 13:50:47 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id AABB968BC58; Wed, 25 Nov 2020 13:50:47 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from haasn.xyz (haasn.xyz [78.46.187.166]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id D185D68BC0E for ; Wed, 25 Nov 2020 13:50:41 +0200 (EET) Received: from haasn.xyz (unknown [10.30.0.2]) by haasn.xyz (Postfix) with ESMTP id 83F8E401FD; Wed, 25 Nov 2020 12:50:41 +0100 (CET) From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Wed, 25 Nov 2020 12:50:33 +0100 Message-Id: <20201125115034.75890-1-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.27.0 MIME-Version: 1.0 Subject: [FFmpeg-devel] [PATCH 1/2] hwcontext_vulkan: optionally enable more functionality X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Niklas Haas These two extensions and two features are both optionally used by libplacebo to speed up rendering, so it makes sense for libavutil to automatically enable them as well. --- libavutil/hwcontext_vulkan.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/libavutil/hwcontext_vulkan.c b/libavutil/hwcontext_vulkan.c index a98ea916e7..3fdc09848f 100644 --- a/libavutil/hwcontext_vulkan.c +++ b/libavutil/hwcontext_vulkan.c @@ -210,6 +210,8 @@ enum VulkanExtensions { EXT_EXTERNAL_FD_MEMORY = 1ULL << 2, /* VK_KHR_external_memory_fd */ EXT_EXTERNAL_FD_SEM = 1ULL << 3, /* VK_KHR_external_semaphore_fd */ EXT_EXTERNAL_HOST_MEMORY = 1ULL << 4, /* VK_EXT_external_memory_host */ + EXT_PUSH_DESCRIPTORS = 1ULL << 5, /* VK_KHR_push_descriptor */ + EXT_HOST_QUERY_RESET = 1ULL << 6, /* VK_EXT_host_query_reset */ EXT_NO_FLAG = 1ULL << 63, }; @@ -229,6 +231,8 @@ static const VulkanOptExtension optional_device_exts[] = { { VK_EXT_IMAGE_DRM_FORMAT_MODIFIER_EXTENSION_NAME, EXT_DRM_MODIFIER_FLAGS, }, { VK_KHR_EXTERNAL_SEMAPHORE_FD_EXTENSION_NAME, EXT_EXTERNAL_FD_SEM, }, { VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME, EXT_EXTERNAL_HOST_MEMORY, }, + { VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, EXT_PUSH_DESCRIPTORS, }, + { VK_EXT_HOST_QUERY_RESET_EXTENSION_NAME, EXT_HOST_QUERY_RESET, }, }; /* Converts return values to strings */ @@ -1003,6 +1007,8 @@ static int vulkan_device_create_internal(AVHWDeviceContext *ctx, vkGetPhysicalDeviceFeatures(hwctx->phys_dev, &dev_features); #define COPY_FEATURE(DST, NAME) (DST).features.NAME = dev_features.NAME; COPY_FEATURE(hwctx->device_features, shaderImageGatherExtended) + COPY_FEATURE(hwctx->device_features, shaderStorageImageReadWithoutFormat) + COPY_FEATURE(hwctx->device_features, shaderStorageImageWriteWithoutFormat) COPY_FEATURE(hwctx->device_features, fragmentStoresAndAtomics) COPY_FEATURE(hwctx->device_features, vertexPipelineStoresAndAtomics) COPY_FEATURE(hwctx->device_features, shaderInt64) From patchwork Wed Nov 25 11:50:35 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Niklas Haas X-Patchwork-Id: 24031 Return-Path: X-Original-To: patchwork@ffaux-bg.ffmpeg.org Delivered-To: patchwork@ffaux-bg.ffmpeg.org Received: from ffbox0-bg.mplayerhq.hu (ffbox0-bg.ffmpeg.org [79.124.17.100]) by ffaux.localdomain (Postfix) with ESMTP id AABA144AD38 for ; Wed, 25 Nov 2020 13:51:01 +0200 (EET) Received: from [127.0.1.1] (localhost [127.0.0.1]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 8B5E168BCD9; Wed, 25 Nov 2020 13:51:01 +0200 (EET) X-Original-To: ffmpeg-devel@ffmpeg.org Delivered-To: ffmpeg-devel@ffmpeg.org Received: from haasn.xyz (haasn.xyz [78.46.187.166]) by ffbox0-bg.mplayerhq.hu (Postfix) with ESMTP id 5772868BCD8 for ; Wed, 25 Nov 2020 13:50:55 +0200 (EET) Received: from haasn.xyz (unknown [10.30.0.2]) by haasn.xyz (Postfix) with ESMTP id 1FCE2401FD; Wed, 25 Nov 2020 12:50:55 +0100 (CET) From: Niklas Haas To: ffmpeg-devel@ffmpeg.org Date: Wed, 25 Nov 2020 12:50:35 +0100 Message-Id: <20201125115034.75890-2-ffmpeg@haasn.xyz> X-Mailer: git-send-email 2.27.0 In-Reply-To: <20201125115034.75890-1-ffmpeg@haasn.xyz> References: <20201125115034.75890-1-ffmpeg@haasn.xyz> MIME-Version: 1.0 Subject: [FFmpeg-devel] [RFC] [PATCH 2/2] lavfi: add a libplacebo filter X-BeenThere: ffmpeg-devel@ffmpeg.org X-Mailman-Version: 2.1.20 Precedence: list List-Id: FFmpeg development discussions and patches List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: FFmpeg development discussions and patches Cc: Niklas Haas Errors-To: ffmpeg-devel-bounces@ffmpeg.org Sender: "ffmpeg-devel" From: Niklas Haas This filter conceptually maps the libplacebo `pl_renderer` API into libavfilter, which is a high-level image rendering API designed to produce RGB output. As such, there's no way to avoid e.g. chroma interpolation with this filter. 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 re-approach this by providing separate filters for individual tasks that don't require full chroma interpolation (e.g. vf_deband_vulkan.c or vf_av1grain_vulkan.c). --- configure | 6 + libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_libplacebo.c | 651 ++++++++++++++++++++++++++++++++++++ 4 files changed, 659 insertions(+) create mode 100644 libavfilter/vf_libplacebo.c diff --git a/configure b/configure index 502e229806..36bef88969 100755 --- a/configure +++ b/configure @@ -1794,6 +1794,7 @@ EXTERNAL_LIBRARY_LIST=" libopenmpt libopenvino libopus + libplacebo libpulse librabbitmq librav1e @@ -3571,6 +3572,7 @@ interlace_filter_deps="gpl" kerndeint_filter_deps="gpl" ladspa_filter_deps="ladspa libdl" lensfun_filter_deps="liblensfun version3" +libplacebo_filter_deps="vulkan libplacebo" lv2_filter_deps="lv2" mcdeint_filter_deps="avcodec gpl" movie_filter_deps="avcodec avformat" @@ -6400,6 +6402,10 @@ enabled libopus && { require_pkg_config libopus opus opus_multistream.h opus_multistream_surround_encoder_create } } +enabled libplacebo && { + require_pkg_config libplacebo "libplacebo >= 2.95.0" libplacebo/context.h pl_context_create + enabled vulkan && require_pkg_config libplacebo_vulkan libplacebo 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.1.0" rav1e.h rav1e_context_new diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 794a55ac3d..4260bb5713 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -295,6 +295,7 @@ OBJS-$(CONFIG_KERNDEINT_FILTER) += vf_kerndeint.o OBJS-$(CONFIG_LAGFUN_FILTER) += vf_lagfun.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_LIMITER_FILTER) += vf_limiter.o OBJS-$(CONFIG_LOOP_FILTER) += f_loop.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index fbfd8989c6..79228508aa 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -280,6 +280,7 @@ extern AVFilter ff_vf_kerndeint; extern AVFilter ff_vf_lagfun; extern AVFilter ff_vf_lenscorrection; extern AVFilter ff_vf_lensfun; +extern AVFilter ff_vf_libplacebo; extern AVFilter ff_vf_libvmaf; extern AVFilter ff_vf_limiter; extern AVFilter ff_vf_loop; diff --git a/libavfilter/vf_libplacebo.c b/libavfilter/vf_libplacebo.c new file mode 100644 index 0000000000..92a37a5c85 --- /dev/null +++ b/libavfilter/vf_libplacebo.c @@ -0,0 +1,651 @@ +/* + * 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/opt.h" +#include "internal.h" +#include "vulkan.h" +#include "scale_eval.h" + +#include +#include +#include + +typedef struct LibplaceboContext { + /* lavfi vulkan */ + VulkanFilterContext vkctx; + int initialized; + + /* libplacebo */ + struct pl_context *pl_ctx; + const struct pl_vulkan *pl_vulkan; + struct pl_renderer *pl_renderer; + + /* settings */ + char *out_format_string; + char *w_expr; + char *h_expr; + 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 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 */ + const struct pl_hook *hook; + void *shader; + int shader_len; +} 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_INFO; break; + case PL_LOG_DEBUG: av_lev = AV_LOG_VERBOSE; break; + case PL_LOG_TRACE: av_lev = AV_LOG_TRACE; break; + default: return; + } + + av_log(log_ctx, av_lev, "%s\n", msg); +} + +static int libplacebo_init(AVFilterContext *avctx) +{ + int err; + LibplaceboContext *s = avctx->priv; + + RET(ff_vk_filter_init(avctx)); + + s->pl_ctx = pl_context_create(PL_API_VER, &(struct pl_context_params) { + .log_cb = pl_av_log, + .log_priv = s, + .log_level = PL_LOG_DEBUG, + }); + + if (!s->pl_ctx) + return AVERROR(ENOMEM); + + /* Note: s->pl_vulkan etc. are initialized later, when hwctx is known */ + return 0; + +fail: + return err; +} + +static av_cold int vulkan_init(AVFilterContext *ctx) +{ + LibplaceboContext *s = ctx->priv; + const struct pl_gpu *gpu; + + /* Init the libplacebo context from the libavfilter one */ + s->pl_vulkan = pl_vulkan_import(s->pl_ctx, &(struct pl_vulkan_import_params) { + .instance = s->vkctx.hwctx->inst, + .phys_device = s->vkctx.hwctx->phys_dev, + .device = s->vkctx.hwctx->act_dev, + .extensions = s->vkctx.hwctx->enabled_dev_extensions, + .num_extensions = s->vkctx.hwctx->nb_enabled_dev_extensions, + .features = &s->vkctx.hwctx->device_features, + .queue_graphics = { + .index = s->vkctx.hwctx->queue_family_index, + .count = s->vkctx.hwctx->nb_graphics_queues, + }, + .queue_compute = { + .index = s->vkctx.hwctx->queue_family_comp_index, + .count = s->vkctx.hwctx->nb_comp_queues, + }, + .queue_transfer = { + .index = s->vkctx.hwctx->queue_family_tx_index, + .count = s->vkctx.hwctx->nb_tx_queues, + }, + /* This is the highest version created by hwcontext_vulkan.c */ + .max_api_version = VK_API_VERSION_1_1, + }); + + if (!s->pl_vulkan) { + av_log(s, AV_LOG_ERROR, "Error importing vulkan device to libplacebo!\n"); + return AVERROR_EXTERNAL; + } + + /* Create the renderer */ + gpu = s->pl_vulkan->gpu; + s->pl_renderer = pl_renderer_create(s->pl_ctx, gpu); + + /* Parse the user shader, if requested */ + if (s->shader_len) { + s->hook = pl_mpv_user_shader_parse(gpu, s->shader, s->shader_len); + if (!s->hook) { + av_log(s, AV_LOG_ERROR, "Failed parsing custom shader!\n"); + return AVERROR(EINVAL); + } + } + + s->initialized = 1; + return 0; +} + +static void libplacebo_uninit(AVFilterContext *avctx) +{ + LibplaceboContext *s = avctx->priv; + + pl_mpv_user_shader_destroy(&s->hook); + pl_renderer_destroy(&s->pl_renderer); + pl_vulkan_destroy(&s->pl_vulkan); + pl_context_destroy(&s->pl_ctx); + ff_vk_filter_uninit(avctx); + + s->initialized = 0; +} + +#define DEFAULT_USAGE_FLAGS (VK_IMAGE_USAGE_SAMPLED_BIT | \ + VK_IMAGE_USAGE_STORAGE_BIT | \ + VK_IMAGE_USAGE_TRANSFER_SRC_BIT | \ + VK_IMAGE_USAGE_TRANSFER_DST_BIT) + +static int wrap_vkframe(const struct pl_gpu *gpu, const AVFrame *frame, + int plane, const struct pl_tex **tex) +{ + const AVVkFrame *vk_frame = (AVVkFrame *) frame->data[0]; + const AVHWFramesContext *hwfc = (AVHWFramesContext *) frame->hw_frames_ctx->data; + const AVVulkanFramesContext *vkfc = hwfc->hwctx; + const VkFormat *vk_fmt = av_vkfmt_from_pixfmt(hwfc->sw_format); + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(hwfc->sw_format); + const bool chroma = plane == 1 || plane == 2; + const int shift_w = chroma ? desc->log2_chroma_w : 0; + const int shift_h = chroma ? desc->log2_chroma_h : 0; + + *tex = pl_vulkan_wrap(gpu, &(struct pl_vulkan_wrap_params) { + .image = vk_frame->img[plane], + .width = frame->width >> shift_w, + .height = frame->height >> shift_h, + .format = vk_fmt[plane], + .usage = DEFAULT_USAGE_FLAGS | vkfc->usage, + .sample_mode = PL_TEX_SAMPLE_LINEAR, + .address_mode = PL_TEX_ADDRESS_CLAMP, + }); + + if (!*tex) + return AVERROR(ENOMEM); + + pl_vulkan_release(gpu, *tex, vk_frame->layout[plane], + vk_frame->access[plane], + vk_frame->sem[plane]); + + return 0; +} + +static int unwrap_vkframe(const struct pl_gpu *gpu, AVFrame *frame, + int plane, const struct pl_tex **tex) +{ + AVVkFrame *vk_frame = (AVVkFrame *) frame->data[0]; + + /* FIXME: Re-using vk_frame->sem like this is undefined behaviour, but + * there's currently little way around it because the design of AVVkFrame + * effectively forces undefined behaviour on the user. Change ASAP! */ + bool ok = pl_vulkan_hold_raw(gpu, *tex, &vk_frame->layout[plane], + &vk_frame->access[plane], + vk_frame->sem[plane]); + + if (!ok) + return AVERROR_EXTERNAL; + + pl_tex_destroy(gpu, tex); + + return 0; +} + +static int find_scaler(AVFilterContext *avctx, + const struct pl_filter_config **opt, + const char *name) +{ + const struct pl_named_filter_config *preset; + if (!strcmp(name, "help")) { + av_log(avctx, AV_LOG_INFO, "Available scaler presets:\n"); + for (preset = pl_named_filters; preset->name; preset++) + av_log(avctx, AV_LOG_INFO, " %s\n", preset->name); + return AVERROR_EXIT; + } + + preset = pl_find_named_filter(name); + if (!preset) { + av_log(avctx, AV_LOG_ERROR, "No such scaler preset '%s'.\n", name); + return AVERROR(EINVAL); + } + + *opt = preset->filter; + return 0; +} + +static int process_frames(AVFilterContext *avctx, AVFrame *out, AVFrame *in) +{ + int err = 0; + LibplaceboContext *s = avctx->priv; + const struct pl_gpu *gpu = s->pl_vulkan->gpu; + struct pl_render_params params; + + struct pl_image image = {0}; + struct pl_render_target target = {0}; + pl_image_from_avframe(&image, in); + pl_target_from_avframe(&target, out); + + /* Update render params */ + params = (struct pl_render_params) { + .lut_entries = s->lut_entries, + .antiringing_strength = s->antiringing, + + .deband_params = !s->deband ? NULL : &(struct 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 : &(struct pl_peak_detect_params) { + .smoothing_period = s->smoothing, + .scene_threshold_low = s->scene_low, + .scene_threshold_high = s->scene_high, + .overshoot_margin = s->overshoot, + }, + + .color_map_params = &(struct 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 : &(struct pl_dither_params) { + .method = s->dithering, + .lut_size = s->dither_lut_size, + .temporal = s->dither_temporal, + }, + + .cone_params = !s->cones ? NULL : &(struct pl_cone_params) { + .cones = s->cones, + .strength = s->cone_str, + }, + + .hooks = &s->hook, + .num_hooks = s->hook ? 1 : 0, + + .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, ¶ms.upscaler, s->upscaler)); + RET(find_scaler(avctx, ¶ms.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(gpu, in, i, &image.planes[i].texture)); + RET(wrap_vkframe(gpu, out, 0, &target.fbo)); + + /* Images mapped, perform the actual rendering */ + if (pl_render_target_partial(&target)) + pl_tex_clear(gpu, target.fbo, (float[4]) { 0.0, 0.0, 0.0, 1.0 }); + pl_render_image(s->pl_renderer, &image, &target, ¶ms); + + for (int i = 0; i < image.num_planes; i++) + RET(unwrap_vkframe(gpu, in, i, &image.planes[i].texture)); + RET(unwrap_vkframe(gpu, out, 0, &target.fbo)); + + /* Flush the command queues for performance */ + pl_gpu_flush(gpu); + return 0; + +fail: + for (int i = 0; i < 4; i++) + pl_tex_destroy(gpu, &image.planes[i].texture); + pl_tex_destroy(gpu, &target.fbo); + + return err; +} + +static int libplacebo_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(vulkan_init(ctx)); + + err = av_frame_copy_props(out, in); + if (err < 0) + goto fail; + + out->width = outlink->w; + out->height = outlink->h; + out->colorspace = AVCOL_SPC_RGB; + out->chroma_location = 0; + + 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)); + + 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; + + RET(ff_scale_eval_dimensions(s, s->w_expr, s->h_expr, inlink, outlink, + &s->vkctx.output_width, + &s->vkctx.output_height)); + + 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); + } + + /* Note: Technically, we could also support packed 4:4:4 YUV/XYZ + * output, but these don't map to hwcontext_vulkan */ + if (!ff_vk_mt_is_np_rgb(s->vkctx.output_format)) { + av_log(avctx, AV_LOG_ERROR, "Output format must be non-planar RGB.\n"); + return AVERROR(EINVAL); + } + } else { + /* Default to 8-bit or 16-bit RGBA depending on the input depth */ + const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.input_format); + const bool highp = desc->comp[0].depth > 8; + s->vkctx.output_format = highp ? AV_PIX_FMT_RGBA64 : AV_PIX_FMT_RGBA; + } + + 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 (software format of hardware frames)", OFFSET(out_format_string), AV_OPT_TYPE_STRING, .flags = STATIC }, + + {"range", "select color range", OFFSET(color_range), AV_OPT_TYPE_INT, {.i64=AVCOL_RANGE_JPEG},-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"}, + {"log100", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LOG}, INT_MIN, INT_MAX, STATIC, "color_trc"}, + {"log316", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_LOG_SQRT}, 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"}, + {"smpte428", NULL, 0, AV_OPT_TYPE_CONST, {.i64=AVCOL_TRC_SMPTE428}, 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 }, + { "downsacler", "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 }, + + { "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 }, + { "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}, .flags = DYNAMIC }, + { "desaturation_strength", "Desaturation strength", OFFSET(desat_str), AV_OPT_TYPE_FLOAT, {.dbl = 0.75}, 0.0, 1.0, DYNAMIC }, + { "desaturation_exponent", "Desaturation exponent", OFFSET(desat_exp), AV_OPT_TYPE_FLOAT, {.dbl = 1.5}, 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", "Custom user shader (.hook format)", OFFSET(shader), 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 = &libplacebo_filter_frame, + .config_props = &ff_vk_filter_config_input, + }, + { NULL } +}; + +static const AVFilterPad libplacebo_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = &libplacebo_config_output, + }, + { NULL } +}; + +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, + .query_formats = &ff_vk_filter_query_formats, + .process_command = &ff_filter_process_command, + .inputs = libplacebo_inputs, + .outputs = libplacebo_outputs, + .priv_class = &libplacebo_class, + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, +};