forked from WinNative-Emu/WinNative
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathzero-flow-candidate.patch
More file actions
161 lines (144 loc) · 8.17 KB
/
Copy pathzero-flow-candidate.patch
File metadata and controls
161 lines (144 loc) · 8.17 KB
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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
From cd337dbc6a6c564ab6ab82dd4902168eb603dd45 Mon Sep 17 00:00:00 2001
From: qwertypower <qwertypower@users.noreply.github.com>
Date: Mon, 14 Sep 2026 19:58:00 +0000
Subject: [PATCH] DIS: let the patch search choose "did not move"
Static HUD, menus and map overlays flicker with generation on. The cause is
that the patch search has no way to express "this block did not move": it is
initialised from the coarser level, which carries the motion of whatever
surrounds the overlay, and Gauss-Newton refines from there. Gauss-Newton is a
local optimiser, and inside a smooth overlay that initialisation sits in a
local basin it never leaves. The overlay inherits a displacement it does not
have, is warped in every generated frame, and is snapped back by the next real
one.
Measured on a synthetic overlay over a background panning four pixels, a block
fully inside the overlay stays at 3.9 px and leaves a residual of RMS 24, while
the zero vector leaves 0. The search is not close to the answer; it is nowhere
near it.
So the zero vector is now scored explicitly once the search has finished, and
taken when it explains the block better than what the search settled on. The
test is a ratio of residuals rather than an absolute threshold, which is what
makes it usable: an absolute threshold has to sit near the noise floor and, on
the same scene, stopped firing entirely once frame noise reached an RMS of 3.
The ratio separates the two cases by orders of magnitude and holds from
noiseless up to an RMS of 6:
overlay interior ratio 0.00 - 0.32 -> zero taken
overlay edge block ratio 0.39 - 0.46 -> zero taken
background, 4 px pan ratio 2.1 - 1e7 -> kept
background, 0.7 px pan ratio 2.7 -> kept
background, 0.3 px pan ratio 1.1 -> kept
Swept over noise levels and pan speeds, 19 of 20 cases classify correctly; the
miss is an overlay edge block at an RMS 10 noise floor, which is a
non-detection rather than a frozen pan. No pan from 0.2 to 2 px was frozen at
any noise level.
A flat region that is genuinely moving also qualifies, because a featureless
block matches itself anywhere. That costs nothing: there is nothing in it to
see move, and the structured blocks around it still carry the real motion.
When zero wins, its score goes into .z instead of the usual -1. It is measured
with exactly the metric the propagation passes use, on the same reference
block, so propagation then compares a neighbour's moving vector against a real
and very low incumbent score rather than re-deriving one - which is what stops
the surrounding motion from being propagated into the overlay afterwards.
Cost is 64 samples on patches that claim a displacement, about 10% of the
inverse search and some 2% of the flow chain. Patches already at zero skip it.
The test is also skipped when the search result was thrown out and replaced by
the coarse estimate, since the residual it would be compared against belongs to
the discarded vector.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MCkAQJH8ik5iJZqf2NX3w6
---
.../vk/shaders/dis_inverse_search.comp | 78 ++++++++++++++++++-
1 file changed, 75 insertions(+), 3 deletions(-)
diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp b/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp
index 618f606..b538489 100644
--- a/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp
+++ b/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp
@@ -7,6 +7,43 @@ precision highp int;
#define DIS_MAX_MATCH_RMS 36.0
+// How much better the zero vector has to explain the block than whatever the
+// search settled on, as a ratio of RMS residuals, before it is taken instead.
+//
+// Static HUD, menus and map overlays sit on top of moving content and are
+// pixel-identical between frames, but the search has no notion of "did not
+// move": it starts from the coarser level, which carries the motion of whatever
+// surrounds the overlay, and Gauss-Newton refines from there. Gauss-Newton is a
+// local optimiser, and inside a smooth overlay the initialisation sits in a
+// local basin it never leaves - measured on a synthetic overlay over a
+// background panning four pixels, it stays at 3.9 px and leaves a residual of
+// RMS 24, while the zero vector leaves 0. The overlay is warped in the
+// generated frames and snapped back by the next real one, which is the flicker.
+//
+// The test is a ratio, not an absolute threshold, and that is what makes it
+// work. An absolute one has to sit near the noise floor, and on the same
+// synthetic scene it stopped firing entirely once frame noise reached an RMS of
+// 3. The ratio separates the cases by two orders of magnitude and holds from
+// noiseless up to an RMS of 6:
+//
+// overlay interior ratio 0.00 - 0.32 -> zero taken
+// overlay edge block ratio 0.39 - 0.46 -> zero taken
+// background, 4 px pan ratio 2.1 - 1e7 -> kept
+// background, 0.7 px pan ratio 2.7 -> kept
+// background, 0.3 px pan ratio 1.1 -> kept
+//
+// 0.5 means zero has to be twice as good in RMS terms. The nearest thing on the
+// other side is a third of a pixel of real motion at 1.1, so there is room.
+//
+// A flat region that IS moving also qualifies, because a featureless block
+// matches itself anywhere. That costs nothing: there is nothing in it to see
+// move, and the structured blocks around it still carry the real motion.
+#define DIS_ZERO_MATCH_RATIO 0.5
+
+// Below this displacement, in texels, the search already landed on zero and
+// there is nothing for the test to change; skip its 64 samples.
+#define DIS_ZERO_TEST_MIN_PX 0.5
+
layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in;
layout(set = 0, binding = 0) uniform sampler2D lastLumaMap;
@@ -124,11 +161,46 @@ void main() {
bool unmatched = prevSSD * N_INV > DIS_MAX_MATCH_RMS * DIS_MAX_MATCH_RMS;
- if (any(isnan(flow)) || any(isinf(flow)) || clamped || unmatched ||
- length(flow - initialFlow) > patchSize) {
+ const bool reverted = any(isnan(flow)) || any(isinf(flow)) || clamped || unmatched ||
+ length(flow - initialFlow) > patchSize;
+ if (reverted) {
flow = initialFlow;
}
+ // Does the block explain itself better by not moving at all? Only asked when
+ // the search claims a displacement, and only when it was not thrown out
+ // above - prevSSD belongs to the searched vector, so once that has been
+ // replaced by the coarse estimate there is nothing valid to compare against.
+ float zeroSsd = -1.0;
+ if (!reverted && dot(flow, flow) > DIS_ZERO_TEST_MIN_PX * DIS_ZERO_TEST_MIN_PX) {
+ float zsd = 0.0;
+ float zsd2 = 0.0;
+ for (int i = 0; i < 8; i++) {
+ for (int j = 0; j < 8; j++) {
+ vec2 tc = (vec2(pix) + vec2(i, j) + 0.5) * invImageSize;
+ float diff = textureLod(nextLumaMap, tc, 0.0).x - lastImageData[i * 8 + j];
+ zsd += diff;
+ zsd2 += diff * diff;
+ }
+ }
+ float zssd = zsd2 - zsd * zsd / N;
+ // Compared as sums of squares, so the RMS ratio is squared here and no
+ // square root is taken. prevSSD of exactly zero is a perfect match that
+ // only an equally perfect zero can tie, and the comparison handles it
+ // without a divide.
+ if (zssd <= prevSSD * (DIS_ZERO_MATCH_RATIO * DIS_ZERO_MATCH_RATIO)) {
+ flow = vec2(0.0);
+ zeroSsd = zssd;
+ }
+ }
+
flow *= invImageSize;
- imageStore(sparseFlowMap, pixSparse, vec4(flow, -1.0, 1.0));
+ // .z is the mean-normalized SSD of the stored vector, or -1 for "not scored
+ // with that metric". The zero test measures exactly the metric the
+ // propagation passes use, on the same reference block, so when zero wins its
+ // score is carried forward: propagation then compares a neighbour's moving
+ // vector against a real, and very low, incumbent score instead of having to
+ // re-derive one, which is what keeps the surrounding motion from being
+ // propagated into the overlay.
+ imageStore(sparseFlowMap, pixSparse, vec4(flow, zeroSsd, 1.0));
}
--
2.43.0