It’s That Time Again

Yes, we’ve just skipped over the third anniversary of SGC and the blog. Time flies when you’re making triangles. I had some plans, and I was gonna make some posts, and then I didn’t, so here we are. Great.

There’s been a lot of things in motion over the past however long it’s been since I blogged. Exciting things. Big things. Not like Half Life 3 big, my lawyer is advising me to state for posterity, but you know. Big.

Blogging’s not like riding a bike. You gotta ease back into it after you take some time off. Just ease in…


I’ve been monitoring the issues everyone’s been reporting for GAMES THAT DON’T RENDER GOOD, and before I got sucked into doing this (huge) thing that I’m totally gonna talk about at some point, I was actually organizing some of the reports into tables and such. And investigating them. And one of the ones I was looking at was some broken sampling in Wolfenstein:


Some weird grid lines being rendered there. And so I took out my trusty chainsaw renderdoc, and I looked at the shaders, and, well, I’m not about to bore you with the details, but they were totally unreadable. It turns out that all the “free” bitcasting zink does during SPIRV translation isn’t actually free when you gotta read it.

Fine, you twisted my arm. Here’s some details:


Yeah that’s the good stuff. Ideally you want a 1:1 balance between the bitcasts and the shader ops, so you can see here that I’m really doing a great job. Shoutout to SPIRV-Cross for heroically putting up with it.

So I saw this, and I stared at it a while, and I wasn’t getting anywhere, so I kept staring at it in case it was one of those magic eye puzzles, but I’m pretty convinced now, a month later, that it’s not a magic eye puzzle, so probably I wasted some time, but you can never be too sure with those things, which is why you gotta check just in case.

If it’s not a magic eye puzzle, however, what’s going on here?

Ugh, Too Much Spaghetti

This goes back to one of the oldest ideas in zink, namely that when translating from NIR, all values should be stored as uint for consistency. This is great in terms of usability at the compiler level: when you use a result from op A later on, you know it’s a uint, so if op B takes a float param, you blindly cast the param from uint to float and it’s correct every time. But then also for every single op you’re adding in extra bitcasts. And in the case where op A has a result type of float and op B has an input type of float, that means two bitcasts to reuse one SSA value.

The smarter solution, obviously, is just to track the type of each value and then only bitcast when absolutely necessary. Like I did in this merge request which landed last month.

Which means it’s all fixed now, doesn’t it.

And the shaders are totally readable by humans.

And it probably fixed some random bugs in the process.

And you can already see where this is heading, can’t you.


There’s still a couple bitcasts, sure, and they’re annoying, yeah, and I should probably get rid of them, totally, but ultimately this is still not readable. But it’s better? Probably?

Definitely Better

And since zink is now usable to play most games, this means it should be much easier for people (other than me) to renderdoc their way to fixing shader-related bugs (so I don’t have to).

If they’re so inclined (to help me out).

Anyway great post everyone, we broke the no-blogging streak. Solid hustle. Let’s wrap it up and hit the gym.

Written on June 5, 2023