diff --git a/assets/shaders/fxaa.frag.glsl b/assets/shaders/fxaa.frag.glsl index df35aaa0..158f2f98 100644 --- a/assets/shaders/fxaa.frag.glsl +++ b/assets/shaders/fxaa.frag.glsl @@ -2,7 +2,7 @@ // FXAA 3.11 — Fast Approximate Anti-Aliasing post-process pass. // Reads the resolved scene color and outputs a smoothed result. -// Push constant: rcpFrame = vec2(1/width, 1/height). +// Push constant: rcpFrame = vec2(1/width, 1/height), sharpness (0=off, 2=max), unused. layout(set = 0, binding = 0) uniform sampler2D uScene; @@ -10,7 +10,9 @@ layout(location = 0) in vec2 TexCoord; layout(location = 0) out vec4 outColor; layout(push_constant) uniform PC { - vec2 rcpFrame; + vec2 rcpFrame; + float sharpness; // 0 = no sharpen, 2 = max (matches FSR2 RCAS range) + float _pad; } pc; // Quality tuning @@ -128,5 +130,20 @@ void main() { if ( horzSpan) finalUV.y += pixelOffsetFinal * lengthSign; if (!horzSpan) finalUV.x += pixelOffsetFinal * lengthSign; - outColor = vec4(texture(uScene, finalUV).rgb, 1.0); + vec3 fxaaResult = texture(uScene, finalUV).rgb; + + // Post-FXAA contrast-adaptive sharpening (unsharp mask). + // Counteracts FXAA's sub-pixel blur when sharpness > 0. + if (pc.sharpness > 0.0) { + vec2 r = pc.rcpFrame; + vec3 blur = (texture(uScene, uv + vec2(-r.x, 0)).rgb + + texture(uScene, uv + vec2( r.x, 0)).rgb + + texture(uScene, uv + vec2(0, -r.y)).rgb + + texture(uScene, uv + vec2(0, r.y)).rgb) * 0.25; + // scale sharpness from [0,2] to a modest [0, 0.3] boost factor + float s = pc.sharpness * 0.15; + fxaaResult = clamp(fxaaResult + s * (fxaaResult - blur), 0.0, 1.0); + } + + outColor = vec4(fxaaResult, 1.0); } diff --git a/assets/shaders/fxaa.frag.spv b/assets/shaders/fxaa.frag.spv new file mode 100644 index 00000000..7803f3e2 Binary files /dev/null and b/assets/shaders/fxaa.frag.spv differ diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index f9ce69cd..8dcf724c 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -4968,11 +4968,11 @@ bool Renderer::initFXAAResources() { write.pImageInfo = &imgInfo; vkUpdateDescriptorSets(device, 1, &write, 0, nullptr); - // Pipeline layout — push constant holds vec2 rcpFrame + // Pipeline layout — push constant holds vec4(rcpFrame.xy, sharpness, pad) VkPushConstantRange pc{}; pc.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; pc.offset = 0; - pc.size = 8; // vec2 + pc.size = 16; // vec4 VkPipelineLayoutCreateInfo plCI{}; plCI.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; plCI.setLayoutCount = 1; @@ -5044,19 +5044,31 @@ void Renderer::renderFXAAPass() { vkCmdBindDescriptorSets(currentCmd, VK_PIPELINE_BIND_POINT_GRAPHICS, fxaa_.pipelineLayout, 0, 1, &fxaa_.descSet, 0, nullptr); - // Push rcpFrame = vec2(1/width, 1/height) - float rcpFrame[2] = { + // Pass rcpFrame + sharpness (vec4, 16 bytes). + // When FSR2/FSR3 is active alongside FXAA, forward FSR2's sharpness so the + // post-FXAA unsharp-mask step restores the crispness that FXAA's blur removes. + float sharpness = fsr2_.enabled ? fsr2_.sharpness : 0.0f; + float pc[4] = { 1.0f / static_cast(ext.width), - 1.0f / static_cast(ext.height) + 1.0f / static_cast(ext.height), + sharpness, + 0.0f }; vkCmdPushConstants(currentCmd, fxaa_.pipelineLayout, - VK_SHADER_STAGE_FRAGMENT_BIT, 0, 8, rcpFrame); + VK_SHADER_STAGE_FRAGMENT_BIT, 0, 16, pc); vkCmdDraw(currentCmd, 3, 1, 0, 0); // fullscreen triangle } void Renderer::setFXAAEnabled(bool enabled) { if (fxaa_.enabled == enabled) return; + // FXAA is a post-process AA pass intended to supplement FSR temporal output. + // It conflicts with MSAA (which resolves AA during the scene render pass), so + // refuse to enable FXAA when hardware MSAA is active. + if (enabled && vkCtx && vkCtx->getMsaaSamples() > VK_SAMPLE_COUNT_1_BIT) { + LOG_INFO("FXAA: blocked while MSAA is active — disable MSAA first"); + return; + } fxaa_.enabled = enabled; if (!enabled) { fxaa_.needsRecreate = true; // defer destruction to next beginFrame()