Kelsidavis-WoWee/assets/shaders/fsr2_accumulate.comp.glsl
Kelsi 52317d1edd Implement FSR 2.2 temporal upscaling
Full FSR 2.2 pipeline with depth-based motion vector reprojection,
temporal accumulation with YCoCg neighborhood clamping, and RCAS
contrast-adaptive sharpening.

Architecture (designed for FSR 3.x frame generation readiness):
- Camera: Halton(2,3) sub-pixel jitter with unjittered projection
  stored separately for motion vector computation
- Motion vectors: compute shader reconstructs world position from
  depth + inverse VP, reprojects with previous frame's VP
- Temporal accumulation: compute shader blends 5-10% current frame
  with 90-95% clamped history, adaptive blend for disocclusion
- History: ping-pong R16G16B16A16 buffers at display resolution
- Sharpening: RCAS fragment pass with contrast-adaptive weights

Integration:
- FSR2 replaces both FSR1 and MSAA when enabled
- Scene renders to internal resolution framebuffer (no MSAA)
- Compute passes run between scene and swapchain render passes
- Camera cut detection resets history on teleport
- Quality presets shared with FSR1 (0.50-0.77 scale factors)
- UI: "Upscaling" combo with Off/FSR 1.0/FSR 2.2 options
2026-03-07 23:13:01 -08:00

115 lines
4.1 KiB
GLSL

#version 450
layout(local_size_x = 8, local_size_y = 8) in;
// Inputs (internal resolution)
layout(set = 0, binding = 0) uniform sampler2D sceneColor;
layout(set = 0, binding = 1) uniform sampler2D depthBuffer;
layout(set = 0, binding = 2) uniform sampler2D motionVectors;
// History (display resolution)
layout(set = 0, binding = 3) uniform sampler2D historyInput;
// Output (display resolution)
layout(set = 0, binding = 4, rgba16f) uniform writeonly image2D historyOutput;
layout(push_constant) uniform PushConstants {
vec4 internalSize; // xy = internal resolution, zw = 1/internal
vec4 displaySize; // xy = display resolution, zw = 1/display
vec4 jitterOffset; // xy = current jitter (pixel-space), zw = unused
vec4 params; // x = resetHistory (1=reset), y = sharpness, zw = unused
} pc;
// RGB <-> YCoCg for neighborhood clamping
vec3 rgbToYCoCg(vec3 rgb) {
float y = 0.25 * rgb.r + 0.5 * rgb.g + 0.25 * rgb.b;
float co = 0.5 * rgb.r - 0.5 * rgb.b;
float cg = -0.25 * rgb.r + 0.5 * rgb.g - 0.25 * rgb.b;
return vec3(y, co, cg);
}
vec3 yCoCgToRgb(vec3 ycocg) {
float y = ycocg.x;
float co = ycocg.y;
float cg = ycocg.z;
return vec3(y + co - cg, y + cg, y - co - cg);
}
void main() {
ivec2 outPixel = ivec2(gl_GlobalInvocationID.xy);
ivec2 outSize = ivec2(pc.displaySize.xy);
if (outPixel.x >= outSize.x || outPixel.y >= outSize.y) return;
// Output UV in display space
vec2 outUV = (vec2(outPixel) + 0.5) * pc.displaySize.zw;
// Map display pixel to internal resolution UV (accounting for jitter)
vec2 internalUV = outUV;
// Sample current frame color at internal resolution
vec3 currentColor = texture(sceneColor, internalUV).rgb;
// Sample motion vector at internal resolution
vec2 inUV = outUV; // Approximate — display maps to internal via scale
vec2 motion = texture(motionVectors, inUV).rg;
// Reproject: where was this pixel in the previous frame's history?
vec2 historyUV = outUV - motion;
// History reset: on teleport / camera cut, just use current frame
if (pc.params.x > 0.5) {
imageStore(historyOutput, outPixel, vec4(currentColor, 1.0));
return;
}
// Sample reprojected history
vec3 historyColor = texture(historyInput, historyUV).rgb;
// Neighborhood clamping in YCoCg space to prevent ghosting
// Sample 3x3 neighborhood from current frame
vec2 texelSize = pc.internalSize.zw;
vec3 samples[9];
int idx = 0;
for (int dy = -1; dy <= 1; dy++) {
for (int dx = -1; dx <= 1; dx++) {
samples[idx] = rgbToYCoCg(texture(sceneColor, internalUV + vec2(dx, dy) * texelSize).rgb);
idx++;
}
}
// Compute AABB in YCoCg
vec3 boxMin = samples[0];
vec3 boxMax = samples[0];
for (int i = 1; i < 9; i++) {
boxMin = min(boxMin, samples[i]);
boxMax = max(boxMax, samples[i]);
}
// Slightly expand the box to reduce flickering on edges
vec3 boxCenter = (boxMin + boxMax) * 0.5;
vec3 boxExtent = (boxMax - boxMin) * 0.5;
boxMin = boxCenter - boxExtent * 1.25;
boxMax = boxCenter + boxExtent * 1.25;
// Clamp history to the neighborhood AABB
vec3 historyYCoCg = rgbToYCoCg(historyColor);
vec3 clampedHistory = clamp(historyYCoCg, boxMin, boxMax);
historyColor = yCoCgToRgb(clampedHistory);
// Check if history UV is valid (within [0,1])
float historyValid = (historyUV.x >= 0.0 && historyUV.x <= 1.0 &&
historyUV.y >= 0.0 && historyUV.y <= 1.0) ? 1.0 : 0.0;
// Blend factor: use more current frame for disoccluded regions
// Luminance difference between clamped history and original → confidence
float clampDist = length(historyYCoCg - clampedHistory);
float blendFactor = mix(0.05, 0.3, clamp(clampDist * 4.0, 0.0, 1.0));
// If history is off-screen, use current frame entirely
blendFactor = mix(blendFactor, 1.0, 1.0 - historyValid);
// Final blend
vec3 result = mix(historyColor, currentColor, blendFactor);
imageStore(historyOutput, outPixel, vec4(result, 1.0));
}