1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
|
#version 460 core
layout(local_size_x = 16, local_size_y = 16, local_size_z = 1) in;
layout(binding = 0) uniform sampler2D CurrentFrame;
layout(binding = 1) uniform sampler2D PreviousFrame;
layout(binding = 2, rg16f) uniform writeonly image2D OutMotionVectors;
layout(push_constant) uniform PushConstants {
vec2 inv_render_size;
vec2 render_size;
float search_radius;
float block_size;
};
// ΓöÇΓöÇ Shared memory tile cache ΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇΓöÇ
const uint TILE = 16u;
const uint BORDER = 2u;
const uint PADDED = TILE + 2u * BORDER; // 20
shared float s_curr_luma[PADDED * PADDED];
float rgb2luma(vec3 c) {
return dot(c, vec3(0.299, 0.587, 0.114));
}
void loadTile(ivec2 tile_origin) {
uint thread_id = gl_LocalInvocationID.y * TILE + gl_LocalInvocationID.x;
for (uint i = thread_id; i < PADDED * PADDED; i += TILE * TILE) {
uint lx = i % PADDED;
uint ly = i / PADDED;
ivec2 coord = tile_origin + ivec2(lx, ly) - ivec2(BORDER);
vec2 uv = (vec2(coord) + 0.5) * inv_render_size;
s_curr_luma[i] = rgb2luma(texture(CurrentFrame, uv).rgb);
}
barrier();
}
float getCachedLuma(ivec2 local_coord) {
ivec2 padded = local_coord + ivec2(BORDER);
return s_curr_luma[padded.y * PADDED + padded.x];
}
void main() {
ivec2 pixel_coord = ivec2(gl_GlobalInvocationID.xy);
ivec2 tile_origin = ivec2(gl_WorkGroupID.xy) * ivec2(TILE);
loadTile(tile_origin);
if (pixel_coord.x >= int(render_size.x) || pixel_coord.y >= int(render_size.y)) {
return;
}
vec2 uv = (vec2(pixel_coord) + 0.5) * inv_render_size;
ivec2 local = ivec2(gl_LocalInvocationID.xy);
float curr_luma = getCachedLuma(local);
float center_diff = abs(curr_luma - rgb2luma(texture(PreviousFrame, uv).rgb));
// Early-out only for truly identical pixels (very tight threshold)
if (center_diff < 0.002) {
imageStore(OutMotionVectors, pixel_coord, vec4(0.0, 0.0, 0.0, 0.0));
return;
}
vec2 best_motion = vec2(0.0);
float min_diff = center_diff;
// Large diamond (radius 2)
const vec2 diamond_large[4] = vec2[](
vec2(0.0, -2.0),
vec2(-2.0, 0.0), vec2(2.0, 0.0),
vec2(0.0, 2.0)
);
for (int i = 0; i < 4; ++i) {
vec2 offset_uv = uv + diamond_large[i] * inv_render_size;
float luma = rgb2luma(texture(PreviousFrame, offset_uv).rgb);
float d = abs(curr_luma - luma) + 0.04 * length(diamond_large[i]);
if (d < min_diff) {
min_diff = d;
best_motion = diamond_large[i];
}
}
// Small refinement diamond (radius 1)
const vec2 diamond_small[4] = vec2[](
vec2(0.0, -1.0),
vec2(-1.0, 0.0), vec2(1.0, 0.0),
vec2(0.0, 1.0)
);
vec2 center = best_motion;
for (int i = 0; i < 4; ++i) {
vec2 cand = center + diamond_small[i];
vec2 offset_uv = uv + cand * inv_render_size;
float luma = rgb2luma(texture(PreviousFrame, offset_uv).rgb);
float d = abs(curr_luma - luma) + 0.04 * length(cand);
if (d < min_diff) {
min_diff = d;
best_motion = cand;
}
}
// ΓöÇΓöÇ Dampen very small motions to prevent micro-jitter ΓöÇΓöÇ
// Motions below ~0.5 pixels are likely noise, not real movement
float motion_len = length(best_motion);
if (motion_len < 0.5) {
best_motion *= motion_len * 2.0; // smooth fade to zero
}
imageStore(OutMotionVectors, pixel_coord, vec4(best_motion, 0.0, 0.0));
}
|