Commit cf2f8fc5a2 for ffmpeg

commit cf2f8fc5a2a8dc4a1b490bab99ef0f6019222f2d
Author: Lynne <dev@lynne.ee>
Date:   Sat Nov 7 17:38:00 2026 +0800

    avfilter/vf_bwdif_vulkan: make bwdif's decisions on whole sample values

    bwdif treats a sample as still when its halved temporal differences,
    rounded down, are all 0, so a change of one step counts as none. And it
    interpolates from the current field alone unless abs(c - e) is above
    temporal_diff0. The shader works on floats, which aren't exact and whose
    halves aren't rounded down, so both decisions could go the other way:
    after the previous commit, still 1-row stripes with a step of noise were
    off by up to 1022, and moving pictures by up to 338.

    Pass the size of a step to the shader, and put both thresholds halfway
    between the integer outcomes. Float formats have no steps and keep the
    plain comparisons.

    On moving, edge and still striped pictures in yuv420p, yuv422p10,
    yuv444p12, gbrp10, NV12 and P010, with both field orders and both modes,
    the output now matches bwdif within a code everywhere, on an RX 6900 XT
    and on lavapipe.

    Fixes: #25106
    Reported-by: oittaa

diff --git a/libavfilter/vf_bwdif_vulkan.c b/libavfilter/vf_bwdif_vulkan.c
index 9afd9ae409..0fd0255c4d 100644
--- a/libavfilter/vf_bwdif_vulkan.c
+++ b/libavfilter/vf_bwdif_vulkan.c
@@ -80,10 +80,11 @@ static av_cold int init_filter(AVFilterContext *ctx)
         clip_max = ((1 << desc->comp[0].depth) - 1) * unit;
     }

-    SPEC_LIST_CREATE(sl, 3, 2*sizeof(int) + sizeof(float))
+    SPEC_LIST_CREATE(sl, 4, 2*sizeof(int) + 2*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));
+    SPEC_LIST_ADD(sl, 3, 32, av_float2int(unit));

     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 76d330cf9f..b477be63c4 100644
--- a/libavfilter/vulkan/bwdif.comp.glsl
+++ b/libavfilter/vulkan/bwdif.comp.glsl
@@ -34,6 +34,11 @@ 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;

+/* bwdif decides on whole sample values. These are floats, which aren't
+ * exact, so the decisions are made on steps of this size, 0 for float
+ * formats. */
+layout (constant_id = 3) const float unit = 0.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[];
@@ -80,7 +85,9 @@ vec4 process_line(vec4 prev2[5], vec4 prev1[2], vec4 cur[4], vec4 next1[2], vec4
    temp_diff[1] = (abs(prev1[0] - fc) + abs(prev1[1] - fe)) / 2;
    temp_diff[2] = (abs(next1[0] - fc) + abs(next1[1] - fe)) / 2;
    vec4 diff = max(temp_diff[0] / 2, max(temp_diff[1], temp_diff[2]));
-   bvec4 diff_mask = equal(diff, vec4(0));
+   /* bwdif halves with shifts, so a change of one step is no motion: diff
+    * is then at most half a step, and otherwise at least one */
+   bvec4 diff_mask = lessThanEqual(diff, vec4(unit * 0.75));

    vec4 fbs = prev2[1] + next2[1];
    vec4 ffs = prev2[3] + next2[3];
@@ -97,7 +104,8 @@ vec4 process_line(vec4 prev2[5], vec4 prev1[2], vec4 cur[4], vec4 next1[2], vec4
                            coef_lf[0]*(fc + fe) - coef_lf[1]*(cur[0] + cur[3])) / (1 << 13);
    vec4 interpolate_cur = (coef_sp[0]*(fc + fe) - coef_sp[1]*(cur[0] + cur[3])) / (1 << 13);

-   bvec4 interpolate_cnd1 = greaterThan(abs(fc - fe), temp_diff[0]);
+   /* bwdif takes interpolate_cur when the differences are equal */
+   bvec4 interpolate_cnd1 = greaterThan(abs(fc - fe), temp_diff[0] + unit * 0.5);
    vec4 interpol = mix(interpolate_cur, interpolate_all, interpolate_cnd1);
    interpol = clamp(interpol, fd - diff, fd + diff);
    return mix(interpol, fd, diff_mask);
@@ -117,7 +125,7 @@ vec4 process_edge(vec4 prev2[3], vec4 prev1[2], vec4 cur[2], vec4 next1[2], vec4
    temp_diff[1] = (abs(prev1[0] - fc) + abs(prev1[1] - fe)) / 2;
    temp_diff[2] = (abs(next1[0] - fc) + abs(next1[1] - fe)) / 2;
    vec4 diff = max(temp_diff[0] / 2, max(temp_diff[1], temp_diff[2]));
-   bvec4 diff_mask = equal(diff, vec4(0));
+   bvec4 diff_mask = lessThanEqual(diff, vec4(unit * 0.75));

    if (spat) {
        vec4 fb = (prev2[0] + next2[0]) / 2 - fc;