Zex - A Zig Texture Utility

Are you working on a game, and shipping textures to players as PNGs?

The goal of this post is to convince you that this is suboptimal, and to share my Zig implementation of an alternative approach.

PNGs are a great format for images, but image formats are typically a poor fit for shipping textures due to handling of features like cubemaps, mipmaps, block based compression, and premultiplied alpha among others.

You can read the full post here, and check out the GitHub repo here.

Hopefully some other folks find this useful! If you do and wanna get pinged with more stuff like this, consider signing up for my newsletter.

16 Likes

That was an interesting read, thanks for sharing it!

Tangentially related, I remember reading stuff about FLIF design choices a long time ago. Stuff like lossless incremental compression that allows you to get a lower resolution version of your image by partially loading it, or lossy pre-compression designed to maximize lossless compression afterwards … That’s the kind of techniques in use today for textures, if I read you correctly?

1 Like

That’s more of a Web thing, so you still have something to display while a high-quality image is loading on a slow connection.
Texture format optimisation is primarily about saving RAM, and more specifically VRAM - the naive approach is to decompress textures to upload them to the GPU since shaders are very poor at reading the average compressed format, but BCn allows you to actually store your textures compressed in-VRAM and have shaders unpack them with no extra effort in shader code, which is a massive help.

3 Likes

@tgirod you’re welcome, and good question!

@tholmes is correct, but there is something like this that games do that I didn’t touch on in the article—it just doesn’t involve the compression in particular since the texture compression is sorta either on or off, without much space in between.

To understand how to do what you’re describing, we first have to understand mipmapping.

Textures are typically created with a series of progressively lower resolution versions of themselves baked in, called mipmaps. The idea is that far away or scaled down objects can render the texture at a lower resolution. This is faster than trying to render the full resolution when it’s not necessary, and it also looks better as you can’t do a nice downsample in real time (you’d end up just taking a few samples from the full resolution image and get a lot of aliasing.)

This is a pretty cool technique and the GPU does all the hard work for you, you just have to supply it with the various mip levels. (Though you can hand tune the bias or mip level selection if you want, sometimes that can be desirable, eg I biased a lot of Way of Rhea’s sprites towards lower mip levels to make them a bit crisper.)

Anyway, onto the progressive loading—

If your game involves a large open world, loading the full world into VRAM may not be possible. Instead you typically load in chunks as they become relevant, and then drop them when they’re too far away. This becomes a design challenge though—how can you stream in new data gracefully without showing a loading screen?

One trick is to load the lower resolution mipmaps first so that you can start rendering early at slightly worse quality. This is probably fine since the stuff getting loading is likely far away anyway. Then as the higher resolution mipmaps become available, you update the images. If you’ve ever seen a AAA game where the textures start out blurry then get crisp a few seconds later this is what’s happening. This is also why big games like to make you play an animation where you squeeze through a tight alley or something to get to a new area, that buys them some extra time haha.

My texture tool is able to generate the data you’d need to do this, but I haven’t personally implemented the engine side of it that progressively loads the mips as it’s not useful for the game I’m working on right now, but the Wicked Engine person has a great blog post on it with some cool GIFs demonstrating the results.

5 Likes

Yep, for me, having to cross long bridges to get to another island in the GTA series is what always comes to mind as an example of an in-game loading screen.

Also, the AC series allowed to control the character in the infinite abstract space of the “Animus loading lobby”, which was a pretty handy way of narratively justifying the loading screen.

2 Likes