1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2025-08-10 06:10:52 +02:00

avfilter/interlace_vulkan: add interlace_vulkan filter

This is a Vulkan-accelerated version of the existing interlace filter.
This commit is contained in:
Niklas Haas
2025-02-17 15:46:09 +01:00
parent 0e7c2a6287
commit 4dc2ae69e7
5 changed files with 317 additions and 1 deletions

1
configure vendored
View File

@@ -3920,6 +3920,7 @@ iccdetect_filter_deps="lcms2"
iccgen_filter_deps="lcms2" iccgen_filter_deps="lcms2"
identity_filter_select="scene_sad" identity_filter_select="scene_sad"
interlace_filter_deps="gpl" interlace_filter_deps="gpl"
interlace_vulkan_filter_deps="vulkan spirv_compiler"
kerndeint_filter_deps="gpl" kerndeint_filter_deps="gpl"
ladspa_filter_deps="ladspa libdl" ladspa_filter_deps="ladspa libdl"
lcevc_filter_deps="liblcevc_dec" lcevc_filter_deps="liblcevc_dec"

View File

@@ -16041,7 +16041,7 @@ If 0, plane will remain unchanged.
This filter supports the all above options as @ref{commands}. This filter supports the all above options as @ref{commands}.
@section interlace @section interlace, interlace_vulkan
Simple interlacing filter from progressive contents. This interleaves upper (or Simple interlacing filter from progressive contents. This interleaves upper (or
lower) lines from odd frames with lower (or upper) lines from even frames, lower) lines from odd frames with lower (or upper) lines from even frames,

View File

@@ -358,6 +358,7 @@ OBJS-$(CONFIG_IDET_FILTER) += vf_idet.o
OBJS-$(CONFIG_IL_FILTER) += vf_il.o OBJS-$(CONFIG_IL_FILTER) += vf_il.o
OBJS-$(CONFIG_INFLATE_FILTER) += vf_neighbor.o OBJS-$(CONFIG_INFLATE_FILTER) += vf_neighbor.o
OBJS-$(CONFIG_INTERLACE_FILTER) += vf_tinterlace.o OBJS-$(CONFIG_INTERLACE_FILTER) += vf_tinterlace.o
OBJS-$(CONFIG_INTERLACE_VULKAN_FILTER) += vf_interlace_vulkan.o vulkan.o vulkan_filter.o
OBJS-$(CONFIG_INTERLEAVE_FILTER) += f_interleave.o OBJS-$(CONFIG_INTERLEAVE_FILTER) += f_interleave.o
OBJS-$(CONFIG_KERNDEINT_FILTER) += vf_kerndeint.o OBJS-$(CONFIG_KERNDEINT_FILTER) += vf_kerndeint.o
OBJS-$(CONFIG_KIRSCH_FILTER) += vf_convolution.o OBJS-$(CONFIG_KIRSCH_FILTER) += vf_convolution.o

View File

@@ -333,6 +333,7 @@ extern const FFFilter ff_vf_idet;
extern const FFFilter ff_vf_il; extern const FFFilter ff_vf_il;
extern const FFFilter ff_vf_inflate; extern const FFFilter ff_vf_inflate;
extern const FFFilter ff_vf_interlace; extern const FFFilter ff_vf_interlace;
extern const FFFilter ff_vf_interlace_vulkan;
extern const FFFilter ff_vf_interleave; extern const FFFilter ff_vf_interleave;
extern const FFFilter ff_vf_kerndeint; extern const FFFilter ff_vf_kerndeint;
extern const FFFilter ff_vf_kirsch; extern const FFFilter ff_vf_kirsch;

View File

@@ -0,0 +1,313 @@
/*
* Copyright 2025 (c) Niklas Haas
*
* 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/vulkan_spirv.h"
#include "libavutil/opt.h"
#include "vulkan_filter.h"
#include "tinterlace.h"
#include "filters.h"
#include "video.h"
typedef struct InterlaceVulkanContext {
FFVulkanContext vkctx;
int initialized;
FFVkExecPool e;
AVVulkanDeviceQueueFamily *qf;
VkSampler sampler;
FFVulkanShader shd;
int mode;
int lowpass;
AVFrame *cur; /* first frame in pair */
} InterlaceVulkanContext;
static const char lowpass_off[] = {
C(0, vec4 get_line(sampler2D tex, const vec2 pos) )
C(0, { )
C(1, return texture(tex, pos); )
C(0, } )
};
static const char lowpass_lin[] = {
C(0, vec4 get_line(sampler2D tex, const vec2 pos) )
C(0, { )
C(1, return 0.50 * texture(tex, pos) + )
C(1, 0.25 * texture(tex, pos - ivec2(0, 1)) + )
C(1, 0.25 * texture(tex, pos + ivec2(0, 1)); )
C(0, } )
};
static const char lowpass_complex[] = {
C(0, vec4 get_line(sampler2D tex, const vec2 pos) )
C(0, { )
C(1, return 0.75 * texture(tex, pos) + )
C(1, 0.25 * texture(tex, pos - ivec2(0, 1)) + )
C(1, 0.25 * texture(tex, pos + ivec2(0, 1)) + )
C(1, -0.125 * texture(tex, pos - ivec2(0, 2)) + )
C(1, -0.125 * texture(tex, pos + ivec2(0, 2)); )
C(0, } )
};
static av_cold int init_filter(AVFilterContext *ctx)
{
int err;
uint8_t *spv_data;
size_t spv_len;
void *spv_opaque = NULL;
InterlaceVulkanContext *s = ctx->priv;
FFVulkanContext *vkctx = &s->vkctx;
const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
FFVulkanShader *shd;
FFVkSPIRVCompiler *spv;
FFVulkanDescriptorSetBinding *desc;
spv = ff_vk_spirv_init();
if (!spv) {
av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
return AVERROR_EXTERNAL;
}
s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
if (!s->qf) {
av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
err = AVERROR(ENOTSUP);
goto fail;
}
RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
RET(ff_vk_init_sampler(vkctx, &s->sampler, 1,
s->lowpass == VLPF_OFF ? VK_FILTER_NEAREST
: VK_FILTER_LINEAR));
RET(ff_vk_shader_init(vkctx, &s->shd, "interlace",
VK_SHADER_STAGE_COMPUTE_BIT,
NULL, 0,
32, 32, 1,
0));
shd = &s->shd;
desc = (FFVulkanDescriptorSetBinding []) {
{
.name = "top_field",
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.dimensions = 2,
.elems = planes,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.samplers = DUP_SAMPLER(s->sampler),
},
{
.name = "bot_field",
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
.dimensions = 2,
.elems = planes,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
.samplers = DUP_SAMPLER(s->sampler),
},
{
.name = "output_img",
.type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
.mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format, FF_VK_REP_FLOAT),
.mem_quali = "writeonly",
.dimensions = 2,
.elems = planes,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
},
};
RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 3, 0, 0));
switch (s->lowpass) {
case VLPF_OFF:
GLSLD(lowpass_off);
break;
case VLPF_LIN:
GLSLD(lowpass_lin);
break;
case VLPF_CMP:
GLSLD(lowpass_complex);
break;
}
GLSLC(0, void main() );
GLSLC(0, { );
GLSLC(1, vec4 res; );
GLSLC(1, ivec2 size; );
GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
GLSLC(1, const vec2 ipos = pos + vec2(0.5); );
for (int i = 0; i < planes; i++) {
GLSLC(0, );
GLSLF(1, size = imageSize(output_img[%i]); ,i);
GLSLC(1, if (!IS_WITHIN(pos, size)) );
GLSLC(2, return; );
GLSLC(1, if (pos.y %% 2 == 0) );
GLSLF(1, res = get_line(top_field[%i], ipos); ,i);
GLSLC(1, else );
GLSLF(1, res = get_line(bot_field[%i], ipos); ,i);
GLSLF(1, imageStore(output_img[%i], pos, res); ,i);
}
GLSLC(0, } );
RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main",
&spv_opaque));
RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
s->initialized = 1;
fail:
if (spv_opaque)
spv->free_shader(spv, &spv_opaque);
if (spv)
spv->uninit(&spv);
return err;
}
static int interlace_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
{
int err;
AVFrame *out = NULL, *input_top, *input_bot;
AVFilterContext *ctx = link->dst;
InterlaceVulkanContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
if (!s->initialized)
RET(init_filter(ctx));
/* Need both frames to filter */
if (!s->cur) {
s->cur = in;
return 0;
}
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
err = AVERROR(ENOMEM);
goto fail;
}
if (s->mode == MODE_TFF) {
input_top = s->cur;
input_bot = in;
} else {
input_top = in;
input_bot = s->cur;
}
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd,
out, (AVFrame *[]){ input_top, input_bot }, 2,
s->sampler, NULL, 0));
err = av_frame_copy_props(out, s->cur);
if (err < 0)
goto fail;
out->flags |= AV_FRAME_FLAG_INTERLACED;
if (s->mode == MODE_TFF)
out->flags |= AV_FRAME_FLAG_TOP_FIELD_FIRST;
av_frame_free(&s->cur);
av_frame_free(&in);
return ff_filter_frame(outlink, out);
fail:
av_frame_free(&s->cur);
av_frame_free(&in);
av_frame_free(&out);
return err;
}
static void interlace_vulkan_uninit(AVFilterContext *avctx)
{
InterlaceVulkanContext *s = avctx->priv;
FFVulkanContext *vkctx = &s->vkctx;
FFVulkanFunctions *vk = &vkctx->vkfn;
av_frame_free(&s->cur);
ff_vk_exec_pool_free(vkctx, &s->e);
ff_vk_shader_free(vkctx, &s->shd);
if (s->sampler)
vk->DestroySampler(vkctx->hwctx->act_dev, s->sampler,
vkctx->hwctx->alloc);
ff_vk_uninit(&s->vkctx);
s->initialized = 0;
}
static int config_out_props(AVFilterLink *outlink)
{
FilterLink *ol = ff_filter_link(outlink);
ol->frame_rate = av_mul_q(ol->frame_rate, av_make_q(1, 2));
return ff_vk_filter_config_output(outlink);
}
#define OFFSET(x) offsetof(InterlaceVulkanContext, x)
#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
static const AVOption interlace_vulkan_options[] = {
{ "scan", "scanning mode", OFFSET(mode), AV_OPT_TYPE_INT, {.i64 = MODE_TFF}, 0, 1, FLAGS, .unit = "mode"},
{ "tff", "top field first", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_TFF}, INT_MIN, INT_MAX, FLAGS, .unit = "mode"},
{ "bff", "bottom field first", 0, AV_OPT_TYPE_CONST, {.i64 = MODE_BFF}, INT_MIN, INT_MAX, FLAGS, .unit = "mode"},
{ "lowpass", "set vertical low-pass filter", OFFSET(lowpass), AV_OPT_TYPE_INT, {.i64 = VLPF_LIN}, 0, 2, FLAGS, .unit = "lowpass" },
{ "off", "disable vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = VLPF_OFF}, INT_MIN, INT_MAX, FLAGS, .unit = "lowpass" },
{ "linear", "linear vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = VLPF_LIN}, INT_MIN, INT_MAX, FLAGS, .unit = "lowpass" },
{ "complex", "complex vertical low-pass filter", 0, AV_OPT_TYPE_CONST, {.i64 = VLPF_CMP}, INT_MIN, INT_MAX, FLAGS, .unit = "lowpass" },
{ NULL },
};
AVFILTER_DEFINE_CLASS(interlace_vulkan);
static const AVFilterPad interlace_vulkan_inputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.filter_frame = &interlace_vulkan_filter_frame,
.config_props = &ff_vk_filter_config_input,
},
};
static const AVFilterPad interlace_vulkan_outputs[] = {
{
.name = "default",
.type = AVMEDIA_TYPE_VIDEO,
.config_props = &config_out_props,
},
};
const FFFilter ff_vf_interlace_vulkan = {
.p.name = "interlace_vulkan",
.p.description = NULL_IF_CONFIG_SMALL("Convert progressive video into interlaced."),
.p.priv_class = &interlace_vulkan_class,
.p.flags = AVFILTER_FLAG_HWDEVICE,
.priv_size = sizeof(InterlaceVulkanContext),
.init = &ff_vk_filter_init,
.uninit = &interlace_vulkan_uninit,
FILTER_INPUTS(interlace_vulkan_inputs),
FILTER_OUTPUTS(interlace_vulkan_outputs),
FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN),
.flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE,
};