Archive:
Been a busy couple of weeks, and gamedev hours have been few and far between, but I did have a few spare on Thursday, which I thought I'd use to bang out some Eggnob levels. So of course, things did not go to plan. I fired up the build and... I could barely see a thing. Everything was coming out at 50% brightness. WTF?
The post processing stack was the last major thing that I'd touched in the EH500, so I immediately started to panic; was I working in the Git submodule, instead of the main engine repo? Did I not test the changes properly? Stale builds? Shaders out of sync? Me confooozed!
Things being too dark immediately made me suspect that the Vignette was to blame, but for the life of me, I couldn't see anything wrong with it. It's not the hardest shader in the world to write. I trawled through the history, rebuilt all the code, rebuilt the shaders, but nope. Couldn't spot anything wrong. So I turned the entire stack off, and ping! The colours were correct. So... it's definitely something to do with the post. But uh?
Then it dawned on me. pacman -Qi sdl3: SDL3 had been updated from 3.4.0 to 3.4.2, two days earlier.
The release notes didn't scream "we broke your shaders" but digging through the commit history turned up a couple of interesting items:
Both touch how the GPU renderer handles fragment shaders... Life on the bleeding edge, Baby!
The post-process is multi-step, with various intermediary buffers and an upscale. Something's popping, and the only way to find it is to pull everything out, then put it back in, one-by-one until things break. Disabling all post-processing: fine. CRT alone: fine. CRT + bloom: fine. Adding Chromatic aberration or Vignette: things were too dark.
Cool, both of those are broke, but both of those work on the upscaled buffer, so maybe it's something in the multi-pass swap chain? Fortunately, no, running chromatic as the only shader in the simplest possible pipeline (game -> shader -> screen), resulted in things being too dark. Same for the Vignette. Awesome, I can rule out texture copies, blend modes, render target state, and SDL_FlushRenderer. My working shaders worky no more.
Comparing the working CRT shader to the broken Chromatic shader, one thing jumped out:
// CRT (works)
o_color = vec4(col, 1.0) * v_color;
// Chromatic (dark)
o_color = vec4(r, g, b, 1.0);
The CRT shader multiplied its output by v_color -- the vertex colour passed through from SDL's renderer. The Chromatic and Vignette shaders didn't. Under 3.4.0, this apparently didn't matter, but under 3.4.2, it seems to. Ish. Adding * v_color to the Chromatic shader worked, but it didn't fix the Vignette.
Forcing the Vignette's multiplier to 1.0 looked fine. So I embedded the uniform values I was using as constants... and it worked. WTAF?
I was passing in four floats:
uniform FVignetteUniforms {
float fRadius;
float fSoftness;
float fAspect;
float fIntensity;
};
So I packed them in a vec4:
uniform FVignetteUniforms {
vec4 vParams; // x=radius, y=softness, z=aspect, w=intensity
};
Boom.
The C-side struct is 4 contiguous floats either way, so it should be the same memory layout. But SDL 3.4.2's GPU renderer clearly handles the two differently. Don't ask me why, that's as far down the rabbit hole as I went, but it was a confusing one, to say the least.
While I'm on the subject, SDL3's GPU renderer doesn't seem to like consecutive shader passes that ping-pong between two textures. I keep getting solid flat colours, instead of the expected output. Probably a "me thing" but it's something I've hit since 3.4.0, that I'd already worked around with a "copy-back resolve": after each shader pass, copy the result back to the source texture, instead of swapping pointers. Less than ideal, obviously, but these buffers are tiny (compared to "modern" games), so who really cares?
v_color -- I mean, yeah, makes sense I guess.Total: 10 lines of shader code changed across 4 files. Four hours of debugging.
Gamedev, Yo!
Musings, random thoughts, work in progress screenshots, and occasional swears at Unreal Engine's lack of documentation -- this is a rare insight into what happens when a supposedly professional game developer plans very little up-front, and instead follows where the jokes lead them.
Journal IndexFriends:
If you like any of my work, please consider checking out some of the fantastic games made by the following super talented people: