From: Lynne via ffmpeg-devel <ffmpeg-devel@ffmpeg.org>
To: ffmpeg-devel@ffmpeg.org
Cc: Lynne <code@ffmpeg.org>
Subject: [FFmpeg-devel] [PR] Convert a few filters to compile-time SPIR-V (PR #21810)
Date: Thu, 19 Feb 2026 22:39:55 -0000
Message-ID: <177154079595.25.11008091249156812316@29965ddac10e> (raw)
PR #21810 opened by Lynne
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21810
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21810.patch
It only takes 10 minutes.
>From e730556dca8b33820b08969e9e89ffe9c5a0e483 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Tue, 10 Feb 2026 20:36:28 +0100
Subject: [PATCH 1/7] vf_flip_vulkan: convert to compile-time SPIR-V generation
---
configure | 6 +-
libavfilter/vf_flip_vulkan.c | 101 ++++++++----------------------
libavfilter/vulkan/Makefile | 1 +
libavfilter/vulkan/flip.comp.glsl | 59 +++++++++++++++++
4 files changed, 88 insertions(+), 79 deletions(-)
create mode 100644 libavfilter/vulkan/flip.comp.glsl
diff --git a/configure b/configure
index b629173712..f2531aacf0 100755
--- a/configure
+++ b/configure
@@ -4113,7 +4113,7 @@ elbg_filter_deps="avcodec"
eq_filter_deps="gpl"
erosion_opencl_filter_deps="opencl"
find_rect_filter_deps="avcodec avformat gpl"
-flip_vulkan_filter_deps="vulkan spirv_library"
+flip_vulkan_filter_deps="vulkan spirv_compiler"
flite_filter_deps="libflite threads"
framerate_filter_select="scene_sad"
freezedetect_filter_select="scene_sad"
@@ -4123,7 +4123,7 @@ frei0r_src_filter_deps="frei0r"
fspp_filter_deps="gpl"
fsync_filter_deps="avformat"
gblur_vulkan_filter_deps="vulkan spirv_library"
-hflip_vulkan_filter_deps="vulkan spirv_library"
+hflip_vulkan_filter_deps="vulkan spirv_compiler"
histeq_filter_deps="gpl"
hqdn3d_filter_deps="gpl"
iccdetect_filter_deps="lcms2"
@@ -4215,7 +4215,7 @@ transpose_vulkan_filter_deps="vulkan spirv_library"
unsharp_opencl_filter_deps="opencl"
uspp_filter_deps="gpl avcodec"
vaguedenoiser_filter_deps="gpl"
-vflip_vulkan_filter_deps="vulkan spirv_library"
+vflip_vulkan_filter_deps="vulkan spirv_compiler"
vidstabdetect_filter_deps="libvidstab"
vidstabtransform_filter_deps="libvidstab"
libvmaf_filter_deps="libvmaf"
diff --git a/libavfilter/vf_flip_vulkan.c b/libavfilter/vf_flip_vulkan.c
index 3e2aed0fda..72fba7d972 100644
--- a/libavfilter/vf_flip_vulkan.c
+++ b/libavfilter/vf_flip_vulkan.c
@@ -19,14 +19,15 @@
* 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 "video.h"
+extern const unsigned char ff_flip_comp_spv_data[];
+extern const unsigned int ff_flip_comp_spv_len;
+
enum FlipType {
FLIP_VERTICAL,
FLIP_HORIZONTAL,
@@ -42,24 +43,13 @@ typedef struct FlipVulkanContext {
FFVulkanShader shd;
} FlipVulkanContext;
-static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType type)
+static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
{
int err = 0;
- uint8_t *spv_data;
- size_t spv_len;
- void *spv_opaque = NULL;
FlipVulkanContext *s = ctx->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(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) {
@@ -69,77 +59,36 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType
}
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, "flip",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 32, 32, 1,
- 0));
- desc = (FFVulkanDescriptorSetBinding []) {
- {
- .name = "input_image",
- .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,
+ ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 32, 8, planes }, 0);
+
+ ff_vk_shader_add_push_const(&s->shd, 0, sizeof(int),
+ VK_SHADER_STAGE_COMPUTE_BIT);
+
+ const FFVulkanDescriptorSetBinding desc[] = {
+ { /* input_img */
+ .type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE,
+ .stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ .elems = planes,
},
- {
- .name = "output_image",
- .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, 2, 0, 0);
- RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
-
- 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_image[%i]); ,i);
- GLSLC(1, if (IS_WITHIN(pos, size)) { );
- switch (type)
- {
- case FLIP_HORIZONTAL:
- GLSLF(2, vec4 res = imageLoad(input_image[%i], ivec2(size.x - pos.x, pos.y)); ,i);
- break;
- case FLIP_VERTICAL:
- GLSLF(2, vec4 res = imageLoad(input_image[%i], ivec2(pos.x, size.y - pos.y)); ,i);
- break;
- case FLIP_BOTH:
- GLSLF(2, vec4 res = imageLoad(input_image[%i], ivec2(size.xy - pos.xy));, i);
- break;
- default:
- GLSLF(2, vec4 res = imageLoad(input_image[%i], pos); ,i);
- break;
- }
- GLSLF(2, imageStore(output_image[%i], pos, res); ,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, shd,
+ ff_flip_comp_spv_data,
+ ff_flip_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;
}
@@ -171,10 +120,10 @@ static int filter_frame(AVFilterLink *link, AVFrame *in, enum FlipType type)
}
if (!s->initialized)
- RET(init_filter(ctx, in, type));
+ RET(init_filter(ctx, in));
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- VK_NULL_HANDLE, NULL, 0));
+ VK_NULL_HANDLE, &type, sizeof(int)));
RET(av_frame_copy_props(out, in));
diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile
index dc15353e9e..bb9aa1baab 100644
--- a/libavfilter/vulkan/Makefile
+++ b/libavfilter/vulkan/Makefile
@@ -4,3 +4,4 @@ clean::
OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o
OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o
OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
+OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o
diff --git a/libavfilter/vulkan/flip.comp.glsl b/libavfilter/vulkan/flip.comp.glsl
new file mode 100644
index 0000000000..f711586238
--- /dev/null
+++ b/libavfilter/vulkan/flip.comp.glsl
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2021 Wu Jianhua <jianhua.wu@intel.com>
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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 (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) uniform writeonly image2D output_img[];
+
+#define FLIP_VERTICAL 0
+#define FLIP_HORIZONTAL 1
+#define FLIP_BOTH 2
+
+layout (push_constant, scalar) uniform pushConstants {
+ int flip;
+};
+
+void main()
+{
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(input_img[nonuniformEXT(gl_LocalInvocationID.z)]);
+ if (any(greaterThanEqual(pos, size)))
+ return;
+
+ ivec2 dst;
+ switch (flip) {
+ case FLIP_HORIZONTAL: dst = ivec2(size.x - pos.x, pos.y); break;
+ case FLIP_VERTICAL: dst = ivec2(pos.x, size.y - pos.y); break;
+ case FLIP_BOTH: dst = ivec2(size.xy - pos.xy); break;
+ default: dst = pos; break;
+ }
+
+ vec4 res = imageLoad(input_img[nonuniformEXT(gl_LocalInvocationID.z)], pos);
+
+ imageStore(output_img[nonuniformEXT(gl_LocalInvocationID.z)], pos, res);
+}
--
2.52.0
>From 7a32f4c32a659aff70bc15662a75f62635ffe65f Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Tue, 10 Feb 2026 20:51:38 +0100
Subject: [PATCH 2/7] vf_transpose_vulkan: convert to compile-time SPIR-V
generation
---
configure | 2 +-
libavfilter/vf_transpose_vulkan.c | 95 +++++++-------------------
libavfilter/vulkan/Makefile | 1 +
libavfilter/vulkan/transpose.comp.glsl | 63 +++++++++++++++++
4 files changed, 90 insertions(+), 71 deletions(-)
create mode 100644 libavfilter/vulkan/transpose.comp.glsl
diff --git a/configure b/configure
index f2531aacf0..d1b5c89ae5 100755
--- a/configure
+++ b/configure
@@ -4211,7 +4211,7 @@ tonemap_opencl_filter_deps="opencl const_nan"
transpose_opencl_filter_deps="opencl"
transpose_vaapi_filter_deps="vaapi VAProcPipelineCaps_rotation_flags"
transpose_vt_filter_deps="videotoolbox VTPixelRotationSessionCreate"
-transpose_vulkan_filter_deps="vulkan spirv_library"
+transpose_vulkan_filter_deps="vulkan spirv_compiler"
unsharp_opencl_filter_deps="opencl"
uspp_filter_deps="gpl avcodec"
vaguedenoiser_filter_deps="gpl"
diff --git a/libavfilter/vf_transpose_vulkan.c b/libavfilter/vf_transpose_vulkan.c
index 3fe2d11cb2..2665ababfe 100644
--- a/libavfilter/vf_transpose_vulkan.c
+++ b/libavfilter/vf_transpose_vulkan.c
@@ -19,15 +19,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 "transpose.h"
#include "video.h"
+extern const unsigned char ff_transpose_comp_spv_data[];
+extern const unsigned int ff_transpose_comp_spv_len;
+
typedef struct TransposeVulkanContext {
FFVulkanContext vkctx;
@@ -43,22 +44,9 @@ typedef struct TransposeVulkanContext {
static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
{
int err;
- uint8_t *spv_data;
- size_t spv_len;
- void *spv_opaque = NULL;
TransposeVulkanContext *s = ctx->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(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) {
@@ -68,70 +56,36 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
}
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, "transpose",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 32, 1, 1,
- 0));
- desc = (FFVulkanDescriptorSetBinding []) {
- {
- .name = "input_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,
+ ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 32, 1, planes }, 0);
+
+ ff_vk_shader_add_push_const(&s->shd, 0, sizeof(int),
+ VK_SHADER_STAGE_COMPUTE_BIT);
+
+ const FFVulkanDescriptorSetBinding desc[] = {
+ { /* input_img */
+ .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_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, 2, 0, 0);
- RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
-
- GLSLC(0, void main() );
- GLSLC(0, { );
- GLSLC(1, ivec2 size; );
- GLSLC(1, 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)) { );
- if (s->dir == TRANSPOSE_CCLOCK)
- GLSLF(2, vec4 res = imageLoad(input_images[%i], ivec2(size.y - pos.y, pos.x)); ,i);
- else if (s->dir == TRANSPOSE_CLOCK_FLIP || s->dir == TRANSPOSE_CLOCK) {
- GLSLF(2, vec4 res = imageLoad(input_images[%i], ivec2(size.yx - pos.yx)); ,i);
- if (s->dir == TRANSPOSE_CLOCK)
- GLSLC(2, pos = ivec2(pos.x, size.y - pos.y); );
- } else
- GLSLF(2, vec4 res = imageLoad(input_images[%i], pos.yx); ,i);
- GLSLF(2, imageStore(output_images[%i], pos, res); ,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_transpose_comp_spv_data,
+ ff_transpose_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;
}
@@ -156,7 +110,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
RET(init_filter(ctx, in));
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- VK_NULL_HANDLE, NULL, 0));
+ VK_NULL_HANDLE, &s->dir, sizeof(int)));
RET(av_frame_copy_props(out, in));
@@ -223,7 +177,8 @@ static int config_props_output(AVFilterLink *outlink)
}
#define OFFSET(x) offsetof(TransposeVulkanContext, x)
-#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM)
+#define FLAGS (AV_OPT_FLAG_FILTERING_PARAM | AV_OPT_FLAG_VIDEO_PARAM | \
+ AV_OPT_FLAG_RUNTIME_PARAM)
static const AVOption transpose_vulkan_options[] = {
{ "dir", "set transpose direction", OFFSET(dir), AV_OPT_TYPE_INT, { .i64 = TRANSPOSE_CCLOCK_FLIP }, 0, 7, FLAGS, .unit = "dir" },
diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile
index bb9aa1baab..e47655c8bd 100644
--- a/libavfilter/vulkan/Makefile
+++ b/libavfilter/vulkan/Makefile
@@ -5,3 +5,4 @@ OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o
OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o
OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o
+OBJS-$(CONFIG_TRANSPOSE_VULKAN_FILTER) += vulkan/transpose.comp.spv.o
diff --git a/libavfilter/vulkan/transpose.comp.glsl b/libavfilter/vulkan/transpose.comp.glsl
new file mode 100644
index 0000000000..a8ab6b5843
--- /dev/null
+++ b/libavfilter/vulkan/transpose.comp.glsl
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2021 Wu Jianhua <jianhua.wu@intel.com>
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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 (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) uniform writeonly image2D output_img[];
+
+#define TRANSPOSE_CCLOCK_FLIP 0
+#define TRANSPOSE_CLOCK 1
+#define TRANSPOSE_CCLOCK 2
+#define TRANSPOSE_CLOCK_FLIP 3
+#define TRANSPOSE_REVERSAL 4
+#define TRANSPOSE_HFLIP 5
+#define TRANSPOSE_VFLIP 6
+
+layout (push_constant, scalar) uniform pushConstants {
+ int dir;
+};
+
+void main()
+{
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(input_img[nonuniformEXT(gl_LocalInvocationID.z)]);
+ if (any(greaterThanEqual(pos, size)))
+ return;
+
+ ivec2 dst;
+ switch (dir) {
+ case TRANSPOSE_CCLOCK: dst = ivec2(size.y - pos.y, pos.x); break;
+ case TRANSPOSE_CLOCK: pos = ivec2(pos.x, size.y - pos.y); /* fall */
+ case TRANSPOSE_CLOCK_FLIP: dst = ivec2(size.yx - pos.yx); break;
+ default: dst = pos.yx; break;
+ }
+
+ vec4 res = imageLoad(input_img[nonuniformEXT(gl_LocalInvocationID.z)], pos);
+
+ imageStore(output_img[nonuniformEXT(gl_LocalInvocationID.z)], pos, res);
+}
--
2.52.0
>From 8b15ff524fa0241965c7d3a638e235ea4b9e7c84 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 12 Feb 2026 11:33:07 +0100
Subject: [PATCH 3/7] vulkan_filter: add an argument for setting the Z
workgroup count
---
libavfilter/vf_avgblur_vulkan.c | 2 +-
libavfilter/vf_blend_vulkan.c | 2 +-
libavfilter/vf_bwdif_vulkan.c | 2 +-
libavfilter/vf_chromaber_vulkan.c | 2 +-
libavfilter/vf_flip_vulkan.c | 2 +-
libavfilter/vf_gblur_vulkan.c | 2 +-
libavfilter/vf_interlace_vulkan.c | 2 +-
libavfilter/vf_overlay_vulkan.c | 2 +-
libavfilter/vf_scale_vulkan.c | 2 +-
libavfilter/vf_transpose_vulkan.c | 2 +-
libavfilter/vf_xfade_vulkan.c | 2 +-
libavfilter/vsrc_testsrc_vulkan.c | 4 ++--
libavfilter/vulkan_filter.c | 15 +++++++++------
libavfilter/vulkan_filter.h | 9 ++++++---
14 files changed, 28 insertions(+), 22 deletions(-)
diff --git a/libavfilter/vf_avgblur_vulkan.c b/libavfilter/vf_avgblur_vulkan.c
index db0b1b8612..3716f62ccd 100644
--- a/libavfilter/vf_avgblur_vulkan.c
+++ b/libavfilter/vf_avgblur_vulkan.c
@@ -122,7 +122,7 @@ static int avgblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd,
out, in, VK_NULL_HANDLE,
- &s->opts, sizeof(s->opts)));
+ 1, &s->opts, sizeof(s->opts)));
err = av_frame_copy_props(out, in);
if (err < 0)
diff --git a/libavfilter/vf_blend_vulkan.c b/libavfilter/vf_blend_vulkan.c
index 57cf3c696b..a6492a5c32 100644
--- a/libavfilter/vf_blend_vulkan.c
+++ b/libavfilter/vf_blend_vulkan.c
@@ -264,7 +264,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, NULL, 0));
+ VK_NULL_HANDLE, 1, NULL, 0));
return ff_filter_frame(outlink, out);
diff --git a/libavfilter/vf_bwdif_vulkan.c b/libavfilter/vf_bwdif_vulkan.c
index 5fefb3396d..ea4154daf5 100644
--- a/libavfilter/vf_bwdif_vulkan.c
+++ b/libavfilter/vf_bwdif_vulkan.c
@@ -115,7 +115,7 @@ static void bwdif_vulkan_filter_frame(AVFilterContext *ctx, AVFrame *dst,
ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, dst,
(AVFrame *[]){ y->prev, y->cur, y->next }, 3,
- VK_NULL_HANDLE, ¶ms, sizeof(params));
+ VK_NULL_HANDLE, 1, ¶ms, sizeof(params));
if (y->current_field == YADIF_FIELD_END)
y->current_field = YADIF_FIELD_NORMAL;
diff --git a/libavfilter/vf_chromaber_vulkan.c b/libavfilter/vf_chromaber_vulkan.c
index 65b53afd64..6dd50c1bb4 100644
--- a/libavfilter/vf_chromaber_vulkan.c
+++ b/libavfilter/vf_chromaber_vulkan.c
@@ -188,7 +188,7 @@ static int chromaber_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(init_filter(ctx, in));
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- s->sampler, &s->opts, sizeof(s->opts)));
+ s->sampler, 1, &s->opts, sizeof(s->opts)));
err = av_frame_copy_props(out, in);
if (err < 0)
diff --git a/libavfilter/vf_flip_vulkan.c b/libavfilter/vf_flip_vulkan.c
index 72fba7d972..b86f236d2a 100644
--- a/libavfilter/vf_flip_vulkan.c
+++ b/libavfilter/vf_flip_vulkan.c
@@ -123,7 +123,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *in, enum FlipType type)
RET(init_filter(ctx, in));
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- VK_NULL_HANDLE, &type, sizeof(int)));
+ VK_NULL_HANDLE, 1, &type, sizeof(int)));
RET(av_frame_copy_props(out, in));
diff --git a/libavfilter/vf_gblur_vulkan.c b/libavfilter/vf_gblur_vulkan.c
index 1b447e2754..8ade1c955d 100644
--- a/libavfilter/vf_gblur_vulkan.c
+++ b/libavfilter/vf_gblur_vulkan.c
@@ -316,7 +316,7 @@ static int gblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(ff_vk_filter_process_2pass(&s->vkctx, &s->e,
(FFVulkanShader *[2]){ &s->shd_hor, &s->shd_ver },
- out, tmp, in, VK_NULL_HANDLE, NULL, 0));
+ out, tmp, in, VK_NULL_HANDLE, 1, NULL, 0));
err = av_frame_copy_props(out, in);
if (err < 0)
diff --git a/libavfilter/vf_interlace_vulkan.c b/libavfilter/vf_interlace_vulkan.c
index 7afb30c2d7..afa8a634f8 100644
--- a/libavfilter/vf_interlace_vulkan.c
+++ b/libavfilter/vf_interlace_vulkan.c
@@ -218,7 +218,7 @@ static int interlace_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd,
out, (AVFrame *[]){ input_top, input_bot }, 2,
- s->sampler, NULL, 0));
+ s->sampler, 1, NULL, 0));
err = av_frame_copy_props(out, s->cur);
if (err < 0)
diff --git a/libavfilter/vf_overlay_vulkan.c b/libavfilter/vf_overlay_vulkan.c
index 1f9eed8e08..e1a07e4bfd 100644
--- a/libavfilter/vf_overlay_vulkan.c
+++ b/libavfilter/vf_overlay_vulkan.c
@@ -240,7 +240,7 @@ static int overlay_vulkan_blend(FFFrameSync *fs)
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd,
out, (AVFrame *[]){ input_main, input_overlay }, 2,
- VK_NULL_HANDLE, &s->opts, sizeof(s->opts)));
+ VK_NULL_HANDLE, 1, &s->opts, sizeof(s->opts)));
err = av_frame_copy_props(out, input_main);
if (err < 0)
diff --git a/libavfilter/vf_scale_vulkan.c b/libavfilter/vf_scale_vulkan.c
index a1e4f59a72..e7e23826ed 100644
--- a/libavfilter/vf_scale_vulkan.c
+++ b/libavfilter/vf_scale_vulkan.c
@@ -372,7 +372,7 @@ static int scale_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
s->opts.crop_h = in->height - (in->crop_top + in->crop_bottom);
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- s->sampler, &s->opts, sizeof(s->opts)));
+ s->sampler, 1, &s->opts, sizeof(s->opts)));
err = av_frame_copy_props(out, in);
if (err < 0)
diff --git a/libavfilter/vf_transpose_vulkan.c b/libavfilter/vf_transpose_vulkan.c
index 2665ababfe..e193bb6829 100644
--- a/libavfilter/vf_transpose_vulkan.c
+++ b/libavfilter/vf_transpose_vulkan.c
@@ -110,7 +110,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
RET(init_filter(ctx, in));
RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
- VK_NULL_HANDLE, &s->dir, sizeof(int)));
+ VK_NULL_HANDLE, 1, &s->dir, sizeof(int)));
RET(av_frame_copy_props(out, in));
diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c
index 58e8797733..3aa96d62b0 100644
--- a/libavfilter/vf_xfade_vulkan.c
+++ b/libavfilter/vf_xfade_vulkan.c
@@ -447,7 +447,7 @@ static int xfade_frame(AVFilterContext *avctx, AVFrame *frame_a, AVFrame *frame_
0.f, 1.f);
RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, output,
- (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler,
+ (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler, 1,
&(XFadeParameters){ progress }, sizeof(XFadeParameters)));
return ff_filter_frame(outlink, output);
diff --git a/libavfilter/vsrc_testsrc_vulkan.c b/libavfilter/vsrc_testsrc_vulkan.c
index cb3c787213..68cb8b9ef5 100644
--- a/libavfilter/vsrc_testsrc_vulkan.c
+++ b/libavfilter/vsrc_testsrc_vulkan.c
@@ -235,7 +235,7 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
return AVERROR(ENOMEM);
err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, s->picref, NULL,
- VK_NULL_HANDLE, &s->opts, sizeof(s->opts));
+ VK_NULL_HANDLE, 1, &s->opts, sizeof(s->opts));
if (err < 0)
return err;
}
@@ -254,7 +254,7 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
frame->sample_aspect_ratio = s->sar;
if (!s->draw_once) {
err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, frame, NULL,
- VK_NULL_HANDLE, &s->opts, sizeof(s->opts));
+ VK_NULL_HANDLE, 1, &s->opts, sizeof(s->opts));
if (err < 0) {
av_frame_free(&frame);
return err;
diff --git a/libavfilter/vulkan_filter.c b/libavfilter/vulkan_filter.c
index 44a4ce7242..2fc467c6d9 100644
--- a/libavfilter/vulkan_filter.c
+++ b/libavfilter/vulkan_filter.c
@@ -241,7 +241,8 @@ int ff_vk_filter_init(AVFilterContext *avctx)
int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd, AVFrame *out_f, AVFrame *in_f,
- VkSampler sampler, void *push_src, size_t push_size)
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size)
{
int err = 0;
FFVulkanFunctions *vk = &vkctx->vkfn;
@@ -304,7 +305,7 @@ int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
vk->CmdDispatch(exec->buf,
FFALIGN(vkctx->output_width, shd->lg_size[0])/shd->lg_size[0],
FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
- 1);
+ wgc_z);
return ff_vk_exec_submit(vkctx, exec);
fail:
@@ -315,7 +316,8 @@ fail:
int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd_list[2],
AVFrame *out, AVFrame *tmp, AVFrame *in,
- VkSampler sampler, void *push_src, size_t push_size)
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size)
{
int err = 0;
FFVulkanFunctions *vk = &vkctx->vkfn;
@@ -395,7 +397,7 @@ int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
vk->CmdDispatch(exec->buf,
FFALIGN(vkctx->output_width, shd->lg_size[0])/shd->lg_size[0],
FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
- 1);
+ wgc_z);
}
return ff_vk_exec_submit(vkctx, exec);
@@ -407,7 +409,8 @@ fail:
int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd,
AVFrame *out, AVFrame *in[], int nb_in,
- VkSampler sampler, void *push_src, size_t push_size)
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size)
{
int err = 0;
FFVulkanFunctions *vk = &vkctx->vkfn;
@@ -474,7 +477,7 @@ int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
vk->CmdDispatch(exec->buf,
FFALIGN(vkctx->output_width, shd->lg_size[0])/shd->lg_size[0],
FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
- 1);
+ wgc_z);
return ff_vk_exec_submit(vkctx, exec);
fail:
diff --git a/libavfilter/vulkan_filter.h b/libavfilter/vulkan_filter.h
index 6ed9c4de39..c54c8c56c4 100644
--- a/libavfilter/vulkan_filter.h
+++ b/libavfilter/vulkan_filter.h
@@ -44,7 +44,8 @@ int ff_vk_filter_init_context(AVFilterContext *avctx, FFVulkanContext *s,
*/
int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd, AVFrame *out_f, AVFrame *in_f,
- VkSampler sampler, void *push_src, size_t push_size);
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size);
/**
* Submit a compute shader with a single in and single out with 2 stages.
@@ -52,7 +53,8 @@ int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd_list[2],
AVFrame *out, AVFrame *tmp, AVFrame *in,
- VkSampler sampler, void *push_src, size_t push_size);
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size);
/**
* Up to 16 inputs, one output
@@ -60,6 +62,7 @@ int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
FFVulkanShader *shd,
AVFrame *out, AVFrame *in[], int nb_in,
- VkSampler sampler, void *push_src, size_t push_size);
+ VkSampler sampler, uint32_t wgc_z,
+ void *push_src, size_t push_size);
#endif /* AVFILTER_VULKAN_FILTER_H */
--
2.52.0
>From 8e384c57d708ee5fdc0d2ded29aa1631f0e13810 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 12 Feb 2026 12:45:31 +0100
Subject: [PATCH 4/7] vulkan: add LUT permutation table for BGR0/BGRA
Fixes filters.
---
libavutil/vulkan.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index 5264abd7ad..334f913e28 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -1585,6 +1585,13 @@ void ff_vk_set_perm(enum AVPixelFormat pix_fmt, int lut[4], int inv)
lut[2] = 1;
lut[3] = 3;
break;
+ case AV_PIX_FMT_BGRA:
+ case AV_PIX_FMT_BGR0:
+ lut[0] = 2;
+ lut[1] = 1;
+ lut[2] = 0;
+ lut[3] = 3;
+ break;
default:
lut[0] = 0;
lut[1] = 1;
--
2.52.0
>From f71d68e27d747a7cc83b9eea559ed3927900a1a5 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 12 Feb 2026 12:02:00 +0100
Subject: [PATCH 5/7] vsrc_testsrc_vulkan: convert to compile-time SPIR-V
generation
---
configure | 2 +-
libavfilter/vsrc_testsrc_vulkan.c | 77 +++++++++---------------------
libavfilter/vulkan/Makefile | 1 +
libavfilter/vulkan/color.comp.glsl | 45 +++++++++++++++++
4 files changed, 70 insertions(+), 55 deletions(-)
create mode 100644 libavfilter/vulkan/color.comp.glsl
diff --git a/configure b/configure
index d1b5c89ae5..58682c9c27 100755
--- a/configure
+++ b/configure
@@ -4084,7 +4084,7 @@ bwdif_cuda_filter_deps="ffnvcodec"
bwdif_cuda_filter_deps_any="cuda_nvcc cuda_llvm"
bwdif_vulkan_filter_deps="vulkan spirv_compiler"
chromaber_vulkan_filter_deps="vulkan spirv_library"
-color_vulkan_filter_deps="vulkan spirv_library"
+color_vulkan_filter_deps="vulkan spirv_compiler"
colorkey_opencl_filter_deps="opencl"
colormatrix_filter_deps="gpl"
convolution_opencl_filter_deps="opencl"
diff --git a/libavfilter/vsrc_testsrc_vulkan.c b/libavfilter/vsrc_testsrc_vulkan.c
index 68cb8b9ef5..60cd285dd9 100644
--- a/libavfilter/vsrc_testsrc_vulkan.c
+++ b/libavfilter/vsrc_testsrc_vulkan.c
@@ -18,10 +18,8 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
-#include "libavutil/random_seed.h"
#include "libavutil/csp.h"
#include "libavutil/opt.h"
-#include "libavutil/vulkan_spirv.h"
#include "vulkan_filter.h"
#include "filters.h"
#include "colorspace.h"
@@ -31,8 +29,11 @@ enum TestSrcVulkanMode {
TESTSRC_COLOR,
};
+extern const unsigned char ff_color_comp_spv_data[];
+extern const unsigned int ff_color_comp_spv_len;
+
typedef struct TestSrcVulkanPushData {
- float color_comp[4];
+ float color_data[4][4];
} TestSrcVulkanPushData;
typedef struct TestSrcVulkanContext {
@@ -65,23 +66,11 @@ typedef struct TestSrcVulkanContext {
static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode)
{
int err;
- uint8_t *spv_data;
- size_t spv_len;
- void *spv_opaque = NULL;
TestSrcVulkanContext *s = ctx->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_set;
const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.output_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");
@@ -90,37 +79,22 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
}
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, "scale",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 32, 32, 1,
- 0));
- GLSLC(0, layout(push_constant, std430) uniform pushConstants { );
- GLSLC(1, vec4 color_comp; );
- GLSLC(0, }; );
- GLSLC(0, );
+ ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 32, 32, 1 }, 0);
ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
VK_SHADER_STAGE_COMPUTE_BIT);
- desc_set = (FFVulkanDescriptorSetBinding []) {
- {
- .name = "output_img",
+ const FFVulkanDescriptorSetBinding desc_set[] = {
+ { /* 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,
+ .elems = planes,
},
};
+ ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 1, 0, 0);
- RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc_set, 1, 0, 0));
-
- GLSLC(0, void main() );
- GLSLC(0, { );
- GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
if (mode == TESTSRC_COLOR) {
double rgb2yuv[3][3];
double rgbad[4];
@@ -167,38 +141,30 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
if (desc->nb_components <= 2)
yuvad[1] = yuvad[3];
+ float color_comp[4];
for (int i = 0; i < 4; i++)
- s->opts.color_comp[i] = yuvad[i];
+ color_comp[i] = yuvad[i];
- GLSLC(1, vec4 r; );
- GLSLC(0, );
+ int fmt_lut[4];
+ ff_vk_set_perm(s->vkctx.output_format, fmt_lut, 0);
for (int i = 0, c_off = 0; i < planes; i++) {
for (int c = 0; c < desc->nb_components; c++) {
if (desc->comp[c].plane == i) {
int off = desc->comp[c].offset / (FFALIGN(desc->comp[c].depth, 8)/8);
- GLSLF(1, r[%i] = color_comp[%i]; ,off, c_off++);
+ s->opts.color_data[i][off] = color_comp[fmt_lut[c_off++]];
}
}
- GLSLF(1, imageStore(output_img[%i], pos, r); ,i);
- GLSLC(0, );
}
- }
- 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_color_comp_spv_data,
+ ff_color_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;
}
@@ -207,6 +173,7 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
int err;
AVFilterLink *outlink = ctx->outputs[0];
TestSrcVulkanContext *s = ctx->priv;
+ const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
AVFrame *frame;
if (!s->initialized) {
@@ -235,7 +202,8 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
return AVERROR(ENOMEM);
err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, s->picref, NULL,
- VK_NULL_HANDLE, 1, &s->opts, sizeof(s->opts));
+ VK_NULL_HANDLE, planes,
+ &s->opts, sizeof(s->opts));
if (err < 0)
return err;
}
@@ -254,7 +222,8 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
frame->sample_aspect_ratio = s->sar;
if (!s->draw_once) {
err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, frame, NULL,
- VK_NULL_HANDLE, 1, &s->opts, sizeof(s->opts));
+ VK_NULL_HANDLE, planes,
+ &s->opts, sizeof(s->opts));
if (err < 0) {
av_frame_free(&frame);
return err;
diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile
index e47655c8bd..f0d737733c 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_BWDIF_VULKAN_FILTER) += vulkan/bwdif.comp.spv.o
+OBJS-$(CONFIG_COLOR_VULKAN_FILTER) += vulkan/color.comp.spv.o
OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
OBJS-$(CONFIG_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o
OBJS-$(CONFIG_TRANSPOSE_VULKAN_FILTER) += vulkan/transpose.comp.spv.o
diff --git a/libavfilter/vulkan/color.comp.glsl b/libavfilter/vulkan/color.comp.glsl
new file mode 100644
index 0000000000..9f23323f24
--- /dev/null
+++ b/libavfilter/vulkan/color.comp.glsl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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 (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
+
+layout (set = 0, binding = 0) uniform writeonly image2D output_img[];
+
+layout (push_constant, scalar) uniform pushConstants {
+ mat4 color_data;
+};
+
+void main()
+{
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(output_img[nonuniformEXT(gl_WorkGroupID.z)]);
+ if (any(greaterThanEqual(pos, size)))
+ return;
+
+ mat4 tmp = color_data;
+ vec4 res = tmp[gl_WorkGroupID.z];
+ imageStore(output_img[nonuniformEXT(gl_WorkGroupID.z)], pos, res);
+}
--
2.52.0
>From 66f62f60f503261308b715b98879512a5bf54480 Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 12 Feb 2026 15:07:12 +0100
Subject: [PATCH 6/7] vf_gblur_vulkan: port to compile-time SPIR-V generation
---
configure | 2 +-
libavfilter/vf_gblur_vulkan.c | 138 +++++++----------------------
libavfilter/vulkan/Makefile | 1 +
libavfilter/vulkan/gblur.comp.glsl | 62 +++++++++++++
4 files changed, 94 insertions(+), 109 deletions(-)
create mode 100644 libavfilter/vulkan/gblur.comp.glsl
diff --git a/configure b/configure
index 58682c9c27..c124582c44 100755
--- a/configure
+++ b/configure
@@ -4122,7 +4122,7 @@ frei0r_filter_deps="frei0r"
frei0r_src_filter_deps="frei0r"
fspp_filter_deps="gpl"
fsync_filter_deps="avformat"
-gblur_vulkan_filter_deps="vulkan spirv_library"
+gblur_vulkan_filter_deps="vulkan spirv_compiler"
hflip_vulkan_filter_deps="vulkan spirv_compiler"
histeq_filter_deps="gpl"
hqdn3d_filter_deps="gpl"
diff --git a/libavfilter/vf_gblur_vulkan.c b/libavfilter/vf_gblur_vulkan.c
index 8ade1c955d..18d65df8c9 100644
--- a/libavfilter/vf_gblur_vulkan.c
+++ b/libavfilter/vf_gblur_vulkan.c
@@ -20,24 +20,24 @@
*/
#include "libavutil/mem.h"
-#include "libavutil/random_seed.h"
#include "libavutil/opt.h"
-#include "libavutil/vulkan_spirv.h"
#include "vulkan_filter.h"
#include "filters.h"
#include "video.h"
-#define CGS 32
#define GBLUR_MAX_KERNEL_SIZE 127
+extern const unsigned char ff_gblur_comp_spv_data[];
+extern const unsigned int ff_gblur_comp_spv_len;
+
typedef struct GBlurVulkanContext {
FFVulkanContext vkctx;
int initialized;
FFVkExecPool e;
AVVulkanDeviceQueueFamily *qf;
- VkSampler sampler;
+
FFVulkanShader shd_hor;
FFVkBuffer params_hor;
FFVulkanShader shd_ver;
@@ -50,20 +50,6 @@ typedef struct GBlurVulkanContext {
float sigmaV;
} GBlurVulkanContext;
-static const char gblur_func[] = {
- C(0, void gblur(const ivec2 pos, const int index) )
- C(0, { )
- C(1, vec4 sum = imageLoad(input_images[index], pos) * kernel[0]; )
- C(0, )
- C(1, for(int i = 1; i < kernel.length(); i++) { )
- C(2, sum += imageLoad(input_images[index], pos + OFFSET) * kernel[i]; )
- C(2, sum += imageLoad(input_images[index], pos - OFFSET) * kernel[i]; )
- C(1, } )
- C(0, )
- C(1, imageStore(output_images[index], pos, sum); )
- C(0, } )
-};
-
static inline float gaussian(float sigma, float x)
{
return 1.0 / (sqrt(2.0 * M_PI) * sigma) *
@@ -124,50 +110,23 @@ static av_cold void init_gaussian_params(AVFilterContext *ctx)
static int init_gblur_pipeline(GBlurVulkanContext *s,
FFVulkanShader *shd, FFVkBuffer *params_buf,
- int ksize, float sigma, FFVkSPIRVCompiler *spv)
+ int ksize, float sigma)
{
int err = 0;
uint8_t *kernel_mapped;
- uint8_t *spv_data;
- size_t spv_len;
- void *spv_opaque = NULL;
- const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
+ ff_vk_shader_add_push_const(shd, 0, sizeof(int),
+ VK_SHADER_STAGE_COMPUTE_BIT);
- FFVulkanDescriptorSetBinding buf_desc = {
- .name = "data",
+ const FFVulkanDescriptorSetBinding buf_desc = {
.type = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
- .mem_quali = "readonly",
- .mem_layout = "std430",
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
- .buf_content = "float kernel",
- .buf_elems = ksize,
};
+ ff_vk_shader_add_descriptor_set(&s->vkctx, shd, &buf_desc, 1, 1, 0);
- RET(ff_vk_shader_add_descriptor_set(&s->vkctx, shd, &buf_desc, 1, 1, 0));
-
- GLSLD( gblur_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)) );
- GLSLC(2, return; );
- if (s->planes & (1 << i)) {
- GLSLF(1, gblur(pos, %i); ,i);
- } else {
- GLSLF(1, vec4 res = imageLoad(input_images[%i], pos); ,i);
- GLSLF(1, imageStore(output_images[%i], pos, res); ,i);
- }
- }
- GLSLC(0, } );
-
- RET(spv->compile_shader(&s->vkctx, spv, shd, &spv_data, &spv_len, "main",
- &spv_opaque));
- RET(ff_vk_shader_link(&s->vkctx, shd, spv_data, spv_len, "main"));
+ RET(ff_vk_shader_link(&s->vkctx, shd,
+ ff_gblur_comp_spv_data,
+ ff_gblur_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(&s->vkctx, &s->e, shd));
@@ -184,8 +143,6 @@ static int init_gblur_pipeline(GBlurVulkanContext *s,
VK_FORMAT_UNDEFINED));
fail:
- if (spv_opaque)
- spv->free_shader(spv, &spv_opaque);
return err;
}
@@ -196,16 +153,6 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
FFVulkanContext *vkctx = &s->vkctx;
const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
- FFVulkanShader *shd;
- FFVkSPIRVCompiler *spv;
- FFVulkanDescriptorSetBinding *desc;
-
- spv = ff_vk_spirv_init();
- if (!spv) {
- av_log(ctx, AV_LOG_ERROR, "Unable to initialize SPIR-V compiler!\n");
- return AVERROR_EXTERNAL;
- }
-
s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
if (!s->qf) {
av_log(ctx, AV_LOG_ERROR, "Device has no compute queues\n");
@@ -215,63 +162,36 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
- desc = (FFVulkanDescriptorSetBinding []) {
- {
- .name = "input_images",
+ 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 = planes,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
+ .elems = planes,
},
- {
- .name = "output_images",
+ { /* 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,
+ .elems = planes,
},
};
init_gaussian_params(ctx);
- {
- shd = &s->shd_hor;
- RET(ff_vk_shader_init(vkctx, shd, "gblur_hor",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 32, 1, 1,
- 0));
+ /* Horizontal */
+ ff_vk_shader_load(&s->shd_hor, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 32, 1, 1 }, 0);
+ ff_vk_shader_add_descriptor_set(vkctx, &s->shd_hor, desc, 2, 0, 0);
+ RET(init_gblur_pipeline(s, &s->shd_hor, &s->params_hor, s->size, s->sigma));
- RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
-
- GLSLC(0, #define OFFSET (ivec2(i, 0.0)));
- RET(init_gblur_pipeline(s, shd, &s->params_hor, s->size, s->sigma, spv));
- }
-
- {
- shd = &s->shd_ver;
- RET(ff_vk_shader_init(vkctx, shd, "gblur_hor",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 1, 32, 1,
- 0));
-
- RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
-
- GLSLC(0, #define OFFSET (ivec2(0.0, i)));
- RET(init_gblur_pipeline(s, shd, &s->params_ver, s->sizeV, s->sigmaV, spv));
- }
+ /* Vertical */
+ ff_vk_shader_load(&s->shd_ver, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 1, 32, 1 }, 0);
+ ff_vk_shader_add_descriptor_set(vkctx, &s->shd_ver, desc, 2, 0, 0);
+ RET(init_gblur_pipeline(s, &s->shd_ver, &s->params_ver, s->sizeV, s->sigmaV));
s->initialized = 1;
fail:
- if (spv)
- spv->uninit(&spv);
-
return err;
}
@@ -298,6 +218,7 @@ static int gblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
AVFilterContext *ctx = link->dst;
GBlurVulkanContext *s = ctx->priv;
AVFilterLink *outlink = ctx->outputs[0];
+ const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
out = ff_get_video_buffer(outlink, outlink->w, outlink->h);
if (!out) {
@@ -316,7 +237,8 @@ static int gblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
RET(ff_vk_filter_process_2pass(&s->vkctx, &s->e,
(FFVulkanShader *[2]){ &s->shd_hor, &s->shd_ver },
- out, tmp, in, VK_NULL_HANDLE, 1, NULL, 0));
+ out, tmp, in, VK_NULL_HANDLE,
+ planes, &s->planes, sizeof(int)));
err = av_frame_copy_props(out, in);
if (err < 0)
diff --git a/libavfilter/vulkan/Makefile b/libavfilter/vulkan/Makefile
index f0d737733c..47f3f3b75d 100644
--- a/libavfilter/vulkan/Makefile
+++ b/libavfilter/vulkan/Makefile
@@ -4,6 +4,7 @@ clean::
OBJS-$(CONFIG_AVGBLUR_VULKAN_FILTER) += vulkan/avgblur.comp.spv.o
OBJS-$(CONFIG_BWDIF_VULKAN_FILTER) += vulkan/bwdif.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_FLIP_VULKAN_FILTER) += vulkan/flip.comp.spv.o
OBJS-$(CONFIG_TRANSPOSE_VULKAN_FILTER) += vulkan/transpose.comp.spv.o
diff --git a/libavfilter/vulkan/gblur.comp.glsl b/libavfilter/vulkan/gblur.comp.glsl
new file mode 100644
index 0000000000..77c337020e
--- /dev/null
+++ b/libavfilter/vulkan/gblur.comp.glsl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2021-2022 Wu Jianhua <jianhua.wu@intel.com>
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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 (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) uniform writeonly image2D output_img[];
+
+layout (set = 1, binding = 0, scalar) readonly buffer kernel_buf {
+ float kernel[];
+};
+
+layout (push_constant, scalar) uniform pushConstants {
+ int planes;
+};
+
+#define P_IDX nonuniformEXT(gl_WorkGroupID.z)
+void main()
+{
+ if (!bool(planes & (1 << gl_WorkGroupID.z)))
+ return;
+
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(input_img[P_IDX]);
+ if (any(greaterThanEqual(pos, size)))
+ return;
+
+ vec4 sum = imageLoad(input_img[P_IDX], pos) * kernel[0];
+
+ for(int i = 1; i < kernel.length(); i++) {
+ ivec2 offs = gl_WorkGroupSize.x > gl_WorkGroupSize.y ? ivec2(i, 0) :
+ ivec2(0, i);
+ sum += imageLoad(input_img[P_IDX], pos + offs) * kernel[i];
+ sum += imageLoad(input_img[P_IDX], pos - offs) * kernel[i];
+ }
+
+ imageStore(output_img[P_IDX], pos, sum);
+}
--
2.52.0
>From b414c2a7b72cd458b90ca783297eadf07b69232b Mon Sep 17 00:00:00 2001
From: Lynne <dev@lynne.ee>
Date: Thu, 12 Feb 2026 21:57:02 +0100
Subject: [PATCH 7/7] chromaber_vulkan: switch to compile-time SPIR-V
generation
---
configure | 2 +-
libavfilter/vf_chromaber_vulkan.c | 104 ++++---------------------
libavfilter/vulkan/Makefile | 1 +
libavfilter/vulkan/chromaber.comp.glsl | 76 ++++++++++++++++++
4 files changed, 94 insertions(+), 89 deletions(-)
create mode 100644 libavfilter/vulkan/chromaber.comp.glsl
diff --git a/configure b/configure
index c124582c44..31a59730aa 100755
--- a/configure
+++ b/configure
@@ -4083,7 +4083,7 @@ bs2b_filter_deps="libbs2b"
bwdif_cuda_filter_deps="ffnvcodec"
bwdif_cuda_filter_deps_any="cuda_nvcc cuda_llvm"
bwdif_vulkan_filter_deps="vulkan spirv_compiler"
-chromaber_vulkan_filter_deps="vulkan spirv_library"
+chromaber_vulkan_filter_deps="vulkan spirv_compiler"
color_vulkan_filter_deps="vulkan spirv_compiler"
colorkey_opencl_filter_deps="opencl"
colormatrix_filter_deps="gpl"
diff --git a/libavfilter/vf_chromaber_vulkan.c b/libavfilter/vf_chromaber_vulkan.c
index 6dd50c1bb4..fa0bc8e300 100644
--- a/libavfilter/vf_chromaber_vulkan.c
+++ b/libavfilter/vf_chromaber_vulkan.c
@@ -20,12 +20,14 @@
#include "libavutil/random_seed.h"
#include "libavutil/opt.h"
-#include "libavutil/vulkan_spirv.h"
#include "vulkan_filter.h"
#include "filters.h"
#include "video.h"
+extern const unsigned char ff_chromaber_comp_spv_data[];
+extern const unsigned int ff_chromaber_comp_spv_len;
+
typedef struct ChromaticAberrationVulkanContext {
FFVulkanContext vkctx;
@@ -38,56 +40,22 @@ typedef struct ChromaticAberrationVulkanContext {
/* Push constants / options */
struct {
float dist[2];
+ uint32_t single_plane;
} opts;
} ChromaticAberrationVulkanContext;
-static const char distort_chroma_kernel[] = {
- C(0, void distort_rgb(ivec2 size, ivec2 pos) )
- C(0, { )
- C(1, const vec2 p = ((vec2(pos)/vec2(size)) - 0.5f)*2.0f; )
- C(1, const vec2 o = p * (dist - 1.0f); )
- C(0, )
- C(1, vec4 res; )
- C(1, res.r = texture(input_img[0], ((p - o)/2.0f) + 0.5f).r; )
- C(1, res.g = texture(input_img[0], ((p )/2.0f) + 0.5f).g; )
- C(1, res.b = texture(input_img[0], ((p + o)/2.0f) + 0.5f).b; )
- C(1, res.a = texture(input_img[0], ((p )/2.0f) + 0.5f).a; )
- C(1, imageStore(output_img[0], pos, res); )
- C(0, } )
- C(0, )
- C(0, void distort_chroma(int idx, ivec2 size, ivec2 pos) )
- C(0, { )
- C(1, vec2 p = ((vec2(pos)/vec2(size)) - 0.5f)*2.0f; )
- C(1, float d = sqrt(p.x*p.x + p.y*p.y); )
- C(1, p *= d / (d*dist); )
- C(1, vec4 res = texture(input_img[idx], (p/2.0f) + 0.5f); )
- C(1, imageStore(output_img[idx], pos, res); )
- C(0, } )
-};
-
static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
{
int err;
- uint8_t *spv_data;
- size_t spv_len;
- void *spv_opaque = NULL;
ChromaticAberrationVulkanContext *s = ctx->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;
/* Normalize options */
s->opts.dist[0] = (s->opts.dist[0] / 100.0f) + 1.0f;
s->opts.dist[1] = (s->opts.dist[1] / 100.0f) + 1.0f;
- 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");
@@ -97,77 +65,37 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, s->qf->num*4, 0, 0, 0, NULL));
RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, VK_FILTER_LINEAR));
- RET(ff_vk_shader_init(vkctx, &s->shd, "chromatic_abberation",
- VK_SHADER_STAGE_COMPUTE_BIT,
- NULL, 0,
- 32, 32, 1,
- 0));
- GLSLC(0, layout(push_constant, std430) uniform pushConstants { );
- GLSLC(1, vec2 dist; );
- GLSLC(0, }; );
- GLSLC(0, );
+ ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, NULL,
+ (uint32_t []) { 32, 32, 1 }, 0);
ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
VK_SHADER_STAGE_COMPUTE_BIT);
- desc = (FFVulkanDescriptorSetBinding []) {
- {
- .name = "input_img",
+ const FFVulkanDescriptorSetBinding desc[] = {
+ { /* input_img */
.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
- .dimensions = 2,
- .elems = planes,
.stages = VK_SHADER_STAGE_COMPUTE_BIT,
- .samplers = DUP_SAMPLER(s->sampler),
+ .elems = planes,
},
- {
- .name = "output_img",
+ { /* 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,
+ .elems = planes,
},
};
+ 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));
-
- GLSLD( distort_chroma_kernel );
- GLSLC(0, void main() );
- GLSLC(0, { );
- GLSLC(1, ivec2 pos = ivec2(gl_GlobalInvocationID.xy); );
- if (planes == 1) {
- GLSLC(1, distort_rgb(imageSize(output_img[0]), pos); );
- } else {
- GLSLC(1, ivec2 size = imageSize(output_img[0]); );
- GLSLC(1, vec2 npos = vec2(pos)/vec2(size); );
- GLSLC(1, vec4 res = texture(input_img[0], npos); );
- GLSLC(1, imageStore(output_img[0], pos, res); );
- for (int i = 1; i < planes; i++) {
- GLSLC(0, );
- GLSLF(1, size = imageSize(output_img[%i]); ,i);
- GLSLC(1, if (!IS_WITHIN(pos, size)) );
- GLSLC(2, return; );
- GLSLF(1, distort_chroma(%i, size, pos); ,i);
- }
- }
- 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_chromaber_comp_spv_data,
+ ff_chromaber_comp_spv_len, "main"));
RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
+ s->opts.single_plane = planes == 1;
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 47f3f3b75d..82b3dff054 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_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
OBJS-$(CONFIG_GBLUR_VULKAN_FILTER) += vulkan/gblur.comp.spv.o
OBJS-$(CONFIG_SCALE_VULKAN_FILTER) += vulkan/debayer.comp.spv.o
diff --git a/libavfilter/vulkan/chromaber.comp.glsl b/libavfilter/vulkan/chromaber.comp.glsl
new file mode 100644
index 0000000000..d2e4e60f12
--- /dev/null
+++ b/libavfilter/vulkan/chromaber.comp.glsl
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2026 Lynne <dev@lynne.ee>
+ *
+ * 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 (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
+
+layout (set = 0, binding = 0) uniform sampler2D input_img[];
+layout (set = 0, binding = 1) uniform writeonly image2D output_img[];
+
+layout (push_constant, scalar) uniform pushConstants {
+ vec2 dist;
+ bool single_plane;
+};
+
+void distort_rgba(ivec2 pos, ivec2 size)
+{
+ const vec2 p = ((vec2(pos)/vec2(size)) - 0.5f)*2.0f;
+ const vec2 o = p * (dist - 1.0f);
+
+ vec4 res;
+ res.r = texture(input_img[0], ((p - o)/2.0f) + 0.5f).r;
+ res.g = texture(input_img[0], ((p )/2.0f) + 0.5f).g;
+ res.b = texture(input_img[0], ((p + o)/2.0f) + 0.5f).b;
+ res.a = texture(input_img[0], ((p )/2.0f) + 0.5f).a;
+ imageStore(output_img[0], pos, res);
+}
+
+void distort_plane(ivec2 pos, ivec2 size)
+{
+ vec2 p = ((vec2(pos)/vec2(size)) - 0.5f)*2.0f;
+ float d = sqrt(p.x*p.x + p.y*p.y);
+ p *= d / (d*dist);
+ vec4 res = texture(input_img[nonuniformEXT(gl_WorkGroupID.z)], (p/2.0f) + 0.5f);
+ imageStore(output_img[nonuniformEXT(gl_WorkGroupID.z)], pos, res);
+}
+
+void main()
+{
+ ivec2 pos = ivec2(gl_GlobalInvocationID.xy);
+ ivec2 size = imageSize(output_img[nonuniformEXT(gl_WorkGroupID.z)]);
+ if (any(greaterThanEqual(pos, size)))
+ return;
+
+ if (single_plane) {
+ distort_rgba(pos, pos);
+ } else {
+ vec2 npos = vec2(pos)/vec2(size);
+ vec4 res = texture(input_img[0], npos);
+ if (gl_WorkGroupID.z == 0)
+ imageStore(output_img[0], pos, res);
+ else
+ distort_plane(pos, size);
+ }
+}
--
2.52.0
_______________________________________________
ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org
To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org
reply other threads:[~2026-02-19 22:40 UTC|newest]
Thread overview: [no followups] expand[flat|nested] mbox.gz Atom feed
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=177154079595.25.11008091249156812316@29965ddac10e \
--to=ffmpeg-devel@ffmpeg.org \
--cc=code@ffmpeg.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Git Inbox Mirror of the ffmpeg-devel mailing list - see https://ffmpeg.org/mailman/listinfo/ffmpeg-devel
This inbox may be cloned and mirrored by anyone:
git clone --mirror https://master.gitmailbox.com/ffmpegdev/0 ffmpegdev/git/0.git
# If you have public-inbox 1.1+ installed, you may
# initialize and index your mirror using the following commands:
public-inbox-init -V2 ffmpegdev ffmpegdev/ https://master.gitmailbox.com/ffmpegdev \
ffmpegdev@gitmailbox.com
public-inbox-index ffmpegdev
Example config snippet for mirrors.
AGPL code for this site: git clone https://public-inbox.org/public-inbox.git