Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions BUILD.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ Defined in [`source/CMakeLists.txt`](source/CMakeLists.txt). Pass via `-D<NAME>=
| `BUILD_STEAMLIB` | OFF* | Build the Steamworks integration module |
| `BUILD_UNIT_TEST` | OFF | Build cmocka/utest-based unit tests |
| `GAME_MODULES_ONLY` | OFF | Build only `cgame`/`game`/`ui` modules |
| `USE_GRAPHICS_NRI` | OFF* | Use the NVIDIA NRI renderer (Vulkan/D3D) |
| `USE_GRAPHICS_NRI` | OFF* | Use the RI renderer (Vulkan) |
| `USE_CRASHPAD` | OFF* | Enable Crashpad crash reporting |
| `USE_GRAPHICS_X11` | ON | (Linux) X11 backend |
| `USE_GRAPHICS_WAYLAND`| ON | (Linux) Wayland backend |
Expand All @@ -181,5 +181,5 @@ Defined in [`source/CMakeLists.txt`](source/CMakeLists.txt). Pass via `-D<NAME>=
| `USE_SYSTEM_FREETYPE` | OFF* | Use system FreeType |
| `USE_SYSTEM_OGG` | OFF | Use system Ogg |
| `USE_SYSTEM_VORBIS` | OFF | Use system Vorbis |

| `TRACY_ENABLE` | OFF | Compile in Tracy profiling instrumentation |
\* The workflow presets override several of these defaults — check [`source/CMakePresets.json`](source/CMakePresets.json).
107 changes: 107 additions & 0 deletions PROFILING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
# Profiling Warfork with Tracy

The codebase ships with [Tracy](https://github.com/wolfpld/tracy) zones across the per-frame hot paths. This guide walks through building a profiler-enabled game, building the Tracy GUI client, and connecting them.

Tracy is vendored at version **0.11.2** in [`source/extern/tracy/`](source/extern/tracy/) — you do not need to install it system-wide.

## How it works

- The game runtime links `TracyClient` and emits zone events when `TRACY_ENABLE` is defined at compile time.
- The Tracy profiler (a separate GUI app) connects to the running game over TCP and visualizes the timeline.
- When `TRACY_ENABLE` is **off** (the default), every Tracy macro expands to nothing — there is zero runtime cost, so non-profiled release builds are unaffected.

## 1. Build the game with Tracy enabled

Pass `-DTRACY_ENABLE=ON` through the platform wrapper:

```bash
# Linux
./build-linux.sh release -- -DTRACY_ENABLE=ON

# macOS
./build-macos.sh release -- -DTRACY_ENABLE=ON

# Windows (Developer PowerShell for VS 2022)
.\build-windows.ps1 release -- -DTRACY_ENABLE=ON
```

> **Tip**: Profile a `RelWithDebInfo` build (the default for the workflow presets) rather than `Debug` — debug builds spend most of their time in unoptimized code, which makes the timeline dominated by overhead rather than the work you care about.

When the executable starts, it will listen on TCP port **8086** for an incoming Tracy connection.

## 2. Build the Tracy profiler GUI

The GUI is a separate application that lives in [`source/extern/tracy/profiler/`](source/extern/tracy/profiler/). Build it once and reuse the binary across captures.

### Linux

System packages required (Debian/Ubuntu shown — the names map directly on Fedora/Arch):

```bash
sudo apt install build-essential cmake git pkg-config \
libcapstone-dev libtbb-dev libfreetype-dev libdbus-1-dev \
libwayland-dev libxkbcommon-dev wayland-protocols
```

Build:

```bash
cd source/extern/tracy/profiler
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j"$(nproc)"
```

The binary lands at `source/extern/tracy/profiler/build/tracy-profiler`.

If you're on a system without Wayland (e.g. an older X11-only setup), pass `-DLEGACY=ON` to fall back to the X11 backend:

```bash
cmake -B build -DCMAKE_BUILD_TYPE=Release -DLEGACY=ON
```

### macOS

```bash
brew install cmake capstone freetype tbb glfw
cd source/extern/tracy/profiler
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
```

The binary lands at `source/extern/tracy/profiler/build/tracy-profiler`.

### Windows

From a *Developer PowerShell for VS 2022*:

```powershell
cd source\extern\tracy\profiler
cmake -B build -G "Visual Studio 17 2022" -A x64
cmake --build build --config Release
```

The binary lands at `source\extern\tracy\profiler\build\Release\tracy-profiler.exe`. Dependencies (capstone, freetype, glfw, etc.) are fetched automatically via CPM during configure — no manual setup needed.

### Prebuilt option

If you don't want to build the GUI yourself, the upstream project publishes prebuilt Windows binaries on its [releases page](https://github.com/wolfpld/tracy/releases). Make sure the download matches the vendored version (**0.11.2**) — the wire protocol is not stable across major versions.

## 3. Connect and capture

1. Launch the Tracy profiler GUI (`tracy-profiler` / `tracy-profiler.exe`).
2. In the connection dialog, leave the address as `127.0.0.1` (or enter the game's IP if profiling remotely) and click **Connect**.
3. Start the game (`warfork`, `wf_server`, or `wftv_server`). The profiler attaches automatically and the timeline begins streaming.
4. Play through the scenario you want to profile.
5. Disconnect or close the game — the GUI keeps the capture loaded and offers **File → Save** to write a `.tracy` file for later analysis.

For headless / CI captures, the `tracy-capture` tool in [`source/extern/tracy/capture/`](source/extern/tracy/capture/) records to a file without a GUI:

```bash
cd source/extern/tracy/capture
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/tracy-capture -o my-session.tracy
```

Run that *before* starting the game; it will wait for an instrumented client to connect, record until you hit Ctrl+C, then write the capture.

3 changes: 3 additions & 0 deletions source/client/cl_input.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
// cl.input.c -- builds an intended movement command to send to the server

#include "client.h"
#include "tracy/TracyC.h"

cvar_t *cl_ucmdMaxResend;

Expand Down Expand Up @@ -901,6 +902,7 @@ void CL_ShutdownInput( void )
*/
void CL_UserInputFrame( void )
{
TracyCZoneN( ctx, "CL_UserInputFrame", 1 );
// let the mouse activate or deactivate
IN_Frame();

Expand All @@ -912,6 +914,7 @@ void CL_UserInputFrame( void )

// process console commands
Cbuf_Execute();
TracyCZoneEnd( ctx );
}

/*
Expand Down
22 changes: 19 additions & 3 deletions source/client/cl_main.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#include "../qcommon/asyncstream.h"
#include "../qalgo/hash.h"
#include "../steamshim/src/parent/parent.h"
#include "tracy/TracyC.h"
#include <stdlib.h>

cvar_t *cl_stereo_separation;
Expand Down Expand Up @@ -1456,6 +1457,7 @@ void CL_ReadPackets( void )
int socketind, ret;
socket_t *socket;
netadr_t address;
TracyCZoneN( ctx, "CL_ReadPackets", 1 );

socket_t* sockets [] =
{
Expand Down Expand Up @@ -1540,6 +1542,7 @@ void CL_ReadPackets( void )
}

if( cls.demo.playing ) {
TracyCZoneEnd( ctx );
return;
}

Expand All @@ -1556,12 +1559,15 @@ void CL_ReadPackets( void )
{
Com_Printf( "\nServer connection timed out.\n" );
CL_Disconnect( "Connection timed out" );
TracyCZoneEnd( ctx );
return;
}
}
}
else
cl.timeoutcount = 0;

TracyCZoneEnd( ctx );
}

//=============================================================================
Expand Down Expand Up @@ -2756,6 +2762,8 @@ static void CL_SendVoiceData() {
*/
static void CL_NetFrame( int realmsec, int gamemsec )
{
TracyCZoneN( ctx, "CL_NetFrame", 1 );

// read packets from server
if( realmsec > 5000 ) // if in the debugger last frame, don't timeout
cls.lastPacketReceivedTime = cls.realtime;
Expand All @@ -2776,6 +2784,8 @@ static void CL_NetFrame( int realmsec, int gamemsec )
CL_CheckDownloadTimeout();

CL_ServerListFrame();

TracyCZoneEnd( ctx );
}

/*
Expand All @@ -2791,6 +2801,8 @@ void CL_Frame( int realmsec, int gamemsec )
if( dedicated->integer )
return;

TracyCZoneN( cl_frame_ctx, "CL_Frame_active", 1 );

cls.realtime += realmsec;

if( cls.demo.playing && cls.demo.play_ignore_next_frametime )
Expand Down Expand Up @@ -2841,7 +2853,8 @@ void CL_Frame( int realmsec, int gamemsec )

CL_UpdateSnapshot();
CL_AdjustServerTime( gamemsec );
CL_UserInputFrame();
CL_UserInputFrame();

CL_NetFrame( realmsec, gamemsec );
if (STEAMSHIM_active()) {
STEAMSHIM_dispatch();
Expand Down Expand Up @@ -2889,7 +2902,7 @@ void CL_Frame( int realmsec, int gamemsec )

if( allRealMsec + extraMsec < minMsec )
{
// let CPU sleep while playing fullscreen video, while minimized
// let CPU sleep while playing fullscreen video, while minimized
// or when cl_sleep is enabled
bool sleep = cl_sleep->integer != 0;

Expand All @@ -2898,6 +2911,7 @@ void CL_Frame( int realmsec, int gamemsec )

if( sleep && minMsec - extraMsec > 1 )
Sys_Sleep( 1 );
TracyCZoneEnd( cl_frame_ctx );
return;
}

Expand Down Expand Up @@ -2967,7 +2981,7 @@ void CL_Frame( int realmsec, int gamemsec )

// update discord
CL_UpdatePresence();

// advance local effects for next frame
SCR_RunCinematic();
SCR_RunConsole( allRealMsec );
Expand All @@ -2976,6 +2990,8 @@ void CL_Frame( int realmsec, int gamemsec )
allGameMsec = 0;

cls.framecount++;

TracyCZoneEnd( cl_frame_ctx );
}


Expand Down
Loading
Loading