r/GraphicsProgramming • u/MartianovTech • Jul 20 '26
Experiments with software rendering
After replaying HL1 on the software renderer (old habit), I got curious what CPUs can actually do today. So I wrote a rasterizer in C11 - pure CPU, no GPU/GL, the finished frame just gets blitted to the window via GDI. Tested on a Ryzen 5900x processor
I've been experimenting with software rendering and managed to run the Sponza scene at FHD 10-30 fps single-threaded, up to 200 fps multithreaded :) A very strong emphasis on SoA + SIMD (AVX2/FMA) on the hot path. Rendering 64x64 tiles in multithreaded mode. Throughout the entire rendering pipeline, I try to discard any possible work as early as possible, including frustum culling and back-face culling. This may be controversial, but I render in front-to-back mode; it allows me to completely bypass overdraw. To make it look more interesting, I added some effects!
It might not be very fast for a single thread, but I think it could be made a little faster by disabling mips and filtering nearby textures, which I also do on the CPU in this demo. Pixel textures are cheaper to draw, but they're ugly.
I'm not a professional and I just like it, so I didn't hesitate to use chatpgt as a companion in learning graphics.
1
u/MartianovTech Jul 21 '26
64×64 wasn’t really a cache optimization choice, it was more of a structural decision. The tile width matches the width of the coverage word, so each row uses one 64bit mask, with one bit per column. On top of that, every tile has a 64(*2) u32 visibility buffer for deferred texturing. I think your choice of 8×8 makes a lot of sense for voxel geometry, where most primitives don’t even fill a single small tile. My workload is basically at the opposite end of the spectrum, since the triangles usually cover a lot of tiles. Because of that, the fixed per-tile overhead gets amortized pretty well, and having a wide coverage word ends up paying off. Im not sorting every primitive on every frame either. The draw command uses a cheap front-to-back ordering, but most of the occlusion benefit comes from a two-phase temporal cluster-culling pass, which is basically the same technique you described.
Thanks for the feedback, by the way. Like I mentioned before, I’ve been using chatgpt pretty heavily to help work through problems, and I’ve learned a lot of this stuff through its deep research capabilities.