Built with Monogame
Our engine is built and backed by Monogame and .NET Core, written in C# and is job/task driven in an asynchronous manner that runs about 2.4x faster than single threaded execution!
While a managed language by C# generally precludes coding-to-the-metal, it brings significant advantages in terms of stability and portability. This makes the engine potentially portable to a large variety of platforms including Windows, macOS, and Linux, as well as consoles (Xbox One, PS4) and mobile devices (Android, iOS).
Current target platforms are Windows and Xbox, with others to follow. The engine has been built on top of Monogame, making the best use of the strongest parts of the framework while not being limited by it.
Much of Monogame mimics the structure and operation of the XNA framework that was its predecessor, and we do not use those parts which do not add value to our engine. For example, Monogame presents a concept of Game Components, which have distinct Initialization, Update, and Draw phases intended to be called in lockstep with the core refresh rate. We make use of this high level pump to drive the startup and update of our various subsystems, which then can run independently.
The engine is completely and flexibly configurable in a human readable manner from program start, and in many ways works in a manner similar to a small operating system. This enables subsystems to be enabled, disabled, or reordered without modifying code – a huge win for sustainability.
The heart of the engine is the JobsSubsystem. Nearly everything the engine does is a Job, and the complexity of task management is abstracted away from the rest of the code. Monogame has a constraint that all graphics code must run on the “UI” thread (thread 1), so we accommodate that constraint when necessary.
Logically, the engine code is broken into three phases:
Game Logic -> Render Draw -> Render Flush
All of the game computation and logic is done here.
This code can run on any thread.
Pre-batched data that can be computed, sorted, and finalized using any processing resource.
This code can run on any thread.
Actual rendering to render targets and the back buffer.
This code must run on the UI thread (thread 1).
Basically, a pipeline! The engine is built so that code for the first two phases is thread-agnostic, and the third phase runs on the UI thread as required. Using the definition that a ‘frame’ is a collection of data that gets rendered on screen, and maintaining the state for each frame independently, we can easily have 3 or more frames processing at the same time for a high degree of coarse parallelism.
The RenderSubsystem has a concept of layers (game, UI) and render tasks associated with those layers, which are driven in the background by the JobSubsystem. The render tasks queue up batches of work in the Draw phase, which are later executed in the Flush phase. This enables expensive tasks like sorting to be pushed to other threads, keeping the Flush phase as lean and mean as possible.
Expensive or commonly used large objects are recycled in pools for efficiency (including Jobs), which also neatly enables simple statistics tracking. Container objects are parallel friendly wherever possible, and loops are parallelized wherever it makes sense as well.
To sum up, we have built a highly parallel game engine that makes use of both coarse and fine grained parallelism in a task-driven manner to run as efficiently as possible!