A Fool’s Errand
Don’t build a game engine to make a game.
If you want to launch a product, existing tools are ready. Building your own is a massive time sink. But if the goal is to understand how computer systems, memory, graphics APIs, and operating systems interact, there is no better playground.
Writing an engine isn’t about making the game easier to build. It is about making the underlying systems transparent.
This series documents the development of Blossom: a custom 2D game engine built in Rust.
The Motivation
Modern software is built on layers of abstraction so thick the hardware is rarely visible. We call an API, and something happens. We import a library, and a window appears.
That is convenient, but it hides the underlying mechanics. The motivation for building an engine is to strip away those layers.
Understanding the Stack
Understanding exactly what happens between the operating system and the GPU:
- How does an OS window event loop hand off to a graphics pipeline?
- How are CPU buffers structured, mapped, and uploaded to GPU memory?
- How is state synchronized between asynchronous processor cores without blocking?
Using an existing engine means accepting their solutions to these questions. Writing one means discovering them.
Solving Core Engine Problems
Every engine must solve a specific set of problems. Solving them from scratch teaches more about software architecture than building a dozen games.
- Time and Sync: Decoupling the physics loop (fixed timestep) from the render loop (variable framerate) so that game logic remains deterministic across different hardware.
- Resource Management: Loading, caching, and freeing assets (textures, shaders, audio) deterministically without garbage collection pauses.
- State and Querying: Managing entity state efficiently, exploring Entity Component Systems (ECS) and spatial partitioning.
- Input Mapping: Translating raw OS keyboard and controller events into rebindable action maps.
- Networking: Tackling client-server synchronization, latency compensation (like client-side prediction and server reconciliation), and dealing with packet loss over raw sockets.
Rust and Nix
A suckless engine demands an equally disciplined toolchain.
- Nix: Standardizes the environment. A single flake guarantees that the graphics libraries, toolchains, and build targets are identical, reproducible, and deterministic across any machine.
- Rust: Offering C-level control and performance, but with safe defaults. The borrow checker ensures resources (like GPU buffers or textures) are freed exactly when they are no longer needed.
Constraining the Canvas
The fastest way to fail at building a game engine is to build too much.
Expanding scope takes no discipline. As explored in Constraining the Canvas, keeping a sharp, constrained focus is where engineering begins. If you try to build a general-purpose engine on day one, you will never render a single quad.
The boundaries for Blossom are tight:
- Pixel-Perfect 2D: No 3D meshes or complex camera projections.
- GPU-Accelerated: Using
wgputo pipeline rendering, compute physics, and post-processing. - Targeted Features: Built-in support for Aseprite assets, tilemaps, compute-shader particles, and 2D dynamic lighting.
By limiting the canvas, we can focus on precision. We don’t need a kitchen-sink engine. We need a well-defined, highly optimized environment that solves our specific constraints.