Files
Michael Anthony Bitoon 5b23d6671b Make DLSS's picture reach the screen and stop the whole image wobbling with DLSS and DLAA
With a DLSS mode the game's own scaling draw covered DLSS's picture with the jittered scene. At a 1280x720 scene and
1920x1080 output the scaling is done by the game's tonemap, not by the FXAA shader vk_dlaa_skip_draw looks for, so no
hash list could catch it. From DLSS's evaluation to the frame's end, passes larger than the light target now sample
DLSS's output in its place (vk_dlaa_swap_img, applied in vk_exec_draw.c tex_info), whichever shader scales.

The motion-vector shader inverted the view-projection in 32-bit floats. With a still camera (identical matrices each
frame) the rounding still gave a few hundredths of a pixel of motion that changed with every jitter step; DLSS
followed it and the whole picture wobbled. mv_pass now makes last frame's view-projection times the inverse of this
frame's in double precision from the uniform snapshot (vk_usnap_host), and the shader only applies it.

Measured on 1.4.3 indoors, still camera, mean per-pixel change between consecutive frames on static areas:
DLSS quality 0.18 -> 0.06, DLAA 0.14 -> 0.05. A camera pan at 10 px per frame shows no ghosting.
2026-11-03 08:40:13 +08:00

64 lines
3.6 KiB
Plaintext

#version 450
// Camera motion vectors for DLAA (vk_dlaa.c mv_pass): each pixel's depth is taken back to the world through this
// frame's view-projection and forward through last frame's; the vector is last frame's window position minus this
// frame's, in render pixels, without the jitter (DLSS's default convention). Objects that move on their own get only
// the camera's motion.
// The camera block is TotK's scene uniform block (bound as c4 in the scene's vertex shaders): vec4 rows 3-6 are the
// view-projection, rows of a row-major 4x4 with OpenGL clip depth (-w..w). The replay's viewports have negative
// heights, so window y grows downwards from ndc y = +1, and depth is z_ndc * 0.5 + 0.5.
// With pc.bias > 0 the pass also writes DLSS's bias mask (vk_dlaa.c, "characters"): G-buffer target 0 carries an object
// class in green, above 64/255 for characters, creatures and what they hold (Link 253, others 125, held items 160;
// terrain 0, foliage 32). A pixel gets pc.bias when a character covers it now, or covered the place its surface came
// from last frame (the previous class mask, read through the motion vector, 3x3): there the history holds a moving
// character that the camera-only vector does not follow, which is the trail. chars_out keeps this frame's mask for the
// next; chars_prev is last frame's (pc.prev_ok 0: none).
layout(local_size_x = 8, local_size_y = 8) in;
layout(binding = 0) uniform sampler2D depth_tex;
layout(binding = 1, rg16f) uniform writeonly image2D mv_out;
layout(binding = 2, std140) uniform Cur { vec4 r[16]; } cur;
layout(binding = 3, std140) uniform Prev { vec4 r[16]; } prev;
layout(binding = 4) uniform sampler2D ids_tex;
layout(binding = 5, r8) uniform writeonly image2D mask_out;
layout(binding = 6, r8) uniform readonly image2D chars_prev;
layout(binding = 7, r8) uniform writeonly image2D chars_out;
layout(push_constant) uniform P {
vec2 size; // render size in pixels
vec2 jitter; // this frame's jitter, pixels, as the scene was drawn (window x right, y down)
float bias; // the mask value for character pixels; 0: no mask (bindings 4-7 unused)
float prev_ok; // 1: chars_prev holds last frame's class mask at this size
} pc;
mat4 vp(vec4 a, vec4 b, vec4 c, vec4 d) { return transpose(mat4(a, b, c, d)); }
void main() {
ivec2 p = ivec2(gl_GlobalInvocationID.xy);
if (p.x >= int(pc.size.x) || p.y >= int(pc.size.y)) return;
float d = texelFetch(depth_tex, p, 0).r;
vec2 win = vec2(p) + 0.5 - pc.jitter; // where this pixel's surface sits without the jitter
vec4 ndc = vec4(win.x / pc.size.x * 2.0 - 1.0, 1.0 - win.y / pc.size.y * 2.0, d * 2.0 - 1.0, 1.0);
mat4 c = vp(cur.r[3], cur.r[4], cur.r[5], cur.r[6]);
mat4 q = vp(prev.r[3], prev.r[4], prev.r[5], prev.r[6]);
vec4 world = inverse(c) * ndc;
vec2 mv = vec2(0.0);
if (abs(world.w) > 1e-12) {
vec4 clip = q * (world / world.w);
if (clip.w > 1e-6) {
vec2 pn = clip.xy / clip.w;
vec2 pwin = vec2((pn.x * 0.5 + 0.5) * pc.size.x, (0.5 - pn.y * 0.5) * pc.size.y);
mv = pwin - win;
}
}
imageStore(mv_out, p, vec4(mv, 0.0, 0.0));
if (pc.bias <= 0.0) return;
float ch = texelFetch(ids_tex, p, 0).g > 64.0 / 255.0 ? 1.0 : 0.0;
imageStore(chars_out, p, vec4(ch));
float was = 0.0;
if (pc.prev_ok > 0.5) {
ivec2 q = ivec2(floor(win + mv)), hi = ivec2(pc.size) - 1;
for (int dy = -1; dy <= 1; dy++)
for (int dx = -1; dx <= 1; dx++)
was = max(was, imageLoad(chars_prev, clamp(q + ivec2(dx, dy), ivec2(0), hi)).r);
}
imageStore(mask_out, p, vec4(max(ch, was) * pc.bias));
}