A bit of a technical question about image formats.
I’m developing a small 2D point’n’click game using C++ with SDL2. So far it’s going very well and it’s been a pleasure to use the SDL, but there’s a little thing that’s been bothering me for a while: how long it takes to load the input images. Overall, for a big level, it can take up to 5 seconds on a recent computer (and up to 10 or 15 seconds on a small tablet for example).
Of course, these timings are not terrible. But considering it’s “just” a modest 2D game, it seems like a lot (I would expect, as a player, a loading time less than 1 second per level). Of course, the game runs at 1920x1080 and the images are thus quite big (especially the animations), but still.
Now to the technical part: I’m using PNG compressed images. Considering my images come from vector drawing (with large flat areas), using pngquant
allows to substantially reduce the weight of the image files. I’ve been doing some profiling on the image loading parts of the SDL functions, and here’s something interesting:
- using uncompressed bitmaps BMP files with
SDL_LoadBPM()
loads very fast, but of course the files are also very big - “standard” PNG files take in average 10 times longer to load with
IMG_Load()
than BMP, but files are 10 to 20 times smaller - lossy compressed PNG (again, using
pngquant
) are “only” 2 to 5 times longer to load than BMP, with files 50 to 120 times smaller
So far I’ve continued using the latest variant, which gives me quite small files with an average loading time. Ideally, I’d like to get the loading times of SDL_LoadBMP()
, but using BMP files really seems like an overkill in terms of disk usage (my simple small 2D game would end up weighing several GB).
Another interesting part: I also did some profiling but separating the file reading itself (which requires disk access, which I know is very costly) and the construction of the SDL image itself. It looks like:
// Reading from the disk (custom function)
std::vector<char> buffer = file_to_buffer(filename);
// Constructing the image from RAM access
SDL_RWops* input = SDL_RWFromMem(buffer.data(), buffer.size());
SDL_Surface* image = SDL_LoadBMP_RW(input, 0); // or IMG_Load_RW()
Feeding some of my images to a similar, profiling code, here are some results:
- disk access for BMP is 4ms in average
- disk access for PNG is 0.05ms in average (makes sense considering the files are way smaller)
- construction of the image from BMP input is 3ms in average
- construction of the image from PNG input is 18ms in average
So what happens is that the longer reading time of BMP is largely compensated by how fast constructing the image from BMP is (whereas PNG is fast to read but slow to decompress). I guess construct a surface from a BMP is basically “just” copying a big chunk of bytes directly into memory, so it makes sense.
So the decompression of PNG images is the costly part in my code. From that, I have some questions:
- is it possible that the SDL’s PNG decompression algorithm is not optimal? I haven’t checked, but I imagine it’s using an external PNG library for that, so I doubt it. The solution in that case would be to use another PNG decoder
- are there other image formats that are both efficient in terms of compression and fast to decompress? I’ve read TGA is often used for texturing, but I’ve never used it so far. I’ve been also wondering if there were some formats specifically more efficient with my type of image (very flat vector drawings - I imagine using my source SVG would be slow as hell as they would require quantization to be stored in SDL structures)
- should I maybe use a different approach? I’ve been thinking that using a unique ZIP container for the assets of my game could maybe make things overall more efficient (provided ZIP decompression is faster than PNG decompression), using one disk access to load everything while allowing to also compress other assets (level descriptions, sounds, etc.)
- I’ve seen some interesting development about direct GPU texture interchange (see basis_universal for example), which I can imagine could make loading textures much faster. Do you think this could be integrated in an SDL-based game? (To be clear: this part is far too technical for me, my knowledge of GPU programming is very light, so I have no idea how it works)
Maybe the answer is “your loading times are fine, don’t overthink it”, but I was quite curious to see if people here had some hindsight to share on this issue.