PR #22875 opened by Lynne URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22875 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/22875.patch
Except nlmeans. >From 931d974a49e77d09ce343286951571c0efe091bb Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Tue, 21 Apr 2026 10:00:32 +0200 Subject: [PATCH 1/4] vf_blackdetect_vulkan: port to compile-time SPIR-V generation --- configure | 2 +- libavfilter/vf_blackdetect_vulkan.c | 82 ++++++------------------ libavfilter/vulkan/Makefile | 1 + libavfilter/vulkan/blackdetect.comp.glsl | 59 +++++++++++++++++ 4 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 libavfilter/vulkan/blackdetect.comp.glsl diff --git a/configure b/configure index d7fba1620b..8b41b49ba3 100755 --- a/configure +++ b/configure @@ -4146,7 +4146,7 @@ ass_filter_deps="libass" avgblur_opencl_filter_deps="opencl" avgblur_vulkan_filter_deps="vulkan spirv_compiler" azmq_filter_deps="libzmq" -blackdetect_vulkan_filter_deps="vulkan spirv_library" +blackdetect_vulkan_filter_deps="vulkan spirv_compiler" blackframe_filter_deps="gpl" blend_vulkan_filter_deps="vulkan spirv_library" boxblur_filter_deps="gpl" diff --git a/libavfilter/vf_blackdetect_vulkan.c b/libavfilter/vf_blackdetect_vulkan.c index 279b057148..1b8a7ce0ed 100644 --- a/libavfilter/vf_blackdetect_vulkan.c +++ b/libavfilter/vf_blackdetect_vulkan.c @@ -19,13 +19,14 @@ */ #include <float.h> -#include "libavutil/vulkan_spirv.h" #include "libavutil/opt.h" #include "libavutil/timestamp.h" #include "vulkan_filter.h" #include "filters.h" -#include "video.h" + +extern const unsigned char ff_blackdetect_comp_spv_data[]; +extern const unsigned int ff_blackdetect_comp_spv_len; typedef struct BlackDetectVulkanContext { FFVulkanContext vkctx; @@ -56,14 +57,8 @@ typedef struct BlackDetectBuf { static av_cold int init_filter(AVFilterContext *ctx) { int err; - uint8_t *spv_data; - size_t spv_len; - void *spv_opaque = NULL; BlackDetectVulkanContext *s = ctx->priv; FFVulkanContext *vkctx = &s->vkctx; - FFVulkanShader *shd; - FFVkSPIRVCompiler *spv; - FFVulkanDescriptorSetBinding *desc; const int plane = s->alpha ? 3 : 0; const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format); @@ -72,12 +67,6 @@ static av_cold int init_filter(AVFilterContext *ctx) return AVERROR(ENOTSUP); } - spv = ff_vk_spirv_init(); - if (!spv) { - av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); - return AVERROR_EXTERNAL; - } - s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0); if (!s->qf) { av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n"); @@ -86,61 +75,33 @@ static av_cold int init_filter(AVFilterContext *ctx) } RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL)); - RET(ff_vk_shader_init(vkctx, &s->shd, "blackdetect", - VK_SHADER_STAGE_COMPUTE_BIT, - (const char *[]) { "GL_KHR_shader_subgroup_ballot" }, 1, - 32, 32, 1, - 0)); - shd = &s->shd; - GLSLC(0, layout(push_constant, std430) uniform pushConstants { ); - GLSLC(1, float threshold; ); - GLSLC(0, }; ); + SPEC_LIST_CREATE(sl, 2, 2*sizeof(uint32_t)) + SPEC_LIST_ADD(sl, 0, 32, plane); + SPEC_LIST_ADD(sl, 1, 32, SLICES); - ff_vk_shader_add_push_const(shd, 0, sizeof(BlackDetectPushData), + ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, sl, + (int []) { 32, 32, 1 }, 0); + + ff_vk_shader_add_push_const(&s->shd, 0, sizeof(BlackDetectPushData), VK_SHADER_STAGE_COMPUTE_BIT); - desc = (FFVulkanDescriptorSetBinding []) { - { - .name = "input_img", + const FFVulkanDescriptorSetBinding desc[] = { + { /* input_img */ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = av_pix_fmt_count_planes(s->vkctx.input_format), .stages = VK_SHADER_STAGE_COMPUTE_BIT, - }, { - .name = "sum_buffer", + .elems = av_pix_fmt_count_planes(s->vkctx.input_format), + }, + { /* sum_buffer */ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .stages = VK_SHADER_STAGE_COMPUTE_BIT, - .buf_content = "uint slice_sum[];", } }; + ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0); - RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0)); - - GLSLC(0, shared uint wg_sum; ); - GLSLC(0, ); - GLSLC(0, void main() ); - GLSLC(0, { ); - GLSLC(1, wg_sum = 0u; ); - GLSLC(1, barrier(); ); - GLSLC(0, ); - GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); - GLSLF(1, if (!IS_WITHIN(pos, imageSize(input_img[%d]))) ,plane); - GLSLC(2, return; ); - GLSLF(1, float value = imageLoad(input_img[%d], pos).x; ,plane); - GLSLC(1, uvec4 isblack = subgroupBallot(value <= threshold); ); - GLSLC(1, if (subgroupElect()) ); - GLSLC(2, atomicAdd(wg_sum, subgroupBallotBitCount(isblack)); ); - GLSLC(1, barrier(); ); - GLSLC(1, if (gl_LocalInvocationIndex == 0u) ); - GLSLF(2, atomicAdd(slice_sum[gl_WorkGroupID.x %% %du], wg_sum); ,SLICES); - GLSLC(0, } ); - - RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main", - &spv_opaque)); - RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main")); + RET(ff_vk_shader_link(vkctx, &s->shd, + ff_blackdetect_comp_spv_data, + ff_blackdetect_comp_spv_len, "main")); RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd)); @@ -148,11 +109,6 @@ static av_cold int init_filter(AVFilterContext *ctx) s->initialized = 1; fail: - if (spv_opaque) - spv->free_shader(spv, &spv_opaque); - if (spv) - spv->uninit(&spv); - return err; } diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile index 59c7de46ce..df81664716 100644 --- a/libavfilter/vulkan/Makefile +++ b/libavfilter/vulkan/Makefile @@ -2,6 +2,7 @@ clean:: $(RM) $(CLEANSUFFIXES:%=libavfilter/vulkan/%) OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o +OBJS-$(CONFIG_BLACKDETECT_VULKAN_FILTER) += vulkan/blackdetect.comp.spv.o OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o OBJS-$(CONFIG_CHROMABER_VULKAN_FILTER) += vulkan/chromaber.comp.spv.o OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o diff --git a/libavfilter/vulkan/blackdetect.comp.glsl b/libavfilter/vulkan/blackdetect.comp.glsl new file mode 100644 index 0000000000..db54f123d7 --- /dev/null +++ b/libavfilter/vulkan/blackdetect.comp.glsl @@ -0,0 +1,59 @@ +/* + * Copyright 2025 (c) Niklas Haas + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma shader_stage(compute) + +#extension GL_EXT_shader_image_load_formatted : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_KHR_shader_subgroup_ballot : require +#extension GL_EXT_null_initializer : require + +layout (constant_id = 0) const uint plane = 0; +layout (constant_id = 1) const uint slices = 0; + +layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in; + +layout (set = 0, binding = 0) uniform readonly image2D input_img[]; +layout (set = 0, binding = 1, scalar) writeonly buffer sum_buffer { + uint slice_sum[]; +}; + +layout (push_constant, scalar) uniform pushConstants { + float threshold; +}; + +shared uint wg_sum = { }; + +void main() +{ + const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (any(greaterThanEqual(pos, imageSize(input_img[plane])))) + return; + + float value = imageLoad(input_img[plane], pos).x; + uvec4 isblack = subgroupBallot(value <= threshold); + if (subgroupElect()) + atomicAdd(wg_sum, subgroupBallotBitCount(isblack)); + + barrier(); + if (gl_LocalInvocationIndex == 0) + atomicAdd(slice_sum[gl_WorkGroupID.x % slices], wg_sum); +} -- 2.52.0 >From c356dec6510232dfcc157edc1faa66b4ec62728c Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Tue, 21 Apr 2026 10:32:38 +0200 Subject: [PATCH 2/4] vf_scdet_vulkan: port to compile-time SPIR-V generation --- configure | 2 +- libavfilter/vf_scdet_vulkan.c | 91 ++++++++---------------------- libavfilter/vulkan/Makefile | 1 + libavfilter/vulkan/scdet.comp.glsl | 62 ++++++++++++++++++++ 4 files changed, 87 insertions(+), 69 deletions(-) create mode 100644 libavfilter/vulkan/scdet.comp.glsl diff --git a/configure b/configure index 8b41b49ba3..2db990fbc1 100755 --- a/configure +++ b/configure @@ -4258,7 +4258,7 @@ frc_amf_filter_deps="amf windows_h" scale_qsv_filter_deps="libmfx" scale_qsv_filter_select="qsvvpp" scdet_filter_select="scene_sad" -scdet_vulkan_filter_deps="vulkan spirv_library" +scdet_vulkan_filter_deps="vulkan spirv_compiler" select_filter_select="scene_sad" sharpness_vaapi_filter_deps="vaapi" showcqt_filter_deps="avformat swscale" diff --git a/libavfilter/vf_scdet_vulkan.c b/libavfilter/vf_scdet_vulkan.c index ee2bf248a2..fc206d0fa3 100644 --- a/libavfilter/vf_scdet_vulkan.c +++ b/libavfilter/vf_scdet_vulkan.c @@ -19,13 +19,15 @@ */ #include "libavutil/avassert.h" -#include "libavutil/vulkan_spirv.h" #include "libavutil/opt.h" #include "libavutil/timestamp.h" #include "vulkan_filter.h" #include "filters.h" +extern const unsigned char ff_scdet_comp_spv_data[]; +extern const unsigned int ff_scdet_comp_spv_len; + typedef struct SceneDetectVulkanContext { FFVulkanContext vkctx; @@ -52,26 +54,14 @@ typedef struct SceneDetectBuf { static av_cold int init_filter(AVFilterContext *ctx) { int err; - uint8_t *spv_data; - size_t spv_len; - void *spv_opaque = NULL; SceneDetectVulkanContext *s = ctx->priv; FFVulkanContext *vkctx = &s->vkctx; - FFVulkanShader *shd; - FFVkSPIRVCompiler *spv; - FFVulkanDescriptorSetBinding *desc; const AVPixFmtDescriptor *pixdesc = av_pix_fmt_desc_get(s->vkctx.input_format); const int lumaonly = !(pixdesc->flags & AV_PIX_FMT_FLAG_RGB) && (pixdesc->flags & AV_PIX_FMT_FLAG_PLANAR); s->nb_planes = lumaonly ? 1 : av_pix_fmt_count_planes(s->vkctx.input_format); - spv = ff_vk_spirv_init(); - if (!spv) { - av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); - return AVERROR_EXTERNAL; - } - s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0); if (!s->qf) { av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n"); @@ -80,76 +70,41 @@ static av_cold int init_filter(AVFilterContext *ctx) } RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL)); - RET(ff_vk_shader_init(vkctx, &s->shd, "scdet", - VK_SHADER_STAGE_COMPUTE_BIT, - (const char *[]) { "GL_KHR_shader_subgroup_arithmetic" }, 1, - 32, 32, 1, - 0)); - shd = &s->shd; - desc = (FFVulkanDescriptorSetBinding []) { - { - .name = "prev_img", + SPEC_LIST_CREATE(sl, 2, 2*sizeof(uint32_t)) + SPEC_LIST_ADD(sl, 0, 32, s->nb_planes); + SPEC_LIST_ADD(sl, 1, 32, SLICES); + + ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + (int []) { 32, 32, 1 }, 0); + + const FFVulkanDescriptorSetBinding desc[] = { + { /* prev_img */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = av_pix_fmt_count_planes(s->vkctx.input_format), + }, + { /* cur_img */ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = av_pix_fmt_count_planes(s->vkctx.input_format), .stages = VK_SHADER_STAGE_COMPUTE_BIT, - }, { - .name = "cur_img", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_UINT), - .mem_quali = "readonly", - .dimensions = 2, .elems = av_pix_fmt_count_planes(s->vkctx.input_format), - .stages = VK_SHADER_STAGE_COMPUTE_BIT, - }, { - .name = "sad_buffer", + }, + { /* sad_buffer */ .type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, .stages = VK_SHADER_STAGE_COMPUTE_BIT, - .buf_content = "uint frame_sad[];", } }; + ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0); - RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0)); - - GLSLC(0, shared uint wg_sum; ); - GLSLC(0, void main() ); - GLSLC(0, { ); - GLSLF(1, const uint slice = gl_WorkGroupID.x %% %u; ,SLICES); - GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); - GLSLC(1, wg_sum = 0; ); - GLSLC(1, barrier(); ); - for (int i = 0; i < s->nb_planes; i++) { - GLSLF(1, if (IS_WITHIN(pos, imageSize(cur_img[%d]))) { ,i); - GLSLF(2, uvec4 prev = imageLoad(prev_img[%d], pos); ,i); - GLSLF(2, uvec4 cur = imageLoad(cur_img[%d], pos); ,i); - GLSLC(2, uvec4 sad = abs(ivec4(cur) - ivec4(prev)); ); - GLSLC(2, uint sum = subgroupAdd(sad.x + sad.y + sad.z); ); - GLSLC(2, if (subgroupElect()) ); - GLSLC(3, atomicAdd(wg_sum, sum); ); - GLSLC(1, } ); - } - GLSLC(1, barrier(); ); - GLSLC(1, if (gl_LocalInvocationIndex == 0) ); - GLSLC(2, atomicAdd(frame_sad[slice], wg_sum); ); - GLSLC(0, } ); - - RET(spv->compile_shader(vkctx, spv, &s->shd, &spv_data, &spv_len, "main", - &spv_opaque)); - RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main")); + RET(ff_vk_shader_link(vkctx, &s->shd, + ff_scdet_comp_spv_data, + ff_scdet_comp_spv_len, "main")); RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd)); s->initialized = 1; fail: - if (spv_opaque) - spv->free_shader(spv, &spv_opaque); - if (spv) - spv->uninit(&spv); - return err; } diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile index df81664716..317c4868e0 100644 --- a/libavfilter/vulkan/Makefile +++ b/libavfilter/vulkan/Makefile @@ -8,6 +8,7 @@ OBJS-$(CONFIG_CHROMABER_VULKAN_FILTER) += vulkan/chromaber.comp.spv.o OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o OBJS-$(CONFIG_GBLUR_VULKAN_FILTER) += vulkan/gblur.comp.spv.o OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o +OBJS-$(CONFIG_SCDET_VULKAN_FILTER) += vulkan/scdet.comp.spv.o OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o OBJS-$(CONFIG_TRANSPOSE_VULKAN_FILTER) += vulkan/transpose.comp.spv.o OBJS-$(CONFIG_V360_VULKAN_FILTER) += vulkan/v360.comp.spv.o diff --git a/libavfilter/vulkan/scdet.comp.glsl b/libavfilter/vulkan/scdet.comp.glsl new file mode 100644 index 0000000000..deadeec67a --- /dev/null +++ b/libavfilter/vulkan/scdet.comp.glsl @@ -0,0 +1,62 @@ +/* + * Copyright 2025 (c) Niklas Haas + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#pragma shader_stage(compute) + +#extension GL_EXT_shader_image_load_formatted : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_nonuniform_qualifier : require +#extension GL_KHR_shader_subgroup_arithmetic : require +#extension GL_EXT_null_initializer : require + +layout (constant_id = 0) const uint planes = 0; +layout (constant_id = 1) const uint slices = 0; + +layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in; + +layout (set = 0, binding = 0) uniform readonly uimage2D prev_img[]; +layout (set = 0, binding = 1) uniform readonly uimage2D cur_img[]; +layout (set = 0, binding = 2, scalar) writeonly buffer sad_buffer { + uint frame_sad[]; +}; + +shared uint wg_sum = { }; + +void main() +{ + const uint slice = gl_WorkGroupID.x % slices; + const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + + for (uint i = 0; i < planes; i++) { + const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + if (all(lessThan(pos, imageSize(cur_img[i])))) { + uvec4 prev = imageLoad(prev_img[i], pos); + uvec4 cur = imageLoad(cur_img[i], pos); + uvec4 sad = abs(ivec4(cur) - ivec4(prev)); + uint sum = subgroupAdd(sad.x + sad.y + sad.z); + if (subgroupElect()) + atomicAdd(wg_sum, sum); + } + } + + barrier(); + if (gl_LocalInvocationIndex == 0) + atomicAdd(frame_sad[slice], wg_sum); +} -- 2.52.0 >From d6828f5294d14368ff0ce9d1a2dce1beaad2d778 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Tue, 21 Apr 2026 15:39:35 +0200 Subject: [PATCH 3/4] vf_blend_vulkan: port to compile-time SPIR-V generation --- configure | 2 +- libavfilter/vf_blend_vulkan.c | 225 +++++++++++------------------ libavfilter/vulkan/Makefile | 1 + libavfilter/vulkan/blend.comp.glsl | 178 +++++++++++++++++++++++ 4 files changed, 263 insertions(+), 143 deletions(-) create mode 100644 libavfilter/vulkan/blend.comp.glsl diff --git a/configure b/configure index 2db990fbc1..2394a54fae 100755 --- a/configure +++ b/configure @@ -4148,7 +4148,7 @@ avgblur_vulkan_filter_deps="vulkan spirv_compiler" azmq_filter_deps="libzmq" blackdetect_vulkan_filter_deps="vulkan spirv_compiler" blackframe_filter_deps="gpl" -blend_vulkan_filter_deps="vulkan spirv_library" +blend_vulkan_filter_deps="vulkan spirv_compiler" boxblur_filter_deps="gpl" boxblur_opencl_filter_deps="opencl gpl" bs2b_filter_deps="libbs2b" diff --git a/libavfilter/vf_blend_vulkan.c b/libavfilter/vf_blend_vulkan.c index 4f1d4644c3..7cfc85b150 100644 --- a/libavfilter/vf_blend_vulkan.c +++ b/libavfilter/vf_blend_vulkan.c @@ -21,8 +21,6 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavutil/random_seed.h" -#include "libavutil/vulkan_spirv.h" #include "libavutil/opt.h" #include "vulkan_filter.h" @@ -31,14 +29,15 @@ #include "blend.h" #include "video.h" +extern const unsigned char ff_blend_comp_spv_data[]; +extern const unsigned int ff_blend_comp_spv_len; + #define IN_TOP 0 #define IN_BOTTOM 1 typedef struct FilterParamsVulkan { - const char *blend; - const char *blend_func; - double opacity; - enum BlendMode mode; + float opacity[4]; + enum BlendMode mode[4]; } FilterParamsVulkan; typedef struct BlendVulkanContext { @@ -50,63 +49,21 @@ typedef struct BlendVulkanContext { AVVulkanDeviceQueueFamily *qf; FFVulkanShader shd; - FilterParamsVulkan params[4]; + FilterParamsVulkan params; double all_opacity; /* enum BlendMode */ int all_mode; } BlendVulkanContext; -#define DEFINE_BLEND_MODE(MODE, EXPR) \ -static const char blend_##MODE[] = "blend_"#MODE; \ -static const char blend_##MODE##_func[] = { \ - C(0, vec4 blend_##MODE(vec4 top, vec4 bottom, float opacity) { ) \ - C(1, vec4 dst = EXPR; ) \ - C(1, return dst; ) \ - C(0, } ) \ -}; - -#define A top -#define B bottom - -#define FN(EXPR) A + ((EXPR) - A) * opacity - -DEFINE_BLEND_MODE(NORMAL, A * opacity + B * (1.0f - opacity)) -DEFINE_BLEND_MODE(MULTIPLY, FN(1.0f * A * B / 1.0f)) - -static inline void init_blend_func(FilterParamsVulkan *param) -{ -#define CASE(MODE) case BLEND_##MODE: \ - param->blend = blend_##MODE;\ - param->blend_func = blend_##MODE##_func; \ - break; - - switch (param->mode) { - CASE(NORMAL) - CASE(MULTIPLY) - default: param->blend = NULL; break; - } - -#undef CASE -} - static int config_params(AVFilterContext *avctx) { BlendVulkanContext *s = avctx->priv; - for (int plane = 0; plane < FF_ARRAY_ELEMS(s->params); plane++) { - FilterParamsVulkan *param = &s->params[plane]; - + for (int plane = 0; plane < 4; plane++) { if (s->all_mode >= 0) - param->mode = s->all_mode; + s->params.mode[plane] = s->all_mode; if (s->all_opacity < 1) - param->opacity = s->all_opacity; - - init_blend_func(param); - if (!param->blend) { - av_log(avctx, AV_LOG_ERROR, - "Currently the blend mode specified is not supported yet.\n"); - return AVERROR(EINVAL); - } + s->params.opacity[plane] = s->all_opacity; } return 0; @@ -125,22 +82,11 @@ static int process_command(AVFilterContext *ctx, const char *cmd, const char *ar static av_cold int init_filter(AVFilterContext *avctx) { int err = 0; - uint8_t *spv_data; - size_t spv_len; - void *spv_opaque = NULL; BlendVulkanContext *s = avctx->priv; FFVulkanContext *vkctx = &s->vkctx; const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); - FFVulkanShader *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; - } - s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0); if (!s->qf) { av_log(avctx, AV_LOG_ERROR, "Device has no compute queues\n"); @@ -149,86 +95,41 @@ static av_cold int init_filter(AVFilterContext *avctx) } RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL)); - RET(ff_vk_shader_init(vkctx, &s->shd, "blend", - VK_SHADER_STAGE_COMPUTE_BIT, - NULL, 0, - 32, 32, 1, - 0)); + + ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + (int []) { 32, 32, 1 }, 0); + + ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->params), + VK_SHADER_STAGE_COMPUTE_BIT); desc = (FFVulkanDescriptorSetBinding []) { - { - .name = "top_images", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + { /* top_images */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, - { - .name = "bottom_images", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + { /* bottom_images */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, - { - .name = "output_images", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format, FF_VK_REP_FLOAT), - .mem_quali = "writeonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + { /* output_images */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, }; + ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0); - RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0)); - - for (int i = 0, j = 0; i < planes; i++) { - for (j = 0; j < i; j++) - if (s->params[i].blend_func == s->params[j].blend_func) - break; - /* note: the bracket is needed, for GLSLD is a macro with multiple statements. */ - if (j == i) { - GLSLD(s->params[i].blend_func); - } - } - - GLSLC(0, void main() ); - GLSLC(0, { ); - GLSLC(1, ivec2 size; ); - GLSLC(1, const ivec2 pos = ivec2(gl_GlobalInvocationID.xy); ); - for (int i = 0; i < planes; i++) { - GLSLC(0, ); - GLSLF(1, size = imageSize(output_images[%i]); ,i); - GLSLC(1, if (IS_WITHIN(pos, size)) { ); - GLSLF(2, const vec4 top = imageLoad(top_images[%i], pos); ,i); - GLSLF(2, const vec4 bottom = imageLoad(bottom_images[%i], pos); ,i); - GLSLF(2, const float opacity = %f; ,s->params[i].opacity); - GLSLF(2, vec4 dst = %s(top, bottom, opacity); ,s->params[i].blend); - GLSLC(0, ); - GLSLF(2, imageStore(output_images[%i], pos, dst); ,i); - GLSLC(1, } ); - } - GLSLC(0, } ); - - RET(spv->compile_shader(vkctx, spv, shd, &spv_data, &spv_len, "main", - &spv_opaque)); - RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main")); + RET(ff_vk_shader_link(vkctx, &s->shd, + ff_blend_comp_spv_data, + ff_blend_comp_spv_len, "main")); RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd)); s->initialized = 1; fail: - if (spv_opaque) - spv->free_shader(spv, &spv_opaque); - if (spv) - spv->uninit(&spv); - return err; } @@ -265,7 +166,7 @@ static int blend_frame(FFFrameSync *fs) RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, out, (AVFrame *[]){ top, bottom }, 2, - VK_NULL_HANDLE, 1, NULL, 0)); + VK_NULL_HANDLE, 1, &s->params, sizeof(s->params))); return ff_filter_frame(outlink, out); @@ -342,19 +243,59 @@ static int activate(AVFilterContext *avctx) #define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM) static const AVOption blend_vulkan_options[] = { - { "c0_mode", "set component #0 blend mode", OFFSET(params[0].mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, - { "c1_mode", "set component #1 blend mode", OFFSET(params[1].mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, - { "c2_mode", "set component #2 blend mode", OFFSET(params[2].mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, - { "c3_mode", "set component #3 blend mode", OFFSET(params[3].mode), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, + { "c0_mode", "set component #0 blend mode", OFFSET(params.mode[0]), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, + { "c1_mode", "set component #1 blend mode", OFFSET(params.mode[1]), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, + { "c2_mode", "set component #2 blend mode", OFFSET(params.mode[2]), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, + { "c3_mode", "set component #3 blend mode", OFFSET(params.mode[3]), AV_OPT_TYPE_INT, { .i64 = 0 }, 0, BLEND_NB - 1, FLAGS, .unit = "mode" }, { "all_mode", "set blend mode for all components", OFFSET(all_mode), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, BLEND_NB - 1, FLAGS, .unit = "mode" }, - { "normal", "", 0, AV_OPT_TYPE_CONST, { .i64 = BLEND_NORMAL }, 0, 0, FLAGS, .unit = "mode" }, - { "multiply", "", 0, AV_OPT_TYPE_CONST, { .i64 = BLEND_MULTIPLY }, 0, 0, FLAGS, .unit = "mode" }, + { "addition", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_ADDITION}, 0, 0, FLAGS, .unit = "mode" }, + { "addition128","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GRAINMERGE}, 0, 0, FLAGS, .unit = "mode" }, + { "grainmerge", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GRAINMERGE}, 0, 0, FLAGS, .unit = "mode" }, + { "and", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_AND}, 0, 0, FLAGS, .unit = "mode" }, + { "average", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_AVERAGE}, 0, 0, FLAGS, .unit = "mode" }, + { "burn", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_BURN}, 0, 0, FLAGS, .unit = "mode" }, + { "darken", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_DARKEN}, 0, 0, FLAGS, .unit = "mode" }, + { "difference", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_DIFFERENCE}, 0, 0, FLAGS, .unit = "mode" }, + { "difference128", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GRAINEXTRACT}, 0, 0, FLAGS, .unit = "mode" }, + { "grainextract", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GRAINEXTRACT}, 0, 0, FLAGS, .unit = "mode" }, + { "divide", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_DIVIDE}, 0, 0, FLAGS, .unit = "mode" }, + { "dodge", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_DODGE}, 0, 0, FLAGS, .unit = "mode" }, + { "exclusion", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_EXCLUSION}, 0, 0, FLAGS, .unit = "mode" }, + { "extremity", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_EXTREMITY}, 0, 0, FLAGS, .unit = "mode" }, + { "freeze", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_FREEZE}, 0, 0, FLAGS, .unit = "mode" }, + { "glow", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GLOW}, 0, 0, FLAGS, .unit = "mode" }, + { "hardlight", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_HARDLIGHT}, 0, 0, FLAGS, .unit = "mode" }, + { "hardmix", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_HARDMIX}, 0, 0, FLAGS, .unit = "mode" }, + { "heat", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_HEAT}, 0, 0, FLAGS, .unit = "mode" }, + { "lighten", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_LIGHTEN}, 0, 0, FLAGS, .unit = "mode" }, + { "linearlight","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_LINEARLIGHT},0, 0, FLAGS, .unit = "mode" }, + { "multiply", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_MULTIPLY}, 0, 0, FLAGS, .unit = "mode" }, + { "multiply128","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_MULTIPLY128},0, 0, FLAGS, .unit = "mode" }, + { "negation", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_NEGATION}, 0, 0, FLAGS, .unit = "mode" }, + { "normal", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_NORMAL}, 0, 0, FLAGS, .unit = "mode" }, + { "or", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_OR}, 0, 0, FLAGS, .unit = "mode" }, + { "overlay", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_OVERLAY}, 0, 0, FLAGS, .unit = "mode" }, + { "phoenix", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_PHOENIX}, 0, 0, FLAGS, .unit = "mode" }, + { "pinlight", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_PINLIGHT}, 0, 0, FLAGS, .unit = "mode" }, + { "reflect", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_REFLECT}, 0, 0, FLAGS, .unit = "mode" }, + { "screen", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_SCREEN}, 0, 0, FLAGS, .unit = "mode" }, + { "softlight", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_SOFTLIGHT}, 0, 0, FLAGS, .unit = "mode" }, + { "subtract", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_SUBTRACT}, 0, 0, FLAGS, .unit = "mode" }, + { "vividlight", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_VIVIDLIGHT}, 0, 0, FLAGS, .unit = "mode" }, + { "xor", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_XOR}, 0, 0, FLAGS, .unit = "mode" }, + { "softdifference","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_SOFTDIFFERENCE}, 0, 0, FLAGS, .unit = "mode" }, + { "geometric", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_GEOMETRIC}, 0, 0, FLAGS, .unit = "mode" }, + { "harmonic", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_HARMONIC}, 0, 0, FLAGS, .unit = "mode" }, + { "bleach", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_BLEACH}, 0, 0, FLAGS, .unit = "mode" }, + { "stain", "", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_STAIN}, 0, 0, FLAGS, .unit = "mode" }, + { "interpolate","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_INTERPOLATE},0, 0, FLAGS, .unit = "mode" }, + { "hardoverlay","", 0, AV_OPT_TYPE_CONST, {.i64=BLEND_HARDOVERLAY},0, 0, FLAGS, .unit = "mode" }, - { "c0_opacity", "set color component #0 opacity", OFFSET(params[0].opacity), AV_OPT_TYPE_DOUBLE, { .dbl = 1 }, 0, 1, FLAGS }, - { "c1_opacity", "set color component #1 opacity", OFFSET(params[1].opacity), AV_OPT_TYPE_DOUBLE, { .dbl = 1 }, 0, 1, FLAGS }, - { "c2_opacity", "set color component #2 opacity", OFFSET(params[2].opacity), AV_OPT_TYPE_DOUBLE, { .dbl = 1 }, 0, 1, FLAGS }, - { "c3_opacity", "set color component #3 opacity", OFFSET(params[3].opacity), AV_OPT_TYPE_DOUBLE, { .dbl = 1 }, 0, 1, FLAGS }, - { "all_opacity", "set opacity for all color components", OFFSET(all_opacity), AV_OPT_TYPE_DOUBLE, { .dbl = 1 }, 0, 1, FLAGS }, + { "c0_opacity", "set color component #0 opacity", OFFSET(params.opacity[0]), AV_OPT_TYPE_FLOAT, { .dbl = 1.0 }, 0, 1, FLAGS }, + { "c1_opacity", "set color component #1 opacity", OFFSET(params.opacity[1]), AV_OPT_TYPE_FLOAT, { .dbl = 1.0 }, 0, 1, FLAGS }, + { "c2_opacity", "set color component #2 opacity", OFFSET(params.opacity[2]), AV_OPT_TYPE_FLOAT, { .dbl = 1.0 }, 0, 1, FLAGS }, + { "c3_opacity", "set color component #3 opacity", OFFSET(params.opacity[3]), AV_OPT_TYPE_FLOAT, { .dbl = 1.0 }, 0, 1, FLAGS }, + { "all_opacity", "set opacity for all color components", OFFSET(all_opacity), AV_OPT_TYPE_FLOAT, { .dbl = 1.0 }, 0, 1, FLAGS }, { NULL } }; diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile index 317c4868e0..f68ca4bc3d 100644 --- a/libavfilter/vulkan/Makefile +++ b/libavfilter/vulkan/Makefile @@ -3,6 +3,7 @@ clean:: OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o OBJS-$(CONFIG_BLACKDETECT_VULKAN_FILTER) += vulkan/blackdetect.comp.spv.o +OBJS-$(CONFIG_BLEND_VULKAN_FILTER) += vulkan/blend.comp.spv.o OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o OBJS-$(CONFIG_CHROMABER_VULKAN_FILTER) += vulkan/chromaber.comp.spv.o OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o diff --git a/libavfilter/vulkan/blend.comp.glsl b/libavfilter/vulkan/blend.comp.glsl new file mode 100644 index 0000000000..5ba2eeaf7f --- /dev/null +++ b/libavfilter/vulkan/blend.comp.glsl @@ -0,0 +1,178 @@ +/* + * 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 + */ + +#pragma shader_stage(compute) + +#extension GL_EXT_shader_image_load_formatted : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_nonuniform_qualifier : require + +layout (constant_id = 0) const uint planes = 0; + +layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in; + +layout (set = 0, binding = 0) uniform readonly image2D top_img[]; +layout (set = 0, binding = 0) uniform readonly image2D bottom_img[]; +layout (set = 0, binding = 1) uniform writeonly image2D output_img[]; + +layout (push_constant, scalar) uniform pushConstants { + vec4 opacity; + ivec4 blend_mode; +}; + +#define MULTIPLY(x, a, b) ((x) * (((a) * (b)) / 1.0)) +#define SCREEN(x, a, b) (1.0 - (x) * ((1.0 - (a)) * (1.0 - (b)) / 1.0)) +#define GEOMETRIC(a, b) (sqrt(max(A, vec4(0)) * max(B, vec4(0)))) +#define MDIV 0.125f + +#define A top +#define B bottom +#define MAX vec4(1.0f) +#define HALF vec4(0.5f) +#define M_PI radians(180) +#define FLT_MIN (1.175494351e-38) + +vec4 safe_div(vec4 a, vec4 b) +{ + vec4 is_zero = 1.0 - step(FLT_MIN, abs(b)); + return mix(a / max(abs(b), vec4(FLT_MIN)), vec4(1.0), is_zero); +} + +vec4 burn(vec4 a, vec4 b) +{ + vec4 res = max(vec4(0.0), vec4(1.0) - (vec4(1.0) - b) / max(a, vec4(FLT_MIN))); + return mix(res, a, vec4(lessThanEqual(a, vec4(0.0)))); +} + +vec4 dodge(vec4 b, vec4 a) { + vec4 result = min(vec4(1.0), b / max(vec4(1.0) - a, vec4(FLT_MIN))); + return mix(result, a, vec4(greaterThanEqual(a, vec4(1.0)))); +} + +vec4 blend_normal(vec4 top, vec4 bottom, float a) +{ + return top * a + bottom * (1.0f - a); +} + +#define fn(name, expr) \ +vec4 blend_ ## name(vec4 top, vec4 bottom, float a) \ +{ \ + return top + ((expr) - top) * a; \ +} + +fn(addition, min(MAX, A + B)) +fn(grainmerge, (A + B - HALF)) +fn(multiply, MULTIPLY(1, A, B)) +fn(multiply128, ((A - HALF) * B / MDIV + HALF)) +fn(negation, MAX - abs(MAX - A - B)) +fn(extremity, abs(MAX - A - B)) +fn(grainextract, (HALF + A - B)) +fn(screen, SCREEN(1, A, B)) +fn(overlay, mix(SCREEN(2, A, B), MULTIPLY(2, A, B), lessThan(A, HALF))) +fn(hardlight, mix(SCREEN(2, B, A), MULTIPLY(2, B, A), lessThan(B, HALF))) +fn(hardmix, mix(MAX, vec4(0.0), lessThan(A, MAX - B))) +fn(heat, MAX - min(safe_div((MAX - B) * (MAX - B), A), MAX)) +fn(freeze, MAX - min(safe_div((MAX - A) * (MAX - A), B), MAX)) +fn(divide, safe_div(MAX * A, B)) +fn(dodge, dodge(A, B)) +fn(burn, burn(A, B)) +fn(softlight, (A * A + 2 * B * A * (MAX - A))) +fn(exclusion, A + B - 2 * A * B) +fn(pinlight, mix(max(A, 2 * (B - HALF)), min(A, 2 * B), lessThan(B, HALF))) +fn(phoenix, MAX - abs(A - B)) +fn(reflect, min(MAX, safe_div(A * A, MAX - B))) +fn(glow, min(MAX, safe_div(B * B, MAX - A))) +fn(vividlight, mix(dodge(2 * (A - HALF), B), burn(2 * A, B), lessThan(A, HALF))) +fn(linearlight, B + 2 * A - MAX) +fn(softdifference, mix(mix(safe_div(B - A, B), vec4(0.0), equal(B, vec4(0.0))), \ + mix(safe_div(A - B, MAX - B), vec4(0.0), equal(B, MAX)), \ + greaterThan(A, B))) +fn(bleach, (MAX - B) + (MAX - A) - MAX) +fn(stain, 2 * MAX - A - B) +fn(interpolate, (2 - cos(A * M_PI) - cos(B * M_PI)) * 0.25) +fn(hardoverlay, min(MAX, mix(safe_div(B, 2 * (MAX - A)), 2 * A * B, lessThanEqual(A, HALF)))) +fn(average, (A + B) / 2) +fn(subtract, max(vec4(0), A - B)) +fn(difference, abs(A - B)) +fn(darken, min(A, B)) +fn(lighten, max(A, B)) +fn(and, uintBitsToFloat(floatBitsToUint(A) & floatBitsToUint(B))) +fn(or, uintBitsToFloat(floatBitsToUint(A) | floatBitsToUint(B))) +fn(xor, uintBitsToFloat(floatBitsToUint(A) ^ floatBitsToUint(B))) +fn(geometric, GEOMETRIC(A, B)) +fn(harmonic, 2 * A * B / max(A + B, vec4(FLT_MIN))) + +void main() +{ + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + + for (uint i = 0; i < planes; i++) { + if (any(greaterThanEqual(pos, imageSize(output_img[i])))) + return; + + vec4 top = imageLoad(top_img[i], pos); + vec4 bottom = imageLoad(bottom_img[i], pos); + float a = opacity[i]; + vec4 res; + + switch (blend_mode[i]) { + case 0 /* BLEND_NORMAL */: res = blend_normal(top, bottom, a); break; + case 1 /* BLEND_ADDITION */: res = blend_addition(top, bottom, a); break; + case 2 /* BLEND_AND */: res = blend_and(top, bottom, a); break; + case 3 /* BLEND_AVERAGE */: res = blend_average(top, bottom, a); break; + case 4 /* BLEND_BURN */: res = blend_burn(top, bottom, a); break; + case 5 /* BLEND_DARKEN */: res = blend_darken(top, bottom, a); break; + case 6 /* BLEND_DIFFERENCE */: res = blend_difference(top, bottom, a); break; + case 7 /* BLEND_GRAINEXTRACT */: res = blend_grainextract(top, bottom, a); break; + case 8 /* BLEND_DIVIDE */: res = blend_divide(top, bottom, a); break; + case 9 /* BLEND_DODGE */: res = blend_dodge(top, bottom, a); break; + case 10 /* BLEND_EXCLUSION */: res = blend_exclusion(top, bottom, a); break; + case 11 /* BLEND_HARDLIGHT */: res = blend_hardlight(top, bottom, a); break; + case 12 /* BLEND_LIGHTEN */: res = blend_lighten(top, bottom, a); break; + case 13 /* BLEND_MULTIPLY */: res = blend_multiply(top, bottom, a); break; + case 14 /* BLEND_NEGATION */: res = blend_negation(top, bottom, a); break; + case 15 /* BLEND_OR */: res = blend_or(top, bottom, a); break; + case 16 /* BLEND_OVERLAY */: res = blend_overlay(top, bottom, a); break; + case 17 /* BLEND_PHOENIX */: res = blend_phoenix(top, bottom, a); break; + case 18 /* BLEND_PINLIGHT */: res = blend_pinlight(top, bottom, a); break; + case 19 /* BLEND_REFLECT */: res = blend_reflect(top, bottom, a); break; + case 20 /* BLEND_SCREEN */: res = blend_screen(top, bottom, a); break; + case 21 /* BLEND_SOFTLIGHT */: res = blend_softlight(top, bottom, a); break; + case 22 /* BLEND_SUBTRACT */: res = blend_subtract(top, bottom, a); break; + case 23 /* BLEND_VIVIDLIGHT */: res = blend_vividlight(top, bottom, a); break; + case 24 /* BLEND_XOR */: res = blend_xor(top, bottom, a); break; + case 25 /* BLEND_HARDMIX */: res = blend_hardmix(top, bottom, a); break; + case 26 /* BLEND_LINEARLIGHT */: res = blend_linearlight(top, bottom, a); break; + case 27 /* BLEND_GLOW */: res = blend_glow(top, bottom, a); break; + case 28 /* BLEND_GRAINMERGE */: res = blend_grainmerge(top, bottom, a); break; + case 29 /* BLEND_MULTIPLY128 */: res = blend_multiply128(top, bottom, a); break; + case 30 /* BLEND_HEAT */: res = blend_heat(top, bottom, a); break; + case 31 /* BLEND_FREEZE */: res = blend_freeze(top, bottom, a); break; + case 32 /* BLEND_EXTREMITY */: res = blend_extremity(top, bottom, a); break; + case 33 /* BLEND_SOFTDIFFERENCE */: res = blend_softdifference(top, bottom, a); break; + case 34 /* BLEND_GEOMETRIC */: res = blend_geometric(top, bottom, a); break; + case 35 /* BLEND_HARMONIC */: res = blend_harmonic(top, bottom, a); break; + case 36 /* BLEND_BLEACH */: res = blend_bleach(top, bottom, a); break; + case 37 /* BLEND_STAIN */: res = blend_stain(top, bottom, a); break; + case 38 /* BLEND_INTERPOLATE */: res = blend_interpolate(top, bottom, a); break; + case 39 /* BLEND_HARDOVERLAY */: res = blend_hardoverlay(top, bottom, a); break; + }; + + imageStore(output_img[i], pos, res); + } +} -- 2.52.0 >From 967054e6c275ede01d2c16208c95d779cee311a3 Mon Sep 17 00:00:00 2001 From: Lynne <[email protected]> Date: Tue, 21 Apr 2026 16:11:24 +0200 Subject: [PATCH 4/4] vf_overlay_vulkan: port to compile-time SPIR-V generation --- configure | 2 +- libavfilter/vf_overlay_vulkan.c | 133 ++++++--------------------- libavfilter/vulkan/Makefile | 1 + libavfilter/vulkan/overlay.comp.glsl | 81 ++++++++++++++++ 4 files changed, 111 insertions(+), 106 deletions(-) create mode 100644 libavfilter/vulkan/overlay.comp.glsl diff --git a/configure b/configure index 2394a54fae..928d7d1651 100755 --- a/configure +++ b/configure @@ -4233,7 +4233,7 @@ overlay_opencl_filter_deps="opencl" overlay_qsv_filter_deps="libmfx" overlay_qsv_filter_select="qsvvpp" overlay_vaapi_filter_deps="vaapi VAProcPipelineCaps_blend_flags" -overlay_vulkan_filter_deps="vulkan spirv_library" +overlay_vulkan_filter_deps="vulkan spirv_compiler" owdenoise_filter_deps="gpl" pad_opencl_filter_deps="opencl" pan_filter_deps="swresample" diff --git a/libavfilter/vf_overlay_vulkan.c b/libavfilter/vf_overlay_vulkan.c index e1a07e4bfd..246b4625c6 100644 --- a/libavfilter/vf_overlay_vulkan.c +++ b/libavfilter/vf_overlay_vulkan.c @@ -18,15 +18,16 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ -#include "libavutil/random_seed.h" #include "libavutil/opt.h" -#include "libavutil/vulkan_spirv.h" #include "vulkan_filter.h" #include "filters.h" #include "framesync.h" #include "video.h" +extern const unsigned char ff_overlay_comp_spv_data[]; +extern const unsigned int ff_overlay_comp_spv_len; + typedef struct OverlayVulkanContext { FFVulkanContext vkctx; FFFrameSync fs; @@ -38,8 +39,8 @@ typedef struct OverlayVulkanContext { /* Push constants / options */ struct { - int32_t o_offset[2*3]; - int32_t o_size[2*3]; + int32_t o_offset[2*4]; + int32_t o_size[2*4]; } opts; int overlay_x; @@ -48,57 +49,15 @@ typedef struct OverlayVulkanContext { int overlay_h; } OverlayVulkanContext; -static const char overlay_noalpha[] = { - C(0, void overlay_noalpha(int i, ivec2 pos) ) - C(0, { ) - C(1, if ((o_offset[i].x <= pos.x) && (o_offset[i].y <= pos.y) && - (pos.x < (o_offset[i].x + o_size[i].x)) && - (pos.y < (o_offset[i].y + o_size[i].y))) { ) - C(2, vec4 res = imageLoad(overlay_img[i], pos - o_offset[i]); ) - C(2, imageStore(output_img[i], pos, res); ) - C(1, } else { ) - C(2, vec4 res = imageLoad(main_img[i], pos); ) - C(2, imageStore(output_img[i], pos, res); ) - C(1, } ) - C(0, } ) -}; - -static const char overlay_alpha[] = { - C(0, void overlay_alpha_opaque(int i, ivec2 pos) ) - C(0, { ) - C(1, vec4 res = imageLoad(main_img[i], pos); ) - C(1, if ((o_offset[i].x <= pos.x) && (o_offset[i].y <= pos.y) && - (pos.x < (o_offset[i].x + o_size[i].x)) && - (pos.y < (o_offset[i].y + o_size[i].y))) { ) - C(2, vec4 ovr = imageLoad(overlay_img[i], pos - o_offset[i]); ) - C(2, res = ovr * ovr.a + res * (1.0f - ovr.a); ) - C(2, res.a = 1.0f; ) - C(2, imageStore(output_img[i], pos, res); ) - C(1, } ) - C(1, imageStore(output_img[i], pos, res); ) - C(0, } ) -}; - static av_cold int init_filter(AVFilterContext *ctx) { int err; - uint8_t *spv_data; - size_t spv_len; - void *spv_opaque = NULL; OverlayVulkanContext *s = ctx->priv; FFVulkanContext *vkctx = &s->vkctx; const int planes = av_pix_fmt_count_planes(s->vkctx.output_format); const int ialpha = av_pix_fmt_desc_get(s->vkctx.input_format)->flags & AV_PIX_FMT_FLAG_ALPHA; const AVPixFmtDescriptor *pix_desc = av_pix_fmt_desc_get(s->vkctx.output_format); FFVulkanShader *shd = &s->shd; - FFVkSPIRVCompiler *spv; - FFVulkanDescriptorSetBinding *desc; - - spv = ff_vk_spirv_init(); - if (!spv) { - av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n"); - return AVERROR_EXTERNAL; - } s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0); if (!s->qf) { @@ -108,70 +67,39 @@ static av_cold int init_filter(AVFilterContext *ctx) } RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL)); - RET(ff_vk_shader_init(vkctx, &s->shd, "overlay", - VK_SHADER_STAGE_COMPUTE_BIT, - NULL, 0, - 32, 32, 1, - 0)); - GLSLC(0, layout(push_constant, std430) uniform pushConstants { ); - GLSLC(1, ivec2 o_offset[3]; ); - GLSLC(1, ivec2 o_size[3]; ); - GLSLC(0, }; ); - GLSLC(0, ); + SPEC_LIST_CREATE(sl, 2, 2*sizeof(uint32_t)) + SPEC_LIST_ADD(sl, 0, 32, planes); + SPEC_LIST_ADD(sl, 1, 32, ialpha); + + ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL, + (int []) { 32, 32, 1 }, 0); ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts), VK_SHADER_STAGE_COMPUTE_BIT); - desc = (FFVulkanDescriptorSetBinding []) { - { - .name = "main_img", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + const FFVulkanDescriptorSetBinding desc[] = { + { /* main_img */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, - { - .name = "overlay_img", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.input_format, FF_VK_REP_FLOAT), - .mem_quali = "readonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + { /* overlay_img */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, - { - .name = "output_img", - .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - .mem_layout = ff_vk_shader_rep_fmt(s->vkctx.output_format, FF_VK_REP_FLOAT), - .mem_quali = "writeonly", - .dimensions = 2, - .elems = planes, - .stages = VK_SHADER_STAGE_COMPUTE_BIT, + { /* output_img */ + .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, + .stages = VK_SHADER_STAGE_COMPUTE_BIT, + .elems = planes, }, }; + ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0); - RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0)); - - GLSLD( overlay_noalpha ); - GLSLD( overlay_alpha ); - 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++) { ); - if (ialpha) - GLSLC(2, overlay_alpha_opaque(i, pos); ); - else - GLSLC(2, overlay_noalpha(i, pos); ); - GLSLC(1, } ); - GLSLC(0, } ); - - RET(spv->compile_shader(vkctx, spv, shd, &spv_data, &spv_len, "main", - &spv_opaque)); - RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main")); + RET(ff_vk_shader_link(vkctx, shd, + ff_overlay_comp_spv_data, + ff_overlay_comp_spv_len, "main")); RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd)); @@ -192,11 +120,6 @@ static av_cold int init_filter(AVFilterContext *ctx) s->initialized = 1; fail: - if (spv_opaque) - spv->free_shader(spv, &spv_opaque); - if (spv) - spv->uninit(&spv); - return err; } diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile index f68ca4bc3d..16b2dff456 100644 --- a/libavfilter/vulkan/Makefile +++ b/libavfilter/vulkan/Makefile @@ -10,6 +10,7 @@ OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o OBJS-$(CONFIG_GBLUR_VULKAN_FILTER) += vulkan/gblur.comp.spv.o OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o OBJS-$(CONFIG_SCDET_VULKAN_FILTER) += vulkan/scdet.comp.spv.o +OBJS-$(CONFIG_OVERLAY_VULKAN_FILTER) += vulkan/overlay.comp.spv.o OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o OBJS-$(CONFIG_TRANSPOSE_VULKAN_FILTER) += vulkan/transpose.comp.spv.o OBJS-$(CONFIG_V360_VULKAN_FILTER) += vulkan/v360.comp.spv.o diff --git a/libavfilter/vulkan/overlay.comp.glsl b/libavfilter/vulkan/overlay.comp.glsl new file mode 100644 index 0000000000..96523b1236 --- /dev/null +++ b/libavfilter/vulkan/overlay.comp.glsl @@ -0,0 +1,81 @@ +/* + * Copyright (c) Lynne + * + * 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 + */ + +#pragma shader_stage(compute) + +#extension GL_EXT_shader_image_load_formatted : require +#extension GL_EXT_scalar_block_layout : require +#extension GL_EXT_nonuniform_qualifier : require + +layout (constant_id = 0) const uint planes = 0; +layout (constant_id = 1) const bool has_alpha = false; + +layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in; + +layout (set = 0, binding = 0) uniform readonly image2D main_img[]; +layout (set = 0, binding = 0) uniform readonly image2D overlay_img[]; +layout (set = 0, binding = 1) uniform writeonly image2D output_img[]; + +layout (push_constant, scalar) uniform pushConstants { + ivec2 o_offset[4]; + ivec2 o_size[4]; +}; + +void overlay_noalpha(uint i, ivec2 pos) +{ + if ((o_offset[i].x <= pos.x) && (o_offset[i].y <= pos.y) && + (pos.x < (o_offset[i].x + o_size[i].x)) && + (pos.y < (o_offset[i].y + o_size[i].y))) { + vec4 res = imageLoad(overlay_img[i], pos - o_offset[i]); + imageStore(output_img[i], pos, res); + } else { + vec4 res = imageLoad(main_img[i], pos); + imageStore(output_img[i], pos, res); + } +} + +void overlay_alpha_opaque(uint i, ivec2 pos) +{ + vec4 res = imageLoad(main_img[i], pos); + if ((o_offset[i].x <= pos.x) && (o_offset[i].y <= pos.y) && + (pos.x < (o_offset[i].x + o_size[i].x)) && + (pos.y < (o_offset[i].y + o_size[i].y))) { + vec4 ovr = imageLoad(overlay_img[i], pos - o_offset[i]); + res = ovr * ovr.a + res * (1.0f - ovr.a); + res.a = 1.0f; + imageStore(output_img[i], pos, res); + } + imageStore(output_img[i], pos, res); +} + +void main() +{ + ivec2 pos = ivec2(gl_GlobalInvocationID.xy); + + for (uint i = 0; i < planes; i++) { + if (any(greaterThanEqual(pos, imageSize(output_img[i])))) + return; + + if (has_alpha) + overlay_alpha_opaque(i, pos); + else + overlay_noalpha(i, pos); + } +} -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]
