Aurora Engine


A modular C++23 game engine built around a custom RHI, multi-queue render graph, ECS modules, and asynchronous rendering, compute, and transfers.

2026

Aurora

During my 20-week graduation project I built Poly Gone Spheres, a compute-heavy procedural planet editor, and runtime for repeatable planet creation pipelines. Because 20 weeks isn't as much time as you might expect, I had to make some sacrifices and choose priorities. As a result of this, the api is still relatively low-level in certain aspects, the reasoning was that I wanted to focus on the core first, ensuring that I have the capabilities and performance to work on poly-gone-spheres, with a slightly higher level wrapper potentially coming later.

This project along with poly-gone-sphers, proved to be quite a challenge, and perhaps a bit ambitious, but it has significantly deepened my understanding of realtime-graphics, rendering apis/engines, and game engines. After the project ended, I worked on the engine for a few more weeks, hence this page may differ slightly than that of poly-gone-spheres.

The result is a lightweight modular C++23 -code-only- engine, with a custom R.H.I., a multi-queue render graph, an ECS application model, and explicit asynchronous paths for rendering, compute, transfers, and asset work.

Exceptions

Throughout the Engine code, and it's sub-projects such as bifrost. Error values are preferred, over throwing exceptions. This is in part because it can be more efficient, but the most prominent reason is to make errors more explicit, and prevent unexpected thrown exceptions.

Concurrency model

Aurora separates gameplay, rendering, compute, and transfers. The game thread advances the ECS, drives input and UI, and submits general work to a work-stealing pool. The render thread builds and executes the frame, while dedicated compute and transfer threads own long-running GPU submission paths when the hardware provides suitable queues. These paths are still available when the hardware doesn't support dedicated queues, however then they will be shared with the render thread, instead of owning their own, leading to some potential performance loss. ECS systems can receive automatic inner parallelism, through flecs, where entity iterations are fanned-out to a worker pool.

GPU completion is treated as part of ownership. Uploads, readbacks, compute work, render submissions, and deferred resource destruction communicate through completion tokens so CPU threads can continue without destroying objects still in use by the device. GPU resource creation is thread-safe, and can be done from any thread, allowing for example, fully asynchronous mesh creation/loading.

Rendering deep dives

Render Hardware Interface

Bifrost

A custom R.H.I., implementing Vulkan 1.4. With D3D12 in progress.

Read about Bifrost

Scheduling and synchronization

Render Graph

Pass declarations become a dependency graph with virtual resources, dead-pass removal, transient reuse, calculated resource barriers, and release/acquire transitions between queues.

Read about the Render Graph.

Shaders

Aurora makes use of Slang for it's shaders, but -while not heavily tested- glsl and hlsl shaders should both work without problems. The render program and pipeline are separated, so shader variants can still use the same core program. The Shader Registry will automatically cache these, so you can easily re-use programs and create new variants. It uses SPIRV reflection to figure out the attributes of a shader if non were provided.

Program and Pipelines - C++
using enum bifrost::ShaderStage;
constexpr bifrost::ShaderStage stages = Vertex | Fragment;

auto program = aurora::RenderProgramBuilder(device)
                   .shader_file("assets/shaders/mandelbulb_background.spv")
                   .push_constants<BackgroundPushConstants>(stages)
                   .build();

auto pipeline = aurora::RenderPipelineShaderBuilder(program)
                    .cull_mode(bifrost::CullMode::None)
                    .depth_test(false, bifrost::CompareFunction::Always)
                    .depth_write(false)
                    .build(color_format_, depth_format_);

Modules and ECS

An earlier subsystem model depended on static initialization, which made registration convenient but rather brittle. Aurora now builds an explicit module graph around Flecs. A module can request its dependencies, expose a configuration type, register systems, and inject concrete or abstract resources into the world. The flecs explorer and stats local server are both supported as well, so you can use the flecs explorer site as an inspector for your scenes, and measure the performance of systems and queries. Flecs script can also be used, to get a data driven declaration of a scene, rather than declaring it in code. Note; systems must still be defined in C++. Hot-reloading of scenes, and other assets, is currently in progress.

Module bundles provide useful defaults, while still allowing custom configuration for each provided module, simply include those modules before the bundle, duplicates are automatically de-duplicated, the first entry is used. Aggregate components can be registered with automatically reflected fields (this is required if you want to use flecs reflection, for example for flecs scripts), systems infer their ECS queries from callable arguments, and interfaces such as the renderer can be replaced through injected resources.

Application configuration - C++
int
main()
{
    App::create()
        .add_module<CoreModule>({.log_level = spdlog::level::info})
        .add_module<DefaultModules>()
        .add_system(flecs::OnStart, setup)
        .add_system(flecs::OnUpdate, rotate)
        .run();
}
Module dependency and registration - C++
struct SceneModule
{
    explicit SceneModule(App &app)
    {
        app.try_add_module<CoreModule>();

        auto &world = app.world();
        world.component<Transform>();
        world.component<PlayerTag>();
        world.component<LookComponent>();
    }
};

Beyond rendering

Assets and shaders

Coroutine-based mesh and texture loading, cached PBR materials (experimental), Compile-time Slang compilation, reflection, and cached pipeline variants.

Physics and input

Jolt rigid-body integration with asynchronous mesh cooking, plus prioritized input contexts with triggers, chords, dead zones, and modifiers.

Build and verification

Independently buildable plugins, CMake/vcpkg and xmake workflows, GCC and Zig toolchains, Windows cross-compilation, and focused resource-lifetime tests.

Cross Platform

First Party Windows and Linux support, with Mac potentially coming in the future.