- C++ 52.1%
- JavaScript 22.9%
- Python 9.3%
- CMake 7.8%
- Shell 5.1%
- Other 2.7%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .claude | ||
| 3rdparty | ||
| apps | ||
| assets/shaders | ||
| cmake | ||
| docs | ||
| editor@0dd7f7df89 | ||
| engine | ||
| plugins | ||
| tools | ||
| .gitignore | ||
| .gitmodules | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| CMakeLists.txt | ||
| LICENSE | ||
| PLAN.md | ||
| README.md | ||
Karasu
A C23/C++26 3D game engine on SDL3 (window/input), SDL3_ttf (font rasterization + HarfBuzz shaping), Vulkan (GPU graphics), Box3D (physics), PhysicsFS (ZIP / VFS), glaze (TOML/JSON), Umka (plugins/scripts), log.c (logging), Dear ImGui (dev UI), and argparse (CLI argument parsing). Math goes through the engine's own library over GLM, GTE, Highway and FastNoise2. Ships with tengu, a native (wxWidgets) game editor sharing the same Vulkan rendering code as the games.
| Doc | Contents |
|---|---|
| PLAN.md | Architecture, phases, status, next steps |
| docs/PLUGINS.md | Plugin packages, ZIP archives, overwrites, load order |
| docs/ENGINE.md | Public modules, APIs, the math library, build notes, gotchas |
| docs/EDITOR.md | tengu, the brush editor: tools, geometry model, roadmap |
Quick start
# Arch Linux deps (Vulkan-capable driver also required)
sudo pacman -S sdl3 sdl3_ttf harfbuzz freetype2 vulkan-headers vulkan-icd-loader \
vulkan-validation-layers shaderc glslang cmake ninja base-devel fontforge
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
cmake --build build
./build/dist/hello_world/hello_world # engine smoke test
./build/dist/tri/tri # colored triangle + Dear ImGui
./build/dist/plugin_host/plugin_host # plugins, resource index, Umka inits
./build/dist/tengu/tengu # the game editor: wxWidgets-owned native window, same Gfx code as the games
Building tengu needs gtk3 (sudo pacman -S gtk3); everything else above builds without it. wxWidgets itself is vendored under 3rdparty/ and built from source as a shared library (see "Shipping a game" below for why shared), so no wxwidgets-gtk3 package is required — but that first build compiles wx from source, so expect a wx-enabled tree to take noticeably longer to configure and build the first time.
plugin_host copies plugins/ next to the binary on build; it also finds the repo-root plugins/ when run from the project tree.
What’s in the tree
3rdparty/ Vendored (all git submodules): argparse, box3d, logc, imgui,
imguizmo, physfs, glaze, umka, lz4 + zstd (.kba codecs),
glm + gte + highway + FastNoise2 (the math stack),
wxWidgets (tengu's window backend; built shared),
liberation-fonts + tabler-icons (content, staged into .kba),
ggml + llama.cpp / stable-diffusion.cpp / transcribe.cpp (opt-in AI)
<lib>_compat/ holds local patches; the trees stay pristine
assets/ GLSL sources (compiled to build/shaders/*.spv)
tools/ Build helpers (fonts, license collector)
kbx/ is a submodule: the .kba archive tool, and the only
thing that writes one (the build calls it to pack assets.kba)
engine/ Shared library karasu_<branch>.so, one per engine.branch
(core, math, gfx, window backends, ui, vfs, plugins, script host)
apps/ hello_world, tri, plugin_host, sandbox, widget_demo, flycam_demo
editor/ tengu: wxWidgets-backed native app, same engine::Gfx as the games
plugins/ Sample mods (hello, demo_mod, patch_mod, scene_api_test)
docs/ PLUGINS.md, ENGINE.md, EDITOR.md
PLAN.md Roadmap and architecture
Apps
| Binary | Purpose |
|---|---|
hello_world |
Init engine, probe deps, log hello, exit |
tri |
Vulkan triangle; per-vertex colors; Dear ImGui blend controls |
plugin_host |
Discover plugins, auto load order, index resources, run plugin_init* |
sandbox |
Black-screen stub; manifest + CLI demo; ships a dist folder (see below) |
widget_demo |
Nested Scene2D trees, WidgetScene, Labels, nested Layouts; --frames N runs headlessly and logs resolved layout |
flycam_demo |
Scene3D + Camera3D/CameraController; infinite ground plane, WASD/mouse-look fly camera; --frames N runs headlessly |
tengu |
The game editor. Native (wxWidgets) window + shared Vulkan viewport via the same engine::Gfx. Brushes as convex solids, the Brush/Select/Move/Rotate/Grab/Scale tools, Mesh Edit (subdivide, extrude, clip, bevel, proportional editing), flip/duplicate and clipboard copy/paste, layers, undo/redo — see docs/EDITOR.md |
Shipping a game
cmake --build build also assembles build/dist/sandbox/ — a self-contained
folder to copy anywhere:
sandbox the executable (RUNPATH $ORIGIN)
karasu_sandbox.so the engine, named by the manifest's engine.branch
assets.kba PhysicsFS asset archive
For a game: three files and no lib/. Everything vendored under 3rdparty/ is
statically linked into the engine library, and everything else (SDL3, SDL3_ttf,
the Vulkan loader) is a system package the machine already provides.
tengu is the exception, and gets a fourth entry — a lib/ holding the
wxWidgets shared libraries, found through an added $ORIGIN/lib RUNPATH. wx is
copyleft, and its licence entitles whoever receives the binary to drop in their
own build of the library; static linking would deny them that without shipping
the editor's object files. Nothing else ever goes in lib/ — see
cmake/WxRuntimeDist.cmake.
Every app is built into its own folder under build/dist/ — there is no
build/bin, so what you run out of the build tree is exactly what ships. The
engine itself is a shared library whose file name each game picks with
engine.branch in its manifest, and every dist folder carries its own copy, so
patching one game's engine cannot disturb another's.
assets.kba is a ZIP under a game extension — PhysFS mounts it by content, not
by name. Engine content is namespaced under engine/:
engine/fonts/liberation/*.ttf default engine fonts (+ upstream LICENSE)
The engine's own SPIR-V is compiled into the executable (#embed), not shipped
in the archive, so the folder runs with no build tree present.
The Liberation family is the engine default (LiberationSans-Regular.ttf).
It is built from the vendored FontForge .sfd sources at build time, so
FontForge is a build dependency (pacman -S fontforge), like glslc.
Add the same folder to another game with engine_add_game_dist(<target>).
CMake options
| Option | Default | Description |
|---|---|---|
ENGINE_BUILD_APPS |
ON | Build demo apps |
ENGINE_ENABLE_VALIDATION |
ON | Vulkan validation layers (Debug) |
ENGINE_ENABLE_DEVUI |
ON | Dear ImGui support |
ENGINE_BUILD_DOCS |
ON | Make the docs Doxygen target available |
ENGINE_ENABLE_LLM |
AUTO | Build llama.cpp (AUTO = whatever the game manifests ask for) |
ENGINE_ENABLE_DIFFUSION |
AUTO | Build stable-diffusion.cpp |
ENGINE_ENABLE_TRANSCRIBE |
AUTO | Build transcribe.cpp speech-to-text |
ENGINE_GGML_VULKAN |
ON | Compile ggml's Vulkan backend alongside CPU |
ENGINE_SIMD_TIER |
AUTO | CPU baseline for the whole build (AUTO = manifest cpu.simd_tier, else avx2) |
ENGINE_GGML_NATIVE |
OFF | Shorthand for ENGINE_SIMD_TIER=native (not portable) |
ENGINE_RELEASE_OPT_LEVEL |
O2 | Optimization level for Release (O2/O3/Os/Oz) |
ENGINE_LTO |
AUTO | Link-time optimization in Release/MinSizeRel |
ENGINE_LINKER |
AUTO | Linker for Release/MinSizeRel (AUTO prefers mold, then lld) |
ENGINE_RELEASE_STRIP |
ON | Strip symbol tables from shipping binaries |
ENGINE_RELEASE_ICF |
ON | Fold identical functions at link time (needs mold/lld/gold) |
Release builds
cmake -S . -B build-release -G Ninja -DCMAKE_BUILD_TYPE=Release
cmake --build build-release
Release and MinSizeRel are optimized for size and speed together
(cmake/ReleaseOptimization.cmake): LTO across the engine sources and the
vendored archives absorbed into karasu_<branch>.so, -ffunction-sections
plus --gc-sections, -fno-semantic-interposition to recover intra-library
inlining, identical-code folding where the linker supports it, and stripped
symbol tables. The two configs differ in one flag — Release is -O2,
MinSizeRel is -Os. Debug is untouched by any of it, so the day-to-day
loop keeps fast links and working backtraces.
What that is worth, measured on this tree (GCC 16.1, mold, avx2 tier):
| Debug | Release | MinSizeRel | |
|---|---|---|---|
karasu_sandbox.so |
105.8 MB | 5.6 MB | 4.3 MB |
sandbox |
1.46 MB | 92 KB | 45 KB |
karasu_tengu.so |
105.8 MB | 5.6 MB | — |
tengu |
14.4 MB | 700 KB | — |
sandbox dist folder |
105 MB | 7.8 MB | — |
tengu dist folder |
117 MB | 8.4 MB | — |
assets.kba is 2.4 MB of every dist folder, so after this the Liberation fonts
are the largest single thing that ships. The tengu executable and
tengu dist folder rows predate the switch to shared wxWidgets and have not
been re-measured — wx no longer sits inside that executable, so the binary is
smaller and its dist folder now also carries lib/. karasu_tengu.so is
unaffected (no karasu_<branch>.so ever linked wx), and neither are the game
numbers.
-ffast-math and its constituents are deliberately excluded and should stay
excluded: GTE is linked here specifically for geometry predicates whose value is
correct behaviour in degenerate cases, and relaxed floating point is exactly
what breaks them.
LTO makes the per-branch link genuinely expensive — one whole-program link per
distinct engine.branch, over every vendored archive. -DENGINE_LTO=OFF is the
lever when iterating on a Release tree.
SIMD tier. Everything — engine, apps, and every vendored library including
ggml — is compiled for one x86-64 SIMD baseline, avx2 (x86-64-v3) by default.
Set it per game via cpu.simd_tier in the manifest, or per build with
-DENGINE_SIMD_TIER=. It is deliberately not -march=native: that tunes to the
machine that compiled it and SIGILLs on anything older, which is invisible until
a player hits it.
Vendored libraries are never edited in place. Local fixes live as patches in
3rdparty/<lib>_compat/ and are applied at configure time. If you sync a library
and a patch stops applying, the configure fails and tells you which
_compat folder to refresh. Every build also reverts them again at the end
(cmake/ThirdPartyCompatRevert.cmake), so a patched submodule never shows up as
"modified content" in git status; the next configure re-applies it.
The three AI backends are normally selected per game rather than with these
flags: apps/<game>/game_manifest.toml carries features.enable_llm,
enable_diffusion and enable_transcribe, and AUTO builds the union of what
the manifests ask for. All of them run on the single shared 3rdparty/ggml,
with CPU and Vulkan backends chosen at runtime. With no game opting in (the
default), none of it — ggml included — is configured or compiled.
Language
- C23 for C units (third-party often C99 with extensions where required)
- C++26 for engine and apps
Third-party (summary)
| Library | Role | CMake target |
|---|---|---|
| SDL3, SDL3_ttf, Vulkan | System packages | SDL3::SDL3, SDL3_ttf::SDL3_ttf, Vulkan::Vulkan |
| argparse | CLI argument parsing | argparse::argparse |
| Box3D | Physics | box3d::box3d |
| PhysicsFS | VFS + ZIP archives | physfs::physfs |
| glaze | TOML/JSON serialization; compile-time manifest parsing (consteval TOML) and runtime plugin.toml |
glaze::glaze |
| Umka | Scripting / plugins | umka::umka |
| log.c | Logging | logc::logc |
| Dear ImGui | Dev UI | imgui::imgui |
| ImGuizmo | 3D gizmos + the curve editor tengu's falloff uses | imguizmo::imguizmo |
| GLM | Linear algebra (mirrors GLSL) | glm::glm |
| GTE | Robust geometric predicates and queries | gte::gte |
| Highway | Portable SIMD over point arrays | hwy, hwy_contrib |
| FastNoise2 | Procedural noise (linked, no facade yet) | FastNoise |
| wxWidgets | tengu's window backend; built shared, shipped in lib/ |
engine::window_wx |
| liberation-fonts, tabler-icons | Content — built/staged into assets.kba |
engine_fonts |
Note: rxi's log_error/log_warn/log_info macros collide textually with any library declaring identifiers of those names, so include engine/log.h late in a translation unit. (simdjson was the library that actually collided; glaze, which replaced it, has no such identifier.)
Status
Working: build system and per-branch shared engine library, Vulkan frame loop with a
per-frame-in-flight depth buffer, Dear ImGui dev UI, the math library over
GLM/GTE/Highway, Renderer2D + SDL3_ttf shaped text, the Scene/SceneGraph stack
with Scene2D/Scene3D trees and the widget/layout toolkit, Camera3D +
CameraController (perspective and orthographic), PhysicsFS VFS and resource
catalog (index + lazy load), the Umka plugin host with explicit imports,
automatic load order from depends + overwrites, compile-time game
manifests via #embed + C++26 static reflection, and both window backends
(SDL for games, wxWidgets for tengu).
Not done yet: a generic mesh/material renderer (Scene3D::on_draw_3d() is still a
raw hook), physics demos beyond linking Box3D, entity system / map serialization /
texturing in tengu, audio, networking, tests and CI.