Profiler
Turian ships an in-engine profiler so optimisation is driven by data, not guesswork. It is the game's profiler — it measures the work the engine and your scripts do, not the editor's chrome — and it runs both inside Turian Studio and in the built game.
What it measures
- CPU zones — nested, scoped timers on every thread. Built-in zones cover
the GPU scene renderer (
render.scene,render.upload,render.shadow,render.main) and script updates (scripts.update); you can add your own. - Render counters — per-frame draw calls, triangles, vertices, texture binds, material switches, and textures created, gathered straight from the GPU scene renderer.
- Frame timing — wall-clock CPU time per frame, charted over the last few seconds.
Recording: tied to Play mode
Profiling runs only in Play mode, and you control when:
- ● Record / ❚❚ Pause starts and pauses capture while playing — handy when you only want to profile a later part of a run.
- auto-record on Play (checkbox) decides whether recording starts the instant you press Play, or waits for you to hit Record.
- Pressing Stop freezes the captured history so you can study it.
The idle editor viewport is never measured, so the numbers always reflect your game. A status line (● recording / ❚❚ paused / ■ idle) shows the current state.
The Studio panel
Open it from View ▸ Show Profiler (toggle). The floating panel shows:
- The recording transport, a VSync toggle, and an FPS cap selector (unlimited / 30 / 60 / 90 / 120). VSync is independent of the cap — caps above your monitor's refresh rate only take effect with VSync off.
- Capture Screenshot and Export Trace buttons (see below).
- A frame-time chart on a fixed 0–50 ms scale with 60 fps and 30 fps reference lines, so a change in frame rate visibly changes the bars. Each bar is split: the CPU-busy part at the bottom (coloured blue/yellow/red by how it fits the 60/30 fps budget) and the idle / cap-wait part on top in dim grey. So capping the fps makes a tall, mostly-grey bar — the bar height shows the lower frame rate, but it isn't coloured red, because your code isn't slow; it's just waiting out the cap. Click or drag across the chart to scrub the pinned frame in the timeline; the newest column (or ● live) resumes following the latest frame. A white marker shows the pinned/live frame, and a history selector (2 s / 4 s / 8 s) sets how far back you can scrub.
- A timeline — the centerpiece. Each thread is a lane; nested zones stack downward by depth and are laid out left-to-right in proportion to the frame's duration, like a flame graph. Zone colours are stable across frames, so a scope keeps its colour as you watch it. The timeline shows the pinned frame, so you can scrub back through history and inspect exactly what ran in any frame.
- Render (scene) counters for the shown frame.
- A collapsible Editor CPU (studio) section — the editor's own frame timing, tucked away so the game's scene metrics stay front and centre.
Because the renderer is shared between the editor viewport and the shipped game, the numbers you see in the panel are the real cost of rendering your scene.
Export for analysis (Perfetto)
Export Trace writes the recorded history to
<project>/profiles/trace_NNNN.json in Chrome/Perfetto trace-event format.
Open it in ui.perfetto.dev or chrome://tracing to
analyse zones across frames, compare frames, and drill into flame charts with a
full-featured tool. (Perfetto is the analysis side, so there's no need to import
traces back into the Studio.)
Screenshots
Capture the 3D viewport (the actual GPU-rendered scene) to a PNG at any moment — useful for spotting rendering glitches and checking proportions, and for sharing a frame with a teammate or an AI assistant (PNG opens everywhere).
- View ▸ Capture Screenshot, or the Capture Screenshot button in the profiler panel.
- Files land in
<project>/screenshots/shot_NNNN.png(the next free index, so shots are never clobbered). The panel reports the saved path.
Profiling your own code
Wrap any section of a script in a scoped zone — it appears in the timeline:
const engine = @import("engine");
pub fn update(self: *@This(), frame: engine.Frame) void {
var z = engine.Profiler.zone("MyScript.heavyWork");
defer z.end();
// ... expensive work ...
}
Zones nest automatically: a zone opened inside another becomes its child in the flame graph.
Cost when disabled
The profiler is on by default in Debug builds and off otherwise, so shipped release games pay nothing unless they opt in:
engine.Profiler.enabled = true; // turn it on in a release build
When disabled, every entry point early-outs on a single branch.
Built-game profiling
Built games run the same profiler: the generated game loop brackets each frame
with Profiler.beginFrame/endFrame and records the renderer and script-update
zones. An on-screen overlay HUD, frame-data export (JSON/CSV), and optional
Tracy integration are on the roadmap
(issue #35).