diff --git a/libavfilter/vf_avgblur_vulkan.c b/libavfilter/vf_avgblur_vulkan.c
index 439766968e..3972ea0cbb 100644
--- a/libavfilter/vf_avgblur_vulkan.c
+++ b/libavfilter/vf_avgblur_vulkan.c
@@ -33,8 +33,7 @@ typedef struct AvgBlurVulkanContext {
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
     VkSampler sampler;
-    FFVulkanPipeline pl;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
 
     /* Push constants / options */
     struct {
@@ -68,7 +67,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     AvgBlurVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd;
+    FFVulkanShader *shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -81,12 +80,13 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_LINEAR));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "avgblur_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
+    RET(ff_vk_shader_init(vkctx, &s->shd, "avgblur",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 1, 1,
+                          0));
     shd = &s->shd;
 
-    ff_vk_shader_set_compute_sizes(shd, 32, 1, 1);
-
     desc = (FFVulkanDescriptorSetBinding []) {
         {
             .name       = "input_img",
@@ -107,7 +107,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 2, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
 
     GLSLC(0, layout(push_constant, std430) uniform pushConstants {        );
     GLSLC(1,    vec4 filter_norm;                                         );
@@ -115,8 +115,8 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     GLSLC(0, };                                                           );
     GLSLC(0,                                                              );
 
-    ff_vk_add_push_constant(&s->pl, 0, sizeof(s->opts),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     GLSLD(   blur_kernel                                                  );
     GLSLC(0, void main()                                                  );
@@ -139,10 +139,9 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 
     RET(spv->compile_shader(spv, ctx, &s->shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, &s->shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, &s->shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
     s->opts.filter_len[0] = s->size_x - 1;
@@ -180,7 +179,7 @@ static int avgblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
     if (!s->initialized)
         RET(init_filter(ctx, in));
 
-    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl,
+    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd,
                                     out, in, s->sampler, &s->opts, sizeof(s->opts)));
 
     err = av_frame_copy_props(out, in);
@@ -204,7 +203,6 @@ static void avgblur_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_blend_vulkan.c b/libavfilter/vf_blend_vulkan.c
index f3eb5355f1..ccc025fe6b 100644
--- a/libavfilter/vf_blend_vulkan.c
+++ b/libavfilter/vf_blend_vulkan.c
@@ -46,10 +46,9 @@ typedef struct BlendVulkanContext {
     FFFrameSync fs;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 
     FilterParamsVulkan params[4];
@@ -132,7 +131,7 @@ static av_cold int init_filter(AVFilterContext *avctx)
     BlendVulkanContext *s = avctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -145,10 +144,11 @@ static av_cold int init_filter(AVFilterContext *avctx)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "blend_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    RET(ff_vk_shader_init(vkctx, &s->shd, "blend",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 32, 1,
+                          0));
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -178,7 +178,7 @@ static av_cold int init_filter(AVFilterContext *avctx)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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++)
@@ -210,10 +210,9 @@ static av_cold int init_filter(AVFilterContext *avctx)
 
     RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -257,7 +256,7 @@ static int blend_frame(FFFrameSync *fs)
         RET(init_filter(avctx));
     }
 
-    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl,
+    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd,
                                  out, (AVFrame *[]){ top, bottom }, 2,
                                  s->sampler, NULL, 0));
 
@@ -284,7 +283,6 @@ static av_cold void uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_bwdif_vulkan.c b/libavfilter/vf_bwdif_vulkan.c
index 4e43ade1c2..3164eb3395 100644
--- a/libavfilter/vf_bwdif_vulkan.c
+++ b/libavfilter/vf_bwdif_vulkan.c
@@ -35,8 +35,7 @@ typedef struct BWDIFVulkanContext {
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
     VkSampler sampler;
-    FFVulkanPipeline pl;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
 } BWDIFVulkanContext;
 
 typedef struct BWDIFParameters {
@@ -100,7 +99,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
     BWDIFVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd;
+    FFVulkanShader *shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -113,11 +112,13 @@ static av_cold int init_filter(AVFilterContext *ctx)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "bwdif_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-    shd = &s->shd;
 
-    ff_vk_shader_set_compute_sizes(shd, 1, 64, 1);
+    RET(ff_vk_shader_init(vkctx, &s->shd, "bwdif",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          1, 64, 1,
+                          0));
+    shd = &s->shd;
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -155,7 +156,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 4, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 4, 0, 0));
 
     GLSLC(0, layout(push_constant, std430) uniform pushConstants {                 );
     GLSLC(1,    int parity;                                                        );
@@ -163,8 +164,8 @@ static av_cold int init_filter(AVFilterContext *ctx)
     GLSLC(1,    int current_field;                                                 );
     GLSLC(0, };                                                                    );
 
-    ff_vk_add_push_constant(&s->pl, 0, sizeof(BWDIFParameters),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(BWDIFParameters),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     GLSLD(   filter_fn                                                             );
     GLSLC(0, void main()                                                           );
@@ -245,10 +246,9 @@ static av_cold int init_filter(AVFilterContext *ctx)
 
     RET(spv->compile_shader(spv, ctx, &s->shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, &s->shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, &s->shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, &s->shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -272,7 +272,7 @@ static void bwdif_vulkan_filter_frame(AVFilterContext *ctx, AVFrame *dst,
         .current_field = y->current_field,
     };
 
-    ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, dst,
+    ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, dst,
                              (AVFrame *[]){ y->prev, y->cur, y->next }, 3,
                              s->sampler, &params, sizeof(params));
 
@@ -287,7 +287,6 @@ static void bwdif_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_chromaber_vulkan.c b/libavfilter/vf_chromaber_vulkan.c
index 60c8378b11..450715b36c 100644
--- a/libavfilter/vf_chromaber_vulkan.c
+++ b/libavfilter/vf_chromaber_vulkan.c
@@ -30,10 +30,9 @@ typedef struct ChromaticAberrationVulkanContext {
     FFVulkanContext vkctx;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 
     /* Push constants / options */
@@ -75,7 +74,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ChromaticAberrationVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -92,18 +91,19 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, VK_FILTER_LINEAR));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "chromaber_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    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_add_push_constant(&s->pl, 0, sizeof(s->opts),
-                             VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -125,7 +125,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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()                                                  );
@@ -150,10 +150,9 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -182,7 +181,7 @@ static int chromaber_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
     if (!s->initialized)
         RET(init_filter(ctx, in));
 
-    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, out, in,
+    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
                                     s->sampler, &s->opts, sizeof(s->opts)));
 
     err = av_frame_copy_props(out, in);
@@ -206,7 +205,6 @@ static void chromaber_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_flip_vulkan.c b/libavfilter/vf_flip_vulkan.c
index 1271396803..2b89a9a6a3 100644
--- a/libavfilter/vf_flip_vulkan.c
+++ b/libavfilter/vf_flip_vulkan.c
@@ -37,10 +37,9 @@ typedef struct FlipVulkanContext {
     FFVulkanContext vkctx;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 } FlipVulkanContext;
 
@@ -53,7 +52,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType
     FlipVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -66,10 +65,11 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_LINEAR));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "flip_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    RET(ff_vk_shader_init(vkctx, &s->shd, "flip",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 32, 1,
+                          0));
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -91,7 +91,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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, {                                                                              );
@@ -123,10 +123,9 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in, enum FlipType
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -147,7 +146,6 @@ static av_cold void flip_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
@@ -176,7 +174,7 @@ static int filter_frame(AVFilterLink *link, AVFrame *in, enum FlipType type)
     if (!s->initialized)
         RET(init_filter(ctx, in, type));
 
-    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, out, in,
+    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
                                     s->sampler, NULL, 0));
 
     RET(av_frame_copy_props(out, in));
diff --git a/libavfilter/vf_gblur_vulkan.c b/libavfilter/vf_gblur_vulkan.c
index d0fb6c9940..a56a6ece00 100644
--- a/libavfilter/vf_gblur_vulkan.c
+++ b/libavfilter/vf_gblur_vulkan.c
@@ -38,11 +38,9 @@ typedef struct GBlurVulkanContext {
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
     VkSampler sampler;
-    FFVulkanPipeline pl_hor;
-    FFVkSPIRVShader shd_hor;
+    FFVulkanShader shd_hor;
     FFVkBuffer params_hor;
-    FFVulkanPipeline pl_ver;
-    FFVkSPIRVShader shd_ver;
+    FFVulkanShader shd_ver;
     FFVkBuffer params_ver;
 
     int size;
@@ -123,8 +121,8 @@ static av_cold void init_gaussian_params(GBlurVulkanContext *s)
         init_kernel_size(s, &s->sizeV);
 }
 
-static int init_gblur_pipeline(GBlurVulkanContext *s, FFVulkanPipeline *pl,
-                               FFVkSPIRVShader *shd, FFVkBuffer *params_buf,
+static int init_gblur_pipeline(GBlurVulkanContext *s,
+                               FFVulkanShader *shd, FFVkBuffer *params_buf,
                                int ksize, float sigma, FFVkSPIRVCompiler *spv)
 {
     int err = 0;
@@ -150,7 +148,7 @@ static int init_gblur_pipeline(GBlurVulkanContext *s, FFVulkanPipeline *pl,
 
     buf_desc.buf_content = kernel_def;
 
-    RET(ff_vk_pipeline_descriptor_set_add(&s->vkctx, pl, 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()                                              );
@@ -173,10 +171,9 @@ static int init_gblur_pipeline(GBlurVulkanContext *s, FFVulkanPipeline *pl,
 
     RET(spv->compile_shader(spv, s, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(&s->vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(&s->vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(&s->vkctx, pl, shd));
-    RET(ff_vk_exec_pipeline_register(&s->vkctx, &s->e, pl));
+    RET(ff_vk_shader_register_exec(&s->vkctx, &s->e, shd));
 
     RET(ff_vk_create_buf(&s->vkctx, params_buf, sizeof(float) * ksize, NULL, NULL,
                          VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT |
@@ -187,10 +184,9 @@ static int init_gblur_pipeline(GBlurVulkanContext *s, FFVulkanPipeline *pl,
     init_gaussian_kernel((float *)kernel_mapped, sigma, ksize);
 
     RET(ff_vk_unmap_buffer(&s->vkctx, params_buf, 1));
-
-    RET(ff_vk_set_descriptor_buffer(&s->vkctx, pl, NULL, 1, 0, 0,
-                                    params_buf, 0, params_buf->size,
-                                    VK_FORMAT_UNDEFINED));
+    RET(ff_vk_shader_update_desc_buffer(&s->vkctx, &s->e.contexts[0], shd, 1, 0, 0,
+                                        params_buf, 0, params_buf->size,
+                                        VK_FORMAT_UNDEFINED));
 
 fail:
     av_free(kernel_def);
@@ -206,7 +202,7 @@ 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);
 
-    FFVkSPIRVShader *shd;
+    FFVulkanShader *shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -219,10 +215,6 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_LINEAR));
-    RET(ff_vk_shader_init(&s->pl_hor, &s->shd_hor, "gblur_hor_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-    RET(ff_vk_shader_init(&s->pl_ver, &s->shd_ver, "gblur_ver_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -248,22 +240,30 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 
     {
         shd = &s->shd_hor;
-        ff_vk_shader_set_compute_sizes(shd, 32, 1, 1);
+        RET(ff_vk_shader_init(vkctx, shd, "gblur_hor",
+                              VK_SHADER_STAGE_COMPUTE_BIT,
+                              NULL, 0,
+                              32, 1, 1,
+                              0));
 
-        RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl_hor, shd, desc, 2, 0, 0));
+        RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
 
         GLSLC(0, #define OFFSET (vec2(i, 0.0)));
-        RET(init_gblur_pipeline(s, &s->pl_hor, shd, &s->params_hor, s->size, s->sigma, spv));
+        RET(init_gblur_pipeline(s, shd, &s->params_hor, s->size, s->sigma, spv));
     }
 
     {
         shd = &s->shd_ver;
-        ff_vk_shader_set_compute_sizes(shd, 1, 32, 1);
+        RET(ff_vk_shader_init(vkctx, shd, "gblur_hor",
+                              VK_SHADER_STAGE_COMPUTE_BIT,
+                              NULL, 0,
+                              1, 32, 1,
+                              0));
 
-        RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl_ver, shd, desc, 2, 0, 0));
+        RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc, 2, 0, 0));
 
         GLSLC(0, #define OFFSET (vec2(0.0, i)));
-        RET(init_gblur_pipeline(s, &s->pl_ver, shd, &s->params_ver, s->sizeV, s->sigmaV, spv));
+        RET(init_gblur_pipeline(s, shd, &s->params_ver, s->sizeV, s->sigmaV, spv));
     }
 
     s->initialized = 1;
@@ -282,8 +282,6 @@ static av_cold void gblur_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl_hor);
-    ff_vk_pipeline_free(vkctx, &s->pl_ver);
     ff_vk_shader_free(vkctx, &s->shd_hor);
     ff_vk_shader_free(vkctx, &s->shd_ver);
     ff_vk_free_buf(vkctx, &s->params_hor);
@@ -322,7 +320,7 @@ static int gblur_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
         RET(init_filter(ctx, in));
 
     RET(ff_vk_filter_process_2pass(&s->vkctx, &s->e,
-                                   (FFVulkanPipeline *[2]){ &s->pl_hor, &s->pl_ver },
+                                   (FFVulkanShader *[2]){ &s->shd_hor, &s->shd_ver },
                                    out, tmp, in, s->sampler, NULL, 0));
 
     err = av_frame_copy_props(out, in);
diff --git a/libavfilter/vf_nlmeans_vulkan.c b/libavfilter/vf_nlmeans_vulkan.c
index fc9b522c80..05c752925e 100644
--- a/libavfilter/vf_nlmeans_vulkan.c
+++ b/libavfilter/vf_nlmeans_vulkan.c
@@ -45,11 +45,8 @@ typedef struct NLMeansVulkanContext {
     FFVkBuffer xyoffsets_buf;
 
     int pl_weights_rows;
-    FFVulkanPipeline pl_weights;
-    FFVkSPIRVShader shd_weights;
-
-    FFVulkanPipeline pl_denoise;
-    FFVkSPIRVShader shd_denoise;
+    FFVulkanShader shd_weights;
+    FFVulkanShader shd_denoise;
 
     int *xoffsets;
     int *yoffsets;
@@ -69,7 +66,7 @@ typedef struct NLMeansVulkanContext {
 
 extern const char *ff_source_prefix_sum_comp;
 
-static void insert_first(FFVkSPIRVShader *shd, int r, const char *off, int horiz, int plane, int comp)
+static void insert_first(FFVulkanShader *shd, int r, const char *off, int horiz, int plane, int comp)
 {
     GLSLF(4, s1    = texture(input_img[%i], pos + ivec2(%i + %s, %i + %s))[%i];
           ,plane, horiz ? r : 0, horiz ? off : "0", !horiz ? r : 0, !horiz ? off : "0", comp);
@@ -86,7 +83,7 @@ static void insert_first(FFVkSPIRVShader *shd, int r, const char *off, int horiz
     GLSLC(4, s2 = (s1 - s2) * (s1 - s2);                                                    );
 }
 
-static void insert_horizontal_pass(FFVkSPIRVShader *shd, int nb_rows, int first, int plane, int comp)
+static void insert_horizontal_pass(FFVulkanShader *shd, int nb_rows, int first, int plane, int comp)
 {
     GLSLF(1, pos.y = int(gl_GlobalInvocationID.x) * %i;                           ,nb_rows);
     if (!first)
@@ -112,7 +109,7 @@ static void insert_horizontal_pass(FFVkSPIRVShader *shd, int nb_rows, int first,
     GLSLC(0,                                                                      );
 }
 
-static void insert_vertical_pass(FFVkSPIRVShader *shd, int nb_rows, int first, int plane, int comp)
+static void insert_vertical_pass(FFVulkanShader *shd, int nb_rows, int first, int plane, int comp)
 {
     GLSLF(1, pos.x = int(gl_GlobalInvocationID.x) * %i;                           ,nb_rows);
     GLSLC(1, #pragma unroll(1)                                                    );
@@ -141,7 +138,7 @@ static void insert_vertical_pass(FFVkSPIRVShader *shd, int nb_rows, int first, i
     GLSLC(0,                                                                      );
 }
 
-static void insert_weights_pass(FFVkSPIRVShader *shd, int nb_rows, int vert,
+static void insert_weights_pass(FFVulkanShader *shd, int nb_rows, int vert,
                                 int t, int dst_comp, int plane, int comp)
 {
     GLSLF(1, p = patch_size[%i];                                              ,dst_comp);
@@ -214,7 +211,7 @@ typedef struct HorizontalPushData {
 } HorizontalPushData;
 
 static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *exec,
-                                         FFVulkanPipeline *pl, FFVkSPIRVShader *shd,
+                                         FFVulkanShader *shd,
                                          VkSampler sampler, FFVkSPIRVCompiler *spv,
                                          int width, int height, int t,
                                          const AVPixFmtDescriptor *desc,
@@ -241,8 +238,12 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             wg_rows++;
     }
 
-    RET(ff_vk_shader_init(pl, shd, "nlmeans_weights", VK_SHADER_STAGE_COMPUTE_BIT, 0));
-    ff_vk_shader_set_compute_sizes(shd, wg_size, 1, 1);
+    RET(ff_vk_shader_init(vkctx, shd, "nlmeans_weights",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          wg_size, 1, 1,
+                          0));
+
     *nb_rows = wg_rows;
 
     if (t > 1)
@@ -269,7 +270,8 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
     GLSLC(0, };                                                               );
     GLSLC(0,                                                                  );
 
-    ff_vk_add_push_constant(pl, 0, sizeof(HorizontalPushData), VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(shd, 0, sizeof(HorizontalPushData),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc_set = (FFVulkanDescriptorSetBinding []) {
         {
@@ -329,7 +331,7 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .buf_content = "float sums_3[];",
         },
     };
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, pl, shd, desc_set, 1 + 2*desc->nb_components, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 1 + 2*desc->nb_components, 0, 0));
 
     desc_set = (FFVulkanDescriptorSetBinding []) {
         {
@@ -340,7 +342,7 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .buf_content = "ivec2 xyoffsets[];",
         },
     };
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, pl, shd, desc_set, 1, 1, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 1, 1, 0));
 
     GLSLC(0,                                                                     );
     GLSLC(0, void main()                                                         );
@@ -401,10 +403,9 @@ static av_cold int init_weights_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
     GLSLC(0, }                                                                   );
 
     RET(spv->compile_shader(spv, vkctx, shd, &spv_data, &spv_len, "main", &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, exec, pl));
+    RET(ff_vk_shader_register_exec(vkctx, exec, shd));
 
 fail:
     if (spv_opaque)
@@ -418,7 +419,7 @@ typedef struct DenoisePushData {
 } DenoisePushData;
 
 static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *exec,
-                                         FFVulkanPipeline *pl, FFVkSPIRVShader *shd,
+                                         FFVulkanShader *shd,
                                          VkSampler sampler, FFVkSPIRVCompiler *spv,
                                          const AVPixFmtDescriptor *desc, int planes)
 {
@@ -428,16 +429,18 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
     void *spv_opaque = NULL;
     FFVulkanDescriptorSetBinding *desc_set;
 
-    RET(ff_vk_shader_init(pl, shd, "nlmeans_denoise",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(shd, 32, 32, 1);
+    RET(ff_vk_shader_init(vkctx, shd, "nlmeans_denoise",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 32, 1,
+                          0));
 
     GLSLC(0, layout(push_constant, std430) uniform pushConstants {        );
     GLSLC(1,    uvec4 ws_stride;                                          );
     GLSLC(0, };                                                           );
 
-    ff_vk_add_push_constant(pl, 0, sizeof(DenoisePushData), VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(shd, 0, sizeof(DenoisePushData),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc_set = (FFVulkanDescriptorSetBinding []) {
         {
@@ -458,7 +461,7 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
             .stages      = VK_SHADER_STAGE_COMPUTE_BIT,
         },
     };
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, pl, shd, desc_set, 2, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 2, 0, 0));
 
     desc_set = (FFVulkanDescriptorSetBinding []) {
         {
@@ -519,7 +522,7 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, pl, shd, desc_set, 2*desc->nb_components, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, shd, desc_set, 2*desc->nb_components, 0, 0));
 
     GLSLC(0, void main()                                                      );
     GLSLC(0, {                                                                );
@@ -551,10 +554,9 @@ static av_cold int init_denoise_pipeline(FFVulkanContext *vkctx, FFVkExecPool *e
     GLSLC(0, }                                                                );
 
     RET(spv->compile_shader(spv, vkctx, shd, &spv_data, &spv_len, "main", &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, exec, pl));
+    RET(ff_vk_shader_register_exec(vkctx, exec, shd));
 
 fail:
     if (spv_opaque)
@@ -654,14 +656,15 @@ static av_cold int init_filter(AVFilterContext *ctx)
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, 1, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
 
-    RET(init_weights_pipeline(vkctx, &s->e, &s->pl_weights, &s->shd_weights, s->sampler,
+    RET(init_weights_pipeline(vkctx, &s->e, &s->shd_weights, s->sampler,
                               spv, s->vkctx.output_width, s->vkctx.output_height,
                               s->opts.t, desc, planes, &s->pl_weights_rows));
 
-    RET(init_denoise_pipeline(vkctx, &s->e, &s->pl_denoise, &s->shd_denoise, s->sampler,
+    RET(init_denoise_pipeline(vkctx, &s->e, &s->shd_denoise, s->sampler,
                               spv, desc, planes));
 
-    RET(ff_vk_set_descriptor_buffer(&s->vkctx, &s->pl_weights, NULL, 1, 0, 0,
+    RET(ff_vk_shader_update_desc_buffer(vkctx, &s->e.contexts[0], &s->shd_weights,
+                                        1, 0, 0,
                                     &s->xyoffsets_buf, 0, s->xyoffsets_buf.size,
                                     VK_FORMAT_UNDEFINED));
 
@@ -697,11 +700,12 @@ static int denoise_pass(NLMeansVulkanContext *s, FFVkExecContext *exec,
     };
 
     /* Denoise pass pipeline */
-    ff_vk_exec_bind_pipeline(vkctx, exec, &s->pl_denoise);
+    ff_vk_exec_bind_shader(vkctx, exec, &s->shd_denoise);
 
     /* Push data */
-    ff_vk_update_push_exec(vkctx, exec, &s->pl_denoise, VK_SHADER_STAGE_COMPUTE_BIT,
-                           0, sizeof(pd), &pd);
+    ff_vk_shader_update_push_const(vkctx, exec, &s->shd_denoise,
+                                   VK_SHADER_STAGE_COMPUTE_BIT,
+                                   0, sizeof(pd), &pd);
 
     buf_bar[nb_buf_bar++] = (VkBufferMemoryBarrier2) {
         .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
@@ -726,8 +730,8 @@ static int denoise_pass(NLMeansVulkanContext *s, FFVkExecContext *exec,
 
     /* End of denoise pass */
     vk->CmdDispatch(exec->buf,
-                    FFALIGN(vkctx->output_width,  s->pl_denoise.wg_size[0])/s->pl_denoise.wg_size[0],
-                    FFALIGN(vkctx->output_height, s->pl_denoise.wg_size[1])/s->pl_denoise.wg_size[1],
+                    FFALIGN(vkctx->output_width,  s->shd_denoise.lg_size[0])/s->shd_denoise.lg_size[0],
+                    FFALIGN(vkctx->output_height, s->shd_denoise.lg_size[1])/s->shd_denoise.lg_size[1],
                     av_pix_fmt_count_planes(s->vkctx.output_format));
 
     return 0;
@@ -780,15 +784,15 @@ static int nlmeans_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
         return AVERROR(EINVAL);
 
     /* Integral image */
-    int_stride = s->pl_weights.wg_size[0]*s->pl_weights_rows*TYPE_SIZE;
-    int_size = s->pl_weights.wg_size[0]*s->pl_weights_rows*int_stride;
+    int_stride = s->shd_weights.lg_size[0]*s->pl_weights_rows*TYPE_SIZE;
+    int_size = s->shd_weights.lg_size[0]*s->pl_weights_rows*int_stride;
 
     /* Plane dimensions */
     for (int i = 0; i < desc->nb_components; i++) {
         plane_widths[i] = !i || (i == 3) ? vkctx->output_width : AV_CEIL_RSHIFT(vkctx->output_width, desc->log2_chroma_w);
         plane_heights[i] = !i || (i == 3) ? vkctx->output_height : AV_CEIL_RSHIFT(vkctx->output_height, desc->log2_chroma_w);
-        plane_widths[i]  = FFALIGN(plane_widths[i],  s->pl_denoise.wg_size[0]);
-        plane_heights[i] = FFALIGN(plane_heights[i], s->pl_denoise.wg_size[1]);
+        plane_widths[i]  = FFALIGN(plane_widths[i],  s->shd_denoise.lg_size[0]);
+        plane_heights[i] = FFALIGN(plane_heights[i], s->shd_denoise.lg_size[1]);
 
         ws_stride[i] = plane_widths[i];
         ws_size[i] = ws_stride[i] * plane_heights[i] * sizeof(float);
@@ -933,35 +937,35 @@ static int nlmeans_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
     ws_vk->access = buf_bar[0].dstAccessMask;
 
     /* Update weights descriptors */
-    ff_vk_update_descriptor_img_array(vkctx, &s->pl_weights, exec, in, in_views, 0, 0,
-                                      VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                                      s->sampler);
+    ff_vk_shader_update_img_array(vkctx, exec, &s->shd_weights, in, in_views, 0, 0,
+                                  VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                                  s->sampler);
     for (int i = 0; i < desc->nb_components; i++) {
-        RET(ff_vk_set_descriptor_buffer(&s->vkctx, &s->pl_weights, exec, 0, 1 + i*2 + 0, 0,
-                                        ws_vk, weights_offs[i], ws_size[i],
-                                        VK_FORMAT_UNDEFINED));
-        RET(ff_vk_set_descriptor_buffer(&s->vkctx, &s->pl_weights, exec, 0, 1 + i*2 + 1, 0,
-                                        ws_vk, sums_offs[i], ws_size[i],
-                                        VK_FORMAT_UNDEFINED));
+        RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd_weights, 0, 1 + i*2 + 0, 0,
+                                            ws_vk, weights_offs[i], ws_size[i],
+                                            VK_FORMAT_UNDEFINED));
+        RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd_weights, 0, 1 + i*2 + 1, 0,
+                                            ws_vk, sums_offs[i], ws_size[i],
+                                            VK_FORMAT_UNDEFINED));
     }
 
     /* Update denoise descriptors */
-    ff_vk_update_descriptor_img_array(vkctx, &s->pl_denoise, exec, in, in_views, 0, 0,
-                                      VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                                      s->sampler);
-    ff_vk_update_descriptor_img_array(vkctx, &s->pl_denoise, exec, out, out_views, 0, 1,
-                                      VK_IMAGE_LAYOUT_GENERAL, s->sampler);
+    ff_vk_shader_update_img_array(vkctx, exec, &s->shd_denoise, in, in_views, 0, 0,
+                                  VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                                  s->sampler);
+    ff_vk_shader_update_img_array(vkctx, exec, &s->shd_denoise, out, out_views, 0, 1,
+                                  VK_IMAGE_LAYOUT_GENERAL, s->sampler);
     for (int i = 0; i < desc->nb_components; i++) {
-        RET(ff_vk_set_descriptor_buffer(&s->vkctx, &s->pl_denoise, exec, 1, i*2 + 0, 0,
-                                        ws_vk, weights_offs[i], ws_size[i],
-                                        VK_FORMAT_UNDEFINED));
-        RET(ff_vk_set_descriptor_buffer(&s->vkctx, &s->pl_denoise, exec, 1, i*2 + 1, 0,
-                                        ws_vk, sums_offs[i], ws_size[i],
-                                        VK_FORMAT_UNDEFINED));
+        RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd_denoise, 1, i*2 + 0, 0,
+                                            ws_vk, weights_offs[i], ws_size[i],
+                                            VK_FORMAT_UNDEFINED));
+        RET(ff_vk_shader_update_desc_buffer(&s->vkctx, exec, &s->shd_denoise, 1, i*2 + 1, 0,
+                                            ws_vk, sums_offs[i], ws_size[i],
+                                            VK_FORMAT_UNDEFINED));
     }
 
     /* Weights pipeline */
-    ff_vk_exec_bind_pipeline(vkctx, exec, &s->pl_weights);
+    ff_vk_exec_bind_shader(vkctx, exec, &s->shd_weights);
 
     do {
         int wg_invoc;
@@ -978,8 +982,9 @@ static int nlmeans_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
         };
 
         /* Push data */
-        ff_vk_update_push_exec(vkctx, exec, &s->pl_weights, VK_SHADER_STAGE_COMPUTE_BIT,
-                               0, sizeof(pd), &pd);
+        ff_vk_shader_update_push_const(vkctx, exec, &s->shd_weights,
+                                       VK_SHADER_STAGE_COMPUTE_BIT,
+                                       0, sizeof(pd), &pd);
 
         if (offsets_dispatched) {
             nb_buf_bar = 0;
@@ -1044,9 +1049,7 @@ static void nlmeans_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl_weights);
     ff_vk_shader_free(vkctx, &s->shd_weights);
-    ff_vk_pipeline_free(vkctx, &s->pl_denoise);
     ff_vk_shader_free(vkctx, &s->shd_denoise);
 
     av_buffer_pool_uninit(&s->integral_buf_pool);
diff --git a/libavfilter/vf_overlay_vulkan.c b/libavfilter/vf_overlay_vulkan.c
index 09444067af..f1ad27167e 100644
--- a/libavfilter/vf_overlay_vulkan.c
+++ b/libavfilter/vf_overlay_vulkan.c
@@ -32,10 +32,9 @@ typedef struct OverlayVulkanContext {
     FFFrameSync fs;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 
     /* Push constants / options */
@@ -92,7 +91,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
     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);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -105,10 +104,11 @@ static av_cold int init_filter(AVFilterContext *ctx)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "overlay_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    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];                                        );
@@ -116,8 +116,8 @@ static av_cold int init_filter(AVFilterContext *ctx)
     GLSLC(0, };                                                           );
     GLSLC(0,                                                              );
 
-    ff_vk_add_push_constant(&s->pl, 0, sizeof(s->opts),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -147,7 +147,7 @@ static av_cold int init_filter(AVFilterContext *ctx)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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                                                );
@@ -165,10 +165,9 @@ static av_cold int init_filter(AVFilterContext *ctx)
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->opts.o_offset[0] = s->overlay_x;
     s->opts.o_offset[1] = s->overlay_y;
@@ -233,7 +232,7 @@ static int overlay_vulkan_blend(FFFrameSync *fs)
         goto fail;
     }
 
-    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl,
+    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd,
                                  out, (AVFrame *[]){ input_main, input_overlay }, 2,
                                  s->sampler, &s->opts, sizeof(s->opts)));
 
@@ -288,7 +287,6 @@ static void overlay_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_scale_vulkan.c b/libavfilter/vf_scale_vulkan.c
index 6a32ebbd5e..e39bc62aa8 100644
--- a/libavfilter/vf_scale_vulkan.c
+++ b/libavfilter/vf_scale_vulkan.c
@@ -38,10 +38,9 @@ typedef struct ScaleVulkanContext {
     FFVulkanContext vkctx;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 
     /* Push constants / options */
@@ -118,7 +117,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     VkFilter sampler_mode;
     ScaleVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -146,18 +145,19 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 0, sampler_mode));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "scale_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    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,    mat4 yuv_matrix;                                          );
     GLSLC(0, };                                                           );
     GLSLC(0,                                                              );
 
-    ff_vk_add_push_constant(&s->pl, 0, sizeof(s->opts),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -179,7 +179,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 2, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 2, 0, 0));
 
     GLSLD(   scale_bilinear                                                  );
 
@@ -249,10 +249,9 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -281,7 +280,7 @@ static int scale_vulkan_filter_frame(AVFilterLink *link, AVFrame *in)
     if (!s->initialized)
         RET(init_filter(ctx, in));
 
-    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, out, in,
+    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
                                     s->sampler, &s->opts, sizeof(s->opts)));
 
     err = av_frame_copy_props(out, in);
@@ -353,7 +352,6 @@ static void scale_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_transpose_vulkan.c b/libavfilter/vf_transpose_vulkan.c
index 72ea142e52..c3282339af 100644
--- a/libavfilter/vf_transpose_vulkan.c
+++ b/libavfilter/vf_transpose_vulkan.c
@@ -32,10 +32,9 @@ typedef struct TransposeVulkanContext {
     FFVulkanContext vkctx;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
     VkSampler sampler;
 
     int dir;
@@ -52,7 +51,7 @@ 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);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -65,10 +64,11 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_LINEAR));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "transpose_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 1, 1);
+    RET(ff_vk_shader_init(vkctx, &s->shd, "transpose",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 1, 1,
+                          0));
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -90,7 +90,7 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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, {                                                         );
@@ -115,10 +115,9 @@ static av_cold int init_filter(AVFilterContext *ctx, AVFrame *in)
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -151,7 +150,7 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in)
     if (!s->initialized)
         RET(init_filter(ctx, in));
 
-    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, out, in,
+    RET(ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, out, in,
                                     s->sampler, NULL, 0));
 
     RET(av_frame_copy_props(out, in));
@@ -180,7 +179,6 @@ static av_cold void transpose_vulkan_uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vf_xfade_vulkan.c b/libavfilter/vf_xfade_vulkan.c
index 94acc5258f..c230f5d833 100644
--- a/libavfilter/vf_xfade_vulkan.c
+++ b/libavfilter/vf_xfade_vulkan.c
@@ -40,10 +40,9 @@ typedef struct XFadeVulkanContext {
     int64_t             offset;
 
     int                 initialized;
-    FFVulkanPipeline    pl;
     FFVkExecPool        e;
     FFVkQueueFamilyCtx  qf;
-    FFVkSPIRVShader     shd;
+    FFVulkanShader      shd;
     VkSampler           sampler;
 
     // PTS when the fade should start (in IN_A timebase)
@@ -326,7 +325,7 @@ static av_cold int init_vulkan(AVFilterContext *avctx)
     XFadeVulkanContext *s = avctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc;
 
@@ -339,10 +338,11 @@ static av_cold int init_vulkan(AVFilterContext *avctx)
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
     RET(ff_vk_init_sampler(vkctx, &s->sampler, 1, VK_FILTER_NEAREST));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "xfade_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    RET(ff_vk_shader_init(vkctx, &s->shd, "xfade",
+                          VK_SHADER_STAGE_COMPUTE_BIT,
+                          NULL, 0,
+                          32, 32, 1,
+                          0));
 
     desc = (FFVulkanDescriptorSetBinding []) {
         {
@@ -372,14 +372,14 @@ static av_cold int init_vulkan(AVFilterContext *avctx)
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, shd, desc, 3, 0, 0));
+    RET(ff_vk_shader_add_descriptor_set(vkctx, &s->shd, desc, 3, 0, 0));
 
     GLSLC(0, layout(push_constant, std430) uniform pushConstants {                 );
     GLSLC(1,    float progress;                                                    );
     GLSLC(0, };                                                                    );
 
-    ff_vk_add_push_constant(&s->pl, 0, sizeof(XFadeParameters),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(XFadeParameters),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     // Add the right transition type function to the shader
     GLSLD(transitions_map[s->transition]);
@@ -395,10 +395,9 @@ static av_cold int init_vulkan(AVFilterContext *avctx)
 
     RET(spv->compile_shader(spv, avctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -441,7 +440,7 @@ static int xfade_frame(AVFilterContext *avctx, AVFrame *frame_a, AVFrame *frame_
     progress = av_clipf((float)(s->pts - s->start_pts) / s->duration_pts,
                         0.f, 1.f);
 
-    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->pl, output,
+    RET(ff_vk_filter_process_Nin(&s->vkctx, &s->e, &s->shd, output,
                                  (AVFrame *[]){ frame_a, frame_b }, 2, s->sampler,
                                  &(XFadeParameters){ progress }, sizeof(XFadeParameters)));
 
@@ -632,7 +631,6 @@ static av_cold void uninit(AVFilterContext *avctx)
     FFVulkanFunctions *vk = &vkctx->vkfn;
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     if (s->sampler)
diff --git a/libavfilter/vsrc_testsrc_vulkan.c b/libavfilter/vsrc_testsrc_vulkan.c
index 77fa9a0ba1..bfef38ce51 100644
--- a/libavfilter/vsrc_testsrc_vulkan.c
+++ b/libavfilter/vsrc_testsrc_vulkan.c
@@ -39,10 +39,9 @@ typedef struct TestSrcVulkanContext {
     FFVulkanContext vkctx;
 
     int initialized;
-    FFVulkanPipeline pl;
     FFVkExecPool e;
     FFVkQueueFamilyCtx qf;
-    FFVkSPIRVShader shd;
+    FFVulkanShader shd;
 
     /* Only used by color_vulkan */
     uint8_t color_rgba[4];
@@ -72,7 +71,7 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
     TestSrcVulkanContext *s = ctx->priv;
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
-    FFVkSPIRVShader *shd = &s->shd;
+    FFVulkanShader *shd = &s->shd;
     FFVkSPIRVCompiler *spv;
     FFVulkanDescriptorSetBinding *desc_set;
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(s->vkctx.output_format);
@@ -85,18 +84,19 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
 
     ff_vk_qf_init(vkctx, &s->qf, VK_QUEUE_COMPUTE_BIT);
     RET(ff_vk_exec_pool_init(vkctx, &s->qf, &s->e, s->qf.nb_queues*4, 0, 0, 0, NULL));
-    RET(ff_vk_shader_init(&s->pl, &s->shd, "testsrc_compute",
-                          VK_SHADER_STAGE_COMPUTE_BIT, 0));
-
-    ff_vk_shader_set_compute_sizes(&s->shd, 32, 32, 1);
+    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_add_push_constant(&s->pl, 0, sizeof(s->opts),
-                            VK_SHADER_STAGE_COMPUTE_BIT);
+    ff_vk_shader_add_push_const(&s->shd, 0, sizeof(s->opts),
+                                VK_SHADER_STAGE_COMPUTE_BIT);
 
     desc_set = (FFVulkanDescriptorSetBinding []) {
         {
@@ -110,7 +110,7 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
         },
     };
 
-    RET(ff_vk_pipeline_descriptor_set_add(vkctx, &s->pl, 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, {                                                            );
@@ -181,10 +181,9 @@ static av_cold int init_filter(AVFilterContext *ctx, enum TestSrcVulkanMode mode
 
     RET(spv->compile_shader(spv, ctx, shd, &spv_data, &spv_len, "main",
                             &spv_opaque));
-    RET(ff_vk_shader_create(vkctx, shd, spv_data, spv_len, "main"));
+    RET(ff_vk_shader_link(vkctx, shd, spv_data, spv_len, "main"));
 
-    RET(ff_vk_init_compute_pipeline(vkctx, &s->pl, shd));
-    RET(ff_vk_exec_pipeline_register(vkctx, &s->e, &s->pl));
+    RET(ff_vk_shader_register_exec(vkctx, &s->e, &s->shd));
 
     s->initialized = 1;
 
@@ -229,7 +228,7 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
             if (!s->picref)
                 return AVERROR(ENOMEM);
 
-            err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, s->picref, NULL,
+            err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, s->picref, NULL,
                                               VK_NULL_HANDLE, &s->opts, sizeof(s->opts));
             if (err < 0)
                 return err;
@@ -248,7 +247,7 @@ static int testsrc_vulkan_activate(AVFilterContext *ctx)
     frame->pict_type           = AV_PICTURE_TYPE_I;
     frame->sample_aspect_ratio = s->sar;
     if (!s->draw_once) {
-        err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->pl, frame, NULL,
+        err = ff_vk_filter_process_simple(&s->vkctx, &s->e, &s->shd, frame, NULL,
                                           VK_NULL_HANDLE, &s->opts, sizeof(s->opts));
         if (err < 0) {
             av_frame_free(&frame);
@@ -311,7 +310,6 @@ static void testsrc_vulkan_uninit(AVFilterContext *avctx)
     av_frame_free(&s->picref);
 
     ff_vk_exec_pool_free(vkctx, &s->e);
-    ff_vk_pipeline_free(vkctx, &s->pl);
     ff_vk_shader_free(vkctx, &s->shd);
 
     ff_vk_uninit(&s->vkctx);
diff --git a/libavfilter/vulkan_filter.c b/libavfilter/vulkan_filter.c
index 2c6ab72849..5cee4572e6 100644
--- a/libavfilter/vulkan_filter.c
+++ b/libavfilter/vulkan_filter.c
@@ -238,7 +238,7 @@ int ff_vk_filter_init(AVFilterContext *avctx)
 }
 
 int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
-                                FFVulkanPipeline *pl, AVFrame *out_f, AVFrame *in_f,
+                                FFVulkanShader *shd, AVFrame *out_f, AVFrame *in_f,
                                 VkSampler sampler, void *push_src, size_t push_size)
 {
     int err = 0;
@@ -256,24 +256,24 @@ int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
                                  VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
                                  VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
     RET(ff_vk_create_imageviews(vkctx, exec, out_views, out_f));
-    ff_vk_update_descriptor_img_array(vkctx, pl, exec, out_f, out_views, 0, !!in_f,
-                                      VK_IMAGE_LAYOUT_GENERAL,
-                                      VK_NULL_HANDLE);
+    ff_vk_shader_update_img_array(vkctx, exec, shd, out_f, out_views, 0, !!in_f,
+                                  VK_IMAGE_LAYOUT_GENERAL,
+                                  VK_NULL_HANDLE);
     if (in_f) {
         RET(ff_vk_exec_add_dep_frame(vkctx, exec, in_f,
                                      VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT,
                                      VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT));
         RET(ff_vk_create_imageviews(vkctx, exec, in_views,  in_f));
-        ff_vk_update_descriptor_img_array(vkctx, pl, exec,  in_f,  in_views, 0, 0,
+        ff_vk_shader_update_img_array(vkctx, exec, shd,  in_f,  in_views, 0, 0,
                                           VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
                                           sampler);
     }
 
     /* Bind pipeline, update push data */
-    ff_vk_exec_bind_pipeline(vkctx, exec, pl);
+    ff_vk_exec_bind_shader(vkctx, exec, shd);
     if (push_src)
-        ff_vk_update_push_exec(vkctx, exec, pl, VK_SHADER_STAGE_COMPUTE_BIT,
-                               0, push_size, push_src);
+        ff_vk_shader_update_push_const(vkctx, exec, shd, VK_SHADER_STAGE_COMPUTE_BIT,
+                                       0, push_size, push_src);
 
     /* Add data sync barriers */
     ff_vk_frame_barrier(vkctx, exec, out_f, img_bar, &nb_img_bar,
@@ -297,9 +297,9 @@ int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
         });
 
     vk->CmdDispatch(exec->buf,
-                    FFALIGN(vkctx->output_width,  pl->wg_size[0])/pl->wg_size[0],
-                    FFALIGN(vkctx->output_height, pl->wg_size[1])/pl->wg_size[1],
-                    pl->wg_size[2]);
+                    FFALIGN(vkctx->output_width,  shd->lg_size[0])/shd->lg_size[0],
+                    FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
+                    shd->lg_size[2]);
 
     return ff_vk_exec_submit(vkctx, exec);
 fail:
@@ -308,7 +308,7 @@ fail:
 }
 
 int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
-                               FFVulkanPipeline *pls[2],
+                               FFVulkanShader *shd_list[2],
                                AVFrame *out, AVFrame *tmp, AVFrame *in,
                                VkSampler sampler, void *push_src, size_t push_size)
 {
@@ -364,30 +364,30 @@ int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
         });
 
     for (int i = 0; i < 2; i++) {
-        FFVulkanPipeline *pl = pls[i];
+        FFVulkanShader *shd = shd_list[i];
         AVFrame *src_f = !i ? in : tmp;
         AVFrame *dst_f = !i ? tmp : out;
         VkImageView *src_views = !i ? in_views : tmp_views;
         VkImageView *dst_views = !i ? tmp_views : out_views;
 
-        ff_vk_update_descriptor_img_array(vkctx, pl, exec, src_f, src_views, 0, 0,
-                                          !i ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL :
-                                               VK_IMAGE_LAYOUT_GENERAL,
-                                          sampler);
-        ff_vk_update_descriptor_img_array(vkctx, pl, exec, dst_f, dst_views, 0, 1,
-                                          VK_IMAGE_LAYOUT_GENERAL,
-                                          VK_NULL_HANDLE);
+        ff_vk_shader_update_img_array(vkctx, exec, shd, src_f, src_views, 0, 0,
+                                      !i ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL :
+                                           VK_IMAGE_LAYOUT_GENERAL,
+                                      sampler);
+        ff_vk_shader_update_img_array(vkctx, exec, shd, dst_f, dst_views, 0, 1,
+                                      VK_IMAGE_LAYOUT_GENERAL,
+                                      VK_NULL_HANDLE);
 
         /* Bind pipeline, update push data */
-        ff_vk_exec_bind_pipeline(vkctx, exec, pl);
+        ff_vk_exec_bind_shader(vkctx, exec, shd);
         if (push_src)
-            ff_vk_update_push_exec(vkctx, exec, pl, VK_SHADER_STAGE_COMPUTE_BIT,
-                                   0, push_size, push_src);
+            ff_vk_shader_update_push_const(vkctx, exec, shd, VK_SHADER_STAGE_COMPUTE_BIT,
+                                           0, push_size, push_src);
 
         vk->CmdDispatch(exec->buf,
-                        FFALIGN(vkctx->output_width,  pl->wg_size[0])/pl->wg_size[0],
-                        FFALIGN(vkctx->output_height, pl->wg_size[1])/pl->wg_size[1],
-                        pl->wg_size[2]);
+                        FFALIGN(vkctx->output_width,  shd->lg_size[0])/shd->lg_size[0],
+                        FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
+                        shd->lg_size[2]);
     }
 
     return ff_vk_exec_submit(vkctx, exec);
@@ -397,7 +397,7 @@ fail:
 }
 
 int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
-                             FFVulkanPipeline *pl,
+                             FFVulkanShader *shd,
                              AVFrame *out, AVFrame *in[], int nb_in,
                              VkSampler sampler, void *push_src, size_t push_size)
 {
@@ -425,19 +425,19 @@ int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
     }
 
     /* Update descriptor sets */
-    ff_vk_update_descriptor_img_array(vkctx, pl, exec, out, out_views, 0, nb_in,
-                                      VK_IMAGE_LAYOUT_GENERAL,
-                                      VK_NULL_HANDLE);
+    ff_vk_shader_update_img_array(vkctx, exec, shd, out, out_views, 0, nb_in,
+                                  VK_IMAGE_LAYOUT_GENERAL,
+                                  VK_NULL_HANDLE);
     for (int i = 0; i < nb_in; i++)
-        ff_vk_update_descriptor_img_array(vkctx, pl, exec, in[i], in_views[i], 0, i,
-                                          VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                                          sampler);
+        ff_vk_shader_update_img_array(vkctx, exec, shd, in[i], in_views[i], 0, i,
+                                      VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                                      sampler);
 
     /* Bind pipeline, update push data */
-    ff_vk_exec_bind_pipeline(vkctx, exec, pl);
+    ff_vk_exec_bind_shader(vkctx, exec, shd);
     if (push_src)
-        ff_vk_update_push_exec(vkctx, exec, pl, VK_SHADER_STAGE_COMPUTE_BIT,
-                               0, push_size, push_src);
+        ff_vk_shader_update_push_const(vkctx, exec, shd, VK_SHADER_STAGE_COMPUTE_BIT,
+                                       0, push_size, push_src);
 
     /* Add data sync barriers */
     ff_vk_frame_barrier(vkctx, exec, out, img_bar, &nb_img_bar,
@@ -461,9 +461,9 @@ int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
         });
 
     vk->CmdDispatch(exec->buf,
-                    FFALIGN(vkctx->output_width,  pl->wg_size[0])/pl->wg_size[0],
-                    FFALIGN(vkctx->output_height, pl->wg_size[1])/pl->wg_size[1],
-                    pl->wg_size[2]);
+                    FFALIGN(vkctx->output_width,  shd->lg_size[0])/shd->lg_size[0],
+                    FFALIGN(vkctx->output_height, shd->lg_size[1])/shd->lg_size[1],
+                    shd->lg_size[2]);
 
     return ff_vk_exec_submit(vkctx, exec);
 fail:
diff --git a/libavfilter/vulkan_filter.h b/libavfilter/vulkan_filter.h
index d2c14601d9..9f1297e0d2 100644
--- a/libavfilter/vulkan_filter.h
+++ b/libavfilter/vulkan_filter.h
@@ -43,14 +43,14 @@ int ff_vk_filter_init_context(AVFilterContext *avctx, FFVulkanContext *s,
  * Submit a compute shader with a zero/one input and single out for execution.
  */
 int ff_vk_filter_process_simple(FFVulkanContext *vkctx, FFVkExecPool *e,
-                                FFVulkanPipeline *pl, AVFrame *out_f, AVFrame *in_f,
+                                FFVulkanShader *shd, AVFrame *out_f, AVFrame *in_f,
                                 VkSampler sampler, void *push_src, size_t push_size);
 
 /**
  * Submit a compute shader with a single in and single out with 2 stages.
  */
 int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
-                               FFVulkanPipeline *pls[2],
+                               FFVulkanShader *shd_list[2],
                                AVFrame *out, AVFrame *tmp, AVFrame *in,
                                VkSampler sampler, void *push_src, size_t push_size);
 
@@ -58,7 +58,7 @@ int ff_vk_filter_process_2pass(FFVulkanContext *vkctx, FFVkExecPool *e,
  * Up to 16 inputs, one output
  */
 int ff_vk_filter_process_Nin(FFVulkanContext *vkctx, FFVkExecPool *e,
-                             FFVulkanPipeline *pl,
+                             FFVulkanShader *shd,
                              AVFrame *out, AVFrame *in[], int nb_in,
                              VkSampler sampler, void *push_src, size_t push_size);
 
diff --git a/libavfilter/vulkan_glslang.c b/libavfilter/vulkan_glslang.c
index 845a530ee0..3c1b0a8fe6 100644
--- a/libavfilter/vulkan_glslang.c
+++ b/libavfilter/vulkan_glslang.c
@@ -137,7 +137,7 @@ static const glslang_resource_t glslc_resource_limits = {
 };
 
 static int glslc_shader_compile(FFVkSPIRVCompiler *ctx, void *avctx,
-                                FFVkSPIRVShader *shd, uint8_t **data,
+                                FFVulkanShader *shd, uint8_t **data,
                                 size_t *size, const char *entrypoint,
                                 void **opaque)
 {
@@ -153,7 +153,7 @@ static int glslc_shader_compile(FFVkSPIRVCompiler *ctx, void *avctx,
 
     const glslang_input_t glslc_input = {
         .language                          = GLSLANG_SOURCE_GLSL,
-        .stage                             = glslc_stage[shd->shader.stage],
+        .stage                             = glslc_stage[shd->stage],
         .client                            = GLSLANG_CLIENT_VULKAN,
         /* GLSLANG_TARGET_VULKAN_1_2 before 11.6 resulted in targeting 1.0 */
 #if (((GLSLANG_VERSION_MAJOR) > 11) || ((GLSLANG_VERSION_MAJOR) == 11 && \
diff --git a/libavfilter/vulkan_shaderc.c b/libavfilter/vulkan_shaderc.c
index 9e8a3d17ac..d2bd60e7ef 100644
--- a/libavfilter/vulkan_shaderc.c
+++ b/libavfilter/vulkan_shaderc.c
@@ -22,7 +22,7 @@
 #include "vulkan_spirv.h"
 
 static int shdc_shader_compile(FFVkSPIRVCompiler *ctx, void *avctx,
-                               FFVkSPIRVShader *shd, uint8_t **data,
+                               FFVulkanShader *shd, uint8_t **data,
                                size_t *size, const char *entrypoint,
                                void **opaque)
 {
@@ -57,7 +57,7 @@ static int shdc_shader_compile(FFVkSPIRVCompiler *ctx, void *avctx,
 
     res = shaderc_compile_into_spv((shaderc_compiler_t)ctx->priv,
                                    shd->src.str, strlen(shd->src.str),
-                                   shdc_kind[shd->shader.stage],
+                                   shdc_kind[shd->stage],
                                    shd->name, entrypoint, opts);
     shaderc_compile_options_release(opts);
 
diff --git a/libavfilter/vulkan_spirv.h b/libavfilter/vulkan_spirv.h
index 5638cd9696..c46c9e52da 100644
--- a/libavfilter/vulkan_spirv.h
+++ b/libavfilter/vulkan_spirv.h
@@ -27,7 +27,7 @@
 typedef struct FFVkSPIRVCompiler {
     void *priv;
     int (*compile_shader)(struct FFVkSPIRVCompiler *ctx, void *avctx,
-                          struct FFVkSPIRVShader *shd, uint8_t **data,
+                          struct FFVulkanShader *shd, uint8_t **data,
                           size_t *size, const char *entrypoint, void **opaque);
     void (*free_shader)(struct FFVkSPIRVCompiler *ctx, void **opaque);
     void (*uninit)(struct FFVkSPIRVCompiler **ctx);
diff --git a/libavutil/vulkan.c b/libavutil/vulkan.c
index 046ac5d67e..9ea9e478df 100644
--- a/libavutil/vulkan.c
+++ b/libavutil/vulkan.c
@@ -1191,17 +1191,18 @@ int ff_vk_get_pooled_buffer(FFVulkanContext *ctx, AVBufferPool **buf_pool,
     return 0;
 }
 
-int ff_vk_add_push_constant(FFVulkanPipeline *pl, int offset, int size,
-                            VkShaderStageFlagBits stage)
+int ff_vk_shader_add_push_const(FFVulkanShader *shd, int offset, int size,
+                                VkShaderStageFlagBits stage)
 {
     VkPushConstantRange *pc;
 
-    pl->push_consts = av_realloc_array(pl->push_consts, sizeof(*pl->push_consts),
-                                       pl->push_consts_num + 1);
-    if (!pl->push_consts)
+    shd->push_consts = av_realloc_array(shd->push_consts,
+                                        sizeof(*shd->push_consts),
+                                        shd->push_consts_num + 1);
+    if (!shd->push_consts)
         return AVERROR(ENOMEM);
 
-    pc = &pl->push_consts[pl->push_consts_num++];
+    pc = &shd->push_consts[shd->push_consts_num++];
     memset(pc, 0, sizeof(*pc));
 
     pc->stageFlags = stage;
@@ -1397,44 +1398,79 @@ void ff_vk_frame_barrier(FFVulkanContext *s, FFVkExecContext *e,
     ff_vk_exec_update_frame(s, e, pic, &bar[*nb_bar - nb_images], NULL);
 }
 
-int ff_vk_shader_init(FFVulkanPipeline *pl, FFVkSPIRVShader *shd, const char *name,
-                      VkShaderStageFlags stage, uint32_t required_subgroup_size)
+int ff_vk_shader_init(FFVulkanContext *s, FFVulkanShader *shd, const char *name,
+                      VkPipelineStageFlags stage,
+                      const char *extensions[], int nb_extensions,
+                      int lg_x, int lg_y, int lg_z,
+                      uint32_t required_subgroup_size)
 {
     av_bprint_init(&shd->src, 0, AV_BPRINT_SIZE_UNLIMITED);
 
-    shd->shader.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
-    shd->shader.stage = stage;
+    shd->name = name;
+    shd->stage = stage;
+    shd->lg_size[0] = lg_x;
+    shd->lg_size[1] = lg_y;
+    shd->lg_size[2] = lg_z;
+
+    switch (shd->stage) {
+    case VK_SHADER_STAGE_ANY_HIT_BIT_KHR:
+    case VK_SHADER_STAGE_CALLABLE_BIT_KHR:
+    case VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR:
+    case VK_SHADER_STAGE_INTERSECTION_BIT_KHR:
+    case VK_SHADER_STAGE_MISS_BIT_KHR:
+    case VK_SHADER_STAGE_RAYGEN_BIT_KHR:
+        shd->bind_point = VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR;
+        break;
+    case VK_SHADER_STAGE_COMPUTE_BIT:
+        shd->bind_point = VK_PIPELINE_BIND_POINT_COMPUTE;
+        break;
+    default:
+        shd->bind_point = VK_PIPELINE_BIND_POINT_GRAPHICS;
+        break;
+    };
 
     if (required_subgroup_size) {
-        shd->shader.flags |= VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT;
-        shd->shader.pNext = &shd->subgroup_info;
         shd->subgroup_info.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_REQUIRED_SUBGROUP_SIZE_CREATE_INFO;
         shd->subgroup_info.requiredSubgroupSize = required_subgroup_size;
     }
 
-    shd->name = name;
-
+    av_bprintf(&shd->src, "/* %s shader: %s */\n",
+               (stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
+                stage == VK_SHADER_STAGE_MESH_BIT_EXT) ?
+               "Mesh" :
+               (shd->bind_point == VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR) ?
+               "Raytrace" :
+               (shd->bind_point == VK_PIPELINE_BIND_POINT_COMPUTE) ?
+               "Compute" : "Graphics",
+               name);
     GLSLF(0, #version %i                                                  ,460);
+    GLSLC(0,                                                                  );
+
+    /* Common utilities */
     GLSLC(0, #define IS_WITHIN(v1, v2) ((v1.x < v2.x) && (v1.y < v2.y))       );
     GLSLC(0,                                                                  );
-    GLSLC(0, #extension GL_EXT_buffer_reference : require                     );
-    GLSLC(0, #extension GL_EXT_buffer_reference2 : require                    );
 
-    return 0;
-}
+    if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
+        GLSLC(0, #extension GL_EXT_buffer_reference : require                 );
+        GLSLC(0, #extension GL_EXT_buffer_reference2 : require                );
+    }
 
-void ff_vk_shader_set_compute_sizes(FFVkSPIRVShader *shd, int x, int y, int z)
-{
-    shd->local_size[0] = x;
-    shd->local_size[1] = y;
-    shd->local_size[2] = z;
+    if (stage == VK_SHADER_STAGE_TASK_BIT_EXT ||
+        stage == VK_SHADER_STAGE_MESH_BIT_EXT)
+        GLSLC(0, #extension GL_EXT_mesh_shader : require                      );
 
-    av_bprintf(&shd->src, "layout (local_size_x = %i, "
-               "local_size_y = %i, local_size_z = %i) in;\n\n",
-               shd->local_size[0], shd->local_size[1], shd->local_size[2]);
+    for (int i = 0; i < nb_extensions; i++)
+        GLSLF(0, #extension %s : %s                  ,extensions[i], "require");
+    GLSLC(0,                                                                  );
+
+    GLSLF(0, layout (local_size_x = %i, local_size_y = %i, local_size_z = %i) in;
+          , shd->lg_size[0], shd->lg_size[1], shd->lg_size[2]);
+    GLSLC(0,                                                                  );
+
+    return 0;
 }
 
-void ff_vk_shader_print(void *ctx, FFVkSPIRVShader *shd, int prio)
+void ff_vk_shader_print(void *ctx, FFVulkanShader *shd, int prio)
 {
     int line = 0;
     const char *p = shd->src.str;
@@ -1456,37 +1492,86 @@ void ff_vk_shader_print(void *ctx, FFVkSPIRVShader *shd, int prio)
     av_bprint_finalize(&buf, NULL);
 }
 
-void ff_vk_shader_free(FFVulkanContext *s, FFVkSPIRVShader *shd)
+static int init_pipeline_layout(FFVulkanContext *s, FFVulkanShader *shd)
 {
+    VkResult ret;
     FFVulkanFunctions *vk = &s->vkfn;
-    av_bprint_finalize(&shd->src, NULL);
+    VkPipelineLayoutCreateInfo pipeline_layout_info;
 
-    if (shd->shader.module)
-        vk->DestroyShaderModule(s->hwctx->act_dev, shd->shader.module, s->hwctx->alloc);
+    /* Finally create the pipeline layout */
+    pipeline_layout_info = (VkPipelineLayoutCreateInfo) {
+        .sType                  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
+        .pSetLayouts            = shd->desc_layout,
+        .setLayoutCount         = shd->nb_descriptor_sets,
+        .pushConstantRangeCount = shd->push_consts_num,
+        .pPushConstantRanges    = shd->push_consts,
+    };
+
+    ret = vk->CreatePipelineLayout(s->hwctx->act_dev, &pipeline_layout_info,
+                                   s->hwctx->alloc, &shd->pipeline_layout);
+    if (ret != VK_SUCCESS) {
+        av_log(s, AV_LOG_ERROR, "Unable to init pipeline layout: %s\n",
+               ff_vk_ret2str(ret));
+        return AVERROR_EXTERNAL;
+    }
+
+    return 0;
 }
 
-int ff_vk_shader_create(FFVulkanContext *s, FFVkSPIRVShader *shd,
-                        uint8_t *spirv, size_t spirv_size, const char *entrypoint)
+static int create_shader_module(FFVulkanContext *s, FFVulkanShader *shd,
+                                VkShaderModule *mod,
+                                uint8_t *spirv, size_t spirv_len)
 {
     VkResult ret;
     FFVulkanFunctions *vk = &s->vkfn;
-    VkShaderModuleCreateInfo shader_create;
 
-    shd->shader.pName = entrypoint;
+    VkShaderModuleCreateInfo shader_module_info = {
+        .sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
+        .pNext    = NULL,
+        .flags    = 0x0,
+        .pCode    = (void *)spirv,
+        .codeSize = spirv_len,
+    };
 
-    av_log(s, AV_LOG_VERBOSE, "Shader %s compiled! Size: %zu bytes\n",
-           shd->name, spirv_size);
+    ret = vk->CreateShaderModule(s->hwctx->act_dev, &shader_module_info,
+                                 s->hwctx->alloc, mod);
+    if (ret != VK_SUCCESS) {
+        av_log(s, AV_LOG_VERBOSE, "Error creating shader module: %s\n",
+               ff_vk_ret2str(ret));
+        return AVERROR_EXTERNAL;
+    }
 
-    shader_create.sType    = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
-    shader_create.pNext    = NULL;
-    shader_create.codeSize = spirv_size;
-    shader_create.flags    = 0;
-    shader_create.pCode    = (void *)spirv;
+    return 0;
+}
 
-    ret = vk->CreateShaderModule(s->hwctx->act_dev, &shader_create, NULL,
-                                 &shd->shader.module);
+static int init_compute_pipeline(FFVulkanContext *s, FFVulkanShader *shd,
+                                 VkShaderModule mod, const char *entrypoint)
+{
+    VkResult ret;
+    FFVulkanFunctions *vk = &s->vkfn;
+
+    VkComputePipelineCreateInfo pipeline_create_info = {
+        .sType  = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
+        .flags = (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) ?
+                 VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT : 0x0,
+        .layout = shd->pipeline_layout,
+        .stage = (VkPipelineShaderStageCreateInfo) {
+            .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
+            .pNext = shd->subgroup_info.requiredSubgroupSize ?
+                     &shd->subgroup_info : NULL,
+            .pName = entrypoint,
+            .flags = shd->subgroup_info.requiredSubgroupSize ?
+                     VK_PIPELINE_SHADER_STAGE_CREATE_REQUIRE_FULL_SUBGROUPS_BIT : 0x0,
+            .stage = shd->stage,
+            .module = mod,
+        },
+    };
+
+    ret = vk->CreateComputePipelines(s->hwctx->act_dev, VK_NULL_HANDLE, 1,
+                                     &pipeline_create_info,
+                                     s->hwctx->alloc, &shd->pipeline);
     if (ret != VK_SUCCESS) {
-        av_log(s, AV_LOG_VERBOSE, "Error creating shader module: %s\n",
+        av_log(s, AV_LOG_ERROR, "Unable to init compute pipeline: %s\n",
                ff_vk_ret2str(ret));
         return AVERROR_EXTERNAL;
     }
@@ -1494,6 +1579,110 @@ int ff_vk_shader_create(FFVulkanContext *s, FFVkSPIRVShader *shd,
     return 0;
 }
 
+static int init_descriptors(FFVulkanContext *s, FFVulkanShader *shd)
+{
+    VkResult ret;
+    FFVulkanFunctions *vk = &s->vkfn;
+
+    shd->desc_layout = av_malloc_array(shd->nb_descriptor_sets,
+                                       sizeof(*shd->desc_layout));
+    if (!shd->desc_layout)
+        return AVERROR(ENOMEM);
+
+    if (!(s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER)) {
+        int has_singular = 0;
+        for (int i = 0; i < shd->nb_descriptor_sets; i++) {
+            if (shd->desc_set[i].singular) {
+                has_singular = 1;
+                break;
+            }
+        }
+        shd->use_push = (s->extensions & FF_VK_EXT_PUSH_DESCRIPTOR) &&
+                        (shd->nb_descriptor_sets == 1) &&
+                        !has_singular;
+    }
+
+    for (int i = 0; i < shd->nb_descriptor_sets; i++) {
+        FFVulkanDescriptorSet *set = &shd->desc_set[i];
+        VkDescriptorSetLayoutCreateInfo desc_layout_create = {
+            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
+            .bindingCount = set->nb_bindings,
+            .pBindings = set->binding,
+            .flags = (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) ?
+                     VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT :
+                     (shd->use_push) ?
+                     VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR :
+                     0x0,
+        };
+
+        ret = vk->CreateDescriptorSetLayout(s->hwctx->act_dev,
+                                            &desc_layout_create,
+                                            s->hwctx->alloc,
+                                            &shd->desc_layout[i]);
+        if (ret != VK_SUCCESS) {
+            av_log(s, AV_LOG_ERROR, "Unable to create descriptor set layout: %s",
+                   ff_vk_ret2str(ret));
+            return AVERROR_EXTERNAL;
+        }
+
+        if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
+            vk->GetDescriptorSetLayoutSizeEXT(s->hwctx->act_dev, shd->desc_layout[i],
+                                              &set->layout_size);
+
+            set->aligned_size = FFALIGN(set->layout_size,
+                                        s->desc_buf_props.descriptorBufferOffsetAlignment);
+
+            for (int j = 0; j < set->nb_bindings; j++)
+                vk->GetDescriptorSetLayoutBindingOffsetEXT(s->hwctx->act_dev,
+                                                           shd->desc_layout[i],
+                                                           j,
+                                                           &set->binding_offset[j]);
+        }
+    }
+
+    return 0;
+}
+
+int ff_vk_shader_link(FFVulkanContext *s, FFVulkanShader *shd,
+                      uint8_t *spirv, size_t spirv_len,
+                      const char *entrypoint)
+{
+    int err;
+    FFVulkanFunctions *vk = &s->vkfn;
+
+    err = init_descriptors(s, shd);
+    if (err < 0)
+        return err;
+
+    err = init_pipeline_layout(s, shd);
+    if (err < 0)
+        return err;
+
+    {
+        VkShaderModule mod;
+        err = create_shader_module(s, shd, &mod, spirv, spirv_len);
+        if (err < 0)
+            return err;
+
+        switch (shd->bind_point) {
+        case VK_PIPELINE_BIND_POINT_COMPUTE:
+            err = init_compute_pipeline(s, shd, mod, entrypoint);
+            break;
+        default:
+            av_log(s, AV_LOG_ERROR, "Unsupported shader type: %i\n",
+                   shd->bind_point);
+            err = AVERROR(EINVAL);
+            break;
+        };
+
+        vk->DestroyShaderModule(s->hwctx->act_dev, mod, s->hwctx->alloc);
+        if (err < 0)
+            return err;
+    }
+
+    return 0;
+}
+
 static const struct descriptor_props {
     size_t struct_size; /* Size of the opaque which updates the descriptor */
     const char *type;
@@ -1515,10 +1704,9 @@ static const struct descriptor_props {
     [VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER]   = { sizeof(VkBufferView),           "imageBuffer",   1, 0, 0, 0, },
 };
 
-int ff_vk_pipeline_descriptor_set_add(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                      FFVkSPIRVShader *shd,
-                                      FFVulkanDescriptorSetBinding *desc, int nb,
-                                      int singular, int print_to_shader_only)
+int ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
+                                    FFVulkanDescriptorSetBinding *desc, int nb,
+                                    int singular, int print_to_shader_only)
 {
     int has_sampler = 0;
     FFVulkanDescriptorSet *set;
@@ -1527,13 +1715,14 @@ int ff_vk_pipeline_descriptor_set_add(FFVulkanContext *s, FFVulkanPipeline *pl,
         goto print;
 
     /* Actual layout allocated for the pipeline */
-    set = av_realloc_array(pl->desc_set, sizeof(*pl->desc_set),
-                           pl->nb_descriptor_sets + 1);
+    set = av_realloc_array(shd->desc_set,
+                           sizeof(*shd->desc_set),
+                           shd->nb_descriptor_sets + 1);
     if (!set)
         return AVERROR(ENOMEM);
-    pl->desc_set = set;
+    shd->desc_set = set;
 
-    set = &set[pl->nb_descriptor_sets];
+    set = &set[shd->nb_descriptor_sets];
     memset(set, 0, sizeof(*set));
 
     set->binding = av_calloc(nb, sizeof(*set->binding));
@@ -1567,34 +1756,34 @@ int ff_vk_pipeline_descriptor_set_add(FFVulkanContext *s, FFVulkanPipeline *pl,
         for (int i = 0; i < nb; i++) {
             int j;
             VkDescriptorPoolSize *desc_pool_size;
-            for (j = 0; j < pl->nb_desc_pool_size; j++)
-                if (pl->desc_pool_size[j].type == desc[i].type)
+            for (j = 0; j < shd->nb_desc_pool_size; j++)
+                if (shd->desc_pool_size[j].type == desc[i].type)
                     break;
-            if (j >= pl->nb_desc_pool_size) {
-                desc_pool_size = av_realloc_array(pl->desc_pool_size,
+            if (j >= shd->nb_desc_pool_size) {
+                desc_pool_size = av_realloc_array(shd->desc_pool_size,
                                                   sizeof(*desc_pool_size),
-                                                  pl->nb_desc_pool_size + 1);
+                                                  shd->nb_desc_pool_size + 1);
                 if (!desc_pool_size)
                     return AVERROR(ENOMEM);
 
-                pl->desc_pool_size = desc_pool_size;
-                pl->nb_desc_pool_size++;
+                shd->desc_pool_size = desc_pool_size;
+                shd->nb_desc_pool_size++;
                 memset(&desc_pool_size[j], 0, sizeof(VkDescriptorPoolSize));
             }
-            pl->desc_pool_size[j].type             = desc[i].type;
-            pl->desc_pool_size[j].descriptorCount += FFMAX(desc[i].elems, 1);
+            shd->desc_pool_size[j].type             = desc[i].type;
+            shd->desc_pool_size[j].descriptorCount += FFMAX(desc[i].elems, 1);
         }
     }
 
     set->singular = singular;
     set->nb_bindings = nb;
-    pl->nb_descriptor_sets++;
+    shd->nb_descriptor_sets++;
 
 print:
     /* Write shader info */
     for (int i = 0; i < nb; i++) {
         const struct descriptor_props *prop = &descriptor_props[desc[i].type];
-        GLSLA("layout (set = %i, binding = %i", pl->nb_descriptor_sets - 1, i);
+        GLSLA("layout (set = %i, binding = %i", shd->nb_descriptor_sets - 1, i);
 
         if (desc[i].mem_layout)
             GLSLA(", %s", desc[i].mem_layout);
@@ -1627,26 +1816,26 @@ print:
     return 0;
 }
 
-int ff_vk_exec_pipeline_register(FFVulkanContext *s, FFVkExecPool *pool,
-                                 FFVulkanPipeline *pl)
+int ff_vk_shader_register_exec(FFVulkanContext *s, FFVkExecPool *pool,
+                               FFVulkanShader *shd)
 {
     int err;
 
-    if (!pl->nb_descriptor_sets)
+    if (!shd->nb_descriptor_sets)
         return 0;
 
     if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
-        pl->desc_bind = av_calloc(pl->nb_descriptor_sets, sizeof(*pl->desc_bind));
-        if (!pl->desc_bind)
+        shd->desc_bind = av_calloc(shd->nb_descriptor_sets, sizeof(*shd->desc_bind));
+        if (!shd->desc_bind)
             return AVERROR(ENOMEM);
 
-        pl->bound_buffer_indices = av_calloc(pl->nb_descriptor_sets,
-                                             sizeof(*pl->bound_buffer_indices));
-        if (!pl->bound_buffer_indices)
+        shd->bound_buffer_indices = av_calloc(shd->nb_descriptor_sets,
+                                             sizeof(*shd->bound_buffer_indices));
+        if (!shd->bound_buffer_indices)
             return AVERROR(ENOMEM);
 
-        for (int i = 0; i < pl->nb_descriptor_sets; i++) {
-            FFVulkanDescriptorSet *set = &pl->desc_set[i];
+        for (int i = 0; i < shd->nb_descriptor_sets; i++) {
+            FFVulkanDescriptorSet *set = &shd->desc_set[i];
             int nb = set->singular ? 1 : pool->pool_size;
 
             err = ff_vk_create_buf(s, &set->buf, set->aligned_size*nb,
@@ -1661,34 +1850,34 @@ int ff_vk_exec_pipeline_register(FFVulkanContext *s, FFVkExecPool *pool,
             if (err < 0)
                 return err;
 
-            pl->desc_bind[i] = (VkDescriptorBufferBindingInfoEXT) {
+            shd->desc_bind[i] = (VkDescriptorBufferBindingInfoEXT) {
                 .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_BUFFER_BINDING_INFO_EXT,
                 .usage = set->usage,
                 .address = set->buf.address,
             };
 
-            pl->bound_buffer_indices[i] = i;
+            shd->bound_buffer_indices[i] = i;
         }
-    } else if (!pl->use_push) {
+    } else if (!shd->use_push) {
         VkResult ret;
         FFVulkanFunctions *vk = &s->vkfn;
         VkDescriptorSetLayout *tmp_layouts;
         VkDescriptorSetAllocateInfo set_alloc_info;
         VkDescriptorPoolCreateInfo pool_create_info;
 
-        for (int i = 0; i < pl->nb_desc_pool_size; i++)
-            pl->desc_pool_size[i].descriptorCount *= pool->pool_size;
+        for (int i = 0; i < shd->nb_desc_pool_size; i++)
+            shd->desc_pool_size[i].descriptorCount *= pool->pool_size;
 
         pool_create_info = (VkDescriptorPoolCreateInfo) {
             .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
             .flags = 0,
-            .pPoolSizes = pl->desc_pool_size,
-            .poolSizeCount = pl->nb_desc_pool_size,
-            .maxSets = pl->nb_descriptor_sets*pool->pool_size,
+            .pPoolSizes = shd->desc_pool_size,
+            .poolSizeCount = shd->nb_desc_pool_size,
+            .maxSets = shd->nb_descriptor_sets*pool->pool_size,
         };
 
         ret = vk->CreateDescriptorPool(s->hwctx->act_dev, &pool_create_info,
-                                       s->hwctx->alloc, &pl->desc_pool);
+                                       s->hwctx->alloc, &shd->desc_pool);
         if (ret != VK_SUCCESS) {
             av_log(s, AV_LOG_ERROR, "Unable to create descriptor pool: %s\n",
                    ff_vk_ret2str(ret));
@@ -1701,33 +1890,33 @@ int ff_vk_exec_pipeline_register(FFVulkanContext *s, FFVkExecPool *pool,
 
         /* Colate each execution context's descriptor set layouts */
         for (int i = 0; i < pool->pool_size; i++)
-            for (int j = 0; j < pl->nb_descriptor_sets; j++)
-                tmp_layouts[i*pl->nb_descriptor_sets + j] = pl->desc_layout[j];
+            for (int j = 0; j < shd->nb_descriptor_sets; j++)
+                tmp_layouts[i*shd->nb_descriptor_sets + j] = shd->desc_layout[j];
 
         set_alloc_info = (VkDescriptorSetAllocateInfo) {
             .sType              = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
-            .descriptorPool     = pl->desc_pool,
+            .descriptorPool     = shd->desc_pool,
             .pSetLayouts        = tmp_layouts,
             .descriptorSetCount = pool_create_info.maxSets,
         };
 
-        pl->desc_sets = av_malloc_array(pool_create_info.maxSets,
+        shd->desc_sets = av_malloc_array(pool_create_info.maxSets,
                                         sizeof(*tmp_layouts));
-        if (!pl->desc_sets) {
+        if (!shd->desc_sets) {
             av_free(tmp_layouts);
             return AVERROR(ENOMEM);
         }
         ret = vk->AllocateDescriptorSets(s->hwctx->act_dev, &set_alloc_info,
-                                         pl->desc_sets);
+                                         shd->desc_sets);
         av_free(tmp_layouts);
         if (ret != VK_SUCCESS) {
             av_log(s, AV_LOG_ERROR, "Unable to allocate descriptor set: %s\n",
                    ff_vk_ret2str(ret));
-            av_freep(&pl->desc_sets);
+            av_freep(&shd->desc_sets);
             return AVERROR_EXTERNAL;
         }
 
-        pl->assoc_pool = pool;
+        shd->assoc_pool = pool;
     }
 
     return 0;
@@ -1749,38 +1938,37 @@ static inline void update_set_descriptor(FFVulkanContext *s, FFVkExecContext *e,
     vk->GetDescriptorEXT(s->hwctx->act_dev, desc_get_info, desc_size, desc);
 }
 
-static inline void update_set_pool_write(FFVulkanContext *s,
-                                         FFVulkanPipeline *pl,
+static inline void update_set_pool_write(FFVulkanContext *s, FFVulkanShader *shd,
                                          FFVkExecContext *e,
                                          FFVulkanDescriptorSet *desc_set, int set,
                                          VkWriteDescriptorSet *write_info)
 {
     FFVulkanFunctions *vk = &s->vkfn;
     if (desc_set->singular) {
-        for (int i = 0; i < pl->assoc_pool->pool_size; i++) {
-            write_info->dstSet = pl->desc_sets[i*pl->nb_descriptor_sets + set];
+        for (int i = 0; i < shd->assoc_pool->pool_size; i++) {
+            write_info->dstSet = shd->desc_sets[i*shd->nb_descriptor_sets + set];
             vk->UpdateDescriptorSets(s->hwctx->act_dev, 1, write_info, 0, NULL);
         }
     } else {
-        if (pl->use_push) {
+        if (shd->use_push) {
             vk->CmdPushDescriptorSetKHR(e->buf,
-                                        pl->bind_point,
-                                        pl->pipeline_layout,
+                                        shd->bind_point,
+                                        shd->pipeline_layout,
                                         set, 1,
                                         write_info);
         } else {
-            write_info->dstSet = pl->desc_sets[e->idx*pl->nb_descriptor_sets + set];
+            write_info->dstSet = shd->desc_sets[e->idx*shd->nb_descriptor_sets + set];
             vk->UpdateDescriptorSets(s->hwctx->act_dev, 1, write_info, 0, NULL);
         }
     }
 }
 
-static int vk_set_descriptor_image(FFVulkanContext *s, FFVulkanPipeline *pl,
+static int vk_set_descriptor_image(FFVulkanContext *s, FFVulkanShader *shd,
                                    FFVkExecContext *e, int set, int bind, int offs,
                                    VkImageView view, VkImageLayout layout,
                                    VkSampler sampler)
 {
-    FFVulkanDescriptorSet *desc_set = &pl->desc_set[set];
+    FFVulkanDescriptorSet *desc_set = &shd->desc_set[set];
 
     if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
         VkDescriptorGetInfoEXT desc_get_info = {
@@ -1834,18 +2022,19 @@ static int vk_set_descriptor_image(FFVulkanContext *s, FFVulkanPipeline *pl,
             .descriptorType = desc_set->binding[bind].descriptorType,
             .pImageInfo = &desc_pool_write_info_img,
         };
-        update_set_pool_write(s, pl, e, desc_set, set, &desc_pool_write_info);
+        update_set_pool_write(s, shd, e, desc_set, set, &desc_pool_write_info);
     }
 
     return 0;
 }
 
-int ff_vk_set_descriptor_buffer(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                FFVkExecContext *e, int set, int bind, int elem,
-                                FFVkBuffer *buf, VkDeviceSize offset, VkDeviceSize len,
-                                VkFormat fmt)
+int ff_vk_shader_update_desc_buffer(FFVulkanContext *s, FFVkExecContext *e,
+                                    FFVulkanShader *shd,
+                                    int set, int bind, int elem,
+                                    FFVkBuffer *buf, VkDeviceSize offset, VkDeviceSize len,
+                                    VkFormat fmt)
 {
-    FFVulkanDescriptorSet *desc_set = &pl->desc_set[set];
+    FFVulkanDescriptorSet *desc_set = &shd->desc_set[set];
 
     if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
         VkDescriptorGetInfoEXT desc_get_info = {
@@ -1899,208 +2088,84 @@ int ff_vk_set_descriptor_buffer(FFVulkanContext *s, FFVulkanPipeline *pl,
             .descriptorType = desc_set->binding[bind].descriptorType,
             .pBufferInfo = &desc_pool_write_info_buf,
         };
-        update_set_pool_write(s, pl, e, desc_set, set, &desc_pool_write_info);
+        update_set_pool_write(s, shd, e, desc_set, set, &desc_pool_write_info);
     }
 
     return 0;
 }
 
-void ff_vk_update_descriptor_img_array(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                       FFVkExecContext *e, AVFrame *f,
-                                       VkImageView *views, int set, int binding,
-                                       VkImageLayout layout, VkSampler sampler)
+void ff_vk_shader_update_img_array(FFVulkanContext *s, FFVkExecContext *e,
+                                   FFVulkanShader *shd, AVFrame *f,
+                                   VkImageView *views, int set, int binding,
+                                   VkImageLayout layout, VkSampler sampler)
 {
     AVHWFramesContext *hwfc = (AVHWFramesContext *)f->hw_frames_ctx->data;
     const int nb_planes = av_pix_fmt_count_planes(hwfc->sw_format);
 
     for (int i = 0; i < nb_planes; i++)
-        vk_set_descriptor_image(s, pl, e, set, binding, i,
+        vk_set_descriptor_image(s, shd, e, set, binding, i,
                                 views[i], layout, sampler);
 }
 
-void ff_vk_update_push_exec(FFVulkanContext *s, FFVkExecContext *e,
-                            FFVulkanPipeline *pl,
-                            VkShaderStageFlagBits stage,
-                            int offset, size_t size, void *src)
+void ff_vk_shader_update_push_const(FFVulkanContext *s, FFVkExecContext *e,
+                                    FFVulkanShader *shd,
+                                    VkShaderStageFlagBits stage,
+                                    int offset, size_t size, void *src)
 {
     FFVulkanFunctions *vk = &s->vkfn;
-    vk->CmdPushConstants(e->buf, pl->pipeline_layout,
+    vk->CmdPushConstants(e->buf, shd->pipeline_layout,
                          stage, offset, size, src);
 }
 
-static int init_descriptors(FFVulkanContext *s, FFVulkanPipeline *pl)
-{
-    VkResult ret;
-    FFVulkanFunctions *vk = &s->vkfn;
-
-    pl->desc_layout = av_malloc_array(pl->nb_descriptor_sets,
-                                      sizeof(*pl->desc_layout));
-    if (!pl->desc_layout)
-        return AVERROR(ENOMEM);
-
-    if (!(s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER)) {
-        int has_singular = 0;
-        for (int i = 0; i < pl->nb_descriptor_sets; i++) {
-            if (pl->desc_set[i].singular) {
-                has_singular = 1;
-                break;
-            }
-        }
-        pl->use_push = (s->extensions & FF_VK_EXT_PUSH_DESCRIPTOR) &&
-                       (pl->nb_descriptor_sets == 1) &&
-                       !has_singular;
-    }
-
-    for (int i = 0; i < pl->nb_descriptor_sets; i++) {
-        FFVulkanDescriptorSet *set = &pl->desc_set[i];
-        VkDescriptorSetLayoutCreateInfo desc_layout_create = {
-            .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
-            .bindingCount = set->nb_bindings,
-            .pBindings = set->binding,
-            .flags = (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) ?
-                     VK_DESCRIPTOR_SET_LAYOUT_CREATE_DESCRIPTOR_BUFFER_BIT_EXT :
-                     (pl->use_push) ?
-                     VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR :
-                     0x0,
-        };
-
-        ret = vk->CreateDescriptorSetLayout(s->hwctx->act_dev,
-                                            &desc_layout_create,
-                                            s->hwctx->alloc,
-                                            &pl->desc_layout[i]);
-        if (ret != VK_SUCCESS) {
-            av_log(s, AV_LOG_ERROR, "Unable to create descriptor set layout: %s",
-                   ff_vk_ret2str(ret));
-            return AVERROR_EXTERNAL;
-        }
-
-        if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
-            vk->GetDescriptorSetLayoutSizeEXT(s->hwctx->act_dev, pl->desc_layout[i],
-                                              &set->layout_size);
-
-            set->aligned_size = FFALIGN(set->layout_size,
-                                        s->desc_buf_props.descriptorBufferOffsetAlignment);
-
-            for (int j = 0; j < set->nb_bindings; j++)
-                vk->GetDescriptorSetLayoutBindingOffsetEXT(s->hwctx->act_dev,
-                                                           pl->desc_layout[i],
-                                                           j,
-                                                           &set->binding_offset[j]);
-        }
-    }
-
-    return 0;
-}
-
-static int init_pipeline_layout(FFVulkanContext *s, FFVulkanPipeline *pl)
-{
-    VkResult ret;
-    FFVulkanFunctions *vk = &s->vkfn;
-    VkPipelineLayoutCreateInfo pipeline_layout_info;
-
-    /* Finally create the pipeline layout */
-    pipeline_layout_info = (VkPipelineLayoutCreateInfo) {
-        .sType                  = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
-        .pSetLayouts            = pl->desc_layout,
-        .setLayoutCount         = pl->nb_descriptor_sets,
-        .pushConstantRangeCount = pl->push_consts_num,
-        .pPushConstantRanges    = pl->push_consts,
-    };
-
-    ret = vk->CreatePipelineLayout(s->hwctx->act_dev, &pipeline_layout_info,
-                                   s->hwctx->alloc, &pl->pipeline_layout);
-    if (ret != VK_SUCCESS) {
-        av_log(s, AV_LOG_ERROR, "Unable to init pipeline layout: %s\n",
-               ff_vk_ret2str(ret));
-        return AVERROR_EXTERNAL;
-    }
-
-    return 0;
-}
-
-int ff_vk_init_compute_pipeline(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                FFVkSPIRVShader *shd)
-{
-    int err;
-    VkResult ret;
-    FFVulkanFunctions *vk = &s->vkfn;
-
-    VkComputePipelineCreateInfo pipeline_create_info;
-
-    err = init_descriptors(s, pl);
-    if (err < 0)
-        return err;
-
-    err = init_pipeline_layout(s, pl);
-    if (err < 0)
-        return err;
-
-    pipeline_create_info = (VkComputePipelineCreateInfo) {
-        .sType  = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
-        .flags = (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) ?
-                 VK_PIPELINE_CREATE_DESCRIPTOR_BUFFER_BIT_EXT : 0x0,
-        .layout = pl->pipeline_layout,
-        .stage = shd->shader,
-    };
-
-    ret = vk->CreateComputePipelines(s->hwctx->act_dev, VK_NULL_HANDLE, 1,
-                                     &pipeline_create_info,
-                                     s->hwctx->alloc, &pl->pipeline);
-    if (ret != VK_SUCCESS) {
-        av_log(s, AV_LOG_ERROR, "Unable to init compute pipeline: %s\n",
-               ff_vk_ret2str(ret));
-        return AVERROR_EXTERNAL;
-    }
-
-    pl->bind_point = VK_PIPELINE_BIND_POINT_COMPUTE;
-    pl->wg_size[0] = shd->local_size[0];
-    pl->wg_size[1] = shd->local_size[1];
-    pl->wg_size[2] = shd->local_size[2];
-
-    return 0;
-}
-
-void ff_vk_exec_bind_pipeline(FFVulkanContext *s, FFVkExecContext *e,
-                              FFVulkanPipeline *pl)
+void ff_vk_exec_bind_shader(FFVulkanContext *s, FFVkExecContext *e,
+                            FFVulkanShader *shd)
 {
     FFVulkanFunctions *vk = &s->vkfn;
     VkDeviceSize offsets[1024];
 
     /* Bind pipeline */
-    vk->CmdBindPipeline(e->buf, pl->bind_point, pl->pipeline);
+    vk->CmdBindPipeline(e->buf, shd->bind_point, shd->pipeline);
 
-    if (pl->nb_descriptor_sets) {
+    if (shd->nb_descriptor_sets) {
         if (s->extensions & FF_VK_EXT_DESCRIPTOR_BUFFER) {
-            for (int i = 0; i < pl->nb_descriptor_sets; i++)
-                offsets[i] = pl->desc_set[i].singular ? 0 : pl->desc_set[i].aligned_size*e->idx;
+            for (int i = 0; i < shd->nb_descriptor_sets; i++)
+                offsets[i] = shd->desc_set[i].singular ? 0 : shd->desc_set[i].aligned_size*e->idx;
 
             /* Bind descriptor buffers */
-            vk->CmdBindDescriptorBuffersEXT(e->buf, pl->nb_descriptor_sets, pl->desc_bind);
+            vk->CmdBindDescriptorBuffersEXT(e->buf, shd->nb_descriptor_sets, shd->desc_bind);
             /* Binding offsets */
-            vk->CmdSetDescriptorBufferOffsetsEXT(e->buf, pl->bind_point, pl->pipeline_layout,
-                                                 0, pl->nb_descriptor_sets,
-                                                 pl->bound_buffer_indices, offsets);
-        } else if (!pl->use_push) {
-            vk->CmdBindDescriptorSets(e->buf, pl->bind_point, pl->pipeline_layout,
-                                      0, pl->nb_descriptor_sets,
-                                      &pl->desc_sets[e->idx*pl->nb_descriptor_sets],
+            vk->CmdSetDescriptorBufferOffsetsEXT(e->buf, shd->bind_point, shd->pipeline_layout,
+                                                 0, shd->nb_descriptor_sets,
+                                                 shd->bound_buffer_indices, offsets);
+        } else if (!shd->use_push) {
+            vk->CmdBindDescriptorSets(e->buf, shd->bind_point, shd->pipeline_layout,
+                                      0, shd->nb_descriptor_sets,
+                                      &shd->desc_sets[e->idx*shd->nb_descriptor_sets],
                                       0, NULL);
         }
     }
 }
 
-void ff_vk_pipeline_free(FFVulkanContext *s, FFVulkanPipeline *pl)
+void ff_vk_shader_free(FFVulkanContext *s, FFVulkanShader *shd)
 {
     FFVulkanFunctions *vk = &s->vkfn;
 
-    if (pl->pipeline)
-        vk->DestroyPipeline(s->hwctx->act_dev, pl->pipeline, s->hwctx->alloc);
-    if (pl->pipeline_layout)
-        vk->DestroyPipelineLayout(s->hwctx->act_dev, pl->pipeline_layout,
+    av_bprint_finalize(&shd->src, NULL);
+
+#if 0
+    if (shd->shader.module)
+        vk->DestroyShaderModule(s->hwctx->act_dev, shd->shader.module,
+                                s->hwctx->alloc);
+#endif
+
+    if (shd->pipeline)
+        vk->DestroyPipeline(s->hwctx->act_dev, shd->pipeline, s->hwctx->alloc);
+    if (shd->pipeline_layout)
+        vk->DestroyPipelineLayout(s->hwctx->act_dev, shd->pipeline_layout,
                                   s->hwctx->alloc);
 
-    for (int i = 0; i < pl->nb_descriptor_sets; i++) {
-        FFVulkanDescriptorSet *set = &pl->desc_set[i];
+    for (int i = 0; i < shd->nb_descriptor_sets; i++) {
+        FFVulkanDescriptorSet *set = &shd->desc_set[i];
         if (set->buf.mem)
             ff_vk_unmap_buffer(s, &set->buf, 0);
         ff_vk_free_buf(s, &set->buf);
@@ -2108,23 +2173,23 @@ void ff_vk_pipeline_free(FFVulkanContext *s, FFVulkanPipeline *pl)
         av_free(set->binding_offset);
     }
 
-    for (int i = 0; i < pl->nb_descriptor_sets; i++)
-        if (pl->desc_layout[i])
-            vk->DestroyDescriptorSetLayout(s->hwctx->act_dev, pl->desc_layout[i],
+    for (int i = 0; i < shd->nb_descriptor_sets; i++)
+        if (shd->desc_layout[i])
+            vk->DestroyDescriptorSetLayout(s->hwctx->act_dev, shd->desc_layout[i],
                                            s->hwctx->alloc);
 
-    if (pl->desc_pool)
-        vk->DestroyDescriptorPool(s->hwctx->act_dev, pl->desc_pool,
+    if (shd->desc_pool)
+        vk->DestroyDescriptorPool(s->hwctx->act_dev, shd->desc_pool,
                                   s->hwctx->alloc);
 
-    av_freep(&pl->desc_pool_size);
-    av_freep(&pl->desc_layout);
-    av_freep(&pl->desc_sets);
-    av_freep(&pl->desc_set);
-    av_freep(&pl->desc_bind);
-    av_freep(&pl->bound_buffer_indices);
-    av_freep(&pl->push_consts);
-    pl->push_consts_num = 0;
+    av_freep(&shd->desc_pool_size);
+    av_freep(&shd->desc_layout);
+    av_freep(&shd->desc_sets);
+    av_freep(&shd->desc_set);
+    av_freep(&shd->desc_bind);
+    av_freep(&shd->bound_buffer_indices);
+    av_freep(&shd->push_consts);
+    shd->push_consts_num = 0;
 }
 
 void ff_vk_uninit(FFVulkanContext *s)
diff --git a/libavutil/vulkan.h b/libavutil/vulkan.h
index e03fd702ca..9c87dd29cf 100644
--- a/libavutil/vulkan.h
+++ b/libavutil/vulkan.h
@@ -72,14 +72,6 @@
 
 #define DUP_SAMPLER(x) { x, x, x, x }
 
-typedef struct FFVkSPIRVShader {
-    const char *name;                       /* Name for id/debugging purposes */
-    AVBPrint src;
-    int local_size[3];                      /* Compute shader workgroup sizes */
-    VkPipelineShaderStageCreateInfo shader;
-    VkPipelineShaderStageRequiredSubgroupSizeCreateInfo subgroup_info;
-} FFVkSPIRVShader;
-
 typedef struct FFVulkanDescriptorSetBinding {
     const char         *name;
     VkDescriptorType    type;
@@ -204,20 +196,35 @@ typedef struct FFVulkanDescriptorSet {
     int singular;
 } FFVulkanDescriptorSet;
 
-typedef struct FFVulkanPipeline {
+typedef struct FFVulkanShader {
+    /* Name for id/debugging purposes */
+    const char *name;
+
+    /* Shader text */
+    AVBPrint src;
+
+    /* Compute shader local group sizes */
+    int lg_size[3];
+
+    /* Shader bind point/type */
+    VkPipelineStageFlags stage;
     VkPipelineBindPoint bind_point;
 
-    /* Contexts */
+    /* Creation info */
+    VkPipelineShaderStageRequiredSubgroupSizeCreateInfo subgroup_info;
+
+    /* Base shader object */
+    union {
+        VkPipeline pipeline;
+    };
+
+    /* Pipeline layout */
     VkPipelineLayout pipeline_layout;
-    VkPipeline       pipeline;
 
     /* Push consts */
     VkPushConstantRange *push_consts;
     int push_consts_num;
 
-    /* Workgroup */
-    int wg_size[3];
-
     /* Descriptor buffer */
     VkDescriptorSetLayout *desc_layout;
     FFVulkanDescriptorSet *desc_set;
@@ -233,7 +240,7 @@ typedef struct FFVulkanPipeline {
     int nb_desc_pool_size;
     int total_desc_sets;
     FFVkExecPool *assoc_pool;
-} FFVulkanPipeline;
+} FFVulkanShader;
 
 typedef struct FFVulkanContext {
     const AVClass *class;
@@ -472,59 +479,86 @@ int ff_vk_init_sampler(FFVulkanContext *s, VkSampler *sampler,
                        int unnorm_coords, VkFilter filt);
 
 /**
- * Shader management.
+ * Initialize a shader object, with a specific set of extensions, type+bind,
+ * local group size, and subgroup requirements.
  */
-int ff_vk_shader_init(FFVulkanPipeline *pl, FFVkSPIRVShader *shd, const char *name,
-                      VkShaderStageFlags stage, uint32_t required_subgroup_size);
-void ff_vk_shader_set_compute_sizes(FFVkSPIRVShader *shd, int x, int y, int z);
-void ff_vk_shader_print(void *ctx, FFVkSPIRVShader *shd, int prio);
-int ff_vk_shader_create(FFVulkanContext *s, FFVkSPIRVShader *shd,
-                        uint8_t *spirv, size_t spirv_size, const char *entrypoint);
-void ff_vk_shader_free(FFVulkanContext *s, FFVkSPIRVShader *shd);
+int ff_vk_shader_init(FFVulkanContext *s, FFVulkanShader *shd, const char *name,
+                      VkPipelineStageFlags stage,
+                      const char *extensions[], int nb_extensions,
+                      int lg_x, int lg_y, int lg_z,
+                      uint32_t required_subgroup_size);
 
 /**
- * Add/update push constants for execution.
+ * Output the shader code as logging data, with a specific
+ * priority.
  */
-int ff_vk_add_push_constant(FFVulkanPipeline *pl, int offset, int size,
-                            VkShaderStageFlagBits stage);
-void ff_vk_update_push_exec(FFVulkanContext *s, FFVkExecContext *e,
-                            FFVulkanPipeline *pl,
-                            VkShaderStageFlagBits stage,
-                            int offset, size_t size, void *src);
+void ff_vk_shader_print(void *ctx, FFVulkanShader *shd, int prio);
 
 /**
- * Add descriptor to a pipeline. Must be called before pipeline init.
+ * Link a shader into an executable.
  */
-int ff_vk_pipeline_descriptor_set_add(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                      FFVkSPIRVShader *shd,
-                                      FFVulkanDescriptorSetBinding *desc, int nb,
-                                      int singular, int print_to_shader_only);
+int ff_vk_shader_link(FFVulkanContext *s, FFVulkanShader *shd,
+                      uint8_t *spirv, size_t spirv_len,
+                      const char *entrypoint);
 
-/* Initialize/free a pipeline. */
-int ff_vk_init_compute_pipeline(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                FFVkSPIRVShader *shd);
-void ff_vk_pipeline_free(FFVulkanContext *s, FFVulkanPipeline *pl);
+/**
+ * Add/update push constants for execution.
+ */
+int ff_vk_shader_add_push_const(FFVulkanShader *shd, int offset, int size,
+                                VkShaderStageFlagBits stage);
 
 /**
- * Register a pipeline with an exec pool.
+ * Add descriptor to a shader. Must be called before shader init.
+ */
+int ff_vk_shader_add_descriptor_set(FFVulkanContext *s, FFVulkanShader *shd,
+                                    FFVulkanDescriptorSetBinding *desc, int nb,
+                                    int singular, int print_to_shader_only);
+
+/**
+ * Register a shader with an exec pool.
  * Pool may be NULL if all descriptor sets are read-only.
  */
-int ff_vk_exec_pipeline_register(FFVulkanContext *s, FFVkExecPool *pool,
-                                 FFVulkanPipeline *pl);
-
-/* Bind pipeline */
-void ff_vk_exec_bind_pipeline(FFVulkanContext *s, FFVkExecContext *e,
-                              FFVulkanPipeline *pl);
-
-int ff_vk_set_descriptor_buffer(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                FFVkExecContext *e, int set, int bind, int elem,
-                                FFVkBuffer *buf, VkDeviceSize offset, VkDeviceSize len,
-                                VkFormat fmt);
-
-void ff_vk_update_descriptor_img_array(FFVulkanContext *s, FFVulkanPipeline *pl,
-                                       FFVkExecContext *e, AVFrame *f,
-                                       VkImageView *views, int set, int binding,
-                                       VkImageLayout layout, VkSampler sampler);
+int ff_vk_shader_register_exec(FFVulkanContext *s, FFVkExecPool *pool,
+                               FFVulkanShader *shd);
+
+/**
+ * Bind a shader.
+ */
+void ff_vk_exec_bind_shader(FFVulkanContext *s, FFVkExecContext *e,
+                            FFVulkanShader *shd);
+
+/**
+ * Update push constant in a shader.
+ * Must be called before binding the shader.
+ */
+void ff_vk_shader_update_push_const(FFVulkanContext *s, FFVkExecContext *e,
+                                    FFVulkanShader *shd,
+                                    VkShaderStageFlagBits stage,
+                                    int offset, size_t size, void *src);
+
+/**
+ * Update a descriptor in a buffer with a buffer.
+ * Must be called before binding the shader.
+ */
+int ff_vk_shader_update_desc_buffer(FFVulkanContext *s, FFVkExecContext *e,
+                                    FFVulkanShader *shd,
+                                    int set, int bind, int elem,
+                                    FFVkBuffer *buf, VkDeviceSize offset, VkDeviceSize len,
+                                    VkFormat fmt);
+
+/**
+ * Update a descriptor in a buffer with an image array..
+ * Must be called before binding the shader.
+ */
+void ff_vk_shader_update_img_array(FFVulkanContext *s, FFVkExecContext *e,
+                                   FFVulkanShader *shd, AVFrame *f,
+                                   VkImageView *views, int set, int binding,
+                                   VkImageLayout layout, VkSampler sampler);
+
+/**
+ * Free a shader.
+ */
+void ff_vk_shader_free(FFVulkanContext *s, FFVulkanShader *shd);
 
 /**
  * Frees main context.
