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]

Reply via email to