What budgets measure and how they help you ship.
Performance budgets
A performance budget is a number you commit to: “this game will use at most this much”. DemiEngine does not enforce it and never changes quality to hit it. It gives you a measuring stick.
What problem this solves
“It runs fine on my machine” is not a plan. Budgets turn feel into data:
- A level designer knows the triangle cap before placing the 400th crate.
- A CI job can fail when a change doubles draw calls.
- A qualification run has a declared intent to compare against.
Without budgets, regressions hide until a player complains on a low-end phone.
Declare budgets
"performance_budgets": {
"maximum_frame_ms": 16.7,
"maximum_draw_calls": 800,
"maximum_resident_assets": 256,
"maximum_visible_instances": 4000,
"maximum_unique_meshes": 120,
"maximum_triangles": 500000,
"maximum_texture_memory_mb": 256,
"maximum_lights": 8,
"maximum_shadow_lights": 2,
"maximum_transparent_draws": 200
}
What each number means
| Field | Unit | What it measures |
|---|---|---|
maximum_frame_ms | milliseconds | Whole frame time. 16.7 ms is 60 FPS; 8.3 ms is 120 FPS. |
maximum_draw_calls | count | Render submissions per frame. Each is CPU + driver overhead. |
maximum_resident_assets | count | Assets kept loaded at once. Drives memory and load stalls. |
maximum_visible_instances | count | Drawn model instances after culling. |
maximum_unique_meshes | count | Distinct meshes submitted. High values break batching. |
maximum_triangles | count | Triangles submitted per frame. |
maximum_texture_memory_mb | MiB | GPU texture memory. |
maximum_lights | count | Lights affecting the frame. |
maximum_shadow_lights | count | Lights allowed to cast shadows — the expensive ones. |
maximum_transparent_draws | count | Transparent/unsorted draws, which are hard to batch. |
Set only the fields you care about. Missing fields are simply not budgeted.
How to use the numbers
1. Pick targets per platform
Desktop 1080p and Android mid-range are different games. Use different project files or different values in CI profiles. A starting point for a 60 FPS 2D game:
"performance_budgets": {
"maximum_frame_ms": 16.7,
"maximum_draw_calls": 300,
"maximum_resident_assets": 128,
"maximum_texture_memory_mb": 128
}
2. Measure against them
demi asset budget games/my_game/demi.project.json --platform linux
This reports current usage versus each declared limit, so you see headroom or overflow before shipping.
3. Profile the overage
demi run linux \
--profiler --profile-report report.csv
Budgets tell you that you overflowed; the profiler tells you where. See Replay & profiler.
4. Put it in the pipeline
demi asset budget project/demi.project.json --platform android
# fail the job when the command reports overflow
Combine with a deterministic scene load and a profile CSV so every merge request has a number to compare.
What budgets do not do
- They never lower quality, hide objects, or change resolution.
- They never block a build. Exceeding one is a measurement, not a compile error.
- They are project data, not device auto-detection. You decide the intent per target.
Choosing sensible caps
- Build the heaviest scene you expect to ship.
- Measure it on your weakest target device.
- Leave 20–30% headroom for polish and late content.
- Round the number and write it down in the project file.
Budgets are most useful early. Setting them after the game is done only documents the overrun.