Skip to content

Benchmarks

KKE's promise is that a game made with it runs on a 1-core, 2 GB machine with no GPU. This page is the evidence: what is measured, how to measure it yourself, and the numbers so far.

There are two benchmarks, and both write the same kind of report (kke::BenchmarkReport: a .txt for people and a .json for tools):

What it measures Run it
Stress test The whole engine as a player sees it: the showcase level for 36 s (walk with animation and IK, 300 crates raining, heavy impacts), uncapped. Frames per second, 1% lows, GPU and physics time per phase, and the list of modules that were running. KKE_STRESS_TEST=1 ./kke_demo, or Performance → Run stress test
kke_bench The engine's CPU building blocks one at a time, headless and single-threaded, with fixed seeds, so a change in a number means the code changed. ./kke_bench (--quick, --filter NAME, --list)

Reports go to benchmark/ (or $KKE_BENCH_DIR). Use a Release build for numbers you compare; Debug is several times slower.

kke_bench cases

Each case reports the median and p95 time of one sample, in ms (lower is better), and a checksum of what it computed. The checksum is the same on every run of the same commit; if it isn't, the workload stopped being deterministic, and that's a bug.

Case One sample is
rigid_crates_400 One 60 Hz Jolt step while 400 crates fall and pile up
rigid_raycast_1000 1000 raycasts into a settled pile (picking, audio occlusion, camera arm)
fracture_bake_cube Voxelizing a 1 m cube at 10 cm and baking a Voronoi fracture (what spawning a breakable costs)
particle_fluid_2000 One step of 2000 Position Based Fluids particles (the melt and lava budget)
audio_mix_32_voices 10 ms of output with 32 spatial voices and reverb; under 10 ms is real time
impact_synth_8_materials Synthesizing one impact sound for each default material
lua_think_50_hooks One Think hook fanned out to 50 Lua handlers
net_snapshot_256_bodies Bit-packing and unpacking a 256-body network snapshot

Cases for code that needs Jolt or Lua are left out of builds without them.

Tracked over time

The Benchmarks workflow (.github/workflows/benchmarks.yml) builds Release on every push to main, runs kke_bench and the stress test (under Xvfb with Mesa's lavapipe software Vulkan, like the smoke tests), and appends the results to the benchmark-data branch: history.jsonl holds every run, and its README shows a table and a trend chart per number. Pull requests run a quick pass and get a comparison in the job summary without being recorded. Runs on main go one at a time; when several pushes land while one runs, only the newest waits and the ones in between are skipped, so the history samples main rather than listing every commit.

Every number is compared with the median of the last 10 runs, and changes beyond ±25% are marked in the job summary. Nothing fails on a slow number: GitHub's shared runners vary by 10-20% between runs, so a single flag is a reason to look at the chart, not proof. The job does fail when a benchmark crashes, writes no report, or the build has a warning.

benchmarks/track.py does the bookkeeping and needs only Python 3. To compare your own runs: python3 benchmarks/track.py --history my-history --commit $(git rev-parse HEAD) --date now benchmark/*.json.

Hardware profiles

benchmarks/docker/compose.yml runs both benchmarks inside limits, so anyone can reproduce the low-end rows on whatever machine they have:

docker compose -f benchmarks/docker/compose.yml run --rm build         # once: Release build in build-bench/
docker compose -f benchmarks/docker/compose.yml run --rm floor-1c-2g   # reports in benchmark/floor-1c-2g/
Profile Cores RAM GPU
floor-1c-2g 1 2 GB none (lavapipe)
laptop-1c-2g-vram512 1 2 GB lavapipe, 512 MB VRAM cap
dual-2c-2g-vram1g 2 2 GB lavapipe, 1 GB VRAM cap
mid-4c-8g 4 8 GB lavapipe
high-8c-16g 8 16 GB lavapipe
intel-arc, amd all all the host GPU through /dev/dri (and /dev/kfd for AMD)
nvidia all all the host GPU (needs nvidia-container-toolkit)
custom KKE_CPUSET KKE_MEM KKE_VRAM_BUDGET_MB, KKE_GPU_DEVICE, KKE_VK_DRIVER_FILES

What this can and can't show, honestly:

  • Cores are pinned with a cpuset, so the engine sees exactly that many (it sizes its worker threads from CPU affinity). RAM is a hard cgroup limit with no extra swap.
  • Docker can't limit VRAM or turn one GPU into another. The engine's KKE_VRAM_BUDGET_MB setting caps the Vulkan allocator's device-local heaps, so allocations beyond the cap fail as they would on the smaller card, and the stress report records the cap. It works outside Docker too.
  • The software-GPU profiles measure the CPU doing the GPU's work, which is the real worst case for "no GPU". They say nothing about how fast a real low-end GPU is; those rows have to come from real hardware.
  • Phones and Macs can't be emulated honestly (different CPUs, GPUs and drivers). Their rows come from people's own devices.

Results

Release builds, kke_demo without FEMFX (the default build), 1280x720. Frame times are the stress test's; the kke_bench column is the rigid_crates_400 median as a CPU speed reference.

Core modules (Settings, Input, RigidBodies, Network, Models, UI, Audio, Scripts, Showcase)

Machine Profile GPU FPS avg 1% low Verdict Jolt step (ms)
Cloud VM, Xeon @ 2.80 GHz (2026-09-26) floor-1c-2g lavapipe (software) 7.2 4.1 too slow 1.66
Cloud VM, Xeon @ 2.80 GHz (2026-09-26) laptop-1c-2g-vram512 lavapipe, 512 MB cap 7.3 4.6 too slow 1.70
Cloud VM, Xeon @ 2.80 GHz (2026-09-26) dual-2c-2g-vram1g lavapipe, 1 GB cap 11.8 7.6 too slow 1.69
Cloud VM, Xeon @ 2.80 GHz (2026-09-26) mid-4c-8g lavapipe (software) 19.4 11.3 too slow 1.66
Cloud VM, Xeon @ 2.80 GHz, 4 vCPU, 16 GB (2026-09-26) whole machine, no Docker lavapipe (software) 20.0 12.3 too slow 1.74

What these say: on a machine with no GPU at all, the CPU also draws every pixel, and drawing is most of the frame (on the floor profile the GPU passes take about 95% of it; physics is under 1 ms). The stress test is deliberately harder than a normal scene, so "too slow" here is the worst case, not what a typical game on that machine gets. The fps roughly scale with cores because lavapipe renders on every core it's given. The Jolt column stays flat because kke_bench is single-threaded by design. The VRAM caps didn't change anything: the showcase fits in 512 MB.

Core + FEMFX

No rows yet: the default build leaves FEMFX off. Build with -DKKE_ENABLE_FEMFX=ON and send yours.

Your hardware

Real machines are the rows that matter. Run the stress test (and ./kke_bench if you like) on a Release build, then open a Benchmark result issue with the reports attached. Rows are added here with the CPU, GPU and OS from the report, never the host name.