* [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan @ 2023-05-30 1:33 Marvin Scholz 2023-05-30 1:58 ` [FFmpeg-devel] [PATCH v2] " Marvin Scholz 2023-05-30 8:05 ` [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Paul B Mahol 0 siblings, 2 replies; 8+ messages in thread From: Marvin Scholz @ 2023-05-30 1:33 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz This is an initial version of vf_xfade_vulkan based on vf_xfade_opencl, for now only fade and wipeleft transitions are supported. --- libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_xfade_vulkan.c | 441 ++++++++++++++++++++++++++++++++++ 3 files changed, 443 insertions(+) create mode 100644 libavfilter/vf_xfade_vulkan.c diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 18935b1616..ff149a3733 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -552,6 +552,7 @@ OBJS-$(CONFIG_XBR_FILTER) += vf_xbr.o OBJS-$(CONFIG_XCORRELATE_FILTER) += vf_convolve.o framesync.o OBJS-$(CONFIG_XFADE_FILTER) += vf_xfade.o OBJS-$(CONFIG_XFADE_OPENCL_FILTER) += vf_xfade_opencl.o opencl.o opencl/xfade.o +OBJS-$(CONFIG_XFADE_VULKAN_FILTER) += vf_xfade_vulkan.o vulkan.o vulkan_filter.o OBJS-$(CONFIG_XMEDIAN_FILTER) += vf_xmedian.o framesync.o OBJS-$(CONFIG_XSTACK_FILTER) += vf_stack.o framesync.o OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o yadif_common.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f1f781101b..6593e4eb83 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -519,6 +519,7 @@ extern const AVFilter ff_vf_xbr; extern const AVFilter ff_vf_xcorrelate; extern const AVFilter ff_vf_xfade; extern const AVFilter ff_vf_xfade_opencl; +extern const AVFilter ff_vf_xfade_vulkan; extern const AVFilter ff_vf_xmedian; extern const AVFilter ff_vf_xstack; extern const AVFilter ff_vf_yadif; diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c new file mode 100644 index 0000000000..4a47c68fb4 --- /dev/null +++ b/libavfilter/vf_xfade_vulkan.c @@ -0,0 +1,441 @@ +/* + * 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/random_seed.h" +#include "libavutil/opt.h" +#include "vulkan_filter.h" +#include "vulkan_spirv.h" +#include "filters.h" +#include "internal.h" + +#define IN_A 0 +#define IN_B 1 + +enum XFadeTransitions { + FADE, + WIPELEFT, + NB_TRANSITIONS, +}; + +typedef struct XFadeParameters { + float progress; +} XFadeParameters; + +typedef struct XFadeVulkanContext { + FFVulkanContext vkctx; + + int transition; + int64_t duration; + int64_t offset; + + int initialized; + FFVulkanPipeline pl; + FFVkExecPool e; + FFVkQueueFamilyCtx qf; + FFVkSPIRVShader shd; + VkSampler sampler; + + int64_t duration_pts; + int64_t offset_pts; + int64_t first_pts; + int64_t last_pts; + int64_t pts; + int xfade_is_over; + int need_second; + int eof[2]; + AVFrame *xf[2]; +} XFadeVulkanContext; + +static const char transition_fade[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, mix(b, a, progress)); ) + C(0, } ) +}; + +static const char transition_wipeleft[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.x * progress); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.x > s ? b : a); ) + C(0, } ) +}; + +static av_cold int init_filter(AVFilterContext *avctx) +{ + int err = 0; + uint8_t *spv_data; + size_t spv_len; + void *spv_opaque = NULL; + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); + FFVkSPIRVShader *shd = &s->shd; + FFVkSPIRVCompiler *spv; + FFVulkanDescriptorSetBinding *desc; + + spv = ff_vk_spirv_init(); + if (!spv) { + av_log(avctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); + return AVERROR_EXTERNAL; + } + + ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT); + RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL)); + RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST)); + RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute", + VK_SHADER_STAGE_COMPUTE_BIT, 0)); + + ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1); + + desc = (FFVulkanDescriptorSetBinding []) { + { + .name = "a_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "b_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "output_images", + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format), + .mem_quali = "writeonly", + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + + RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, 0)); + + GLSLC(0, layout(push_constant, std430) uniform pushConstants { ); + GLSLC(1, float progress; ); + GLSLC(0, }; ); + + ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters), + VK_SHADER_STAGE_COMPUTE_BIT); + + switch (s->transition) { + case FADE: + GLSLD(transition_fade); + break; + case WIPELEFT: + GLSLD(transition_wipeleft); + break; + default: + err = AVERROR_BUG; + goto fail; + } + + GLSLC(0, void main() ); + GLSLC(0, { ); + GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); + GLSLF(1, int planes = %i; ,planes); + GLSLC(1, for (int i = 0; i < planes; i++) { ); + GLSLC(2, transition(i, pos, progress); ); + GLSLC(1, } ); + GLSLC(0, } ); + + RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main", + &spv_opaque)); + RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main")); + + RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd)); + RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl)); + + s->initialized = 1; + +fail: + if (spv_opaque) + spv->free_shader(spv, &spv_opaque); + if (spv) + spv->uninit(&spv); + + return err; +} + +static int xfade_frame(AVFilterContext *avctx, AVFrame *a, AVFrame *b) +{ + int err; + AVFilterLink *outlink = avctx->outputs[0]; + XFadeVulkanContext *s = avctx->priv; + AVFrame *frame_a = s->xf[IN_A]; + AVFrame *frame_b = s->xf[IN_B]; + float progress; + + AVFrame *output = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!output) { + err = AVERROR(ENOMEM); + goto fail; + } + + if (!s->initialized) { + AVHWFramesContext *a_fc = (AVHWFramesContext*)frame_a->hw_frames_ctx->data; + AVHWFramesContext *b_fc = (AVHWFramesContext*)frame_b->hw_frames_ctx->data; + if (a_fc->sw_format != b_fc->sw_format) { + av_log(avctx, AV_LOG_ERROR, + "Currently the sw format of the first video neede to match the second!\n"); + return AVERROR(EINVAL); + } + RET(init_filter(avctx)); + } + + RET(av_frame_copy_props(output, frame_a)); + output->pts = s->pts; + + progress = av_clipf( + 1.f - ((float)(s->pts - s->first_pts - s->offset_pts) / s->duration_pts), + 0.f, 1.f); + + RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output, + (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler, + &(XFadeParameters){ progress }, sizeof(XFadeParameters))); + + return ff_filter_frame(outlink, output); + +fail: + av_frame_free(&output); + return err; +} + +static int config_props_output(AVFilterLink *outlink) +{ + int err; + AVFilterContext *avctx = outlink->src; + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *inlink0 = avctx->inputs[IN_A]; + AVFilterLink *inlink1 = avctx->inputs[IN_B]; + + if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) { + av_log(avctx, AV_LOG_ERROR, "First input link %s parameters " + "(size %dx%d) do not match the corresponding " + "second input link %s parameters (size %dx%d)\n", + avctx->input_pads[IN_A].name, inlink0->w, inlink0->h, + avctx->input_pads[IN_B].name, inlink1->w, inlink1->h); + return AVERROR(EINVAL); + } + + if (inlink0->time_base.num != inlink1->time_base.num || + inlink0->time_base.den != inlink1->time_base.den) { + av_log(avctx, AV_LOG_ERROR, "First input link %s timebase " + "(%d/%d) do not match the corresponding " + "second input link %s timebase (%d/%d)\n", + avctx->input_pads[0].name, inlink0->time_base.num, inlink0->time_base.den, + avctx->input_pads[1].name, inlink1->time_base.num, inlink1->time_base.den); + return AVERROR(EINVAL); + } + + s->first_pts = s->last_pts = s->pts = AV_NOPTS_VALUE; + + outlink->time_base = inlink0->time_base; + outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio; + outlink->frame_rate = inlink0->frame_rate; + + if (s->duration) + s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, outlink->time_base); + if (s->offset) + s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, outlink->time_base); + + RET(ff_vk_filter_config_output(outlink)); + +fail: + return err; +} + +static int activate(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *outlink = avctx->outputs[0]; + AVFrame *in = NULL; + int ret = 0, status; + int64_t pts; + + FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx); + + if (s->xfade_is_over) { + ret = ff_inlink_consume_frame(avctx->inputs[1], &in); + if (ret < 0) { + return ret; + } else if (ret > 0) { + in->pts = (in->pts - s->last_pts) + s->pts; + return ff_filter_frame(outlink, in); + } else if (ff_inlink_acknowledge_status(avctx->inputs[1], &status, &pts)) { + ff_outlink_set_status(outlink, status, s->pts); + return 0; + } else if (!ret) { + if (ff_outlink_frame_wanted(outlink)) { + ff_inlink_request_frame(avctx->inputs[1]); + return 0; + } + } + } + + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0) { + s->xf[0] = ff_inlink_peek_frame(avctx->inputs[0], 0); + if (s->xf[0]) { + if (s->first_pts == AV_NOPTS_VALUE) { + s->first_pts = s->xf[0]->pts; + } + s->pts = s->xf[0]->pts; + if (s->first_pts + s->offset_pts > s->xf[0]->pts) { + s->xf[0] = NULL; + s->need_second = 0; + ff_inlink_consume_frame(avctx->inputs[0], &in); + return ff_filter_frame(outlink, in); + } + + s->need_second = 1; + } + } + + if (s->xf[0] && ff_inlink_queued_frames(avctx->inputs[1]) > 0) { + ff_inlink_consume_frame(avctx->inputs[0], &s->xf[0]); + ff_inlink_consume_frame(avctx->inputs[1], &s->xf[1]); + + s->last_pts = s->xf[1]->pts; + s->pts = s->xf[0]->pts; + if (s->xf[0]->pts - (s->first_pts + s->offset_pts) > s->duration_pts) + s->xfade_is_over = 1; + ret = xfade_frame(avctx, s->xf[0], s->xf[1]); + av_frame_free(&s->xf[0]); + av_frame_free(&s->xf[1]); + return ret; + } + + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0 && + ff_inlink_queued_frames(avctx->inputs[1]) > 0) { + ff_filter_set_ready(avctx, 100); + return 0; + } + + if (ff_outlink_frame_wanted(outlink)) { + if (!s->eof[0] && ff_outlink_get_status(avctx->inputs[0])) { + s->eof[0] = 1; + s->xfade_is_over = 1; + } + if (!s->eof[1] && ff_outlink_get_status(avctx->inputs[1])) { + s->eof[1] = 1; + } + if (!s->eof[0] && !s->xf[0]) + ff_inlink_request_frame(avctx->inputs[0]); + if (!s->eof[1] && (s->need_second || s->eof[0])) + ff_inlink_request_frame(avctx->inputs[1]); + if (s->eof[0] && s->eof[1] && ( + ff_inlink_queued_frames(avctx->inputs[0]) <= 0 || + ff_inlink_queued_frames(avctx->inputs[1]) <= 0)) + ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE); + return 0; + } + + return FFERROR_NOT_READY; +} + +static av_cold void uninit(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + FFVulkanFunctions *vk = &vkctx->vkfn; + + ff_vk_exec_pool_free(vkctx, &s->e); + ff_vk_pipeline_free(vkctx, &s->pl); + 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 AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) +{ + XFadeVulkanContext *s = inlink->dst->priv; + + return s->xfade_is_over || !s->need_second ? + ff_null_get_video_buffer (inlink, w, h) : + ff_default_get_video_buffer(inlink, w, h); +} + +#define OFFSET(x) offsetof(XFadeVulkanContext, x) +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) + +static const AVOption xfade_vulkan_options[] = { + { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, 0, NB_TRANSITIONS-1, FLAGS, "transition" }, + { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" }, + { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, + { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, + { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(xfade_vulkan); + +static const AVFilterPad xfade_vulkan_inputs[] = { + { + .name = "main", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, + { + .name = "xfade", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, +}; + +static const AVFilterPad xfade_vulkan_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = &config_props_output, + }, +}; + +const AVFilter ff_vf_xfade_vulkan = { + .name = "xfade_vulkan", + .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."), + .priv_size = sizeof(XFadeVulkanContext), + .init = &ff_vk_filter_init, + .uninit = &uninit, + .activate = &activate, + FILTER_INPUTS(xfade_vulkan_inputs), + FILTER_OUTPUTS(xfade_vulkan_outputs), + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN), + .priv_class = &xfade_vulkan_class, + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, + .flags = AVFILTER_FLAG_HWDEVICE, +}; -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v2] libavfilter: add vf_xfade_vulkan 2023-05-30 1:33 [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Marvin Scholz @ 2023-05-30 1:58 ` Marvin Scholz 2023-05-30 12:30 ` Niklas Haas 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz 2023-05-30 8:05 ` [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Paul B Mahol 1 sibling, 2 replies; 8+ messages in thread From: Marvin Scholz @ 2023-05-30 1:58 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz This is an initial version of vf_xfade_vulkan based on vf_xfade_opencl, for now only fade and wipeleft transitions are supported. --- Changes to v1: - Added proper configure _deps to require vulkan and the spirv compiler This should fix the Patchwork build failure. configure | 1 + libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_xfade_vulkan.c | 441 ++++++++++++++++++++++++++++++++++ 4 files changed, 444 insertions(+) create mode 100644 libavfilter/vf_xfade_vulkan.c diff --git a/configure b/configure index 495493aa0e..a7c5897fd8 100755 --- a/configure +++ b/configure @@ -3777,6 +3777,7 @@ scale_vulkan_filter_deps="vulkan spirv_compiler" vpp_qsv_filter_deps="libmfx" vpp_qsv_filter_select="qsvvpp" xfade_opencl_filter_deps="opencl" +xfade_vulkan_filter_deps="vulkan spirv_compiler" yadif_cuda_filter_deps="ffnvcodec" yadif_cuda_filter_deps_any="cuda_nvcc cuda_llvm" yadif_videotoolbox_filter_deps="metal corevideo videotoolbox" diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 18935b1616..ff149a3733 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -552,6 +552,7 @@ OBJS-$(CONFIG_XBR_FILTER) += vf_xbr.o OBJS-$(CONFIG_XCORRELATE_FILTER) += vf_convolve.o framesync.o OBJS-$(CONFIG_XFADE_FILTER) += vf_xfade.o OBJS-$(CONFIG_XFADE_OPENCL_FILTER) += vf_xfade_opencl.o opencl.o opencl/xfade.o +OBJS-$(CONFIG_XFADE_VULKAN_FILTER) += vf_xfade_vulkan.o vulkan.o vulkan_filter.o OBJS-$(CONFIG_XMEDIAN_FILTER) += vf_xmedian.o framesync.o OBJS-$(CONFIG_XSTACK_FILTER) += vf_stack.o framesync.o OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o yadif_common.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f1f781101b..6593e4eb83 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -519,6 +519,7 @@ extern const AVFilter ff_vf_xbr; extern const AVFilter ff_vf_xcorrelate; extern const AVFilter ff_vf_xfade; extern const AVFilter ff_vf_xfade_opencl; +extern const AVFilter ff_vf_xfade_vulkan; extern const AVFilter ff_vf_xmedian; extern const AVFilter ff_vf_xstack; extern const AVFilter ff_vf_yadif; diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c new file mode 100644 index 0000000000..4a47c68fb4 --- /dev/null +++ b/libavfilter/vf_xfade_vulkan.c @@ -0,0 +1,441 @@ +/* + * 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/random_seed.h" +#include "libavutil/opt.h" +#include "vulkan_filter.h" +#include "vulkan_spirv.h" +#include "filters.h" +#include "internal.h" + +#define IN_A 0 +#define IN_B 1 + +enum XFadeTransitions { + FADE, + WIPELEFT, + NB_TRANSITIONS, +}; + +typedef struct XFadeParameters { + float progress; +} XFadeParameters; + +typedef struct XFadeVulkanContext { + FFVulkanContext vkctx; + + int transition; + int64_t duration; + int64_t offset; + + int initialized; + FFVulkanPipeline pl; + FFVkExecPool e; + FFVkQueueFamilyCtx qf; + FFVkSPIRVShader shd; + VkSampler sampler; + + int64_t duration_pts; + int64_t offset_pts; + int64_t first_pts; + int64_t last_pts; + int64_t pts; + int xfade_is_over; + int need_second; + int eof[2]; + AVFrame *xf[2]; +} XFadeVulkanContext; + +static const char transition_fade[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, mix(b, a, progress)); ) + C(0, } ) +}; + +static const char transition_wipeleft[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.x * progress); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.x > s ? b : a); ) + C(0, } ) +}; + +static av_cold int init_filter(AVFilterContext *avctx) +{ + int err = 0; + uint8_t *spv_data; + size_t spv_len; + void *spv_opaque = NULL; + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); + FFVkSPIRVShader *shd = &s->shd; + FFVkSPIRVCompiler *spv; + FFVulkanDescriptorSetBinding *desc; + + spv = ff_vk_spirv_init(); + if (!spv) { + av_log(avctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); + return AVERROR_EXTERNAL; + } + + ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT); + RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL)); + RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST)); + RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute", + VK_SHADER_STAGE_COMPUTE_BIT, 0)); + + ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1); + + desc = (FFVulkanDescriptorSetBinding []) { + { + .name = "a_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "b_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "output_images", + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format), + .mem_quali = "writeonly", + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + + RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, 0)); + + GLSLC(0, layout(push_constant, std430) uniform pushConstants { ); + GLSLC(1, float progress; ); + GLSLC(0, }; ); + + ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters), + VK_SHADER_STAGE_COMPUTE_BIT); + + switch (s->transition) { + case FADE: + GLSLD(transition_fade); + break; + case WIPELEFT: + GLSLD(transition_wipeleft); + break; + default: + err = AVERROR_BUG; + goto fail; + } + + GLSLC(0, void main() ); + GLSLC(0, { ); + GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); + GLSLF(1, int planes = %i; ,planes); + GLSLC(1, for (int i = 0; i < planes; i++) { ); + GLSLC(2, transition(i, pos, progress); ); + GLSLC(1, } ); + GLSLC(0, } ); + + RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main", + &spv_opaque)); + RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main")); + + RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd)); + RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl)); + + s->initialized = 1; + +fail: + if (spv_opaque) + spv->free_shader(spv, &spv_opaque); + if (spv) + spv->uninit(&spv); + + return err; +} + +static int xfade_frame(AVFilterContext *avctx, AVFrame *a, AVFrame *b) +{ + int err; + AVFilterLink *outlink = avctx->outputs[0]; + XFadeVulkanContext *s = avctx->priv; + AVFrame *frame_a = s->xf[IN_A]; + AVFrame *frame_b = s->xf[IN_B]; + float progress; + + AVFrame *output = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!output) { + err = AVERROR(ENOMEM); + goto fail; + } + + if (!s->initialized) { + AVHWFramesContext *a_fc = (AVHWFramesContext*)frame_a->hw_frames_ctx->data; + AVHWFramesContext *b_fc = (AVHWFramesContext*)frame_b->hw_frames_ctx->data; + if (a_fc->sw_format != b_fc->sw_format) { + av_log(avctx, AV_LOG_ERROR, + "Currently the sw format of the first video neede to match the second!\n"); + return AVERROR(EINVAL); + } + RET(init_filter(avctx)); + } + + RET(av_frame_copy_props(output, frame_a)); + output->pts = s->pts; + + progress = av_clipf( + 1.f - ((float)(s->pts - s->first_pts - s->offset_pts) / s->duration_pts), + 0.f, 1.f); + + RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output, + (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler, + &(XFadeParameters){ progress }, sizeof(XFadeParameters))); + + return ff_filter_frame(outlink, output); + +fail: + av_frame_free(&output); + return err; +} + +static int config_props_output(AVFilterLink *outlink) +{ + int err; + AVFilterContext *avctx = outlink->src; + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *inlink0 = avctx->inputs[IN_A]; + AVFilterLink *inlink1 = avctx->inputs[IN_B]; + + if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) { + av_log(avctx, AV_LOG_ERROR, "First input link %s parameters " + "(size %dx%d) do not match the corresponding " + "second input link %s parameters (size %dx%d)\n", + avctx->input_pads[IN_A].name, inlink0->w, inlink0->h, + avctx->input_pads[IN_B].name, inlink1->w, inlink1->h); + return AVERROR(EINVAL); + } + + if (inlink0->time_base.num != inlink1->time_base.num || + inlink0->time_base.den != inlink1->time_base.den) { + av_log(avctx, AV_LOG_ERROR, "First input link %s timebase " + "(%d/%d) do not match the corresponding " + "second input link %s timebase (%d/%d)\n", + avctx->input_pads[0].name, inlink0->time_base.num, inlink0->time_base.den, + avctx->input_pads[1].name, inlink1->time_base.num, inlink1->time_base.den); + return AVERROR(EINVAL); + } + + s->first_pts = s->last_pts = s->pts = AV_NOPTS_VALUE; + + outlink->time_base = inlink0->time_base; + outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio; + outlink->frame_rate = inlink0->frame_rate; + + if (s->duration) + s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, outlink->time_base); + if (s->offset) + s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, outlink->time_base); + + RET(ff_vk_filter_config_output(outlink)); + +fail: + return err; +} + +static int activate(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *outlink = avctx->outputs[0]; + AVFrame *in = NULL; + int ret = 0, status; + int64_t pts; + + FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx); + + if (s->xfade_is_over) { + ret = ff_inlink_consume_frame(avctx->inputs[1], &in); + if (ret < 0) { + return ret; + } else if (ret > 0) { + in->pts = (in->pts - s->last_pts) + s->pts; + return ff_filter_frame(outlink, in); + } else if (ff_inlink_acknowledge_status(avctx->inputs[1], &status, &pts)) { + ff_outlink_set_status(outlink, status, s->pts); + return 0; + } else if (!ret) { + if (ff_outlink_frame_wanted(outlink)) { + ff_inlink_request_frame(avctx->inputs[1]); + return 0; + } + } + } + + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0) { + s->xf[0] = ff_inlink_peek_frame(avctx->inputs[0], 0); + if (s->xf[0]) { + if (s->first_pts == AV_NOPTS_VALUE) { + s->first_pts = s->xf[0]->pts; + } + s->pts = s->xf[0]->pts; + if (s->first_pts + s->offset_pts > s->xf[0]->pts) { + s->xf[0] = NULL; + s->need_second = 0; + ff_inlink_consume_frame(avctx->inputs[0], &in); + return ff_filter_frame(outlink, in); + } + + s->need_second = 1; + } + } + + if (s->xf[0] && ff_inlink_queued_frames(avctx->inputs[1]) > 0) { + ff_inlink_consume_frame(avctx->inputs[0], &s->xf[0]); + ff_inlink_consume_frame(avctx->inputs[1], &s->xf[1]); + + s->last_pts = s->xf[1]->pts; + s->pts = s->xf[0]->pts; + if (s->xf[0]->pts - (s->first_pts + s->offset_pts) > s->duration_pts) + s->xfade_is_over = 1; + ret = xfade_frame(avctx, s->xf[0], s->xf[1]); + av_frame_free(&s->xf[0]); + av_frame_free(&s->xf[1]); + return ret; + } + + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0 && + ff_inlink_queued_frames(avctx->inputs[1]) > 0) { + ff_filter_set_ready(avctx, 100); + return 0; + } + + if (ff_outlink_frame_wanted(outlink)) { + if (!s->eof[0] && ff_outlink_get_status(avctx->inputs[0])) { + s->eof[0] = 1; + s->xfade_is_over = 1; + } + if (!s->eof[1] && ff_outlink_get_status(avctx->inputs[1])) { + s->eof[1] = 1; + } + if (!s->eof[0] && !s->xf[0]) + ff_inlink_request_frame(avctx->inputs[0]); + if (!s->eof[1] && (s->need_second || s->eof[0])) + ff_inlink_request_frame(avctx->inputs[1]); + if (s->eof[0] && s->eof[1] && ( + ff_inlink_queued_frames(avctx->inputs[0]) <= 0 || + ff_inlink_queued_frames(avctx->inputs[1]) <= 0)) + ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE); + return 0; + } + + return FFERROR_NOT_READY; +} + +static av_cold void uninit(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + FFVulkanFunctions *vk = &vkctx->vkfn; + + ff_vk_exec_pool_free(vkctx, &s->e); + ff_vk_pipeline_free(vkctx, &s->pl); + 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 AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) +{ + XFadeVulkanContext *s = inlink->dst->priv; + + return s->xfade_is_over || !s->need_second ? + ff_null_get_video_buffer (inlink, w, h) : + ff_default_get_video_buffer(inlink, w, h); +} + +#define OFFSET(x) offsetof(XFadeVulkanContext, x) +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) + +static const AVOption xfade_vulkan_options[] = { + { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, 0, NB_TRANSITIONS-1, FLAGS, "transition" }, + { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" }, + { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, + { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, + { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(xfade_vulkan); + +static const AVFilterPad xfade_vulkan_inputs[] = { + { + .name = "main", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, + { + .name = "xfade", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, +}; + +static const AVFilterPad xfade_vulkan_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = &config_props_output, + }, +}; + +const AVFilter ff_vf_xfade_vulkan = { + .name = "xfade_vulkan", + .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."), + .priv_size = sizeof(XFadeVulkanContext), + .init = &ff_vk_filter_init, + .uninit = &uninit, + .activate = &activate, + FILTER_INPUTS(xfade_vulkan_inputs), + FILTER_OUTPUTS(xfade_vulkan_outputs), + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN), + .priv_class = &xfade_vulkan_class, + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, + .flags = AVFILTER_FLAG_HWDEVICE, +}; -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [FFmpeg-devel] [PATCH v2] libavfilter: add vf_xfade_vulkan 2023-05-30 1:58 ` [FFmpeg-devel] [PATCH v2] " Marvin Scholz @ 2023-05-30 12:30 ` Niklas Haas 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz 1 sibling, 0 replies; 8+ messages in thread From: Niklas Haas @ 2023-05-30 12:30 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz On Tue, 30 May 2023 03:58:54 +0200 Marvin Scholz <epirat07@gmail.com> wrote: > +static const char transition_fade[] = { > + C(0, void transition(int idx, ivec2 pos, float progress) ) > + C(0, { ) > + C(1, vec4 a = texture(a_images[idx], pos); ) > + C(1, vec4 b = texture(b_images[idx], pos); ) > + C(1, imageStore(output_images[idx], pos, mix(b, a, progress)); ) > + C(0, } ) > +}; This seems inverted compared to logic in vf_xfade. _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v3 1/4] libavfilter: add vf_xfade_vulkan 2023-05-30 1:58 ` [FFmpeg-devel] [PATCH v2] " Marvin Scholz 2023-05-30 12:30 ` Niklas Haas @ 2023-06-06 22:22 ` Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 2/4] lavfi/vf_xfade_vulkan: add wipeup transition Marvin Scholz ` (2 more replies) 1 sibling, 3 replies; 8+ messages in thread From: Marvin Scholz @ 2023-06-06 22:22 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz This is an initial version of vf_xfade_vulkan based on vf_xfade_opencl, for now only a subset of transitions are supported. --- Changes to v2: - Fixed activate handling, same as in my patch for the xfade filter - Added all remaining transitions the OpenCL filter supports configure | 1 + libavfilter/Makefile | 1 + libavfilter/allfilters.c | 1 + libavfilter/vf_xfade_vulkan.c | 499 ++++++++++++++++++++++++++++++++++ 4 files changed, 502 insertions(+) create mode 100644 libavfilter/vf_xfade_vulkan.c diff --git a/configure b/configure index 2992dae283..378cab1f3d 100755 --- a/configure +++ b/configure @@ -3824,6 +3824,7 @@ scale_vulkan_filter_deps="vulkan spirv_compiler" vpp_qsv_filter_deps="libmfx" vpp_qsv_filter_select="qsvvpp" xfade_opencl_filter_deps="opencl" +xfade_vulkan_filter_deps="vulkan spirv_compiler" yadif_cuda_filter_deps="ffnvcodec" yadif_cuda_filter_deps_any="cuda_nvcc cuda_llvm" yadif_videotoolbox_filter_deps="metal corevideo videotoolbox" diff --git a/libavfilter/Makefile b/libavfilter/Makefile index 18935b1616..ff149a3733 100644 --- a/libavfilter/Makefile +++ b/libavfilter/Makefile @@ -552,6 +552,7 @@ OBJS-$(CONFIG_XBR_FILTER) += vf_xbr.o OBJS-$(CONFIG_XCORRELATE_FILTER) += vf_convolve.o framesync.o OBJS-$(CONFIG_XFADE_FILTER) += vf_xfade.o OBJS-$(CONFIG_XFADE_OPENCL_FILTER) += vf_xfade_opencl.o opencl.o opencl/xfade.o +OBJS-$(CONFIG_XFADE_VULKAN_FILTER) += vf_xfade_vulkan.o vulkan.o vulkan_filter.o OBJS-$(CONFIG_XMEDIAN_FILTER) += vf_xmedian.o framesync.o OBJS-$(CONFIG_XSTACK_FILTER) += vf_stack.o framesync.o OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o yadif_common.o diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c index f1f781101b..6593e4eb83 100644 --- a/libavfilter/allfilters.c +++ b/libavfilter/allfilters.c @@ -519,6 +519,7 @@ extern const AVFilter ff_vf_xbr; extern const AVFilter ff_vf_xcorrelate; extern const AVFilter ff_vf_xfade; extern const AVFilter ff_vf_xfade_opencl; +extern const AVFilter ff_vf_xfade_vulkan; extern const AVFilter ff_vf_xmedian; extern const AVFilter ff_vf_xstack; extern const AVFilter ff_vf_yadif; diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c new file mode 100644 index 0000000000..f18a7b33aa --- /dev/null +++ b/libavfilter/vf_xfade_vulkan.c @@ -0,0 +1,499 @@ +/* + * 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/avassert.h" +#include "libavutil/random_seed.h" +#include "libavutil/opt.h" +#include "vulkan_filter.h" +#include "vulkan_spirv.h" +#include "filters.h" +#include "internal.h" + +#define IN_A 0 +#define IN_B 1 +#define IN_NB 2 + +typedef struct XFadeParameters { + float progress; +} XFadeParameters; + +typedef struct XFadeVulkanContext { + FFVulkanContext vkctx; + + int transition; + int64_t duration; + int64_t offset; + + int initialized; + FFVulkanPipeline pl; + FFVkExecPool e; + FFVkQueueFamilyCtx qf; + FFVkSPIRVShader shd; + VkSampler sampler; + + // PTS when the fade should start (in IN_A timebase) + int64_t start_pts; + + // PTS offset between IN_A and IN_B + int64_t inputs_offset_pts; + + // Duration of the transition + int64_t duration_pts; + + // Current PTS of the first input (IN_A) + int64_t pts; + + // If frames are currently just passed through + // unmodified, like before and after the actual + // transition. + int passthrough; + + int status[IN_NB]; +} XFadeVulkanContext; + +enum XFadeTransitions { + FADE, + WIPELEFT, + WIPERIGHT, + NB_TRANSITIONS, +}; + +static const char transition_fade[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, mix(a, b, progress)); ) + C(0, } ) +}; + +static const char transition_wipeleft[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.x * (1.0 - progress)); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.x > s ? b : a); ) + C(0, } ) +}; + +static const char transition_wiperight[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.x * progress); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.x > s ? a : b); ) + C(0, } ) +}; + +static const char* transitions_map[NB_TRANSITIONS] = { + [FADE] = transition_fade, + [WIPELEFT] = transition_wipeleft, + [WIPERIGHT] = transition_wiperight, +}; + +static av_cold int init_vulkan(AVFilterContext *avctx) +{ + int err = 0; + uint8_t *spv_data; + size_t spv_len; + void *spv_opaque = NULL; + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); + FFVkSPIRVShader *shd = &s->shd; + FFVkSPIRVCompiler *spv; + FFVulkanDescriptorSetBinding *desc; + + spv = ff_vk_spirv_init(); + if (!spv) { + av_log(avctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); + return AVERROR_EXTERNAL; + } + + ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT); + RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL)); + RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST)); + RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute", + VK_SHADER_STAGE_COMPUTE_BIT, 0)); + + ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1); + + desc = (FFVulkanDescriptorSetBinding []) { + { + .name = "a_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "b_images", + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .samplers = DUP_SAMPLER(s->sampler), + }, + { + .name = "output_images", + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format), + .mem_quali = "writeonly", + .dimensions = 2, + .elems = planes, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + }, + }; + + RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, 0)); + + GLSLC(0, layout(push_constant, std430) uniform pushConstants { ); + GLSLC(1, float progress; ); + GLSLC(0, }; ); + + ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters), + VK_SHADER_STAGE_COMPUTE_BIT); + + // Add the right transition type function to the shader + GLSLD(transitions_map[s->transition]); + + GLSLC(0, void main() ); + GLSLC(0, { ); + GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); + GLSLF(1, int planes = %i; ,planes); + GLSLC(1, for (int i = 0; i < planes; i++) { ); + GLSLC(2, transition(i, pos, progress); ); + GLSLC(1, } ); + GLSLC(0, } ); + + RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main", + &spv_opaque)); + RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main")); + + RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd)); + RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl)); + + s->initialized = 1; + +fail: + if (spv_opaque) + spv->free_shader(spv, &spv_opaque); + if (spv) + spv->uninit(&spv); + + return err; +} + +static int xfade_frame(AVFilterContext *avctx, AVFrame *frame_a, AVFrame *frame_b) +{ + int err; + AVFilterLink *outlink = avctx->outputs[0]; + XFadeVulkanContext *s = avctx->priv; + float progress; + + AVFrame *output = ff_get_video_buffer(outlink, outlink->w, outlink->h); + if (!output) { + err = AVERROR(ENOMEM); + goto fail; + } + + if (!s->initialized) { + AVHWFramesContext *a_fc = (AVHWFramesContext*)frame_a->hw_frames_ctx->data; + AVHWFramesContext *b_fc = (AVHWFramesContext*)frame_b->hw_frames_ctx->data; + if (a_fc->sw_format != b_fc->sw_format) { + av_log(avctx, AV_LOG_ERROR, + "Currently the sw format of the first input needs to match the second!\n"); + return AVERROR(EINVAL); + } + RET(init_vulkan(avctx)); + } + + RET(av_frame_copy_props(output, frame_a)); + output->pts = s->pts; + + progress = av_clipf( + (float)(s->pts - s->start_pts) / s->duration_pts, + 0.f, 1.f); + + RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output, + (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler, + &(XFadeParameters){ progress }, sizeof(XFadeParameters))); + + return ff_filter_frame(outlink, output); + +fail: + av_frame_free(&output); + return err; +} + +static int config_props_output(AVFilterLink *outlink) +{ + int err; + AVFilterContext *avctx = outlink->src; + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *inlink_a = avctx->inputs[IN_A]; + AVFilterLink *inlink_b = avctx->inputs[IN_B]; + + if (inlink_a->w != inlink_b->w || inlink_a->h != inlink_b->h) { + av_log(avctx, AV_LOG_ERROR, "First input link %s parameters " + "(size %dx%d) do not match the corresponding " + "second input link %s parameters (size %dx%d)\n", + avctx->input_pads[IN_A].name, inlink_a->w, inlink_a->h, + avctx->input_pads[IN_B].name, inlink_b->w, inlink_b->h); + return AVERROR(EINVAL); + } + + if (inlink_a->time_base.num != inlink_b->time_base.num || + inlink_a->time_base.den != inlink_b->time_base.den) { + av_log(avctx, AV_LOG_ERROR, "First input link %s timebase " + "(%d/%d) does not match the corresponding " + "second input link %s timebase (%d/%d)\n", + avctx->input_pads[IN_A].name, inlink_a->time_base.num, inlink_a->time_base.den, + avctx->input_pads[IN_B].name, inlink_b->time_base.num, inlink_b->time_base.den); + return AVERROR(EINVAL); + } + + s->start_pts = s->inputs_offset_pts = AV_NOPTS_VALUE; + + outlink->time_base = inlink_a->time_base; + outlink->frame_rate = inlink_a->frame_rate; + outlink->sample_aspect_ratio = inlink_a->sample_aspect_ratio; + + if (s->duration) + s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, inlink_a->time_base); + RET(ff_vk_filter_config_output(outlink)); + +fail: + return err; +} + +static int forward_frame(XFadeVulkanContext *s, + AVFilterLink *inlink, AVFilterLink *outlink) +{ + int64_t status_pts; + int ret = 0, status; + AVFrame *frame = NULL; + + ret = ff_inlink_consume_frame(inlink, &frame); + if (ret < 0) + return ret; + + if (ret > 0) { + // If we do not have an offset yet, it's because we + // never got a first input. Just offset to 0 + if (s->inputs_offset_pts == AV_NOPTS_VALUE) + s->inputs_offset_pts = -frame->pts; + + // We got a frame, nothing to do other than adjusting the timestamp + frame->pts += s->inputs_offset_pts; + return ff_filter_frame(outlink, frame); + } + + // Forward status with our timestamp + if (ff_inlink_acknowledge_status(inlink, &status, &status_pts)) { + if (s->inputs_offset_pts == AV_NOPTS_VALUE) + s->inputs_offset_pts = -status_pts; + + ff_outlink_set_status(outlink, status, status_pts + s->inputs_offset_pts); + return 0; + } + + // No frame available, request one if needed + if (ff_outlink_frame_wanted(outlink)) + ff_inlink_request_frame(inlink); + + return 0; +} + +static int activate(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + AVFilterLink *in_a = avctx->inputs[IN_A]; + AVFilterLink *in_b = avctx->inputs[IN_B]; + AVFilterLink *outlink = avctx->outputs[0]; + int64_t status_pts; + + FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx); + + // Check if we already transitioned or IN_A ended prematurely, + // in which case just forward the frames from IN_B with adjusted + // timestamps until EOF. + if (s->status[IN_A] && !s->status[IN_B]) + return forward_frame(s, in_b, outlink); + + // We did not finish transitioning yet and the first stream + // did not end either, so check if there are more frames to consume. + if (ff_inlink_check_available_frame(in_a)) { + AVFrame *peeked_frame = ff_inlink_peek_frame(in_a, 0); + s->pts = peeked_frame->pts; + + if (s->start_pts == AV_NOPTS_VALUE) + s->start_pts = + s->pts + av_rescale_q(s->offset, AV_TIME_BASE_Q, in_a->time_base); + + // Check if we are not yet transitioning, in which case + // just request and forward the input frame. + if (s->start_pts > s->pts) { + AVFrame *frame_a = NULL; + s->passthrough = 1; + ff_inlink_consume_frame(in_a, &frame_a); + return ff_filter_frame(outlink, frame_a); + } + s->passthrough = 0; + + // We are transitioning, so we need a frame from IN_B + if (ff_inlink_check_available_frame(in_b)) { + int ret; + AVFrame *frame_a = NULL, *frame_b = NULL; + ff_inlink_consume_frame(avctx->inputs[IN_A], &frame_a); + ff_inlink_consume_frame(avctx->inputs[IN_B], &frame_b); + + // Calculate PTS offset to first input + if (s->inputs_offset_pts == AV_NOPTS_VALUE) + s->inputs_offset_pts = s->pts - frame_b->pts; + + // Check if we finished transitioning, in which case we + // report back EOF to IN_A as it is no longer needed. + if (s->pts - s->start_pts > s->duration_pts) { + s->status[IN_A] = AVERROR_EOF; + ff_inlink_set_status(in_a, AVERROR_EOF); + s->passthrough = 1; + } + ret = xfade_frame(avctx, frame_a, frame_b); + av_frame_free(&frame_a); + av_frame_free(&frame_b); + return ret; + } + + // We did not get a frame from IN_B, check its status. + if (ff_inlink_acknowledge_status(in_b, &s->status[IN_B], &status_pts)) { + // We should transition, but IN_B is EOF so just report EOF output now. + ff_outlink_set_status(outlink, s->status[IN_B], s->pts); + return 0; + } + + // We did not get a frame for IN_B but no EOF either, so just request more. + if (ff_outlink_frame_wanted(outlink)) { + ff_inlink_request_frame(in_b); + return 0; + } + } + + // We did not get a frame from IN_A, check its status. + if (ff_inlink_acknowledge_status(in_a, &s->status[IN_A], &status_pts)) { + // No more frames from IN_A, do not report EOF though, we will just + // forward the IN_B frames in the next activate calls. + s->passthrough = 1; + ff_filter_set_ready(avctx, 100); + return 0; + } + + // We have no frames yet from IN_A and no EOF, so request some. + if (ff_outlink_frame_wanted(outlink)) { + ff_inlink_request_frame(in_a); + return 0; + } + + return FFERROR_NOT_READY; +} + +static av_cold void uninit(AVFilterContext *avctx) +{ + XFadeVulkanContext *s = avctx->priv; + FFVulkanContext *vkctx = &s->vkctx; + FFVulkanFunctions *vk = &vkctx->vkfn; + + ff_vk_exec_pool_free(vkctx, &s->e); + ff_vk_pipeline_free(vkctx, &s->pl); + 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 AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) +{ + XFadeVulkanContext *s = inlink->dst->priv; + + return s->passthrough ? + ff_null_get_video_buffer (inlink, w, h) : + ff_default_get_video_buffer(inlink, w, h); +} + +#define OFFSET(x) offsetof(XFadeVulkanContext, x) +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) + +static const AVOption xfade_vulkan_options[] = { + { "transition", "set cross fade transition", OFFSET(transition), AV_OPT_TYPE_INT, {.i64=FADE}, 0, NB_TRANSITIONS-1, FLAGS, "transition" }, + { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" }, + { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, + { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" }, + { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, + { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, + { NULL } +}; + +AVFILTER_DEFINE_CLASS(xfade_vulkan); + +static const AVFilterPad xfade_vulkan_inputs[] = { + { + .name = "main", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = &get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, + { + .name = "xfade", + .type = AVMEDIA_TYPE_VIDEO, + .get_buffer.video = &get_video_buffer, + .config_props = &ff_vk_filter_config_input, + }, +}; + +static const AVFilterPad xfade_vulkan_outputs[] = { + { + .name = "default", + .type = AVMEDIA_TYPE_VIDEO, + .config_props = &config_props_output, + }, +}; + +const AVFilter ff_vf_xfade_vulkan = { + .name = "xfade_vulkan", + .description = NULL_IF_CONFIG_SMALL("Cross fade one video with another video."), + .priv_size = sizeof(XFadeVulkanContext), + .init = &ff_vk_filter_init, + .uninit = &uninit, + .activate = &activate, + FILTER_INPUTS(xfade_vulkan_inputs), + FILTER_OUTPUTS(xfade_vulkan_outputs), + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN), + .priv_class = &xfade_vulkan_class, + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, + .flags = AVFILTER_FLAG_HWDEVICE, +}; -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v3 2/4] lavfi/vf_xfade_vulkan: add wipeup transition 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz @ 2023-06-06 22:23 ` Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 3/4] lavfi/vf_xfade_vulkan: add wipedown transition Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 4/4] lavfi/vf_xfade_vulkan: add slide transitions Marvin Scholz 2 siblings, 0 replies; 8+ messages in thread From: Marvin Scholz @ 2023-06-06 22:23 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz --- libavfilter/vf_xfade_vulkan.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c index f18a7b33aa..0244802e9c 100644 --- a/libavfilter/vf_xfade_vulkan.c +++ b/libavfilter/vf_xfade_vulkan.c @@ -70,6 +70,7 @@ enum XFadeTransitions { FADE, WIPELEFT, WIPERIGHT, + WIPEUP, NB_TRANSITIONS, }; @@ -104,10 +105,22 @@ static const char transition_wiperight[] = { C(0, } ) }; +static const char transition_wipeup[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.y * (1.0 - progress)); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.y > s ? b : a); ) + C(0, } ) +}; + static const char* transitions_map[NB_TRANSITIONS] = { [FADE] = transition_fade, [WIPELEFT] = transition_wipeleft, [WIPERIGHT] = transition_wiperight, + [WIPEUP] = transition_wipeup, }; static av_cold int init_vulkan(AVFilterContext *avctx) @@ -453,6 +466,7 @@ static const AVOption xfade_vulkan_options[] = { { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, {.i64=FADE}, 0, 0, FLAGS, "transition" }, { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" }, + { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" }, { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, { NULL } -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v3 3/4] lavfi/vf_xfade_vulkan: add wipedown transition 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 2/4] lavfi/vf_xfade_vulkan: add wipeup transition Marvin Scholz @ 2023-06-06 22:23 ` Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 4/4] lavfi/vf_xfade_vulkan: add slide transitions Marvin Scholz 2 siblings, 0 replies; 8+ messages in thread From: Marvin Scholz @ 2023-06-06 22:23 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz --- libavfilter/vf_xfade_vulkan.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c index 0244802e9c..58552ab734 100644 --- a/libavfilter/vf_xfade_vulkan.c +++ b/libavfilter/vf_xfade_vulkan.c @@ -71,6 +71,7 @@ enum XFadeTransitions { WIPELEFT, WIPERIGHT, WIPEUP, + WIPEDOWN, NB_TRANSITIONS, }; @@ -116,11 +117,23 @@ static const char transition_wipeup[] = { C(0, } ) }; +static const char transition_wipedown[] = { + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, ivec2 size = imageSize(output_images[idx]); ) + C(1, int s = int(size.y * progress); ) + C(1, vec4 a = texture(a_images[idx], pos); ) + C(1, vec4 b = texture(b_images[idx], pos); ) + C(1, imageStore(output_images[idx], pos, pos.y > s ? a : b); ) + C(0, } ) +}; + static const char* transitions_map[NB_TRANSITIONS] = { [FADE] = transition_fade, [WIPELEFT] = transition_wipeleft, [WIPERIGHT] = transition_wiperight, [WIPEUP] = transition_wipeup, + [WIPEDOWN] = transition_wipedown, }; static av_cold int init_vulkan(AVFilterContext *avctx) @@ -467,6 +480,7 @@ static const AVOption xfade_vulkan_options[] = { { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" }, { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" }, + { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, "transition" }, { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, { NULL } -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* [FFmpeg-devel] [PATCH v3 4/4] lavfi/vf_xfade_vulkan: add slide transitions 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 2/4] lavfi/vf_xfade_vulkan: add wipeup transition Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 3/4] lavfi/vf_xfade_vulkan: add wipedown transition Marvin Scholz @ 2023-06-06 22:23 ` Marvin Scholz 2 siblings, 0 replies; 8+ messages in thread From: Marvin Scholz @ 2023-06-06 22:23 UTC (permalink / raw) To: ffmpeg-devel; +Cc: Marvin Scholz --- libavfilter/vf_xfade_vulkan.c | 58 +++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c index 58552ab734..f1f248c288 100644 --- a/libavfilter/vf_xfade_vulkan.c +++ b/libavfilter/vf_xfade_vulkan.c @@ -72,6 +72,10 @@ enum XFadeTransitions { WIPERIGHT, WIPEUP, WIPEDOWN, + SLIDEDOWN, + SLIDEUP, + SLIDELEFT, + SLIDERIGHT, NB_TRANSITIONS, }; @@ -128,12 +132,62 @@ static const char transition_wipedown[] = { C(0, } ) }; +#define SHADER_SLIDE_COMMON \ + C(0, void slide(int idx, ivec2 pos, float progress, ivec2 direction) ) \ + C(0, { ) \ + C(1, ivec2 size = imageSize(output_images[idx]); ) \ + C(1, ivec2 pi = ivec2(progress * size); ) \ + C(1, ivec2 p = pos + pi * direction; ) \ + C(1, ivec2 f = p % size; ) \ + C(1, f = f + size * ivec2(f.x < 0, f.y < 0); ) \ + C(1, vec4 a = texture(a_images[idx], f); ) \ + C(1, vec4 b = texture(b_images[idx], f); ) \ + C(1, vec4 r = (p.y >= 0 && p.x >= 0 && size.y > p.y && size.x > p.x) ? a : b; ) \ + C(1, imageStore(output_images[idx], pos, r); ) \ + C(0, } ) + +static const char transition_slidedown[] = { + SHADER_SLIDE_COMMON + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, slide(idx, pos, progress, ivec2(0, -1)); ) + C(0, } ) +}; + +static const char transition_slideup[] = { + SHADER_SLIDE_COMMON + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, slide(idx, pos, progress, ivec2(0, +1)); ) + C(0, } ) +}; + +static const char transition_slideleft[] = { + SHADER_SLIDE_COMMON + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, slide(idx, pos, progress, ivec2(+1, 0)); ) + C(0, } ) +}; + +static const char transition_slideright[] = { + SHADER_SLIDE_COMMON + C(0, void transition(int idx, ivec2 pos, float progress) ) + C(0, { ) + C(1, slide(idx, pos, progress, ivec2(-1, 0)); ) + C(0, } ) +}; + static const char* transitions_map[NB_TRANSITIONS] = { [FADE] = transition_fade, [WIPELEFT] = transition_wipeleft, [WIPERIGHT] = transition_wiperight, [WIPEUP] = transition_wipeup, [WIPEDOWN] = transition_wipedown, + [SLIDEDOWN] = transition_slidedown, + [SLIDEUP] = transition_slideup, + [SLIDELEFT] = transition_slideleft, + [SLIDERIGHT]= transition_slideright, }; static av_cold int init_vulkan(AVFilterContext *avctx) @@ -481,6 +535,10 @@ static const AVOption xfade_vulkan_options[] = { { "wiperight", "wipe right transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPERIGHT}, 0, 0, FLAGS, "transition" }, { "wipeup", "wipe up transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEUP}, 0, 0, FLAGS, "transition" }, { "wipedown", "wipe down transition", 0, AV_OPT_TYPE_CONST, {.i64=WIPEDOWN}, 0, 0, FLAGS, "transition" }, + { "slidedown", "slide down transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEDOWN}, 0, 0, FLAGS, "transition" }, + { "slideup", "slide up transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDEUP}, 0, 0, FLAGS, "transition" }, + { "slideleft", "slide left transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDELEFT}, 0, 0, FLAGS, "transition" }, + { "slideright","slide right transition", 0, AV_OPT_TYPE_CONST, {.i64=SLIDERIGHT}, 0, 0, FLAGS, "transition" }, { "duration", "set cross fade duration", OFFSET(duration), AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, { "offset", "set cross fade start relative to first input stream", OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS }, { NULL } -- 2.37.0 (Apple Git-136) _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan 2023-05-30 1:33 [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Marvin Scholz 2023-05-30 1:58 ` [FFmpeg-devel] [PATCH v2] " Marvin Scholz @ 2023-05-30 8:05 ` Paul B Mahol 1 sibling, 0 replies; 8+ messages in thread From: Paul B Mahol @ 2023-05-30 8:05 UTC (permalink / raw) To: FFmpeg development discussions and patches; +Cc: Marvin Scholz On Tue, May 30, 2023 at 3:34 AM Marvin Scholz <epirat07@gmail.com> wrote: > This is an initial version of vf_xfade_vulkan based > on vf_xfade_opencl, for now only fade and wipeleft > transitions are supported. > --- > libavfilter/Makefile | 1 + > libavfilter/allfilters.c | 1 + > libavfilter/vf_xfade_vulkan.c | 441 ++++++++++++++++++++++++++++++++++ > 3 files changed, 443 insertions(+) > create mode 100644 libavfilter/vf_xfade_vulkan.c > > diff --git a/libavfilter/Makefile b/libavfilter/Makefile > index 18935b1616..ff149a3733 100644 > --- a/libavfilter/Makefile > +++ b/libavfilter/Makefile > @@ -552,6 +552,7 @@ OBJS-$(CONFIG_XBR_FILTER) += > vf_xbr.o > OBJS-$(CONFIG_XCORRELATE_FILTER) += vf_convolve.o framesync.o > OBJS-$(CONFIG_XFADE_FILTER) += vf_xfade.o > OBJS-$(CONFIG_XFADE_OPENCL_FILTER) += vf_xfade_opencl.o > opencl.o opencl/xfade.o > +OBJS-$(CONFIG_XFADE_VULKAN_FILTER) += vf_xfade_vulkan.o > vulkan.o vulkan_filter.o > OBJS-$(CONFIG_XMEDIAN_FILTER) += vf_xmedian.o framesync.o > OBJS-$(CONFIG_XSTACK_FILTER) += vf_stack.o framesync.o > OBJS-$(CONFIG_YADIF_FILTER) += vf_yadif.o yadif_common.o > diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c > index f1f781101b..6593e4eb83 100644 > --- a/libavfilter/allfilters.c > +++ b/libavfilter/allfilters.c > @@ -519,6 +519,7 @@ extern const AVFilter ff_vf_xbr; > extern const AVFilter ff_vf_xcorrelate; > extern const AVFilter ff_vf_xfade; > extern const AVFilter ff_vf_xfade_opencl; > +extern const AVFilter ff_vf_xfade_vulkan; > extern const AVFilter ff_vf_xmedian; > extern const AVFilter ff_vf_xstack; > extern const AVFilter ff_vf_yadif; > diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c > new file mode 100644 > index 0000000000..4a47c68fb4 > --- /dev/null > +++ b/libavfilter/vf_xfade_vulkan.c > @@ -0,0 +1,441 @@ > +/* > + * 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/random_seed.h" > +#include "libavutil/opt.h" > +#include "vulkan_filter.h" > +#include "vulkan_spirv.h" > +#include "filters.h" > +#include "internal.h" > + > +#define IN_A 0 > +#define IN_B 1 > + > +enum XFadeTransitions { > + FADE, > + WIPELEFT, > + NB_TRANSITIONS, > +}; > + > +typedef struct XFadeParameters { > + float progress; > +} XFadeParameters; > + > +typedef struct XFadeVulkanContext { > + FFVulkanContext vkctx; > + > + int transition; > + int64_t duration; > + int64_t offset; > + > + int initialized; > + FFVulkanPipeline pl; > + FFVkExecPool e; > + FFVkQueueFamilyCtx qf; > + FFVkSPIRVShader shd; > + VkSampler sampler; > + > + int64_t duration_pts; > + int64_t offset_pts; > + int64_t first_pts; > + int64_t last_pts; > + int64_t pts; > + int xfade_is_over; > + int need_second; > + int eof[2]; > + AVFrame *xf[2]; > +} XFadeVulkanContext; > + > +static const char transition_fade[] = { > + C(0, void transition(int idx, ivec2 pos, float progress) > ) > + C(0, { > ) > + C(1, vec4 a = texture(a_images[idx], pos); > ) > + C(1, vec4 b = texture(b_images[idx], pos); > ) > + C(1, imageStore(output_images[idx], pos, mix(b, a, progress)); > ) > + C(0, } > ) > +}; > + > +static const char transition_wipeleft[] = { > + C(0, void transition(int idx, ivec2 pos, float progress) > ) > + C(0, { > ) > + C(1, ivec2 size = imageSize(output_images[idx]); > ) > + C(1, int s = int(size.x * progress); > ) > + C(1, vec4 a = texture(a_images[idx], pos); > ) > + C(1, vec4 b = texture(b_images[idx], pos); > ) > + C(1, imageStore(output_images[idx], pos, pos.x > s ? b : a); > ) > + C(0, } > ) > +}; > + > +static av_cold int init_filter(AVFilterContext *avctx) > +{ > + int err = 0; > + uint8_t *spv_data; > + size_t spv_len; > + void *spv_opaque = NULL; > + XFadeVulkanContext *s = avctx->priv; > + FFVulkanContext *vkctx = &s->vkctx; > + const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); > + FFVkSPIRVShader *shd = &s->shd; > + FFVkSPIRVCompiler *spv; > + FFVulkanDescriptorSetBinding *desc; > + > + spv = ff_vk_spirv_init(); > + if (!spv) { > + av_log(avctx, AV_LOG_ERROR, "Unable to initialize SPIR-V > compiler!\n"); > + return AVERROR_EXTERNAL; > + } > + > + ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT); > + RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, > 0, 0, NULL)); > + RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST)); > + RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute", > + VK_SHADER_STAGE_COMPUTE_BIT, 0)); > + > + ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1); > + > + desc = (FFVulkanDescriptorSetBinding []) { > + { > + .name = "a_images", > + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, > + .dimensions = 2, > + .elems = planes, > + .stages = VK_SHADER_STAGE_COMPUTE_BIT, > + .samplers = DUP_SAMPLER(s->sampler), > + }, > + { > + .name = "b_images", > + .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, > + .dimensions = 2, > + .elems = planes, > + .stages = VK_SHADER_STAGE_COMPUTE_BIT, > + .samplers = DUP_SAMPLER(s->sampler), > + }, > + { > + .name = "output_images", > + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, > + .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format), > + .mem_quali = "writeonly", > + .dimensions = 2, > + .elems = planes, > + .stages = VK_SHADER_STAGE_COMPUTE_BIT, > + }, > + }; > + > + RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, > 0)); > + > + GLSLC(0, layout(push_constant, std430) uniform pushConstants { > ); > + GLSLC(1, float progress; > ); > + GLSLC(0, }; > ); > + > + ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters), > + VK_SHADER_STAGE_COMPUTE_BIT); > + > + switch (s->transition) { > + case FADE: > + GLSLD(transition_fade); > + break; > + case WIPELEFT: > + GLSLD(transition_wipeleft); > + break; > + default: > + err = AVERROR_BUG; > + goto fail; > + } > + > + GLSLC(0, void main() > ); > + GLSLC(0, { > ); > + GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); > ); > + GLSLF(1, int planes = %i; > ,planes); > + GLSLC(1, for (int i = 0; i < planes; i++) { > ); > + GLSLC(2, transition(i, pos, progress); > ); > + GLSLC(1, } > ); > + GLSLC(0, } > ); > + > + RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main", > + &spv_opaque)); > + RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main")); > + > + RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd)); > + RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl)); > + > + s->initialized = 1; > + > +fail: > + if (spv_opaque) > + spv->free_shader(spv, &spv_opaque); > + if (spv) > + spv->uninit(&spv); > + > + return err; > +} > + > +static int xfade_frame(AVFilterContext *avctx, AVFrame *a, AVFrame *b) > +{ > + int err; > + AVFilterLink *outlink = avctx->outputs[0]; > + XFadeVulkanContext *s = avctx->priv; > + AVFrame *frame_a = s->xf[IN_A]; > + AVFrame *frame_b = s->xf[IN_B]; > + float progress; > + > + AVFrame *output = ff_get_video_buffer(outlink, outlink->w, > outlink->h); > + if (!output) { > + err = AVERROR(ENOMEM); > + goto fail; > + } > + > + if (!s->initialized) { > + AVHWFramesContext *a_fc = > (AVHWFramesContext*)frame_a->hw_frames_ctx->data; > + AVHWFramesContext *b_fc = > (AVHWFramesContext*)frame_b->hw_frames_ctx->data; > + if (a_fc->sw_format != b_fc->sw_format) { > + av_log(avctx, AV_LOG_ERROR, > + "Currently the sw format of the first video neede to > match the second!\n"); > + return AVERROR(EINVAL); > + } > + RET(init_filter(avctx)); > + } > + > + RET(av_frame_copy_props(output, frame_a)); > + output->pts = s->pts; > + > + progress = av_clipf( > + 1.f - ((float)(s->pts - s->first_pts - s->offset_pts) / > s->duration_pts), > + 0.f, 1.f); > + > + RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output, > + (AVFrame *[]){ frame_a, frame_b }, 2, > s->sampler, > + &(XFadeParameters){ progress }, > sizeof(XFadeParameters))); > + > + return ff_filter_frame(outlink, output); > + > +fail: > + av_frame_free(&output); > + return err; > +} > + > +static int config_props_output(AVFilterLink *outlink) > +{ > + int err; > + AVFilterContext *avctx = outlink->src; > + XFadeVulkanContext *s = avctx->priv; > + AVFilterLink *inlink0 = avctx->inputs[IN_A]; > + AVFilterLink *inlink1 = avctx->inputs[IN_B]; > + > + if (inlink0->w != inlink1->w || inlink0->h != inlink1->h) { > + av_log(avctx, AV_LOG_ERROR, "First input link %s parameters " > + "(size %dx%d) do not match the corresponding " > + "second input link %s parameters (size %dx%d)\n", > + avctx->input_pads[IN_A].name, inlink0->w, inlink0->h, > + avctx->input_pads[IN_B].name, inlink1->w, inlink1->h); > + return AVERROR(EINVAL); > + } > + > + if (inlink0->time_base.num != inlink1->time_base.num || > + inlink0->time_base.den != inlink1->time_base.den) { > + av_log(avctx, AV_LOG_ERROR, "First input link %s timebase " > + "(%d/%d) do not match the corresponding " > + "second input link %s timebase (%d/%d)\n", > + avctx->input_pads[0].name, inlink0->time_base.num, > inlink0->time_base.den, > + avctx->input_pads[1].name, inlink1->time_base.num, > inlink1->time_base.den); > + return AVERROR(EINVAL); > + } > + > + s->first_pts = s->last_pts = s->pts = AV_NOPTS_VALUE; > + > + outlink->time_base = inlink0->time_base; > + outlink->sample_aspect_ratio = inlink0->sample_aspect_ratio; > + outlink->frame_rate = inlink0->frame_rate; > + > + if (s->duration) > + s->duration_pts = av_rescale_q(s->duration, AV_TIME_BASE_Q, > outlink->time_base); > + if (s->offset) > + s->offset_pts = av_rescale_q(s->offset, AV_TIME_BASE_Q, > outlink->time_base); > + > + RET(ff_vk_filter_config_output(outlink)); > + > +fail: > + return err; > +} > + > +static int activate(AVFilterContext *avctx) > +{ > + XFadeVulkanContext *s = avctx->priv; > + AVFilterLink *outlink = avctx->outputs[0]; > + AVFrame *in = NULL; > + int ret = 0, status; > + int64_t pts; > + > + FF_FILTER_FORWARD_STATUS_BACK_ALL(outlink, avctx); > + > + if (s->xfade_is_over) { > + ret = ff_inlink_consume_frame(avctx->inputs[1], &in); > + if (ret < 0) { > + return ret; > + } else if (ret > 0) { > + in->pts = (in->pts - s->last_pts) + s->pts; > + return ff_filter_frame(outlink, in); > + } else if (ff_inlink_acknowledge_status(avctx->inputs[1], > &status, &pts)) { > + ff_outlink_set_status(outlink, status, s->pts); > + return 0; > + } else if (!ret) { > + if (ff_outlink_frame_wanted(outlink)) { > + ff_inlink_request_frame(avctx->inputs[1]); > + return 0; > + } > + } > + } > + > + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0) { > + s->xf[0] = ff_inlink_peek_frame(avctx->inputs[0], 0); > + if (s->xf[0]) { > + if (s->first_pts == AV_NOPTS_VALUE) { > + s->first_pts = s->xf[0]->pts; > + } > + s->pts = s->xf[0]->pts; > + if (s->first_pts + s->offset_pts > s->xf[0]->pts) { > + s->xf[0] = NULL; > + s->need_second = 0; > + ff_inlink_consume_frame(avctx->inputs[0], &in); > + return ff_filter_frame(outlink, in); > + } > + > + s->need_second = 1; > + } > + } > + > + if (s->xf[0] && ff_inlink_queued_frames(avctx->inputs[1]) > 0) { > + ff_inlink_consume_frame(avctx->inputs[0], &s->xf[0]); > + ff_inlink_consume_frame(avctx->inputs[1], &s->xf[1]); > + > + s->last_pts = s->xf[1]->pts; > + s->pts = s->xf[0]->pts; > + if (s->xf[0]->pts - (s->first_pts + s->offset_pts) > > s->duration_pts) > + s->xfade_is_over = 1; > + ret = xfade_frame(avctx, s->xf[0], s->xf[1]); > + av_frame_free(&s->xf[0]); > + av_frame_free(&s->xf[1]); > + return ret; > + } > + > + if (ff_inlink_queued_frames(avctx->inputs[0]) > 0 && > + ff_inlink_queued_frames(avctx->inputs[1]) > 0) { > + ff_filter_set_ready(avctx, 100); > + return 0; > + } > + > + if (ff_outlink_frame_wanted(outlink)) { > + if (!s->eof[0] && ff_outlink_get_status(avctx->inputs[0])) { > This is not correct, yes I'm aware filters have it, but it should not be introduced again. To get EOF status of inlink only use ack call only. Will fix vf_xfade*.c in master if time permits. > + s->eof[0] = 1; > + s->xfade_is_over = 1; > + } > + if (!s->eof[1] && ff_outlink_get_status(avctx->inputs[1])) { > + s->eof[1] = 1; > + } > + if (!s->eof[0] && !s->xf[0]) > + ff_inlink_request_frame(avctx->inputs[0]); > + if (!s->eof[1] && (s->need_second || s->eof[0])) > + ff_inlink_request_frame(avctx->inputs[1]); > + if (s->eof[0] && s->eof[1] && ( > + ff_inlink_queued_frames(avctx->inputs[0]) <= 0 || > + ff_inlink_queued_frames(avctx->inputs[1]) <= 0)) > + ff_outlink_set_status(outlink, AVERROR_EOF, AV_NOPTS_VALUE); > + return 0; > + } > + > + return FFERROR_NOT_READY; > +} > + > +static av_cold void uninit(AVFilterContext *avctx) > +{ > + XFadeVulkanContext *s = avctx->priv; > + FFVulkanContext *vkctx = &s->vkctx; > + FFVulkanFunctions *vk = &vkctx->vkfn; > + > + ff_vk_exec_pool_free(vkctx, &s->e); > + ff_vk_pipeline_free(vkctx, &s->pl); > + 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 AVFrame *get_video_buffer(AVFilterLink *inlink, int w, int h) > +{ > + XFadeVulkanContext *s = inlink->dst->priv; > + > + return s->xfade_is_over || !s->need_second ? > + ff_null_get_video_buffer (inlink, w, h) : > + ff_default_get_video_buffer(inlink, w, h); > +} > + > +#define OFFSET(x) offsetof(XFadeVulkanContext, x) > +#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) > + > +static const AVOption xfade_vulkan_options[] = { > + { "transition", "set cross fade transition", OFFSET(transition), > AV_OPT_TYPE_INT, {.i64=FADE}, 0, NB_TRANSITIONS-1, FLAGS, "transition" }, > + { "fade", "fade transition", 0, AV_OPT_TYPE_CONST, > {.i64=FADE}, 0, 0, FLAGS, "transition" }, > + { "wipeleft", "wipe left transition", 0, AV_OPT_TYPE_CONST, > {.i64=WIPELEFT}, 0, 0, FLAGS, "transition" }, > + { "duration", "set cross fade duration", OFFSET(duration), > AV_OPT_TYPE_DURATION, {.i64=1000000}, 0, 60000000, FLAGS }, > + { "offset", "set cross fade start relative to first input stream", > OFFSET(offset), AV_OPT_TYPE_DURATION, {.i64=0}, INT64_MIN, INT64_MAX, FLAGS > }, > + { NULL } > +}; > + > +AVFILTER_DEFINE_CLASS(xfade_vulkan); > + > +static const AVFilterPad xfade_vulkan_inputs[] = { > + { > + .name = "main", > + .type = AVMEDIA_TYPE_VIDEO, > + .get_buffer.video = get_video_buffer, > + .config_props = &ff_vk_filter_config_input, > + }, > + { > + .name = "xfade", > + .type = AVMEDIA_TYPE_VIDEO, > + .get_buffer.video = get_video_buffer, > + .config_props = &ff_vk_filter_config_input, > + }, > +}; > + > +static const AVFilterPad xfade_vulkan_outputs[] = { > + { > + .name = "default", > + .type = AVMEDIA_TYPE_VIDEO, > + .config_props = &config_props_output, > + }, > +}; > + > +const AVFilter ff_vf_xfade_vulkan = { > + .name = "xfade_vulkan", > + .description = NULL_IF_CONFIG_SMALL("Cross fade one video with > another video."), > + .priv_size = sizeof(XFadeVulkanContext), > + .init = &ff_vk_filter_init, > + .uninit = &uninit, > + .activate = &activate, > + FILTER_INPUTS(xfade_vulkan_inputs), > + FILTER_OUTPUTS(xfade_vulkan_outputs), > + FILTER_SINGLE_PIXFMT(AV_PIX_FMT_VULKAN), > + .priv_class = &xfade_vulkan_class, > + .flags_internal = FF_FILTER_FLAG_HWFRAME_AWARE, > + .flags = AVFILTER_FLAG_HWDEVICE, > +}; > -- > 2.37.0 (Apple Git-136) > > _______________________________________________ > ffmpeg-devel mailing list > ffmpeg-devel@ffmpeg.org > https://ffmpeg.org/mailman/listinfo/ffmpeg-devel > > To unsubscribe, visit link above, or email > ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". > _______________________________________________ ffmpeg-devel mailing list ffmpeg-devel@ffmpeg.org https://ffmpeg.org/mailman/listinfo/ffmpeg-devel To unsubscribe, visit link above, or email ffmpeg-devel-request@ffmpeg.org with subject "unsubscribe". ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2023-06-06 22:24 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-05-30 1:33 [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Marvin Scholz 2023-05-30 1:58 ` [FFmpeg-devel] [PATCH v2] " Marvin Scholz 2023-05-30 12:30 ` Niklas Haas 2023-06-06 22:22 ` [FFmpeg-devel] [PATCH v3 1/4] " Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 2/4] lavfi/vf_xfade_vulkan: add wipeup transition Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 3/4] lavfi/vf_xfade_vulkan: add wipedown transition Marvin Scholz 2023-06-06 22:23 ` [FFmpeg-devel] [PATCH v3 4/4] lavfi/vf_xfade_vulkan: add slide transitions Marvin Scholz 2023-05-30 8:05 ` [FFmpeg-devel] [PATCH] libavfilter: add vf_xfade_vulkan Paul B Mahol
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel This inbox may be cloned and mirrored by anyone: git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git # If you have public-inbox 1.1+ installed, you may # initialize and index your mirror using the following commands: public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \ ffmpegdev@gitmailbox.com public-inbox-index ffmpegdev Example config snippet for mirrors. AGPL code for this site: git clone https://public-inbox.org/public-inbox.git