Skip to content

LeftofZen/Meridian

Repository files navigation

Meridian

A next-gen game engine.

Build

Meridian supports two build workflows:

  • Visual Studio 2026 with the MSVC 19.50 toolset
  • VS Code with clang-cl against the same Windows/MSVC SDK toolchain

Set VCPKG_ROOT to your vcpkg checkout before configuring. The VS Code build task also falls back to a sibling checkout at ../vcpkg if present.

For Visual Studio 2026 with MSVC:

cmake --preset vs2026-debug
cmake --build --preset vs2026-debug

For VS Code with Clang:

cmake --preset vscode-clang-debug
cmake --build --preset vscode-clang-debug

The Clang preset is portable on purpose: it does not hardcode local install paths. In VS Code, use the Build Meridian (VS Code Clang) task, or run it from a shell where clang-cl and the MSVC/Windows SDK build environment are already available.

This writes generated files to the local build/ directory, which is ignored by Git.

Profiling

Detailed runtime profiling now uses Tracy instead of the in-engine timing tables. Run Meridian with a Tracy viewer connected to inspect per-frame update, render, Vulkan, and render-feature zones.

In VS Code, use the Run Meridian + Tracy launch compound to build, open the Tracy viewer, and start Meridian together. If you only need the viewer, run the Open Tracy Profiler (VS 2026) task. Both prompt for the Tracy viewer path so you can point them at a local Tracy viewer install.

RenderDoc shader debugging is supported in the normal debug build. Meridian now compiles GLSL shaders to SPIR-V with embedded debug information and assigns Vulkan debug names to the main swapchain, pipeline, buffer, and synchronization objects when VK_EXT_debug_utils is available. That gives RenderDoc readable capture object names and source-level shader mapping.

For RenderDoc captures, launch build/vs2026-debug/Debug/Meridian.exe from RenderDoc or inject into a running debug build. If RenderDoc cannot show source-level shader information, rebuild once to regenerate the .spv files with current sources before taking the capture.

All future runtime-facing work should add or update Tracy coverage as part of the implementation, rather than relying on ad hoc timing code.

Architecture Guide

This document outlines the core data structures and acceleration strategies for implementing a high-performance path tracer within a procedural, infinite, and simulation-heavy environment.

Voxel Data Design References

  • Runevision, "Fast and Gorgeous Erosion Filter": heightfield generation reference for large-scale terrain shape.
  • John Lin, "The Perfect Voxel Engine": data-oriented reference for keeping a common raw voxel volume, converting to task-specific formats, and avoiding one-format-fits-all voxel architecture.

Rendering Architecture Rules

  • Keep the rendering core/engine backend separate from frontend or game-level graphics responsibilities.
  • Treat Vulkan/device/swapchain/frame execution as core renderer concerns.
  • Treat scene presentation, debug UI, ImGui windows, and other game-facing visuals as frontend features layered on top of the backend.
  • Prefer a modular, idiomatic frame pipeline that composes multiple rendering features rather than growing a single monolithic renderer class.
  • New rendering work should extend the pipeline through focused feature modules before adding responsibilities to the core backend.

1. High-Level Architecture: Two-Level Acceleration

To handle dynamic entities (NPCs, players) alongside a dynamic world, we utilize a Two-Level Acceleration Structure (TLAS/BLAS).

  • Bottom-Level (BLAS): Contains local geometry (Triangles for NPCs, SVOs for terrain).
  • Top-Level (TLAS): Contains instances of BLAS objects with transform matrices.
    • Implementation Note: Update only the TLAS matrices during the simulation loop to avoid expensive geometry rebuilds.

2. Terrain Data: Sparse Voxel Octree (SVO)

Traditional meshes are unsuitable for infinite, editable terrain. Meridian treats the world as an infinite 3D grid of cubic chunks, where each chunk is an $N^3$ voxel volume and $N$ is a power of two. The default is $N = 32$. Terrain generation can still start from a heightfield, but chunks themselves are volumetric and not height-bounded objects.

The current terrain bootstrap uses a heightfield as source data, following Runevision's erosion work: https://blog.runevision.com/2026/03/fast-and-gorgeous-erosion-filter.html?m=1.

Meridian's terrain compute path now ports the actual Buffer A generation logic from Runevision's advanced terrain erosion shader rather than a simplified approximation. That includes derivative noise for the base height field, Phacelle noise, the full erosion filter with assumed-slope control, pointy-peak gully weighting, stacked fading, separate ridge and crease rounding, and ridge-map generation alongside the final height output.

The broader chunk-data pipeline follows John Lin's argument that voxel engines should preserve a common raw format and convert into specialized structures as needed rather than forcing all systems onto one representation: https://voxely.net/blog/the-perfect-voxel-engine/.

  • Efficiency: O(\log n) traversal using a Digital Differential Analyzer (DDA) algorithm.
  • Chunk Model: Chunk storage is cubic voxel data first; the per-chunk SVO is a derived acceleration structure for traversal.
  • Editability: Real-time "digging" or "building" only requires updating local chunk voxels and propagating changes into derived structures.
  • LOD: The tree depth inherently provides Levels of Detail; distant rays can stop at higher-level nodes to save cycles.
  • Format Conversion: Different systems are free to consume different derived voxel formats when needed, instead of baking gameplay, rendering, persistence, and simulation into one monolithic storage layout.

3. Global Management: Spatial Hash Grid

For an infinite world, we manage memory through a Spatial Hash Grid that stores active chunks in all directions.

  • Key: Chunk Coordinates (e.g., x, y, z hashed to a uint64).
  • Value: Pointer to a local SVO/BLAS.
  • Workflow:
    1. Ray exits current chunk bounds.
    2. Query Hash Grid for the neighbor chunk.
    3. If chunk exists, continue ray-march; if not, return "sky" or trigger procedural generation.

4. Entity Handling: Dynamic BVH

For characters and physics-driven objects that deform or move rapidly:

  • Structure: A specialized Bounding Volume Hierarchy that supports "refitting" (adjusting bounds without a full rebuild).
  • Integration: These BVHs are referenced as instances within the TLAS, allowing rays to transition seamlessly between voxel terrain and polygonal NPCs.

5. Performance Checklist

Strategy Purpose
DDA Stepping Fast ray-traversal through voxels.
Temporal Re-projection Smear lighting data across frames to reduce noise (e.g., ASVGF).
Stochastic Updates Prioritize updating acceleration structures for objects closest to the camera.
Ray Binning Group rays by direction to improve GPU cache hits.

Technology Stack Decisions

The following library selections were evaluated against the requirements of a real-time, sim-heavy, path-traced voxel game engine.

Physics — Jolt Physics

Selected: Jolt Physics

vcpkg package: joltphysics

Candidate Verdict Notes
Jolt Physics Selected Multi-threaded, deterministic, modern C++17/20 API, production-proven (Horizon Forbidden West, Death Stranding 2), MIT licensed
Bullet3 Considered Mature and stable but predominantly single-threaded, older API design
PhysX (NVIDIA) Rejected Proprietary license, GPU path tightly coupled to CUDA rather than Vulkan
Havok Rejected Commercial/enterprise license, unsuitable for open development

Key reasons:

  • Multi-threaded job system is essential to overlap physics simulation with Vulkan path-tracing work
  • Deterministic mode enables lock-step multiplayer replay without re-running full physics on each client
  • Soft body and shape-cast queries map naturally onto destructible voxel chunks
  • Header-friendly C++20 integration

Fluid Simulation — Custom GPU SPH/FLIP via Vulkan Compute

Selected: Custom Vulkan Compute Shader implementation (SPH or FLIP method)

Candidate Verdict Notes
Custom GPU SPH/FLIP Selected Zero-copy with path tracer, native voxel grid coupling, Vulkan-native
SPlisHSPlasH Rejected CPU-primary research library; GPU support requires CUDA, incompatible with Vulkan path
PhysBAM Rejected Academic, outdated toolchain, not game-engine-ready

Key reasons:

  • No existing real-time fluid library supports direct Vulkan compute integration
  • GPU SPH runs entirely on the same queue as the path tracer, eliminating CPU↔GPU synchronisation overhead
  • FLIP (Fluid-Implicit-Particle) offers higher visual fidelity for water; SPH for gas/fire
  • Sparse fluid data stays in Meridian-owned GPU buffers, avoiding an extra volumetric format dependency

Particle Simulation — Custom GPU Particle System via Vulkan Compute

Selected: Custom Vulkan Compute Shader particle system

Optional cache/replay: Partio (Disney) if offline export is needed

Candidate Verdict Notes
Custom GPU Compute Selected Native Vulkan, zero-copy with path tracer, direct voxel density read/write
Partio (Disney) Optional Excellent for particle cache I/O (BGEO, PDB formats); not a simulator
Third-party VFX library Rejected All mature options (Houdini-side) are CPU-primary or proprietary

Key reasons:

  • Particles are a first-class data structure in the voxel simulation loop: they read voxel density, deposit material, and drive fluid advection
  • A custom system avoids serialisation across library boundaries
  • The particle-to-grid (P2G) and grid-to-particle (G2P) transfers map directly to compute dispatch calls alongside the path-tracing pipeline
  • Estimated implementation cost: ~1–2 weeks for a production-quality GPU particle system

ECS (Entity Component System) — EnTT

Selected: EnTT

vcpkg package: entt

Candidate Verdict Notes
EnTT Selected Header-only, C++20, sparse-set storage gives optimal cache locality, 12K+ stars, used in Minecraft Bedrock
Flecs Considered Excellent query system and built-in pipelines; slightly heavier and less C++20-idiomatic
EntityX Rejected Archived/unmaintained project

Key reasons:

  • Sparse-set groups guarantee sequential memory access for tight simulation loops over millions of physics/particle entities
  • Header-only: no binary ABI issues across MSVC/GCC/Clang toolchains
  • entt::registry integrates cleanly with Jolt Physics body handles and the spatial hash grid chunk handles
  • Benchmarks consistently show 10–50× iteration throughput advantage over Flecs for dense component queries

Networking — GameNetworkingSockets (Valve)

Selected: GameNetworkingSockets

vcpkg package: gamenetworkingsockets

Candidate Verdict Notes
GameNetworkingSockets Selected Production-proven (Steam), reliable UDP, NAT traversal, P2P + relay hybrid, 9K+ stars
yojimbo Considered Clean game-focused API, but smaller community and not in vcpkg
ENet Fallback Excellent lightweight reliable-UDP; lacks built-in NAT traversal
Asio Rejected Raw sockets only; requires a full game-protocol layer on top

Key reasons:

  • NAT traversal and relay are mandatory for peer-to-peer voxel world sharing without dedicated server infrastructure
  • Reliable-on-unreliable lanes let physics state updates use fire-and-forget while chat and chunk data use guaranteed delivery — all on one socket
  • Deterministic physics (Jolt) + reliable transport (GNS) enables authoritative rollback networking with minimal bandwidth

Input Management — SDL3 (raw input) + custom action-mapping layer

Selected: SDL3 built-in input + thin custom action-mapping layer

Optional binding library: gainput

vcpkg package: SDL3 already included; gainput available if needed

Candidate Verdict Notes
SDL3 built-in Selected Keyboard, mouse, gamepad (including sensors, rumble, hot-plug), touch — all unified under one event loop we already own
gainput Optional Cross-platform action-mapping/binding abstraction on top of raw input; vcpkg available; last major release 2018 — limited C++20 support
OIS Rejected Primarily OGRE-ecosystem, outdated

Key reasons:

  • SDL3's SDL_Event loop already handles all device types including DualSense/Xbox haptics, touch screens, and IME text input — no additional library needed for raw input
  • A lightweight action-mapping layer (e.g., InputActionSDL_Scancode binding table, ~200 lines) is engine-specific enough to write in-house; avoids an extra dependency
  • Add gainput only if cross-platform controller remapping UI becomes a priority

World Persistence — LMDB (chunks) + SQLite/SQLiteCpp (game state)

Selected:

  • LMDB for voxel chunk on-disk storage
  • SQLiteCpp wrapping SQLite3 for entity/game state

vcpkg packages: lmdb, sqlitecpp (pulls in sqlite3 automatically)

Candidate Verdict Notes
LMDB Selected (chunks) Memory-mapped, ACID, key = uint64 chunk coordinates, value = zstd-compressed SVO bytes; single-process read/write; near-zero copy on reads
SQLite3 + SQLiteCpp Selected (game state) Transactional, zero-config embedded SQL; used for player data, inventories, quest state; R-tree extension available for spatial indexing
RocksDB Rejected Better for distributed systems; write-amplification overhead undesirable for game chunks
LevelDB Considered Precursor to RocksDB; simpler, but lacks LMDB's memory-map advantage for large random reads

Persistence strategy:

Chunk key   = morton_encode(chunk_x, chunk_y, chunk_z)  // uint64
Chunk value = zstd_compress(serialise_svo(chunk))        // byte blob
  • LMDB stores the compressed SVO blob — mmap gives O(1) random access without malloc
  • SQLite R-tree extension (USING rtree) provides bounding-box spatial index for persistent entity queries
  • On save: dirty chunks are flushed to LMDB; entity/physics state snapshotted to SQLite

Spatial Queries — SQLite R-tree + EnTT views (in-memory)

Selected: SQLite3 R-tree extension for persistent spatial index; EnTT views for live in-memory queries

No additional library required beyond SQLite support already used for persistence

Candidate Verdict Notes
SQLite R-tree extension Selected (persistent) Uses SQLite's R-tree virtual table support (USING rtree) to handle bounding-box overlap queries on saved entity state
EnTT views/groups Selected (in-memory) Sparse-set iteration with component filters serves as the live ECS query layer
Boost.Geometry Rejected Overkill; adds heavy Boost dependency for a feature covered by SQLite and EnTT

Key reasons:

  • In-memory spatial queries (nearest entity, frustum cull, physics broad phase) are handled by Jolt Physics (broad phase BVH) and EnTT component views — no extra library needed
  • Persistent queries (spawn tables, saved region triggers) naturally fit the SQLite R-tree used for entity persistence

Spatial Audio — Steam Audio

Selected: Steam Audio

vcpkg package: steam-audio

Audio I/O backend: SDL3 audio output (already present) or miniaudio

Candidate Verdict Notes
Steam Audio Selected HRTF binaural rendering, physics-based occlusion/reverb via ray casting, path tracing integration possible, Apache-2.0, from Valve (same ecosystem as GNS)
SDL3 built-in audio Partial Raw PCM playback only — no 3D positioning or reverb
SDL3-mixer Considered Multi-channel mixing, format decoding — can sit on top of Steam Audio for decoding
OpenAL-Soft Considered 3D positional audio with EFX reverb; less integrated with a ray-cast world
miniaudio Fallback Single-header audio I/O; excellent as the device output layer beneath Steam Audio

Key reasons:

  • Steam Audio's occlusion model uses ray casting against the scene geometry — this maps directly onto the SVO path tracer already in the engine; voxel geometry can be submitted as an occlusion mesh at essentially zero extra cost
  • HRTF (Head-Related Transfer Function) provides AAA-quality binaural sound in an open world with minimal CPU cost
  • Apache-2.0 license is compatible with MIT/BSD stack
  • SDL3 remains the audio device output backend (SDL3 → Steam Audio steam-audio pipeline: SDL3 opens device → miniaudio/SDL3 feeds PCM → Steam Audio applies spatialization and convolution reverb)

Math Library — GLM

Selected: GLM (OpenGL Mathematics)

vcpkg package: glm

Candidate Verdict Notes
GLM Selected Header-only, mirrors GLSL types (vec3, mat4, quat), Vulkan-column-major convention built-in, MIT licensed, 10K+ stars
Eigen Considered More powerful for linear algebra/physics solvers, but heavier; Jolt ships its own math types anyway
Custom Rejected No justification given GLM's quality and vcpkg availability

Key reasons:

  • GLSL-compatible type names make shader ↔ CPU data layout trivial
  • Jolt Physics has its own JPH::Vec3/JPH::Mat44; conversion helpers to/from glm types are one-liners
  • glm::packUnorm4x8 and similar functions are directly useful for voxel material packing

Logging — spdlog

Selected: spdlog

vcpkg package: spdlog

Candidate Verdict Notes
spdlog Selected Header-only mode or pre-compiled, async sinks, structured log levels, pattern formatting, MIT licensed, industry standard
std::print / printf Rejected No log levels, no file rotation, no async
Boost.Log Rejected Heavy dependency for logging alone

Key reasons:

  • Async logging sink ensures the hot path (simulation loop, path tracer dispatch) is never stalled by I/O
  • Multiple sinks simultaneously (console + rotating file) with zero configuration
  • SPDLOG_TRACE/SPDLOG_DEBUG macros compile to nothing in release builds

Serialization — FlatBuffers

Selected: FlatBuffers

vcpkg package: flatbuffers

Candidate Verdict Notes
FlatBuffers Selected Zero-copy deserialisation, memory-mapped access, schema-driven, cross-language (useful for tooling), Apache-2.0; already a transitive dependency of Steam Audio
cereal Considered Header-only, simple API, but requires full deserialisation into C++ types before use
MessagePack Considered Compact binary format; no zero-copy
Protocol Buffers Rejected Already a GNS transitive dependency but overkill as primary serialisation

Key reasons:

  • Chunk data, network packets, and save files all benefit from zero-copy reads — FlatBuffers buffers can be written directly to LMDB values and read back without parsing
  • flatc code generator produces typed C++ accessors, eliminating serialisation bugs
  • Reuses a dependency already pulled in by steam-audio

Compression — zstd

Selected: zstd (Zstandard)

vcpkg package: zstd

Candidate Verdict Notes
zstd Selected Best ratio/speed tradeoff for structured binary data; dictionary training for voxel chunk patterns; BSD-3 licensed
lz4 Considered Fastest decompression (~5 GB/s); best for network packets where bandwidth < CPU
zlib/deflate Rejected Older algorithm; worse ratio and speed than zstd
snappy Rejected No dictionary support; worse ratio than zstd

Key reasons:

  • Voxel chunks have repeated structure (homogeneous regions) that benefits enormously from zstd dictionary compression (train a dictionary on a sample of chunks → 2–4× better ratio)
  • Fits Meridian chunk serialisation directly without adding a second runtime compression path
  • Level 1 zstd decompresses at ~2 GB/s; sufficient for real-time chunk streaming

Debug UI / Engine Editor — Dear ImGui

Selected: Dear ImGui

vcpkg package: imgui with features vulkan-binding and sdl3-binding

Candidate Verdict Notes
Dear ImGui Selected Immediate-mode, zero-retained-state, Vulkan + SDL3 backends both in vcpkg, MIT licensed, industry standard for in-engine tooling
Qt Rejected Heavyweight, separate event loop incompatible with SDL3
Custom UI Rejected High cost for low differentiation

Key reasons:

  • Vulkan backend (imgui[vulkan-binding]) submits draw calls directly into the main render command buffer
  • SDL3 backend (imgui[sdl3-binding]) integrates event handling with zero extra work
  • Essential from day one: performance overlay, voxel inspector, physics debugger, console

Scripting — Lua + sol2

Selected: Lua via sol2 C++ bindings

vcpkg packages: lua, sol2

Candidate Verdict Notes
Lua + sol2 Selected Tiny VM (~250 KB), coroutines native, MIT license, sol2 gives zero-overhead C++ ↔ Lua binding, industry-standard for game modding
AngelScript Considered Statically typed, C-like syntax; good for gameplay programmers but heavier than Lua
ChaiScript Rejected Header-only but slower than Lua; limited ecosystem
Python Rejected Too heavy for an embedded game scripting VM

Key reasons:

  • Lua coroutines are ideal for NPC behaviour trees and quest scripts — a coroutine can yield each frame without callback hell
  • sol2's usertype binds EnTT entities, Jolt body handles, and voxel grid functions to Lua with minimal boilerplate
  • Modding community expects Lua; proven in Factorio, World of Warcraft, Roblox

AI / Navigation — Recast & Detour

Selected: Recast Navigation

vcpkg package: recastnavigation

Candidate Verdict Notes
Recast & Detour Selected Industry standard navmesh (used in Unity, Unreal, Godot), Zlib licensed; Recast builds the mesh, Detour queries it
Custom A* on voxel grid Considered Simpler but does not handle 3-D environments, crowds, or off-mesh connections
PathEngine Rejected Commercial license

Key reasons:

  • Recast generates navmeshes from triangle soup or voxel heightfields — can consume SVO geometry directly
  • Detour handles multi-floor 3-D navigation with crowds and dynamic obstacle avoidance
  • Tile-based incremental navmesh rebuild matches the chunk-streaming world model

Task Scheduling — Taskflow

Selected: Taskflow

vcpkg package: taskflow

Candidate Verdict Notes
Taskflow Selected C++17 task graph with work-stealing, async tasks, CUDA/Vulkan compute integration hooks, MIT licensed
Intel TBB Considered Mature, high-performance; heavier dependency, Apache-2.0
Jolt's job system Partial Excellent for physics work but private API; cannot schedule non-physics tasks
std::async / thread pool Rejected No dependency graph; deadlock-prone for complex pipelines

Key reasons:

  • The main loop is a dependency graph: [Input] → [Physics] → [SVO update] → [TLAS rebuild] → [Vulkan submit]
  • Taskflow's tf::Taskflow expresses this graph declaratively; the work-stealing executor saturates all cores
  • Integrates with Jolt's job system by wrapping the physics step as a single Taskflow task
  • Header-only mode available for faster iteration

Implementation Roadmap

Phase 0 — Foundation (Current)

  • SDL3 window creation
  • Vulkan surface initialisation
  • Integrate Vulkan device/swapchain setup
  • Add basic render loop (clear colour, present)
  • Integrate spdlog logging and Dear ImGui debug overlay
  • Integrate Taskflow executor for the main loop dependency graph

Phase 1 — World Representation

  1. Implement Spatial Hash Grid for chunk lifecycle management
  2. Build SVO generator driven by procedural noise (Perlin/Simplex + erosion filter per blog post)
  3. Implement DDA Ray-Marcher in GLSL/HLSL for SVO traversal
  4. Set up LMDB chunk persistence (zstd-compressed FlatBuffers SVO blobs)
  5. Set up SQLite/SQLiteCpp for entity and game state persistence

Phase 2 — Rendering

  1. Integrate TLAS/BLAS two-level acceleration structure
  2. Implement path tracer kernel (primary rays → shadow/AO → GI)
  3. Add ASVGF temporal re-projection for denoising

Phase 3 — Simulation

  1. Integrate Jolt Physics for rigid body + soft body simulation
  2. Implement GPU SPH/FLIP fluid simulation in Vulkan compute
  3. Implement GPU particle system with P2G/G2P voxel coupling

Phase 4 — Entities & Gameplay

  1. Integrate EnTT ECS; define core component types
  2. Implement Dynamic BVH for character/NPC meshes
  3. Integrate Recast & Detour navmesh for NPC pathfinding
  4. Add Lua + sol2 scripting for NPC behaviours, quests, and modding hooks
  5. Implement SDL3 input action-mapping layer (keyboard/gamepad bindings)

Phase 5 — Audio

  1. Integrate Steam Audio (HRTF, occlusion via SVO ray casts, convolution reverb)
  2. Connect SDL3 audio output to Steam Audio PCM pipeline

Phase 6 — Networking

  1. Integrate GameNetworkingSockets
  2. Implement chunk streaming and FlatBuffers + zstd delta-compression protocol
  3. Add rollback/reconciliation layer over deterministic Jolt physics

About

A next-gen game engine

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors