A next-gen game engine.
Meridian supports two build workflows:
- Visual Studio 2026 with the MSVC 19.50 toolset
- VS Code with
clang-clagainst 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-debugFor VS Code with Clang:
cmake --preset vscode-clang-debug
cmake --build --preset vscode-clang-debugThe 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.
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.
- Tracy github: https://github.com/wolfpld/tracy
- Tracy documentation/manual: https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf
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.
- 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.
- 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.
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.
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
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.
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:
- Ray exits current chunk bounds.
- Query Hash Grid for the neighbor chunk.
- If chunk exists, continue ray-march; if not, return "sky" or trigger procedural generation.
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.
| 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. |
The following library selections were evaluated against the requirements of a real-time, sim-heavy, path-traced voxel game engine.
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
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
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
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::registryintegrates 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
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
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_Eventloop 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.,
InputAction→SDL_Scancodebinding 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
Selected:
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
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
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)
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/fromglmtypes are one-liners glm::packUnorm4x8and similar functions are directly useful for voxel material packing
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_DEBUGmacros compile to nothing in release builds
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
flatccode generator produces typed C++ accessors, eliminating serialisation bugs- Reuses a dependency already pulled in by
steam-audio
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
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
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
yieldeach frame without callback hell - sol2's
usertypebinds 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
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
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::Taskflowexpresses 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
- 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
- Implement Spatial Hash Grid for chunk lifecycle management
- Build SVO generator driven by procedural noise (Perlin/Simplex + erosion filter per blog post)
- Implement DDA Ray-Marcher in GLSL/HLSL for SVO traversal
- Set up LMDB chunk persistence (zstd-compressed FlatBuffers SVO blobs)
- Set up SQLite/SQLiteCpp for entity and game state persistence
- Integrate TLAS/BLAS two-level acceleration structure
- Implement path tracer kernel (primary rays → shadow/AO → GI)
- Add ASVGF temporal re-projection for denoising
- Integrate Jolt Physics for rigid body + soft body simulation
- Implement GPU SPH/FLIP fluid simulation in Vulkan compute
- Implement GPU particle system with P2G/G2P voxel coupling
- Integrate EnTT ECS; define core component types
- Implement Dynamic BVH for character/NPC meshes
- Integrate Recast & Detour navmesh for NPC pathfinding
- Add Lua + sol2 scripting for NPC behaviours, quests, and modding hooks
- Implement SDL3 input action-mapping layer (keyboard/gamepad bindings)
- Integrate Steam Audio (HRTF, occlusion via SVO ray casts, convolution reverb)
- Connect SDL3 audio output to Steam Audio PCM pipeline
- Integrate GameNetworkingSockets
- Implement chunk streaming and FlatBuffers + zstd delta-compression protocol
- Add rollback/reconciliation layer over deterministic Jolt physics