Luth Engine

July 13, 2026 · View on GitHub

For progress tracking and upcoming work, see ROADMAP.md.


Design Philosophy

  1. No OS blocking on worker threads. Fibers yield to the scheduler; OS threads always stay busy.
  2. No thread_local. Fiber Local Storage (FLS) carried in JobContext.
  3. No std::mutex in the hot path. Spin-locks (< 100 cycles) or lock-free structures only.
  4. No new/delete in gameplay/render. Use LinearAllocator (frame) or TaggedPageAllocator / GPUTaggedPageAllocator (tagged lifetime — Onion/Garlic split).
  5. No VkRenderPass/VkFramebuffer. Dynamic Rendering only (vkCmdBeginRendering).
  6. No vkWaitForFences. Timeline Semaphores; per-frame completion via VulkanBackend::IsFrameComplete.
  7. Pipelined execution. Game(N) | Render(N-1) | GPU(N-2). Realized in v2.8.4 (history) — game + render dispatch concurrently on worker fibers, handoff via RenderSnapshot captured at end of game stage.
  8. Main thread is isolated. OS message pump + present only. Never steals jobs.

For detailed fiber hazard analysis (V1-V6 vulnerability mitigations), see arch/fiber-system.md.

At any frame T, three stages overlap, each one frame behind the last:

time ──►
              ┌──────────┬──────────┬──────────┬──────────┐
   CPU game   │ N        │ N+1      │ N+2      │ N+3      │
              ├──────────┼──────────┼──────────┼──────────┤
   CPU render │ N-1      │ N        │ N+1      │ N+2      │
              ├──────────┼──────────┼──────────┼──────────┤
   GPU exec   │ N-2      │ N-1      │ N        │ N+1      │
              └──────────┴──────────┴──────────┴──────────┘

Game (N) updates transforms and animation, then freezes a RenderSnapshot POD into the frame's arena. Render (N-1) reads the previous snapshot, builds the render graph, records per-pass command buffers in parallel, and submits. GPU (N-2) executes what was submitted before. The frame boundary is the snapshot, not shared mutable state.


Build Targets

Since arch-target-split (v2.0.0) the engine and editor are separate static libs:

TargetKindLinksContents
Luth.libStaticLibEngine only — no ImGui panels, no editor classes
Luthien.libStaticLibLuthEditor — panels, inspectors, commands, style, widgets, LuthienEditorHooks impl
Luthien.exeConsoleAppLuth, LuthienThin runtime at runtime/LuthienApp subclass; CreateApp() installs editor hooks before app ctor

Engine → editor calls route through the nullptr-safe Luth::EditorHooks interface in luth/core/EditorHooks.h. A runtime-only host can link Luth.lib alone; every engine-side hook call short-circuits when the registry is empty.

System Hierarchy

[Luth Engine — Luth.lib]

 ├── [Core]
 │    ├── JobSystem .............. N:M Fiber Scheduler (FLS, Chase-Lev, MPMC)
 │    ├── Memory ................. TaggedPageAllocator (CPU) + GPUTaggedPageAllocator (GPU) + LinearAllocator
 │    ├── IOThread ............... Dedicated OS thread for disk I/O
 │    ├── App .................... Two-phase init: Engine boot → Project load
 │    ├── EntryPoint, Version, FrameData, UUID, ProjectFile, EditorHooks .. top-level lifecycle (core/ root)
 │    ├── types/                 LuthTypes (primitives), LuthMath (Vec/Mat/Quat aliases + `Math::` facade + Assimp/AABB/Frustum), TypeTraits (IsGLMVector/Matrix)
 │    ├── diagnostics/           Log, LogFormatters (fmt + ostream<< Vec3/Mat4), Profiler
 │    └── time/                  Time, Timer

 ├── [Events]
 │    └── EventBus ............... Deferred queue-swap dispatch (AppEvent, KeyEvent, MouseEvent, RenderEvent, FileDropEvent)

 ├── [Platform]
 │    ├── Window / WinWindow ..... GLFW window + Win32 dark-mode title bar
 │    ├── Input .................. Keyboard/mouse state (queries editor capture via EditorHooks)
 │    └── FileDialog ............. Native open/save dialogs

 ├── [Renderer (Vulkan 1.3 + hardware RT)]
 │    ├── RenderPipeline ......... ~650-LOC orchestrator — holds the subsystem instances, dispatches Init/Shutdown/Update/Add*Pass in dependency order, owns per-view scratch + named-texture registry + shader-reload dispatch
 │    ├── rendergraph/ ........... RenderGraph (DAG compile → barrier inject → execute) + FrameCapture / ArchivedImage / RenderResourceCache
 │    ├── subsystems/ ............ Per-Set / per-feature lifecycle hosts. Raster: Global, Lighting, Geometry, GTAO, PostProcess, EditorOverlays, Skinning, DebugDraw. RT: Rt (BLAS/TLAS), RtRestir (DI), RtRestirGi (GI), Reflections, PathTrace. Denoise/atmos: SvgfDenoiser (IDenoiser), Volumetric
 │    ├── FrameTargets ........... SceneColor / SceneDepth / slim G-buffer (normal/roughness/motion/matID) / EntityID / LDROutput / Selection {mask,depth}
 │    ├── DrawListBuilder ........ ECS walk → opaque/cutout/transparent buckets
 │    ├── TaaJitter / QueueRecorders / CameraParams .. per-view Halton jitter, per-queue cmd recorders, camera POD
 │    ├── Backend ................ VulkanContext (RT extensions + PFN cache), VulkanBackend, Timeline Semaphores
 │    ├── lighting/ .............. LightGatherer, CascadeBuilder, FogVolumeGatherer, IBLPrecompute, LightTypes
 │    ├── resources/ ............. Texture, Mesh, Model, Buffer, Skeleton, AnimationClip, BoneMatrixBuffer (Set 4), GlobalUniforms, per-view ViewResources (RT/denoise/volumetric atlases)
 │    ├── material/ .............. Material, MaterialSystem (GPUMaterialData SSBO, Set 2)
 │    ├── shader/ ................ Shader, ShaderCompiler, ShaderLibrary, ShaderWatcher (hot-reload service owned by RenderPipeline)
 │    ├── pipeline/ .............. PipelineManager (lazy variant creation, disk-persisted VkPipelineCache, keyed by shader+mode)
 │    ├── settings/ .............. GTAO, PostProcess, Volumetric, Restir (DI), RestirGi, Svgf, Reflections, PathTrace
 │    ├── draw/ .................. DrawCommand
 │    ├── passes/ ................ ImGuiPass (remaining standalone pass; the rest moved into subsystems/)
 │    ├── debug/ ................. FrameDebuggerContext (preview textures + blit pass + per-draw replay-then-copy + depth archive blit)
 │    ├── FrameDebugger .......... Archive + state machine (owned by RenderingSystem); render-side infrastructure in debug/FrameDebuggerContext
 │    └── Renderer ............... High-level BeginFrame/EndFrame façade

 ├── [Scene / ECS]
 │    ├── Scene, Entity
 │    ├── components/ ............ Granular headers (Common, Transform, Camera, Rendering, Lights, Animation, AnimationController); Components.h umbrella preserved
 │    └── SystemRegistry ......... vector<unique_ptr<ISystem>>, Update<T>() dispatch
 │         ├── TransformSystem ... Parallel level-based hierarchy propagation
 │         ├── AnimationSystem ... Fiber-parallel keyframe sampling, GPU skinning
 │         ├── CameraSystem ..... Projection + view matrix computation per frame
 │         ├── LightingSystem ... Light gather + CSM cascade fit (LightGatherer + CascadeBuilder); outputs consumed by RenderPipeline
 │         ├── RenderingSystem .. ~200-LOC ECS→DrawList dispatcher; owns FrameTargets + CameraParams + FrameDebugger + DrawList; invokes RenderPipeline::Execute
 │         └── PickingSystem .... Mouse-pick readback from EntityID buffer; runs post-render

 └── [Asset Pipeline (resources/)]
      ├── AssetDatabase .......... Two-phase: InitEngine() → LoadProject()
      ├── AssetManager ........... Async loading, GPU upload queue, GC
      ├── Importers (Model, Texture, Shader, Material)
      └── FileSystem ............. Dual-root: engine assets + project assets

[Luthien Editor — Luthien.lib, at luthien/source/luthien/]  (details: arch/editor.md)

 ├── Editor, UI, EditorSelection, EditorCamera, EditorSettings, EditorStyle
 ├── Bootstrap.h / EditorHooks.cpp ─ InstallLuthienEditorHooks() forwards IEditorHooks → Editor::*
 ├── CommandHistory + commands/ ... Undo/redo: ICommand + 14 command types, compound recording
 ├── ProjectLauncher ............. Startup project selector, recent projects
 ├── panels/ ..................... Scene, Hierarchy, Inspector, Project, Render, FrameDebugger, Profiler, History, TextureRemapDialog
 ├── inspectors/ ................. MaterialEditor, ModelViewer, TextureEditor, ShaderEditor, SceneViewer, FontViewer
 └── widgets/ .................... Icons (semantic map → Phosphor), ImGuiUtils

For pipelined frame execution details, see arch/frame-pipeline.md.


Render Graph

Each frame the renderer builds a DAG of passes. A pass declares its reads and writes through a RenderPassBuilder; the graph solves pipeline barriers, culls unused passes, and computes resource lifetimes. Passes then execute in topological order, with command-buffer recording parallelized across worker threads.

graph.AddPass<GeometryPassData>("GeometryPass",
    [&](GeometryPassData& data, RG::RenderPassBuilder& builder) {
        data.depthTex  = builder.WriteDepth(sceneDepth, ...);
        data.outputTex = builder.Write(sceneColor);
        data.indirect  = builder.ReadIndirectBuffer(indirectBuffer);
    },
    [=](GeometryPassData& data, RG::RenderPassContext& ctx) {
        // record draw commands on ctx.commandBuffer
    });

Rendering — Current Baseline

SystemState
RenderGraphDAG compile, barrier injection, dead-pass cull, serial execution; per-pass secondary-cmd recording
GeometryClustered Forward+ PBR (Olsson log-slice clusters); slim G-buffer prepass (normal RG16F / roughness R8 / motion RG16F / matID R16U); 3 render-mode variants
ShaderSlang-only stack (one compiler, reflection-driven); Cook-Torrance BRDF shared raster/RT eval via common/brdf.slang
MaterialGPUMaterialData SSBO (Set 2), 9 bindless maps + flag bitfield, 176 B std430, JSON .mat
Shading modelsClear coat, anisotropy, dielectric transmission (IOR + GGX BTDF + Beer-Lambert), Charlie sheen, subsurface scattering (random-walk PT oracle + raster BRDF); present in both raster and path-traced paths
Surface detailParallax occlusion mapping (tangent-space height raymarch) + UV-projected decals; authored in the node graph
LightingClustered Forward+ — 1 directional + clustered point and spot lights; per-view cluster grid + light-index SSBOs (Set 3)
ShadowsRT ray-query sun shadows (default) + per-view mask; 4-cascade PSSM CSM retained as an A/B ShadowingMode toggle
RT foundationKHR ray query + acceleration structure (RT-mandatory); per-mesh BLAS (static + skinned refit), per-frame TLAS rebuild on async compute; bindless geometry table (instanceCustomIndex → BDA deref)
RT global illuminationReSTIR DI (Bitterli 2020) + ReSTIR GI (Ouyang 2021) — device-local reservoir reuse, reconnection Jacobian, demodulated + denoised
RT reflectionsStochastic GGX-VNDF specular rays from the slim G-buffer + dedicated specular denoiser; supersedes SSR
DenoisingSVGF (Schied 2017) — three channel-parameterized instances (DI / GI / specular) behind an IDenoiser interface
Path tracingReference path-traced mode (RenderMode::PathTrace) — rayQuery megakernel, multi-bounce NEE + GGX-VNDF lobe MIS, progressive fp32 accumulation
Volumetric fogWronski froxel grid — density/scatter inject → integrate → temporal resolve → composite; optional per-froxel RT fog shadows
AOGTAO half-res compute chain (prefilter → horizon integral → bilateral denoise), Jimenez 2016 slice integral
GPU cullingCompute frustum cull per shadow cascade + main scene, GPUObjectData SSBO (Set 5), vkCmdDrawIndexedIndirect
AnimationFiber-parallel keyframe sampling, GPU skinning via BoneMatrixBuffer SSBO (Set 4), SQT blending, crossfade, layered override, root motion
AATAA (Karis14 YCoCg-clip recipe) + specular AA (Tokuyoshi 2019)
Post-processingHDR (RGBA16F), bloom, tonemapping (ACES variants + AgX / AgX Punchy), vignette, grain, CA
BindlessBDA enabled; Set 1 — 16384-texture array + 32-slot sampler array (UPDATE_AFTER_BIND, partially-bound); integer material/texture indices
Shader systemSlang shader assets (.slang, staged via reflection, one artifact + UUID); ShaderLibrary::LoadEngine routes engine shaders through the asset pipeline; hot-reload via FileWatcher; SPIRV-Cross reflection
Frame DebuggerGPU timers, pass tree, pipeline state, per-draw replay, texture preview
MipmapsvkCmdBlitImage chain, per-texture .meta settings
Texture compressionImport-time BCn via bc7enc/rgbcx; role-based Auto (BC7 color, BC5 normals), transfer-queue level uploads
Scene serializationJSON .luth format, native file dialogs
Pipeline cacheVkPipelineCache disk persistence + PipelineManager (lazy, keyed by shader+mode)
Skybox / IBLHDR equirect→cubemap, irradiance convolution, pre-filtered env (5 mips), BRDF LUT, split-sum ambient

For descriptor set layout, pass order, and memory budget, see arch/rendering-pipeline.md.


Detailed Architecture References

AreaFileWhen to read
Fiber system (V1-V6 mitigations)arch/fiber-system.mdWorking on jobs/core
Memory (allocators, tracker, STL gap)arch/memory.mdWorking on allocators or memory budgets
Profiling (Tracy + GPUTimerPool)arch/profiling.mdAdding instrumentation or chasing perf
Vulkan validation layersarch/validation-layers.mdVulkan setup, debug callback, build-define overrides
GPU crash debugging (Aftermath, device-lost)arch/gpu-crash-debugging.mdChasing VK_ERROR_DEVICE_LOST / GPU hangs
Version markers (V1-V6)arch/version-glossary.mdReading Vn source comments — JobSystem hazards vs asset format versions
Rendering pipeline (descriptor sets, pass order)arch/rendering-pipeline.mdWorking on renderer
Multi-queue (async compute, queue families)arch/multi-queue.mdWorking on async compute / queue submission
Frame pipeline (triple-buffer model)arch/frame-pipeline.mdWorking on frame pipeline
Asset pipeline (importers, loading, caching)arch/asset-pipeline.mdWorking on assets/resources
Scene & ECS (components, systems, serialization)arch/scene-ecs.mdWorking on scene/ECS
Animation (clips, skeleton, GPU skinning, blending)arch/animation-system.mdWorking on animation
Physics (Jolt integration, jobified)arch/physics.mdWorking on physics / colliders
Editor (panels, IEditorHooks, selection, UI)arch/editor.mdWorking on editor