Screenshots and profiling
Posted: Mon Mar 02, 2026 7:59 pm
As a part of the preparations for setting up the Steam page, I thought it'd be cool to have a better, more automated way to capture screenshots from the game. So I created this fancy new ScreenshotManager thing that can extract pixels from the game's framebuffer and save them as PNG files.
Here you have it in all its glory, and as a bonus you get to see some unsafe C# in action! I gotta say, SDL makes this stuff easy and hard at the same time: Easy because the documentation is amazing and everything has examples. Hard, because there were a couple nasty surprises to figure out, especially figuring out when asking OpenGL for "RGBA" pixels you have to tell SDL to save in ABGR, plus the mandatory OpenGL vertical flip, of course
But then, I tried it, and the thing froze my game... not by much, only a split second, but enough for me to be annoyed. Because I want this thing running in the background taking screenshots while I play, and I don't wanna get a hiccup every few seconds when playing!
At first glance, it seemed the culprit was that glGetTexImage. Everyone will tell you that transferring pixel data from the GPU to the CPU is super slow and will block everything, stall rendering, and probably will make you late for dinner too. Because suddenly we're all hardware people and everyone knows "pci express is super slow", or something.
Anyway, after a quick detour into Pixel Buffer Objects, which apparently can speed this up, but not as much as one would hope, I had an epiphany: What if I actually profile this?
Well, turns out 100% of the time is taken by that SavePNG function in SDL_Image and the actual data transfer from the GPU to CPU is negligible... Why does SavePNG take so long (about 250ms)? Don't know don't care, but now we have a solution. We run it in a separate thread and let the magic of parallelism do the rest. Since we have the pixel data in an array that nobody else knows about (something something *ownership*), we can do all the SDL stuff on the side while the game continues running. I'll also take the opportunity to appreciate how easy it was to do all this "low level" manipulation mostly-safely (no formal proof for you
) and quite idiomatically. Especially spawning that thread which took almost no effort!
Remember kids: Always profile first
*EDIT*: Oops! And there was a tiny memory leak in there. I forgor to destroy the SDL surface. Guess I'm not used to cleaning up after myself ^^' Serves me right for boasting about how (un)safe this stuff was.
Here you have it in all its glory, and as a bonus you get to see some unsafe C# in action! I gotta say, SDL makes this stuff easy and hard at the same time: Easy because the documentation is amazing and everything has examples. Hard, because there were a couple nasty surprises to figure out, especially figuring out when asking OpenGL for "RGBA" pixels you have to tell SDL to save in ABGR, plus the mandatory OpenGL vertical flip, of course
At first glance, it seemed the culprit was that glGetTexImage. Everyone will tell you that transferring pixel data from the GPU to the CPU is super slow and will block everything, stall rendering, and probably will make you late for dinner too. Because suddenly we're all hardware people and everyone knows "pci express is super slow", or something.
Anyway, after a quick detour into Pixel Buffer Objects, which apparently can speed this up, but not as much as one would hope, I had an epiphany: What if I actually profile this?
Well, turns out 100% of the time is taken by that SavePNG function in SDL_Image and the actual data transfer from the GPU to CPU is negligible... Why does SavePNG take so long (about 250ms)? Don't know don't care, but now we have a solution. We run it in a separate thread and let the magic of parallelism do the rest. Since we have the pixel data in an array that nobody else knows about (something something *ownership*), we can do all the SDL stuff on the side while the game continues running. I'll also take the opportunity to appreciate how easy it was to do all this "low level" manipulation mostly-safely (no formal proof for you
Remember kids: Always profile first
*EDIT*: Oops! And there was a tiny memory leak in there. I forgor to destroy the SDL surface. Guess I'm not used to cleaning up after myself ^^' Serves me right for boasting about how (un)safe this stuff was.