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;