Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
896f100
fix: order vulkan forward and taa pass writes before their readers
CanReader Oct 8, 2026
1c9d12a
fix: clear the vulkan shadow map when it is created
CanReader Oct 8, 2026
d4833fc
fix: seed the first vulkan taa reprojection with the current matrix
CanReader Oct 8, 2026
6bac533
feat: add vulkan gpu pass timing and a deterministic frame capture
CanReader Oct 8, 2026
318b746
perf: load the vulkan validation layer only in debug or with --validate
CanReader Oct 8, 2026
acd2769
perf: skip repeated vulkan shadow push constants
CanReader Oct 8, 2026
816542d
perf: remove vulkan upload stalls and slow constant buffer reads
CanReader Oct 8, 2026
746702b
perf: persist the vulkan pipeline cache between runs
CanReader Oct 8, 2026
6d3abef
perf: build vulkan custom-format pipelines before their first draw
CanReader Oct 8, 2026
0b3c401
perf: enable only the vulkan device features the engine uses
CanReader Oct 8, 2026
4c90687
fix: declare only the vertex attributes vulkan forward shaders read
CanReader Oct 8, 2026
6926cac
fix: stop leaking vulkan shaders, pools and texture descriptor sets
CanReader Oct 8, 2026
36d6f3b
fix: grow vulkan per-image semaphores when the swapchain is recreated
CanReader Oct 8, 2026
85ac56f
perf: recreate the vulkan swapchain on suboptimal only when it resized
CanReader Oct 8, 2026
67db5f6
perf: read the vulkan taa resolve directly instead of copying it back
CanReader Oct 8, 2026
aa03f78
perf: skip dead ssr and bloom fetches in the vulkan composite
CanReader Oct 8, 2026
a9cbcb2
perf: gather the taa depth neighborhood and fetch its color taps
CanReader Oct 8, 2026
4e2cbf8
perf: fetch the gbuffer and skip back-face pcf in the vulkan lighting…
CanReader Oct 8, 2026
140430d
fix: apply ssao in the vulkan lighting pass when ibl is off
CanReader Oct 8, 2026
156d108
fix: warn once when ibl is requested on vulkan
CanReader Oct 8, 2026
0964165
fix: keep the imgui frame open on vulkan frames that fail to acquire
CanReader Oct 8, 2026
67a3969
perf: use a precomputed vogel disk for the vulkan shadow pcf
CanReader Oct 8, 2026
e356f20
fix: sample vulkan shadows with the matrix the map was rendered with
CanReader Oct 8, 2026
3f9928b
feat: expose the light matrix the next shadow pass renders with
CanReader Oct 8, 2026
c9aea73
fix: give every vulkan bone palette bind its own uniform slot
CanReader Oct 8, 2026
e92204c
fix: sample opengl shadows with the matrix the map was rendered with
CanReader Oct 9, 2026
1919fd8
refactor: replace bare literals with named constants
CanReader Oct 18, 2026
114447b
feat: let transparent vertex formats read the scene behind them
CanReader Oct 18, 2026
5ee11a9
feat: snapshot the scene for scene-reading transparent draws on vulkan
CanReader Oct 18, 2026
f0c7d24
feat: snapshot the scene for scene-reading transparent draws on opengl
CanReader Oct 18, 2026
5d3dd88
feat(Render): add deferred rendering enabled
CanReader Oct 24, 2026
2088c94
feat: mark transparent draws as taa-reactive in the scene alpha
CanReader Oct 25, 2026
095c4bc
fix: reproject vulkan taa in double precision and tighten its history…
CanReader Oct 25, 2026
1f643ac
feat: add rcas sharpening after taa on vulkan
CanReader Oct 25, 2026
807cc94
Merge branch 'staging' into vulkan-optimization
CanReader Oct 25, 2026
92180f8
style: apply clang-format to changed lines
CanReader Oct 25, 2026
fe77bd3
fix: avoid the windows far macro in the vulkan scene-read clear
CanReader Oct 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -148,20 +148,24 @@ set(GRAPHICS_OPENGL_SOURCES
src/Graphics/OpenGL/OpenGLCubemapTexture.cpp
src/Graphics/OpenGL/OpenGLIBL.cpp
src/Graphics/OpenGL/OpenGLRenderer.cpp
src/Graphics/OpenGL/OpenGLSceneRead.cpp
src/Graphics/OpenGL/OpenGLShader.cpp
src/Graphics/OpenGL/OpenGLTexture.cpp
)

set(GRAPHICS_VULKAN_SOURCES
src/Graphics/Vulkan/VulkanBloom.cpp
src/Graphics/Vulkan/VulkanBuffer.cpp
src/Graphics/Vulkan/VulkanCapture.cpp
src/Graphics/Vulkan/VulkanCubemapTexture.cpp
src/Graphics/Vulkan/VulkanDeferred.cpp
src/Graphics/Vulkan/VulkanDescriptors.cpp
src/Graphics/Vulkan/VulkanDevice.cpp
src/Graphics/Vulkan/VulkanIBL.cpp
src/Graphics/Vulkan/VulkanPipelines.cpp
src/Graphics/Vulkan/VulkanProfiler.cpp
src/Graphics/Vulkan/VulkanRenderer.cpp
src/Graphics/Vulkan/VulkanSceneRead.cpp
src/Graphics/Vulkan/VulkanShader.cpp
src/Graphics/Vulkan/VulkanShadow.cpp
src/Graphics/Vulkan/VulkanSSAO.cpp
Expand Down
49 changes: 43 additions & 6 deletions assets/shaders/bloom_composite.frag
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,42 @@ layout(push_constant) uniform BloomCompositePC {
float bloomStrength; // bloom mix amount (typical 0.04..0.08 UE-style)
float exposure; // exposure multiplier for HDR scene
float gamma; // 1.0 unless tonemapping is enabled
uint ssrEnabled; // 0 skips the SSR fetch
float sharpenStrength; // post-TAA RCAS strength, 0 = off, 1 = strongest
float _pad0;
float _pad1;
float _pad2;
};

// AMD FidelityFX FSR1 RCAS: caps the negative lobe so sharpening never rings
const float RCAS_LIMIT = 0.25 - 1.0 / 16.0;
const float RCAS_EPSILON = 1e-4;
const float PERCEPTUAL_MAX = 1.0 - 1e-4;

// Reversible per-channel Reinhard: RCAS expects values in [0, 1]
vec3 ToPerceptual(vec3 c) { return c / (1.0 + c); }
vec3 FromPerceptual(vec3 c) { return c / (1.0 - min(c, vec3(PERCEPTUAL_MAX))); }

/// Robust contrast-adaptive sharpening of the centre texel e over its 4 neighbours.
vec3 SharpenRCAS(vec3 e, vec2 uv) {
vec3 b = ToPerceptual(textureLodOffset(sceneHDR, uv, 0.0, ivec2( 0, -1)).rgb);
vec3 d = ToPerceptual(textureLodOffset(sceneHDR, uv, 0.0, ivec2(-1, 0)).rgb);
vec3 f = ToPerceptual(textureLodOffset(sceneHDR, uv, 0.0, ivec2( 1, 0)).rgb);
vec3 h = ToPerceptual(textureLodOffset(sceneHDR, uv, 0.0, ivec2( 0, 1)).rgb);
vec3 c = ToPerceptual(e);

vec3 mn4 = min(min(b, d), min(f, h));
vec3 mx4 = max(max(b, d), max(f, h));
vec3 hitMin = min(mn4, c) / max(4.0 * mx4, vec3(RCAS_EPSILON));
vec3 hitMax = (1.0 - max(mx4, c)) / min(4.0 * mn4 - 4.0, vec3(-RCAS_EPSILON));
vec3 lobeRGB = max(-hitMin, hitMax);
float lobe = max(-RCAS_LIMIT, min(max(lobeRGB.r, max(lobeRGB.g, lobeRGB.b)), 0.0))
* sharpenStrength;

vec3 sharp = (lobe * (b + d + f + h) + c) / (4.0 * lobe + 1.0);
return FromPerceptual(clamp(sharp, vec3(0.0), vec3(PERCEPTUAL_MAX)));
}

// ACES Filmic tonemap (Narkowicz 2015)
vec3 ACESFilm(vec3 x) {
const float a = 2.51;
Expand All @@ -31,23 +64,27 @@ vec3 ACESFilm(vec3 x) {
}

void main() {
vec3 hdr = texture(sceneHDR, fragUV).rgb;
vec3 bloom = texture(bloomTex, fragUV).rgb;
vec3 hdr = texture(sceneHDR, fragUV).rgb;
if (sharpenStrength > 0.0) hdr = SharpenRCAS(hdr, fragUV);

// SSR: ssr.rgb = reflectedColor * Fresnel * fades (premultiplied), ssr.a = fades only.
// Unpremultiply to get reflectedColor * Fresnel, then lerp over the base scene using
// ssr.a as coverage. This replaces (rather than adds to) the IBL-approximated specular,
// so reflections are visible even on bright surfaces.
vec4 ssr = texture(ssrTex, fragUV);
if (ssr.a > 0.001) {
hdr = mix(hdr, ssr.rgb / max(ssr.a, 1e-4), min(ssr.a, 1.0));
if (ssrEnabled != 0u) {
vec4 ssr = texture(ssrTex, fragUV);
if (ssr.a > 0.001) {
hdr = mix(hdr, ssr.rgb / max(ssr.a, 1e-4), min(ssr.a, 1.0));
}
}

// Add bloom on top of the HDR scene. Bloom is a soft glow derived
// from the bright parts of the scene; adding it preserves the base
// luminance and only brightens hot spots (unlike mix, which dims
// the scene by the bloom factor in non-glowing regions).
vec3 combined = hdr + bloom * clamp(bloomStrength, 0.0, 1.0);
vec3 combined = hdr;
if (bloomStrength > 0.0)
combined += texture(bloomTex, fragUV).rgb * clamp(bloomStrength, 0.0, 1.0);

// Apply exposure before tone mapping.
combined *= exposure;
Expand Down
Binary file modified assets/shaders/bloom_composite.frag.spv
Binary file not shown.
40 changes: 25 additions & 15 deletions assets/shaders/lighting_pass.frag
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,29 @@ float InterleavedGradientNoise(vec2 screenPos) {
return fract(magic.z * fract(dot(screenPos, magic.xy)));
}

vec2 VogelDisk(int i, int count, float phi) {
const float goldenAngle = 2.39996323;
float r = sqrt((float(i) + 0.5) / float(count));
float theta = float(i) * goldenAngle + phi;
return vec2(r * cos(theta), r * sin(theta));
}
// r_i * (cos, sin)(i * 2.39996323), r_i = sqrt((i + 0.5) / 16)
const vec2 kVogel16[16] = vec2[](
vec2( 0.176776692, 0.000000000), vec2(-0.225772178, 0.206825846),
vec2( 0.034557982, -0.393771189), vec2( 0.284571416, 0.371172631),
vec2(-0.522223197, -0.092373738), vec2( 0.494695337, -0.314684824),
vec2(-0.165465304, 0.615525170), vec2(-0.315562004, -0.607594155),
vec2( 0.684642316, 0.250029722), vec2(-0.712255912, 0.294009357),
vec2( 0.343354186, -0.733728746), vec2( 0.253730458, 0.808931929),
vec2(-0.764746789, -0.443184328), vec2( 0.897133617, -0.197234073),
vec2(-0.547505567, 0.778773187), vec2(-0.126488309, -0.976089476));

// Fixed-radius rotated PCF — crisp shadows like a classic forward renderer,
// at a fraction of PCSS cost (no blocker search). Hardware comparison sampler
// (gShadow) does a 2x2 lerp per tap, so 12 rotated taps read smooth and crisp.
const int PCF_SAMPLES = 16;

float PCFFilter(vec2 uv, float zRef, float filterRadius, float phi) {
vec2 cs = vec2(cos(phi), sin(phi)) * filterRadius;
float shadow = 0.0;
for (int i = 0; i < PCF_SAMPLES; ++i) {
vec2 off = VogelDisk(i, PCF_SAMPLES, phi) * filterRadius;
shadow += texture(gShadow, vec3(uv + off, zRef));
vec2 d = kVogel16[i];
vec2 off = vec2(d.x * cs.x - d.y * cs.y, d.x * cs.y + d.y * cs.x);
shadow += textureLod(gShadow, vec3(uv + off, zRef), 0.0);
}
return shadow / float(PCF_SAMPLES);
}
Expand Down Expand Up @@ -209,16 +215,17 @@ vec3 ReconstructWorldPos(vec2 uv, float depth) {
// ==================================================================
void main() {
// Early-out for sky pixels (depth == 1.0 means nothing was drawn)
float depth = texture(gDepth, fragUV).r;
ivec2 px = ivec2(gl_FragCoord.xy);
float depth = texelFetch(gDepth, px, 0).r;
if (depth >= 1.0) { outColor = vec4(0.0, 0.0, 0.0, 1.0); return; }

// Reconstruct world position from depth instead of a GBuffer RT.
vec3 worldPos = ReconstructWorldPos(fragUV, depth);
vec4 ndcPos = vec4(fragUV.x * 2.0 - 1.0, 1.0 - 2.0 * fragUV.y,
depth, 1.0);
vec4 albedoAO = texture(gAlbedoAO, fragUV);
vec4 normalRg = texture(gNormalRough, fragUV);
vec4 metalEmit = texture(gMetalEmit, fragUV); // R16G16B16A16: .r=metallic, .gba=emissive
vec4 albedoAO = texelFetch(gAlbedoAO, px, 0);
vec4 normalRg = texelFetch(gNormalRough, px, 0);
vec4 metalEmit = texelFetch(gMetalEmit, px, 0); // R16G16B16A16: .r=metallic, .gba=emissive

vec3 albedo = albedoAO.rgb;
float bakedAO = albedoAO.a; // vertex AO baked into GBuffer — always [0.4, 1.0]
Expand Down Expand Up @@ -308,15 +315,18 @@ void main() {
vec3 Ldir = normalize(-uLightDir.xyz);
float rawNdL = dot(N, Ldir);
float wrapNdL = clamp(rawNdL * 0.85 + 0.15, 0.0, 1.0);
float shad = CalcShadow(worldPos, N);
shad *= smoothstep(-0.15, 0.0, rawNdL);
float shad = 0.0;
if (rawNdL > -0.15)
shad = CalcShadow(worldPos, N) * smoothstep(-0.15, 0.0, rawNdL);
vec3 direct = uLightColor.rgb * uLightColor.a * wrapNdL * shad;
for (uint li = 0u; li < uNumExtraLights; ++li) {
vec3 Lx = normalize(-uExtraDir[li].xyz);
float NdLx = max(dot(N, Lx), 0.0);
direct += uExtraColor[li].rgb * uExtraColor[li].a * NdLx;
}
ambient = albedo * mix(groundAmbient, skyAmbient, hemisphere) * bakedAO;
// SSAO remapped like GL
float aoGL = bakedAO * (0.5 + 0.5 * ssao);
ambient = albedo * mix(groundAmbient, skyAmbient, hemisphere) * aoGL;
Lo = albedo * direct;
}

Expand Down
Binary file modified assets/shaders/lighting_pass.frag.spv
Binary file not shown.
Loading
Loading