So I decided, after going back and forth on it a lot, that I think I want to open source px, my suite of 2D game development libraries coded in Rust.
In preparation for this, I did a major organizational and coding refactor of the entire thing. The guts are all there and working now, and all I pretty much want to do before making it public is to go through and document everything thoroughly.
So here’s a rambly post about what it is, how it is structured, and what is different about it.
So What Is PX?
I’m really excited about this project because it is quite different than most game engines/tools, and something I’ve wanted for a long time.
Normally, there are two routes:
- use a game engine
- make your own game engine
But px falls in a sweet spot between these things. It is a collection of modular tools to make assembling a custom 2D game engine fast, easy, and exactly to your liking. All the essential pieces you need are here:
- windowing, event loop, and input
- gamepad input
- low level graphics support (dx, vulkan, metal, webgpu)
- high level graphics support (sprite batching, image packing, tiles, animations, font rendering)
- immediate mode editor gui system
- game math primitives and polygonal collisions
- vector graphics tesselator
- reflection support
- entity component system
The cool part is that every single one of these pieces is a separate library that you can choose to use, ignore, or even replace with your own preferred system. Everything works together nicely, but nothing is required, and you can even use most of the individual pieces on their own, completely decoupled from the rest.
When I make a game, I like to make a game engine for that specific game, tuned and tweaked to serve it perfectly, with no clutter and deadweight bogging it down.
Rather than “Make games, not engines”, I decided that I wanted something better: to make engine development so fast, smooth, and easy that developing an engine for every new game is a trivial and practical decision.
That is the goal of px.
The Intermediate Graphics Layer
Graphics is notoriously just layers and layers of abstractions stacked on top of each other. In px’s road to “assemble it how you like”, how to handle support for mixing and matching multiple different high level graphics APIs without forcing any of them on the user was a difficult one.
So I finally settled on the “intermediate graphics layer” (IGL). I was inspired by how Dear Imgui makes itself cross engine/library/graphics api compatible: rather than directly rendering, it just generates draw lists for you. Then, to hook it into your own ecosystem, all you have to do is write a shim that translates those draw lists into your rendering system.
So I thought, what if these draw lists themselves was a separate layer completely? If it was, then ImGui could still use it, but you could write any other rendering system that also generated them. That’s what the IGL does. It’s basically an unopinionated system for generating a sequence of batched, optimized render passes. The stack looks like this:
- High Level APIs (gui, vectors, sprites, custom renderer)
- Intermediate Layer (list of draw calls)
- Low Level API
What this has allowed me to do is just write one shim to rule them all: the low level API. All the high-level APIs only interact with the intermediate layer, and so if I want to switch from Vulkan to DX, I just flip a switch and everything still works.
The greatest part of all this is that it allows me to do context switching at any point! In the middle of rendering my gui code with the gui system, I can switch over to the sprite system and draw animations, then switch back to the gui system. And the intermediate layer, which tracks state, is still able to batch these draw calls together as if they were all coming from a single system.
I can’t be the first to structure my engine this way, so I’d be curious if anyone could point me to some prior art. It feels so obvious in retrospect, and the flexibility and portability of it is so wonderful.
What’s Left to Do
I basically want to go through and thoroughly document everything, and then assemble a comprehensive README with a tutorial explaining how the system works and how to get started using it. Once that is done, I want to open source it, after which I will continue developing the high level APIs required to achieve the end goal I’ve described above.
I’m very excited to get making games with it.
