Compare commits

...

532 Commits

Author SHA1 Message Date
Bartosz Taudul
30997d5ca6 Release 0.14.1. 2026-08-22 19:06:43 +02:00
Bartosz Taudul
aa53e377e7 Use xdg-foreign v2 protocol for Wayland file dialog parent window. 2026-08-22 18:31:26 +02:00
Bartosz Taudul
cae9bf0bef Decrease rate of event production in test application. 2026-08-22 14:31:58 +02:00
Bartosz Taudul
4d3a84a97e Refine GUI verify skill name and description. 2026-08-22 13:07:05 +02:00
Bartosz Taudul
10926caed4 Add tooltip stating statistics limit also affects source view. 2026-08-22 12:57:34 +02:00
Bartosz Taudul
073152a10a Support time range limit entries tooltips. 2026-08-22 12:57:34 +02:00
Bartosz Taudul
2ec2c2c8ac Mention that statistics range limit also affects source view. 2026-08-22 12:41:29 +02:00
Bartosz Taudul
dce2c710e5 Merge pull request #1459 from alandtse/feat/mcp-sdk-v2
feat(mcp): migrate to MCP Python SDK v2
2026-08-22 11:04:22 +02:00
Alan Tse
e670bd18f7 feat(mcp): migrate to MCP Python SDK v2
`pip install mcp` now installs 2.x by default (v1 is
maintenance-only going forward), and nothing in this repo pinned a
version, so a fresh install already broke: v2 removed
mcp.server.fastmcp entirely, no compat shim.

- Import/class: mcp.server.fastmcp.FastMCP ->
  mcp.server.mcpserver.MCPServer.
- Transport options (host, port, sse_path, streamable_http_path)
  moved off the constructor and the settings object onto run();
  mutating mcp_server.settings.port now raises ValueError.
- _http_ping and the startup message read _SSE_PATH/
  _STREAMABLE_HTTP_PATH constants instead of settings.sse_path/
  settings.streamable_http_path -- v2 no longer exposes them to
  read back. Values match the SDK's own defaults in both versions
  ("/sse", "/mcp"), so served paths are unchanged.
- Adds extra/mcp/requirements.txt (mcp>=2.0.0,<3) -- there was no
  dependency manifest at all before this.

Decorator API is unchanged; every tool/resource here was already
async def, so the "sync handlers now run on worker threads" change
doesn't apply.

Verified in an isolated venv (mcp 2.0.0): module imports cleanly
with all 10 tools and 2 resources registered, the server starts
and serves streamable-http on the expected path, and _http_ping
correctly reports a running instance as alive.
2026-08-21 20:42:28 -07:00
Bartosz Taudul
1dfe828cdf Do not assert on zero content length in HttpRequest. 2026-08-22 02:26:54 +02:00
Bartosz Taudul
23ffe89d24 Abort pending update check on profiler exit. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
f5938e0336 Remove unused Socket::ConnectBlocking. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
86ea59bbe3 Make HttpRequest interruptible. 2026-08-22 02:20:24 +02:00
Bartosz Taudul
56f5d1239d Add IsConnecting and interruptible ReadUpTo to Socket. 2026-08-22 02:20:21 +02:00
Bartosz Taudul
cc04578f68 Derive meson and python package versions from TracyVersion.hpp. 2026-08-21 23:05:58 +02:00
Bartosz Taudul
39722e1e3f Skip timeline drawing when no width is available. 2026-08-21 22:10:07 +02:00
Bartosz Taudul
78a7553c6f Guarantee forward progress in the plot preprocess loop. 2026-08-21 22:10:05 +02:00
Bartosz Taudul
cdd5940b44 Define TRACY_NO_FILESELECTOR when the NO_FILESELECTOR option is set.
The CMake option only controlled whether NFD is built and linked, but
the file selector code is guarded by the TRACY_NO_FILESELECTOR macro,
which the build never defined - configuring with -DNO_FILESELECTOR=ON
failed to compile (nfd.h not found in TracyFileselector.cpp,
BackendWayland.cpp and BackendGlfw.cpp). Define the macro for the
profiler target when the option is set.
2026-08-21 20:20:45 +02:00
Bartosz Taudul
246139fb04 Show the NFD error message in the file selector failure popup.
When the native dialog failed, the popup said "File selector cannot
be displayed. Check nfd library implementation for details." but never
showed any details: NFD_GetError() was never read, so the actual
failure reason (e.g. the xdg-desktop-portal lacking a FileChooser
implementation on the session) was invisible and the user was stuck
without a way to open or save traces.

Capture NFD_GetError() into a static string when
NFD_OpenDialogU8_With()/NFD_SaveDialogU8_With() return NFD_ERROR and
display it in the popup. The copy is required: the returned pointer
references NFD-internal storage (the D-Bus error message or a static
buffer) that is overwritten by the next NFD call or cleared on
shutdown. In TRACY_NO_FILESELECTOR builds no NFD exists, so the
previous fallback line is kept there.

The error is shown as a single line (TextUnformatted) rather than
wrapped: in an AlwaysAutoResize window the wrap width is derived from
the previous frame's window size, which feeds back into the auto-size
and oscillates, converging to a narrow multi-line column for a ~110
character D-Bus message.
2026-08-21 20:20:45 +02:00
Bartosz Taudul
5257132d97 Indent grouped child zone rows in the zone info window.
When a child group was expanded, the member rows were not visibly
indented under the group name: each row starts at the tree indent
(column 0 is IndentEnable by default, and TableBeginRow() latches the
open group's TreePush indent), so a single Indent() put the label two
IndentSpacing from the cell origin. The group header, however, leads
with the source location color box plus item spacing before its tree
arrow, so its name starts four IndentSpacing further right than the
member labels - the child list read as a continuation of the header
row instead of a subtree.

Add a second Indent() (and the balancing Unindent()) so the member
labels land clearly to the right of the group name. The full-row
Selectable behavior is unchanged. GPU zones are unaffected: their
rows carry no color box, so the member labels were already one level
right of the group name.
2026-08-21 20:20:44 +02:00
Bartosz Taudul
db851737e0 Prevent ini settings from reversing sortable table column order.
The imgui 1.92.9b ini writer omits 'Column N' lines for columns without
saved data (e.g. the unsized, unsorted 'Zone' column of the child zones
table, which only persists the default Time sort). On restore, the
missing entry keeps Index == -1, the sequential fallback in
TableLoadSettingsForColumns() assigns it to the remaining column, and
TableFixDisplayOrder() sorts DisplayOrder -1 to the end - flipping the
column order (Time | Zone in the zone info child list) for the session.
Guard the non-reorderable load path against phantom entries.

Upstream: https://github.com/ocornut/imgui/issues/9519
2026-08-21 20:20:42 +02:00
Bartosz Taudul
73917aaef5 Sort single-member child groups by zone name in the zone info window.
The group name-sort key was always the source location name, but
single-member groups display the zone-level name, which prefers the
runtime ZoneName() string. Such groups sorted by a name the user could
not see. The key now mirrors the display: zone name for single-member
groups, source location name for the rest. GPU zones are unaffected
(their names always resolve from the source location).
2026-08-21 02:03:50 +02:00
Bartosz Taudul
f0fce23bb6 Allow sorting child zones by name in the zone info window.
The child zone list was always sorted by time. It is now a sortable
table with Zone and Time columns, like the statistics view and the
time distribution table in the same window: Time is the default sort
(descending, as before), Zone sorts by name with time as the
tiebreak, and groups plus their members follow the same selection.
The sort choice persists across zones and the group toggle. Group
and child names are looked up once per draw only when name-sorting.
2026-08-21 02:03:50 +02:00
Bartosz Taudul
ad7a1744fa Add web gui test skill. 2026-08-21 02:03:47 +02:00
Bartosz Taudul
62bffd085c Merge pull request #1458 from TravisGesslein/master
D3D12: pass an empty written range when unmapping the readback buffer
2026-08-20 20:46:16 +02:00
Travis Gesslein
f8a75703f9 D3D12: pass an empty written range when unmapping the readback buffer
Otherwise on unmap you can get the validation layer error:

"ID3D12Resource3::ID3D12Resource::Unmap: pWrittenRange does not point to an empty D3D12_RANGE and
the heap type is D3D12_HEAP_TYPE_READBACK. Readback resources can be written by the CPU but there's
not much utility. The rationale is that readback heaps are stuck in COPY_DEST state such that the
GPU can never use what the CPU is writing. The range [0, 524288) should be empty (Begin >= End)."

Check also https://learn.microsoft.com/en-us/windows/win32/api/d3d12/nf-d3d12-id3d12resource-unmap

"This indicates the region the CPU might have modified" -> tracy essentially declares the entire range as written, but it's not even a cpu->gpu write buffer -> bug.
2026-08-20 17:36:39 +02:00
Bartosz Taudul
e3cde055cd Export Tracy version in the installed CMake package.
TracyConfig.cmake now sets TRACY_VERSION_STRING and a
TracyConfigVersion.cmake is generated and installed, so
find_package(Tracy X.Y CONFIG) works against installed trees.
Compatibility mode is SameMinorVersion: for a 0.x project the minor
version is the compatibility unit, and consumers who need to pin the
exact version can use find_package(... EXACT). SamePatchVersion is
not an option - it requires CMake 4.4 while the project minimum is
3.13.
2026-08-20 01:03:49 +02:00
Bartosz Taudul
0e37b94e6c Add TracyAssert.hpp to CMake / Meson. 2026-08-20 01:03:47 +02:00
Bartosz Taudul
6b58f7d8d0 Report system tracing setup failures.
SysTraceStart previously reported success even when no perf events
could be opened, leaving a worker thread running over no buffers
and an empty CPU section without explanation; it now fails in that
case. Also log when /proc/kallsyms cannot be read, which otherwise
silently results in ??? kernel stack frames.
2026-08-19 22:18:59 +02:00
Bartosz Taudul
05f0508684 Fix system tracing on kernels older than 4.1.
perf_event_attr.use_clockid only exists since Linux 4.1 and
sample_max_stack since Linux 4.8; older kernels (e.g. the 3.18
kernels of 32-bit Android devices) reject the attributes with
EINVAL/E2BIG, which silently disabled all of system tracing.
Classify the running kernel from uname(2) and only send the fields
its perf_event_open ABI supports.
2026-08-19 22:18:57 +02:00
Bartosz Taudul
e2c05210e7 Add a note about zones crossing multiple on-demand connections. 2026-08-19 20:30:42 +02:00
Bartosz Taudul
275fef576a Bring Lua on-demand connection matching logic up-to-date with C++ side. 2026-08-19 20:30:40 +02:00
Bartosz Taudul
2a3792aaa3 Handle source code queries in no-callstack builds.
The symbol worker thread, which answers source code queries, only exists
when call stack support is compiled in. Handle the query directly in the
main thread so it is always answered and the server can terminate.
2026-08-19 17:54:44 +02:00
Bartosz Taudul
ad99139f8e Include TracyWebGPU.hpp in CMakeLists.txt and meson.build. 2026-08-18 23:20:01 +02:00
Bartosz Taudul
fee9285d68 Use the compiler builtin for the ARM thread id.
The mrc to c13/c0/3 (thread id register) is undefined on ARMv5 (e.g.
ARM926EJ-S) and SIGILL'd during init. __builtin_thread_pointer lowers
to the same mrc on targets with a thread pointer register and to
__aeabi_read_tp on soft-TLS targets.
2026-08-18 22:35:44 +02:00
Bartosz Taudul
a4b51631ed Regenerate markdown manual. 2026-08-18 18:41:32 +02:00
Bartosz Taudul
433ac3629f Document that manual lifetime requires profiler shutdown.
https://github.com/wolfpld/tracy/issues/483
2026-08-18 18:39:45 +02:00
Bartosz Taudul
16d9c15663 Include TracyMangle.hpp in TracyDebug.hpp for consistent delayed init state. 2026-08-18 00:30:28 +02:00
Bartosz Taudul
4e9121eb2f Typos. 2026-08-18 00:28:32 +02:00
Bartosz Taudul
7f7bd69195 Remove TRACY_DELAYED_INIT option.
Delayed init was introduced 2019-02-19 in ef5e30056 and was lazy init at
that time, matching the name.

This changed 2020-05-19 in 4eb78f5c8, when auto-init was added, making
delayed init not delayed anymore.

While the option is still needed to make manual lifetime work, and it has
to be enabled on apple, there's no reason anymore to expose it to users.
2026-08-18 00:26:24 +02:00
Bartosz Taudul
2a7542ccd8 Mangle the GetToken accessor based on config.
The macro fast paths only reference GetProfiler from -O0 code, direct
calls, and ON_DEMAND builds: the single GetProfiler() call on the macro
path sits behind the constexpr-dead callstack guard and is folded away
at -O1+, so Release builds carried no config fingerprint and mismatched
clients linked silently.

GetToken is the hot-path binding of every queueing macro and is
referenced at all optimization levels; mangle it the same way so
mismatches fail at link time in Release builds too.
2026-08-17 22:21:57 +02:00
Bartosz Taudul
328f50211f Link TracyImGui library with libdl.
ImGui OpenGL loader uses dlopen / dlsym / dlclose to load the OpenGL
implementation.

Linking with libdl is not needed with glibc >= 2.34 (released in 2021):

NEWS for version 2.34
=====================

Major new features:

* In order to support smoother in-place-upgrades and to simplify
  the implementation of the runtime all functionality formerly
  implemented in the libraries libpthread, libdl, libutil, libanl has
  been integrated into libc.  New applications do not need to link with
  -lpthread, -ldl, -lutil, -lanl anymore.

(...)

Older glibc versions and also possibly other libc implementations still
need the libdl link.
2026-08-17 22:13:59 +02:00
Bartosz Taudul
f5c53af367 Fix plot color R/B channel swap. 2026-08-17 00:17:19 +02:00
Bartosz Taudul
b751c1c8d0 Fix csvexport --sep and --filter options. 2026-08-16 23:41:09 +02:00
Bartosz Taudul
b6cfdd2d2e Update embed.tracy to 0.14.
Previous was 0.10, which was too old after bumping minver to 0.11.
2026-08-16 15:26:10 +02:00
Bartosz Taudul
453d649c1a Add TRACY_ASSERT for custom assert implementations. 2026-08-16 13:32:44 +02:00
Bartosz Taudul
254f0ad210 Drop support for pre 0.11 traces. 2026-08-16 00:27:30 +02:00
Bartosz Taudul
3f9714059a Add protocol history for 0.14. 2026-08-16 00:04:13 +02:00
Bartosz Taudul
8fdfd08af0 Refactor GPU context id handling.
The gpu backends each used a bare 255 as the sentinel for a
not-yet-initialized context id. Define InvalidGpuContextId (-1) in
TracyQueue.hpp and replace the scattered 255s and their asserts
with it.

Id exhaustion is handled by an error message and an assert in the
new NextGpuContextId() getter. Non-assert builds continue execution,
at which point they are no longer valid. Handling this code path
is out of scope here. Various attempts at handling the exhaustion
problem have been otherwise purged from the API implementations.
2026-08-15 21:46:29 +02:00
Bartosz Taudul
54ecee1f74 Fix memory events not being attributed to fibers.
Alloc and free events stored the OS thread, so the memory view's
Zone alloc/Zone free columns resolved the zone on the OS thread's
timeline instead of the currently-running fiber. Attribute them to
the active fiber the same way zone and message events are
(follow ThreadData::fiber).
2026-08-15 15:15:54 +02:00
Bartosz Taudul
b10d466a38 Regenerate markdown manual. 2026-08-15 13:41:23 +02:00
Bartosz Taudul
86fc7f02cb Document the need to reflect order of allocations in reported events. 2026-08-15 13:21:11 +02:00
Bartosz Taudul
16805b47ef Update TRACY_IGNORE_MEMORY_FAULTS description in CMake/Meson options. 2026-08-15 13:08:44 +02:00
Bartosz Taudul
756411d802 Allow disabling wait stacks capture. 2026-08-14 01:28:22 +02:00
Bartosz Taudul
d579ed3cce Fix VSync on 32-bit win32 targets.
The struct sizes are kernel sizes. A 64-bit kernel is required for
tracing anyway.
2026-08-13 18:34:58 +02:00
Bartosz Taudul
edc09a4e97 Fix 16-bit string length desync on offset recovery
The decoder recovered offset-encoded 16-bit string lengths into a
uint16_t, so sz += ProtocolOffset8Bit truncated lengths in [65536,
65791] back into [0, 255] in release builds, desynchronizing the
stream. Read the wire value into uint16_t sz16 and recover into a
uint32_t sz, relying on automatic promotion for the addition.

Align the client asserts with the encoder's actual capacity
(ProtocolOffset8Bit + uint16 max).
2026-08-13 18:18:58 +02:00
Bartosz Taudul
821d43c6b5 Send string for SectionSetup during on-demand deferred messages replay. 2026-08-13 18:15:20 +02:00
Bartosz Taudul
16329e0a0c Silence MSVC warning C4366.
warning C4366: The result of the unary '&' operator may be unaligned
2026-08-10 18:16:39 +02:00
Bartosz Taudul
099df3de3d Release 0.14.0. 2026-08-09 21:06:31 +02:00
Bartosz Taudul
a7926283a8 Wait for capture workers to disconnect before saving. 2026-08-09 18:32:17 +02:00
Bartosz Taudul
6ce8ad7aff CI: centralize cmake --parallel flag for linux workflow
Define CMAKE_PARALLEL once per environment (--parallel 2 on GitHub CI,
unlimited locally) and use it across every cmake build, so all builds
are OOM-protected, not just the profiler GUI.
2026-08-09 12:46:43 +02:00
Bartosz Taudul
37a7c2c197 Add permissions and concurrency groups to all workflows
Set contents: read permissions on every workflow and add per-workflow
concurrency groups keyed on the git ref to deduplicate concurrent runs.
Release workflow keeps cancel-in-progress: false so a release build is
never canceled; job-level contents: write on attach-to-release is
preserved. Concurrency groups use hardcoded prefixes so reusable
workflows called from release.yml do not inherit the caller workflow
name and collide.
2026-08-09 12:46:40 +02:00
Bartosz Taudul
a8f9faf496 Fix parallel flags for profiler GUI build in linux workflow.
GitHub runners: limit to 2 jobs
Non-gimped environments: use all you got.
2026-08-09 12:11:19 +02:00
Bartosz Taudul
f57986e17d Add parallel flag to library build in linux workflow. 2026-08-09 12:11:04 +02:00
Bartosz Taudul
5f647e7eaf Build dyna example multithreaded in linux workflow. 2026-08-09 12:02:20 +02:00
Bartosz Taudul
e06fc5211b No meson/ninja needed in windows workflow. 2026-08-09 11:56:48 +02:00
Bartosz Taudul
01a9e6d3af No version for meson. 2026-08-08 02:15:18 +02:00
Bartosz Taudul
c228c662bb Install TracyVersion.hpp in CMake and meson
Ship the version header alongside the rest of public/common so installed
users can query the Tracy version at compile time; it is the single
source of truth CMake already parses via cmake/version.cmake.
2026-08-08 02:09:08 +02:00
Bartosz Taudul
30d14caab6 Install TracyFormat.h in CMake and meson
TracyFormat.h is transitively included by installed public headers
(TracyC.h, TracyProfiler.hpp, TracyScoped.hpp) but was missing from
the common_includes install list in both build systems, breaking
installed users. Add it to the list in CMakeLists.txt and meson.build.
2026-08-08 02:09:08 +02:00
Bartosz Taudul
5d2c353335 Fix TRACY_NO_CODE_TRANSFER source code transfer. 2026-08-07 20:44:51 +02:00
Bartosz Taudul
92f0e0e951 Make linux-cli workflow compatible with act runner. 2026-08-07 00:28:10 +02:00
Bartosz Taudul
7dafaca602 Stage linux-cli artifact in a relative path for container job 2026-08-07 00:25:13 +02:00
Bartosz Taudul
18eaa221d3 Regenerate markdown manual. 2026-08-06 22:37:24 +02:00
Bartosz Taudul
6bca1141fe Update manual. 2026-08-06 22:37:02 +02:00
Bartosz Taudul
a5d155250b Update NEWS. 2026-08-06 22:37:02 +02:00
Bartosz Taudul
63c94bae7d Strip release binaries on Linux and macOS. 2026-08-06 22:36:59 +02:00
Bartosz Taudul
8163734842 Ship Linux CLI tools in a versioned release zip
Build the CLI tools in a dedicated ubuntu:24.04 workflow and pack them
with the AppImage into a single linux-<version>.zip, matching the
Windows/macOS release shape.
2026-08-06 22:22:09 +02:00
Bartosz Taudul
c119f1c8de Package tracy-capture-daemon in release binaries. 2026-08-06 21:22:02 +02:00
Bartosz Taudul
dcc86d343b Link to cropped guadec video. 2026-08-06 21:21:26 +02:00
Bartosz Taudul
507c77ba41 Move continuation words wider than the leftover whole to the next line.
ImGui's CalcWordWrapPosition() only cuts a word mid-word when it fits on
no line, i.e. when it is wider than the full line width. PrintTextWrapped
passes the leftover width for a glued continuation segment's first line,
so a continuation word wider than the leftover (but fitting on the next
full-width line) was being cut mid-word. Detect that case and move the
whole word to the next line instead.
2026-08-05 01:37:05 +02:00
Bartosz Taudul
eacb64a714 Hack around lack of flag to always enable scrollbar in a table.
https://github.com/ocornut/imgui/issues/9499#issuecomment-5182563922
2026-08-04 19:43:55 +02:00
Bartosz Taudul
bdb0f3c3d7 Replace vertical / horizontal AddLine calls with AddLineV / AddLineH. 2026-08-04 19:00:59 +02:00
Bartosz Taudul
757c2fff30 Grammar. 2026-08-03 22:23:19 +02:00
Bartosz Taudul
a6d96d4201 Ignore all-newline message chunks at the beginning of LLM content.
DeepSeek 4 Flash likes to send "\n\n" as content between the reasoning
and tool call sections.
2026-08-03 22:23:03 +02:00
Bartosz Taudul
1c2a8b4c40 Filter out thinking, tool calls, attachments from the summary and suggestion. 2026-08-03 22:23:03 +02:00
Bartosz Taudul
6dd09e3ed8 Make the tool reply limit easier to understand. 2026-08-03 20:09:24 +02:00
Bartosz Taudul
96dcd7a38b Source tooltip may render more than one active line. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
0290f0ba54 Give instructions for providing source file ranges. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
1866cf25b9 Support extracting lines range. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
d91769b72e Check if there's a parent directory to go to on .. in NormalizePath. 2026-08-03 18:26:49 +02:00
Bartosz Taudul
d0ffd8dc86 Merge pull request #1448 from 17steen/fix/cuda-defer-gpu-new-context
Defer CUDA GpuNewContext for on-demand connections
2026-08-03 12:31:21 +02:00
Robin Oger
5df9ac73c7 Defer CUDA GpuNewContext for on-demand connections.
CUDACtx's constructor writes GpuNewContext directly through
QueueSerialFinish(), unlike every other GPU backend (Vulkan, OpenGL,
D3D11/12, Metal, WebGPU, Rocprof), which all defer it via
GetProfiler().DeferItem() so it survives on-demand's per-connection
queue clear.

A profiler connecting any time after the CUDA context is created (in
practice: any time after process start) never receives GpuNewContext.
The GpuContextName message that Name() sends right after (already
correctly deferred) then crashes the server's
Worker::ProcessGpuContextName with an unregistered context id
(assert(ctx) fails; undefined behavior in release builds).

Same fix already applied to the Rocprof backend in #1336. Fixes #1171.

Includes a repro test under tests/cuda/repro/on_demand/, mirroring the
structure #1336 added for Rocprof: a minimal CUDA program that creates
an on-demand context (repro.cu/CMakeLists.txt), and a check_gpu_zones
tool that loads the resulting .tracy file and verifies the GPU context
was named and populated with zones. Verified locally: unpatched
tracy-capture crashes on the first connection attempt; patched, three
consecutive connect/disconnect cycles all succeed and check_gpu_zones
reports a named context with recorded zones.
2026-08-03 10:48:20 +02:00
Bartosz Taudul
facbd3c0a7 Add connectionId protection to GPU zone scopes. 2026-08-02 20:44:48 +02:00
Bartosz Taudul
df29943d78 Add TRACY_ON_DEMAND guards to C API GPU functions. 2026-08-02 20:44:46 +02:00
Bartosz Taudul
9209bcdafd Add connectionId protection to C API zone functions. 2026-08-02 19:57:13 +02:00
Bartosz Taudul
8c8d451a2d Merge pull request #1446 from nyanpasu64/fork
Add formatted zone text/name macros to C API
2026-08-02 11:51:53 +02:00
nyanpasu64
bfdf647114 Rename TracyFormat.hpp to .h since it's used in the C API 2026-08-01 18:19:01 -07:00
nyanpasu64
0558301e49 Address LLM review comments 2026-08-01 18:15:27 -07:00
nyanpasu64
71098f707c TracyCZoneTextF/TracyCZoneNameF 2026-08-01 16:27:55 -07:00
Bartosz Taudul
a1fb20e620 Initialize uninitialized View members 2026-08-01 13:51:47 +02:00
Bartosz Taudul
9e163ad6b9 Remove dead vertical timeline centering toggle 2026-08-01 13:42:51 +02:00
Bartosz Taudul
93a8f86850 Use the mouse event abstraction in timeline click handlers 2026-08-01 13:27:43 +02:00
Bartosz Taudul
5698b45820 Hold worker data lock for the whole draw frame 2026-08-01 12:24:17 +02:00
Bartosz Taudul
95811a658a Save drawFrameTargets and plotHeight as configurable defaults 2026-08-01 11:52:00 +02:00
Bartosz Taudul
37468811c7 Declare ___tracy_get_time with explicit void parameter list. 2026-08-01 11:37:36 +02:00
Bartosz Taudul
a6352890f4 Check buffer bounds before accessing the buffer. 2026-08-01 02:17:59 +02:00
Bartosz Taudul
07b7219498 Use proper buffer size for program name normalization. 2026-08-01 02:15:29 +02:00
Bartosz Taudul
370a41225e The suggested llm query should be from the user's perspective. 2026-08-01 01:42:59 +02:00
Bartosz Taudul
1ad64f19a7 Use ctx.scale in timeline items instead of GetScale(). 2026-08-01 01:38:18 +02:00
Bartosz Taudul
93af7f987a Fix data race on pending thread hints. 2026-08-01 01:38:16 +02:00
Bartosz Taudul
144fec96d3 Do not scan source length over the destination size. 2026-08-01 00:59:29 +02:00
Bartosz Taudul
3448832b72 Replace impactful cases of strcpy with strzcpy. 2026-08-01 00:37:35 +02:00
Bartosz Taudul
c136232c97 Use strzcpy in client library. 2026-08-01 00:06:39 +02:00
Bartosz Taudul
7ee384630b Do not use strncpy in trace info window. 2026-08-01 00:06:39 +02:00
Bartosz Taudul
5ef415619d Add safe string copy. 2026-08-01 00:04:02 +02:00
Bartosz Taudul
8985887128 Don't include GUI profiler deps for all other tools. 2026-07-31 22:01:52 +02:00
Bartosz Taudul
bf21d8ea11 Bump ImGui to 1.92.9b-docking. 2026-07-31 18:12:14 +02:00
Bartosz Taudul
bc85c4faa5 Fix include guard. 2026-07-31 18:12:13 +02:00
Bartosz Taudul
8fd29b1038 Merge pull request #1441 from alandtse/fix/mcp-live-memory-limit
fix(mcp): cap live sessions to avoid OOM
2026-07-31 13:03:45 +02:00
Bartosz Taudul
d46fe6fd9f Merge pull request #1432 from alandtse/feat/mcp-crash-diagnostics
fix(mcp): recover from hangs and evict idle files
2026-07-31 13:03:12 +02:00
Alan Tse
613524247f docs(mcp): document file-idle eviction TTL
The guide claimed "two backstops" and only described the LRU cap
and the disconnected-live TTL, omitting the new file-idle TTL
entirely -- a cold agent reading it would have no idea idle
file-loaded captures now expire too, or what env var controls it.
2026-07-30 22:07:54 -07:00
Alan Tse
a418ec6a03 fix(mcp): evict idle file-loaded captures too
Automatic eviction only ever covered disconnected live instances
(_evict_disconnected_idle explicitly skipped anything with a
path), so a file-loaded capture -- potentially many GB -- stayed
resident forever until the TRACY_MCP_MAX_INSTANCES cap forced an
LRU eviction to make room. list_instances already documented
"unload_capture instead of waiting for automatic eviction" as the
alternative, but no automatic path actually existed for this case.

Generalizes the sweep into _evict_idle: file-loaded instances now
get their own idle-since-last-use TTL (TRACY_MCP_FILE_IDLE_TTL_S,
default 1800s matching the disconnected-live TTL). Safe to evict
on a timer since they're already durably on disk -- load_capture
brings them back. Connected live instances are untouched, same as
before.
2026-07-30 22:07:33 -07:00
Alan Tse
00c2245c49 fix(mcp): recover cleanly from a hung server
Addresses the "server just vanishes" pattern from
alandtse/tracy#2, where Unable to connect gives no way to tell a
segfault apart from a hang and required manually killing an
unresponsive process before restart.

- faulthandler.enable() at startup writes a thread-state
  traceback to tracy_mcp.crash.log on a genuine fatal crash
  (works on Windows via SetUnhandledExceptionFilter).
- The periodic sweep loop now also logs a heartbeat (uptime,
  instance/task counts, evictions) so a hung event loop is
  distinguishable from a dead process by the last timestamp on
  disk.
- _is_our_server_running() now backs its os.kill(pid, 0) check
  with an HTTP self-ping. A deadlocked-but-alive process passed
  the old PID-only check, silently blocking restart; a
  non-responsive server is now reported by PID and a fresh
  instance starts on a new port instead.
2026-07-30 22:06:57 -07:00
Alan Tse
e4fab6af3a fix(mcp): cap live sessions to avoid OOM
live_connect wrapped Worker(addr, port) with no memoryLimit, so
the binding default (-1, unlimited) applied. A long-lived live
session on a busy target grows unbounded -- every zone/message/
memory event stays resident until disconnect -- and can OOM-kill
the whole server process rather than just that one instance.

Worker already has a graceful cap: TracyWorker.cpp's receive loop
checks memoryLimit and calls QueryTerminate() + a clean disconnect
once exceeded, instead of continuing to grow. Just wasn't wired up
from Python.

Adds memory_limit_mb to live_connect (defaults to
TRACY_MCP_LIVE_MEMORY_LIMIT_MB, 8192 if unset; 0 disables),
converts to bytes for the Worker constructor, and reports the
active limit in the connect response.
2026-07-30 21:48:06 -07:00
Bartosz Taudul
b418abd8de Merge pull request #1439 from ivanstepanovftw/fix-dwarf-npd
fix #1438 null dereference in dwarf
2026-07-30 19:25:41 +02:00
Ivan Stepanov
1673a305cf fix #1438 null dereference in dwarf 2026-07-30 14:33:49 +02:00
Bartosz Taudul
3baa2f0a09 Fix typo. 2026-07-29 18:57:03 +02:00
Bartosz Taudul
46b21c0c75 Update ARM CPU identifiers. 2026-07-29 18:54:10 +02:00
Bartosz Taudul
a02513c1a2 Update Tim Apple devices list. 2026-07-29 15:18:46 +02:00
Bartosz Taudul
ead931ef3c Safeguard against zero count entries in statistics list.
There is a legitimate, if rare, way to get nonReentrantCount == 0 with
total != 0 — an outer zone that never terminated before the capture ended,
whose reentrant inner zone did terminate. The outer never contributes to
slz (no end event), but it did increment the stack count, so the inner one
is correctly recorded as a reentry.
2026-07-28 23:49:01 +02:00
Bartosz Taudul
6b2abcb47a Properly set countMap to 0 when loading a trace.
The live capture code path already does the equivalent thing.
2026-07-28 23:47:49 +02:00
Bartosz Taudul
e0862e8534 Disallow implicit StringIdx construction. 2026-07-28 22:33:26 +02:00
Bartosz Taudul
ec0a1c0606 Update manual. 2026-07-28 20:52:41 +02:00
Bartosz Taudul
372b83d590 Update NEWS. 2026-07-28 20:48:14 +02:00
Bartosz Taudul
4779856174 Shorten image names in flame graph blocks. 2026-07-28 20:36:06 +02:00
Bartosz Taudul
c4b1308c21 Use image name shorten helper. 2026-07-28 20:36:06 +02:00
Bartosz Taudul
488270c728 Add image name shortening helper. 2026-07-28 20:28:27 +02:00
Bartosz Taudul
ff81feedca Add tooltip for unknown flame graph blocks. 2026-07-28 20:25:39 +02:00
Bartosz Taudul
b677b352ee Show image names in flame graph unknown items. 2026-07-28 20:09:13 +02:00
Bartosz Taudul
8b836a5d56 Display unknown frames on flame graph. 2026-07-28 19:27:30 +02:00
Bartosz Taudul
ee54c04d3b Don't display empty tooltip in no-symbol-data case. 2026-07-28 19:26:57 +02:00
Bartosz Taudul
fa8bd1adf8 Fix grouping by name when merging flame graphs. 2026-07-28 19:25:35 +02:00
Bartosz Taudul
987d440d74 Merge pull request #1435 from damcclos/damcclos/client-ordering
Ordering the clients in the connection dialog to prevent jumping in the list. Ordering by time still caused jumps.
2026-07-28 17:46:29 +02:00
David McCloskey
064d400cf1 Ordering the clients in the connection dialog to prevent jumping in the list. Ordering by time still caused jumps. 2026-07-27 15:33:14 -05:00
Bartosz Taudul
d3af48990c Change "hide unknown" check from unknown file name to unknown function name.
Knowing that function "memcpy" took a certain amount of time is very
actionable, even if the source file name (from libc) is not available.
2026-07-27 22:07:34 +02:00
Bartosz Taudul
36d93e27c4 Add a table documenting client build options to the user manual. 2026-07-27 19:58:28 +02:00
Bartosz Taudul
3a65b27838 Update NEWS. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
71c98c9dfb Ignoring mem faults also ignores repeated allocation for the same address. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
12fdc7d8d2 Extract recording free event in MemData. 2026-07-27 19:33:55 +02:00
Bartosz Taudul
9481319d4c Regenerate markdown manual. 2026-07-26 19:00:15 +02:00
Bartosz Taudul
3c88115926 Update NEWS. 2026-07-26 19:00:15 +02:00
Bartosz Taudul
ed5f333f90 Update manual. 2026-07-26 18:59:26 +02:00
Bartosz Taudul
a1ef58efac Remove an unreachable leaf flag ternary.
The tree node is only drawn when the count is greater than zero, so the
leaf flag could never be selected. Use the plain TreeNode call.
2026-07-26 18:59:25 +02:00
Bartosz Taudul
24b2702977 Track the maximum of combined sample costs for hotness colors.
The child calls display normalized hotness colors by the sum of two
independently tracked maxima, which can occur on different addresses.
The denominator could therefore exceed any real combined cost, so
nothing reached full heat and the color scale changed meaning when the
child calls display was toggled. Track the maximum of the per-address
sums instead.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
9b3c6784fb Allow opening entry stacks for symbols with only inclusive counts.
Rows in the sampling statistics were only clickable when the symbol had
exclusive samples. In the "with children" accumulation mode this made
symbols which never directly executed, such as dispatchers or wrappers
with all cost in their callees, visible but completely inert, even
though the entry stacks window can now display how such symbols were
reached. Base the interactivity on the count shown in the current
accumulation mode. The popup menu items already disable themselves when
their data is not available.

The inline function expansion follows the same rule, so expanding such
a symbol now lists its inline functions with their inclusive counts
instead of showing an empty tree.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
26d61dfdba Make find zone sample percentages relative to the matched zones.
The find zone samples list shares the drawing code with the sampling
statistics window, which computed the percentage denominator from its
own state: the whole-trace sample count, or the statistics range
filter, if one was active. The find zone counts are scoped to the
matched zones, so the percentages mixed two meanings in one table: the
time column was relative to the zone selection while the count column
was relative to the whole trace, and changing the range filter in the
statistics window silently rescaled it.

Pass the denominator from the caller. Find zone sums its zone-scoped
counts, so both columns are now relative to the selection, and the
statistics window computes the same denominator as before.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
c2c1c4e2b7 Ignore the statistics range filter in the disassembly LLM tool.
The symbol disassembly tool scoped its cost data to the statistics
range filter. This is invisible UI state which the model cannot see, so
range-limited numbers were indistinguishable from whole-trace figures
and could silently change between tool calls. The other sampling tools
always report whole-trace data; do the same here.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
a429242f0f Exclude context switch samples from zone call stack reconstruction.
The heuristic reconstruction picks the call stack from the samples
within the zone's time span. Context switch samples are always parked
at the scheduler, so for zones which spent their time blocked they
dominated the root selection and the reconstructed stack showed the
scheduler path instead of the zone's real call stack. A zone covering
only context switch samples now reconstructs nothing instead.
2026-07-26 18:55:09 +02:00
Bartosz Taudul
2d5e397c9f Exclude context switch samples from the sampling flame graph.
Context switch samples are excluded from the sampling statistics, but
the flame graph built from thread samples included them. Threads which
spend time blocked accumulated large scheduler towers which none of the
other sampling views show, and the flame graph totals did not
correspond to the statistics for the same trace. Filter the samples the
same way the trace load jobs do.
2026-07-26 18:55:08 +02:00
Bartosz Taudul
b743c13695 Exclude context switch samples from the LLM sampling stats total.
The per-symbol exclusive counts reported by the tool come from the
sampling statistics, which exclude context switch samples, but the
total time reference was computed from the full sample count. This
inflated the total and made every symbol look proportionally cheaper
to the model. Use the same denominator as the statistics window.
2026-07-26 18:55:06 +02:00
Bartosz Taudul
52e31be7f4 Do not touch the thread compression cache in a load-time job.
The job which builds the symbol samples and child samples maps called
CompressThread, which updates the lookup cache and can insert into the
compression map, while the timeline processing job concurrently reads
the map, deliberately using the raw lookup to avoid this exact hazard.
All threads are already present in the compression data loaded from the
trace, so use the raw lookup as well.
2026-07-25 23:02:14 +02:00
Bartosz Taudul
f51b984edb Guard against missing symbol data for the top inline name.
When displaying the hottest inline function's name in place of the base
symbol name, the symbol map lookup was dereferenced without checking
for a missing entry, crashing in release builds. Keep the base symbol
name when the inline symbol has no symbol data.
2026-07-25 23:02:14 +02:00
Bartosz Taudul
8da27d6042 Gate sampling data accessors on background processing completion.
Several code paths read the per-symbol sample lists, symbol statistics
or child sample data without checking the readiness flags. These
structures are populated by background jobs during trace load, and the
flags are the only synchronization mechanism, so reading early races
the jobs and trips the readiness asserts in debug builds. The find zone
samples list checked a different flag than the data it reads requires,
and two symbol view conditions called the accessor before the readiness
check made elsewhere in the same function. The trace information window
performed no check at all, while it is typically open during loading.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
74b08cf5c6 Clarify the sampling percentage denominator comment.
The denominator includes samples with unresolved call stacks and
samples belonging to filtered-out rows, so describing it as the number
of samples attributable to the displayed symbols was overstating what
the code does.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
b3de7f34fb Require symbol data for the entry stacks button in symbol view.
The button was gated on the entry stack maps being non-empty, but the
sample entry stacks window also needs the symbol data to display
anything. Without it, clicking the button silently closed the window
through its null guard. Match the gate used by the statistics window
menu items.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
dd8eff0de6 Guard relative inline percentages against an empty base count.
Since the inclusive count of an aggregated symbol entry is taken from
the base symbol's own statistics, a row whose base symbol could not be
resolved can display an inclusive count of zero while its inline
functions have nonzero counts. With the relative inline display active,
the per-inline percentages divided by the base count, printing infinity
in such cases. The time percentage variant divided by the same count
scaled by the sampling period, which cancels out, so both variants can
share the guarded reciprocal.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
46c70fcbb8 Do not mark sorted vectors as unsorted on equal elements.
SortedVector considered an appended element equal to the last one to
break the ordering, marking the vector as unsorted. A non-decreasing
sequence is sorted, so only a strictly smaller element has to trigger
the marker. Equal keys are common: child sample vectors receive
identical timestamps whenever a recursive call stack contains the same
call site twice, which flipped the vectors to unsorted on virtually
every recursive workload and caused the lazy sort in GetChildSamples to
run over and over again while holding the data lock.

The lazy sort machinery handles duplicate keys correctly, as both the
prefix and tail merge windows are computed with lower bounds.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
97d6b5f9fa Guard sampling percentage denominators against underflow.
The denominator subtracts context switch sample counts from total
sample counts, both for the whole trace and within the range filter.
The subtraction could underflow: on traces with inconsistent sample
ordering the binary searches over the unsorted sample vectors can
return too few samples, and a sample which arrives with a duplicated
timestamp is merged into the existing entry while its context switch
classification is still recorded, so the context switch samples are not
a strict subset of the samples. An underflow made every percentage in
the table display as zero.

Clamp the subtraction, per thread in range mode, so that one thread
with bad data does not affect the counts of the others.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
b68fe43335 Keep context switch sample vectors sorted.
Context switch samples were appended in arrival order. Samples which
were postponed due to missing context switch data are replayed after
newer samples were already classified, so the vector could become
unordered. Everything that reads it assumes time order: the wait stacks
range filter, the sampling statistics percentage denominator, and the
context switch sample filters in the trace load jobs. In the load jobs
an unordered vector could silently disable the filtering for the rest
of a thread, reintroducing the context switch samples into the symbol
and child sample maps.

Use a SortedVector and restore the ordering at the points where it can
break: after the postponed sample replay and when saving a trace. The
save file version is bumped, so that the sort order check on load is
only performed for traces saved by previous versions.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
5563461201 Extend the symbol parents LLM tool with the reached modes.
The tool only reported "was executing" entry stacks, while the sample
entry stacks window now defaults to showing the stacks through which a
symbol was reached. The model was told there is no data for symbols
which never executed directly, even when the window right next to it
displayed their entry stacks.

Add an optional "mode" parameter with the "reached" (non-reentrant),
"reached_recursive" and "executing" values. The default matches the UI
default. Inline symbols are now accepted, as the reached maps cover
them directly, and the executing mode can use their exact statistics.
The reported mode is included in the tool output, and the chat view
shows it in the tool call label.

The tool description keeps the mode summaries short. The detailed
guidance on choosing a mode is in the optimization skill, so it only
occupies context when the skill is loaded.
2026-07-25 23:02:13 +02:00
Bartosz Taudul
851b1f80f1 Take the data lock in LLM tools which access worker state.
LLM tool calls execute on the LLM worker thread, while the render
thread holds the worker data lock for the duration of each frame and
the network thread mutates worker data under the same lock during live
capture. The tools accessed worker data with no synchronization at all.
Some of the accessors also lazily modify worker state, such as the
postponed symbol list sorting or the lazy sorting of child sample
vectors, so this raced even in a fully loaded trace.

Take the data lock in the five tools which access worker state. No LLM
locks are held while tools execute, so no lock ordering cycle with the
render thread is possible. Tools which perform network requests do not
touch worker data and remain lockless, as holding the lock across a
network transfer would stall the profiler.

The background jobs which compute sampling statistics during trace load
do not hold the data lock. The readiness flags are the synchronization
mechanism there, so gate the sampling tools on them. This also matches
the readiness asserts in the worker accessors these tools call.
2026-07-25 23:02:10 +02:00
Bartosz Taudul
507f2ae8b5 Update NEWS. 2026-07-25 19:46:02 +02:00
Bartosz Taudul
941669c1c6 Base sampling statistics percentages on real sample counts.
With an active range filter, the percentage denominator was a
theoretical capacity estimate: the number of samples a single thread
would produce if it ran continuously through the range at the nominal
sampling rate. Actual sample volume scales with total CPU occupancy
across all threads, so the estimate was off in either direction: on
multi-core workloads percentages were inflated several times over and
could exceed 100%, while on mostly idle workloads they were deflated.
Toggling the range filter also silently changed what the percentages
meant, as the whole-trace mode divides by the collected sample count.

Count the samples actually present in the range instead, and exclude
context switch samples from both denominators, as they cannot be
attributed to any displayed symbol. Percentages now mean the same thing
with and without a range filter: the share of attributable samples.

Time percentages are intentionally unchanged. They are normalized by
wall clock time in both modes, where exceeding 100% legitimately means
more than one core was busy.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
c880163f96 Sort child sample vectors lazily.
During live capture, child samples were appended to their per-address
vectors in arrival order. Samples postponed due to missing context
switch data are replayed after newer samples were already processed, so
the vectors could become unordered. All range-limited queries binary
search these vectors by time and would silently return wrong results.
Inserting in sorted order at collection time would require a mid-vector
insertion for every stack frame of every replayed sample, so instead
the vectors are now SortedVector and are sorted lazily when accessed,
following what the inline symbol list already does. This also restores
proper query results for traces with inconsistent sample order.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
3293e263c6 Exclude context switch samples from symbol and child sample maps.
Context switch samples are excluded from sampling statistics, as they
are not produced by the statistical profiling timer and their stacks
are always parked at the scheduler. During live capture this exclusion
is structural, as such samples never enter the statistics processing
path. On trace load, however, only the job which computes symbol
statistics and the instruction pointer map filtered them out. The job
which builds the per-symbol sample lists and the child sample map did
not, so on a loaded trace these two structures included tens of
thousands of context switch samples that a live session would not
count.

This made range-limited statistics counts exceed the whole-trace
counts, inflated child sample costs in the symbol view, and caused the
same trace to show different numbers live and after a save and reload.
On the test trace, __schedule reported 56 exclusive samples but 32747
entries in its sample list.

Apply the same context switch sample filter when building the symbol
samples and child samples maps.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
6063e22bc7 Fix inclusive counts of symbols with inlined functions.
When inline functions are aggregated into their base symbols in the
sampling statistics, the inclusive counts were summed up, the same as
the exclusive counts. This is wrong. Exclusive counts partition the
samples, as each sample is attributed to exactly one symbol. Inclusive
counts overlap: an inline function can only ever be on the stack within
a frame group whose base is its parent symbol, so every sample counted
for an inline function is also counted for the base symbol. Summing
therefore counted the same sample multiple times, inflating the counts,
the sort order and the displayed percentages, in the worst observed
case by a factor of 7.

The base symbol's own inclusive count is exactly the correct value for
the aggregated entry, so use it directly.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
8711f87cb5 Disable entry stacks menu item when there is no data to show.
The statistics window can display rows for which the sample entry
stacks window cannot show anything: symbols with no symbol data,
symbols that only have range-mode sample counts without callstack
statistics, or any row while the statistics are still being computed
in the background. Grey out the menu item in such cases, following
what the "View symbol" item already does.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
34587e6b4c Only show entry stacks button in symbol view when data is available.
The button was displayed unconditionally, allowing the sample entry
stacks window to be opened for symbols without any sample data. Gate it
on the availability of callstack sample statistics and a non-empty
reached map, following what the call stack window does. Checking for
statistics readiness also prevents asserting when the button is used
while a trace is still being loaded in the background.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
d5760a05ae Do not crash on missing symbol data in sample entry stacks window.
The window can be opened for a symbol which has no symbol stats or no
symbol data. For example, the statistics window can produce clickable
rows for symbols that are not present in the symbol map. Close the
window instead of dereferencing null pointers.
2026-07-25 19:46:01 +02:00
Bartosz Taudul
bacfbccb97 Use reached stats for entry stacks access in assembly view.
The instruction tooltip advertised the number of "was executing" entry
call stacks, but middle-clicking opens the sample entry stacks window,
which now defaults to the "was reached" mode. Count the non-reentrant
reached stacks instead, so the tooltip matches what the window shows.

Gating on the reached map also makes entry stacks accessible for
symbols which only ever appear deeper in the call stack. This can
happen when inspecting instructions whose cost consists purely of
child samples.
2026-07-25 19:45:58 +02:00
Bartosz Taudul
2e9e0e2643 Update NEWS. 2026-07-25 17:33:00 +02:00
Bartosz Taudul
fc26cc0f79 Update manual. 2026-07-25 17:30:32 +02:00
Bartosz Taudul
849bc3ae8c Allow entry stacks for symbols that were only reached. 2026-07-25 17:28:07 +02:00
Bartosz Taudul
67ae7d849f Add "was reached" modes to sample entry stacks window. 2026-07-25 17:25:17 +02:00
Bartosz Taudul
1dfcacd802 Collect entry stacks for "was this symbol reached" statistics.
SymbolStats now has two additional entry stack maps, which record the
call stacks below every occurrence of a symbol in a sample, not only
when the symbol was at the top of the stack:

- wasReached counts each occurrence separately. If a symbol re-enters
  itself through recursion, every re-entry adds an entry stack, which
  will itself contain the symbol somewhere below.
- wasReachedNonReentrant counts only the outermost occurrence, so each
  sample contributes exactly once and recursion is ignored. The sum of
  counts in this map equals the symbol's inclusive sample count.

To achieve this, the call stack is walked bottom-up, which makes the
first encountered occurrence of a symbol the outermost one. Symbols at
inline positions get the remaining inline frames of their frame group
as a synthetic frame, mirroring what was already done for the top of
the stack.

The wasExecuting and wasExecutingBase maps are now filled by the same
walk, as its topmost-frame special case. The keys they receive are
identical to what the previous code produced.

Note that there is no "base" variant of the reached maps. A base symbol
is present as the last frame of every frame group that contains its
inline functions, so its wasReached map already covers the whole symbol
at base granularity. Merging in the inline symbols' maps, as done for
wasExecutingBase, would only multi-count the same samples.
2026-07-25 17:02:42 +02:00
Bartosz Taudul
2e23c112bd Extract synthetic call stack interning into a helper. 2026-07-25 16:57:57 +02:00
Bartosz Taudul
d74d77226c Guard against empty synthetic call stacks in parents frame trees.
A sample with a single-frame call stack already produces a zero-length parent
stack, and GetParentsCallstackFrameTree{BottomUp,TopDown} call cs.back() /
cs.front() on it, resulting in UB.
2026-07-25 16:43:43 +02:00
Bartosz Taudul
8f52e0e5f5 Enable clipboard on macOS. 2026-07-25 14:34:24 +02:00
Bartosz Taudul
9db60de03b Do not embed default ImGui font. 2026-07-25 14:30:13 +02:00
Bartosz Taudul
e53c2849fa Bump ImGui to 1.92.9-docking. 2026-07-25 14:29:12 +02:00
Bartosz Taudul
7c30fedaaa Rebuild markdown manual. 2026-07-25 00:19:47 +02:00
Bartosz Taudul
9314b8cbce Update manual. 2026-07-25 00:17:42 +02:00
Bartosz Taudul
ff3e31f564 Update desktop file. 2026-07-24 23:14:09 +02:00
Bartosz Taudul
ac159d6127 Fix RUNPATH in tracy-profiler AppImage
patchelf the stripped binary to /../lib, collapsing the RUNPATH
to the single intended entry. The wayland .pc files baked an absolute
build-host path and a trailing empty component into RUNPATH; the former
leaks the build environment and could shadow bundled libs on the target,
the latter can cause cwd library searches on older glibc.

Add patchelf to the appimage workflow's apt install list.
2026-07-24 20:34:10 +02:00
Bartosz Taudul
bcddc1d9f1 Cache wayland checkout. 2026-07-24 20:34:08 +02:00
Bartosz Taudul
d231c2e95f Correct way to link with wayland libraries. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
b94a1e22c1 Update git ignore list. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
cf82fb9bb0 Disable unused stuff if building libcurl. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
f30d32a908 Add AppImage build script and workflow. 2026-07-24 18:25:05 +02:00
Bartosz Taudul
b7eda34332 Add a warning about minimum supported wayland-client version. 2026-07-24 18:11:42 +02:00
Bartosz Taudul
5036ec9128 Merge pull request #1434 from damcclos/damcclos/thread-lock-time
Add function to calculate thread lock wait time in TracyTimelineItemThread
2026-07-23 22:14:46 +02:00
David McCloskey
59a8ae2d7d Adding documentation 2026-07-23 14:38:56 -05:00
David McCloskey
ea3d0b4d8d Add function to calculate thread lock wait time in TracyTimelineItemThread
This update introduces a new function, GetThreadLockWaitTime, which computes the total wait time for locks held by a specific thread. The function aggregates wait times across all locks and updates the tooltip to display lock wait time and count, enhancing the profiling capabilities of the timeline item.
2026-07-23 14:21:54 -05:00
Bartosz Taudul
917c4baffb GLFW from brew should no longer be needed on macOS. 2026-07-23 21:11:07 +02:00
Bartosz Taudul
faab64018a Update NEWS. 2026-07-23 21:03:07 +02:00
Bartosz Taudul
fb296ba1a4 Update manual. 2026-07-23 21:03:06 +02:00
Bartosz Taudul
877efe4eda Track current personality and warn when the selected one is not active. 2026-07-23 20:34:59 +02:00
Bartosz Taudul
90c6ad80e1 Clamp personality index to personality table size. 2026-07-23 20:29:26 +02:00
Bartosz Taudul
67c4102bf3 Restore disabling fast model selection during replies. 2026-07-23 20:26:41 +02:00
Bartosz Taudul
c1fa33a875 Link with system libcurl to avoid libssh deps. 2026-07-23 15:43:59 +02:00
Bartosz Taudul
1d6f43d92f Disable external libraries used by freetype.
Not needed for the current set of fonts.
2026-07-23 15:30:39 +02:00
Bartosz Taudul
c2b489a0ff Do not link macOS bundle with homebrew libraries.
Do not want dependency on something being installed via brew.
2026-07-23 14:57:45 +02:00
Bartosz Taudul
7986dc18d0 Add yalter's guadec presentation to list of videos. 2026-07-23 01:53:54 +02:00
Bartosz Taudul
f9413c268d Bump usearch to 2.26.0. 2026-07-22 22:35:42 +02:00
Bartosz Taudul
6e23d220d7 Bump capstone to 6.0.0-Alpha10. 2026-07-22 22:33:29 +02:00
Bartosz Taudul
158ab87bee Rebuild markdown manual. 2026-07-22 22:04:04 +02:00
Bartosz Taudul
d2cfc7b5d1 Document LLM personality selection. 2026-07-22 21:40:01 +02:00
Bartosz Taudul
b0d75acdef Add LLM personality presets. 2026-07-22 02:10:24 +02:00
Bartosz Taudul
c482f1e25d Bury fast model deeper in the UI. 2026-07-22 01:18:47 +02:00
Bartosz Taudul
8221a80b47 Add icon to LLM API entry field. 2026-07-22 01:13:20 +02:00
Bartosz Taudul
afe249b6d3 Do not disconnect active connections.
The timeout option stops the connection if it has now finished after the
specified time, even if data transfer is underway. Switch to using a low
speed timeout instead, which stops the connection only if no data is being
transferred.

A typical case where this was hit was a slow-responding LLM generating
a large amount of text.

The high timeout value is still needed to account for long prefill times.
2026-07-21 23:53:01 +02:00
Bartosz Taudul
72b1edc39d Set executable bit in macOS bundle. 2026-07-21 20:24:38 +02:00
Bartosz Taudul
2247f1d14b Add macOS artifacts to release workflow. 2026-07-21 19:57:45 +02:00
Bartosz Taudul
b0be3a5776 Bump actions/setup-python from 2 to 7. 2026-07-21 19:52:22 +02:00
Bartosz Taudul
dc261fe799 Add permissions contents write to release workflow. 2026-07-21 19:46:37 +02:00
Bartosz Taudul
21f0d15f5f Do not install library in CI workflows. 2026-07-21 19:35:43 +02:00
Bartosz Taudul
51f2b1da87 Do not zip bundle. 2026-07-21 19:21:33 +02:00
Bartosz Taudul
b32d454bcd Try making a macos bundle. 2026-07-21 16:42:33 +02:00
Bartosz Taudul
fb4dcd4e96 Switch macos CI runner to macos-latest. 2026-07-21 15:04:37 +02:00
Bartosz Taudul
d67cf385ff Merge pull request #1431 from alandtse/fix/mcp-live-worker-lock-race
fix(mcp): lock Worker reads vs receive thread
2026-07-20 12:04:36 +02:00
Bartosz Taudul
4ffc8b0dd2 Merge pull request #1430 from alandtse/fix/d3d11-zero-frequency-divide
fix(d3d11): guard collector against zero frequency
2026-07-20 12:04:07 +02:00
Alan Tse
5f2d14338e docs(mcp): fix eval-guide staleness
Two staleness gaps a cold agent would hit: get_sections()'s
documented {start, end, text} shape didn't match the actual
return value once category was added, and is_background_done()
-- introduced to close the stats-read race exercised while
testing the lock fix -- wasn't mentioned anywhere, so a cold
agent had no way to discover it short of dir(ctx).
2026-07-19 22:32:06 -07:00
Alan Tse
bfcec08e93 feat(mcp): expose background-load readiness
Smoke-testing the lock fix against a real 143M-zone capture
surfaced a second concurrent-mutation source: file loads spawn a
background thread that finishes populating zone/symbol statistics
after Worker construction returns, so get_all_zone_stats() et al.
could silently come back empty or partial with no way to tell
"not built yet" from "genuinely empty".

Adds is_background_done (a relaxed atomic, same as is_connected,
so no locked() wrapper needed) and surfaces it as background_done
in list_instances, with load_capture's docstring pointing callers
at it.
2026-07-19 22:32:04 -07:00
Alan Tse
15bc3fce1a fix(mcp): lock Worker reads vs receive thread
Every Worker-reading Python binding except save_worker() read
m_data's zones/plots/threads/etc. without taking
Worker::ObtainLockForMainThread(), racing the live receive thread
that mutates the same structures. This matches
alandtse/tracy#2: crashes only ever hit live instances, never
file-loaded ones (no concurrent writer there), and cluster right
after a big save_trace when the receive thread is catching up on
backlog.

Routes every Worker accessor through a new locked() wrapper using
the same cooperative lock TracyView.cpp already takes once per
frame, so reads can't race live writes without introducing new
stalls on the profiled client.
2026-07-19 22:32:04 -07:00
Alan Tse
dc9dfb2e65 fix(d3d11): guard collector against zero frequency
D3D11_QUERY_DATA_TIMESTAMP_DISJOINT can report Frequency == 0 even
when Disjoint == FALSE. Neither the initial CPU/GPU calibration
loop nor the per-frame Collect() path guarded against this, so
`timestamp * (1000000000 / disjoint.Frequency)` divides by zero
and crashes the profiled application with
EXCEPTION_INT_DIVIDE_BY_ZERO inside client instrumentation code.

Treats a zero frequency the same as an existing guard already does
for a disjoint result: skip/retry in calibration, and in Collect()
advance the checkpoint and drop the batch via TracyD3D11Panic,
matching the existing disjoint-timestamp handling immediately
above it.
2026-07-19 22:28:52 -07:00
Bartosz Taudul
ef099b0555 The UD- prefix is problematic in router mode. 2026-07-19 01:17:52 +02:00
Bartosz Taudul
956e082e5b ResetGraph lambda can't be static. 2026-07-18 15:33:43 +02:00
Bartosz Taudul
be6377f81b Constrain source view window size. 2026-07-18 14:51:41 +02:00
Bartosz Taudul
249b76f175 Do not display time, reason data for aggregate wait stacks. 2026-07-18 14:39:52 +02:00
Bartosz Taudul
6a916a6c9f Constrain zone info window size. 2026-07-18 14:31:15 +02:00
Bartosz Taudul
0f56c8ac7e Constrain callstack window size. 2026-07-18 14:19:33 +02:00
Bartosz Taudul
8a4876a071 Fix wrong formatting. 2026-07-18 14:15:14 +02:00
Bartosz Taudul
7539daa602 Constrain sample entry stacks window size. 2026-07-18 14:11:33 +02:00
Bartosz Taudul
ab46247a9c Constrain frame statistics window size. 2026-07-18 14:07:48 +02:00
Bartosz Taudul
7bc32a1536 Use sub-child in frame statistics window. 2026-07-18 14:07:22 +02:00
Bartosz Taudul
6858a664dc Constrain wait stacks window size. 2026-07-18 14:01:29 +02:00
Bartosz Taudul
3eae7a6d65 Constrain LLM window size. 2026-07-18 13:52:58 +02:00
Bartosz Taudul
64c0c5c85f Constrain compare window size. 2026-07-18 13:46:24 +02:00
Bartosz Taudul
74e1ddfbeb Constraint window memory size. 2026-07-18 13:42:14 +02:00
Bartosz Taudul
f472b92f13 Constraint statistics window size. 2026-07-18 13:40:20 +02:00
Bartosz Taudul
6c8dd9fd93 Constrain find zone window size. 2026-07-18 13:37:10 +02:00
Bartosz Taudul
8184964c2d Include sub-window padding and scrollbars. 2026-07-18 13:36:45 +02:00
Bartosz Taudul
eef8fac58b Constrain messages window size. 2026-07-18 13:21:35 +02:00
Bartosz Taudul
2d2487e2e0 Constrain flame graph window size. 2026-07-18 13:20:15 +02:00
Bartosz Taudul
562fea5564 Add DPI change notification method in View. 2026-07-18 13:17:42 +02:00
Bartosz Taudul
3608d0c271 Add imgui window size constraints helper. 2026-07-18 13:17:42 +02:00
Bartosz Taudul
f08792f217 Remove filtering for things that are no longer present. 2026-07-18 12:50:47 +02:00
Bartosz Taudul
87b838f553 Update manual. 2026-07-18 12:32:50 +02:00
Bartosz Taudul
ed39de8025 Update NEWS. 2026-07-18 12:23:14 +02:00
Bartosz Taudul
84c6548c94 Add option to group symbol flame graph items by name, not symaddr. 2026-07-18 12:20:38 +02:00
Bartosz Taudul
0b2e7dd13c Add StringIdx equality comparison operator. 2026-07-18 12:20:32 +02:00
Bartosz Taudul
eb12b62af6 Extract flame graph reset functionality. 2026-07-18 12:07:43 +02:00
Bartosz Taudul
91bababef7 Common path for both trees in sample entry stacks. 2026-07-17 22:24:58 +02:00
Bartosz Taudul
f13bb0bf52 Fix UI logic. 2026-07-15 02:09:34 +02:00
Bartosz Taudul
0caee2147c Update manual. 2026-07-15 01:20:14 +02:00
Bartosz Taudul
3e91ee15b1 Display FPS time range values in frame compare. 2026-07-14 23:56:28 +02:00
Bartosz Taudul
f7c63ac40c Display single FPS value in frame stats tooltip, if min and max are equal. 2026-07-14 23:55:44 +02:00
Bartosz Taudul
3d85d0468e Remove unneeded capture. 2026-07-14 21:28:33 +02:00
Bartosz Taudul
ff3c57c8d3 Category may exist while sections are not filled.
Dereference of end() iterator in section category options list
(profiler/src/profiler/TracyView_Options.cpp:152-153)

The new category list iterates categories = m_worker.GetSectionDescriptions()
(the description map) and then looks up each category in sections = m_worker.
GetSections():

for( const auto& v : categories )
{
    ...
    auto it = sections.find( v.first );
    ImGui::TextDisabled( "(%s)", RealToString( it->second.size() ) );
    ...
}

sectionsDescription is a superset of sections's keys: ProcessSectionSetup
(server/TracyWorker.cpp:7484-7496) inserts into sectionsDescription only,
while ProcessSectionEnter (server/TracyWorker.cpp:7416-7454) inserts into
both. A category set up via TracySectionSetup that has no TracySectionEnter
events yet (or a saved trace containing such a category) will be in
sectionsDescription but not in sections. find returns end() and
it->second.size() is undefined behavior (likely crash).
2026-07-14 21:21:45 +02:00
Bartosz Taudul
a501117392 Use proper data size. 2026-07-14 21:20:00 +02:00
Bartosz Taudul
916e62de17 Display count of sections in each category. 2026-07-14 19:17:22 +02:00
Bartosz Taudul
88c491e0f9 Add select/unselect all buttons to section categories list. 2026-07-14 19:14:55 +02:00
Bartosz Taudul
1a6b7781bd Update manual. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
71810c4265 Update NEWS. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
15fecdbf79 Display section category in section tooltips. 2026-07-14 19:13:01 +02:00
Bartosz Taudul
c1e8c7bac9 Implement disabling specific section categories. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
03af70fd78 Add section categories descriptions accessor. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
7933c00447 Add notification for when sections are hidden. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
eb53d28358 Repurpose "limit to view" icon for sections. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
2e0367cd1f Implement disabling drawing of sections. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
c2fa2d4d7b New section selection menu in compare window. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
f86105c600 New section range copy in playback. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
2b9e7a53ff Further extraction of common functionality. 2026-07-14 19:13:00 +02:00
Bartosz Taudul
172f32d075 Use section categories in dyna example. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
7dab948434 List sections and categories in range limits window. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
e49b96ec03 Section items lister. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
42fc8d8d8f Add getter for section category name. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
ece976e820 Move Range, RangeSlim from TracyViewData.hpp to TracyUtility.hpp. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
a8d5f12f16 Properly process section categories.
Section selection UI disabled for now.
2026-07-14 19:12:59 +02:00
Bartosz Taudul
aacc82df3d Hook up server-side no-op section setup. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
3e1de56e15 Add section category to section enter message. 2026-07-14 19:12:59 +02:00
Bartosz Taudul
859709a98a Add client-side section category setup. 2026-07-14 19:12:58 +02:00
Bartosz Taudul
50c3c89c16 Rename parents / baseParents maps to wasExecuting / wasExecutingBase.
The current "parents" mode of operation is "was this symbol executing".
The new name for the maps reflects that.

There should also be "was this symbol reached" (both recursive and non-
reentrant) modes.
2026-07-12 16:38:42 +02:00
Bartosz Taudul
60ce6fb25e Rename parent call stacks to synthetic call stacks. 2026-07-12 16:30:16 +02:00
Bartosz Taudul
9ec0535e03 Extract parent frame interning function. 2026-07-12 16:19:08 +02:00
Bartosz Taudul
81ccf5149a Increase code legibility. 2026-07-12 16:02:28 +02:00
Bartosz Taudul
7b36ab9793 Regenerate markdown manual. 2026-07-11 16:22:09 +02:00
Bartosz Taudul
2b49e91804 Update manual. 2026-07-11 16:21:55 +02:00
Bartosz Taudul
6bd6483181 Update NEWS. 2026-07-11 16:00:42 +02:00
Bartosz Taudul
22a5a5fb78 Add copy message context menu to zone info message list. 2026-07-11 16:00:42 +02:00
Bartosz Taudul
580a43c05f Enable copying messages to clipboard. 2026-07-11 15:38:12 +02:00
Bartosz Taudul
3b2d97ab21 Set filename when saving a trace to disk. 2026-07-10 20:34:22 +02:00
Bartosz Taudul
b76468885b Show callstack lines in memory plot tooltips. 2026-07-10 20:34:22 +02:00
Bartosz Taudul
c35e0f07c5 Draw longer callstack lines in memory alloc window. 2026-07-10 20:34:19 +02:00
Bartosz Taudul
7979404a8f Merge pull request #1428 from damcclos/damcclos/flame-thread-sort
Enhance Flame Graph View with Sorting Functionality
2026-07-10 20:34:09 +02:00
David McCloskey
7d1a37de2d Pulling implementation in to a separate function. 2026-07-10 12:00:48 -05:00
Bartosz Taudul
0350df11f8 Hook up section enter / leave to Lua bindings. 2026-07-10 16:24:46 +02:00
Bartosz Taudul
59013f2a8c Add GetNextSectionId() for getting section id. 2026-07-10 16:05:37 +02:00
Bartosz Taudul
a208c3c665 Merge pull request #1427 from nicozink/fix/VulkanQueryCount
Reduce Vulkan QueryCount for Apple devices
2026-07-10 15:27:50 +02:00
David McCloskey
655b8295cf Enhance Flame Graph View with Sorting Functionality
Added a "Sort" button to the Flame Graph view, allowing users to sort threads based on group hints, thread names, and IDs. Updated the child window to always show a vertical scrollbar for better usability.
2026-07-09 12:16:17 -05:00
Nico Zink
e20c716b80 Reduce Vulkan QueryCount for Apple devices 2026-07-09 16:32:22 +01:00
Bartosz Taudul
ac4defb4f3 Merge pull request #1426 from YaLTeR/fix-manual
manual: Fix decorators in the Python listing and typo
2026-07-09 13:51:39 +02:00
Ivan Molodetskikh
c6f5cf50fb manual: Fix decorators in the Python listing and typo 2026-07-09 14:06:41 +03:00
Bartosz Taudul
96a91da63a Merge pull request #1425 from YaLTeR/desktop-tracy-profiler
.desktop: Change binary name to tracy-profiler
2026-07-09 11:55:06 +02:00
Ivan Molodetskikh
e0929e9cd8 .desktop: Change binary name to tracy-profiler
That's the current binary name.
2026-07-09 12:50:15 +03:00
Bartosz Taudul
05f620ab97 Merge pull request #1424 from YaLTeR/static-nfd
Link NFD statically
2026-07-09 11:16:56 +02:00
Ivan Molodetskikh
e40ce11ba9 Link NFD statically
Currently, the profiler will build against the git NFD, but link it as a
shared library. Since libnfd is not installed when building Tracy as a
distro package, it will end up actually depending on and running against
an older distro-provided NFD, hitting an unresolved function error due
to the newly added NFD function in git.

Other deps fetched with CPM are linked statically, avoiding this
problem.
2026-07-09 10:46:00 +03:00
Bartosz Taudul
66e5b81804 Draw rectangle encompassing CPU topology package. 2026-07-09 01:04:51 +02:00
Bartosz Taudul
d565afde82 Remove force inline for SectionEnter.
gcc breaks on this:
error: function ‘static uint32_t tracy::Profiler::SectionEnter(const char*, ...)’ can never be inlined because it uses variable argument lists
2026-07-08 18:04:01 +02:00
Bartosz Taudul
bd8246cba0 Build library / test application before building profiler tools. 2026-07-08 17:55:43 +02:00
Bartosz Taudul
2205d7972f Build dyna example in linux CI workflow. 2026-07-08 17:54:12 +02:00
Bartosz Taudul
e842bf223f Use correct type for phdr addresses. 2026-07-07 17:54:03 +02:00
Bartosz Taudul
6495aab062 Add android library CI workflow. 2026-07-07 17:53:14 +02:00
Bartosz Taudul
8555514d59 Merge pull request #1423 from YaLTeR/update-install
update: Add install() to CMakeLists.txt
2026-07-07 14:06:21 +02:00
Ivan Molodetskikh
3e0ded59a5 update: Add install() to CMakeLists.txt 2026-07-07 14:03:03 +03:00
Bartosz Taudul
ba8e4bdcbf Merge pull request #1422 from alandtse/fix/mcp-instance-eviction
fix(mcp): cap tracy_mcp instance memory growth
2026-07-07 11:21:56 +02:00
Alan Tse
9bbbec42f4 fix(mcp): cap tracy_mcp instance memory growth
tracy_mcp.py runs as a long-lived singleton shared across every MCP
client, and the `instances` dict never evicted entries — each
live_connect/load_capture materializes a full trace (zones, messages,
callstacks, memory events) with no size cap, so private bytes grow
without bound across recording sessions unless unload_capture is
called manually every time.

Add two backstops, both env-configurable: an LRU cap
(TRACY_MCP_MAX_INSTANCES, default 4) enforced on live_connect/
load_capture that never evicts a still-connected live instance, and a
periodic sweep that drops a disconnected live instance once it has
sat idle past TRACY_MCP_DISCONNECTED_TTL_S (default 1800s), keeping a
grace window for post-session analysis. list_instances now reports
connected/idle_seconds so callers can spot stale sessions, and
unload_capture/eviction now explicitly call Worker.shutdown() instead
of relying on GC. Document the lifecycle expectations (call
unload_capture proactively; treat eviction as a backstop) in
eval_guide.md.
2026-07-07 00:23:47 -07:00
Bartosz Taudul
69fead1c16 Add CMake presets for the rest of utils. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
df463fcbfe Add CMake presets for profiler. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
467ba60cdf Add proper compile options to dyna example. 2026-07-07 01:43:43 +02:00
Bartosz Taudul
cbbfd13fa2 Merge pull request #1421 from YaLTeR/sys-reg-h
Change musl bits/reg.h to sys/reg.h
2026-07-06 18:55:20 +02:00
Ivan Molodetskikh
16a148f0be Replace __WORDSIZE with __SIZEOF_POINTER__
__WORDSIZE has been relocated from bits/reg.h (now an empty header) to
sys/reg.h in 2024:
https://git.musl-libc.org/cgit/musl/commit/?id=e709a6f07ade208ba513f9225222336f30c304b0

And in any case, it's a private definition. Let's use a common
documented define instead:
https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html

Fixes symbol resolution on musl (particularly, postmarketOS aarch64).
2026-07-06 18:57:31 +03:00
Bartosz Taudul
9131f3600b Fix copy from dialog for sections that were not ended. 2026-07-05 19:50:03 +02:00
Bartosz Taudul
b2d5d9bc52 Allow limiting playback to section scope. 2026-07-05 18:53:52 +02:00
Bartosz Taudul
ec884b2d60 Allow setting time range limit from section. 2026-07-05 18:51:58 +02:00
Bartosz Taudul
81cb4acbe6 Use ImGui mouse button identifiers instead of raw numeric ids. 2026-07-05 18:47:38 +02:00
Bartosz Taudul
f2256cf260 Add ellipsis to buttons that require further action. 2026-07-05 18:47:07 +02:00
Bartosz Taudul
ae83b00951 Add option to reset compare time range limits. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
d2c69868d1 Better compare range limit time display. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
18cfcd7e90 Add copy from section to compare range limits. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
71d88fd198 Annotations may have the same name. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
17bbc4049a Cosmetics for compare range limit. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
2b0b7293a1 Filter frames by range in compare view. 2026-07-04 22:29:14 +02:00
Bartosz Taudul
07cb1030e7 Filter zones by range in compare view. 2026-07-04 22:29:13 +02:00
Bartosz Taudul
af8dc30912 Add sumSq cache to compare data for range filtering. 2026-07-04 17:41:18 +02:00
Bartosz Taudul
8f2bbc6116 Display message when no data to compare. 2026-07-03 15:24:47 +02:00
Bartosz Taudul
f01607714e Reset data when changing range limits. 2026-07-03 15:14:21 +02:00
Bartosz Taudul
533e1af389 Set time range limits in compare window from annotations. 2026-07-02 17:40:35 +02:00
Bartosz Taudul
25139748b2 Add annotations accessor to UserData. 2026-07-02 17:40:09 +02:00
Bartosz Taudul
853fff614e Support nfd parent window with wayland backend. 2026-07-02 02:14:04 +02:00
Bartosz Taudul
5be36d732b Bump nfd to current master. 2026-07-02 01:29:50 +02:00
Bartosz Taudul
836bf9d2eb Pass parent window handle to file dialogs. 2026-07-02 01:26:58 +02:00
Bartosz Taudul
db383f738b Store window handle for file selector. 2026-07-02 01:26:58 +02:00
Bartosz Taudul
a8c642f42b Cosmetics. 2026-07-02 01:01:09 +02:00
Bartosz Taudul
641bd09b6f Use extended-params version of NFD functions. 2026-07-02 00:46:30 +02:00
Bartosz Taudul
280af2e7bd Bump curl. 2026-07-02 00:30:53 +02:00
Bartosz Taudul
83298a3459 Bump usearch. 2026-07-02 00:30:48 +02:00
Bartosz Taudul
21f9c00e9f Bump md4c to current master. 2026-07-02 00:24:57 +02:00
Bartosz Taudul
b0a094bb42 Update manual. 2026-07-01 18:26:29 +02:00
Bartosz Taudul
70a09f5663 Update NEWS. 2026-07-01 17:58:05 +02:00
Bartosz Taudul
467be67807 Adjust colors. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
80476da597 Color-code difference between values in comparison window. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
d94d8f11ac Show P75, P90, P99, P99.9 in comparison window. 2026-07-01 17:33:29 +02:00
Bartosz Taudul
da495be8de Remove separation in "speedup or slowdown" label. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
fef901f595 Show comparison of standard deviation in compare window. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
74650af7a0 Show coefficient of variation in frames compare. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
e740bc652c Display FPS values for mean and median in frames comparison. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
7da72e51d2 Compare FPS ranges for frames. 2026-07-01 17:33:28 +02:00
Bartosz Taudul
ac5682bc75 Use boolean to differentiate between zones / frames comparison mode. 2026-07-01 17:33:27 +02:00
Bartosz Taudul
570a06dced Be more accurate about normalization behavior. 2026-07-01 17:33:24 +02:00
Bartosz Taudul
00c079cec9 Put summary updates under a lock. 2026-07-01 02:10:52 +02:00
Bartosz Taudul
80a1772d0b Separate external frames visibility for wait stacks (enabled by default). 2026-07-01 02:03:28 +02:00
Bartosz Taudul
f65949cc26 Move callstack table params to a struct. 2026-07-01 01:53:08 +02:00
Bartosz Taudul
b40ef0ff29 Draw timeline thread label after message markers to make it readable. 2026-07-01 01:26:26 +02:00
Bartosz Taudul
aca0e39190 Do not summarize very short llm queries. 2026-06-30 21:06:43 +02:00
Bartosz Taudul
ba9388cd9f Merge pull request #1404 from wolfpld/slomp/gl_query_result_no_wait
Possibly faster GL timestamp query result check
2026-06-30 21:03:40 +02:00
Bartosz Taudul
1f2190a44a Merge pull request #1420 from YaLTeR/flame-graph-pan
Fix "quantization" of sampling flame graph horizontal panning
2026-06-30 19:52:49 +02:00
Ivan Molodetskikh
b11fa7caa2 Store flame graph time as time instead of samples
Practically, this fixes horizontal panning being "quantized" to 100 µs
steps (one period). I guess it also simplifies the code a bit and makes
"time" actually mean time.
2026-06-30 19:54:54 +03:00
Bartosz Taudul
ed1a4b06b6 No cumulate time for frames. 2026-06-30 16:57:19 +02:00
Bartosz Taudul
d34659f57b Remove tree node for histogram in compare window. 2026-06-30 16:44:54 +02:00
Bartosz Taudul
a67a29e9cd Use tabs in compare window. 2026-06-30 16:39:06 +02:00
Bartosz Taudul
dc4b51e522 Always draw separator in compare window. 2026-06-30 14:22:46 +02:00
Bartosz Taudul
0a0cd27db4 Update manual. 2026-06-29 17:20:10 +02:00
Bartosz Taudul
472c84ac78 Implement coverage requirement in GetPlaybackFrameRangeFromTime. 2026-06-29 17:16:27 +02:00
Bartosz Taudul
bde9e50c84 Use binary search in GetPlaybackFrameRangeFromTime(). 2026-06-29 17:02:51 +02:00
Bartosz Taudul
dba0b6263e Add button for resetting frame image playback speed. 2026-06-29 16:32:59 +02:00
Bartosz Taudul
27f4e00aea Add require coverage checkbox. 2026-06-29 16:30:21 +02:00
Bartosz Taudul
6d6a4beda3 Add button for resetting frame image playback range. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
bbb43549a6 Move playback "copy from" to a button with a popup menu. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
83a703640c Add limits button to frame statistics window. 2026-06-29 16:18:58 +02:00
Bartosz Taudul
7b4a128080 Use full size button for flame graph reset view. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
547891f044 Update manual. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
e4acd2db77 Add icon for the "limit to view" button in time ranges window. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
e7bd441ebb Hide time range copy sources in a popup menu. 2026-06-29 16:18:57 +02:00
Bartosz Taudul
4cd6c38932 Include exclusive sample count in inclusive sample count. 2026-06-27 16:44:26 +02:00
Bartosz Taudul
331881ca36 Fix counting of "with children" sample counts in recursive cases. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
9aa48a62cb Fix remains of old font awesome markup. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
50982fe7a3 Update manual. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
549226751e Update NEWS. 2026-06-27 15:00:42 +02:00
Bartosz Taudul
6921fbfdf8 Color sample markers according to sample type. 2026-06-27 15:00:41 +02:00
Bartosz Taudul
90d4463ef0 Determine sample type. 2026-06-27 11:55:00 +02:00
Bartosz Taudul
323659d4f7 Merge pull request #1416 from YaLTeR/fix-elf-zstd-rle-mode
libbacktrace: Fix missing table assignment in RLE seq decode
2026-06-27 10:57:45 +02:00
Ivan Molodetskikh
279f7ba0df libbacktrace: Fix missing table assignment in RLE seq decode
Fixes segfault when decompressing mesa debuginfo on Aeryn.
2026-06-27 11:05:12 +03:00
Bartosz Taudul
0b120ba84f Merge pull request #1415 from wolfpld/slomp/webgpu-logging
WebGPU: more informative errors
2026-06-26 23:09:58 +02:00
Bartosz Taudul
04a8ba4c2b Update manual. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
576d03e414 Update NEWS. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
88702dad39 Allow setting frame image playback limit to an annotation of range limit. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
30572e4810 Don't crash on annotation with no name. 2026-06-26 20:21:03 +02:00
Bartosz Taudul
57c143c9b7 Translate time range to frame image range. 2026-06-26 20:21:03 +02:00
Marcos Slomp
2221138299 more informative errors 2026-06-26 10:40:48 -07:00
Bartosz Taudul
4bc37ccb4d Allow limiting frame image playback to given frame range. 2026-06-26 19:39:24 +02:00
Bartosz Taudul
e9ad49a846 Introduce abstraction of frame image begin and end. 2026-06-26 19:07:47 +02:00
Bartosz Taudul
64a43ef3d3 Add looping to frame image playback. 2026-06-26 18:37:54 +02:00
Bartosz Taudul
8aae924d51 Make play button disabled if playback is at the end. 2026-06-26 17:46:58 +02:00
Bartosz Taudul
9370d7a379 Unify entry stacks icon. 2026-06-26 14:25:06 +02:00
Bartosz Taudul
e10c865fbf Merge pull request #1414 from YaLTeR/fix-elf-symbols
Fix image and symbol resolution on AerynOS and elsewhere
2026-06-26 14:19:08 +02:00
Ivan Molodetskikh
bd2106db68 libbacktrace: support compressed block with window descriptor
Some debuginfo on AerynOS has this format.
2026-06-26 10:46:33 +03:00
Ian Lance Taylor
99c13e53bf libbacktrace: support compressed block with no sequences
* elf.c (elf_zstd_decompress_frame): Support a compressed block
	with no sequences, only literals.

Fixes https://github.com/ianlancetaylor/libbacktrace/issues/164
2026-06-26 10:46:17 +03:00
Ian Lance Taylor
7fbe1d1345 libbacktrace: support multiple zstd frames
Based on patch by GitHub user ofats.

	* elf.c (elf_zstd_decompress_frame): New static function,
	broken out of elf_zstd_decompress.
	(elf_zstd_decompress): Call elf_zstd_decompress_frame in a loop.
	* zstdtest.c (test_large): Compress the file in chunks.
2026-06-26 10:41:29 +03:00
Ivan Molodetskikh
5745b489a2 callstack: Calculate end address from highest LOAD header
Headers aren't guaranteed to be sorted in final address order. For
example, from readelf -lW of a .so built with clang:

Program Headers:
  Type           Offset   VirtAddr           PhysAddr           FileSiz  MemSiz   Flg Align
  PHDR           0x000040 0x0000000000000040 0x0000000000000040 0x000230 0x000230 R   0x8
  LOAD           0x000000 0x0000000000000000 0x0000000000000000 0x5e8274 0x5e8274 R   0x1000
  LOAD           0x5e8280 0x00000000005e9280 0x00000000005e9280 0x51e990 0x51e990 R E 0x1000
  LOAD           0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 RW  0x1000
  LOAD           0xb38240 0x0000000000b3b240 0x0000000000b3b240 0x009aa8 0x011c40 RW  0x1000
  DYNAMIC        0xb32270 0x0000000000b34270 0x0000000000b34270 0x000410 0x000410 RW  0x8
  GNU_RELRO      0xb06c10 0x0000000000b08c10 0x0000000000b08c10 0x031628 0x0323f0 R   0x1
  GNU_EH_FRAME   0x4e8360 0x00000000004e8360 0x00000000004e8360 0x029cf4 0x029cf4 R   0x4
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0
  NOTE           0x000270 0x0000000000000270 0x0000000000000270 0x000024 0x000024 R   0x4
2026-06-26 10:41:29 +03:00
Bartosz Taudul
7e2e1829c9 Update manual. 2026-06-25 18:12:21 +02:00
Bartosz Taudul
1ca38e2b70 Update NEWS. 2026-06-25 18:12:03 +02:00
Bartosz Taudul
1a420d0f5e Add frame statistics limit range. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
7eae48c1a8 Make range stripes more visible when changing the range size. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
1c712e41d9 Allow making annotations from ranges. 2026-06-25 17:58:34 +02:00
Bartosz Taudul
a03e5e8968 Use note sticky icon for add annotation button. 2026-06-25 17:58:32 +02:00
Bartosz Taudul
3e5310cac6 Fix sigma conversion in latex -> md script. 2026-06-25 00:14:21 +02:00
Bartosz Taudul
606cd7bdaf Protect against zero frames to show. 2026-06-24 23:49:53 +02:00
Bartosz Taudul
d924b05ab7 Calculate square of averages with double precision. 2026-06-24 23:27:36 +02:00
Bartosz Taudul
d0c7596977 Regenerate markdown manual. 2026-06-24 23:02:25 +02:00
Bartosz Taudul
0bee59ff85 Update manual. 2026-06-24 23:01:34 +02:00
Bartosz Taudul
963a968a9e Update NEWS. 2026-06-24 20:37:51 +02:00
Bartosz Taudul
f700597067 Display P75, P90, P99 and P99.9 for frame statistics. 2026-06-24 20:09:03 +02:00
Bartosz Taudul
a237b009ec Use unified percentile value calculation in find zone. 2026-06-24 19:56:24 +02:00
Bartosz Taudul
5970e90233 Display coefficient of variation in frame statistics. 2026-06-24 19:32:50 +02:00
Bartosz Taudul
5fc4ae8eef Display coefficient of variation in find zone. 2026-06-24 19:30:42 +02:00
Bartosz Taudul
a35f8a738d Display standard deviation in frame statistics. 2026-06-24 19:21:40 +02:00
Bartosz Taudul
3714e36652 Frame count in frame statistics may be not full. 2026-06-24 18:56:08 +02:00
Bartosz Taudul
565f3e5a72 Add mode to frame statistics. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
17720a0cd8 Keep FPS range and max counts in the same line. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
a7b0907d6c Move mean and median in frame statistics to match find zone. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
552159ccf6 Remove tree node for historgram in frame statistics. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
7e8180a2a7 Move frame statistics from trace info to its own window. 2026-06-24 18:48:38 +02:00
Bartosz Taudul
05d34e4daa Make it possible to hide annotation. 2026-06-24 18:48:37 +02:00
Bartosz Taudul
72d45dfdad Fix display of annotations color on annotations list. 2026-06-24 18:48:35 +02:00
Bartosz Taudul
280475ff3d Iterate ranges array when drawing ranges on timeline. 2026-06-23 23:45:41 +02:00
Bartosz Taudul
922604fe6a Do not include alpha in color entries in ranges array. 2026-06-23 23:45:11 +02:00
Bartosz Taudul
5b29550ded Move ranges setup to an appropriate file. 2026-06-23 23:29:33 +02:00
Bartosz Taudul
56eb3b776f Show ranges window after setting a range via timeline right-click popup.
Without this using the popup is quite unintuitive. Setting the range
apparently does not have an effect – because ranges are only shown if the
ranges window is open (or the windows appropriate for each of the ranges).
2026-06-23 23:07:27 +02:00
Bartosz Taudul
98ad778495 Iterate ranges array to handle range adjustments by mouse. 2026-06-23 23:06:02 +02:00
Bartosz Taudul
fa4c28af8a Iterate ranges array in timeline right-click menu. 2026-06-23 23:03:26 +02:00
Bartosz Taudul
a73a733644 Popup label is now just label, do not pass it twice. 2026-06-23 22:54:09 +02:00
Bartosz Taudul
d4cceb3e0d Use ranges array in DrawRangeEntry(). 2026-06-23 22:45:45 +02:00
Bartosz Taudul
b715c0c32e Add icon to annotation tooltip. 2026-06-23 22:41:18 +02:00
Bartosz Taudul
f71620c8c8 Use ranges array in DrawRanges(). 2026-06-23 22:33:07 +02:00
Bartosz Taudul
51e467d7b9 Make ranges private in View. 2026-06-23 21:50:22 +02:00
Bartosz Taudul
eba8c1e99a Setup ranges array. 2026-06-23 21:49:15 +02:00
Bartosz Taudul
9a785976c0 Ignore zero-length sections. 2026-06-23 02:00:11 +02:00
Bartosz Taudul
711e3a7bcf Add separator between timeline headers and content. 2026-06-23 01:58:35 +02:00
Bartosz Taudul
d84a085b71 Collapse small sections. 2026-06-23 01:58:35 +02:00
Bartosz Taudul
ae38665b95 Draw sections. 2026-06-22 19:17:01 +02:00
Bartosz Taudul
800e415400 Calculate section rows content. 2026-06-22 19:17:00 +02:00
Bartosz Taudul
f1dfedffaf Instrument program sections in dyna example. 2026-06-22 15:10:38 +02:00
Bartosz Taudul
67bb927d61 Store level name in World. 2026-06-22 15:10:28 +02:00
Bartosz Taudul
415197bd5d Remove semicolon from TracySetProgramName macro. 2026-06-22 14:57:25 +02:00
Bartosz Taudul
5177e587e9 Do not include semicolons in TracySectionEnter / TracySectionLeave macros. 2026-06-22 14:56:32 +02:00
Bartosz Taudul
ee7d2b4fd8 Track memory allocations in dyna example. 2026-06-21 23:25:42 +02:00
Bartosz Taudul
1d0806c374 Add frame image capture to the dyna example. 2026-06-21 23:12:52 +02:00
Bartosz Taudul
9cc8f3752e Mark zones in dyna example. 2026-06-21 23:05:20 +02:00
Bartosz Taudul
79e0efac96 Mark frame boundaries in dyna example. 2026-06-21 22:53:31 +02:00
Bartosz Taudul
8531e879a9 Use TracyNoop to ensure profiler library is linked with executable. 2026-06-21 22:47:34 +02:00
Bartosz Taudul
89a75d5c71 Integrate Tracy into dyna example. 2026-06-21 22:47:34 +02:00
Bartosz Taudul
e9e85ca6ee Add Dyna.net example. 2026-06-21 22:47:34 +02:00
Bartosz Taudul
3a6a23ca98 Save and load sections data. 2026-06-21 00:56:32 +02:00
Bartosz Taudul
3157dfae3e Merge pull request #1412 from jensenr30/fix-signed-int-comparison-warning
Fix warning [-Wsign-compare]
2026-06-21 00:32:51 +02:00
Ryan Jensen
978b720759 Fix warning [-Wsign-compare] 2026-06-20 17:09:26 -05:00
Bartosz Taudul
77c655eb7d Merge pull request #1411 from alandtse/feat/mcp-sections-accessor
feat(mcp): expose sections accessor in eval bindings
2026-06-20 11:49:45 +02:00
Alan Tse
bee6ac566e docs(mcp): drop dangling tracy://catalog reference
eval_guide.md referenced a tracy://catalog resource that was never
registered (only tracy://prompt and tracy://eval-guide exist), so an
agent following the guide would try to read a nonexistent resource.
Remove the references; the worked snippets the catalog described are
already inlined under "Common query patterns".

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvfzHvUsDBSAwEzTfLTtA
2026-06-19 15:51:51 -07:00
Alan Tse
6e4041b14d feat(mcp): expose sections accessor in eval bindings
Bind Worker::GetSections() as get_sections() so the TracySectionEnter /
TracySectionLeave instrumentation added to the client is reachable from
the MCP eval tool's ctx object. Returns a list of {start, end, text}
dicts with nanosecond timestamps, matching the existing list-of-dict
accessor convention. Documented in eval_guide.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016EvfzHvUsDBSAwEzTfLTtA
2026-06-19 15:44:12 -07:00
Bartosz Taudul
1706ac57ac Separate id field is not needed. 2026-06-19 20:37:49 +02:00
Bartosz Taudul
72d62bea25 Display count of sections. 2026-06-19 20:37:49 +02:00
Bartosz Taudul
0ae37172dc Add sections accessor. 2026-06-19 20:37:45 +02:00
Marcos Slomp
d1373d3f4c code hardening for GLES 2.0 2026-06-19 11:03:30 -07:00
Bartosz Taudul
9502ab728d Merge pull request #1410 from shekhirin/alexey/mcp-streamable-http
feat(mcp): default to streamable-http transport
2026-06-19 19:45:51 +02:00
Alexey Shekhirin
7dd4dbc8a5 update docs 2026-06-19 18:36:03 +01:00
Alexey Shekhirin
96c681cf61 feat(mcp): default to streamable-http transport 2026-06-19 16:42:14 +01:00
Bartosz Taudul
2052aa2ab8 Regenerate markdown manual. 2026-06-19 15:05:58 +02:00
Bartosz Taudul
977218ea04 Document that ___tracy_alloc_srcloc strings must not be null-terminated. 2026-06-19 15:05:05 +02:00
Bartosz Taudul
fcd5944d5e Better default llm config. 2026-06-19 12:36:39 +02:00
Bartosz Taudul
34395f97ed Regenerate markdown manual. 2026-06-18 21:31:03 +02:00
Bartosz Taudul
c897729b74 Use safe file names for cache. 2026-06-18 21:30:07 +02:00
Bartosz Taudul
f8208ce5ef Add quick start guide for getting Tracy Assist running. 2026-06-18 21:21:15 +02:00
Bartosz Taudul
3516c96afd Retrieve available context on llama.cpp if not available on start.
In order for context size to be available, the model has to be loaded on
the server. However, the typical flow will be to load models on demand.
2026-06-18 21:00:25 +02:00
Bartosz Taudul
3bb5027565 Add llama.cpp tokenization support. 2026-06-18 20:58:40 +02:00
Bartosz Taudul
eefc7aee4b Detect llama.cpp router mode. 2026-06-18 20:56:07 +02:00
Bartosz Taudul
3ccda1d3c4 Add embedding model heuristic to not recognized llama API providers. 2026-06-18 20:51:53 +02:00
Bartosz Taudul
802e88585a Merge pull request #1407 from shekhirin/tracy-consistent-thread-order
feat: display threads in a consistent order
2026-06-18 14:41:20 +02:00
Alexey Shekhirin
74bbe5988b Use consistent thread order in more thread pickers 2026-06-18 09:41:45 +01:00
Alexey Shekhirin
066cb89f75 Display threads in a consistent order 2026-06-18 09:41:31 +01:00
Bartosz Taudul
8c289104d6 Regenerate markdown manual. 2026-06-18 00:39:26 +02:00
Bartosz Taudul
274e453bb6 Process section enter / leave events. 2026-06-17 22:37:31 +02:00
Bartosz Taudul
d89055908b Update manual. 2026-06-17 22:37:31 +02:00
Bartosz Taudul
801a34ea66 Add trigger parameter. 2026-06-17 22:37:31 +02:00
Bartosz Taudul
0cdb6d365e Initial section enter / leave instrumentation. 2026-06-17 22:37:31 +02:00
Bartosz Taudul
e3f4c72c85 Move TRACY_ATTRIBUTE_FORMAT_PRINTF to its own header. 2026-06-17 22:37:31 +02:00
Bartosz Taudul
eeab1bf1a9 Border sizes are scaled by ScaleAllSizes() since ImGui 1.92.7. 2026-06-17 22:37:30 +02:00
Bartosz Taudul
d26176baae Merge pull request #1406 from mcourteaux/fix-charconv
Implement C++ feature check for charconv.
2026-06-17 11:37:38 +02:00
Martijn Courteaux
021aa6a9e6 Implement C++ feature check for charconv. 2026-06-17 09:27:00 +02:00
Bartosz Taudul
8b86ada40e Fix checks. 2026-06-17 01:31:56 +02:00
Bartosz Taudul
616f33ff65 Consistent wait.time checks. 2026-06-17 01:31:01 +02:00
Bartosz Taudul
023cb20ba9 Only display wait reason or state if present. 2026-06-17 00:33:00 +02:00
Bartosz Taudul
073cd266ac Use proper thread id for wait stacks.
ContextSwitchData::Thread() is something related to fibers. Pass and use
known thread id from external context instead.
2026-06-17 00:21:51 +02:00
Bartosz Taudul
2e252d3988 Fix indentation level. 2026-06-17 00:14:03 +02:00
Bartosz Taudul
77978b68ca Explain wait stacks in callstack skill. 2026-06-16 23:53:41 +02:00
Bartosz Taudul
d9939362f5 Add wait stack data to the LLM attachment. 2026-06-16 23:53:40 +02:00
Bartosz Taudul
bba05bd3ad Update manual. 2026-06-16 23:33:16 +02:00
Bartosz Taudul
83a4a13cbd Display wait stack label and tooltip in callstack window. 2026-06-16 23:28:24 +02:00
Bartosz Taudul
9e9aabe9a1 Push CallstackViewWait to View::DrawCallstackTable(). 2026-06-16 23:18:40 +02:00
Bartosz Taudul
382a887ce9 Provide default values for View::DrawCallstackTable() parameters. 2026-06-16 23:17:47 +02:00
Bartosz Taudul
fbd1c55151 Extend View::ViewCallstack() to support optional wait stack data. 2026-06-16 23:10:43 +02:00
Bartosz Taudul
398bab8041 Store wait stack data in CallstackView. 2026-06-16 23:05:59 +02:00
Bartosz Taudul
69245e751b Cosmetics. 2026-06-16 23:05:58 +02:00
Marcos Slomp
22abcd272f cosmetic: making things a bit more idiomatic 2026-06-16 11:30:21 -07:00
Marcos Slomp
32b712c120 force inline helper method 2026-06-16 11:24:47 -07:00
Marcos Slomp
704b5c4030 refactor sentinel value 2026-06-16 11:24:27 -07:00
Marcos Slomp
dd6962b42c use queryId arg, not m_tail member 2026-06-16 11:24:12 -07:00
Bartosz Taudul
d571f2bd59 Fix integer trace parameter input field size. 2026-06-16 19:17:18 +02:00
Bartosz Taudul
781938317d Bump pugixml to 1.16. 2026-06-16 18:10:38 +02:00
Marcos Slomp
16cfd25320 potentially faster timestamp query result check
Co-authored-by: Sergio Acereda <sergio.acereda@gmail.com>
2026-06-16 08:36:55 -07:00
Bartosz Taudul
f9365abe4f Do not use samplers in renderer.
Tracy requires some textures to have repeat wrapping mode set. The ImGui
implementation of samplers doesn't make it easy to achieve. Disalbe use
of samplers and rely on texture flags, as done originally.
2026-06-16 16:02:08 +02:00
Bartosz Taudul
3f91f35d59 Merge pull request #1403 from wolfpld/slomp/gl-feature-check
Add routine to check for GL features/extensions at run-time
2026-06-16 14:50:30 +02:00
Marcos Slomp
1de94aa856 add routine to check for GL features/extensions at run-time 2026-06-15 21:19:12 -07:00
230 changed files with 12926 additions and 3676 deletions

View File

@@ -24,8 +24,8 @@ runs:
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build
# same with TRACY_DELAYED_INIT and TRACY_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
# same with TRACY_MANUAL_LIFETIME
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
cmake --build tests/tracy/build --parallel
cmake -E rm -rf tests/tracy/build

49
.github/workflows/android.yml vendored Normal file
View File

@@ -0,0 +1,49 @@
name: android
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: android-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-android:
runs-on: ubuntu-latest
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git ninja nodejs unzip
- name: Setup NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r29
- run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Library v7
run: |
cmake -B build-v7 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a
cmake --build build-v7
- name: Library v8
run: |
cmake -B build-v8 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a
cmake --build build-v8

55
.github/workflows/appimage.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: appimage
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: appimage-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-appimage:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake ninja-build meson pkg-config git ca-certificates \
curl file desktop-file-utils nodejs patchelf \
libegl-dev libxkbcommon-dev libdbus-1-dev libssl-dev \
libffi-dev libexpat1-dev
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Cache wayland
uses: actions/cache@v4
with:
path: profiler/build-appimage/wayland
key: ${{ runner.os }}-wayland-${{ hashFiles('profiler/appimage/build.sh') }}
- name: Build AppImage
run: profiler/appimage/build.sh
env:
GIT_REV: ${{ github.sha }}
- uses: actions/upload-artifact@v4
with:
name: appimage
path: profiler/build-appimage/tracy-profiler-x86_64.AppImage

View File

@@ -7,6 +7,13 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: emscripten-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache

View File

@@ -8,6 +8,13 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: latex-${{ github.ref }}
cancel-in-progress: true
jobs:
build-manual:

74
.github/workflows/linux-cli.yml vendored Normal file
View File

@@ -0,0 +1,74 @@
name: linux-cli
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: linux-cli-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-linux-cli:
runs-on: ubuntu-latest
container: ubuntu:24.04
steps:
- name: Install dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential cmake git ca-certificates curl nodejs
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
- name: Cache CPM packages
uses: actions/cache@v4
with:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
- name: Build capture
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
- name: Build csvexport
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
- name: Build import
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
- name: Build merge
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
- name: Stage release files
run: |
mkdir -p linux-cli
cp update/build/tracy-update linux-cli/
cp capture/build/tracy-capture linux-cli/
cp capture/build/tracy-capture-daemon linux-cli/
cp csvexport/build/tracy-csvexport linux-cli/
cp import/build/tracy-import-chrome linux-cli/
cp import/build/tracy-import-fuchsia linux-cli/
cp merge/build/tracy-merge linux-cli/
strip linux-cli/tracy-*
- uses: actions/upload-artifact@v4
with:
name: linux-cli
path: linux-cli

View File

@@ -7,6 +7,13 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: linux-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
@@ -16,7 +23,7 @@ jobs:
container: archlinux:base-devel
steps:
- name: Install dependencies
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua clang sdl3_image python-jinja
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4
@@ -26,39 +33,49 @@ jobs:
path: ${{ env.CPM_SOURCE_CACHE }}
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Determine build parallelism
run: |
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
echo "CMAKE_PARALLEL=--parallel 2" >> "$GITHUB_ENV"
else
echo "CMAKE_PARALLEL=--parallel" >> "$GITHUB_ENV"
fi
- name: Dyna example / clang
run: |
CC=clang CXX=clang++ cmake -B examples/dyna/build-clang -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-clang $CMAKE_PARALLEL
- name: Dyna example / gcc
run: |
CC=gcc CXX=g++ cmake -B examples/dyna/build-gcc -S examples/dyna -DCMAKE_BUILD_TYPE=Release
cmake --build examples/dyna/build-gcc $CMAKE_PARALLEL
- name: Profiler GUI
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel 2
fi
cmake --build profiler/build $CMAKE_PARALLEL
- name: Update utility
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build update/build --parallel
cmake --build update/build $CMAKE_PARALLEL
- name: Capture utility
run: |
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build capture/build --parallel
cmake --build capture/build $CMAKE_PARALLEL
- name: Csvexport utility
run: |
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build csvexport/build --parallel
cmake --build csvexport/build $CMAKE_PARALLEL
- name: Import utilities
run: |
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build import/build --parallel
cmake --build import/build $CMAKE_PARALLEL
- name: Merge utility
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel
cmake --build merge/build $CMAKE_PARALLEL
- name: Library (cmake)
run: |
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
cmake --build build
cmake --install build
cmake --build build $CMAKE_PARALLEL
env:
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
- name: Library (meson)
@@ -74,6 +91,7 @@ jobs:
cp profiler/build/tracy-profiler bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin

View File

@@ -6,13 +6,21 @@ on:
pull_request:
branches: [ master ]
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: macos-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
jobs:
build-macos:
runs-on: macos-15
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Cache CPM packages
@@ -22,12 +30,16 @@ jobs:
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- name: Install dependencies
run: brew install pkg-config glfw meson
run: brew install pkg-config meson
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Build library
run: meson setup -Dtracy_enable=true build && meson compile -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DBUNDLE=ON -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel 2 --config Release
- name: Build update
run: |
@@ -49,20 +61,19 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel --config Release
- name: Build library
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build && meson compile -C build && meson install -C build
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir -p bin
cp profiler/build/tracy-profiler bin
cp -r profiler/build/tracy-profiler.app bin
cp update/build/tracy-update bin
cp capture/build/tracy-capture bin
cp capture/build/tracy-capture-daemon bin
cp csvexport/build/tracy-csvexport bin
cp import/build/tracy-import-chrome bin
cp import/build/tracy-import-fuchsia bin
cp merge/build/tracy-merge bin
strip bin/tracy-profiler.app/Contents/MacOS/tracy-profiler
strip bin/tracy-update bin/tracy-capture bin/tracy-capture-daemon bin/tracy-csvexport bin/tracy-import-chrome bin/tracy-import-fuchsia bin/tracy-merge
- uses: actions/upload-artifact@v4
with:
name: macos

View File

@@ -7,6 +7,13 @@ on:
branches: [ master ]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: mingw-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache

View File

@@ -4,26 +4,62 @@ on:
release:
types: [published]
permissions:
contents: read
concurrency:
group: release-${{ github.event.release.tag_name }}
cancel-in-progress: false
jobs:
release-build-windows:
uses: ./.github/workflows/windows.yml
release-build-macos:
uses: ./.github/workflows/macos.yml
release-build-manual:
uses: ./.github/workflows/latex.yml
release-build-appimage:
uses: ./.github/workflows/appimage.yml
release-build-linux-cli:
uses: ./.github/workflows/linux-cli.yml
attach-to-release:
needs: [release-build-windows, release-build-manual]
needs: [release-build-windows, release-build-macos, release-build-manual, release-build-appimage, release-build-linux-cli]
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@v4
- name: Create versioned zip
- name: Create versioned Windows zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd windows
zip -r ../windows-$VERSION_NO_V.zip .
- name: Create versioned macOS zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
cd macos
chmod +x tracy-* tracy-profiler.app/Contents/MacOS/*
zip -r ../macos-$VERSION_NO_V.zip .
- name: Create versioned Linux zip
run: |
VERSION="${{ github.event.release.tag_name }}"
VERSION_NO_V="${VERSION#v}"
mkdir linux
cp appimage/* linux/
cp linux-cli/* linux/
cd linux
zip -r ../linux-$VERSION_NO_V.zip .
- uses: softprops/action-gh-release@v2
with:
files: |
windows-*.zip
macos-*.zip
linux-*.zip
manual/tracy.pdf

View File

@@ -8,6 +8,13 @@ on:
workflow_dispatch:
workflow_call:
permissions:
contents: read
concurrency:
group: windows-${{ github.ref }}
cancel-in-progress: true
env:
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
@@ -23,12 +30,10 @@ jobs:
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
restore-keys: ${{ runner.os }}-cpm-
- uses: microsoft/setup-msbuild@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- run: pip install meson ninja
- name: Trust git repo
run: git config --global --add safe.directory '*'
- name: Test application
uses: ./.github/actions/test-tracy
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
@@ -53,14 +58,13 @@ jobs:
run: |
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build merge/build --parallel --config Release
- name: Test application
uses: ./.github/actions/test-tracy
- name: Package artifacts
run: |
mkdir bin
copy profiler\build\Release\tracy-profiler.exe bin
copy update\build\Release\tracy-update.exe bin
copy capture\build\Release\tracy-capture.exe bin
copy capture\build\Release\tracy-capture-daemon.exe bin
copy csvexport\build\Release\tracy-csvexport.exe bin
copy import\build\Release\tracy-import-chrome.exe bin
copy import\build\Release\tracy-import-fuchsia.exe bin

13
.gitignore vendored
View File

@@ -1,16 +1,12 @@
.vs
_build
_compiler
tools/*
*.d
*.o
*.so
*.swp
*.obj
imgui.ini
test/tracy_test
test/tracy_test.exe
*/build/unix/*-*
manual/t*.aux
manual/t*.log
manual/t*.out
@@ -21,19 +17,16 @@ manual/t*.bbl
manual/t*.blg
manual/t*.fdb_latexmk
manual/t*.fls
profiler/build/win32/packages
profiler/build/win32/Tracy.aps
profiler/build-appimage
.deps/
.dirstamp
/_*/**
/**/__pycache__/**
extra/vswhere.exe
extra/tracy-build
.cache
.uv-cache/
.venv/
compile_commands.json
profiler/build/wasm/Tracy-release.*
profiler/build/wasm/Tracy-debug.*
profiler/build/wasm/embed.tracy
examples/ToyPathTracer/Windows/TestCpu
examples/ToyPathTracer/Windows/x64
*.user

View File

@@ -0,0 +1,340 @@
---
name: tracy-gui-verify
description: Build, serve, and drive the Tracy Profiler's emscripten (web) GUI in a headless browser with verified mouse/keyboard semantics. Use when developing or testing the profiler GUI. No X11/Wayland/DISPLAY required — the browser is the display server.
---
# Test the Tracy web GUI in a browser
Build the emscripten profiler GUI into an instrumented temporary workspace,
serve it, and drive it with the browser tool. The harness exports
(`debug_snapshot`/`debug_clear`) expose the app-side mouse/ID state — they
are the ground truth for targeting and verification, not screenshots.
Applies to this repo's emscripten build with ImGui 1.92.9b-docking and
Emscripten 5.0.7. Constants marked M are workstation-dependent — re-verify
them. Everything else is stable; do not re-derive it.
## Setup
```sh
sh <this-skill-dir>/setup.sh # workspace=/tmp/tracy-protocol
# or: TRACY_WEB_WS=/elsewhere TRACY_WEB_REPO=<repo> sh setup.sh
```
`<this-skill-dir>` is the project-scoped `.omp/skills/tracy-gui-verify/`
under the active project root — not a fixed home directory. If multiple
copies exist (one per project that has used this skill), use the copy
inside the repo being tested; `TRACY_WEB_REPO` controls which repo is
copied into the workspace.
The script: fresh source copy (excludes `.git` and build dirs) → applies
`harness.patch` (adds `debug_snapshot`/`debug_clear` exports + event/frame
recording to the emscripten backend, in the COPY only) → configures → builds
Release with the emsdk at `$EMSDK_DIR` (default `~/emsdk`) and the CPM cache at
`~/.cache/cpm`.
**Pitfall:** if the workspace is deleted while a `tracy-web` server is running,
stop the hub process first — a stale server keeps the *deleted* cwd and silently
returns empty responses while the port still accepts connections.
**Pitfall:** a `tracy-web` that died in an earlier session leaves the daemon in
`failed` state: `stop` only reports the old failure, and `start` keeps returning
"Daemon tracy-web has unacknowledged completion notifications" while the port
stays free. Drain the daemon with `hub op=logs name=tracy-web` (the log also
shows the cause), then relaunch with `hub op=restart name=tracy-web` (retained
spec). If the port is NOT free, a stale process owns it — often one serving an
earlier (now-stale) build directory: the new `httpd.py` dies on the bind and
your requests silently hit the old directory. Check `ss -tlnp | grep 8000` and
kill the owner. After any start/restart, verify the server is actually
serving: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/index.html`
must return 200 — the daemon message is not proof of a server.
**After editing the GUI in the repo:** copy the changed files into
`$WS/src/` (same relative path), re-run `cmake --build $WS/build -j$(nproc)`,
reload the page. If a change touches `profiler/src/BackendEmscripten.cpp` or
`profiler/CMakeLists.txt`, the harness patch may no longer apply — re-apply it
manually (keep the `Dbg*` harness section, the `DbgPushEvent`/`DbgPushFrame`
calls in the mouse callbacks + `NewFrame`, and the two `debug_*` exports in
`-sEXPORTED_FUNCTIONS`).
## Serve + open + ready
Server (long-running → `hub`):
```
hub op=start name=tracy-web application=python3 args=["$WS/build/httpd.py"]
cwd=$WS/build ready={port:8000} persist=true
```
`httpd.py` sends the COOP/COEP headers required for pthreads/SharedArrayBuffer —
use it, not a bare http.server.
Browser:
```
browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
```
**Ready signal** (in a `run` cell; `run` code executes in Node scope — page JS
goes through `tab.evaluate`):
```js
let title = null;
for (let i = 0; i < 360; i++) {
title = await tab.evaluate(() => document.title);
if (/ - Tracy Profiler/.test(title)) break;
await new Promise(r => setTimeout(r, 250));
}
```
Title is `Tracy Profiler X.Y.Z` until the preloaded `embed.tracy` (DarkRL
capture) finishes loading, then `<trace> (embed.tracy) - Tracy Profiler X.Y.Z`
(`profiler/src/main.cpp` `SetWindowTitleCallback`). Wait ~1.5 s after, then
confirm the app loop lives: `snap().frames.at(-1).tick > 0`.
## Harness API (ground truth)
```js
const snap = () => tab.evaluate(() => JSON.parse(Module.ccall('debug_snapshot','string',[],[])));
const clear = () => tab.evaluate(() => Module.ccall('debug_clear','void',[],[]));
```
`snap()``{ dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH,
title, cursor, frames:[…64], events:[…64] }` (newest last).
- `frames[]`: one sample **per rAF tick, including idle ticks**. `tick` = main-loop
count (always advances), `f` = `ImGui::GetFrameCount()` (advances only when a
frame actually renders — the idle gate, §Idle), `mx/my` = app mouse position,
`md[3]` = mouse button states, `q` = pending input events, `wa` =
`tracy::s_wasActive` at tick start, `hi`/`ai` = ImGui `HoveredId`/`ActiveId`.
- `events[]`: raw callback data as the app received it. `type` 0=move 1=down
2=up 3=enter 4=leave 5=wheel; `tx/ty` = Emscripten int `targetX/Y`; `ax/ay` =
position passed to ImGui (`-1` for non-move); `t` = DOMHighResTimeStamp.
## Coordinate mapping
```
appX = floor( trunc(cssX - rectL) * dpr ) // device px, what ImGui hit-tests
cssX = appX / dpr // inverse for targeting
screenshot_px = cssX * dpr
```
Chain: DOM `clientX` → Emscripten `targetX = int(clientX - (rect.left|0))`
(`src/lib/libhtml5.js`, `EmscriptenMouseEvent.targetX` is an `int`) → Tracy
`AddMousePosEvent(targetX * dpr, …)` (`BackendEmscripten.cpp`) → ImGui `ImFloor`
(`imgui.cpp` `AddMousePosEvent`). Canvas fills the viewport, so `rectL/rectT`
are 0 (re-read anyway).
**M: `dpr` on this workstation's headless browser is 1.25** (canvas buffer
2000×1125 for a 1600×900 viewport; screenshots are captured at device px).
Never assume 1. App positions come in multiples of `dpr`; verify targeting
within ±1 px via `frames[].mx/my`.
## Click protocol
Click = move, press, refresh through the hold, release, verify — all
against rendered frames. The idle gate (§Idle) renders only 3 frames per
wake, and a hold with no mouse events at all refreshes nothing, so a
fixed-wait down/up from a cold idle can be swallowed (both events received,
zero frames rendered) or released after the wake budget expires. Issuing
mouse moves while the button is down keeps the budget alive: a same-coord
re-move is delivered (verified: it reaches the canvas handler, wakes the
app, and a same-coord hold click straddles cleanly) and is the safest
refresh — zero hover drift; a ±`dx` px wiggle works too, but must stay
inside the hit area:
```js
async function click(cssX, cssY, holdMs = 400) {
await page.mouse.move(cssX, cssY); // 1. ALWAYS move first
await new Promise(r => setTimeout(r, 150)); // 2. >= 1 rendered frame
const pre = await snap();
const pf = pre.frames.at(-1);
await clear();
await page.mouse.down(); // 3. press
let df = null, hiDuring = null;
const t0 = Date.now();
while (Date.now() - t0 < holdMs) {
await new Promise(r => setTimeout(r, 70));
await page.mouse.move(cssX, cssY); // 4. same-coord re-move refreshes the wake budget
const f = (await snap()).frames.at(-1);
if (f.md[0] === 1) { df ??= f.f; hiDuring = f.hi; }
}
await page.mouse.up(); // 5. release
let uf = null;
for (let i = 0; i < 40; i++) {
await new Promise(r => setTimeout(r, 40));
const f = (await snap()).frames.at(-1);
if (f.md[0] === 0) { uf = f.f; break; }
}
await new Promise(r => setTimeout(r, 400));
const s = await snap();
const d = s.dpr, ex = Math.floor(Math.trunc(cssX - s.rectL) * d), ey = Math.floor(Math.trunc(cssY - s.rectT) * d);
return { hoverID: pf.hi, hiDuring, appPos: [pf.mx, pf.my], expected: [ex, ey],
straddle: { df, uf },
ok: df !== null && uf !== null && uf > df && hiDuring === pf.hi &&
Math.abs(pf.mx - ex) <= 1 && Math.abs(pf.my - ey) <= 1 };
}
```
Rules:
1. **Move before every click.** The emscripten mousemove handler is the only
thing that sets the app mouse position; mousedown/mouseup only queue button
state. Fresh page position is invalid (`-FLT_MAX`) — a click without a prior
move activates nothing.
2. **Press and release must each render** in separate frames with the item
hovered (ImGui's `PressedOnClickRelease` — doc table at
`imgui_widgets.cpp:489-497`; input trickling,
`ConfigInputTrickleEventQueue`, defers a same-frame release). The
same-coord refresh is what guarantees rendered frames across the hold
from a cold idle.
3. **Verify every click:** `ok: true` (position landed, press and release each
rendered, hover held — `hiDuring === hoverID`) plus a screenshot diff.
Non-ImGui targets (timeline zone bars) have `hi = 0` by design — for those
verify by screenshot, not hover.
4. **Double-click trap:** two same-position clicks <300 ms apart = double-click
(`MouseDoubleClickTime = 0.3 s`) → zoom/thread-info actions fire. Wait
≥350 ms between repeated clicks at one spot.
5. **Drag:** move → down → (move + wait 70 ms)×n → up. **Wheel:**
`page.mouse.wheel({ deltaY: ±500 })` after positioning. **Keys:**
`page.keyboard.type(..., { delay: 50 })` after focusing the input by click;
`page.keyboard.press('Enter')`.
6. **Danger:** toolbar device x 1032 (css 826) = red power button (leftmost
toolbar icon, hover ID 3313137574), closes the trace view (back to "Get
started"). Never click it unless intended.
## Idle and power saving
`main.cpp DrawContents` keeps an `activeFrames = 3` budget refreshed on any
input event (`tracy::s_wasActive`), a connected client, view animation, or a
queued input; exhausted → `sleep 16 ms` with **no `ImGui::NewFrame` and no GL
draw** (canvas keeps the last frame). Expect ~97 % of ticks to skip rendering
while idle.
- Clicks from a cold idle land only via the click protocol's hold refresh
(§Click protocol) — a fixed-wait down/up can be swallowed by the idle gate
(both events received, zero frames rendered). **Before interacting, assert
`tick`/`f` is advancing**; a stalled main loop (initial trace parse, hidden
tab) renders nothing and drops everything. During the initial load the
cursor style is `wait`.
- `focusLostLimit` (reduce render rate on focus loss) is desktop-only; it does
not exist in the emscripten path.
- `canvas.style.cursor` is **not** a hover oracle: this ImGui build sets the
Hand cursor only in `TextLink`, not on buttons.
## Targeting
Screenshots never resolve targeting. Full-page shots arrive downscaled JPEG
(1024 px wide for a 2000-px device buffer; the factor varies), and
`page.screenshot({ clip })` returns a PNG at device scale — a W×H CSS clip
returns W·dpr × H·dpr px (CSS = clip offset + px/dpr). Icon buttons are
1525 device px wide, so visual estimates carry ±20 px error, enough to hit
the neighboring button or a gap. Target from the app's own hit-testing —
sweep the row and click the run's center:
```js
async function sweepRow(dpr, cssY, devFrom, devTo, step = 4) {
const ranges = []; let cur = null;
for (let dev = devFrom; dev <= devTo; dev += step) {
await page.mouse.move(dev / dpr, cssY);
await new Promise(r => setTimeout(r, 50));
const s = await snap();
const hi = s.frames[s.frames.length - 1].hi;
if (cur && hi === cur.id) cur.end = dev;
else { if (cur) ranges.push(cur); cur = { id: hi, start: dev, end: dev }; }
}
ranges.push(cur);
return ranges.filter(r => r.id !== 0 && r.end - r.start >= 4);
}
```
- Same non-zero `hi` across consecutive steps = one widget; `hi = 0` = gap.
Click the run's center: `click((r.start + r.end) / 2 / dpr, cssY)`.
- **Layout is state-dependent:** docked panels (Statistics, Flame, …) resize the
main window and shift the toolbar — **re-sweep after any state change.**
- **Close popups before sweeping:** while a popup is open, hovers outside the
popup can read `hi = 0` even over live widgets (verified with ZoomPopup over
the toolbar). A % click in ZoomPopup does not close the popup — click an
empty area first.
- Hover IDs are stable across sessions (label hashes) — the map below holds for
the default state (fresh load, no panels, 1600×900, dpr 1.25).
- Zoomed region shots (for correlating IDs with icons): `page.screenshot({clip})`
returns a Buffer — write it with Node `fs`, then `read` the file:
```js
const fs = require('fs');
const buf = await page.screenshot({ clip: { x: 0, y: 0, width: 720, height: 28 } });
fs.writeFileSync('/tmp/toolbar-zoom.png', buf);
```
### Toolbar map — default state (device px; css = device/1.25; row y css 15)
| device x | hover ID | function |
|---|---|---|
| 1032 | 3313137574 | ⏻ power (red, leftmost) — **closes the trace view (DANGER)** |
| 4468 | 608241489 | ⚙ Options gear — opens the Options window (clickable; do not confuse with the power button) |
| 81185 | 2246346107 | 💬 Messages panel toggle |
| 199259 | 3177774389 | 🔍 Find zone toggle (search input hover ID 3637961452, css ~x 100440, y ~90) |
| 271367 | 1419322276 | 📊 Statistics toggle |
| 383451 | 322727105 | 🔥 Flame graph toggle |
| 463551 | 37993378 | 💾 Memory graph toggle |
| 567667 | 2133895361 | ⚖ Compare toggle |
| 679735 | 1808024465 | ⌗ Info (trace information) toggle |
| 751775 | 2535445525 | 🛠 wrench → ToolsPopup (Playback, CPU data, Annotations, Limits, Wait stacks, Frame statistics) |
| 791811 | 3101356487 | 📖 book → User manual toggle |
| 827851 | 2335673581 | 🔍+ → ZoomPopup (50300 % user scale) |
| 864888 | 1263603982 | ◀ previous frame — focuses the timeline on the previous frame (view range shrinks to the frame's timespan) |
| ~8901010 | 0 | frame set name + count text ("Frames: N") — LMB jumps to a specified frame (custom hit-test, like timeline zones) |
| 10121036 | 2142681495 | ▶ next frame — focuses the timeline on the next frame (verified: view range 25.82 s → 203.38 ms) |
| 10521072 | 2961929287 | ▼ frame set selection (switch the active frame set) |
Button names and functions: the user manual, *Control menu* (`manual/tracy.md`). The
web build omits *Connection* (live capture only) and *Tracy Assist* (desktop only).
## User scale (DPI zoom)
The 🔍+ button (hover ID 2335673581) — *Display scale* in the user manual —
opens a ZoomPopup with 50300 % steps.
In the web build this scales the UI **layout only**: fonts,
`Style::ScaleAllSizes`, and window sizes (`main.cpp SetupDPIScale`,
`scale = devicePixelRatio × userScale`). The canvas buffer and the mouse→app
mapping stay at `devicePixelRatio` (`BackendEmscripten.cpp`) — hit regions
become physically larger in the same coordinate space, targeting precision is
unchanged, and the standard click protocol works as-is.
Use it when small buttons are hard to hit or details are hard to read in
screenshots. At 200 % every toolbar hit region is roughly 2× (verified: power
16→40 px, gear 25→50 px, Messages 105→190 px wide; row height 24→46 device
px). The layout **reflows completely** at any scale — re-sweep after changing
it and after setting it back. `userScale` is session-only unless Options →
"Save UI scale" is on; a fresh page load starts at 100 %.
## Limitations
- A *connected* live-streaming client cannot be tested in the web build (the
browser GUI cannot open a TCP server). The embedded-trace GUI is the
live-app proxy: real trace data, full GUI.
- `embed.tracy` (10 MB) is downloaded from share.nereid.pl at configure time —
network needed once per fresh workspace; the file then lives in `$WS/build`.
## References
Sources for the behavior the rules above encode — consult them when re-verifying
a constant or a rule fails:
- `profiler/src/BackendEmscripten.cpp` — emscripten input handlers, canvas/dpr
sizing, cursor mapping.
- `profiler/src/main.cpp:540-574` — idle gate (`activeFrames` budget).
- `profiler/src/profiler/TracyMouse.cpp` — per-frame click state cache.
- `profiler/CMakeLists.txt:334-353` — emscripten link options.
- ImGui 1.92.9b (vendored copy in the CPM cache, `~/.cache/cpm/imgui/`):
`imgui_widgets.cpp:483-545` (click semantics table), `imgui.cpp:11278-11403`
(event trickling), `imgui.cpp:1736` (trickle default on), `imgui.cpp:1992-2016`
(AddMousePosEvent floor/dedup).
- Emscripten 5.0.7: `src/lib/libhtml5.js` (`fillMouseEventData`,
`targetX = e.clientX - (rect.left|0)` into HEAP32), `system/include/emscripten/html5.h`
(`EmscriptenMouseEvent.targetX` is `int`; `EmscriptenWheelEvent` embeds a
`mouse` sub-struct).
- Upstream: imgui issue #4921 (new input event API — events spread over
multiple frames), PR #2525 (same-frame click/release), issue #1992 (rapid
down/up "soft clicks" dropped).

View File

@@ -0,0 +1,221 @@
diff -r a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt
--- a/profiler/CMakeLists.txt
+++ b/profiler/CMakeLists.txt
@@ -340,7 +340,7 @@
-sMAXIMUM_MEMORY=4gb
-sSTACK_SIZE=1048576
-sPTHREAD_POOL_SIZE=8
- -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
+ -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard,_debug_snapshot,_debug_clear
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
diff -r a/profiler/src/BackendEmscripten.cpp b/profiler/src/BackendEmscripten.cpp
--- a/profiler/src/BackendEmscripten.cpp
+++ b/profiler/src/BackendEmscripten.cpp
@@ -1,6 +1,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <cstdarg>
+#include <algorithm>
#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h>
@@ -10,7 +12,146 @@
#include "Backend.hpp"
#include "RunQueue.hpp"
#include "profiler/TracyImGui.hpp"
+#include "imgui_internal.h"
+// ================= DEBUG HARNESS (temporary copy, not for upstream) =================
+struct DbgEvent
+{
+ int type; // 0=move 1=down 2=up 3=enter 4=leave 5=wheel
+ int button;
+ int targetX;
+ int targetY;
+ double appX; // what was passed to ImGui (targetX * dpr), -1 if n/a
+ double appY;
+ double t; // DOMHighResTimeStamp
+ int wheelX;
+ int wheelY;
+};
+struct DbgFrame
+{
+ double t;
+ int tick; // emscripten main-loop tick count (rAF)
+ int imguiFrame; // ImGui::GetFrameCount()
+ float mx, my; // io.MousePos
+ int md0, md1, md2;
+ int queue; // pending ImGui input events
+ int wasActive; // tracy::s_wasActive at tick start
+ unsigned hi; // ImGui hovered item ID
+ unsigned ai; // ImGui active item ID
+};
+static DbgEvent s_dbgEvents[512];
+static int s_dbgEventHead = 0, s_dbgEventN = 0;
+static DbgFrame s_dbgFrames[512];
+static int s_dbgFrameHead = 0, s_dbgFrameN = 0;
+static int s_dbgTick = 0;
+
+static void DbgPushEvent( int type, int button, int tx, int ty, double ax, double ay, double t, int wx = 0, int wy = 0 )
+{
+ auto& e = s_dbgEvents[s_dbgEventHead];
+ e.type = type; e.button = button; e.targetX = tx; e.targetY = ty;
+ e.appX = ax; e.appY = ay; e.t = t; e.wheelX = wx; e.wheelY = wy;
+ s_dbgEventHead = ( s_dbgEventHead + 1 ) % 512;
+ if ( s_dbgEventN < 512 ) s_dbgEventN++;
+}
+
+static void DbgPushFrame()
+{
+ auto& f = s_dbgFrames[s_dbgFrameHead];
+ auto& io = ImGui::GetIO();
+ f.t = EM_ASM_DOUBLE( { return performance.now(); } );
+ f.tick = s_dbgTick;
+ f.imguiFrame = ImGui::GetFrameCount();
+ f.mx = io.MousePos.x; f.my = io.MousePos.y;
+ f.md0 = io.MouseDown[0]; f.md1 = io.MouseDown[1]; f.md2 = io.MouseDown[2];
+ auto ctx = ImGui::GetCurrentContext();
+ f.queue = ctx ? int( ctx->InputEventsQueue.Size ) : -1;
+ f.wasActive = tracy::s_wasActive;
+ f.hi = ctx ? ctx->HoveredId : 0;
+ f.ai = ctx ? ctx->ActiveId : 0;
+ s_dbgFrameHead = ( s_dbgFrameHead + 1 ) % 512;
+ if ( s_dbgFrameN < 512 ) s_dbgFrameN++;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE void debug_clear()
+{
+ s_dbgEventHead = s_dbgEventN = s_dbgFrameHead = s_dbgFrameN = 0;
+}
+
+// Escape a C string for embedding in a JSON string literal (", \, control chars).
+static void JsonEscape( char* out, size_t outsz, const char* in )
+{
+ size_t o = 0;
+ for( const unsigned char* p = ( const unsigned char* )in; *p; p++ )
+ {
+ if ( *p == '"' || *p == '\\' )
+ {
+ if ( o + 2 >= outsz ) break;
+ out[o++] = '\\';
+ out[o++] = char( *p );
+ }
+ else if ( *p < 0x20 )
+ {
+ if ( o + 6 >= outsz ) break;
+ o += size_t( snprintf( out + o, outsz - o, "\\u%04x", *p ) );
+ }
+ else
+ {
+ if ( o + 1 >= outsz ) break;
+ out[o++] = char( *p );
+ }
+ }
+ out[o] = 0;
+}
+
+extern "C" EMSCRIPTEN_KEEPALIVE const char* debug_snapshot()
+{
+ static char buf[512 * 1024];
+ size_t off = 0;
+ auto put = [ & ]( const char* fmt, ... )
+ {
+ if ( off + 1 >= sizeof( buf ) ) return;
+ va_list args;
+ va_start( args, fmt );
+ int n = vsnprintf( buf + off, sizeof( buf ) - off - 1, fmt, args );
+ va_end( args );
+ if ( n > 0 ) off = std::min( off + size_t( n ), sizeof( buf ) - 1 );
+ };
+ const auto dpr = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
+ const auto innerW = EM_ASM_INT( { return window.innerWidth; } );
+ const auto innerH = EM_ASM_INT( { return window.innerHeight; } );
+ const auto bufW = EM_ASM_INT( { return document.getElementById( 'canvas' ).width; } );
+ const auto bufH = EM_ASM_INT( { return document.getElementById( 'canvas' ).height; } );
+ const auto rectL = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.left; } );
+ const auto rectT = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.top; } );
+ const auto rectW = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.width; } );
+ const auto rectH = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.height; } );
+ char title[256];
+ EM_ASM( { stringToUTF8( document.title, $0, 256 ); }, title );
+ char cursor[64];
+ EM_ASM( { stringToUTF8( document.getElementById( 'canvas' ).style.cursor || "", $0, 64 ); }, cursor );
+ char title_e[1536];
+ char cursor_e[384];
+ JsonEscape( title_e, sizeof( title_e ), title );
+ JsonEscape( cursor_e, sizeof( cursor_e ), cursor );
+ put( "{\"dpr\":%.6f,\"innerW\":%d,\"innerH\":%d,\"bufW\":%d,\"bufH\":%d,\"rectL\":%.3f,\"rectT\":%.3f,\"rectW\":%.3f,\"rectH\":%.3f,\"title\":\"%s\",\"cursor\":\"%s\",\"frames\":[", dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH, title_e, cursor_e );
+ const int fn = std::min( s_dbgFrameN, 64 );
+ for ( int i = 0; i < fn; i++ )
+ {
+ auto& f = s_dbgFrames[ ( s_dbgFrameHead - fn + i + 512 ) % 512 ];
+ put( "%s{\"t\":%.3f,\"tick\":%d,\"f\":%d,\"mx\":%.1f,\"my\":%.1f,\"md\":[%d,%d,%d],\"q\":%d,\"wa\":%d,\"hi\":%u,\"ai\":%u}", i ? "," : "", f.t, f.tick, f.imguiFrame, f.mx, f.my, f.md0, f.md1, f.md2, f.queue, f.wasActive, f.hi, f.ai );
+ }
+ put( "],\"events\":[");
+ const int en = std::min( s_dbgEventN, 64 );
+ for ( int i = 0; i < en; i++ )
+ {
+ auto& e = s_dbgEvents[ ( s_dbgEventHead - en + i + 512 ) % 512 ];
+ put( "%s{\"type\":%d,\"btn\":%d,\"tx\":%d,\"ty\":%d,\"ax\":%.3f,\"ay\":%.3f,\"t\":%.3f,\"wx\":%d,\"wy\":%d}", i ? "," : "", e.type, e.button, e.targetX, e.targetY, e.appX, e.appY, e.t, e.wheelX, e.wheelY );
+ }
+ put( "]}");
+ buf[off] = 0;
+ return buf;
+}
+
static std::function<void()> s_redraw;
static std::function<void(float)> s_scaleChanged;
static std::function<int(void)> s_isBusy;
@@ -223,32 +364,38 @@
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
tracy::s_wasActive = true;
+ DbgPushEvent( 1, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mouseup_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, false );
tracy::s_wasActive = true;
+ DbgPushEvent( 2, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_mousemove_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
const auto scale = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
ImGui::GetIO().AddMousePosEvent( e->targetX * scale, e->targetY * scale );
tracy::s_wasActive = true;
+ DbgPushEvent( 0, -1, e->targetX, e->targetY, e->targetX * scale, e->targetY * scale, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( false );
tracy::s_wasActive = true;
+ DbgPushEvent( 4, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
- emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
+ emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddFocusEvent( true );
tracy::s_wasActive = true;
+ DbgPushEvent( 3, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
return EM_TRUE;
} );
emscripten_set_wheel_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenWheelEvent* e, void* ) -> EM_BOOL {
ImGui::GetIO().AddMouseWheelEvent( e->deltaX * -0.05, e->deltaY * -0.05 );
tracy::s_wasActive = true;
+ DbgPushEvent( 5, -1, e->mouse.targetX, e->mouse.targetY, -1, -1, e->mouse.timestamp, int( e->deltaX ), int( e->deltaY ) );
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
@@ -298,6 +445,8 @@
void Backend::NewFrame( int& w, int& h )
{
+ s_dbgTick++;
+ DbgPushFrame();
const auto scale = GetDpiScale();
if( scale != s_prevScale )
{

View File

@@ -0,0 +1,102 @@
#!/bin/sh
# setup.sh - build the instrumented Tracy web GUI workspace (see SKILL.md).
#
# Usage:
# sh setup.sh
# TRACY_WEB_WS=/custom/ws TRACY_WEB_REPO=<repo> sh setup.sh
#
# Idempotent: reuses an existing source copy and build dir, skips
# already-applied harness patch hunks, rebuilds.
set -e
WS="${TRACY_WEB_WS:-/tmp/tracy-protocol}"
# The skill is project-scoped: <repo>/.omp/skills/tracy-gui-verify/
SKILL_DIR=$(cd "$(dirname "$0")" && pwd)
if [ -n "$TRACY_WEB_REPO" ]; then
REPO="$TRACY_WEB_REPO"
else
REPO=$(cd "$SKILL_DIR/../../.." 2>/dev/null && pwd) || REPO=""
fi
EMSDK="${EMSDK_DIR:-$HOME/emsdk}"
[ -x "$EMSDK/emsdk" ] || { echo "error: emsdk not found at $EMSDK (set EMSDK_DIR)" >&2; exit 1; }
[ -f "$REPO/profiler/CMakeLists.txt" ] || { echo "error: not a Tracy repo: '$REPO' (set TRACY_WEB_REPO)" >&2; exit 1; }
. "$EMSDK/emsdk_env.sh" >/dev/null 2>&1 || true
command -v emcc >/dev/null || { echo "error: emcc not on PATH after emsdk_env.sh" >&2; exit 1; }
if [ ! -f "$WS/src/profiler/CMakeLists.txt" ]; then
echo "[setup] fresh source copy -> $WS/src (from $REPO)"
mkdir -p "$WS/src"
# -C applies to both archive creation and extraction; exclude all build dirs
tar -C "$REPO" \
--exclude=.git \
--exclude='*/build*' \
-cf - . | tar -xf - -C "$WS/src"
else
echo "[setup] reusing source copy in $WS/src (copy changed repo files into it first)"
fi
# The harness is fully in place iff each patch hunk's signature line is present.
# patch --forward skips a whole file once its first hunk is detected as already
# applied, so a failed later hunk is invisible in patch's output and exit code —
# verify the content instead of trusting either.
harness_complete()
{
grep -q '_debug_snapshot' "$WS/src/profiler/CMakeLists.txt" \
&& grep -qF 'EMSCRIPTEN_KEEPALIVE const char* debug_snapshot' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 'DbgPushEvent( 1,' "$WS/src/profiler/src/BackendEmscripten.cpp" \
&& grep -qF 's_dbgTick++;' "$WS/src/profiler/src/BackendEmscripten.cpp"
}
echo "[setup] applying instrumentation harness"
if harness_complete; then
echo "[setup] harness already applied"
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
patch -p1 -d "$WS/src" --forward --no-backup-if-mismatch < "$SKILL_DIR/harness.patch" \
|| {
if harness_complete; then
# mixed run: some hunks already present (skipped), the rest applied
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
else
rej=$(ls "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej" 2>/dev/null || true)
if [ -n "$rej" ]; then
kept="The unapplied hunks are kept in:
$(printf '%s\n' $rej | sed 's/^/ /')"
else
kept="No .rej files were produced (fatal patch error); the patch output above is the only evidence."
fi
cat >&2 <<EOF
error: harness patch did not fully apply — $WS/src has drifted from the repo.
$kept
Do not build from this workspace. Either re-apply the harness by hand
(SKILL.md, "After editing the GUI in the repo"), or start fresh:
rm -rf $WS && sh <this-skill-dir>/setup.sh
EOF
exit 1
fi
}
fi
if [ ! -f "$WS/build/CMakeCache.txt" ]; then
echo "[setup] configuring"
cmake -S "$WS/src/profiler" -B "$WS/build" \
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
-DCMAKE_BUILD_TYPE=Release \
-DCPM_SOURCE_CACHE="$HOME/.cache/cpm"
fi
echo "[setup] building (first full build ~3-5 min, incremental ~30 s)"
cmake --build "$WS/build" -j "$(nproc)"
cat <<EOF
[setup] done.
workspace : $WS
repo : $REPO
emcc : $(emcc --version | sed -n 1p)
serve : hub op=start name=tracy-web application=python3 args=[$WS/build/httpd.py] cwd=$WS/build ready={port:8000} persist=true
open : browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
ready : document.title matches / - Tracy Profiler/ (see SKILL.md)
EOF

View File

@@ -121,12 +121,12 @@ set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyC
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF TracyClient)
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF TracyClient)
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF TracyClient)
set_option(TRACY_NO_WAIT_STACKS "Disable capture of context switch call stacks" OFF TracyClient)
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF TracyClient)
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF TracyClient)
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF TracyClient)
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF TracyClient)
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF TracyClient)
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF TracyClient)
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF TracyClient)
set_option(TRACY_FIBERS "Enable fibers support" OFF TracyClient)
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF TracyClient)
@@ -136,7 +136,7 @@ set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported"
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF TracyClient)
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF TracyClient)
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF TracyClient)
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF TracyClient)
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address" OFF TracyClient)
set_option(TRACY_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
# advanced
@@ -152,9 +152,6 @@ if(rocprofiler-sdk_FOUND)
endif()
# handle incompatible combinations
if(TRACY_MANUAL_LIFETIME AND NOT TRACY_DELAYED_INIT)
message(FATAL_ERROR "TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
endif()
if(TRACY_DISALLOW_HW_TIMER AND NOT TRACY_TIMER_FALLBACK)
message(FATAL_ERROR "TRACY_DISALLOW_HW_TIMER can not be activated with disabled TRACY_TIMER_FALLBACK")
endif()
@@ -182,7 +179,8 @@ set(tracy_includes
${TRACY_PUBLIC_DIR}/tracy/TracyMetal.hmm
${TRACY_PUBLIC_DIR}/tracy/TracyOpenCL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyOpenGL.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp)
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp
${TRACY_PUBLIC_DIR}/tracy/TracyWebGPU.hpp)
set(client_includes
${TRACY_PUBLIC_DIR}/client/tracy_concurrentqueue.h
@@ -213,15 +211,19 @@ set(common_includes
${TRACY_PUBLIC_DIR}/common/TracyAlign.hpp
${TRACY_PUBLIC_DIR}/common/TracyAlloc.hpp
${TRACY_PUBLIC_DIR}/common/TracyApi.h
${TRACY_PUBLIC_DIR}/common/TracyAssert.hpp
${TRACY_PUBLIC_DIR}/common/TracyColor.hpp
${TRACY_PUBLIC_DIR}/common/TracyForceInline.hpp
${TRACY_PUBLIC_DIR}/common/TracyFormat.h
${TRACY_PUBLIC_DIR}/common/TracyMutex.hpp
${TRACY_PUBLIC_DIR}/common/TracyProtocol.hpp
${TRACY_PUBLIC_DIR}/common/TracyQueue.hpp
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
${TRACY_PUBLIC_DIR}/common/TracyString.hpp
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
${TRACY_PUBLIC_DIR}/common/TracyVersion.hpp
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
@@ -274,6 +276,11 @@ configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TracyConfig.cmake
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
COMPATIBILITY SameMinorVersion)
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
option(TRACY_CLIENT_PYTHON "Whether to build Tracy python client library" OFF)

View File

@@ -1,5 +1,7 @@
@PACKAGE_INIT@
set(TRACY_VERSION_STRING "@TRACY_VERSION_STRING@")
include(CMakeFindDependencyMacro)
find_dependency(Threads REQUIRED)

156
NEWS
View File

@@ -2,16 +2,66 @@ Note: There is no guarantee that version mismatched client and server will
be able to talk with each other. Network protocol breakages won't be listed
here.
vx.xx.x (2026-xx-xx)
v0.14.1 (2026-08-22)
--------------------
- Dropped support for loading pre-0.11 traces.
- Silenced MSVC warning C4366: The result of the unary '&' operator may be
unaligned.
- Fixed section setup in on-demand mode.
- Hardened string length encoding in protocol.
- Fixed compilation failure related to VSync when targeting 32-bit on
Windows.
- Added TRACY_NO_WAIT_STACKS compile time macro.
- Improved documentation of requirements in edge cases:
- Memory allocation reporting needs to be done under a lock.
- Manual lifetime management requires shutdown of the profiler.
- Documented how zones behave when crossing the on-demand connection
boundary.
- Mention that statistics time range limit also affects source view.
- Memory events are now attributed to virtual fiber threads.
- Added TRACY_ASSERT macro allowing user-defined handling of asserts in the
profiler.
- Fixed csvexport options: -t, --sep, --filter.
- Configuration of plot color now matches the documented RGB channels order.
- Link profiler executable with libdl (required by old glibc versions).
- Improve coverage of link-time macro mismatch detection.
- Removed TRACY_DELAYED_INIT option. It was no-op since 2020.
- Fixed illegal instruction in rpmalloc on ARMv6 and older.
- Fixed handling of source code queries in no-callstack builds. Previously
the profiling session would hang at exit.
- Hardened Lua on-demand handling.
- Fixed system tracing on Linux kernels older than 4.1.
- Linux system tracing setup failures are now reported via Tracy debug
message functionality.
- Improved version handling in CMake / Meson / Python build systems.
- Fixed D3D12 readback buffer error.
- Child zone list in zone info window can be now sorted by name or by time.
- File dialog failures (e.g. related to the XDG portal) are now reported
in the modal dialog.
- Fixed NO_FILESELECTOR builds.
- Fixed possible profiler hang when there was not enough space to draw
a plot on the timeline.
- Update check can be now interrupted, e.g. when exiting the profiler.
- Patched the file selector library to use modern Wayland protocol for
setting parent-child window relation.
v0.14.0 (2026-08-09)
--------------------
- API break: removed "secure" variants of memory alloc and free macros. The
secure code path is now always enabled. Migrate by removing "Secure" from
the macros you use, e.g. TracySecureAlloc(...) -> TracyAlloc(...).
- Added printf-style variants of the C API zone text and name macros:
TracyCZoneTextF and TracyCZoneNameF now accept a format string and
arguments, instead of a fixed-length text buffer.
- Added tracy-capture-daemon for automated multi-client trace capture.
- Added tracy-merge utility for combining multiple trace files into one.
- Added support for Windows on ARM64 with MSVC.
- Added support for WebGPU.
- Binary releases are now also provided for macOS (needs manual quarantine
handling) and Linux (AppImage with Ubuntu 24.04 base, requires Wayland).
- Trace-specific settings storage has been completely overhauled. It is now
possible to make the settings sidecar file public, saved next to the trace
file.
@@ -22,9 +72,44 @@ vx.xx.x (2026-xx-xx)
- Flame graph window.
- Call stack window.
- Statistics window (sampling mode).
- Wait stacks have a separate setting for external frames, as you would
typically want to know what caused the wait state, which can be inferred
from the external code.
- External frames are now dimmed out in call stacks in various parts of UI.
- Sample markers on the timeline are now colored according to sample type:
- Green for samples in the profiled program,
- Blue for samples in external code,
- Red for samples in kernel.
- Single-line call stacks now have ellipsis at the end, if there are frames
remaining.
- Sample entry stacks window can now also show the stacks by which a symbol
was reached, i.e. it was present anywhere on the call stack, not only when
it was executing. Recursive re-entries can be either ignored or counted
separately.
- Entry stacks are now also available for symbols which were only reached,
but never directly sampled, e.g. in the assembly view of a call site with
purely child sample costs, or in the statistics window for symbols which
only have inclusive counts.
- Fixed inclusive sample counts of symbols with inline functions. Each
sample was counted multiple times: once for the symbol itself and once for
every of its inline functions present on the call stack.
- Fixed context switch samples leaking into per-symbol sample lists and
child sample data on trace load. This inflated range-limited sample counts
and child call costs, and made loaded traces disagree with live sessions,
where such samples are excluded from sampling statistics.
- Fixed wrong results of range-limited child call queries during live
capture, when some samples had to wait for context switch data to arrive.
- Fixed crashes when opening the sample entry stacks window for symbols
without any sampling data.
- Sampling statistics percentages are now calculated against the number of
samples that were actually collected. Previously, enabling the range
filter switched the calculation to a theoretical single-thread estimate,
which changed both the meaning and the scale of the displayed values.
- Find zone sample count percentages are now relative to the matched zones,
consistent with the time percentages in the same list, instead of being
relative to the whole trace.
- Context switch samples are now consistently excluded from all sampling
statistics views.
- System tracing on Windows has been refactored to be more robust.
- Tracing on Arm macOS will now have more precise timer readings.
- Extended CUDA support to track some previously missing memory operations.
@@ -36,7 +121,8 @@ vx.xx.x (2026-xx-xx)
- Added inline call stack list to the zone information window.
- The "call stack" button for opening the call stack in a separate window
is still there.
- Call stacks are now also displayed in zone tooltips (single-line).
- Call stacks are now also displayed in zone and memory allocation tooltips
(single-line).
- Implemented heuristic reconstruction of zone call stacks for zones that
were captured without call stacks.
- Requires sampling to be enabled, and at least one sample in the zone
@@ -78,13 +164,16 @@ vx.xx.x (2026-xx-xx)
- Chat topic description is now provided, based on the first user question.
- Each assistant reply is now labeled with used model and reply time.
- Follow-up questions can be automatically suggested.
- It is now possible to alter the assistant personality.
- Expanded LLM attachments.
- You can now attach complete symbol assembly.
- Entry call stacks can be now attached (previously it was only regular
call stacks).
- Crash call stack attachments are now annotated with crash info.
- Wait stack attachments now contain wait metadata.
- Source code can be attached (also with execution costs in symbol view).
- Zone histogram data can be attached for analysis.
- Added MCP server.
- Markdown renderer improvements.
- Tables are now properly rendered.
- Tasklist rendering has been implemented.
@@ -108,6 +197,7 @@ vx.xx.x (2026-xx-xx)
locations won't be fixed.
- Call stack window will now display notification if viewing a crash call
stack.
- Call stack window will now show a proper label if viewing a wait stack.
- Removal of Tracy crash handler stack from the reported crash call stack
should now work again on Linux.
- In disassembly line view, source file names are now displayed instead of
@@ -119,6 +209,8 @@ vx.xx.x (2026-xx-xx)
- Added TRACY_DISALLOW_HW_TIMER define for virtualized environments and WSL2,
which may not have reliable access to hardware timer registers. Falls back
to standard library timer with reduced resolution.
- Fixed TRACY_NO_CODE_TRANSFER define. It didn't prevent source code transfer
before.
- Fixed DPI scaling on macOS.
- Thread names are no longer truncated to 15 characters on Apple.
- Executable path is now inspected when looking for PDB files on Windows.
@@ -148,8 +240,9 @@ vx.xx.x (2026-xx-xx)
- Mismatched versions will break at linking.
- As a reminder, Tracy *always* required using the same set of compilation
options for the entire program.
- Message windows will now properly show full message in a tooltip for
multi-line messages.
- Message windows were improved.
- Full message will be now displayed in a tooltip for multi-line messages.
- Added right click context menu with option to copy message to clipboard.
- Greatly improved the in-profiler user manual.
- There is now chapter tree and the manual contents are displayed section
by section.
@@ -183,7 +276,12 @@ vx.xx.x (2026-xx-xx)
- The profiler can no longer be built with the statistics disabled.
- Fixed NVCC builds.
- Fixed possible lockups in Vulkan timer calibration loop.
- The flame graph view now supports zooming in and panning with the mouse.
- Flame graph improvements.
- The flame graph view now supports zooming in and panning with the mouse.
- Sampling flame graph items can be now also grouped by name, in addition
to symbol address.
- Sampling flame graph no longer omits display of unknown symbols. These
are now aggregated into blocks named after the executable image.
- General application crash information polish in the profiler UI.
- The achievements system has been converted to use markdown renderer.
- Offline symbol resolution with the update utility now supports custom
@@ -194,11 +292,59 @@ vx.xx.x (2026-xx-xx)
with the TRACY_OPENGL_AUTO_CALIBRATION compilation define. Note that this
requires a full CPU/GPU sync on each calibration event. These events will
not fire more often than once every second.
- Additional hardening of OpenGL tracing.
- Added missing C API for shared locks.
- Implemented semi-unique, nonsense random name generator.
- Can be used to set a trace description.
- Will be used to provide default description for newly added annotations.
- Polished look and feel of annotation regions on the timeline.
- Annotations can now be hidden.
- It is now possible to create annotations from time range limits.
- Expanded available tests.
- The previously existing "test" program now lives in "tests/tracy".
- The tests directory also contains some repro cases for solved problems.
- Expanded examples showing how to use the profiler.
- Includes "Dyna.net", a small 2D game that is a variation of Bomberman.
- Made numeric input field for trace parameters usable again.
- Added new type of trace parameter that exposes a trigger button.
- Added "sections" feature for marking high-level sections of program
execution.
- For example, a game with multiple levels may have each of the levels
marked as a section.
- Each section is a distinct region not restricted by other sections.
- Multiple sections can overlap.
- Sections can be assigned to user-defined categories.
- Can be used to set time range limits.
- Exposed via Lua bindings.
- Find zone window now also displays coefficient of variation, in addition
to standard deviation.
- Moved frame statistics from trace information window to its own window.
- Added various statistical data to bring frame statistics up to par with
the find zone window.
- Added a new time range limit for frame statistics. This accompanies the
previously existing "limit to view" option.
- More robust statistics for functions that recursively call themselves.
- General uplift of the compare traces window.
- Added all the statistical parameters displayed in find zone window that
were missing here.
- Frame comparison now also displays FPS values.
- The "more" and "less" indicators are now color coded to show their
importance. The amount of color these keywords are printed with is now
dependant on how divergent the numeric values are.
- It is now possible to limit time range for both traces that are compared
to their respective annotations or sections.
- Frame image playback improvements.
- Playback can be looped.
- The range of played frame images can be limited, either manually, or by
copying annotation, section, or time range limit. There's an "require
frame coverage" option to only include frames that are at least 75%
within the source region.
- Most windows now have a minimum width, in order to make sure all controls
provided in the window are visible. Previously it was easy to oversee some
button that was out of the window draw area.
- The ignore memory faults functionality (enabled by default if profiling
programs on Apple platforms) will now also ignore allocating memory at the
same location twice, without an intermediate free. Thanks, Tim Apple!
v0.13.1 (2025-12-11)

31
capture/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -137,6 +137,7 @@ int main( int argc, char** argv )
{
worker.Disconnect();
s_disconnect.store(false, std::memory_order_relaxed );
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
break;
}

View File

@@ -168,7 +168,13 @@ void CaptureThread( ClientSession* session, const std::string& address, uint16_t
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
if( worker.IsConnected() )
{
worker.Disconnect();
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
}
printf( "\nSaving %s...", outputFile.c_str() );
fflush( stdout );

View File

@@ -0,0 +1,13 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..561ab7782 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -42,7 +42,7 @@
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
-//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
+#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r().
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)

View File

@@ -0,0 +1,13 @@
diff --git i/imconfig.h w/imconfig.h
index b40db3898..f7f864394 100644
--- i/imconfig.h
+++ w/imconfig.h
@@ -50,7 +50,7 @@
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
-//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
+#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_BITMAP // Disable default embedded bitmap font (ProggyClean). Remove ~9 KB from output binary. AddFontDefaultBitmap() will assert.
//#define IMGUI_DISABLE_DEFAULT_FONT_VECTOR // Disable default embedded vector font (ProggyForever), Remove ~14 KB from output binary. AddFontDefaultVector() will assert.
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available

View File

@@ -0,0 +1,13 @@
diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp
index a9e32b7ac..2cdbc4812 100644
--- a/backends/imgui_impl_opengl3.cpp
+++ b/backends/imgui_impl_opengl3.cpp
@@ -1069,7 +1069,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
bd->HasPolygonMode = (!bd->GlProfileIsES2 && !bd->GlProfileIsES3);
#endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_BIND_SAMPLER
- bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
+ //bd->HasBindSampler = (bd->GlVersion >= 330 || bd->GlProfileIsES3);
#endif
bd->HasClipOrigin = (bd->GlVersion >= 450);
#ifdef IMGUI_IMPL_OPENGL_HAS_EXTENSIONS

View File

@@ -0,0 +1,14 @@
diff --git a/imgui_tables.cpp b/imgui_tables.cpp
--- a/imgui_tables.cpp
+++ b/imgui_tables.cpp
@@ -4082,7 +4082,9 @@
}
if (load_flags & ImGuiTableFlags_Reorderable)
column->DisplayOrder = column_settings->DisplayOrder;
- else
+ else if (column_settings->Index != -1)
+ // An Index of -1 means this column had no "Column N" line in the .ini file (columns without saved
+ // data are not written). Keep the display order as initialized (identity for non-reorderable tables). (#9519)
column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults()
if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1);

View File

@@ -0,0 +1,203 @@
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index d6a9edc..abef8d7 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -32,18 +32,18 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
if(NFD_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED wayland-client)
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
- set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
+ set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
- COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
+ COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
add_custom_command(
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
- COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
+ COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
)
- list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
+ list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c)
endif()
endif()
diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp
index 76fdcd7..cec691d 100644
--- a/src/nfd_gtk.cpp
+++ b/src/nfd_gtk.cpp
@@ -433,17 +433,17 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
#if defined(NFD_WAYLAND)
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
GdkWindow* childWindow = static_cast<GdkWindow*>(context);
// GDK doesn't modify the string, even though it takes char*, so we can cast away the constness
gdk_wayland_window_set_transient_for_exported(childWindow, const_cast<char*>(handle));
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
// This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
@@ -645,17 +645,17 @@ struct NativeWindowParenter {
#if defined(NFD_WAYLAND)
void SetParentWayland(GdkWindow* childWindow) {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindowHandle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindowHandle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(childWindow));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(childWindow));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
}
diff --git a/src/nfd_linux_shared.hpp b/src/nfd_linux_shared.hpp
index 1413bac..e826f1e 100644
--- a/src/nfd_linux_shared.hpp
+++ b/src/nfd_linux_shared.hpp
@@ -14,7 +14,7 @@
#ifdef NFD_WAYLAND
#include <wayland-client.h>
-#include "xdg-foreign-unstable-v1.h"
+#include "xdg-foreign-unstable-v2.h"
#endif
namespace {
@@ -81,9 +81,9 @@ nfdnchar_t* emit_case_insensitive_glob(const nfdnchar_t* begin,
#ifdef NFD_WAYLAND
struct wl_display* wayland_display;
struct wl_registry* wayland_registry;
-uint32_t wayland_xdg_exporter_v1_name;
-struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
-constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
+uint32_t wayland_xdg_exporter_v2_name;
+struct zxdg_exporter_v2* wayland_xdg_exporter_v2;
+constexpr const char* XDG_EXPORTER_V2 = "zxdg_exporter_v2";
#endif
void EmptyFn(void*) {}
@@ -103,19 +103,19 @@ void registry_handle_global(void* context,
uint32_t version) {
(void)context;
(void)version;
- if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
- wayland_xdg_exporter_v1_name = name;
- wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(
- registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
+ if (strcmp(interface, XDG_EXPORTER_V2) == 0) {
+ wayland_xdg_exporter_v2_name = name;
+ wayland_xdg_exporter_v2 = static_cast<struct zxdg_exporter_v2*>(wl_registry_bind(
+ registry, name, &zxdg_exporter_v2_interface, zxdg_exporter_v2_interface.version));
}
}
void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
(void)context;
(void)registry;
- if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
- zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
- wayland_xdg_exporter_v1 = nullptr;
+ if (wayland_xdg_exporter_v2 && name == wayland_xdg_exporter_v2_name) {
+ zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
+ wayland_xdg_exporter_v2 = nullptr;
}
}
@@ -128,7 +128,7 @@ void NFD_Wayland_Init(void) {
void NFD_Wayland_Quit(void) {
if (wayland_display) {
- if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
+ if (wayland_xdg_exporter_v2) zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
wl_registry_destroy(wayland_registry);
}
}
@@ -143,7 +143,7 @@ nfdresult_t NFD_SetWaylandDisplay(wl_display* display) {
wayland_display = display;
if (wayland_display) {
wayland_registry = wl_display_get_registry(wayland_display);
- wayland_xdg_exporter_v1 = nullptr;
+ wayland_xdg_exporter_v2 = nullptr;
// seems like registry can't be null
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
wl_display_roundtrip(wayland_display);
diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp
index df840e0..e66ca7f 100644
--- a/src/nfd_portal.cpp
+++ b/src/nfd_portal.cpp
@@ -148,10 +148,10 @@ constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
constexpr const char* WAYLAND_PREFIX = "wayland:";
void DestroyXdgExported(void* context) {
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
}
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
const size_t handle_len = strlen(handle);
const size_t prefix_len = strlen(WAYLAND_PREFIX);
@@ -163,8 +163,8 @@ void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char
NFDi_Free(buf);
}
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
- &zxdg_exported_v1_handle};
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
+ &zxdg_exported_v2_handle};
#endif
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
@@ -194,18 +194,18 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
#endif
#ifdef NFD_WAYLAND
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
- if (wayland_display && wayland_xdg_exporter_v1) {
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
+ if (wayland_display && wayland_xdg_exporter_v2) {
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindow.handle));
if (!exported) {
// if we fail to export the wl_surface, act as if the window has no parent
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
return;
}
- zxdg_exported_v1_add_listener(
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
+ zxdg_exported_v2_add_listener(
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(&iter));
wl_display_roundtrip(wayland_display);
- zxdg_exported_v1_set_user_data(exported, nullptr);
+ zxdg_exported_v2_set_user_data(exported, nullptr);
destroy.fn = &DestroyXdgExported;
destroy.context = static_cast<void*>(exported);
return;

View File

@@ -8,11 +8,20 @@ set (ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../")
include(FindPkgConfig)
include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake)
if(APPLE AND BUNDLE)
set(DLOPT ON)
else()
set(DLOPT OFF)
endif()
option(DOWNLOAD_CAPSTONE "Force download capstone" ON)
option(DOWNLOAD_GLFW "Force download glfw" OFF)
option(DOWNLOAD_FREETYPE "Force download freetype" OFF)
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" OFF)
if(VENDOR_GUI)
option(DOWNLOAD_GLFW "Force download glfw" ${DLOPT})
option(DOWNLOAD_FREETYPE "Force download freetype" ${DLOPT})
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
option(DOWNLOAD_PUGIXML "Force download pugixml" ${DLOPT})
endif()
# capstone
@@ -26,7 +35,7 @@ else()
CPMAddPackage(
NAME capstone
GITHUB_REPOSITORY capstone-engine/capstone
GIT_TAG 6.0.0-Alpha9
GIT_TAG 6.0.0-Alpha10
OPTIONS
"CAPSTONE_X86_ATT_DISABLE ON"
"CAPSTONE_ALPHA_SUPPORT OFF"
@@ -58,52 +67,6 @@ else()
target_link_libraries(TracyCapstone INTERFACE capstone_static)
endif()
# GLFW
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
# freetype
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# Zstd
CPMAddPackage(
@@ -132,65 +95,6 @@ set(GETOPT_HEADERS ${GETOPT_DIR}/getopt.h)
add_library(TracyGetOpt STATIC EXCLUDE_FROM_ALL ${GETOPT_SOURCES} ${GETOPT_HEADERS})
target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
# ImGui
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.8-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC TracyFreetype)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG v1.3.0
EXCLUDE_FROM_ALL TRUE
OPTIONS
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# PPQSort
CPMAddPackage(
@@ -212,95 +116,226 @@ CPMAddPackage(
EXCLUDE_FROM_ALL TRUE
)
# md4c
if(VENDOR_GUI)
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
OPTIONS
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL TRUE
)
# GLFW
if(NOT EMSCRIPTEN)
# base64
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
CPMAddPackage(
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
OPTIONS
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
# usearch
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.25.2
EXCLUDE_FROM_ALL TRUE
)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.15
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
pkg_check_modules(GLFW glfw3)
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
add_library(TracyGlfw3 INTERFACE)
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME glfw
GITHUB_REPOSITORY glfw/glfw
GIT_TAG 3.4
OPTIONS
"GLFW_BUILD_EXAMPLES OFF"
"GLFW_BUILD_TESTS OFF"
"GLFW_BUILD_DOCS OFF"
"GLFW_INSTALL OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyGlfw3 INTERFACE)
target_link_libraries(TracyGlfw3 INTERFACE glfw)
endif()
endif()
# libcurl
# freetype
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
pkg_check_modules(FREETYPE freetype2)
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
add_library(TracyFreetype INTERFACE)
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_20_0
NAME freetype
GITHUB_REPOSITORY freetype/freetype
GIT_TAG VER-2-14-3
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
"FT_DISABLE_HARFBUZZ ON"
"FT_WITH_HARFBUZZ OFF"
"FT_DISABLE_ZLIB ON"
"FT_DISABLE_BZIP2 ON"
"FT_DISABLE_PNG ON"
"FT_DISABLE_BROTLI ON"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
add_library(TracyFreetype INTERFACE)
target_link_libraries(TracyFreetype INTERFACE freetype)
endif()
# ImGui
CPMAddPackage(
NAME ImGui
GITHUB_REPOSITORY ocornut/imgui
GIT_TAG v1.92.9b-docking
DOWNLOAD_ONLY TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-default-font.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-macos-clipboard.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-phantom-column.patch"
)
set(IMGUI_SOURCES
imgui_widgets.cpp
imgui_draw.cpp
imgui_demo.cpp
imgui.cpp
imgui_tables.cpp
misc/freetype/imgui_freetype.cpp
backends/imgui_impl_opengl3.cpp
)
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
target_link_libraries(TracyImGui PUBLIC
TracyFreetype
${CMAKE_DL_LIBS}
)
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
find_package(X11 REQUIRED)
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
endif()
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
endif()
if(APPLE)
target_link_libraries(TracyImGui PUBLIC "-framework ApplicationServices")
endif()
# NFD
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
if(GTK_FILESELECTOR)
set(NFD_PORTAL OFF)
else()
set(NFD_PORTAL ON)
endif()
CPMAddPackage(
NAME nfd
GITHUB_REPOSITORY btzy/nativefiledialog-extended
GIT_TAG 3cd252a8f7ca32419b1ca235c2990ba6a0ecba7c
EXCLUDE_FROM_ALL TRUE
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/nfd-xdg-foreign-v2.patch"
OPTIONS
"BUILD_SHARED_LIBS OFF"
"NFD_PORTAL ${NFD_PORTAL}"
)
endif()
# md4c
CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 65c6c9d72cebd9a731aaa5597414ce04d9ea5de3
OPTIONS
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL TRUE
)
if(NOT EMSCRIPTEN)
# base64
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
set(BUILD_SHARED_LIBS OFF)
CPMAddPackage(
NAME base64
GITHUB_REPOSITORY aklomp/base64
GIT_TAG v0.5.2
OPTIONS
"BASE64_BUILD_CLI OFF"
"BASE64_WITH_OpenMP OFF"
EXCLUDE_FROM_ALL TRUE
)
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
# tidy
CPMAddPackage(
NAME tidy
GITHUB_REPOSITORY htacg/tidy-html5
GIT_TAG 5.8.0
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
EXCLUDE_FROM_ALL TRUE
)
# usearch
CPMAddPackage(
NAME usearch
GITHUB_REPOSITORY unum-cloud/usearch
GIT_TAG v2.26.0
EXCLUDE_FROM_ALL TRUE
)
# pugixml
pkg_check_modules(PUGIXML pugixml)
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
add_library(TracyPugixml INTERFACE)
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.16
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)
target_link_libraries(TracyPugixml INTERFACE pugixml)
endif()
# libcurl
pkg_check_modules(LIBCURL libcurl>=7.87.0)
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
add_library(TracyLibcurl INTERFACE)
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
else()
CPMAddPackage(
NAME libcurl
GITHUB_REPOSITORY curl/curl
GIT_TAG curl-8_21_0
OPTIONS
"BUILD_STATIC_LIBS ON"
"BUILD_SHARED_LIBS OFF"
"HTTP_ONLY ON"
"CURL_ZSTD OFF"
"CURL_USE_LIBPSL OFF"
"CURL_USE_LIBSSH2 OFF"
"CURL_BROTLI OFF"
"USE_NGHTTP2 OFF"
"USE_LIBIDN2 OFF"
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyLibcurl INTERFACE)
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
endif()
endif()
endif()

View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -26,17 +26,17 @@ void print_usage_exit(int e)
fprintf(stderr, "Usage:\n");
fprintf(stderr, " extract [OPTION...] <trace file>\n");
fprintf(stderr, "\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean arg Report truncated mean (arg is the percentile. Default is 90)\n");
fprintf(stderr, " -h, --help Print usage\n");
fprintf(stderr, " -V, --version Show version information\n");
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
fprintf(stderr, " -c, --case Case sensitive filtering\n");
fprintf(stderr, " -e, --self Get self times\n");
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
fprintf(stderr, " -m, --messages Report only messages\n");
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
fprintf(stderr, " -t, --truncated_mean[=arg] Report truncated mean (arg is the percentile. Default is 90)\n");
exit(e);
}
@@ -66,8 +66,8 @@ Args parse_args(int argc, char** argv)
struct option long_opts[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' },
{ "filter", optional_argument, NULL, 'f' },
{ "sep", optional_argument, NULL, 's' },
{ "filter", required_argument, NULL, 'f' },
{ "sep", required_argument, NULL, 's' },
{ "case", no_argument, NULL, 'c' },
{ "self", no_argument, NULL, 'e' },
{ "unwrap", no_argument, NULL, 'u' },
@@ -79,7 +79,7 @@ Args parse_args(int argc, char** argv)
};
int c;
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
while ((c = getopt_long(argc, argv, "hf:s:t:ceugmpV", long_opts, NULL)) != -1)
{
switch (c)
{

View File

@@ -0,0 +1,71 @@
cmake_minimum_required(VERSION 3.29)
project(dyna LANGUAGES C CXX)
option(TRACY_ENABLE "Enable Tracy" ON)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_COLOR_DIAGNOSTICS ON)
include(cmake/CPM.cmake)
if(TRACY_ENABLE)
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
add_compile_options(-fno-omit-frame-pointer)
else()
message(WARNING "Tracy builds should be RelWithDebInfo!")
endif()
endif()
CPMAddPackage(
NAME glad
VERSION 2.0.8
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
GIT_TAG glad2
)
add_subdirectory(${glad_SOURCE_DIR}/cmake ${CMAKE_CURRENT_BINARY_DIR}/glad)
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. client/)
glad_add_library(glad_gl_core_33 STATIC API gl:core=3.3)
find_package(SDL3 REQUIRED)
find_package(SDL3_image REQUIRED)
add_executable(dyna
src/main.cpp
src/datapath.cpp
src/timer.cpp
src/gfx.cpp
src/texture.cpp
src/entity.cpp
src/world.cpp
src/map.cpp
src/player.cpp
src/monster.cpp
src/bomb.cpp
src/bonus.cpp
src/game.cpp
)
target_link_libraries(dyna
PRIVATE
glad_gl_core_33
SDL3::SDL3
SDL3_image::SDL3_image
Tracy::TracyClient
)
target_include_directories(dyna PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
# Mirror the data/ tree next to the executable so the game finds its assets
# when launched from the build directory (paths are resolved via SDL_GetBasePath).
add_custom_command(TARGET dyna POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy_directory
${CMAKE_CURRENT_SOURCE_DIR}/data
$<TARGET_FILE_DIR:dyna>/data
COMMENT "Copying data/ next to dyna executable"
)
file(GENERATE OUTPUT .gitignore CONTENT "*")

7
examples/dyna/LICENSE Normal file
View File

@@ -0,0 +1,7 @@
Dyna.net copyright 2005 by Bartosz Taudul and Ralf Wrześniewski.
This program (including source code and the asset it uses) is NOT licensed
for any use other than being an example of how to integrate Tracy Profiler.
The license terms written in other parts of this repository DO NOT apply
here.

View File

@@ -0,0 +1,24 @@
# SPDX-License-Identifier: MIT
#
# SPDX-FileCopyrightText: Copyright (c) 2019-2023 Lars Melchior and contributors
set(CPM_DOWNLOAD_VERSION 0.42.3)
set(CPM_HASH_SUM "a609e875fd532b067174250f6abbc3dac22fe2d64869783fb1e80bda1625c844")
if(CPM_SOURCE_CACHE)
set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
elseif(DEFINED ENV{CPM_SOURCE_CACHE})
set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
else()
set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake")
endif()
# Expand relative path. This is important if the provided path contains a tilde (~)
get_filename_component(CPM_DOWNLOAD_LOCATION ${CPM_DOWNLOAD_LOCATION} ABSOLUTE)
file(DOWNLOAD
https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake
${CPM_DOWNLOAD_LOCATION} EXPECTED_HASH SHA256=${CPM_HASH_SUM}
)
include(${CPM_DOWNLOAD_LOCATION})

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 667 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 B

View File

@@ -0,0 +1,12 @@
10 1 0 0
@............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -0,0 +1,12 @@
20 4 0 0
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#@#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -0,0 +1,12 @@
40 3 2 0
@............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -0,0 +1,12 @@
40 3 3 0
@............
.###.#.#.###.
.............
.#.#.#.#.#.#.
.............
.###.#.#.###.
.............
.#.#.#.#.#.#.
.............
.###.#.#.###.
.............

View File

@@ -0,0 +1,12 @@
40 2 4 1
@............
.###.#.#.###.
.#.........#.
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.#.........#.
.###.#.#.###.
.............

View File

@@ -0,0 +1,12 @@
50 2 2 3
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#.
.....#.#.....
.#.###.###.#.
.............
.#.###.###.#.
.....#.#.....
.#.#.#.#.#.#.
.............
.#.#.#.#.#.#@

View File

@@ -0,0 +1,12 @@
60 3 3 3
@............
.#.#.###.#.#.
.............
.###.#.#.###.
.............
.#.#.###.#.#.
.............
.###.#.#.###.
.............
.#.#.###.#.#.
.............

View File

@@ -0,0 +1,12 @@
60 5 3 3
@............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............

View File

@@ -0,0 +1,12 @@
90 5 5 5
@............
.#.........#.
.............
.............
.............
.............
.............
.............
.............
.#.........#.
.............

View File

@@ -0,0 +1,12 @@
30 4 4 4
@............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............
.#...#.#...#.
.............
.#.#.#.#.#.#.
.............

143
examples/dyna/src/bomb.cpp Normal file
View File

@@ -0,0 +1,143 @@
#include "bomb.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Bomb::Bomb( int x_, int y_ )
: x( x_ )
, y( y_ )
, left( 9 )
{
}
void Bomb::draw()
{
ZoneScoped;
if( stage == Stage::exploding )
return;
if( stage == Stage::appear )
{
Textures::bomb_appear.bind( 9 - left );
}
else
{
int frame = static_cast<int>( ( time - left ) / static_cast<float>( time ) * 8 );
if( Timer::get_timestamp() / 100 % 2 == 0 )
frame++;
Textures::bomb.bind( frame );
}
Gfx::draw_square( x, y );
}
void Bomb::tick( World& world )
{
ZoneScoped;
delta += Timer::delta;
while( delta > 10 )
{
delta -= 10;
if( stage == Stage::appear )
{
if( left > 0 )
{
delta -= 10; // the fade-in advances at double speed
left--;
}
else
{
stage = Stage::ticking;
left = time;
}
}
else if( left > 0 )
{
left--;
}
else if( stage == Stage::ticking )
{
explode( world );
}
else
{
die( world );
}
}
}
void Bomb::explode( World& world )
{
ZoneScoped;
stage = Stage::exploding;
left = 200;
Map& map = world.map();
map.at( x, y ) = Field::explosion( Field::ExplosionType::center );
struct Dir
{
int dx, dy;
Field::ExplosionType through, tip;
};
const Dir dirs[4] = {
{ -1, 0, Field::ExplosionType::horizontal, Field::ExplosionType::left },
{ 1, 0, Field::ExplosionType::horizontal, Field::ExplosionType::right },
{ 0, -1, Field::ExplosionType::vertical, Field::ExplosionType::up },
{ 0, 1, Field::ExplosionType::vertical, Field::ExplosionType::down },
};
for( const Dir& d : dirs )
{
for( int i = 1; i <= maxrange; i++ )
{
int tx = x + d.dx * i;
int ty = y + d.dy * i;
if( tx < 0 || tx > map.getx() - 1 || ty < 0 || ty > map.gety() - 1 )
break;
Destruction destr = map.at( tx, ty ).destructible();
if( destr == Destruction::none )
break;
etiles.emplace_back( tx, ty );
if( map.at( tx, ty ).kind == Field::Kind::crate )
world.crates_left--;
if( i == maxrange || destr == Destruction::single )
{
map.at( tx, ty ) = Field::explosion( d.tip );
break;
}
else
{
map.at( tx, ty ) = Field::explosion( d.through );
}
}
}
}
void Bomb::die( World& world )
{
ZoneScoped;
dead = true;
Map& map = world.map();
map.at( x, y ) = Field::floor();
for( const auto& [tx, ty] : etiles )
map.at( tx, ty ) = Field::floor();
}
}

View File

@@ -0,0 +1,44 @@
#pragma once
#include <utility>
#include <vector>
namespace dyna
{
class World;
// A bomb on the grid: fades in, counts down, then paints a cross-shaped
// explosion onto the map and clears it again. Ported from bomb.cs.
class Bomb
{
public:
Bomb( int x, int y );
void draw();
void tick( World& world );
bool is_dead() const { return dead; }
private:
void explode( World& world );
void die( World& world );
enum class Stage
{
appear,
ticking,
exploding
};
int x, y; // grid coordinates
Stage stage = Stage::appear;
int left;
int delta = 0;
static constexpr int time = 150;
static constexpr int maxrange = 1;
std::vector<std::pair<int, int>> etiles; // tiles to revert to floor
bool dead = false;
};
}

View File

@@ -0,0 +1,58 @@
#include "bonus.hpp"
#include "gfx.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Vortex::Vortex( int gx, int gy )
{
x = gx; // stored in grid units, drawn via draw_square
y = gy;
set_action( Action::appear );
left = 79;
}
void Vortex::draw()
{
ZoneScoped;
int frame = static_cast<int>( ( Timer::get_timestamp() - action_start ) / 40 );
switch( action )
{
case Action::appear:
Textures::vortex_appear.bind( frame );
break;
case Action::wait:
Textures::vortex.bind( frame );
break;
default:
break;
}
Gfx::draw_square( x, y );
}
void Vortex::tick( World& )
{
ZoneScoped;
delta += Timer::delta;
while( delta > 10 )
{
delta -= 10;
if( left > 0 )
left--;
else if( action == Action::appear )
set_action( Action::wait );
}
}
void Vortex::die( World& ) {}
}

View File

@@ -0,0 +1,20 @@
#pragma once
#include "entity.hpp"
namespace dyna
{
// The level-exit portal. Unlike the other entities its coordinates are stored in
// grid units (it draws via draw_square), matching bonus.cs.
class Vortex : public Entity
{
public:
Vortex( int gx, int gy );
void draw() override;
void tick( World& world ) override;
void die( World& world ) override;
};
}

View File

@@ -0,0 +1,25 @@
#include "datapath.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
namespace dyna
{
std::string data_path( const std::string& rel )
{
ZoneScoped;
ZoneText( rel.c_str(), rel.size() );
// SDL_GetBasePath returns the executable's directory (with a trailing
// separator) and is owned by SDL, so cache it for the program's lifetime.
static const std::string base = []
{
const char* p = SDL_GetBasePath();
return std::string( p ? p : "" );
}();
return base + rel;
}
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <string>
namespace dyna
{
// Resolve a path relative to the directory containing the executable, so the
// game finds its data files regardless of the current working directory (e.g.
// when launched from the build tree). The data/ tree is copied next to the
// binary at build time; see CMakeLists.txt.
std::string data_path( const std::string& rel );
}

View File

@@ -0,0 +1,39 @@
#include "entity.hpp"
#include "map.hpp"
#include "timer.hpp"
namespace dyna
{
void Entity::set_action( Action a )
{
action = a;
action_start = Timer::get_timestamp();
}
bool Entity::can_move( Action a, const Map& map ) const
{
switch( a )
{
case Action::up:
return y > 0 && !map.at( x / 64, y / 64 - 1 ).solid();
case Action::down:
return y / 64 < map.gety() - 1 && !map.at( x / 64, y / 64 + 1 ).solid();
case Action::left:
return x > 0 && !map.at( x / 64 - 1, y / 64 ).solid();
case Action::right:
return x / 64 < map.getx() - 1 && !map.at( x / 64 + 1, y / 64 ).solid();
default:
return true;
}
}
bool Entity::killed( const Map& map ) const
{
int tx = ( x + 32 ) / 64;
int ty = ( y + 32 ) / 64;
return map.at( tx, ty ).kind == Field::Kind::explosion;
}
}

View File

@@ -0,0 +1,52 @@
#pragma once
#include <cstdint>
namespace dyna
{
class Map;
class World;
// Movement/state verbs shared by the player and monsters. In the C# source this
// lived as Entity.Action; promoted to namespace scope so Game can refer to it.
enum class Action
{
wait,
up,
down,
left,
right,
death,
place_bomb,
appear
};
// Base for everything that moves on the grid. Coordinates are in pixels
// (64 per tile) and laid out top-left origin, matching entity.cs.
class Entity
{
public:
virtual ~Entity() = default;
virtual void set_action( Action a );
int getx() const { return x; }
int gety() const { return y; }
virtual void draw() = 0;
virtual void tick( World& world ) = 0;
virtual void die( World& world ) = 0;
protected:
bool can_move( Action a, const Map& map ) const;
virtual bool killed( const Map& map ) const;
int x = 0, y = 0;
std::int64_t action_start = 0;
int delta = 0;
Action action = Action::wait;
int left = 0;
};
}

211
examples/dyna/src/game.cpp Normal file
View File

@@ -0,0 +1,211 @@
#include "game.hpp"
#include "datapath.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "player.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
#include <string>
namespace dyna
{
namespace Game
{
namespace
{
struct TracySection
{
explicit TracySection( const char* name ) { Enter( name ); }
explicit TracySection( const char* name, uint16_t category ) : idx( TracySectionEnterCategory( category, "%s", name ) ) {}
~TracySection() { Leave(); }
void Enter( const char* name )
{
idx = TracySectionEnter( "%s", name );
}
void Leave()
{
if( idx > 0 )
{
TracySectionLeave( idx );
idx = 0;
}
}
private:
uint32_t idx;
};
SDL_Keycode key = 0; // most recently pressed movement key
bool help = false;
// Run one level to completion. Returns true if the player asked to quit the
// whole application (window close), false if the level simply ended (death,
// escape, or reaching the exit) and control should return to the caller.
bool level_loop( World& world )
{
TracySection section( ( std::string( "Level " ) + world.name() ).c_str(), 1 );
Player* p = world.player();
for( ;; )
{
SDL_Event ev;
while( SDL_PollEvent( &ev ) )
{
if( ev.type == SDL_EVENT_QUIT )
return true;
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
{
switch( ev.key.key )
{
case SDLK_ESCAPE:
world.killed = true;
return false;
case SDLK_LEFT:
key = SDLK_LEFT;
p->move( Action::left );
break;
case SDLK_RIGHT:
key = SDLK_RIGHT;
p->move( Action::right );
break;
case SDLK_UP:
key = SDLK_UP;
p->move( Action::up );
break;
case SDLK_DOWN:
key = SDLK_DOWN;
p->move( Action::down );
break;
case SDLK_SPACE:
world.map().place_bomb( ( p->getx() + 32 ) / 64, ( p->gety() + 32 ) / 64 );
break;
default:
break;
}
}
if( ev.type == SDL_EVENT_KEY_UP )
{
switch( ev.key.key )
{
case SDLK_LEFT:
if( key == SDLK_LEFT ) p->move( Action::wait );
break;
case SDLK_RIGHT:
if( key == SDLK_RIGHT ) p->move( Action::wait );
break;
case SDLK_UP:
if( key == SDLK_UP ) p->move( Action::wait );
break;
case SDLK_DOWN:
if( key == SDLK_DOWN ) p->move( Action::wait );
break;
default:
break;
}
}
}
Gfx::clear();
Timer::tick();
world.tick();
world.draw();
Gfx::swap();
if( world.killed || world.next_level )
return false;
}
}
// Play through the levels in order. Returns true if the application should quit.
bool new_game()
{
TracySection section( "In-game" );
int level = 1;
for( ;; )
{
World world( data_path( "data/levels/" + std::to_string( level ) ), true );
if( level_loop( world ) )
return true; // window closed
if( world.killed )
return false; // died or escaped to the menu
if( ++level >= 10 )
return false; // cleared the last level
}
}
} // namespace
void menu_loop()
{
constexpr const char* sectionName = "Main menu";
TracySection section( sectionName );
World world( data_path( "data/levels/menu" ), false );
for( ;; )
{
SDL_Event ev;
while( SDL_PollEvent( &ev ) )
{
if( ev.type == SDL_EVENT_QUIT )
return;
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
{
switch( ev.key.key )
{
case SDLK_ESCAPE:
return;
case SDLK_SPACE:
section.Leave();
if( new_game() )
return; // window closed during play
section.Enter( sectionName );
break;
case SDLK_H:
help = !help;
break;
default:
break;
}
}
}
Gfx::clear();
Timer::tick();
world.tick();
world.draw();
if( help )
Gfx::show_help();
else
Gfx::show_menu();
Gfx::swap();
}
}
} // namespace Game
}

View File

@@ -0,0 +1,14 @@
#pragma once
namespace dyna
{
// Top-level game flow, ported from game.cs. The C# original kept the running
// game's state (player, map, win/lose flags) in static fields; that state now
// lives in a World object owned by the loops below, so nothing leaks out here.
namespace Game
{
void menu_loop();
}
}

517
examples/dyna/src/gfx.cpp Normal file
View File

@@ -0,0 +1,517 @@
#include "gfx.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include <SDL3/SDL.h>
#include <tracy/Tracy.hpp>
#include <cassert>
#include <cstdio>
#include <vector>
namespace dyna
{
namespace
{
SDL_Window* g_window = nullptr;
SDL_GLContext g_gl_context = nullptr;
GLuint g_program = 0;
GLuint g_vao = 0;
GLuint g_vbo = 0;
// Current draw state, applied to every quad appended to the batch.
GLuint g_current_tex = 0;
int g_current_layer = 0;
float g_alpha = 1.0f;
// One vertex of the streaming batch: screen position, atlas-array texcoord,
// the array layer to sample and a per-vertex alpha multiplier.
struct GlVert
{
float px, py, tx, ty, layer, a;
};
// A run of consecutive vertices that share one texture, drawn in a single call.
struct DrawCmd
{
GLuint tex;
GLsizei count;
};
std::vector<GlVert> g_verts;
std::vector<DrawCmd> g_cmds;
const char* VERT_SRC = R"(
#version 330 core
uniform mat4 uProjection;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in float aLayer;
layout(location = 3) in float aAlpha;
out vec3 vTexCoord;
out float vAlpha;
void main() {
gl_Position = uProjection * vec4(aPosition, 0.0, 1.0);
vTexCoord = vec3(aTexCoord, aLayer);
vAlpha = aAlpha;
}
)";
const char* FRAG_SRC = R"(
#version 330 core
uniform sampler2DArray uTexture;
in vec3 vTexCoord;
in float vAlpha;
out vec4 fragColor;
void main() {
fragColor = texture(uTexture, vTexCoord) * vec4(1.0, 1.0, 1.0, vAlpha);
}
)";
GLuint compile_shader( GLenum type, const char* src )
{
ZoneScoped;
GLuint s = glCreateShader( type );
glShaderSource( s, 1, &src, nullptr );
glCompileShader( s );
GLint ok = 0;
glGetShaderiv( s, GL_COMPILE_STATUS, &ok );
if( !ok )
{
char log[512];
glGetShaderInfoLog( s, 512, nullptr, log );
std::fprintf( stderr, "Shader compile error: %s\n", log );
glDeleteShader( s );
return 0;
}
return s;
}
bool init_shaders()
{
ZoneScoped;
GLuint vs = compile_shader( GL_VERTEX_SHADER, VERT_SRC );
if( !vs ) return false;
GLuint fs = compile_shader( GL_FRAGMENT_SHADER, FRAG_SRC );
if( !fs )
{
glDeleteShader( vs );
return false;
}
g_program = glCreateProgram();
glAttachShader( g_program, vs );
glAttachShader( g_program, fs );
glLinkProgram( g_program );
glDeleteShader( vs );
glDeleteShader( fs );
GLint ok = 0;
glGetProgramiv( g_program, GL_LINK_STATUS, &ok );
if( !ok )
{
char log[512];
glGetProgramInfoLog( g_program, 512, nullptr, log );
std::fprintf( stderr, "Program link error: %s\n", log );
glDeleteProgram( g_program );
g_program = 0;
return false;
}
// Bottom-left origin orthographic projection, matching the original
// gluOrtho2D(0, w, 0, h) so the ported draw code carries over verbatim.
float l = 0.0f, r = static_cast<float>( Gfx::w );
float b = 0.0f, t = static_cast<float>( Gfx::h );
float proj[16] = {
2.0f / ( r - l ), 0.0f, 0.0f, 0.0f,
0.0f, 2.0f / ( t - b ), 0.0f, 0.0f,
0.0f, 0.0f, -1.0f, 0.0f,
-( r + l ) / ( r - l ), -( t + b ) / ( t - b ), 0.0f, 1.0f };
glUseProgram( g_program );
glUniformMatrix4fv( glGetUniformLocation( g_program, "uProjection" ), 1, GL_FALSE, proj );
glUniform1i( glGetUniformLocation( g_program, "uTexture" ), 0 );
glUseProgram( 0 );
return true;
}
void init_quad_vao()
{
ZoneScoped;
glGenVertexArrays( 1, &g_vao );
glGenBuffers( 1, &g_vbo );
glBindVertexArray( g_vao );
glBindBuffer( GL_ARRAY_BUFFER, g_vbo );
const GLsizei stride = sizeof( GlVert );
glEnableVertexAttribArray( 0 );
glVertexAttribPointer( 0, 2, GL_FLOAT, GL_FALSE, stride, (void*)0 );
glEnableVertexAttribArray( 1 );
glVertexAttribPointer( 1, 2, GL_FLOAT, GL_FALSE, stride, (void*)8 );
glEnableVertexAttribArray( 2 );
glVertexAttribPointer( 2, 1, GL_FLOAT, GL_FALSE, stride, (void*)16 );
glEnableVertexAttribArray( 3 );
glVertexAttribPointer( 3, 1, GL_FLOAT, GL_FALSE, stride, (void*)20 );
glBindVertexArray( 0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
}
// Draw and clear everything accumulated since the last flush, in submission
// order. Consecutive quads that share a texture collapse into one draw call.
void flush_batch()
{
ZoneScoped;
if( g_verts.empty() )
return;
glBindBuffer( GL_ARRAY_BUFFER, g_vbo );
glBufferData( GL_ARRAY_BUFFER,
static_cast<GLsizeiptr>( g_verts.size() * sizeof( GlVert ) ),
g_verts.data(), GL_STREAM_DRAW );
glUseProgram( g_program );
glBindVertexArray( g_vao );
GLint offset = 0;
for( const DrawCmd& cmd : g_cmds )
{
glBindTexture( GL_TEXTURE_2D_ARRAY, cmd.tex );
glDrawArrays( GL_TRIANGLES, offset, cmd.count );
offset += cmd.count;
}
glBindVertexArray( 0 );
glUseProgram( 0 );
glBindBuffer( GL_ARRAY_BUFFER, 0 );
g_verts.clear();
g_cmds.clear();
}
// Frame image capture, following the OpenGL example in the Tracy manual. The
// backbuffer is downscaled on the GPU to a small fixed size and read back
// asynchronously, so a screenshot can be attached to every frame without
// stalling the CPU on the GPU. Several buffer sets are cycled because rendering
// runs a few frames ahead of the GPU.
// Half the render resolution, preserving its aspect ratio; both dimensions
// stay divisible by 4 as FrameImage requires.
constexpr int FI_W = Gfx::w / 2;
constexpr int FI_H = Gfx::h / 2;
constexpr int FI_COUNT = 4;
GLuint g_fi_texture[FI_COUNT];
GLuint g_fi_framebuffer[FI_COUNT];
GLuint g_fi_pbo[FI_COUNT];
GLsync g_fi_fence[FI_COUNT] = {};
int g_fi_idx = 0;
std::vector<int> g_fi_queue;
void init_frame_images()
{
ZoneScoped;
glGenTextures( FI_COUNT, g_fi_texture );
glGenFramebuffers( FI_COUNT, g_fi_framebuffer );
glGenBuffers( FI_COUNT, g_fi_pbo );
for( int i = 0; i < FI_COUNT; i++ )
{
glBindTexture( GL_TEXTURE_2D, g_fi_texture[i] );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, FI_W, FI_H, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
glBindFramebuffer( GL_FRAMEBUFFER, g_fi_framebuffer[i] );
glFramebufferTexture2D( GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, g_fi_texture[i], 0 );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[i] );
glBufferData( GL_PIXEL_PACK_BUFFER, FI_W * FI_H * 4, nullptr, GL_STREAM_READ );
}
glBindFramebuffer( GL_FRAMEBUFFER, 0 );
glBindBuffer( GL_PIXEL_PACK_BUFFER, 0 );
}
void shutdown_frame_images()
{
ZoneScoped;
glDeleteTextures( FI_COUNT, g_fi_texture );
glDeleteFramebuffers( FI_COUNT, g_fi_framebuffer );
glDeleteBuffers( FI_COUNT, g_fi_pbo );
}
// Send any captures the GPU has already finished, then queue a capture of the
// frame just rendered. Call after the batch is flushed but before swapping.
void capture_frame_image()
{
ZoneScoped;
// Hand finished captures from earlier frames to the profiler. The queue
// size is the number of frames we are still ahead of the GPU, which is the
// frame lag Tracy needs as the FrameImage offset.
while( !g_fi_queue.empty() )
{
const int idx = g_fi_queue.front();
if( glClientWaitSync( g_fi_fence[idx], 0, 0 ) == GL_TIMEOUT_EXPIRED ) break;
glDeleteSync( g_fi_fence[idx] );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[idx] );
void* ptr = glMapBufferRange( GL_PIXEL_PACK_BUFFER, 0, FI_W * FI_H * 4, GL_MAP_READ_BIT );
FrameImage( ptr, FI_W, FI_H, g_fi_queue.size(), true );
glUnmapBuffer( GL_PIXEL_PACK_BUFFER );
g_fi_queue.erase( g_fi_queue.begin() );
}
// Downscale the current backbuffer into the next buffer set and start an
// asynchronous read-back, signalled by a fence.
assert( g_fi_queue.empty() || g_fi_queue.front() != g_fi_idx ); // buffer overrun
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, g_fi_framebuffer[g_fi_idx] );
glBlitFramebuffer( 0, 0, Gfx::w, Gfx::h, 0, 0, FI_W, FI_H, GL_COLOR_BUFFER_BIT, GL_LINEAR );
glBindFramebuffer( GL_DRAW_FRAMEBUFFER, 0 );
glBindFramebuffer( GL_READ_FRAMEBUFFER, g_fi_framebuffer[g_fi_idx] );
glBindBuffer( GL_PIXEL_PACK_BUFFER, g_fi_pbo[g_fi_idx] );
glReadPixels( 0, 0, FI_W, FI_H, GL_RGBA, GL_UNSIGNED_BYTE, nullptr );
glBindFramebuffer( GL_READ_FRAMEBUFFER, 0 );
g_fi_fence[g_fi_idx] = glFenceSync( GL_SYNC_GPU_COMMANDS_COMPLETE, 0 );
g_fi_queue.emplace_back( g_fi_idx );
g_fi_idx = ( g_fi_idx + 1 ) % FI_COUNT;
}
} // namespace
namespace Render
{
bool init()
{
ZoneScoped;
if( !init_shaders() ) return false;
init_quad_vao();
init_frame_images();
return true;
}
void shutdown()
{
ZoneScoped;
shutdown_frame_images();
if( g_vbo ) glDeleteBuffers( 1, &g_vbo );
if( g_vao ) glDeleteVertexArrays( 1, &g_vao );
if( g_program ) glDeleteProgram( g_program );
g_vbo = g_vao = g_program = 0;
}
void use_texture( GLuint tex, int layer )
{
g_current_tex = tex;
g_current_layer = layer;
}
GLuint make_texture( int w, int h, int layers, const void* rgba )
{
ZoneScoped;
GLuint tex = 0;
glGenTextures( 1, &tex );
glBindTexture( GL_TEXTURE_2D_ARRAY, tex );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTexParameteri( GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
glTexImage3D( GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, w, h, layers, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba );
return tex;
}
} // namespace Render
namespace Gfx
{
void clear()
{
glClear( GL_COLOR_BUFFER_BIT );
}
void swap()
{
ZoneScoped;
flush_batch();
capture_frame_image();
SDL_GL_SwapWindow( g_window );
FrameMark;
}
void alpha( float a )
{
g_alpha = a;
}
void draw_quad( const Vertex corners[4] )
{
ZoneScoped;
// Two triangles, vertices appended in submission order so painter ordering
// (and the transient per-monster alpha) is preserved by the batch.
const int idx[6] = { 0, 1, 2, 0, 2, 3 };
for( int i : idx )
{
const Vertex& c = corners[i];
g_verts.push_back( { c.x, c.y, c.u, c.v,
static_cast<float>( g_current_layer ), g_alpha } );
}
if( !g_cmds.empty() && g_cmds.back().tex == g_current_tex )
g_cmds.back().count += 6;
else
g_cmds.push_back( { g_current_tex, 6 } );
}
void draw_sprite( int x, int y )
{
ZoneScoped;
float fx = static_cast<float>( x );
float fy = static_cast<float>( y );
float top = static_cast<float>( h ) - fy;
float bottom = static_cast<float>( h ) - ( fy + 64.0f );
Vertex corners[4] = {
{ fx, top, 0.0f, 0.0f },
{ fx + 64.0f, top, 1.0f, 0.0f },
{ fx + 64.0f, bottom, 1.0f, 1.0f },
{ fx, bottom, 0.0f, 1.0f },
};
draw_quad( corners );
}
void draw_square( int x, int y )
{
draw_sprite( x * 64, y * 64 );
}
void show_help()
{
ZoneScoped;
Textures::menu.bind();
const float fw = static_cast<float>( w );
const float fh = static_cast<float>( h );
Vertex bg[4] = {
{ 0.0f, fh, 0.0f, 0.0f },
{ fw, fh, 832.0f / 1024, 0.0f },
{ fw, 0.0f, 832.0f / 1024, 704.0f / 1024 },
{ 0.0f, 0.0f, 0.0f, 704.0f / 1024 },
};
draw_quad( bg );
int t = static_cast<int>( Timer::get_timestamp() / 40 );
Textures::p_r.bind( t );
draw_sprite( 150, 85 );
Textures::m1_r.bind( t );
draw_sprite( 75, 160 );
Textures::m2_r.bind( t );
draw_sprite( 150, 160 );
Textures::m3_r.bind( t );
draw_sprite( 225, 160 );
Textures::bomb.bind( static_cast<int>( Timer::get_timestamp() / 100 % 2 ) );
draw_sprite( 150, 235 );
Textures::wall.bind();
draw_sprite( 150, 310 );
Textures::crate.bind();
draw_sprite( 150, 385 );
Textures::vortex.bind( t );
draw_sprite( 150, 460 );
Textures::bonus1.bind( t );
draw_sprite( 112, 535 );
Textures::bonus2.bind( t );
draw_sprite( 187, 535 );
}
void show_menu()
{
ZoneScoped;
Textures::menu.bind();
Vertex logo[4] = {
{ float( ( w - 594 ) / 2 ), float( h - 50 ), 1.0f, 0.0f },
{ float( ( w + 594 ) / 2 ), float( h - 50 ), 1.0f, 594.0f / 1024 },
{ float( ( w + 594 ) / 2 ), float( h - 50 - 180 ), 1.0f - 180.0f / 1024, 594.0f / 1024 },
{ float( ( w - 594 ) / 2 ), float( h - 50 - 180 ), 1.0f - 180.0f / 1024, 0.0f },
};
draw_quad( logo );
Vertex prompt[4] = {
{ float( ( w - 527 ) / 2 ), 335.0f, 0.0f, 704.0f / 1024 },
{ float( ( w + 527 ) / 2 ), 335.0f, 527.0f / 1024, 704.0f / 1024 },
{ float( ( w + 527 ) / 2 ), 20.0f, 527.0f / 1024, 1019.0f / 1024 },
{ float( ( w - 527 ) / 2 ), 20.0f, 0.0f, 1019.0f / 1024 },
};
draw_quad( prompt );
}
} // namespace Gfx
namespace Init
{
bool all()
{
ZoneScoped;
if( !SDL_Init( SDL_INIT_VIDEO ) )
{
std::fprintf( stderr, "SDL_Init failed: %s\n", SDL_GetError() );
return false;
}
SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION, 3 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION, 3 );
SDL_GL_SetAttribute( SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE );
g_window = SDL_CreateWindow( "Dyna.net", Gfx::w, Gfx::h, SDL_WINDOW_OPENGL );
if( !g_window )
{
std::fprintf( stderr, "SDL_CreateWindow failed: %s\n", SDL_GetError() );
return false;
}
g_gl_context = SDL_GL_CreateContext( g_window );
if( !g_gl_context )
{
std::fprintf( stderr, "SDL_GL_CreateContext failed: %s\n", SDL_GetError() );
return false;
}
int version = gladLoadGL( (GLADloadfunc)SDL_GL_GetProcAddress );
if( version == 0 )
{
std::fprintf( stderr, "gladLoadGL failed\n" );
return false;
}
SDL_GL_SetSwapInterval( 1 ); // vsync; the game is time-based so speed is unaffected
glViewport( 0, 0, Gfx::w, Gfx::h );
glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
glEnable( GL_BLEND );
glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
if( !Render::init() ) return false;
Timer::reset();
Textures::preload();
return true;
}
void shutdown()
{
ZoneScoped;
Render::shutdown();
if( g_gl_context ) SDL_GL_DestroyContext( g_gl_context );
if( g_window ) SDL_DestroyWindow( g_window );
SDL_Quit();
}
} // namespace Init
}

59
examples/dyna/src/gfx.hpp Normal file
View File

@@ -0,0 +1,59 @@
#pragma once
#include <glad/gl.h>
namespace dyna
{
// Screen dimensions, matching the original 13x11 grid of 64px tiles.
namespace Gfx
{
constexpr int w = 832;
constexpr int h = 704;
void clear();
void swap();
// Drawing primitives ported from gfx.cs. They render with the currently
// bound texture (see Texture::bind) and the current alpha. The coordinate
// system is bottom-left origin with y growing upward, exactly as the C#
// gluOrtho2D setup; draw_sprite/draw_square take y measured from the top
// and flip internally, so game-side coordinates stay top-left based.
void alpha( float a );
void draw_sprite( int x, int y ); // pixel position of the top-left corner
void draw_square( int x, int y ); // grid position (multiplied by 64)
// A single textured quad given four explicit (position, texcoord) corners,
// used by the menu/help screens which sample rotated regions of the atlas.
struct Vertex
{
float x, y, u, v;
};
void draw_quad( const Vertex corners[4] );
void show_help();
void show_menu();
}
// Renderer back end shared by the texture loaders.
namespace Render
{
bool init(); // shaders + streaming VBO/VAO
void shutdown(); // delete the program and buffers
// Select the array texture (and layer within it) used by subsequent draws.
void use_texture( GLuint tex, int layer );
// Upload `layers` tightly packed RGBA8 images of size w*h as one
// GL_TEXTURE_2D_ARRAY and return its name (0 on failure).
GLuint make_texture( int w, int h, int layers, const void* rgba );
}
// One-time startup/shutdown, ported from the Init class in gfx.cs.
namespace Init
{
bool all(); // SDL, GL context, renderer, textures, timer
void shutdown();
}
}

View File

@@ -0,0 +1,41 @@
#include "game.hpp"
#include "gfx.hpp"
#include <SDL3/SDL_main.h>
#include <tracy/Tracy.hpp>
#include <cstdlib>
#include <new>
// Route every heap allocation through Tracy so the profiler can track memory
// usage. The default array forms (operator new[]/delete[]) and the nothrow
// forms forward to these, so overriding the scalar operators covers them too.
void* operator new( std::size_t count )
{
void* ptr = std::malloc( count );
if( !ptr ) throw std::bad_alloc();
TracyAlloc( ptr, count );
return ptr;
}
void operator delete( void* ptr ) noexcept
{
TracyFree( ptr );
std::free( ptr );
}
int main( int /*argc*/, char* /*argv*/[] )
{
TracyNoop;
TracySectionSetup( 0, "Game state" );
TracySectionSetup( 1, "Level progression" );
if( !dyna::Init::all() )
return 1;
dyna::Game::menu_loop();
dyna::Init::shutdown();
return 0;
}

334
examples/dyna/src/map.cpp Normal file
View File

@@ -0,0 +1,334 @@
#include "map.hpp"
#include "bomb.hpp"
#include "bonus.hpp"
#include "gfx.hpp"
#include "monster.hpp"
#include "player.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <fstream>
#include <sstream>
#include <tracy/Tracy.hpp>
namespace dyna
{
// ---- Field --------------------------------------------------------------
Field Field::explosion( ExplosionType t )
{
Field f;
f.kind = Kind::explosion;
f.etype = t;
f.tstart = Timer::get_timestamp();
return f;
}
bool Field::solid() const
{
switch( kind )
{
case Kind::wall:
case Kind::crate:
case Kind::bomb:
return true;
default:
return false;
}
}
Destruction Field::destructible() const
{
switch( kind )
{
case Kind::floor:
return Destruction::multi;
case Kind::crate:
return Destruction::single;
default:
return Destruction::none;
}
}
void Field::draw( int x, int y ) const
{
switch( kind )
{
case Kind::wall:
Textures::wall.bind();
Gfx::draw_square( x, y );
break;
case Kind::crate:
Textures::sand.bind();
Gfx::draw_square( x, y );
Textures::crate.bind();
Gfx::draw_square( x, y );
break;
case Kind::explosion: {
Textures::sand.bind();
Gfx::draw_square( x, y );
int frame = static_cast<int>( ( Timer::get_timestamp() - tstart ) / 40 % 8 );
if( frame > 4 ) frame = 8 - frame;
switch( etype )
{
case ExplosionType::center: Textures::e_c.bind( frame ); break;
case ExplosionType::vertical: Textures::e_v.bind( frame ); break;
case ExplosionType::horizontal: Textures::e_h.bind( frame ); break;
case ExplosionType::left: Textures::e_le.bind( frame ); break;
case ExplosionType::right: Textures::e_re.bind( frame ); break;
case ExplosionType::up: Textures::e_ue.bind( frame ); break;
case ExplosionType::down: Textures::e_de.bind( frame ); break;
}
Gfx::draw_square( x, y );
break;
}
// floor, bomb and vortex tiles all show plain sand; the bomb and vortex
// sprites themselves are drawn by their entities.
case Kind::floor:
case Kind::bomb:
case Kind::vortex:
default:
Textures::sand.bind();
Gfx::draw_square( x, y );
break;
}
}
// ---- Map ----------------------------------------------------------------
Map::Map( const std::string& fn )
{
ZoneScoped;
ZoneText( fn.c_str(), fn.size() );
load( fn );
generate_destructibles();
populate_map();
}
Map::~Map() = default;
void Map::load( const std::string& fn )
{
ZoneScoped;
std::ifstream f( fn );
if( !f )
{
std::fprintf( stderr, "Cannot open level %s\n", fn.c_str() );
grid.assign( X * Y, Field::floor() );
return;
}
std::stringstream buf;
buf << f.rdbuf();
std::string content = buf.str();
size_t nl = content.find( '\n' );
std::string header = ( nl == std::string::npos ) ? content : content.substr( 0, nl );
std::sscanf( header.c_str(), "%d %d %d %d", &destructibles, &m1, &m2, &m3 );
grid.assign( X * Y, Field::floor() );
px = -1;
size_t p = ( nl == std::string::npos ) ? content.size() : nl + 1;
for( int ry = 0; ry < Y; ry++ )
{
for( int rx = 0; rx < X; rx++ )
{
char c = ( p < content.size() ) ? content[p++] : '\0';
switch( c )
{
case '.':
at( rx, ry ) = Field::floor();
break;
case '#':
at( rx, ry ) = Field::wall();
break;
case '@':
at( rx, ry ) = Field::floor();
px = rx;
py = ry;
break;
case '\n':
rx--; // newlines don't consume a grid cell
break;
default:
break;
}
}
}
}
bool Map::monster_ok( int rx, int ry, int pxx, int pyy, int r ) const
{
const Field& f = at( rx, ry );
return f.is_floor_family() && f.kind != Field::Kind::crate &&
( std::abs( rx - pxx ) > r || std::abs( ry - pyy ) > r );
}
void Map::generate_destructibles()
{
ZoneScoped;
int i = destructibles;
while( i != 0 )
{
int rx = RNG::next( X );
int ry = RNG::next( Y );
if( monster_ok( rx, ry, px, py, 1 ) )
{
at( rx, ry ) = Field::crate();
i--;
}
}
}
void Map::populate_map()
{
ZoneScoped;
for( int type = 1; type <= 3; type++ )
{
int count = ( type == 1 ) ? m1 : ( type == 2 ) ? m2
: m3;
while( count != 0 )
{
int rx = RNG::next( X );
int ry = RNG::next( Y );
if( monster_ok( rx, ry, px, py, 2 ) )
{
monsters.push_back( std::make_unique<Monster>( type, rx, ry ) );
count--;
}
}
}
}
void Map::draw()
{
ZoneScoped;
for( int ry = 0; ry < Y; ry++ )
for( int rx = 0; rx < X; rx++ )
at( rx, ry ).draw( rx, ry );
for( auto& b : bombs ) b->draw();
for( auto& e : monsters ) e->draw();
for( auto& e : bonuses ) e->draw();
}
void Map::tick( World& world )
{
ZoneScoped;
// Bombs.
for( auto& b : bombs ) b->tick( world );
bombs.erase( std::remove_if( bombs.begin(), bombs.end(),
[]( const std::unique_ptr<Bomb>& b ) { return b->is_dead(); } ),
bombs.end() );
// Monsters: tick, then retire the dead and queue their respawn timers.
for( auto& e : monsters ) e->tick( world );
for( auto& e : monsters )
{
if( e->is_dead() )
{
int delay = ( e->type() == 1 ) ? 10000 : ( e->type() == 2 ) ? 20000
: 30000;
mwait.push_back( { e->type(), Timer::get_timestamp() + delay } );
}
}
monsters.erase( std::remove_if( monsters.begin(), monsters.end(),
[]( const std::unique_ptr<Monster>& e ) { return e->is_dead(); } ),
monsters.end() );
// The respawn and exit-portal placement below need the player's position;
// they only fire during gameplay (a monster died, or every crate is gone),
// never on the player-less menu screen.
Player* player = world.player();
// Respawn monsters whose wait has elapsed.
std::int64_t now = Timer::get_timestamp();
std::vector<MWait> still_waiting;
for( const MWait& m : mwait )
{
if( m.time < now && player )
{
int rx = 0, ry = 0;
bool ok = false;
while( !ok )
{
rx = RNG::next( X );
ry = RNG::next( Y );
if( monster_ok( rx, ry, player->getx() / 64, player->gety() / 64, 3 ) )
ok = true;
}
auto monster = std::make_unique<Monster>( m.type, rx, ry );
monster->set_action( Action::appear );
monsters.push_back( std::move( monster ) );
}
else
{
still_waiting.push_back( m );
}
}
mwait = std::move( still_waiting );
// Bonuses.
for( auto& e : bonuses ) e->tick( world );
// Once every crate is gone, open the exit portal somewhere clear.
if( world.crates_left == 0 && player )
{
world.crates_left--;
int rx = 0, ry = 0;
bool ok = false;
while( !ok )
{
rx = RNG::next( X );
ry = RNG::next( Y );
if( monster_ok( rx, ry, player->getx() / 64, player->gety() / 64, 4 ) )
ok = true;
}
at( rx, ry ) = Field::vortex();
bonuses.push_back( std::make_unique<Vortex>( rx, ry ) );
}
}
std::unique_ptr<Player> Map::create_player() const
{
return std::make_unique<Player>( px, py );
}
void Map::place_bomb( int x, int y )
{
Field& f = at( x, y );
if( f.is_floor_family() && f.kind != Field::Kind::bomb )
{
f = Field::bomb();
bombs.push_back( std::make_unique<Bomb>( x, y ) );
}
}
bool Map::monster_collide( int tx, int ty ) const
{
for( const auto& e : monsters )
{
if( ( e->getx() + 32 ) / 64 == ( tx + 32 ) / 64 &&
( e->gety() + 32 ) / 64 == ( ty + 32 ) / 64 )
return true;
}
return false;
}
}

120
examples/dyna/src/map.hpp Normal file
View File

@@ -0,0 +1,120 @@
#pragma once
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
namespace dyna
{
class Player;
class Bomb;
class Monster;
class Vortex;
class World;
// How a tile reacts to an explosion sweeping through it.
enum class Destruction
{
none, // blocks the blast (wall, bomb, existing explosion, vortex)
single, // destroyed and stops the blast (crate)
multi // passable, blast continues (floor)
};
// A grid cell. The C# version used a small class hierarchy rooted at a Field
// interface; since the variants differ only in a couple of flags and how they
// draw, this collapses them into one value type tagged by Kind. Note that in
// the original everything except Wall derived from Floor, so the "is Floor"
// checks there map to "kind != Wall" here.
struct Field
{
enum class Kind
{
floor,
wall,
crate,
bomb, // tile occupied by a live bomb (solid, indestructible)
explosion, // transient blast tile
vortex // level exit portal
};
enum class ExplosionType
{
center,
vertical,
horizontal,
left,
right,
down,
up
};
Kind kind = Kind::floor;
ExplosionType etype = ExplosionType::center;
std::int64_t tstart = 0; // explosion animation start, set on creation
static Field floor() { return Field{}; }
static Field wall() { return Field{ Kind::wall, {}, 0 }; }
static Field crate() { return Field{ Kind::crate, {}, 0 }; }
static Field bomb() { return Field{ Kind::bomb, {}, 0 }; }
static Field vortex() { return Field{ Kind::vortex, {}, 0 }; }
static Field explosion( ExplosionType t );
bool solid() const;
Destruction destructible() const;
void draw( int x, int y ) const;
bool is_floor_family() const { return kind != Kind::wall; }
};
class Map
{
public:
explicit Map( const std::string& fn );
~Map(); // defined in map.cpp where the entity types are complete
Field& at( int x, int y ) { return grid[index( x, y )]; }
const Field& at( int x, int y ) const { return grid[index( x, y )]; }
void draw();
void tick( World& world );
int getx() const { return X; }
int gety() const { return Y; }
int get_crates() const { return destructibles; }
std::unique_ptr<Player> create_player() const;
void place_bomb( int x, int y );
bool monster_collide( int tx, int ty ) const;
private:
static constexpr int X = 13, Y = 11;
// Deferred monster respawn timer, mirroring Map.MWait.
struct MWait
{
int type; // 1, 2 or 3
std::int64_t time; // timestamp at which it respawns
};
static int index( int x, int y ) { return x * Y + y; }
void load( const std::string& fn );
void generate_destructibles();
void populate_map();
bool monster_ok( int rx, int ry, int px, int py, int r ) const;
std::vector<Field> grid;
int px = -10, py = -10;
int destructibles = 0;
int m1 = 0, m2 = 0, m3 = 0;
std::vector<std::unique_ptr<Bomb>> bombs;
std::vector<std::unique_ptr<Monster>> monsters;
std::vector<std::unique_ptr<Vortex>> bonuses;
std::vector<MWait> mwait;
};
}

View File

@@ -0,0 +1,227 @@
#include "monster.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
namespace
{
bool is_opposite( Action a, Action b )
{
return ( a == Action::up && b == Action::down ) ||
( a == Action::down && b == Action::up ) ||
( a == Action::left && b == Action::right ) ||
( a == Action::right && b == Action::left );
}
} // namespace
Monster::Monster( int type, int gx, int gy )
: mtype( type )
, t( type == 1 ? 14 : type == 2 ? 11
: 7 )
{
x = gx * 64;
y = gy * 64;
}
void Monster::set_action( Action a )
{
Entity::set_action( a );
if( action == Action::appear )
left = 200;
}
std::vector<Action> Monster::possible_dirs( const Map& map ) const
{
std::vector<Action> dirs;
if( x > 0 && !map.at( x / 64 - 1, y / 64 ).solid() )
dirs.push_back( Action::left );
if( x / 64 < map.getx() - 1 && !map.at( x / 64 + 1, y / 64 ).solid() )
dirs.push_back( Action::right );
if( y > 0 && !map.at( x / 64, y / 64 - 1 ).solid() )
dirs.push_back( Action::up );
if( y / 64 < map.gety() - 1 && !map.at( x / 64, y / 64 + 1 ).solid() )
dirs.push_back( Action::down );
return dirs;
}
bool Monster::straight( const std::vector<Action>& dirs )
{
return is_opposite( dirs[0], dirs[1] );
}
Action Monster::any_dir( const Map& map )
{
std::vector<Action> dirs = possible_dirs( map );
if( dirs.empty() )
return Action::wait;
return dirs[RNG::next( static_cast<int>( dirs.size() ) )];
}
Action Monster::rand_dir( const Map& map )
{
Action tmp = any_dir( map );
if( is_opposite( action, tmp ) )
tmp = any_dir( map );
return tmp;
}
void Monster::think( const Map& map )
{
ZoneScoped;
if( action == Action::wait || action == Action::appear )
{
set_action( rand_dir( map ) );
return;
}
std::vector<Action> dirs = possible_dirs( map );
if( dirs.size() == 2 && straight( dirs ) )
{
left = 64;
}
else
{
Action tmp = rand_dir( map );
if( tmp == action )
{
left = 64;
}
else
{
set_action( tmp );
if( tmp != Action::wait )
left = 64;
}
}
}
void Monster::tick( World& world )
{
ZoneScoped;
Map& map = world.map();
delta += Timer::delta;
while( delta > t )
{
delta -= t;
if( action == Action::wait )
{
think( map );
}
else if( left > 0 )
{
left--;
switch( action )
{
case Action::down: y++; break;
case Action::up: y--; break;
case Action::left: x--; break;
case Action::right: x++; break;
default: break;
}
}
else
{
if( action == Action::death )
die( world );
else
think( map );
}
if( action != Action::death && killed( map ) )
{
set_action( Action::death );
left = 790 / t;
}
}
}
void Monster::die( World& )
{
dead = true;
}
const AnimTexture& Monster::texture_for( Action a ) const
{
struct Set
{
const AnimTexture* wait;
const AnimTexture* up;
const AnimTexture* down;
const AnimTexture* left;
const AnimTexture* right;
const AnimTexture* death;
};
Set s;
if( mtype == 1 )
s = { &Textures::m1_d, &Textures::m1_u, &Textures::m1_d, &Textures::m1_l, &Textures::m1_r, &Textures::m1_death };
else if( mtype == 2 )
s = { &Textures::m2_d, &Textures::m2_u, &Textures::m2_d, &Textures::m2_l, &Textures::m2_r, &Textures::m2_death };
else
s = { &Textures::m3_d, &Textures::m3_u, &Textures::m3_d, &Textures::m3_l, &Textures::m3_r, &Textures::m3_death };
switch( a )
{
case Action::up: return *s.up;
case Action::down: return *s.down;
case Action::left: return *s.left;
case Action::right: return *s.right;
case Action::death: return *s.death;
case Action::wait:
case Action::appear:
default: return *s.wait; // wait/appear use the "down" sprite
}
}
void Monster::draw()
{
ZoneScoped;
// The original returns without drawing for unexpected actions; monsters only
// ever hold the actions handled by texture_for, so always draw.
generic_draw( texture_for( action ) );
}
void Monster::generic_draw( const AnimTexture& tex )
{
int frame;
if( action == Action::wait )
{
frame = 0;
}
else if( action == Action::appear )
{
frame = 0;
Gfx::alpha( static_cast<float>( 200 - left ) / 200.0f );
}
else
{
frame = static_cast<int>( ( Timer::get_timestamp() - action_start ) / 40 );
}
tex.bind( frame );
Gfx::draw_sprite( x, y );
if( action == Action::appear )
Gfx::alpha( 1.0f );
}
}

View File

@@ -0,0 +1,41 @@
#pragma once
#include "entity.hpp"
#include <vector>
namespace dyna
{
class AnimTexture;
// The three monster variants from monster.cs differed only in speed, sprite set
// and respawn delay, so they fold into one class parameterised by `type` (1-3).
class Monster : public Entity
{
public:
Monster( int type, int gx, int gy );
void set_action( Action a ) override;
void tick( World& world ) override;
void draw() override;
void die( World& world ) override;
bool is_dead() const { return dead; }
int type() const { return mtype; }
private:
std::vector<Action> possible_dirs( const Map& map ) const;
static bool straight( const std::vector<Action>& dirs );
Action rand_dir( const Map& map );
Action any_dir( const Map& map ); // __rand_dir in the original
void think( const Map& map );
void generic_draw( const AnimTexture& tex );
const AnimTexture& texture_for( Action a ) const;
int mtype; // 1, 2 or 3
int t; // ms per movement sub-step (per-type speed)
bool dead = false;
};
}

View File

@@ -0,0 +1,127 @@
#include "player.hpp"
#include "gfx.hpp"
#include "map.hpp"
#include "texture.hpp"
#include "timer.hpp"
#include "world.hpp"
#include <tracy/Tracy.hpp>
namespace dyna
{
Player::Player( int gx, int gy )
{
x = gx * 64;
y = gy * 64;
set_action( Action::wait );
queue = Action::wait;
}
void Player::tick( World& world )
{
ZoneScoped;
Map& map = world.map();
delta += Timer::delta;
while( delta > t )
{
delta -= t;
if( left > 0 )
{
left--;
switch( action )
{
case Action::down: y++; break;
case Action::up: y--; break;
case Action::left: x--; break;
case Action::right: x++; break;
case Action::place_bomb:
if( left == 0 )
map.place_bomb( x / 64, y / 64 );
break;
default:
break;
}
}
else
{
if( action == Action::death )
{
die( world );
return;
}
if( map.at( x / 64, y / 64 ).kind == Field::Kind::vortex )
{
world.next_level = true;
return;
}
if( !can_move( queue, map ) )
queue = Action::wait;
if( action != queue )
set_action( queue );
if( action != Action::wait )
left = 64;
if( action == Action::place_bomb )
left = 32;
}
if( action != Action::death && killed( map ) )
{
set_action( Action::death );
left = 1140 / t;
}
}
}
void Player::draw()
{
ZoneScoped;
const AnimTexture* tex = nullptr;
switch( action )
{
case Action::wait: tex = &Textures::p_wait; break;
case Action::up: tex = &Textures::p_u; break;
case Action::down: tex = &Textures::p_d; break;
case Action::left: tex = &Textures::p_l; break;
case Action::right: tex = &Textures::p_r; break;
case Action::death: tex = &Textures::p_death; break;
case Action::place_bomb: tex = &Textures::p_wait; break;
default:
return;
}
int frame = static_cast<int>( Timer::get_timestamp() - action_start );
frame /= ( action == Action::death ) ? 60 : 40;
tex->bind( frame );
Gfx::draw_sprite( x, y );
}
void Player::move( Action a )
{
queue = a;
}
void Player::die( World& world )
{
world.killed = true;
}
bool Player::killed( const Map& map ) const
{
if( Entity::killed( map ) )
return true;
if( map.monster_collide( x, y ) )
return true;
return false;
}
}

View File

@@ -0,0 +1,27 @@
#pragma once
#include "entity.hpp"
namespace dyna
{
class Player : public Entity
{
public:
Player( int gx, int gy );
void tick( World& world ) override;
void draw() override;
void die( World& world ) override;
void move( Action a ); // queues the next direction; applied between tiles
protected:
bool killed( const Map& map ) const override;
private:
static constexpr int t = 6; // ms per movement sub-step
Action queue = Action::wait;
};
}

View File

@@ -0,0 +1,221 @@
#include "texture.hpp"
#include "datapath.hpp"
#include "gfx.hpp"
#include <SDL3/SDL.h>
#include <SDL3_image/SDL_image.h>
#include <tracy/Tracy.hpp>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <memory>
#include <vector>
namespace dyna
{
void GlTexture::reset()
{
if( id_ )
{
// The texture globals outlive main(), so their destructors can run after
// the GL context is already gone (which frees its textures anyway). Only
// call into GL while a context is current; otherwise just drop the name.
if( SDL_GL_GetCurrentContext() )
glDeleteTextures( 1, &id_ );
id_ = 0;
}
}
namespace
{
struct SurfaceDeleter
{
void operator()( SDL_Surface* s ) const { SDL_DestroySurface( s ); }
};
using SurfacePtr = std::unique_ptr<SDL_Surface, SurfaceDeleter>;
// Convert an arbitrary surface to tightly addressable RGBA8. Returns null on
// failure; the result owns its pixels.
SurfacePtr to_rgba( SDL_Surface* src )
{
ZoneScoped;
if( !src ) return nullptr;
return SurfacePtr{ SDL_ConvertSurface( src, SDL_PIXELFORMAT_RGBA32 ) };
}
} // namespace
bool Texture::load( const char* fn )
{
ZoneScoped;
ZoneText( fn, strlen( fn ) );
SurfacePtr image{ IMG_Load( fn ) };
if( !image )
{
std::fprintf( stderr, "Cannot open texture %s: %s\n", fn, SDL_GetError() );
return false;
}
SurfacePtr rgba = to_rgba( image.get() );
if( !rgba )
{
std::fprintf( stderr, "Cannot convert texture %s: %s\n", fn, SDL_GetError() );
return false;
}
// Pack the surface into a tight RGBA8 block, skipping any per-row padding.
const int w = rgba->w, h = rgba->h;
std::vector<std::uint8_t> packed( static_cast<size_t>( w ) * h * 4 );
const auto* pixels = static_cast<const std::uint8_t*>( rgba->pixels );
for( int row = 0; row < h; row++ )
{
std::memcpy( &packed[static_cast<size_t>( row ) * w * 4],
pixels + static_cast<size_t>( row ) * rgba->pitch,
static_cast<size_t>( w ) * 4 );
}
tex_ = GlTexture{ Render::make_texture( w, h, 1, packed.data() ) };
return static_cast<bool>( tex_ );
}
void Texture::bind() const
{
Render::use_texture( tex_.get(), 0 );
}
void AnimTexture::load( SDL_Surface* sheet, int tilex, int tiley, int n )
{
ZoneScoped;
SurfacePtr rgba = to_rgba( sheet );
if( !rgba )
{
std::fprintf( stderr, "Cannot convert sprite sheet: %s\n", SDL_GetError() );
return;
}
const auto* pixels = static_cast<const std::uint8_t*>( rgba->pixels );
const int pitch = rgba->pitch;
// Lay the n frames out back to back as the layers of an array texture.
constexpr int frame_bytes = 64 * 64 * 4;
std::vector<std::uint8_t> frames( static_cast<size_t>( n ) * frame_bytes );
for( int i = 0; i < n; i++ )
{
for( int fy = 0; fy < 64; fy++ )
{
int srcy = 64 * ( tiley + i ) + fy;
int srcx = 64 * tilex;
std::memcpy( &frames[static_cast<size_t>( i ) * frame_bytes + static_cast<size_t>( fy ) * 64 * 4],
pixels + static_cast<size_t>( srcy ) * pitch + static_cast<size_t>( srcx ) * 4,
static_cast<size_t>( 64 ) * 4 );
}
}
tex_ = GlTexture{ Render::make_texture( 64, 64, n, frames.data() ) };
frames_ = n;
}
void AnimTexture::bind( int frame ) const
{
if( frames_ <= 0 ) return;
int layer = frame % frames_;
if( layer < 0 ) layer += frames_;
Render::use_texture( tex_.get(), layer );
}
namespace Textures
{
Texture menu, sand, wall, crate;
AnimTexture p_wait, p_u, p_d, p_l, p_r, p_death;
AnimTexture bomb, bomb_appear, e_c, e_h, e_v, e_le, e_re, e_de, e_ue;
AnimTexture m1_death, m1_l, m1_r, m1_d, m1_u;
AnimTexture m2_death, m2_l, m2_r, m2_d, m2_u;
AnimTexture m3_death, m3_l, m3_r, m3_d, m3_u;
AnimTexture bonus1, bonus2;
AnimTexture vortex_appear, vortex;
void preload()
{
ZoneScoped;
menu.load( data_path( "data/gfx/menu.png" ).c_str() );
sand.load( data_path( "data/gfx/sand.png" ).c_str() );
wall.load( data_path( "data/gfx/wall.png" ).c_str() );
crate.load( data_path( "data/gfx/crate.png" ).c_str() );
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/Player.png" ).c_str() ) };
p_wait.load( img.get(), 0, 0, 20 );
p_d.load( img.get(), 1, 0, 20 );
p_u.load( img.get(), 2, 0, 20 );
p_l.load( img.get(), 3, 0, 20 );
p_r.load( img.get(), 4, 0, 20 );
p_death.load( img.get(), 5, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/Bomb.png" ).c_str() ) };
bomb.load( img.get(), 0, 0, 10 );
bomb_appear.load( img.get(), 5, 0, 10 );
e_c.load( img.get(), 1, 0, 5 );
e_h.load( img.get(), 2, 0, 5 );
e_v.load( img.get(), 1, 5, 5 );
e_le.load( img.get(), 3, 0, 5 );
e_re.load( img.get(), 2, 5, 5 );
e_de.load( img.get(), 4, 0, 5 );
e_ue.load( img.get(), 3, 5, 5 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster1.png" ).c_str() ) };
m1_death.load( img.get(), 0, 0, 20 );
m1_u.load( img.get(), 1, 0, 10 );
m1_l.load( img.get(), 2, 0, 10 );
m1_d.load( img.get(), 1, 10, 10 );
m1_r.load( img.get(), 2, 10, 10 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster2.png" ).c_str() ) };
m2_death.load( img.get(), 0, 0, 20 );
m2_d.load( img.get(), 1, 0, 20 );
m2_u.load( img.get(), 2, 0, 20 );
m2_l.load( img.get(), 3, 0, 20 );
m2_r.load( img.get(), 4, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/monster3.png" ).c_str() ) };
m3_death.load( img.get(), 0, 0, 20 );
m3_d.load( img.get(), 1, 0, 9 );
m3_u.load( img.get(), 2, 0, 9 );
m3_l.load( img.get(), 1, 10, 9 );
m3_r.load( img.get(), 2, 10, 9 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/bonusy.png" ).c_str() ) };
bonus1.load( img.get(), 0, 0, 20 );
bonus2.load( img.get(), 1, 0, 20 );
}
{
SurfacePtr img{ IMG_Load( data_path( "data/gfx/portal.png" ).c_str() ) };
vortex_appear.load( img.get(), 0, 0, 20 );
vortex.load( img.get(), 1, 0, 20 );
}
}
}
}

View File

@@ -0,0 +1,91 @@
#pragma once
#include <glad/gl.h>
struct SDL_Surface;
namespace dyna
{
// Move-only RAII owner of a GL texture name. Every texture in the game is a
// GL_TEXTURE_2D_ARRAY (static images use a single layer, animations use one
// layer per frame) so the renderer only ever has to deal with one sampler type.
class GlTexture
{
public:
GlTexture() = default;
explicit GlTexture( GLuint id ) noexcept : id_( id ) {}
~GlTexture() { reset(); }
GlTexture( GlTexture&& o ) noexcept : id_( o.id_ ) { o.id_ = 0; }
GlTexture& operator=( GlTexture&& o ) noexcept
{
if( this != &o )
{
reset();
id_ = o.id_;
o.id_ = 0;
}
return *this;
}
GlTexture( const GlTexture& ) = delete;
GlTexture& operator=( const GlTexture& ) = delete;
GLuint get() const { return id_; }
explicit operator bool() const { return id_ != 0; }
void reset(); // glDeleteTextures; safe on an empty handle
private:
GLuint id_ = 0;
};
// A single static texture loaded from a whole image file. Ported from
// texture.cs; binding just records the texture for the next draw call.
class Texture
{
public:
bool load( const char* fn );
void bind() const;
private:
GlTexture tex_;
};
// A vertical strip of 64x64 animation frames cut out of a sprite sheet, stored
// as the layers of one array texture. Mirrors AnimTexture in texture.cs.
class AnimTexture
{
public:
// Extract n frames from column `tilex`, starting at row `tiley`, where each
// coordinate is in 64px tile units. Mirrors AnimTexture.load in texture.cs.
void load( SDL_Surface* sheet, int tilex, int tiley, int n );
void bind( int frame ) const; // frame is taken modulo the frame count
private:
GlTexture tex_;
int frames_ = 0;
};
// All game textures, loaded once at startup. Mirrors the Textures class.
namespace Textures
{
extern Texture menu, sand, wall, crate;
extern AnimTexture p_wait, p_u, p_d, p_l, p_r, p_death;
extern AnimTexture bomb, bomb_appear, e_c, e_h, e_v, e_le, e_re, e_de, e_ue;
extern AnimTexture m1_death, m1_l, m1_r, m1_d, m1_u;
extern AnimTexture m2_death, m2_l, m2_r, m2_d, m2_u;
extern AnimTexture m3_death, m3_l, m3_r, m3_d, m3_u;
extern AnimTexture bonus1, bonus2;
extern AnimTexture vortex_appear, vortex;
void preload();
}
}

View File

@@ -0,0 +1,51 @@
#include "timer.hpp"
#include <SDL3/SDL.h>
#include <random>
namespace dyna
{
namespace Timer
{
int delta = 0;
static std::int64_t timestamp = 0;
void reset()
{
delta = 0;
timestamp = static_cast<std::int64_t>( SDL_GetTicks() );
}
int tick()
{
std::int64_t tmp = timestamp;
timestamp = static_cast<std::int64_t>( SDL_GetTicks() );
delta = static_cast<int>( timestamp - tmp );
return delta;
}
std::int64_t get_timestamp()
{
return timestamp;
}
}
namespace RNG
{
static std::mt19937& engine()
{
static std::mt19937 e{ std::random_device{}() };
return e;
}
int next( int n )
{
if( n <= 0 ) return 0;
std::uniform_int_distribution<int> dist( 0, n - 1 );
return dist( engine() );
}
}
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <cstdint>
namespace dyna
{
// Frame timing, ported from timer.cs. Timestamps are milliseconds since
// Timer::reset(); kept 64-bit so the modulo arithmetic the animation code
// relies on never overflows during a session.
namespace Timer
{
void reset();
int tick(); // advances the clock, returns delta in ms
std::int64_t get_timestamp();
extern int delta; // ms elapsed during the last tick()
}
// Thin wrapper over a single global PRNG, mirroring the C# RNG helper.
namespace RNG
{
int next( int n ); // uniform in [0, n)
}
}

View File

@@ -0,0 +1,40 @@
#include "world.hpp"
#include "map.hpp"
#include "player.hpp"
namespace dyna
{
World::World( const std::string& level_fn, bool with_player )
: map_( std::make_unique<Map>( level_fn ) )
, name_( level_fn.substr( level_fn.rfind( '/' ) + 1 ) )
{
if( with_player )
{
player_ = map_->create_player();
crates_left = map_->get_crates();
}
else
{
crates_left = -1; // the menu never opens an exit portal
}
}
World::~World() = default;
void World::tick()
{
map_->tick( *this );
if( player_ )
player_->tick( *this );
}
void World::draw()
{
map_->draw();
if( player_ )
player_->draw();
}
}

View File

@@ -0,0 +1,46 @@
#pragma once
#include <memory>
#include <string>
namespace dyna
{
class Map;
class Player;
// Owns the state for one running level: the map, the player (absent on the
// menu screen), and the flags the gameplay code used to reach through global
// variables. Passing a World& into the tick path replaces the old Game::p /
// Game::current_map / Game::killed globals, so there are no non-owning pointers
// to outlive the objects they point at.
class World
{
public:
// Loads `level_fn`; spawns a player from the map's '@' marker when
// with_player is set (gameplay) and leaves it null otherwise (menu).
World( const std::string& level_fn, bool with_player );
~World();
World( const World& ) = delete;
World& operator=( const World& ) = delete;
Map& map() { return *map_; }
const Map& map() const { return *map_; }
Player* player() { return player_.get(); } // null on the menu screen
const std::string& name() const { return name_; }
void tick();
void draw();
bool killed = false;
bool next_level = false;
int crates_left = 0;
private:
std::unique_ptr<Map> map_;
std::unique_ptr<Player> player_;
std::string name_;
};
}

View File

@@ -2,7 +2,20 @@
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/tracy">
<comment>Tracy Profiler trace file</comment>
<comment xml:lang="ar">ملف تتبع لمحلل الأداء Tracy</comment>
<comment xml:lang="de">Trace-Datei des Tracy-Profilers</comment>
<comment xml:lang="es">Archivo de traza del perfilador Tracy</comment>
<comment xml:lang="fr">Fichier de trace du profileur Tracy</comment>
<comment xml:lang="hi">Tracy प्रोफ़ाइलर ट्रेस फ़ाइल</comment>
<comment xml:lang="it">File di traccia del profiler Tracy</comment>
<comment xml:lang="ja">Tracy プロファイラーのトレースファイル</comment>
<comment xml:lang="pl">Zrzut sesji profilera Tracy</comment>
<comment xml:lang="pt">Ficheiro de rastreio do perfilador Tracy</comment>
<comment xml:lang="pt_BR">Arquivo de rastreamento do perfilador Tracy</comment>
<comment xml:lang="ru">Файл трассировки профилировщика Tracy</comment>
<comment xml:lang="uk">Файл трасування профілювальника Tracy</comment>
<comment xml:lang="zh_CN">Tracy 性能分析器跟踪文件</comment>
<comment xml:lang="zh_TW">Tracy 效能分析器追蹤檔案</comment>
<icon name="application-tracy"/>
<magic>
<match type="string" value="tlZ\x04" offset="0"/>

View File

@@ -1,12 +1,53 @@
[Desktop Entry]
Version=1.0
Version=1.5
Type=Application
Name=Tracy Profiler
GenericName=Code profiler
GenericName[ar]=محلل أداء الشيفرة البرمجية
GenericName[de]=Code-Profiler
GenericName[es]=Perfilador de código
GenericName[fr]=Profileur de code
GenericName[hi]=कोड प्रोफ़ाइलर
GenericName[it]=Profiler del codice
GenericName[ja]=コードプロファイラー
GenericName[pl]=Profiler kodu
GenericName[pt]=Analisador de desempenho de código
GenericName[pt_BR]=Analisador de desempenho de código
GenericName[ru]=Профилировщик кода
GenericName[uk]=Профілювальник коду
GenericName[zh_CN]=代码性能分析器
GenericName[zh_TW]=程式碼效能分析器
Comment=Examine code to see where it is slow
Comment[pl]=Znajdowanie wolno wykonującego się kodu
Exec=/usr/bin/tracy %f
Comment[ar]=افحص الشيفرة البرمجية لمعرفة مواضع البطء
Comment[de]=Code untersuchen, um langsame Stellen zu finden
Comment[es]=Examine el código para ver dónde es lento
Comment[fr]=Examiner le code pour voir où il est lent
Comment[hi]=कोड की जाँच करें कि वह कहाँ धीमा है
Comment[it]=Esamina il codice per vedere dove è lento
Comment[ja]=コードを調べて遅い箇所を見つける
Comment[pl]=Przeanalizuj kod, aby znaleźć wolne miejsca
Comment[pt]=Examine o código para ver onde é lento
Comment[pt_BR]=Examine o código para ver onde ele é lento
Comment[ru]=Изучите код, чтобы найти медленные места
Comment[uk]=Досліджуйте код, щоб знайти повільні місця
Comment[zh_CN]=检查代码,找出运行缓慢的部分
Comment[zh_TW]=檢查程式碼,找出執行緩慢的部分
Keywords=profiler;performance;tracing;frame;instrumentation;
Keywords[ar]=محلل;أداء;تتبع;إطار;قياس;
Keywords[de]=Profiler;Leistung;Tracing;Frame;Instrumentierung;
Keywords[es]=perfilador;rendimiento;trazado;fotograma;instrumentación;
Keywords[fr]=profileur;performance;traçage;trame;instrumentation;
Keywords[hi]=प्रोफ़ाइलर;प्रदर्शन;ट्रेसिंग;फ़्रेम;इंस्ट्रुमेंटेशन;
Keywords[it]=profiler;prestazioni;tracciamento;frame;strumentazione;
Keywords[ja]=プロファイラー;パフォーマンス;トレース;フレーム;計測;
Keywords[pl]=profiler;wydajność;śledzenie;klatka;instrumentacja;
Keywords[pt]=perfilador;desempenho;rastreio;fotograma;instrumentação;
Keywords[pt_BR]=perfilador;desempenho;rastreamento;quadro;instrumentação;
Keywords[ru]=профилировщик;производительность;трассировка;кадр;инструментирование;
Keywords[uk]=профілювальник;продуктивність;трасування;кадр;інструментування;
Keywords[zh_CN]=分析器;性能;跟踪;帧;插桩;
Keywords[zh_TW]=分析器;效能;追蹤;影格;插樁;
Exec=tracy-profiler %f
Icon=tracy
Terminal=false
Categories=Development;Profiling;

View File

@@ -1,7 +1,7 @@
# Tracy MCP eval guide
This document covers the bindings-layer detail that the curated catalog
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
This document covers the bindings-layer detail that the analysis
guidance (`tracy://prompt`) does not.
## ctx
@@ -21,8 +21,15 @@ data surface. Common entry points:
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
- Sections: `get_sections()` — timed code sections from
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
`{category, start, end, text}` dicts (start/end in ns); `category` is the
section's named group (multiple unrelated section sets can coexist in one
capture).
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
- Readiness: `is_background_done()` — see "Instance lifecycle" below before
querying zone/symbol stats right after `load_capture`.
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
@@ -41,24 +48,23 @@ Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
- Source-location IDs from `get_all_zone_source_locations()` are the join key
between zone-name lookups and per-callsite queries.
## Translating catalog entries to ctx Python
## Common query patterns
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
Python snippet:
Small Python snippets for the queries you'll reach for most often:
```python
# zone_list — top 10 hottest zones by total time
# top 10 hottest zones by total time
top = sorted(ctx.get_all_zone_stats().items(),
key=lambda kv: kv[1].total, reverse=True)[:10]
for k, v in top:
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
# frame_list — primary frame set timing
# primary frame set timing
times = ctx.get_frame_times() # ns per frame
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
# zone_stats for a named zone — find the srcloc id, then drill in
# stats for a named zone — find the srcloc id, then drill in
import re
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
@@ -70,3 +76,40 @@ stats = ctx.get_zone_stats(sid)
For long-running queries pass `async_mode=True` to `eval`; it returns
`{task_id, status: "running"}`. Poll with the `task` tool
(`action="poll", task_id=...`).
## Instance lifecycle
`tracy_mcp.py` runs as a long-lived singleton process shared across every
MCP client (all VS Code windows), and every `live_connect`/`load_capture`
instance holds its *entire* trace — zones, messages, callstacks, memory
events — resident in memory for as long as that process runs. Nothing
frees it just because your conversation ends.
- `load_capture` returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`is_background_done()` (or `background_done` in `list_instances`) before
relying on `get_all_zone_stats()` and similar stats-based queries; they
can return empty or partial results until it's true. For small captures
this finishes before you'd notice; for multi-GB ones it can take seconds.
- Call `unload_capture(instance_id)` as soon as you're done analyzing a
capture. Don't rely on automatic eviction as your primary cleanup path —
treat it as a backstop for forgotten sessions, not a substitute.
- `live_connect` caps a live session at `TRACY_MCP_LIVE_MEMORY_LIMIT_MB`
(default 8192, override per-call or set 0 to disable) — a long-lived
session on a busy target otherwise grows unbounded and can OOM-kill the
whole server process. Hitting the cap disconnects that instance cleanly;
data collected so far stays queryable and `save_trace`-able.
- `list_instances` reports `idle_seconds`, `connected`, and `background_done`
per instance; use it to spot stale ones before starting a new session,
especially if you're about to load several captures for comparison.
- The server provides three backstops, all configurable via environment
variables at startup: it evicts the least-recently-used *evictable*
instance (disconnected live sessions, or any loaded file capture — never
a still-connected live one) once the instance count reaches
`TRACY_MCP_MAX_INSTANCES` (default 4); it drops a disconnected live
instance that's sat idle for `TRACY_MCP_DISCONNECTED_TTL_S` (default
1800s / 30 min) so a session is still there for analysis right after the
target disconnects, but doesn't linger forever if forgotten; and it drops
a file-loaded capture that's sat idle for `TRACY_MCP_FILE_IDLE_TTL_S`
(default 1800s) — safe to let expire since it's already durably on disk,
just `load_capture` it again.

View File

@@ -0,0 +1 @@
mcp>=2.0.0,<3

View File

@@ -1,6 +1,8 @@
#!/bin/sh
# Start the Tracy MCP server.
#
# First run: pip install -r requirements.txt (next to this script).
#
# Set PYTHONPATH to the directory containing TracyServerBindings.so/.pyd.
# Adjust the Release/Debug suffix to match your CMake build configuration.
PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$(dirname "$0")/../../build/python/Release"

View File

@@ -5,6 +5,7 @@ import asyncio
import atexit
import builtins
import concurrent.futures
import faulthandler
import glob
import io
import os
@@ -14,10 +15,12 @@ import socket
import struct
import sys
import time
import urllib.error
import urllib.request
import uuid
from contextlib import redirect_stdout
from contextlib import asynccontextmanager, redirect_stdout
import mcp.server.fastmcp as fastmcp
from mcp.server.mcpserver import MCPServer
# Suppress noisy ASGI shutdown errors known to occur with SSE and Control-C.
# These occur when Starlette attempts to send a 500 error after the loop is cancelled
@@ -29,7 +32,13 @@ logging.getLogger("starlette").setLevel(logging.CRITICAL)
_HERE = os.path.dirname(os.path.abspath(__file__))
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
_CRASH_LOG_FILE = os.path.join(_HERE, "tracy_mcp.crash.log")
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
# MCPServer.run()'s own defaults -- tracked here too since v2's Settings no
# longer exposes them for us to read back (SDK v1 -> v2 migration).
_SSE_PATH = "/sse"
_STREAMABLE_HTTP_PATH = "/mcp"
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
# system prompt; exposing it as an MCP resource keeps analysis guidance in
@@ -155,10 +164,26 @@ async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
return list(seen.values())
def _is_our_server_running() -> tuple[bool, int]:
def _http_ping(port: int, timeout_s: float = 2.0) -> bool:
"""Confirms the server answers HTTP, not just that its PID exists — a
deadlocked process still passes os.kill(pid, 0). Any response, even an
error one, proves the transport is alive; only a timeout or refused
connection means it's not.
"""
Check the PID file to see if our server is already running.
Returns (running, port). Uses os.kill(pid, 0) to confirm the process is alive.
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=timeout_s)
return True
except urllib.error.HTTPError:
return True
except Exception:
return False
def _is_our_server_running() -> tuple[bool, int]:
"""Check whether our server is running: PID alive AND responding to
an HTTP self-ping (a hung process still passes os.kill(pid, 0)).
Returns (running, port).
"""
try:
with open(_PID_FILE) as f:
@@ -166,9 +191,19 @@ def _is_our_server_running() -> tuple[bool, int]:
with open(_PORT_FILE) as f:
port = int(f.read().strip())
os.kill(pid, 0) # raises OSError if process is gone
return True, port
except Exception:
return False, 0
if not _http_ping(port):
print(
f"Tracy MCP process {pid} is alive (PID check passed) but isn't "
f"responding on port {port} within {2.0:.0f}s -- likely hung/deadlocked, "
f"not just busy. Starting a fresh server on a new port; kill PID {pid} "
f"manually once you're able to (it's still holding the port and the "
f"TracyServerBindings.pyd file lock).",
file=sys.stderr,
)
return False, 0
return True, port
def _find_free_port() -> int:
@@ -218,8 +253,11 @@ except ImportError:
except ImportError:
tracy_server = None
mcp_server = fastmcp.FastMCP("Tracy Profiler")
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
_MAX_INSTANCES = int(os.environ.get("TRACY_MCP_MAX_INSTANCES", "4"))
_DISCONNECTED_TTL_S = float(os.environ.get("TRACY_MCP_DISCONNECTED_TTL_S", "1800"))
_FILE_IDLE_TTL_S = float(os.environ.get("TRACY_MCP_FILE_IDLE_TTL_S", "1800"))
_SWEEP_INTERVAL_S = float(os.environ.get("TRACY_MCP_SWEEP_INTERVAL_S", "300"))
_DEFAULT_LIVE_MEMORY_LIMIT_MB = int(os.environ.get("TRACY_MCP_LIVE_MEMORY_LIMIT_MB", "8192"))
class Task:
@@ -239,7 +277,126 @@ class TracyInstance:
self.worker = worker
self.path = None
self.mtime = None
self.last_used = time.time()
# Wall-clock time the instance was first observed disconnected (live
# instances only). None while connected or for file-loaded captures.
self.disconnected_since: float | None = None
def touch(self) -> None:
self.last_used = time.time()
def is_evictable(self) -> bool:
"""True if this instance is safe to drop without losing in-progress data.
A live instance still connected is actively recording — never evict it
automatically. Everything else (file captures, disconnected live
instances) is fair game for the LRU cap.
"""
if self.worker is not None and self.path is None:
try:
return not self.worker.is_connected()
except Exception:
return True
return True
def _shutdown_worker(worker: object | None) -> None:
if worker is None:
return
try:
worker.shutdown()
except Exception:
pass
def _evict_idle() -> list[str]:
"""Unload instances idle past their TTL: disconnected live instances
past _DISCONNECTED_TTL_S, file-loaded captures past _FILE_IDLE_TTL_S
(safe — already durably on disk). Connected live instances are never
touched here.
"""
now = time.time()
evicted = []
for name, inst in list(instances.items()):
if inst.path is not None:
if now - inst.last_used >= _FILE_IDLE_TTL_S:
del instances[name]
_shutdown_worker(inst.worker)
evicted.append(name)
continue
if inst.worker is None:
continue
try:
connected = inst.worker.is_connected()
except Exception:
connected = False
if connected:
inst.disconnected_since = None
continue
if inst.disconnected_since is None:
inst.disconnected_since = now
continue
if now - inst.disconnected_since >= _DISCONNECTED_TTL_S:
del instances[name]
_shutdown_worker(inst.worker)
evicted.append(name)
return evicted
def _evict_for_capacity(exclude: str | None = None) -> str | None:
"""If at/over _MAX_INSTANCES, drop the least-recently-used evictable
instance to make room for a new one. Returns the evicted name, if any."""
if len(instances) < _MAX_INSTANCES:
return None
candidates = [
inst for name, inst in instances.items()
if name != exclude and inst.is_evictable()
]
if not candidates:
return None
victim = min(candidates, key=lambda inst: inst.last_used)
del instances[victim.name]
_shutdown_worker(victim.worker)
return victim.name
async def _sweep_loop() -> None:
"""Periodic eviction sweep, doubling as a heartbeat — distinguishes a
crashed process (see faulthandler) from a hung one (stops producing
these lines). flush=True since a hang is exactly when buffering would
hide the last known-good timestamp.
"""
start = time.time()
while True:
await asyncio.sleep(_SWEEP_INTERVAL_S)
try:
evicted = _evict_idle()
except Exception:
evicted = None
print(
f"[heartbeat] uptime={int(time.time() - start)}s "
f"instances={len(instances)} tasks={len(tasks)} "
f"evicted={evicted or 'none'}",
file=sys.stderr,
flush=True,
)
@asynccontextmanager
async def _lifespan(_server: MCPServer):
sweep_task = asyncio.create_task(_sweep_loop())
try:
yield
finally:
sweep_task.cancel()
try:
await sweep_task
except asyncio.CancelledError:
pass
mcp_server = MCPServer("Tracy Profiler", lifespan=_lifespan)
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
instances: dict[str, TracyInstance] = {}
tasks: dict[str, Task] = {}
@@ -258,8 +415,7 @@ def _prompt_resource() -> str:
@mcp_server.resource("tracy://eval-guide")
def _eval_guide_resource() -> str:
"""Bindings-layer guide for the eval tool: ctx object model, time units,
source-location ID semantics, and worked examples translating catalog
entries into ctx Python."""
source-location ID semantics, and worked examples of common ctx queries."""
return _read_text(_EVAL_GUIDE_PATH)
@@ -273,13 +429,34 @@ async def list_captures() -> list[str]:
@mcp_server.tool()
async def list_instances() -> list[dict]:
"""List all loaded Tracy instances and captures with metadata."""
"""List all loaded Tracy instances and captures with metadata.
Each full trace stays resident in memory for as long as this server
process runs (it's a singleton shared across all MCP clients) until
unloaded. `idle_seconds` is how long since this instance was last used
via `eval`/`save_trace` — call `unload_capture` on ones you no longer
need instead of waiting for automatic eviction. Automatic eviction kicks
in at the `TRACY_MCP_MAX_INSTANCES` cap (LRU, connected live instances
are never evicted), after a disconnected live instance sits idle past
`TRACY_MCP_DISCONNECTED_TTL_S`, or after a file-loaded capture sits idle
past `TRACY_MCP_FILE_IDLE_TTL_S` — it's already durably on disk, so
nothing is lost; just `load_capture` it again.
`background_done: false` means a just-loaded capture is still building
zone/symbol statistics on a background thread — `get_all_zone_stats`
and similar stats-based `eval` queries can return empty or partial
results until this flips to true.
"""
now = time.time()
return [
{
"id": name,
"path": inst.path,
"mtime": inst.mtime,
"live": inst.path is None
"live": inst.path is None,
"connected": inst.worker.is_connected() if inst.path is None and inst.worker else None,
"background_done": inst.worker.is_background_done() if inst.worker else None,
"idle_seconds": now - inst.last_used,
}
for name, inst in instances.items()
]
@@ -311,11 +488,25 @@ async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
@mcp_server.tool()
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
async def live_connect(
address: str = "127.0.0.1",
port: int = 8086,
alias: str | None = None,
memory_limit_mb: int | None = None,
) -> str:
"""
Connect to a live running Tracy-instrumented application.
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
Wraps Worker(addr, port, memoryLimit). A long-lived session on a busy
target grows unbounded otherwise -- every zone/message/memory event
stays resident until disconnect, which can OOM-kill this whole process
rather than just the one instance. memory_limit_mb defaults to
TRACY_MCP_LIVE_MEMORY_LIMIT_MB (8192 if unset); pass 0 to disable.
Hitting the limit disconnects that Worker cleanly (Tracy's own
QueryTerminate path) -- already-collected data stays queryable and
save_trace-able, it just stops growing.
Returns the instance_id.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
@@ -337,8 +528,10 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
f"the target against a matching Tracy version."
)
limit_mb = _DEFAULT_LIVE_MEMORY_LIMIT_MB if memory_limit_mb is None else memory_limit_mb
memory_limit = limit_mb * 1024 * 1024 if limit_mb > 0 else -1
try:
w = tracy_server.Worker(address, port)
w = tracy_server.Worker(address, port, memory_limit)
except Exception as e:
return f"Failed to connect: {str(e)}"
@@ -382,9 +575,14 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
)
name = alias or f"live_{address}_{port}"
if name in instances:
_shutdown_worker(instances[name].worker)
evicted = _evict_for_capacity(exclude=name)
instances[name] = TracyInstance(name, w)
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
limit_note = f"{limit_mb}MB memory limit" if limit_mb > 0 else "no memory limit"
return (
f"Connected to live instance as '{name}'. "
f"Connected to live instance as '{name}'{note} ({limit_note}). "
f"Before your first eval, read resources tracy://prompt "
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
f"ns time units, srcloc IDs)."
@@ -404,6 +602,12 @@ async def load_capture(path: str, alias: str | None = None) -> str:
If you don't already have a path, call `list_captures` first — it lists
.tracy files in the TRACY_CAPTURES_DIR environment directory.
Loading returns before zone/symbol statistics finish building — a
background thread populates them after the file itself is read. Check
`background_done` in `list_instances` (or poll it) before relying on
`get_all_zone_stats` and similar stats-based `eval` queries; they can
return empty or partial results until it's true.
"""
if not tracy_server:
return "Error: Tracy Server bindings not found."
@@ -418,7 +622,11 @@ async def load_capture(path: str, alias: str | None = None) -> str:
if name in instances:
inst = instances[name]
if inst.path == path and inst.mtime == mtime:
inst.touch()
return f"Instance '{name}' is already loaded and up to date."
_shutdown_worker(inst.worker)
evicted = _evict_for_capacity(exclude=name)
f = tracy_server.open_file(path)
w = tracy_server.create_worker_from_file(f)
@@ -426,8 +634,9 @@ async def load_capture(path: str, alias: str | None = None) -> str:
inst.path = path
inst.mtime = mtime
instances[name] = inst
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
return (
f"Loaded as '{name}'. "
f"Loaded as '{name}'{note}. "
f"Before your first eval, read resources tracy://prompt "
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
f"ns time units, srcloc IDs)."
@@ -476,6 +685,7 @@ async def save_trace(
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
instance.touch()
if not os.path.isabs(path):
if captures_dir and os.path.basename(path) == path:
@@ -542,9 +752,18 @@ async def _execute_save(worker: object, path: str, level: int, streams: int, fi_
@mcp_server.tool()
async def unload_capture(instance_id: str) -> str:
"""Unload a Tracy instance and release its memory."""
"""Unload a Tracy instance and release its memory.
Prefer calling this as soon as you're done analyzing a capture — every
loaded instance holds the *entire* trace (zones, messages, callstacks,
memory events) in memory for as long as this server process runs, and
the server is a long-lived singleton shared across all MCP clients.
Automatic eviction exists as a backstop (see `list_instances`), not a
substitute for unloading what you no longer need.
"""
if instance_id in instances:
del instances[instance_id]
inst = instances.pop(instance_id)
_shutdown_worker(inst.worker)
return f"Instance '{instance_id}' unloaded."
return f"Instance '{instance_id}' not found."
@@ -566,6 +785,7 @@ async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> o
instance = instances[instance_id]
if not instance.worker:
return f"Error: Instance '{instance_id}' has no worker."
instance.touch()
if not async_mode:
return await _execute_eval(code, instance.worker)
@@ -675,8 +895,22 @@ async def shutdown_server() -> str:
if __name__ == "__main__":
# Enabled before anything touches the native bindings, so a genuine
# fatal crash (segfault etc.) writes a thread-state traceback to disk
# before the process dies — works on Windows via
# SetUnhandledExceptionFilter, same mechanism as POSIX signals.
_crash_log_fh = open(_CRASH_LOG_FILE, "a", encoding="utf-8")
faulthandler.enable(file=_crash_log_fh, all_threads=True)
atexit.register(_cleanup_pid_files)
if _TRANSPORT not in ("sse", "streamable-http"):
print(
"TRACY_MCP_TRANSPORT must be 'sse' or 'streamable-http'.",
file=sys.stderr,
)
sys.exit(1)
running, existing_port = _is_our_server_running()
if running:
print(
@@ -689,12 +923,18 @@ if __name__ == "__main__":
port = _find_free_port()
_write_pid_and_port(port)
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
print(f"Tracy MCP listening on http://127.0.0.1:{port}{path}", file=sys.stderr)
mcp_server.settings.host = "127.0.0.1"
mcp_server.settings.port = port
# v2's MCPServer takes transport options on run(), not the constructor
# or a mutable settings object (SDK v1 -> v2 migration).
run_kwargs = {"host": "127.0.0.1", "port": port}
if _TRANSPORT == "sse":
run_kwargs["sse_path"] = _SSE_PATH
else:
run_kwargs["streamable_http_path"] = _STREAMABLE_HTTP_PATH
try:
mcp_server.run(transport="sse")
mcp_server.run(transport=_TRANSPORT, **run_kwargs)
except KeyboardInterrupt:
print("\nTracy MCP server stopped.", file=sys.stderr)
sys.exit(0)

21
extra/tracy-version.sh Executable file
View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Derive the project version from the single source of truth:
# public/common/TracyVersion.hpp. Prints X.Y.Z, exits nonzero on failure.
header="$1"
[ -r "$header" ] || {
echo "tracy-version: cannot read $header" >&2
exit 1
}
major=$(sed -n 's/.*Major = \([0-9][0-9]*\).*/\1/p' "$header")
minor=$(sed -n 's/.*Minor = \([0-9][0-9]*\).*/\1/p' "$header")
patch=$(sed -n 's/.*Patch = \([0-9][0-9]*\).*/\1/p' "$header")
if [ -n "$major" ] && [ -n "$minor" ] && [ -n "$patch" ]; then
printf '%s.%s.%s\n' "$major" "$minor" "$patch"
else
echo "tracy-version: could not parse version from $header" >&2
exit 1
fi

View File

@@ -1,13 +0,0 @@
#!/bin/sh
version_header="../public/common/TracyVersion.hpp"
major=$(grep -o -E 'Major = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
minor=$(grep -o -E 'Minor = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
patch=$(grep -o -E 'Patch = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
version="${major}.${minor}.${patch}"
# the extension is required for macOS's outdated sed
sed -i.bak "s/version: '[0-9]*\.[0-9]*\.[0-9]*'/version: '$version'/g" ../meson.build
rm ../meson.build.bak

31
import/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -12,12 +12,15 @@ sed -i -e 's@\\LMB{}~@@g' _tmp.tex
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
sed -i -e 's@\\textsigma@σ@g' _tmp.tex
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
# pandoc doens't recognize this lstlisting option and emits it verbatim
sed -i -e 's|,\?escapeinside={}{}||g' _tmp.tex
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex

File diff suppressed because it is too large Load Diff

View File

@@ -21,6 +21,7 @@
\usepackage{nameref}
\usepackage{diagbox}
\usepackage{csquotes}
\usepackage{longtable}
\usepackage[hmarginratio=1:1,top=32mm,columnsep=20pt]{geometry} % Document margins
\geometry{a4paper,textwidth=6.5in,hmarginratio=1:1,
@@ -357,11 +358,19 @@ Tracy can be found at the following web addresses:
\subsubsection{Binary distribution}
The version releases of the profiler are provided as precompiled Windows binaries for download at \url{https://github.com/wolfpld/tracy/releases}, along with the user manual. You will need to install the latest Visual C++ redistributable package to use them.
Prebuilt binary releases of the profiler and the user manual can be found at \url{https://github.com/wolfpld/tracy/releases}. The following platforms are supported:
Development builds of Windows binaries, and the user manual are available as artifacts created by the automated Continuous Integration system on GitHub.
\begin{itemize}
\item \textbf{Windows} -- Requires the latest Visual C++ redistributable package to be installed.
\item \textbf{macOS} -- Needs manual quarantine handling before any provided binary can be executed, e.g.:
\begin{lstlisting}
xattr -d com.apple.quarantine tracy-profiler.app
\end{lstlisting}
Thanks, Tim Apple!
\item \textbf{Linux} -- AppImage with Ubuntu 24.04 used as a baseline. Requires Wayland and an implementation of an XDG desktop portal to be installed.
\end{itemize}
Note that these binary releases require AVX2 instruction set support on the processor. If you have an older CPU, you will need to set a proper instruction set architecture in the project properties and build the executables yourself.
Development builds of binaries and the user manual are available as artifacts created by the automated continuous integration system on GitHub.
\section{First steps}
\label{firststeps}
@@ -486,6 +495,78 @@ target_link_libraries(<TARGET> PUBLIC TracyClient)
While using \texttt{set(CMAKE\_INTERPROCEDURAL\_OPTIMIZATION ON)} is a convenient way to enable Link-Time Optimization (LTO) for an entire project, there are situations in which this may not work due to excessive compilation times, linking issues, compiler bugs, or other reasons.
For such cases, Tracy provides an option to enable Link-Time Optimization for itself using the \texttt{TRACY\_LTO} variable during the CMake configuration stage.
\subsubsection{Build options}
\label{buildoptions}
Table~\ref{cmakeoptions} lists the options you may want to set in the program you are profiling. All of them are boolean switches defaulting to \texttt{OFF}, with three exceptions: \texttt{TRACY\_STATIC}, which defaults to the opposite of \texttt{BUILD\_SHARED\_LIBS}, and \texttt{TRACY\_CALLSTACK} and \texttt{TRACY\_PLATFORM\_HEADER}, which take a value instead of a boolean.
Each enabled option is passed down to your program as a preprocessor macro of the same name. If you are not using CMake, you can achieve the same results by defining these macros yourself, as described in section~\ref{initialsetup}. Note that some of the listed options additionally affect how the client library itself is built, so they cannot be replaced with a macro definition alone.
\begin{footnotesize}
\begin{longtable}{@{}p{0.4\textwidth}p{0.56\textwidth}@{}}
\caption{Tracy CMake options}\label{cmakeoptions} \\
\hline
\textbf{Option} & \textbf{Description} \\ \hline
\endfirsthead
\hline
\endhead
\hline
\endfoot
\hline
\endlastfoot
\textbf{Basic setup} & \\
\texttt{TRACY\_ENABLE} & Enable profiling. Without it, the instrumentation is compiled out and the client does nothing (section~\ref{initialsetup}). \\
\texttt{TRACY\_ON\_DEMAND} & Collect data only while the profiler is connected, instead of starting at program launch (section~\ref{ondemand}). \\
\texttt{TRACY\_NO\_EXIT} & Don't let the client exit until all the profiling data has been sent to the server. Useful for short-lived applications. \\
\texttt{TRACY\_STATIC} & Build the client as a static (\texttt{ON}) or shared (\texttt{OFF}) library. Multi-DLL projects need a shared library (section~\ref{multidll}). \\
\texttt{TRACY\_LTO} & Enable link-time optimization of the client library itself. \\
& \\
\textbf{Networking} & \\
\texttt{TRACY\_NO\_BROADCAST} & Don't announce the client's presence on the local network. Manual connections are still possible. \\
\texttt{TRACY\_ONLY\_LOCALHOST} & Listen for connections only on the localhost interface. \\
\texttt{TRACY\_ONLY\_IPV4} & Accept connections only on IPv4 addresses, disabling IPv6 support. \\
& \\
\textbf{Data collection} & \\
\texttt{TRACY\_NO\_SYSTEM\_TRACING} & Prevent the client from accessing kernel data, which disables all system-level tracing (section~\ref{automated}). \\
\texttt{TRACY\_NO\_CONTEXT\_SWITCH} & Disable capture of context switch data (section~\ref{contextswitches}). \\
\texttt{TRACY\_NO\_SAMPLING} & Disable call stack sampling (section~\ref{sampling}). \\
\texttt{TRACY\_NO\_WAIT\_STACKS} & Disable capture of context switch call stacks, while keeping the context switch events themselves (section~\ref{waitstacks}). \\
\texttt{TRACY\_NO\_VSYNC\_CAPTURE} & Disable capture of hardware Vsync events (section~\ref{vsync}). \\
\texttt{TRACY\_NO\_CODE\_TRANSFER} & Disable retrieval of the program's machine code and source files (section~\ref{executableretrieval}). \\
\texttt{TRACY\_NO\_FRAME\_IMAGE} & Disable frame image support, along with the compression thread it needs (section~\ref{frameimages}). \\
\texttt{TRACY\_NO\_CRASH\_HANDLER} & Don't install the crash handler (section~\ref{crashhandling}). \\
\texttt{TRACY\_NO\_VERIFY} & Disable validation of the C API instrumentation correctness (section~\ref{capi}). \\
\texttt{TRACY\_FIBERS} & Enable fiber support, which is opt-in due to the additional cost it adds to zone capture (section~\ref{fibers}). \\
\texttt{TRACY\_IGNORE\_MEMORY\_FAULTS} & Don't report instrumentation failures for free events with no matching allocation, or for repeated allocations of the same address (section~\ref{memoryprofiling}). \\
\texttt{TRACY\_OPENGL\_AUTO\_CALIBRATION} & Periodically correct the OpenGL GPU/CPU clock drift, at the cost of a CPU/GPU synchronization each time (section~\ref{gpuprofiling}). \\
\texttt{TRACY\_ROCPROF\_CALIBRATION} & Periodically correct the ROCm GPU/CPU clock drift. Available only if rocprofiler-sdk was found (section~\ref{rocm}). \\
& \\
\textbf{Call stacks and symbols} & \\
\texttt{TRACY\_CALLSTACK} & Call stack capture depth, which makes the zone macros without the \texttt{S} postfix collect call stacks (section~\ref{collectingcallstacks}). \\
\texttt{TRACY\_NO\_CALLSTACK} & Disable all call stack related functionality (section~\ref{collectingcallstacks}). \\
\texttt{TRACY\_NO\_CALLSTACK\_INLINES} & Don't resolve inline frames, retrieval of which may be very slow on Windows. \\
\texttt{TRACY\_LIBUNWIND\_BACKTRACE} & Capture call stacks using libunwind, which may be faster than the default implementation. Links with libunwind. \\
\texttt{TRACY\_DEBUGINFOD} & Enable on-demand delivery of debugging symbols through debuginfod. Links with libdebuginfod. \\
\texttt{TRACY\_SYMBOL\_OFFLINE\_RESOLVE} & Record only image paths and offsets, leaving symbol resolution to be performed later by the \texttt{update} utility. \\
\texttt{TRACY\_LIBBACKTRACE\_ELF\_DYNLOAD\_SUPPORT} & Let libbacktrace resolve symbols in ELF objects that were loaded after the first symbol resolution took place. \\
\texttt{TRACY\_DEMANGLE} & Don't use the built-in symbol demangling function. You will have to provide your own \texttt{\_\_tracy\_demangle} implementation. \\
& \\
\textbf{Timers} & \\
\texttt{TRACY\_TIMER\_FALLBACK} & Compile in a lower resolution timer, to be used when the hardware timer is unavailable (table~\ref{timeroptions}). \\
\texttt{TRACY\_DISALLOW\_HW\_TIMER} & Never use the hardware timer, even if it \emph{appears} to be available. Requires \texttt{TRACY\_TIMER\_FALLBACK}. \\
& \\
\textbf{Special environments} & \\
\texttt{TRACY\_MANUAL\_LIFETIME} & Provide the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions for manual profiler lifetime management (section~\ref{multidll}). \\
\texttt{TRACY\_PLATFORM\_HEADER} & Path to a header providing the \texttt{TRACY\_HAS\_CUSTOM\_*} hooks for an unsupported platform (section~\ref{customplatform}). \\
\texttt{TRACY\_PATCHABLE\_NOPSLEDS} & Emit nopsleds in front of the timer reads, so that system-level tools such as \emph{rr} can patch them. \\
& \\
\textbf{Diagnostics} & \\
\texttt{TRACY\_VERBOSE} & Print detailed information about the detected features to the standard error output (section~\ref{troubleshooting}). \\
\texttt{TRACY\_NO\_INTERNAL\_MESSAGE} & Don't send the profiler's own diagnostic messages to the server (section~\ref{troubleshooting}). \\
\texttt{TRACY\_ASSERT} & Route the client's internal checks through a custom assert implementation, instead of the standard \texttt{assert}. Define \texttt{TRACY\_ASSERT(condition)} before including any Tracy header; the macro must accept a single condition argument. \\
\end{longtable}
\end{footnotesize}
\subsubsection{Meson integration}
If you are using the Meson build system, you can add Tracy using the Wrap dependency system. To do this, place the \texttt{tracy.wrap} file in the \texttt{subprojects} directory of your project, with the following content. The \texttt{head} \texttt{revision} field tracks Tracy's \texttt{master} branch. If you want to lock to a specific version of Tracy instead, you can just set the \texttt{revision} field to an appropriate git tag.
@@ -523,6 +604,8 @@ Here's a sample command to set up a build directory with profiling enabled. The
meson setup build --buildtype=debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
\end{lstlisting}
Most of the options listed in table~\ref{cmakeoptions} are also available in the Meson build. Their names are lowercased, with the \texttt{TRACY\_} prefix dropped, the only exception being \texttt{tracy\_enable}.
\subsubsection{Short-lived applications}
In case you want to profile a short-lived program (for example, a compression utility that finishes its work in one second), set the \texttt{TRACY\_NO\_EXIT} environment variable to $1$. With this option enabled, Tracy will not exit until an incoming connection is made, even if the application has already finished executing. If your platform doesn't support an easy setup of environment variables, you may also add the \texttt{TRACY\_NO\_EXIT} define to your build configuration, which has the same effect.
@@ -534,6 +617,8 @@ By default, Tracy will begin profiling even before the program enters the \textt
You should note that if on-demand profiling is \emph{disabled} (which is the default), then the recorded events will be stored in the system memory until a server connection is made and the data can be uploaded\footnote{This memory is never released, but the profiler reuses it for collection of other events.}. Depending on the amount of the things profiled, the requirements for event storage can quickly grow up to a couple of gigabytes. Furthermore, since this data is no longer available after the initial connection, you won't be able to perform a second connection to a client unless the on-demand mode is used.
In on-demand mode, each connection to the server is a separate capture. A zone is included in a capture only if both its begin and end events occur during that connection: if the connection is lost and re-established while a zone is open, the end event is dropped and the zone remains unclosed in the previous capture. Zones that begin and end during a later connection, even while an older zone is still open, appear in the new capture as top-level zones.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -557,6 +642,7 @@ If you need to use a specific Tracy client address, such as QNX requires, define
By default, the Tracy client will listen on IPv6 interfaces, falling back to IPv4 only if IPv6 is unavailable. If you want to restrict it to only listening on IPv4 interfaces, define the \texttt{TRACY\_ONLY\_IPV4} macro at compile-time, or set the \texttt{TRACY\_ONLY\_IPV4} environment variable to $1$ at runtime.
\subsubsection{Setup for multi-DLL projects}
\label{multidll}
Things are a bit different in projects that consist of multiple DLLs/shared objects. Compiling \texttt{TracyClient.cpp} into every DLL is not an option because this would result in several instances of Tracy objects lying around in the process. We instead need to pass their instances to the different DLLs to be reused there.
@@ -564,9 +650,9 @@ For that, you need a \emph{profiler DLL} to which your executable and the other
If you are targeting Windows with Microsoft Visual Studio or MinGW, add the \texttt{TRACY\_IMPORTS} define to your application.
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining both \texttt{TRACY\_DELAYED\_INIT} and \texttt{TRACY\_MANUAL\_LIFETIME} macros.
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining the \texttt{TRACY\_MANUAL\_LIFETIME} macro.
\texttt{TRACY\_DELAYED\_INIT} enables a path where profiler data is gathered into one structure and initialized on the first request rather than statically at the DLL load at the expense of atomic load on each request to the profiler data. \texttt{TRACY\_MANUAL\_LIFETIME} flag augments this behavior to provide manual \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually. This manual management removes the need to do an atomic load on each call and lets you define an appropriate place to free the resources.
\texttt{TRACY\_MANUAL\_LIFETIME} provides the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually, letting you define an appropriate place to free the resources. Under \texttt{TRACY\_MANUAL\_LIFETIME} the profiler does not exist until \texttt{StartupProfiler()} is called and ceases to exist after \texttt{ShutdownProfiler()}. Using any instrumentation, zones, locks, plots, messages, and so on before startup or after shutdown is an error. Use \texttt{TracyIsStarted} to guard instrumentation that may run conditionally. If the profiler has been initialized, it must be shut down before the program exits.
\begin{bclogo}[
noborder=true,
@@ -705,6 +791,7 @@ The platform header is intended only for the \texttt{TRACY\_HAS\_CUSTOM\_*} hook
\end{bclogo}
\subsubsection{Troubleshooting}
\label{troubleshooting}
By default, Tracy's diagnostics will be sent as Message logs (section~\ref{messagelog}) to the server.
Setting the \texttt{TRACY\_NO\_INTERNAL\_MESSAGE} define will disable this feature, but setting the \texttt{TRACY\_VERBOSE} will make the client print advanced information about the detected features to the standard error output. By matching those debug prints to the source code, you might be able to uncover why some of the features are missing on your platform.
@@ -1474,6 +1561,32 @@ Exiting the profiled application from inside a zone is not supported. When the c
As a workaround, you may add a \texttt{try}/\texttt{catch} pair at the bottom of the function stack (for example in the \texttt{main()} function) and replace \texttt{exit()} calls with throwing a custom exception. When this exception is caught, you may call \texttt{exit()}, knowing that the application's data structures (including profiling zones) were properly cleaned up.
\subsection{Marking sections}
\label{markingsections}
To provide a high-level overview of what your program is doing, you may mark sections of execution. For example, in a puzzle game, the main menu would be a section, and each of the levels would be a section on its own.
To mark when a section starts, use the \texttt{TracySectionEnter(fmt, ...)} macro with printf-like formatting. The macro will return an \texttt{uint32\_t} identifier, which you must store and pass to the corresponding section-end macro, \texttt{TracySectionLeave(id)}. Here's an example of a simple RAII wrapper:
\begin{lstlisting}
struct TracySection
{
explicit TracySection(const char* name) : idx(TracySectionEnter("%s", name)) {}
~TracySection() { TracySectionLeave(idx); }
uint32_t idx;
};
\end{lstlisting}
Each of the sections you add is handled independently of any other section. Multiple sections may overlap, either with a hierarchical structure or with each section having a part that does not coincide with other sections.
\subsubsection{Section categories}
In some use cases, you will want to label your sections with specific category labels. For example, you may want to mark which level is currently loaded in your game and also mark when the inventory screen is open. When you are only interested in tracking the level progression, sifting through all the times the inventory was on the screen would be quite cumbersome. Section categories let you filter out uninteresting sections.
To set a category for the section, start the section with the \texttt{TracySectionEnterCategory(category, fmt, ...)} macro, where \texttt{category} is an \texttt{uint16\_t} identifier. Sections without an explicit category assignment (i.e., made with the \texttt{TracySectionEnter} macro) are automatically in the $0$ category.
You can describe each of the categories you want to use with the \texttt{TracySectionSetup(category, fmt, ...)} macro. Section categories with no name set will automatically be assigned a name based on the identifier (e.g., \emph{Category 0}).
\subsection{Marking locks}
Modern programs must use multi-threading to achieve the full performance capability of the CPU. However, correct execution requires claiming exclusive access to data shared between threads. When many threads want to simultaneously enter the same critical section, the application's multi-threaded performance advantage nullifies. To help solve this problem, Tracy can collect and display lock interactions in threads.
@@ -1602,11 +1715,14 @@ Tracy can monitor the memory usage of your application. Knowledge about each per
\item Memory allocation hot-spot tree.
\end{itemize}
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically you would do that in overloads of \texttt{operator new} and \texttt{operator delete}, for example:
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically, you would place them in overloads of \texttt{operator new} and \texttt{operator delete}. The order in which memory events actually occur must match the order reported to the profiler, so a mutex or similar synchronization primitive is required to make the whole operation atomic. Here is an example implementation:
\begin{lstlisting}
std::mutex memoryLock;
void* operator new(std::size_t count)
{
std::lock_guard lock(memoryLock);
auto ptr = malloc(count);
TracyAlloc(ptr, count);
return ptr;
@@ -1614,6 +1730,7 @@ void* operator new(std::size_t count)
void operator delete(void* ptr) noexcept
{
std::lock_guard lock(memoryLock);
TracyFree(ptr);
free(ptr);
}
@@ -1661,6 +1778,8 @@ Tracy provides bindings for profiling OpenGL, Vulkan, Direct3D 11, Direct3D 12,
Note that the CPU and GPU timers may be unsynchronized unless you create a calibrated context, but the availability of calibrated contexts is limited. You can try to correct the desynchronization of uncalibrated contexts in the profiler's options (section~\ref{options}).
GPU contexts are identified by a process-wide unique id, assigned when the context is created (the context creation macros do this automatically). Ids are 8 bits wide (0--255) and are allocated sequentially, so a process can create at most 256 GPU contexts.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -1814,6 +1933,7 @@ To instrument a GPU zone, use the various \texttt{TracyWebGPU*Zone*()} macros. N
You are required to periodically collect the GPU events using the \texttt{TracyWebGPUCollect()} macro. Good places for collection are: after synchronous waits, after event processing \texttt{wgpuInstanceProcessEvents}, after present drawable calls (\texttt{wgpuSurfacePresent}), and inside the completion callback of command queues (\texttt{wgpuQueueOnSubmittedWorkDone}).
\subsubsection{ROCm}
\label{rocm}
On Linux, if rocprofiler-sdk is installed, tracy can automatically trace GPU dispatches and collect
performance counter values. If CMake can't find rocprofiler-sdk, you can set the CMake variable
@@ -2221,7 +2341,7 @@ Refer to sections~\ref{markingzones} and~\ref{multizone} for description of macr
Unlike C++, there's no automatic destruction mechanism in C, so you will need to mark where the zone ends manually. To do so use the \texttt{TracyCZoneEnd(ctx)} macro.\footnote{GCC and Clang provide \texttt{\_\_attribute\_\_((cleanup))} which can used to run a function when a variable goes out of scope.}
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. For printf-style formatting, use the \texttt{TracyCZoneTextF(ctx, fmt, ...)} and \texttt{TracyCZoneNameF(ctx, fmt, ...)} variants, which accept a format string and arguments instead of a fixed-length text buffer. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
\paragraph{Zone context data structure}
\label{zonectx}
@@ -2364,7 +2484,7 @@ Moreover, there are two sets of functions described below. The standard set send
A GPU context can be created with the \texttt{\_\_\_tracy\_emit\_gpu\_new\_context} function (or the serialized variant). You'll need to specify:
\begin{itemize}
\item \texttt{context} -- a unique context id.
\item \texttt{context} -- a unique context id (8-bit, 0--255; see section~\ref{gpuprofiling}).
\item \texttt{gpuTime} -- an initial GPU timestamp.
\item \texttt{period} -- the timestamp period of the GPU.
\item \texttt{flags} -- the flags to use.
@@ -2420,10 +2540,7 @@ Tracy C API exposes functions with the \texttt{\_\_\_tracy} prefix that you may
\item \texttt{\_\_\_tracy\_alloc\_srcloc\_name(uint32\_t line, const char* source, size\_t sourceSz, const char* function, size\_t functionSz, const char* name, size\_t nameSz)}
\end{itemize}
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the
name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the
size of the corresponding string arguments in bytes. You may additionally specify an optional zone
name, by providing it in the \texttt{name} variable, and specifying its size in \texttt{nameSz}.
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the sizes of the corresponding string arguments in bytes. You may additionally specify an optional zone name by providing it in the \texttt{name} variable and specifying its size in \texttt{nameSz}. If the passed strings contain null-terminating characters, these characters must be excluded from the provided sizes.
The \texttt{\_\_\_tracy\_alloc\_srcloc} and \texttt{\_\_\_tracy\_alloc\_srcloc\_name} functions
return an \texttt{uint64\_t} source location identifier corresponding to an \emph{allocated source
@@ -2467,7 +2584,7 @@ An especially powerful feature is the ability to profile Python code and any oth
An example of how to use the Tracy-Client bindings is shown below:
\begin{lstlisting}
\begin{lstlisting}[language=Python,escapeinside={}{}]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
@@ -2544,7 +2661,7 @@ if __name__ == "__main__":
main()
\end{lstlisting}
Please not the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
Please note the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
\subsubsection{Building the Python package}
@@ -2602,16 +2719,17 @@ Set the following environment variables before launching (or export them in your
PYTHONPATH=/path/to/tracy/build/python/Release
TRACY_CAPTURES_DIR=/path/to/captures # enables list_captures
TRACY_MCP_PORT=47380 # optional; default 47380
TRACY_MCP_TRANSPORT=streamable-http # optional; streamable-http or sse
\end{lstlisting}
\subsubsection{Integrating with an AI assistant}
The server runs as a singleton on SSE transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately.
The server runs as a singleton on Streamable HTTP transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately. Set \texttt{TRACY\_MCP\_TRANSPORT=sse} before launching to use the legacy SSE transport instead.
The server prints its URL on startup and writes it to \texttt{extra/mcp/tracy\_mcp.port}:
\begin{lstlisting}
Tracy MCP listening on http://127.0.0.1:47380/sse
Tracy MCP listening on http://127.0.0.1:47380/mcp
\end{lstlisting}
Configure your AI assistant using that URL. For example, for a JSON-based MCP configuration:
@@ -2620,7 +2738,7 @@ Configure your AI assistant using that URL. For example, for a JSON-based MCP co
{
"mcpServers": {
"tracy": {
"url": "http://127.0.0.1:47380/sse"
"url": "http://127.0.0.1:47380/mcp"
}
}
}
@@ -2719,7 +2837,6 @@ You can integrate Tracy with CMake by adding the git submodule folder as a subdi
option(TRACY_ENABLE "" ON)
# must be enabled
option(TRACY_Fortran "" ON)
option(TRACY_DELAYED_INIT "" ON)
option(TRACY_MANUAL_LIFETIME "" ON)
add_subdirectory(3rdparty/tracy) # target: TracyClientF90 or alias Tracy::TracyClientF90
\end{lstlisting}
@@ -2753,7 +2870,6 @@ When using CMake 3.11 or newer, you can use Tracy via CMake FetchContent. In thi
\begin{lstlisting}
option(TRACY_Fortran "" ON)
option(TRACY_DELAYED_INIT "" ON)
option(TRACY_MANUAL_LIFETIME "" ON)
FetchContent_Declare(
@@ -3042,6 +3158,10 @@ The sampling functionality also captures call stacks for context switch events.
\item Unexpected waits, which should be immediately taken care of. After all, what's the point of profiling and optimizing your program if it is constantly waiting for something? An example of such an unexpected wait may be some anti-virus service interfering with each of your file read operations. In this case, you could have assumed that the system would buffer a large chunk of the data after the first read to make it immediately available to the application in the following calls.
\end{enumerate}
Since context switch samples are captured at scheduling events rather than by the statistical sampling timer, they are not an unbiased measure of program execution. They are therefore only displayed on the timeline and in the wait stacks window, and are excluded from the sampling statistics, the per-symbol sample data, child calls, entry stacks, flame graph, zone call stack reconstruction, and so on.
Collection of wait stacks makes the kernel walk the stack at context switch events, which may add considerable overhead on weaker hardware. Use the \texttt{TRACY\_NO\_WAIT\_STACKS} macro to disable capture of wait stacks, while still keeping the context switch events themselves.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -3095,6 +3215,7 @@ On Linux, Tracy will override the \texttt{dlclose} function call to prevent shar
\end{bclogo}
\subsubsection{Vertical synchronization}
\label{vsync}
On Windows and Linux, Tracy will automatically capture hardware Vsync events, provided that the application has access to the kernel data (privilege elevation may be needed, see section~\ref{privilegeelevation}). These events will be reported as \enquote{\texttt{[x] Vsync}} frame sets, where \texttt{x} is the identifier of a specific monitor. Note that hardware vertical synchronization might not correspond to the one seen by your application due to desktop composition, command queue buffering, and so on. Also, in some instances, when there is nothing to update on the screen, the graphic driver may choose to stop issuing screen refresh. As a result, there may be periods where no vertical synchronization events are reported.
@@ -3109,9 +3230,15 @@ Sometimes it is desired to change how the profiled application behaves during th
void Callback(void* data, uint32_t idx, int32_t val)
\end{lstlisting}
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface.
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface (in the connection information popup, see section~\ref{connectionpopup}).
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, isBool, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to). \texttt{Name} is the parameter label, displayed on the list of parameters. Finally, \texttt{isBool} determines if \texttt{val} should be interpreted as a boolean value, or as an integer number.
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, type, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to), \texttt{name} is the parameter label, displayed on the list of parameters, and \texttt{val} is the initial value. Finally, \texttt{type} determines how to interpret the \texttt{val} value, and can be selected from:
\begin{itemize}
\item \texttt{TracyParamTypeInt} -- \texttt{val} is an integer value, with the profiler UI displaying a numerical entry field.
\item \texttt{TracyParamTypeBool} -- \texttt{val} is a boolean value, with a checkbox in the user interface.
\item \texttt{TracyParamTypeTrigger} -- a~\emph{\faCircleDot{}~Trigger} button is displayed, for cases when you just want some action to happen. Repeats the initial \texttt{val} value provided to the setup function.
\end{itemize}
\begin{bclogo}[
noborder=true,
@@ -3312,7 +3439,7 @@ You can also adjust some settings that affect global profiler behavior in this w
If this is a real-time capture, you will also have access to the connection information pop-up (figure~\ref{connectioninfo}) through the \emph{\faWifi{}~Connection} button, with the capture status similar to the one displayed by the command-line utility. This dialog also shows the connection speed graphed over time and the profiled application's current frames per second and frame time measurements. The \emph{Query backlog} consists of two numbers. The first represents the number of queries that were held back due to the bandwidth volume overwhelming the available network send buffer. The second one shows how many queries are in-flight, meaning requests sent to the client but not yet answered. While these numbers drain down to zero, the performance of real time profiling may be temporarily compromised. The circle displayed next to the bandwidth graph signals the connection status. If it's red, the connection is active. If it's gray, the client has disconnected.
You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
You can use the \faFloppyDisk{}~\emph{Save trace...} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
\begin{figure}[h]
\centering\begin{tikzpicture}
@@ -3335,7 +3462,7 @@ You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current pro
If frame image capture has been implemented (chapter~\ref{frameimages}), a thumbnail of the last received frame image will be provided for reference.
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{Trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
\subsubsection{Automatic loading or connecting}
@@ -3591,7 +3718,7 @@ The main profiler window is split into three sections, as seen in figure~\ref{ma
\draw[rounded corners=5pt] (1.3, -0.1) rectangle+(2.2, -0.5) node [midway] {\faComment{} Messages};
\draw[rounded corners=5pt] (3.6, -0.1) rectangle+(1.3, -0.5) node [midway] {\faMagnifyingGlass{} Find};
\draw[rounded corners=5pt] (5.0, -0.1) rectangle+(2, -0.5) node [midway] {\faArrowUpWideShort{} Statistics};
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFire{} Flame};
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFireFlameCurved{} Flame};
\draw[rounded corners=5pt] (8.7, -0.1) rectangle+(2.1, -0.5) node [midway] {\faMemory{} Memory};
\draw[rounded corners=5pt] (10.9, -0.1) rectangle+(2.1, -0.5) node [midway] {\faScaleBalanced{} Compare};
\draw[rounded corners=5pt] (13.1, -0.1) rectangle+(1.2, -0.5) node [midway] {\faFingerprint{} Info};
@@ -3627,7 +3754,7 @@ The control menu (top row of buttons) provides access to various profiler featur
\item \emph{\faComment{} Messages} -- Toggles the message log window (section~\ref{messages}), which displays custom messages sent by the client, as described in section~\ref{messagelog}.
\item \emph{\faMagnifyingGlass{} Find} -- This buttons toggles the find zone window, which allows inspection of zone behavior statistics (section~\ref{findzone}).
\item \emph{\faArrowUpWideShort{} Statistics} -- Toggles the statistics window, which displays zones sorted by their total time cost (section~\ref{statistics}).
\item \emph{\faFire{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
\item \emph{\faFireFlameCurved{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
\item \emph{\faMemory{} Memory} -- Various memory profiling options may be accessed here (section~\ref{memorywindow}).
\item \emph{\faScaleBalanced{} Compare} -- Toggles the trace compare window, which allows you to see the performance difference between two profiling runs (section~\ref{compare}).
\item \emph{\faFingerprint{} Info} -- Show general information about the trace (section~\ref{traceinfo}).
@@ -3638,6 +3765,7 @@ The control menu (top row of buttons) provides access to various profiler featur
\item \emph{\faNoteSticky{}~Annotations} -- If annotations have been made (section~\ref{annotatingtrace}), you can open a list of all annotations, described in chapter~\ref{annotationlist}.
\item \emph{\faRuler{}~Limits} -- Displays time range limits window (section~\ref{timeranges}).
\item \emph{\faHourglassHalf{}~Wait stacks} -- If sampling was performed, an option to display wait stacks may be available. See chapter~\ref{waitstacks} for more details.
\item \emph{\faImages{}~Frame statistics} -- Display frame statistics window (section~\ref{framestatistics}).
\end{itemize}
\item \emph{ \faBook{}~User manual} -- Opens the user manual for quick reference. Note that the version of the user manual available directly in the profiler is an inferior quality version compared to the proper PDF.
\item \emph{\faMagnifyingGlassPlus{}~Display scale} -- Enables run-time resizing of the displayed content. This may be useful in environments with potentially reduced visibility, e.g. during a presentation. Note that this setting is independent to the UI scaling coming from the system DPI settings. The scale will be preserved across multiple profiler sessions if the \emph{Save UI scale} option is selected in global settings.
@@ -3737,7 +3865,7 @@ Clicking the \LMB{}~left mouse button on the graph while the \keys{\ctrl}~key is
\subsubsection{Timeline view}
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into three parts: the time scale, the frame sets, and the combined zones, locks, and plots display.
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into four parts: time scale, frame sets, sections, and the combined zones, locks, and plots display.
\subparagraph{Collapsed items}
\label{collapseditems}
@@ -3824,6 +3952,40 @@ If a frame has an associated frame image (see chapter~\ref{frameimages}), you ca
If the \emph{\faFlagCheckered{}~Draw frame targets} option is enabled (see section~\ref{options}), time regions in frames exceeding the set target value will be marked with a red background.
\paragraph{Sections}
\label{sections}
If sections of program execution were marked (see chapter~\ref{markingsections}), the profiler will try to reconstruct a child-parent hierarchy between the sections, as presented on figure~\ref{figsections}.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw(0, 0) rectangle+(2, -0.5) node[midway] {Menu};
\draw(2, 0) rectangle+(10, -0.5) node[midway] {In-game};
\draw(12, 0) rectangle+(1.5, -0.5) node[midway] {Menu};
\draw(2, -0.5) rectangle+(3, -0.5) node[midway] {Level 1};
\draw(5, -0.5) rectangle+(5, -0.5) node[midway] {Level 2};
\draw(10, -0.5) rectangle+(2, -0.5) node[midway] {Level 3};
\end{tikzpicture}
\caption{Sections showing a hierarchy.}
\label{figsections}
\end{figure}
With some data sets such reconstruction will not be possible, in which case sections may look as on figure~\ref{figsections2}.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw(0, 0) rectangle+(8, -0.5) node[midway] {First section};
\draw(3, -0.5) rectangle+(8, -0.5) node[midway] {Second section};
\end{tikzpicture}
\caption{Sections that cannot form a hierarchy.}
\label{figsections2}
\end{figure}
Clicking the \MMB{}~middle mouse button on a section will zoom the view to the extent of the section.
Section hierarchy reconstruction is not affected by section categories. There is only a single lane for all sections. Enabling or disabling specific section categories in the options window, however, will affect which sections are visible here.
\paragraph{Zones, locks and plots display}
\label{zoneslocksplots}
@@ -3919,7 +4081,7 @@ In an example in figure~\ref{zoneslocks} you can see that there are two threads:
Meanwhile, the \emph{Streaming thread} is performing some \emph{Streaming jobs}. The first \emph{Streaming job} sent a message (section~\ref{messagelog}). In addition to being listed in the message log, it is indicated by a triangle over the thread separator. When multiple messages are in one place, the triangle outline shape changes to a filled triangle.
The GPU zones are displayed just like CPU zones, with an OpenGL/Vulkan/Direct3D/Metal/OpenCL/CUDA/WebGPU context in place of a thread name.
The GPU zones are displayed just like CPU zones, with an OpenGL / Vulkan / Direct3D / Metal / OpenCL / CUDA / WebGPU context in place of a thread name.
Hovering the \faArrowPointer{} mouse pointer over a zone will highlight all other zones that have the exact source location with a white outline. Clicking the \LMB{}~left mouse button on a zone will open the zone information window (section~\ref{zoneinfo}). Holding the \keys{\ctrl} key and clicking the \LMB{}~left mouse button on a zone will open the zone statistics window (section~\ref{findzone}). Clicking the \MMB{}~middle mouse button on a zone will zoom the view to the extent of the zone.
@@ -3943,7 +4105,15 @@ Clicking the \LMB{}~left mouse button on a ghost zone will open the correspondin
\subparagraph{Call stack samples}
The row of dots right below the \emph{Main thread} label shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Hovering the \faArrowPointer{}~mouse pointer over each dot will display a short call stack summary while clicking on the dot with the \LMB{}~left mouse button will open a more detailed call stack information window (see section~\ref{callstackwindow}).
The row of dots right below the thread label (\emph{Main thread} in the example) shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Each sample is color-coded as follows:
\begin{itemize}
\item \emph{Green} -- shows samples inside the profiled program.
\item \emph{Blue} -- indicates samples belonging to external libraries.
\item \emph{Red} -- used for samples captured in the kernel.
\end{itemize}
Hovering the \faArrowPointer{}~mouse pointer over each dot displays a short call stack summary, while clicking the dot with the \LMB{}~left mouse button opens a more detailed call stack information window (see section~\ref{callstackwindow}).
\subparagraph{Context switches}
@@ -4027,7 +4197,7 @@ Mutual exclusion zones are displayed in each thread that tries to acquire them.
\item \emph{Red region} -- The thread wants to acquire the lock but is blocked by other thread or threads in case of a shared lock.
\end{itemize}
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Hovering the \faArrowPointer{}~mouse pointer over a thread label shows that thread's aggregate lock wait time across all locks (see above). Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
\subparagraph{Plots}
\label{plots}
@@ -4072,17 +4242,21 @@ Sometimes, you may want to specify a time range, such as limiting some statistic
To define a time range, drag the \LMB{}~left mouse button over the timeline view while holding the \keys{\ctrl} key. When the mouse key is released, the profiler will mark the selected time extent with a blue striped pattern, and it will display a context menu with the following options:
\begin{itemize}
\item \emph{\faMagnifyingGlass{}~Limit find zone time range} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
\item \emph{\faArrowUpWideShort{}~Limit statistics time range} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details.
\item \emph{\faFire{}~Limit flame graph time range} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
\item \emph{\faHourglassHalf{}~Limit wait stacks time range} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
\item \emph{\faMemory{}~Limit memory time range} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
\item \faRuler{}~Set time range for:
\begin{itemize}
\item \emph{\faMagnifyingGlass{}~Find zone} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
\item \emph{\faArrowUpWideShort{}~Statistics} -- selecting this option will limit statistics results (section~\ref{statistics}), as well as the sample statistics shown in the source view (section~\ref{sourceview}).
\item \emph{\faFireFlameCurved{}~Flame graph} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
\item \emph{\faHourglassHalf{}~Wait stacks} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
\item \emph{\faMemory{}~Memory} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
\item \emph{\faImages{}~Frame statistics} -- limits frame statistics. Section~\ref{framestatistics} has more information.
\end{itemize}
\item \emph{\faNoteSticky{}~Add annotation} -- use to annotate regions of interest, as described in chapter~\ref{annotatingtrace}.
\end{itemize}
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone or a frame. The resulting time extent will match the selected item.
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone, section or a frame. The resulting time extent will match the selected item.
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu. Time range limits can also be disabled or enabled in the windows showing the data they affect.
You can freely adjust each time range on the timeline by clicking the \LMB{}~left mouse button on the range's edge and dragging the mouse.
@@ -4093,15 +4267,14 @@ Tracy allows adding custom notes to the trace. For example, you may want to mark
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section~\ref{annotationsettings}), allowing you to enter your own description of the annotation.
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the text description area will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw (0, 0.25) -- (0, 1) -- (5, 1) -- (5, 0.25);
\draw[dotted] (0, -0.2) -- (0, 0.25);
\draw[dotted] (5, -0.2) -- (5, 0.25);
\draw (0.25, 0.75) circle(0.15);
\draw (0.4, 0.7) node[anchor=west] {Description};
\draw (0, -0.25) -- (0, -1) -- (5, -1) -- (5, -0.25);
\draw[dotted] (0, 0.2) -- (0, -0.25);
\draw[dotted] (5, 0.2) -- (5, -0.25);
\draw (0, -0.8) node[anchor=west] {\faNoteSticky{}~Description};
\end{tikzpicture}
\caption{Annotation region.}
\label{annotation}
@@ -4128,7 +4301,9 @@ In this window, you can set various trace-related options. For example, the time
\begin{itemize}
\item \emph{\faSignature{} Draw CPU usage graph} -- You can disable drawing of the CPU usage graph here.
\end{itemize}
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL/Vulkan/Metal/Direct3D/OpenCL/CUDA/WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
\item \emph{\faEyeDropper{}~Draw stack samples} -- Controls if stack samples for each thread are displayed on the timeline.
\item \emph{\faArrowsLeftRightToLine{}~Draw sections} -- Allows disabling display of sections (see chapter~\ref{sections}). If there are multiple section categories, there's also an expandable list of categories that can be disabled or enabled.
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL / Vulkan / Metal / Direct3D / OpenCL / CUDA / WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
\item \emph{\faMicrochip{} Draw CPU zones} -- Determines whether CPU zones are displayed.
\begin{itemize}
\item \emph{\faGhost{} Draw ghost zones} -- Controls if ghost zones should be displayed in threads which don't have any instrumented zones available.
@@ -4166,7 +4341,7 @@ For now, restoring the defaults can be done by deleting the configuration file.
\subsection{Messages window}
\label{messages}
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message.
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message. Clicking the \RMB{}~right mouse button on a message will display context menu with a \emph{\faClipboard{}~Copy message} option, which copies the message text to clipboard.
The \emph{call stack} column is filled only if a call stack capture was requested, as described in section~\ref{collectingcallstacks}. A single entry consists of the \emph{\faAlignJustify{}~Show} button, which opens the call stack information window (chapter~\ref{callstackwindow}) and of abbreviated information about the call path.
@@ -4217,7 +4392,7 @@ The \emph{Name} column contains name of the symbol in which the sampling was don
By default, each inlining of a function is listed separately. If you prefer to combine the measurements for functions that are inlined multiple times within a function, you can do so by enabling the \emph{\faLayerGroup{}~Aggregate} option. You cannot view sample entry stacks of inlined functions when this grouping method is enabled.
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire*~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire{}~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
If the \emph{\faSitemap{}~Inlines} option is enabled, the list will show all functions without grouping them by symbol. In this mode, inline functions are preceded by a \faCaretRight{} symbol and their parent function name is displayed in parentheses.
@@ -4225,9 +4400,9 @@ The \emph{Location} column displays the corresponding source file name and line
The location data is complemented by the originating executable image name, contained in the \emph{Image} column. If the \emph{\faScissors{}~Short images} option is selected, the image path will be shortened to just the image file name, with the full path available in the tooltip.
The profiler may not find some function locations due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
The profiler may not find some function names due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the total number of collected samples. You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the number of collected samples, excluding context switch samples, which do not participate in the sampling statistics (section~\ref{waitstacks}). You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
The last column, \emph{Code size}, displays the size of the symbol in the executable image of the program. Since inlined routines are directly embedded into other functions, their symbol size will be based on the parent symbol and displayed as 'less than'. In some cases, this data won't be available. If the symbol code has been retrieved\footnote{Symbols larger than 128~KB are not captured.} symbol size will be prepended with the \texttt{\faDatabase}~icon, and clicking the \RMB{}~right mouse button on the location column entry will open symbol view window (section~\ref{symbolview}).
@@ -4363,7 +4538,21 @@ Clicking the \LMB{}~left mouse button on the group name will highlight the group
The call stack grouping mode has a different way of listing groups. Here only one group is displayed at any time due to the need to display the call stack frames. You can switch between call stack groups by using the~\faCaretLeft{}~and~\faCaretRight{} buttons. You can select the group by clicking on the~\emph{\faCheck{}~Select} button. You can open the call stack window (section~\ref{callstackwindow}) by pressing the~\emph{\faAlignJustify{}~Call~stack} button.
Tracy displays a variety of statistical values regarding the selected function: mean (average value), median (middle value), mode (most common value, quantized using histogram bins), and \textsigma{} (standard deviation). The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
Tracy displays a variety of statistical values regarding the selected function:
\begin{itemize}
\item \emph{mean} -- average time value,
\item \emph{median} -- middle time value (P50),
\item \emph{mode} -- most common time value, quantized using histogram bins,
\item \emph{\textsigma} -- standard deviation,
\item \emph{coefficient of variation} -- relation of \textsigma{} to mean,
\item \emph{P75} -- 75th percentile time value,
\item \emph{P90} -- 90th percentile time value,
\item \emph{P99} -- 99th percentile time value,
\item \emph{P99.9} -- 99.9th percentile time value.
\end{itemize}
The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
Hovering the \faArrowPointer{}~mouse cursor over a zone on the timeline, which is currently selected in the find zone window, will display a pulsing vertical bar on the histogram, highlighting the bin to which the hovered zone has been assigned. In addition, it will also highlight zone entry on the zone list.
@@ -4419,11 +4608,27 @@ You can further narrow down the list of samples by selecting a time range on the
Comparing the performance impact of the optimization work is not an easy thing to do. Benchmarking is often inconclusive, if even possible, in the case of interactive applications, where the benchmarked function might not have a visible impact on frame render time. Furthermore, doing isolated micro-benchmarks loses the application's execution environment, in which many different parts compete for limited system resources.
Tracy solves this problem by providing a compare traces functionality, very similar to the find zone window, described in section~\ref{findzone}. You can compare traces either by zone or frame timing data.
Tracy solves this problem by providing a compare traces functionality, which is very similar to the find zone window. There are three tabs in this window, for three modes of comparison:
You would begin your work by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
\begin{itemize}
\item \emph{Zones} -- closely matches the find zone window, described in section~\ref{findzone}.
\item \emph{Frames} -- shows frame rate data, following the frame statistics window described in section~\ref{framestatistics}.
\item \emph{Source diff} -- displays differences in source code, covered in section~\ref{sourcefilesdiff}.
\end{itemize}
Now things start to get familiar. You search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram\footnote{When comparing frame times you are presented with a list of available frame sets, without the search box.}. This time there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
To gather the data, you would start by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bclampe
]{Trace descriptions}
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
\end{bclogo}
Now things start to get familiar. In the zone comparison mode, you search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram. When comparing frame times you are presented with a list of available frame sets, without the search box.
In the compare traces window there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
\begin{figure}[h]
\centering\begin{tikzpicture}
@@ -4460,15 +4665,12 @@ When searching for source locations it's not uncommon to match more than one zon
It may be difficult, if not impossible, to perform identical runs of a program. This means that the number of collected zones may differ in both traces, influencing the displayed results. To fix this problem, enable the \emph{Normalize values} option, which will adjust the displayed results as if both traces had the same number of recorded zones.
\begin{bclogo}[
noborder=true,
couleur=black!5,
logo=\bclampe
]{Trace descriptions}
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
\end{bclogo}
The window shows statistical data for both traces, with the values described in chapter~\ref{findzone}. Each value is also compared against the other trace, showing both the absolute and relative differences. The keywords \emph{less} and \emph{more} are color coded to note significance. When the two readings are almost the same, the keywords will be printed in gray. As the values diverge, the amount of color (either green or red) increases, maxing out at twice the difference. This allows quick eyeballing of where the difference is.
In some cases you may want to compare only specific parts of traces, not the whole performance profile. To do so, you can enable the \emph{Limit range} option, which will then present you with time range limiting options for both traces. Use the \emph{\faCopy{}~Copy from...} button to choose a time range limit from a list of annotations (chapter~\ref{annotatingtrace}) or sections (chapter~\ref{sections}). To remove the limit, you can click on the \emph{Reset} button.
\subsubsection{Source files diff}
\label{sourcefilesdiff}
To see what changes were made in the source code between the two compared traces, select the \emph{Source diff} compare mode. This will display a list of deleted, added, and changed files. By default, the difference is calculated from the older trace to the newer one. You can reverse this by clicking on the \emph{Switch} button.
@@ -4535,6 +4737,10 @@ Similar to the statistics window (section~\ref{statistics}), the flame graph can
In the sampling mode, external frames from system libraries are hidden by default. These typically include internal implementation details of starting threads, handling smart pointers, and other such things that are quick to execute and not really interesting. Enabling the \emph{\faShieldHalved{}~External} option will show these frames. One exception is \emph{external tails}, or calls that your code makes that do not eventually land in your application down the call chain. Think of functions that write to a file or send data on the network. These can be time-consuming, and you may want to see them. There is a separate option to disable these.
The sampling mode, by default, groups the displayed zones by symbol address. In some cases, for example, when using templates, the same function may exist at multiple different symbol addresses. Enabling the \emph{Group by name} option will group the graph items by name instead of by symbol address.
Sometimes a stack frame cannot be resolved to a symbol, as described in section~\ref{callstackwindow}. Such frames are aggregated into a single graph item with the label of the executable image the frame address belongs to, or with \enquote{\texttt{[unknown]}} if even this information is unavailable. The label is slightly dimmed to set it apart from the resolved items. The \emph{\faScissors{}~Short images} option shortens the displayed image name to only the file name.
The flame graph can be restricted to a specific time extent using the \emph{Limit range} option (chapter~\ref{timeranges}). You can access more options through the \emph{\faRuler{}~Limits} button, which will open the time range limits window, described in section~\ref{timerangelimits}.
\subsection{Memory window}
@@ -4610,8 +4816,6 @@ If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containi
Open the \emph{Trace statistics} section to see information about the trace, such as achieved timer resolution, number of captured zones, lock events, plot data points, memory allocations, etc.
There's also a section containing the selected frame set timing statistics and histogram\footnote{See section~\ref{findzone} for a description of the histogram. Note that there are subtle differences in the available functionality.}. As a convenience, you can switch the active frame set here and limit the displayed frame statistics to the frame range visible on the screen.
If \emph{CPU topology} data is available (see section~\ref{cputopology}), you will be able to view the package, core, and thread hierarchy.
The \emph{Source location substitutions} section allows adapting the source file paths, as captured by the profiler, to the actual on-disk locations\footnote{This does not affect source files cached during the profiling run.}. You can create a new substitution by clicking the \emph{Add new substitution} button. This will add a new entry, with input fields for ECMAScript-conforming regular expression pattern and its corresponding replacement string. You can quickly test the outcome of substitutions in the \emph{example source location} input field, which will be transformed and displayed below, as \emph{result}.
@@ -4635,6 +4839,13 @@ In this window, you can view the information about the machine on which the prof
If an application should crash during profiling (section~\ref{crashhandling}), the profiler will display the crash information in this window. It provides you information about the thread that has crashed, the crash reason, and the crash call stack (section~\ref{callstackwindow}).
\subsection{Frame statistics window}
\label{framestatistics}
This windows shows statistical information about the selected frame set timing, and a histogram. See section~\ref{findzone} for a description of the displayed data. As a convenience, you can switch the active frame set here.
You can restrict the displayed frame statistics to the frame range currently visible on the screen with the \emph{Limit to view} option, while the \emph{Limit range} switch limits the active range to a preset time limit, as described in chapter~\ref{timerangelimits}. If both limit options are active, the resulting frame range is the intersection of both ranges.
\subsection{Zone information window}
\label{zoneinfo}
@@ -4684,6 +4895,8 @@ A single stack frame may have multiple function call places associated with it.
If the call stack shows a crash (see section~\ref{crashhandling}), a red \emph{\faSkull{}~Crash} label will be displayed. Clicking it will center the timeline on the crash. Note that the crash stack may contain OS or Tracy frames where the crash was intercepted and processed.
If the call stack shows a wait stack (see section~\ref{waitstacks}), a blue \emph{\faHourglassHalf{}~Wait stack} label will be displayed. Hovering the \faArrowPointer{}~mouse pointer over it will display a tooltip displaying how much time was spent waiting in the stack, what was the wait reason and status.
Stack frame location may be displayed in the following number of ways, depending on the \emph{Frame~at} option selection:
\begin{itemize}
@@ -4699,7 +4912,7 @@ External frames from system libraries are hidden by default. Enabling the \emph{
The \emph{\faScissors{}~Short images} option shortens the displayed executable image name to only the file name. The full path is available in the tooltip.
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faDoorOpen{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faArrowDownShortWide{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
Clicking on the \emph{\faClipboard{}~Copy to clipboard} button will copy call stack to the clipboard.
@@ -4767,7 +4980,7 @@ Nevertheless, \textbf{the displayed source files might still not reflect the cod
A much more capable symbol view mode is available if the inspected source location has an associated symbol context (i.e., if it comes from a call stack capture, from call stack sampling, etc.). A symbol is a unit of machine code, basically a callable function. It may be generated using multiple source files and may consist of numerous inlined functions. A list of all captured symbols is available in the statistics window, as described in chapter~\ref{statisticssampling}.
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{Entry stacks} button.
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{\faArrowDownShortWide{}~Entry stacks} button.
Additionally, you may use the \emph{Mode} selector to decide what content should be displayed in the panels below:
@@ -4902,8 +5115,13 @@ The percentage values when \emph{\faCarBurst{}~Impact} option is not selected wi
The profiler can group call stacks leading to certain events and display the resulting information in a variety of ways. In essence, this shows the code paths that lead to these events and the distribution of these paths. At this moment, these events include:
\begin{itemize}
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active.
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information.
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active. The displayed data set can be selected with the following options:
\begin{itemize}
\item \emph{Was reached} -- counts samples in which the symbol was present anywhere on the call stack. Each sample is counted only once, at the outermost occurrence of the symbol, so recursive re-entries are ignored.
\item \emph{Was reached, recursive} -- as above, but each occurrence of the symbol on the call stack is counted separately, showing also the paths by which the symbol re-entered itself.
\item \emph{Was executing} -- counts samples taken while the symbol was executing, i.e.\ it was at the top of the call stack.
\end{itemize}
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information. Note that wait stacks have separate setting for the \emph{\faShieldHalved{}~External} option, as it is generally beneficial to see the external code when determining the cause of program interruption.
\end{itemize}
The call stack paths may be displayed in the following ways:
@@ -4924,9 +5142,11 @@ This window presents information and statistics about a lock. The lock events co
\subsection{Frame image playback window}
\label{playback}
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons to change single frame back or forward.
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons, and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively, you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons, or use the \Scroll{}~mouse wheel on either the \emph{Frame image} slider or the displayed frame image to change a single frame backward or forward.
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing.
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing. With the \emph{Loop} option enabled, playback will start from the beginning after it reaches the end.
Enabling the \emph{Limit frame range} option allows you to manually set the frame image playback region. Adjustments can be made using the start and end sliders or by copying the time range from either an annotation (section~\ref{annotationlist}), a section (chapter~\ref{sections}), or one of the time range limits (section~\ref{timerangelimits}). If the \emph{Require frame coverage} option is enabled, source regions must cover at least 75\% of the frame. Otherwise any amount of overlap is sufficient.
The following parameters also accompany each displayed frame image: \emph{timestamp}, showing at which time the image was captured, \emph{frame}, displaying the numerical value of the corresponding frame, and \emph{ratio}, telling how well the in-memory loss-less compression was able to reduce the image data size.
@@ -4944,7 +5164,7 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
\subsection{Annotation settings window}
\label{annotationsettings}
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also remove it here.
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also temporarily hide or remove it here.
\subsection{Annotation list window}
\label{annotationlist}
@@ -4952,15 +5172,17 @@ In this window, you may modify how a timeline annotation (section~\ref{annotatin
This window lists all annotations marked on the timeline. Each annotation is presented, as shown on figure~\ref{figannlist}. From left to right the elements are:
\begin{itemize}
\item \emph{\faPenToSquare{} Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
\item \emph{\faMicroscope{} Zoom} -- Zooms timeline to the annotation extent.
\item \emph{\faTrashCan{} Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
\item \emph{\faCheck{}~Visible} -- Makes the annotation visible or not on the timeline.
\item \emph{\faPenToSquare{}~Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
\item \emph{\faMicroscope{}~Zoom} -- Zooms timeline to the annotation extent.
\item \emph{\faTrashCan{}~Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
\item Colored box -- Color of the annotation.
\item Text description of the annotation.
\end{itemize}
\begin{figure}[h]
\centering\begin{tikzpicture}
\draw[rounded corners=5pt] (-0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faCheck};
\draw[rounded corners=5pt] (0.0, 0) rectangle+(0.5, -0.5) node [midway] {\faPenToSquare};
\draw[rounded corners=5pt] (0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faMicroscope};
\draw[rounded corners=5pt] (1.2, 0) rectangle+(0.5, -0.5) node [midway] {\faTrashCan};
@@ -4971,22 +5193,28 @@ This window lists all annotations marked on the timeline. Each annotation is pre
\label{figannlist}
\end{figure}
A new view-sized annotation can be added in this window by pressing the \emph{\faPlus{}~Add annotation} button. This effectively saves your current viewport for further reference.
A new view-sized annotation can be added in this window by pressing the \emph{\faNoteSticky{}~Add annotation} button. This effectively saves your current viewport for further reference.
\subsection{Time range limits}
\label{timerangelimits}
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{stackwindows}) results. Each limit can be enabled or disabled and adjusted through the following options:
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), wait stacks (section~\ref{stackwindows}), memory (section~\ref{memorywindow}) and frame statistics (section~\ref{framestatistics}) results. Each limit can be enabled or disabled and adjusted through the following options:
\begin{itemize}
\item \emph{Limit to view} -- Set the time range limit to current view.
\item \emph{\faMicroscope{}~Focus} -- Set the timeline view to the time range extent.
\item \emph{\faNoteSticky{}~From annotation} -- Allows using the annotation region for limiting purposes.
\item \emph{\faMagnifyingGlass{}~Copy find zone} -- Copies the find zone time range limit.
\item \emph{\faArrowUpWideShort{}~Copy statistics} -- Copies the statistics time range limit.
\item \emph{\faFireFlameCurved{}~Copy flame} -- Copies the flame graph time range limit.
\item \emph{\faHourglassHalf{}~Copy wait stacks} -- Copies the wait stacks time range limit.
\item \emph{\faMemory{}~Copy memory} -- Copies the memory time range limit.
\item \emph{Limit to view} -- Set the time range limit to current view.
\item \emph{\faNoteSticky{}~Add annotation} -- Create a new annotation matching the time range. See section~\ref{annotatingtrace} for more information.
\item \faCopy{}~Copy from...:
\begin{itemize}
\item \emph{\faNoteSticky{}~Annotation} -- Allows using the annotation region for limiting purposes.
\item \emph{\faArrowsLeftRightToLine{}~Sections} -- Copies time range of a section. See section~\ref{sections} for more information.
\item \emph{\faMagnifyingGlass{}~Find zone} -- Copies the find zone time range limit.
\item \emph{\faArrowUpWideShort{}~Statistics} -- Copies the statistics time range limit.
\item \emph{\faFireFlameCurved{}~Flame graph} -- Copies the flame graph time range limit.
\item \emph{\faHourglassHalf{}~Wait stacks} -- Copies the wait stacks time range limit.
\item \emph{\faMemory{}~Memory} -- Copies the memory time range limit.
\item \emph{\faImages{}~Frame statistics} -- Copies the frame statistics range limit.
\end{itemize}
\end{itemize}
Note that ranges displayed in the window have color hints that match the color of the striped regions on the timeline.
@@ -5018,6 +5246,42 @@ You do not. Tracy is not a money funnel for Silicon Valley tech bros to get rich
The only way to access the assistant is to run everything locally on your system. This ensures that everything you do stays private and that you won't be subject to forced changes in features or terms and conditions. You should own the tools you work with instead of renting them from someone else.
\end{bclogo}
If you just want to get things running and have a reasonably powerful hardware, follow the steps below.
\begin{enumerate}
\item Go to \url{https://llama.app/} and follow instructions to install llama.cpp.
\item Create the following \texttt{llama.ini} configuration file:
\begin{lstlisting}
; Launch with: llama-server --models-preset llama.ini
[*]
version = 1
cache-type-k = q8_0
cache-type-v = q8_0
[bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M]
hf = bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M
parallel = 1
spec-default = true
spec-type = draft-mtp
chat-template-kwargs = {"preserve_thinking": true}
ctx-size = 100000
[nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M]
hf = nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M
embedding = true
parallel = 4
ctx-size = 8192
cache-ram = 0
\end{lstlisting}
\item Run \texttt{llama serve -{}-models-preset llama.ini}
\item Connect to llama.cpp inside the profiler!
\end{enumerate}
The models will be automatically downloaded when trying to access them for the first time. It may take some time (the main model is 22+ GB).
If you have the resources available you may try replacing \texttt{bartowski/Qwen\_Qwen3.6-35B-A3B-GGUF:Q4\_K\_M} with \texttt{unsloth/Qwen3.6-27B-MTP-GGUF:Q4\_K\_M} in the configuration file to get a more capable model.
\subsection{Service provider}
To get started, you will need to install an LLM\footnote{Large Language Model.} provider on your system. Any service that's compatible with the standard API should work, but some may work better than others. The LLM field is advancing quickly, with new models frequently being released that often require specific support from provider services to deliver the best experience.
@@ -5074,18 +5338,18 @@ Each token present in the context window may require a fairly large amount of me
The realistic minimum required context size for Tracy to run the assistant is 100K tokens, but feel free to experiment.
\end{bclogo}
\subsubsection{Fast model}
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
\subsubsection{Embedding model}
This is a small model used for semantic search in the user manual. This should be \textbf{nomic-embed-text-1.5}, which is provided by default by LM Studio, or which you can download on your own for llama.cpp.
LM Studio properly labels the model's capabilities. This is not the case with the llama.cpp/llama-swap setup. To make it work, your embedding model's name must contain the word \texttt{embed}.
\subsubsection{Fast model}
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
\subsubsection{In practice}
So, which model should you run and what hardware you need to be able to do so? Let's take look at some example systems.
@@ -5115,16 +5379,23 @@ The chat window is divided into three sections:
The control section allows you to clear the chat contents, reconnect to the LLM provider and open the settings panel consisting of:
\begin{itemize}
\item \emph{API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
\item \emph{\faPlug{}~API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
\item \emph{\faComments{}~Chat model} -- Here you can select one of the models you have configured in the LLM provider for chat.
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
\item \emph{\faBookBookmark{}~Embeddings model} -- Select the vector embeddings model.
\item \emph{Personality} -- Choose how the assistant should behave. Note that the actual behavior will vary depending on the language model you have chosen. The following options are available:
\begin{itemize}
\item \emph{\faFaceMeh{}~Assistant} -- The default behavior of the model with Tracy Assist role guidance.
\item \emph{\faFaceGrin{}~Emotion} -- The model will behave like a human with feelings and emotions.
\item \emph{\faFaceFrown{}~Annoyed} -- Does not tolerate nonsense and will respond with sarcasm.
\end{itemize}
Personality changes do not take effect until you start a new chat.
\item \emph{\faEarthAmericas{}~Internet access} -- Determines whether the model can access network resources such as Wikipedia queries, web searches, and web page retrievals.
\item \emph{\faTag{}~Annotate call stacks} -- Enables automatic annotation of call stacks (see section~\ref{callstackwindow}). Disabled by default, as it requires proper configuration of the fast model.
\item \emph{\faHandPointRight{}~Show summary} -- Shows a short conversation topic after the initial question is asked.
\item \emph{\faCommentDots{}~Chat suggestions} -- Suggests the next question the user may want to ask.
\item \emph{Advanced} -- More advanced options are hidden here.
\begin{itemize}
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
\item \emph{\faTemperatureHalf{}~Temperature} -- Allows changing default model temperature setting.
\item \emph{\faLightbulb{}~Show all thinking regions} -- Always shows all reasoning sections and all tool calls made by model.
\item \emph{Tool reply size limit} -- Configurable maximum size for tool responses.

31
merge/CMakePresets.json Normal file
View File

@@ -0,0 +1,31 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
}
]
}

View File

@@ -1,4 +1,4 @@
project('tracy', ['cpp'], version: '0.13.1', meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
project('tracy', ['cpp'], version: run_command('sh', files('extra/tracy-version.sh'), 'public/common/TracyVersion.hpp', check: true).stdout().strip(), meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
# internal compiler flags
tracy_compile_args = []
@@ -61,6 +61,10 @@ if get_option('no_sampling')
tracy_common_args += ['-DTRACY_NO_SAMPLING']
endif
if get_option('no_wait_stacks')
tracy_common_args += ['-DTRACY_NO_WAIT_STACKS']
endif
if get_option('no_verify')
tracy_common_args += ['-DTRACY_NO_VERIFY']
endif
@@ -81,10 +85,6 @@ if get_option('patchable_nopsleds')
tracy_common_args += ['-DTRACY_PATCHABLE_NOPSLEDS']
endif
if get_option('delayed_init')
tracy_common_args += ['-DTRACY_DELAYED_INIT']
endif
if get_option('manual_lifetime')
tracy_common_args += ['-DTRACY_MANUAL_LIFETIME']
endif
@@ -158,7 +158,8 @@ includes = [
'public/tracy/TracyLua.hpp',
'public/tracy/TracyOpenCL.hpp',
'public/tracy/TracyOpenGL.hpp',
'public/tracy/TracyVulkan.hpp'
'public/tracy/TracyVulkan.hpp',
'public/tracy/TracyWebGPU.hpp'
]
client_includes = [
@@ -190,15 +191,19 @@ common_includes = [
'public/common/TracyAlign.hpp',
'public/common/TracyAlloc.hpp',
'public/common/TracyApi.h',
'public/common/TracyAssert.hpp',
'public/common/TracyColor.hpp',
'public/common/TracyForceInline.hpp',
'public/common/TracyFormat.h',
'public/common/TracyMutex.hpp',
'public/common/TracyProtocol.hpp',
'public/common/TracyQueue.hpp',
'public/common/TracySocket.hpp',
'public/common/TracyStackFrames.hpp',
'public/common/TracyString.hpp',
'public/common/TracySystem.hpp',
'public/common/TracyTaggedUserlandAddress.hpp',
'public/common/TracyVersion.hpp',
'public/common/TracyWinFamily.hpp',
'public/common/TracyYield.hpp'
]

View File

@@ -11,6 +11,7 @@ option('no_code_transfer', type : 'boolean', value : false, description : 'Disab
option('no_context_switch', type : 'boolean', value : false, description : 'Disable capture of context switches')
option('no_exit', type : 'boolean', value : false, description : 'Client executable does not exit until all profile data is sent to server')
option('no_sampling', type : 'boolean', value : false, description : 'Disable call stack sampling')
option('no_wait_stacks', type : 'boolean', value : false, description : 'Disable capture of context switch call stacks')
option('no_verify', type : 'boolean', value : false, description : 'Disable zone validation for C API')
option('no_vsync_capture', type : 'boolean', value : false, description : 'Disable capture of hardware Vsync events')
option('no_frame_image', type : 'boolean', value : false, description : 'Disable the frame image support and its thread')
@@ -21,12 +22,11 @@ option('disallow_hw_timer', type : 'boolean', value : false, description : 'Disa
option('libunwind_backtrace', type : 'boolean', value : false, description : 'Use libunwind backtracing where supported')
option('symbol_offline_resolve', type : 'boolean', value : false, description : 'Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution')
option('libbacktrace_elf_dynload_support', type : 'boolean', value : false, description : 'Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation')
option('delayed_init', type : 'boolean', value : false, description : 'Enable delayed initialization of the library (init on first call)')
option('manual_lifetime', type : 'boolean', value : false, description : 'Enable the manual lifetime management of the profile')
option('fibers', type : 'boolean', value : false, description : 'Enable fibers support')
option('no_crash_handler', type : 'boolean', value : false, description : 'Disable crash handling')
option('verbose', type : 'boolean', value : false, description : 'Enable verbose logging')
option('no_internal_message', type : 'boolean', value : false, description : 'Prevent the profiler from logging messages')
option('debuginfod', type : 'boolean', value : false, description : 'Enable debuginfod support')
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation')
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address')
option('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')

View File

@@ -28,7 +28,6 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
$<INSTALL_INTERFACE:include/tracy>)
target_compile_definitions(${PROJECT_NAME} PRIVATE
TRACY_ENABLE
TRACY_DELAYED_INIT
TRACY_MANUAL_LIFETIME
TRACY_NO_FRAME_IMAGE
)

View File

@@ -8,7 +8,12 @@ set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
set_option(SELF_PROFILE "Enable self-profiling" OFF)
set_option_value(SANITIZE "Sanitizer parameters" "")
if(APPLE)
set_option(BUNDLE "Build a macOS application bundle" OFF)
endif()
set(NO_STATISTICS OFF)
set(VENDOR_GUI ON)
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
@@ -101,6 +106,7 @@ set(SERVER_FILES
TracyView_FindZone.cpp
TracyView_FlameGraph.cpp
TracyView_FrameOverview.cpp
TracyView_FrameStatistics.cpp
TracyView_FrameTimeline.cpp
TracyView_FrameTree.cpp
TracyView_GpuTimeline.cpp
@@ -122,6 +128,7 @@ set(SERVER_FILES
TracyView_ZoneInfo.cpp
TracyView_ZoneTimeline.cpp
TracyWeb.cpp
TracyWindowConstraints.cpp
)
if(NOT EMSCRIPTEN)
@@ -152,6 +159,8 @@ set(PROFILER_FILES
src/winmainArchDiscovery.cpp
)
Embed(PROFILER_FILES PersonalityAnnoyed src/llm/personality.annoyed.md TEXT)
Embed(PROFILER_FILES PersonalityEmotion src/llm/personality.emotion.md TEXT)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md TEXT)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md TEXT)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md TEXT)
@@ -185,8 +194,12 @@ set(LIBS "")
if(USE_WAYLAND)
pkg_check_modules(WAYLAND REQUIRED egl wayland-egl wayland-cursor xkbcommon)
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
if(WAYLAND_CLIENT_VERSION VERSION_LESS 1.22)
message(WARNING "libwayland ${WAYLAND_CLIENT_VERSION} is too old and does not provide version 6 of the core protocol. Building in this configuration is not supported and will result in reduced functionality.")
endif()
set(INCLUDES "${INCLUDES};${WAYLAND_INCLUDE_DIRS}")
set(LIBS "${LIBS};${WAYLAND_LIBRARIES}")
set(LIBS "${LIBS};${WAYLAND_LINK_LIBRARIES}")
set(PROFILER_FILES ${PROFILER_FILES}
src/BackendWayland.cpp
)
@@ -259,6 +272,25 @@ if(WIN32)
)
add_executable(${PROJECT_NAME} WIN32 ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
elseif(APPLE AND BUNDLE)
set(ICON_SRC ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.png)
set(ICON_ICNS ${CMAKE_CURRENT_BINARY_DIR}/icon.icns)
add_custom_command(
OUTPUT ${ICON_ICNS}
COMMAND sips -s format icns ${ICON_SRC} --out ${ICON_ICNS}
DEPENDS ${ICON_SRC}
)
set_source_files_properties(${ICON_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES} ${ICON_ICNS})
set_target_properties(${PROJECT_NAME} PROPERTIES
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/macos/Info.plist.in
MACOSX_BUNDLE_BUNDLE_NAME "Tracy Profiler"
MACOSX_BUNDLE_GUI_IDENTIFIER "pl.nereid.tracy"
MACOSX_BUNDLE_BUNDLE_VERSION ${TRACY_VERSION_STRING}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${TRACY_VERSION_STRING}
MACOSX_BUNDLE_ICON_FILE icon.icns
MACOSX_BUNDLE_COPYRIGHT "Bartosz Taudul"
)
else()
add_executable(${PROJECT_NAME} ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
endif()
@@ -293,6 +325,8 @@ add_git_ref(${PROJECT_NAME})
if(NOT EMSCRIPTEN)
if(NOT NO_FILESELECTOR)
target_link_libraries(${PROJECT_NAME} PRIVATE nfd::nfd)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE TRACY_NO_FILESELECTOR)
endif()
if(NOT USE_WAYLAND)
target_link_libraries(${PROJECT_NAME} PRIVATE TracyGlfw3)
@@ -314,7 +348,7 @@ if(EMSCRIPTEN)
--preload-file embed.tracy
)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 f018919fcd34de2fb349be8be354048b)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/httpd.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
file(COPY_FILE ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.svg ${CMAKE_CURRENT_BINARY_DIR}/favicon.svg)

View File

@@ -0,0 +1,47 @@
{
"version": 10,
"configurePresets": [
{
"name": "common",
"hidden": true,
"binaryDir": "${sourceDir}/build-${presetName}",
"generator": "Ninja",
"cacheVariables": {
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
}
},
{
"name": "debug",
"displayName": "Debug configuration",
"inherits": "common",
"binaryDir": "${sourceDir}/build",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug"
}
},
{
"name": "release",
"displayName": "Release configuration",
"inherits": "common",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Release"
}
},
{
"name": "profile",
"displayName": "Self-profiling configuration",
"inherits": "release",
"cacheVariables": {
"SELF_PROFILE": "1"
}
},
{
"name": "web",
"displayName": "Web build",
"inherits": "release",
"cacheVariables": {
"CMAKE_TOOLCHAIN_FILE": "~/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
}
}
]
}

72
profiler/appimage/build.sh Executable file
View File

@@ -0,0 +1,72 @@
#!/bin/sh
set -eu
# Builds the profiler as an AppImage. The Wayland client libraries are built
# from a pinned release and bundled, so the core protocol version does not
# depend on what the build host provides. Everything else that is host-coupled
# (glibc, EGL, dbus, xkbcommon, OpenSSL) is left to the target system, which
# is expected to be reasonably current. The build host sets the glibc floor,
# so this should run on the oldest distro release that is still supported.
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
BUILD="$ROOT/profiler/build-appimage"
PREFIX="$BUILD/wayland"
APPDIR="$BUILD/AppDir"
WAYLAND_VERSION=1.25.0
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
mkdir -p "$BUILD"
if [ ! -e "$PREFIX/lib/pkgconfig/wayland-client.pc" ]; then
git clone --depth 1 --branch $WAYLAND_VERSION https://gitlab.freedesktop.org/wayland/wayland.git "$BUILD/wayland-src"
meson setup "$BUILD/wayland-build" "$BUILD/wayland-src" \
--prefix "$PREFIX" --libdir lib --buildtype release \
-Ddocumentation=false -Dtests=false -Ddtd_validation=false
meson install -C "$BUILD/wayland-build"
fi
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
export PATH="$PREFIX/bin:$PATH"
cmake -B "$BUILD/profiler" -S "$ROOT/profiler" -G Ninja \
-DCMAKE_BUILD_TYPE=Release \
-DNO_ISA_EXTENSIONS=ON \
-DCMAKE_BUILD_RPATH='$ORIGIN/../lib' \
-DDOWNLOAD_CAPSTONE=ON \
-DDOWNLOAD_FREETYPE=ON \
-DDOWNLOAD_LIBCURL=ON \
-DDOWNLOAD_PUGIXML=ON \
${GIT_REV:+-DGIT_REV=$GIT_REV}
cmake --build "$BUILD/profiler" --parallel "$(nproc)"
rm -rf "$APPDIR"
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \
"$APPDIR/usr/share/applications" \
"$APPDIR/usr/share/mime/packages" \
"$APPDIR/usr/share/icons/hicolor/scalable/apps" \
"$APPDIR/usr/share/icons/hicolor/scalable/mimetypes"
cp "$BUILD/profiler/tracy-profiler" "$APPDIR/usr/bin/"
strip "$APPDIR/usr/bin/tracy-profiler"
patchelf --set-rpath '$ORIGIN/../lib' "$APPDIR/usr/bin/tracy-profiler"
cp "$PREFIX/lib/libwayland-client.so.0" \
"$PREFIX/lib/libwayland-cursor.so.0" \
"$PREFIX/lib/libwayland-egl.so.1" \
"$APPDIR/usr/lib/"
cp "$ROOT/extra/desktop/tracy.desktop" "$APPDIR/usr/share/applications/"
cp "$ROOT/extra/desktop/application-tracy.xml" "$APPDIR/usr/share/mime/packages/"
cp "$ROOT/icon/icon.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/tracy.svg"
cp "$ROOT/icon/application-tracy.svg" "$APPDIR/usr/share/icons/hicolor/scalable/mimetypes/"
ln -s usr/share/applications/tracy.desktop "$APPDIR/tracy.desktop"
ln -s usr/share/icons/hicolor/scalable/apps/tracy.svg "$APPDIR/tracy.svg"
ln -s tracy.svg "$APPDIR/.DirIcon"
ln -s usr/bin/tracy-profiler "$APPDIR/AppRun"
ldd "$APPDIR/usr/bin/tracy-profiler"
if [ ! -x "$BUILD/appimagetool" ]; then
curl -Lf -o "$BUILD/appimagetool" "$APPIMAGETOOL_URL"
chmod +x "$BUILD/appimagetool"
fi
ARCH=x86_64 "$BUILD/appimagetool" --appimage-extract-and-run \
"$APPDIR" "$BUILD/tracy-profiler-x86_64.AppImage"

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleIconFile</key>
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
<key>NSHighResolutionCapable</key>
<true/>
</dict>
</plist>

View File

@@ -26,6 +26,9 @@ public:
float GetDpiScale();
size_t HandleType();
void* Handle();
private:
WindowPosition m_winPos;
int m_w, m_h;

View File

@@ -388,3 +388,13 @@ float Backend::GetDpiScale()
{
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
}
size_t Backend::HandleType()
{
return 0;
}
void* Backend::Handle()
{
return nullptr;
}

Some files were not shown because too many files have changed in this diff Show More