Commit e3a228b1d0 for ffmpeg

commit e3a228b1d0e0c93e2a9634c0c179921dec60c683
Author: Lynne <dev@lynne.ee>
Date:   Sat Nov 7 17:36:50 2026 +0800

    avfilter/vf_bwdif_vulkan: clip to the largest sample value

    bwdif clips its output to the largest value of the format's depth. The
    shader sees samples as fractions of their storage, so its results only
    saturated at the storage's largest value, which is above the format's
    whenever the format doesn't fill its storage: in low-aligned formats like
    yuv422p10, and high-aligned ones like P010.

    Pass the largest sample value to the shader, as a fraction of the
    storage, and clip to it. The 10-bit packed formats fill their storage,
    and float formats have no largest value, so they are left as they were.

    On moving, edge and still striped pictures in yuv420p, yuv422p10,
    yuv444p12, gbrp10, NV12 and P010, with both field orders and both modes,
    20 of the 72 cases had samples above the format's range. None do now.

    Reported-by: oittaa

diff --git a/libavfilter/vf_bwdif_vulkan.c b/libavfilter/vf_bwdif_vulkan.c
index 0f122f7642..9afd9ae409 100644
--- a/libavfilter/vf_bwdif_vulkan.c
+++ b/libavfilter/vf_bwdif_vulkan.c
@@ -20,6 +20,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  */

+#include <float.h>
+
+#include "libavutil/intfloat.h"
 #include "libavutil/random_seed.h"
 #include "libavutil/opt.h"
 #include "vulkan_filter.h"
@@ -52,6 +55,8 @@ static av_cold int init_filter(AVFilterContext *ctx)
     FFVulkanContext *vkctx = &s->vkctx;
     const int planes = av_pix_fmt_count_planes(s->vkctx.output_format);
     const AVPixFmtDescriptor *desc = av_pix_fmt_desc_get(vkctx->output_format);
+    const VkFormat *vkfmts = av_vkfmt_from_pixfmt(vkctx->output_format);
+    float unit = 0.0f, clip_max = FLT_MAX;

     s->qf = ff_vk_qf_find(vkctx, VK_QUEUE_COMPUTE_BIT, 0);
     if (!s->qf) {
@@ -62,9 +67,23 @@ static av_cold int init_filter(AVFilterContext *ctx)

     RET(ff_vk_exec_pool_init(vkctx, s->qf, &s->e, FF_VK_DEFAULT_EXEC_CONTEXTS, 0, 0, 0, NULL));

-    SPEC_LIST_CREATE(sl, 2, 2*sizeof(int))
+    /* The shader sees samples as fractions of their storage. bwdif clips to the
+     * largest sample value and decides on whole ones, so pass the largest and
+     * the size of a step. Float formats have neither. */
+    if (vkfmts[0] == VK_FORMAT_A2R10G10B10_UNORM_PACK32 ||
+        vkfmts[0] == VK_FORMAT_A2B10G10R10_UNORM_PACK32) {
+        unit = 1.0f / 1023;
+    } else if (!(desc->flags & AV_PIX_FMT_FLAG_FLOAT) &&
+               desc->comp[0].depth + desc->comp[0].shift <= 16) {
+        int bits = desc->comp[0].depth + desc->comp[0].shift > 8 ? 16 : 8;
+        unit     = (1 << desc->comp[0].shift) / (float)((1 << bits) - 1);
+        clip_max = ((1 << desc->comp[0].depth) - 1) * unit;
+    }
+
+    SPEC_LIST_CREATE(sl, 3, 2*sizeof(int) + sizeof(float))
     SPEC_LIST_ADD(sl, 0, 32, vkctx->output_height);
     SPEC_LIST_ADD(sl, 1, 32, AV_CEIL_RSHIFT(vkctx->output_height, desc->log2_chroma_h));
+    SPEC_LIST_ADD(sl, 2, 32, av_float2int(clip_max));

     ff_vk_shader_load(&s->shd, VK_SHADER_STAGE_COMPUTE_BIT, sl,
                       (uint32_t [3]) { 1, 64, planes }, 0);
diff --git a/libavfilter/vulkan/bwdif.comp.glsl b/libavfilter/vulkan/bwdif.comp.glsl
index afe4486260..76d330cf9f 100644
--- a/libavfilter/vulkan/bwdif.comp.glsl
+++ b/libavfilter/vulkan/bwdif.comp.glsl
@@ -31,6 +31,9 @@ layout (local_size_x_id = 253, local_size_y_id = 254, local_size_z_id = 255) in;
 layout (constant_id = 0) const int height = 0;
 layout (constant_id = 1) const int chroma_height = 0;

+/* The largest sample value, which bwdif clips to */
+layout (constant_id = 2) const float clip_max = 1.0;
+
 layout (set = 0, binding = 0) uniform writeonly image2D  dst[];
 layout (set = 0, binding = 1) uniform readonly  image2D prev[];
 layout (set = 0, binding = 2) uniform readonly  image2D  cur[];
@@ -62,7 +65,7 @@ void process_plane_intra(ivec2 pos, int h)
     dcur[1] = imageLoad(cur[idx], pos + ivec2(0, pos.y > 0     ? -1 :  1));
     dcur[2] = imageLoad(cur[idx], pos + ivec2(0, pos.y + 1 < h ?  1 : -1));
     dcur[3] = imageLoad(cur[idx], pos + ivec2(0, pos.y + 3 < h ?  3 : -1));
-    imageStore(dst[idx], pos, process_intra(dcur));
+    imageStore(dst[idx], pos, min(process_intra(dcur), clip_max));
 }

 vec4 process_line(vec4 prev2[5], vec4 prev1[2], vec4 cur[4], vec4 next1[2], vec4 next2[5])
@@ -178,7 +181,7 @@ void process_plane(const ivec2 pos)
         next2[4] = imageLoad(next[idx], pos + ivec2(0, 4));
     }

-    imageStore(dst[idx], pos, process_line(prev2, prev1, dcur, next1, next2));
+    imageStore(dst[idx], pos, min(process_line(prev2, prev1, dcur, next1, next2), clip_max));
 }

 void process_plane_edge(const ivec2 pos, int h)
@@ -227,7 +230,7 @@ void process_plane_edge(const ivec2 pos, int h)
         next2[2] = imageLoad(next[idx], pos + d2);
     }

-    imageStore(dst[idx], pos, process_edge(prev2, prev1, dcur, next1, next2, spat));
+    imageStore(dst[idx], pos, min(process_edge(prev2, prev1, dcur, next1, next2, spat), clip_max));
 }

 void main()