Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
 help / color / mirror / Atom feed
* [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] 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

* 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

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