Compare commits

...

210 Commits

Author SHA1 Message Date
Marcos Slomp
57ac18bc83 API exploration for kernel metrics 2026-06-24 16:13:03 -07: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
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
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
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
Bartosz Taudul
ec1d5bd3d7 Merge pull request #1402 from wolfpld/slomp/webgpu-example-platform
Switch webgpu example to SDL3, plus patch edge-case for wgpu-native
2026-06-15 23:48:14 +02:00
Marcos Slomp
69af195c98 edge-case bug-fix (could cause wgpu-native to panic) 2026-06-15 13:14:28 -07:00
Marcos Slomp
60699c4a92 fixing win32 builds with SDL3 + WebGPU 2026-06-15 13:14:28 -07:00
Marcos Slomp
cc45cf6046 switch to SDL3 (no cmake fetch, just find_package) 2026-06-15 13:14:28 -07:00
Bartosz Taudul
62560a6429 Add 8-bit length string transfers to the protocol. 2026-06-15 20:43:55 +02:00
Bartosz Taudul
f7b4e177ff Change misleading etc1buf variable to texbuf. 2026-06-15 19:31:06 +02:00
Bartosz Taudul
084daf0516 Force inline send string strlen helpers. 2026-06-15 19:19:13 +02:00
Bartosz Taudul
a98956f2d9 Another typo. 2026-06-15 17:17:32 +02:00
Bartosz Taudul
ac6f0f88fa Actually describe the message severity levels. 2026-06-15 17:09:42 +02:00
Bartosz Taudul
33fccb3530 Typos. 2026-06-15 17:08:39 +02:00
Bartosz Taudul
45576f6972 Merge pull request #1400 from wolfpld/slomp/gl-example
adding OpenGL example (spinning triangle)
2026-06-14 21:39:54 +02:00
Marcos Slomp
17e13bc2e0 SDL2 -> SDL3 2026-06-14 12:18:06 -07:00
Marcos Slomp
ee0c73bf25 switch to SDL2 (no cmake fetch, just find_package) 2026-06-14 11:24:14 -07:00
Bartosz Taudul
343567a3f2 Regenerate markdown manual. 2026-06-14 17:31:49 +02:00
Bartosz Taudul
20b3535623 Use fancy quotes in the manual. 2026-06-14 17:31:32 +02:00
Bartosz Taudul
5298316480 Revert emscripten back to 5.0.7. There are threading problems with 6.0.0.
Specifically, click on the red power off button to go back to the welcome
screen, and the cleanup popup never goes away.
2026-06-14 16:24:13 +02:00
Bartosz Taudul
83719fb29b WASM_BIGINT is enabled by default since emscripten 4.0.0. 2026-06-14 15:17:32 +02:00
Bartosz Taudul
f7d789eddb Split emscripten link options to multiple lines. 2026-06-14 15:09:40 +02:00
Bartosz Taudul
3816b2485e Bump used emscripten version to 6.0.0. 2026-06-14 15:06:53 +02:00
Bartosz Taudul
f8aa88d522 Explicitly disable shared libs for md4c.
Fixes emscripten build.
2026-06-14 15:06:36 +02:00
Bartosz Taudul
b5ae187f76 Disable separate fast model by default. 2026-06-12 22:20:47 +02:00
Marcos Slomp
3f203806e2 X11 workaround check 2026-06-12 13:00:33 -07:00
Bartosz Taudul
15c6b49de2 Mark text embeds as TEXT. 2026-06-12 21:43:51 +02:00
Bartosz Taudul
a153f3a562 Extend Embed macro to support TEXT parameter enabling CRLF to LF conversion. 2026-06-12 21:43:12 +02:00
Bartosz Taudul
c2998310cf Add CRLF to LF conversion support to embed. 2026-06-12 21:42:45 +02:00
Bartosz Taudul
a43b74ed8f Update NEWS. 2026-06-12 21:08:33 +02:00
Bartosz Taudul
d3047f8069 Fix memory discard + callstack.
Bug (High Severity): Wrong queue type in MemDiscardCallstack

In the callstack path of MemDiscardCallstack, the wrong queue type is
sent:

  SendMemDiscard( QueueType::MemDiscard, thread, name );

Every other callstack variant correctly uses its callstack queue type
(MemAllocCallstack, MemFreeCallstack, etc.), but this one uses the
non-callstack type. The SendMemDiscard assertion at line 1026 confirms
MemDiscardCallstack is a valid value.

Impact: The callstack captured by SendCallstackSerial() will be orphaned.
The server processes the event via the non-callstack handler, leaving the
callstack serial data unconsumed, which desynchronizes the serial queue
and corrupts all subsequent events.
2026-06-12 20:30:59 +02:00
Bartosz Taudul
3804b2580a Regenerate markdown manual. 2026-06-12 19:58:06 +02:00
Bartosz Taudul
329ac6c9f1 Document memory discard macro. 2026-06-12 19:57:45 +02:00
Bartosz Taudul
a091bb4ad2 Remove "secure" variant of alloc/free.
Random crashes are not fun. Always use the "secure" code path.
2026-06-12 19:41:17 +02:00
Bartosz Taudul
86b5f43959 Provide proper test directory. 2026-06-12 19:23:28 +02:00
Marcos Slomp
39dc688340 adding Xrandr dependency 2026-06-12 08:44:46 -07:00
Marcos Slomp
832234838b better comments and messages 2026-06-12 07:33:06 -07:00
Marcos Slomp
daba5acfbc more explicit compiler warning message 2026-06-12 07:31:03 -07:00
Bartosz Taudul
07bfe3465e Merge pull request #1356 from wolfpld/slomp/tracy-webgpu
GPU: WebGPU back-end
2026-06-12 12:11:32 +02:00
Bartosz Taudul
0544440a34 Remove unused, extremely broken code. 2026-06-11 22:35:25 +02:00
Marcos Slomp
f287508772 addressing type conversion warning 2026-06-11 13:28:32 -07:00
Bartosz Taudul
f622b97436 Backdate init time when a producer token predates it.
A zone emitted from a shared object initializer runs before the
executable's constructors, so its timestamp precedes s_initTime, which
the server uses as the trace epoch (baseTime). Such a zone converts to
negative trace time and its end no longer satisfies IsEndValid(), which
excludes it from statistics reconstruction and makes it render as
never-ending.

Record the current time when a producer token is created before
s_initTime is constructed and use it as the init time, ensuring no event
timestamp precedes the trace epoch.
2026-06-11 20:05:59 +02:00
Bartosz Taudul
dfded9d55d Recover main thread producer orphaned by cross-module init order.
ELF init_priority only orders constructors within a single module. All of
a shared object's initializers run before any of the executable's, so an
instrumented dependency .so emitting a zone from its static initializer
creates the main thread producer token against the zero-initialized
s_queue. The queue constructor then resets the producer list, orphaning
that producer: every zone emitted on the main thread from that point on
is enqueued into blocks no consumer ever iterates and silently lost,
while sampling (worker thread producer) keeps working.

Re-link such a producer right after the queue is constructed. In the
common case, where nothing was emitted during shared object init, this
merely constructs the main thread token eagerly.
2026-06-11 20:05:57 +02:00
Marcos Slomp
a2555fbb33 fixing Windows/Linux build 2026-06-11 07:37:58 -07:00
Bartosz Taudul
7180ea381f Merge pull request #1401 from Lectem/fix/win32-non-desktop
`TRACY_WIN32_NO_DESKTOP` should use `GetVersionExW` explicitly.
2026-06-11 13:01:24 +02:00
Clément Grégoire
0c74658dd3 TRACY_WIN32_NO_DESKTOP should use GetVersionExW explicitly.
Since we use `RTL_OSVERSIONINFOW` we need to use W version explicitly
2026-06-11 12:06:34 +02:00
Marcos Slomp
debda1df55 scoping the GpuCtx constructor 2026-06-10 18:57:22 -07:00
Marcos Slomp
d98608b022 issue a Tracy warning message when timestamp queries are supported but not properly implemented 2026-06-10 18:52:57 -07:00
Marcos Slomp
eb88c6eba0 adding warning about TracyOpenGL usage on Apple devices 2026-06-10 18:52:09 -07:00
Marcos Slomp
e83429c926 replacing the various platform layers by RGFW 2026-06-10 18:38:48 -07:00
Bartosz Taudul
cc091a99a2 Support key modifiers on emscripten. 2026-06-10 23:39:08 +02:00
Marcos Slomp
1b207d3e2a adding OpenGL example (spinning triangle) 2026-06-10 14:14:54 -07:00
Bartosz Taudul
f89709e99e Prevent click-through when activating annotation. 2026-06-10 22:50:45 +02:00
Bartosz Taudul
a4c5f15312 Rewrite annotations drawing. 2026-06-10 22:42:05 +02:00
Bartosz Taudul
3455fd9f82 Fix regression making existing annotations non-editable after trace load. 2026-06-10 21:48:12 +02:00
Marcos Slomp
cfc046abcd refactoring requirements 2026-06-10 11:09:51 -07:00
Bartosz Taudul
9ab39d8af3 Merge pull request #1398 from Lectem/fix/concurrentqueue-and-malloc-define
WORKAROUND_malloc/free should properly route to tracy_malloc/free
2026-06-10 17:57:32 +02:00
Clément Grégoire
bfab6d03f4 WORKAROUND_malloc/free should properly route to tracy_malloc/free 2026-06-10 17:15:33 +02:00
Marcos Slomp
0d848c3042 proper device descriptor chaining 2026-06-10 08:03:18 -07:00
Marcos Slomp
54270d3fd5 move window to top when launching from console 2026-06-10 06:24:53 -07:00
Marcos Slomp
1341f98c61 cleanup 2026-06-10 06:24:32 -07:00
Marcos Slomp
6fc279eef4 more descriptive API name 2026-06-10 06:23:57 -07:00
Bartosz Taudul
66e4f5cef7 Merge pull request #1397 from MaximeCoutantSonos/master
Fixed typo inconsistency in Shared lock C api
2026-06-10 14:47:56 +02:00
MaximeCoutantSonos
7637971e9e Fixed typo inconsistency in Shared lock C api 2026-06-10 14:26:36 +02:00
Bartosz Taudul
4e3cffc4ba Add name bank related to games. 2026-06-10 01:34:40 +02:00
Marcos Slomp
28d3a91980 more changes to allow for null context 2026-06-09 16:26:51 -07:00
Bartosz Taudul
3956616fc2 Fix repeated entries. 2026-06-10 01:14:22 +02:00
Marcos Slomp
0fbb2eaaa4 typo 2026-06-09 16:00:43 -07:00
Marcos Slomp
b27dab4584 remove "spontaneous" callback (better determinism) 2026-06-09 15:59:38 -07:00
Marcos Slomp
75bee5370f cosmetics 2026-06-09 15:58:24 -07:00
Marcos Slomp
e7499458e9 allow scoped instrumentation to no-op with null context 2026-06-09 15:58:06 -07:00
Bartosz Taudul
d34c45fa5a Use variable structure for random name generator. 2026-06-10 00:27:18 +02:00
Bartosz Taudul
8fe5a511c9 Expand name generator banks. 2026-06-10 00:15:55 +02:00
Bartosz Taudul
afdd2e2f81 Regenerate markdown manual. 2026-06-09 23:49:57 +02:00
Bartosz Taudul
3c1b1b2f80 Update manual. 2026-06-09 23:48:14 +02:00
Bartosz Taudul
992134f85e Fix regression with no tooltips on disabled items. 2026-06-09 23:31:59 +02:00
Bartosz Taudul
37bc986584 Add button for re-rolling annotation name. 2026-06-09 23:27:07 +02:00
Bartosz Taudul
feb4e7c989 Generate random names for new annotations. 2026-06-09 23:24:58 +02:00
Bartosz Taudul
4a8fe6f56e Add button to generate random trace description. 2026-06-09 23:22:21 +02:00
Bartosz Taudul
a960a25285 Add random name generator. 2026-06-09 23:22:21 +02:00
Marcos Slomp
958cb8d7f8 WGPU_PATH fix 2026-06-09 12:56:34 -07:00
Marcos Slomp
59f17794a5 fixing MemWrite casts 2026-06-09 09:06:48 -07:00
Marcos Slomp
3b2c7dbacb fixing webgpu lib linkage based on WGPU_PATH 2026-06-09 09:06:48 -07:00
Marcos Slomp
56ed480ed2 relocating webgpu example 2026-06-09 09:06:48 -07:00
Marcos Slomp
0572c86551 Wayland woes... 2026-06-09 09:06:48 -07:00
Marcos Slomp
6499e3383b fix Linux build 2026-06-09 09:06:48 -07:00
Marcos Slomp
8278ace0c1 build fix 2026-06-09 09:06:48 -07:00
Marcos Slomp
5981eca141 adding webgpu example/demo 2026-06-09 09:06:48 -07:00
Marcos Slomp
1b2856b885 GPU context name 2026-06-09 09:06:48 -07:00
Marcos Slomp
118f18cf4b updating docs 2026-06-09 09:06:48 -07:00
Marcos Slomp
bfbc1d3bee missing interface, and more debugging 2026-06-09 09:06:48 -07:00
Marcos Slomp
831779508f minor fixes/comments 2026-06-09 09:06:48 -07:00
Marcos Slomp
286309af3f refactoring calibration estimations 2026-06-09 09:06:47 -07:00
Marcos Slomp
3db70a2237 refactoring 2026-06-09 09:06:47 -07:00
Marcos Slomp
da952f3f38 more refactoring 2026-06-09 09:06:47 -07:00
Marcos Slomp
efba4685ef more cleanup and refactoring 2026-06-09 09:06:47 -07:00
Marcos Slomp
598984c45d refactoring initial calibration 2026-06-09 09:06:47 -07:00
Marcos Slomp
860011c604 calibration stability 2026-06-09 09:06:47 -07:00
Marcos Slomp
0cdcbfc75d refactoring query resolve 2026-06-09 09:06:47 -07:00
Marcos Slomp
e5d4be95df getting rid of spontaneous callbacks 2026-06-09 09:06:47 -07:00
Marcos Slomp
7b3863d93d redesign... 2026-06-09 09:06:47 -07:00
Marcos Slomp
de2a18d964 initial prototype for WebGPU back-end 2026-06-09 09:06:47 -07:00
Marcos Slomp
9588912aa9 Speeding-up GitHub build-bots (#1392)
* disabling LTO when building the profiler on macos via the github workflow

* diagnosing where in the linking stage it's getting stuck

* fowrward declarations

* compilation time report

* trying clang build analyzer...

* reducing number of parallel workers

* limiting parallel workers on windows/linux as well

* re-enabling LTO on macos

* reverting forward declaration header include (emscripten is failing with them).

* reverting act changes

* removing comments
2026-06-09 11:21:09 +02:00
Bartosz Taudul
7ee4380f64 Merge pull request #1395 from MaximeCoutantSonos/master
Add support for Shared Locks in the C API
2026-06-08 17:34:17 +02:00
Bartosz Taudul
01e639db97 Merge pull request #1336 from bmilanich/rocm-on-demand-fix
TracyRocprof: fix on-demand profiling crash and missing context name
2026-06-08 17:06:45 +02:00
Basil Milanich
030e699eb5 Move rocprof on-demand repro to tests/ and convert to CMake
Conform to the new repo layout (master moved repros under tests/, e.g.
tests/cuda/repro/graph). Relocate examples/RocprofOnDemandRepro to
tests/rocprof/repro/on_demand and replace the hand-written Makefile with
a CMake build mirroring the CUDA repro: builds the HIP reproducer, wires
it as a ctest target, and optionally builds the check_gpu_ctx_name
verification helper against the Tracy server library.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 09:47:22 -05:00
Basil Milanich
16cdf3d645 Merge remote-tracking branch 'origin/master' into rocm-on-demand-fix
# Conflicts:
#	public/client/TracyRocprof.cpp
2026-06-08 09:43:07 -05:00
MaximeCoutantSonos
2f143491eb Fixed typo in grammar 2026-06-08 16:07:06 +02:00
MaximeCoutantSonos
796050ac1e Added documentation for the shared lock C API 2026-06-08 15:52:54 +02:00
MaximeCoutantSonos
31dbfef97d Added C shared lock to C-API 2026-06-08 15:19:00 +02:00
Bartosz Taudul
19519bbeb0 Merge pull request #1394 from bruno-dasilva/bruno/opengl-drift-correction
fix: add opengl drift correction for gpu zones
2026-06-07 22:50:40 +02:00
Bruno Da Silva
fc4f52e61d add opengl drift correction option to meson.options/meson.build 2026-06-07 20:14:36 +00:00
Bruno Da Silva
e2ac8f7973 fix: add opengl drift correction for gpu zones 2026-06-07 00:23:14 +00:00
Bartosz Taudul
e5aa8eba51 Merge pull request #1387 from Lectem/wip/offline-res-for-any-toolchain
Offline resolution for any toolchain
2026-06-06 14:48:54 +02:00
Clément Grégoire
7437c41514 Escape provided addr2line tool path 2026-06-06 14:47:24 +02:00
Bartosz Taudul
f441a5070b Wrap achievements column contents into child windows. 2026-06-06 13:19:56 +02:00
Bartosz Taudul
00b6abd67b Move achievements text to markdown files. 2026-06-06 13:19:55 +02:00
Bartosz Taudul
e4e3d75eb8 Add PDF link to built-in manual viewer. 2026-06-06 10:56:25 +02:00
Bartosz Taudul
fc5318dcad Some more markdown user manual compatibility fixes. 2026-06-06 01:53:05 +02:00
Bartosz Taudul
661c664b75 Converte LaTeX math in markdown to plain text. 2026-06-06 01:44:27 +02:00
Bartosz Taudul
6dbebca666 Reset user manual view scroll position after changing section from toc. 2026-06-06 01:28:24 +02:00
Bartosz Taudul
73d78ad517 Fix tables in markdown manual. 2026-06-06 01:25:49 +02:00
Bartosz Taudul
e5371d7987 Icons description separator must start at newline.
Otherwise it will clash with wrong things, like middle-of-line table header
separators.
2026-06-06 01:25:08 +02:00
Bartosz Taudul
9806f35714 Increase spacing between admonition icon and header text. 2026-06-06 00:43:09 +02:00
Bartosz Taudul
d40289d594 Add support for markdown admonitions. 2026-06-05 23:28:09 +02:00
Bartosz Taudul
86fbe529ed Bump font awesome to 7.2. 2026-06-05 23:28:09 +02:00
Clément Grégoire
7cb98245ce Batch addr2line invocations by command-line length
The fixed batches of 1024 addresses could overflow the platform's command-line limit (`La ligne de commande est trop longue.` from cmd.exe on Windows, whose limit is ~8191 characters). Build each batch by appending addresses until a length budget is reached instead. A single conservative budget of 8000 stays under the smallest limit on every platform, and keeps batches in the same ballpark as before (several hundred addresses per invocation).
2026-06-05 19:10:19 +02:00
Clément Grégoire
55d5436fb9 Add option to reset callstack frame symbols to the unresolved state
The new `-R` option of tracy-update sets every callstack frame back to `[unresolved]` / `[unknown]`. Since failed lookups leave frames untouched and the image-relative offset in `symAddr` survives patching, this makes it possible to chain several resolution passes over the same capture, each with different `-p` path substitutions (e.g. one pass per symbol directory).
2026-06-05 19:03:35 +02:00
Clément Grégoire
2b11785b05 Allow offline symbol resolution with any addr2line-compatible tool
The addr2line backend of tracy-update now builds on every platform, including Windows, and can be pointed at any addr2line-compatible executable:

- `-a`: path to a custom symbol resolution tool (e.g. `llvm-addr2line` or a cross-compilation toolchain's `addr2line`). Works on all platforms and takes precedence over the platform default (DbgHelp on Windows, the `addr2line` found in `PATH` elsewhere). Path-like values are validated up front so a wrong path fails with an actionable message instead of a cryptic, localized shell error.
- `-A`: extra arguments passed verbatim to the tool, e.g. `--relative-address` so `llvm-addr2line`/`llvm-symbolizer` accept the image-relative offsets Tracy records for images with a non-zero preferred base (PE, Mach-O).
- `-v`: verbose output while patching symbols.
2026-06-05 19:03:35 +02:00
Basil Milanich
ebd3d9c3e6 Also defer GpuContextName for on-demand profiling
Without this, a late-connecting client receives the deferred
GpuNewContext but not the GpuContextName, so the GPU context appears
unnamed in the profiler.

Add check_gpu_ctx_name tool to verify context names in captured traces.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-16 09:27:47 -05:00
Basil Milanich
bc8d8f5302 Add repro case for rocprofiler on-demand crash
Minimal HIP program that demonstrates the assertion failure in
tracy-capture when connecting to a TRACY_ON_DEMAND + TRACY_ROCPROF
application. See examples/RocprofOnDemandRepro/README.md for details.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 16:02:36 -05:00
Basil Milanich
b049746853 Fix on-demand profiling support for rocprofiler backend
Two issues prevented the rocprofiler GPU backend from working with
TRACY_ON_DEMAND:

1. GpuNewContext not deferred: When a Tracy client connects late (on-demand
   mode), it never receives the GPU context creation message because the
   GpuNewContext queue item was not buffered via DeferItem. This caused an
   assertion failure (ctx == nullptr) in the capture/profiler when
   processing GPU zone events. Add the same DeferItem pattern used by the
   CUDA backend.

2. Kernel symbols dropped before init: The data->init guard at the top of
   tool_callback_tracing_callback() blocked kernel symbol registrations
   (CODE_OBJECT_DEVICE_KERNEL_SYMBOL_REGISTER) which happen at HIP init
   time, before any Tracy client connects. Move the init guard after the
   code_object block so symbols are always recorded, while dispatch and
   memory-copy events are still gated on initialization.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:56:31 -05:00
159 changed files with 8466 additions and 1028 deletions

View File

@@ -20,7 +20,7 @@ jobs:
- name: Setup emscripten
uses: emscripten-core/setup-emsdk@v16
with:
version: 4.0.10
version: 5.0.7
- name: Trust git repo
run: git config --global --add safe.directory '*'
- uses: actions/checkout@v4

View File

@@ -32,7 +32,7 @@ jobs:
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
cmake --build profiler/build
else
cmake --build profiler/build --parallel
cmake --build profiler/build --parallel 2
fi
- name: Update utility
run: |

View File

@@ -28,7 +28,7 @@ jobs:
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel --config Release
cmake --build profiler/build --parallel 2 --config Release
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}

View File

@@ -32,7 +32,7 @@ jobs:
- name: Build profiler
run: |
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
cmake --build profiler/build --parallel --config Release
cmake --build profiler/build --parallel 2 --config Release
- name: Build update
run: |
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}

2
.gitignore vendored
View File

@@ -30,6 +30,8 @@ profiler/build/win32/Tracy.aps
extra/vswhere.exe
extra/tracy-build
.cache
.uv-cache/
.venv/
compile_commands.json
profiler/build/wasm/Tracy-release.*
profiler/build/wasm/Tracy-debug.*

View File

@@ -6,7 +6,7 @@
"${workspaceFolder}/import",
"${workspaceFolder}/merge",
"${workspaceFolder}/update",
"${workspaceFolder}/test",
"${workspaceFolder}/tests/tracy",
"${workspaceFolder}",
],
"cmake.buildDirectory": "${sourceDirectory}/build",

View File

@@ -137,6 +137,7 @@ set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resoluti
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_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
# advanced
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF TracyClient)

29
NEWS
View File

@@ -5,9 +5,16 @@ here.
vx.xx.x (2026-xx-xx)
--------------------
- 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 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.
- 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.
- External frames are now omitted in the single-line call stack list visible
in messages list, or in memory allocation info window.
- External frames are now hidden by default in various contexts where they
@@ -147,8 +154,13 @@ vx.xx.x (2026-xx-xx)
- There is now chapter tree and the manual contents are displayed section
by section.
- Links to chapters are now properly working.
- The "bclogo" blocks are now correctly processed.
- The "bclogo" blocks are now correctly processed and displayed as proper
admonitions.
- The font awesome icons now show as in the rest of the UI.
- Footnotes are now rendered as proper footnotes.
- Tables are now rendered as intended.
- LaTeX math is now converted to readable form.
- Added a button to download the full PDF manual to the user manual window.
- Call stack window will now show the thread viewed call stack originates
from (if possible).
- "Visible threads" checkboxes in messages, flame graph and wait stacks
@@ -172,6 +184,21 @@ vx.xx.x (2026-xx-xx)
- Fixed NVCC builds.
- Fixed possible lockups in Vulkan timer calibration loop.
- The flame graph view now supports zooming in and panning with the mouse.
- 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
addr2line-compatible tools via -a and -A command line parameters.
Additionally, it is now possible to reset all call stack frame symbols to
unresolved with the -R parameter.
- Periodic recalibration of the clock drift in OpenGL contexts can be enabled
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.
- 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.
v0.13.1 (2025-12-11)

View File

@@ -4,7 +4,7 @@
### A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling profiler for games and other applications.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphic APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphics/compute APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA, WebGPU.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
- [Documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) for usage and build process instructions
- [Releases](https://github.com/wolfpld/tracy/releases) containing the documentation (`tracy.pdf`) and compiled Windows x64 binaries (`Tracy-<version>.7z`) as assets

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

@@ -142,6 +142,7 @@ CPMAddPackage(
PATCHES
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
)
set(IMGUI_SOURCES
@@ -218,6 +219,8 @@ CPMAddPackage(
NAME md4c
GITHUB_REPOSITORY mity/md4c
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
OPTIONS
"BUILD_SHARED_LIBS OFF"
EXCLUDE_FROM_ALL TRUE
)
@@ -269,7 +272,7 @@ if(NOT EMSCRIPTEN)
CPMAddPackage(
NAME pugixml
GITHUB_REPOSITORY zeux/pugixml
GIT_TAG v1.15
GIT_TAG v1.16
EXCLUDE_FROM_ALL TRUE
)
add_library(TracyPugixml INTERFACE)

View File

@@ -0,0 +1,63 @@
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)
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;
};
}

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

@@ -0,0 +1,210 @@
#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 ); }
~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() );
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,38 @@
#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;
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

@@ -0,0 +1,83 @@
# CMakeLists.txt — OpenGL spinning triangle demo
#
# macOS:
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
#
# Linux (requires libsdl3-dev libgl1-mesa-dev):
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
#
# Windows:
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
# cmake --build build/ninja
cmake_minimum_required(VERSION 3.16)
project(gl_spinning_triangle LANGUAGES C CXX)
# ---------------------------------------------------------------------------
# Tracy root — defaults to three directories above this CMakeLists.txt.
# ---------------------------------------------------------------------------
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
option(TRACY_ENABLE "Enable Tracy profiling" ON)
# ---------------------------------------------------------------------------
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
# ---------------------------------------------------------------------------
find_package(SDL3 REQUIRED)
# ---------------------------------------------------------------------------
# GL extension loader — GLEW (Windows + Linux, fetched automatically)
# ---------------------------------------------------------------------------
if(NOT APPLE)
include(FetchContent)
set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "" FORCE)
set(ONLY_LIBS ON CACHE BOOL "" FORCE)
FetchContent_Declare(glew
GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git
GIT_TAG master # pin to a specific commit for reproducible builds
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(glew)
endif()
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
if(APPLE)
set(PLATFORM_LIBS SDL3::SDL3 "-framework OpenGL")
elseif(WIN32)
set(PLATFORM_LIBS SDL3::SDL3 opengl32 libglew_static)
else()
set(PLATFORM_LIBS SDL3::SDL3 GL libglew_static)
endif()
# ---------------------------------------------------------------------------
# Target
# ---------------------------------------------------------------------------
add_executable(gl_spinning_triangle
spinning_triangle.cpp
"${TRACY_DIR}/public/TracyClient.cpp"
${PLATFORM_SOURCES}
)
# Suppress upstream warnings from TracyClient.cpp
if(MSVC)
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "/w"
)
else()
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "-w"
)
endif()
target_compile_features(gl_spinning_triangle PRIVATE cxx_std_17)
if(TRACY_ENABLE)
target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_ENABLE)
endif()
target_include_directories(gl_spinning_triangle PRIVATE
"${TRACY_DIR}/public"
)
target_link_libraries(gl_spinning_triangle PRIVATE ${PLATFORM_LIBS})

View File

@@ -0,0 +1,37 @@
// platform.h — interface between platform-agnostic code and platform backends
//
// Each platform_*.mm / platform_*.cpp file implements these four functions.
// Exactly one backend must be linked into the final binary.
#pragma once
#ifdef __APPLE__
# include <OpenGL/gl3.h>
#else
# include <GL/glew.h>
#endif
// Initialize the windowing system, create a window, and make an OpenGL 3.3
// Core Profile context current on the calling thread.
// Returns true on success.
bool platformInit(int width, int height, const char* title);
// Load OpenGL function pointers (no-op on macOS where the framework exports them directly).
// Must be called after platformInit() while the GL context is current.
// Returns true on success.
bool platformInitGL();
// Elapsed wall-clock time in seconds since platformInit().
double platformGetTime();
// Swap front and back buffers (present the rendered frame).
void platformSwapBuffers();
// Pixel scaling factor relative to the logical window size (1.0 on non-HiDPI displays).
// Must be called after platformInit().
void platformGetPixelDensityScale(float* x, float* y);
// Enter the platform event/render loop.
// Calls render() each frame at ~60 fps.
// Calls shutdown() exactly once before returning.
void platformRunLoop(void (*render)(), void (*shutdown)());

View File

@@ -0,0 +1,85 @@
// platform_sdl3.cpp — SDL3 windowing backend (cross-platform)
#include "platform.h" // GL headers first (gl3.h / glew.h) so SDL sees guards set
#define SDL_MAIN_HANDLED // we don't want SDL_main
#include <SDL3/SDL.h>
#include <chrono>
#include <cstdio>
static SDL_Window* sWin = nullptr;
static SDL_GLContext sCtx = nullptr;
static std::chrono::steady_clock::time_point sStartTime;
bool platformInit(int width, int height, const char* title) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
return false;
}
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);
sWin = SDL_CreateWindow(title, width, height, SDL_WINDOW_OPENGL);
if (!sWin) {
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
sCtx = SDL_GL_CreateContext(sWin);
if (!sCtx) {
fprintf(stderr, "ERROR: SDL_GL_CreateContext failed: %s\n", SDL_GetError());
SDL_DestroyWindow(sWin);
SDL_Quit();
return false;
}
SDL_GL_SetSwapInterval(1);
sStartTime = std::chrono::steady_clock::now();
return true;
}
bool platformInitGL() {
#ifndef __APPLE__
glewExperimental = GL_TRUE;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
return false;
}
#endif
return true;
}
double platformGetTime() {
return std::chrono::duration<double>(
std::chrono::steady_clock::now() - sStartTime).count();
}
void platformSwapBuffers() { SDL_GL_SwapWindow(sWin); }
void platformGetPixelDensityScale(float* x, float* y) {
int pw, ph, ww, wh;
SDL_GetWindowSizeInPixels(sWin, &pw, &ph);
SDL_GetWindowSize(sWin, &ww, &wh);
*x = (ww > 0) ? (float)pw / (float)ww : 1.0f;
*y = (wh > 0) ? (float)ph / (float)wh : 1.0f;
}
void platformRunLoop(void (*render)(), void (*shutdown)()) {
bool running = true;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false;
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
}
if (running) render();
}
shutdown();
SDL_GL_DestroyContext(sCtx);
SDL_DestroyWindow(sWin);
SDL_Quit();
}

View File

@@ -0,0 +1,145 @@
// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling.
#ifdef __APPLE__
// NOTE: OpenGL is only available on MacOS (no iOS support)
// Including and using anything related to OpenGL on Apple (like <OpenGL/gl3.h>)
// will emit deprecation warnings, unless GL_SILENCE_DEPRECATION is defined
#define GL_SILENCE_DEPRECATION
// NOTE: TracyOpenGL.hpp will not work as expected even on Apple devices that
// support OpenGL, because the OpenGL drivers do not implement ARB_timer_query
// properly (querying GL_TIMESTAMP always resolves to 0). TracyOpenGL.hpp will
// emit a compiler warning, and a Tracy message to the trace/profiler, but the
// program will still run.
#endif
#include "platform/platform.h" // also includes OpenGL headers
#include <tracy/Tracy.hpp>
// NOTE: opt-in toggle for periodic recalibrations during Collect()
#define TRACY_OPENGL_AUTO_CALIBRATION
#include <tracy/TracyOpenGL.hpp>
static const int kWidth = 800;
static const int kHeight = 600;
static GLuint gProgram = 0;
static GLuint gVao = 0;
static GLint gAngleLoc = -1;
// Vertex colors and positions are baked in; rotation is driven by a uniform.
static const char* kVertSrc = R"(
#version 150 core
uniform float uAngle;
const vec2 kPos[3] = vec2[3](
vec2( 0.0, 0.5 ),
vec2(-0.433, -0.25 ),
vec2( 0.433, -0.25 )
);
const vec3 kCol[3] = vec3[3](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
out vec3 vColor;
void main() {
float c = cos(uAngle);
float s = sin(uAngle);
vec2 p = kPos[gl_VertexID];
gl_Position = vec4(p.x*c - p.y*s, p.x*s + p.y*c, 0.0, 1.0);
vColor = kCol[gl_VertexID];
}
)";
static const char* kFragSrc = R"(
#version 150 core
in vec3 vColor;
out vec4 fragColor;
void main() { fragColor = vec4(vColor, 1.0); }
)";
static GLuint compileShader(GLenum type, const char* src) {
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, sizeof(log), nullptr, log);
fprintf(stderr, "Shader compile error: %s\n", log);
glDeleteShader(s);
return 0;
}
return s;
}
static int initGL() {
if (!platformInitGL()) return 1;
TracyGpuContext;
TracyGpuContextName("OpenGL", 6);
GLuint vert = compileShader(GL_VERTEX_SHADER, kVertSrc);
GLuint frag = compileShader(GL_FRAGMENT_SHADER, kFragSrc);
if (!vert || !frag) return 1;
gProgram = glCreateProgram();
glAttachShader(gProgram, vert);
glAttachShader(gProgram, frag);
glLinkProgram(gProgram);
glDeleteShader(vert);
glDeleteShader(frag);
GLint ok = 0;
glGetProgramiv(gProgram, GL_LINK_STATUS, &ok);
if (!ok) {
char log[512];
glGetProgramInfoLog(gProgram, sizeof(log), nullptr, log);
fprintf(stderr, "Program link error: %s\n", log);
return 1;
}
gAngleLoc = glGetUniformLocation(gProgram, "uAngle");
// Core profile requires a bound VAO even with no vertex attributes.
glGenVertexArrays(1, &gVao);
glBindVertexArray(gVao);
glClearColor(0.05f, 0.05f, 0.08f, 1.0f);
float scaleX, scaleY;
platformGetPixelDensityScale(&scaleX, &scaleY);
glViewport(0, 0, (int)(kWidth * scaleX), (int)(kHeight * scaleY));
return 0;
}
static void renderFrame() {
ZoneScoped;
glClear(GL_COLOR_BUFFER_BIT);
glUseProgram(gProgram);
{
TracyGpuZone("triangle draw");
glUniform1f(gAngleLoc, (float)platformGetTime());
glDrawArrays(GL_TRIANGLES, 0, 3);
}
platformSwapBuffers();
TracyGpuCollect;
}
static void shutdown() {
fprintf(stderr, "application is shutting down...\n");
glDeleteVertexArrays(1, &gVao);
glDeleteProgram(gProgram);
}
int main() {
if (!platformInit(kWidth, kHeight, "OpenGL Spinning Triangle"))
return 1;
if (initGL() != 0)
return 2;
platformRunLoop(renderFrame, shutdown);
return 0;
}

View File

@@ -0,0 +1,157 @@
# CMakeLists.txt — WebGPU spinning triangle demo
#
# macOS:
# clang++ -std=c++17 -ObjC++ spinning_triangle.cpp platform/platform_macos.mm \
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
# -Wl,-rpath,@executable_path \
# -framework Cocoa -framework Metal -framework QuartzCore \
# -framework Foundation -framework IOKit -framework IOSurface \
# -o spinning_triangle
#
# Windows (MSVC):
# cl /std:c++17 spinning_triangle.cpp platform/platform_windows.cpp \
# /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \
# user32.lib gdi32.lib /Fe:spinning_triangle.exe
#
# Linux (requires libsdl3-dev):
# g++ -std=c++17 spinning_triangle.cpp platform/platform_wayland.cpp \
# xdg-shell-protocol.c \
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
# -lwayland-client -o spinning_triangle
cmake_minimum_required(VERSION 3.16)
project(spinning_triangle LANGUAGES C CXX)
# ---------------------------------------------------------------------------
# WebGPU backend — set WGPU_PATH to your wgpu-native or Dawn installation.
# The library name differs between backends:
# wgpu-native → wgpu_native
# Dawn → webgpu_dawn
# ---------------------------------------------------------------------------
set(WGPU_PATH "" CACHE PATH "Root of the WebGPU native installation (contains include/ and lib/)")
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
if(NOT WGPU_PATH)
message(FATAL_ERROR "Set WGPU_PATH to the root of your WebGPU native installation.")
endif()
# When WGPU_PATH changes, discard any previously auto-detected WGPU_LIB so
# detection re-runs against the new path.
if(NOT "${WGPU_PATH}" STREQUAL "${_WGPU_PATH_LAST}")
unset(WGPU_LIB CACHE)
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
endif()
set(_WGPU_PATH_LAST "${WGPU_PATH}" CACHE INTERNAL "")
if(NOT WGPU_LIB)
unset(_WGPU_NATIVE_LIB CACHE)
unset(_WEBGPU_DAWN_LIB CACHE)
find_library(_WGPU_NATIVE_LIB NAMES wgpu_native wgpu_native.dll PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
find_library(_WEBGPU_DAWN_LIB NAMES webgpu_dawn PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
if(_WGPU_NATIVE_LIB)
set(WGPU_LIB "wgpu_native" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
elseif(_WEBGPU_DAWN_LIB)
set(WGPU_LIB "webgpu_dawn" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
else()
message(FATAL_ERROR "Could not detect a WebGPU library in ${WGPU_PATH}/lib. Set WGPU_LIB explicitly (wgpu_native or webgpu_dawn).")
endif()
message(STATUS "WebGPU library auto-detected: ${WGPU_LIB}")
endif()
# ---------------------------------------------------------------------------
# Tracy root — defaults to two directories above this CMakeLists.txt.
# ---------------------------------------------------------------------------
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
option(TRACY_ENABLE "Enable Tracy profiling" ON)
# ---------------------------------------------------------------------------
# macOS quarantine — pre-built WebGPU binaries downloaded from the internet
# carry a com.apple.quarantine extended attribute that prevents dyld from
# loading them ("damaged or incomplete" / Gatekeeper block). Strip it once
# at configure time so the linker and the runtime loader can both access the
# library directory without further user intervention.
# ---------------------------------------------------------------------------
if(APPLE)
execute_process(
COMMAND xattr -dr com.apple.quarantine "${WGPU_PATH}/lib"
)
endif()
# ---------------------------------------------------------------------------
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
# ---------------------------------------------------------------------------
find_package(SDL3 REQUIRED)
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
if(APPLE)
set(PLATFORM_LIBS
SDL3::SDL3
"-framework Cocoa"
"-framework Metal"
"-framework QuartzCore"
"-framework Foundation"
"-framework IOKit"
"-framework IOSurface"
)
elseif(WIN32)
# wgpu-native (Rust stdlib) pull-ins: NtReadFile, GetUserProfileDirectoryW, ...
set(WGPU_NATIVE_WIN32_LIBS ntdll userenv)
# Dawn pull-ins: WKPDID_D3DDebugObjectName GUID, CompareObjectHandles, ...
set(WEBGPU_DAWN_WIN32_LIBS dxguid onecore)
set(PLATFORM_LIBS SDL3::SDL3 ${WGPU_NATIVE_WIN32_LIBS} ${WEBGPU_DAWN_WIN32_LIBS})
else()
set(PLATFORM_LIBS SDL3::SDL3)
endif()
# ---------------------------------------------------------------------------
# Target
# ---------------------------------------------------------------------------
add_executable(spinning_triangle
spinning_triangle.cpp
"${TRACY_DIR}/public/TracyClient.cpp"
${PLATFORM_SOURCES}
)
# Treat TracyClient.cpp as third-party code — suppress all warnings so that
# upstream changes don't pollute our build output.
if(MSVC)
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "/w"
)
else()
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
PROPERTIES COMPILE_FLAGS "-w"
)
endif()
target_compile_features(spinning_triangle PRIVATE cxx_std_17)
if(TRACY_ENABLE)
target_compile_definitions(spinning_triangle PRIVATE TRACY_ENABLE)
endif()
target_include_directories(spinning_triangle PRIVATE
"${WGPU_PATH}/include"
"${TRACY_DIR}/public"
)
target_link_directories(spinning_triangle PRIVATE "${WGPU_PATH}/lib")
target_link_libraries(spinning_triangle PRIVATE
${WGPU_LIB}
${PLATFORM_LIBS}
)
# Embed the rpath so the binary finds the WebGPU dylib/so next to itself.
if(APPLE)
set_target_properties(spinning_triangle PROPERTIES
BUILD_RPATH "${WGPU_PATH}/lib"
INSTALL_RPATH "@executable_path"
)
elseif(UNIX)
set_target_properties(spinning_triangle PROPERTIES
BUILD_RPATH "${WGPU_PATH}/lib"
INSTALL_RPATH "$ORIGIN"
)
endif()

View File

@@ -0,0 +1,23 @@
// platform.h — interface between platform-agnostic code and platform backends
//
// Each platform_*.mm / platform_*.cpp file implements these five functions.
// Exactly one backend must be linked into the final binary.
#pragma once
#include <webgpu/webgpu.h>
// Initialize the windowing system and create a window of the given dimensions.
// Returns true on success.
bool platformInit(int width, int height, const char* title);
// Create a WebGPU surface backed by the platform window.
// Must be called after wgpuCreateInstance() and platformInit().
WGPUSurface platformCreateSurface(WGPUInstance instance);
// Elapsed wall-clock time in seconds since platformInit().
double platformGetTime();
// Enter the platform event/render loop.
// Calls render() each frame at ~60 fps.
// Calls shutdown() exactly once before returning.
void platformRunLoop(void (*render)(), void (*shutdown)());

View File

@@ -0,0 +1,95 @@
// platform_sdl3.cpp — SDL3 windowing backend for the WebGPU example
#include "platform.h" // webgpu/webgpu.h first
#define SDL_MAIN_HANDLED // we don't want SDL_main
#include <SDL3/SDL.h>
#ifdef __APPLE__
# include <SDL3/SDL_metal.h>
#endif
#include <chrono>
#include <cstdio>
static SDL_Window* sWin = nullptr;
static std::chrono::steady_clock::time_point sStartTime;
#ifdef __APPLE__
static SDL_MetalView sMetalView = nullptr;
#endif
bool platformInit(int width, int height, const char* title) {
if (!SDL_Init(SDL_INIT_VIDEO)) {
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
return false;
}
SDL_WindowFlags flags = 0;
#ifdef __APPLE__
flags |= SDL_WINDOW_METAL;
#endif
sWin = SDL_CreateWindow(title, width, height, flags);
if (!sWin) {
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
SDL_Quit();
return false;
}
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
sStartTime = std::chrono::steady_clock::now();
return true;
}
WGPUSurface platformCreateSurface(WGPUInstance instance) {
WGPUSurfaceDescriptor desc = {};
SDL_PropertiesID props = SDL_GetWindowProperties(sWin);
#if defined(__APPLE__)
sMetalView = SDL_Metal_CreateView(sWin);
if (!sMetalView) {
fprintf(stderr, "ERROR: SDL_Metal_CreateView failed\n");
return nullptr;
}
WGPUSurfaceSourceMetalLayer metalDesc = {};
metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
metalDesc.layer = SDL_Metal_GetLayer(sMetalView);
desc.nextInChain = &metalDesc.chain;
#elif defined(_WIN32)
WGPUSurfaceSourceWindowsHWND hwndDesc = {};
hwndDesc.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
hwndDesc.hinstance = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, nullptr);
hwndDesc.hwnd = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
desc.nextInChain = &hwndDesc.chain;
#else // Linux / X11
WGPUSurfaceSourceXlibWindow x11Desc = {};
x11Desc.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
x11Desc.display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
x11Desc.window = (uint32_t)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
desc.nextInChain = &x11Desc.chain;
#endif
return wgpuInstanceCreateSurface(instance, &desc);
}
double platformGetTime() {
return std::chrono::duration<double>(
std::chrono::steady_clock::now() - sStartTime).count();
}
void platformRunLoop(void (*render)(), void (*shutdown)()) {
bool running = true;
while (running) {
SDL_Event e;
while (SDL_PollEvent(&e)) {
if (e.type == SDL_EVENT_QUIT) running = false;
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
}
if (running) render();
}
shutdown();
#ifdef __APPLE__
SDL_Metal_DestroyView(sMetalView);
#endif
SDL_DestroyWindow(sWin);
SDL_Quit();
}

View File

@@ -0,0 +1,352 @@
// spinning_triangle.cpp — platform-agnostic WebGPU spinning triangle demo.
#include "platform/platform.h"
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <webgpu/webgpu.h>
#include <tracy/Tracy.hpp>
#include <tracy/TracyWebGPU.hpp>
// ---------------------------------------------------------------------------
// Globals
// ---------------------------------------------------------------------------
static const int kWidth = 800;
static const int kHeight = 600;
static WGPUInstance gInstance = nullptr;
static WGPUSurface gSurface = nullptr;
static WGPUAdapter gAdapter = nullptr;
static WGPUDevice gDevice = nullptr;
static WGPUQueue gQueue = nullptr;
static WGPURenderPipeline gPipeline = nullptr;
static WGPUBuffer gUniformBuf = nullptr;
static WGPUBindGroup gBindGroup = nullptr;
static TracyWebGPUCtx gTracyCtx = nullptr;
static WGPUTextureFormat gSurfaceFormat = WGPUTextureFormat_BGRA8Unorm;
// TODO: this can become platformError() instead
int error(int code, const char* message) {
fprintf(stderr, "ERROR: %s (code: %d)\n", message, code);
return code;
}
// ---------------------------------------------------------------------------
// WGSL shader — vertex colours baked in, rotation via a uniform float.
// ---------------------------------------------------------------------------
static const char* kShaderSource = R"(
struct Uniforms {
angle: f32,
};
@group(0) @binding(0) var<uniform> u: Uniforms;
struct VSOut {
@builtin(position) pos: vec4f,
@location(0) color: vec3f,
};
@vertex
fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
var positions = array<vec2f, 3>(
vec2f( 0.0, 0.5),
vec2f(-0.433, -0.25),
vec2f( 0.433, -0.25),
);
var colors = array<vec3f, 3>(
vec3f(1.0, 0.0, 0.0),
vec3f(0.0, 1.0, 0.0),
vec3f(0.0, 0.0, 1.0),
);
let c = cos(u.angle);
let s = sin(u.angle);
let p = positions[vi];
let rotated = vec2f(p.x * c - p.y * s, p.x * s + p.y * c);
var out: VSOut;
out.pos = vec4f(rotated, 0.0, 1.0);
out.color = colors[vi];
return out;
}
@fragment
fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
return vec4f(color, 1.0);
}
)";
// ---------------------------------------------------------------------------
// Adapter / Device request callbacks (current wgpu-native API)
// ---------------------------------------------------------------------------
static void onAdapterReady(WGPURequestAdapterStatus status,
WGPUAdapter adapter,
WGPUStringView message,
void* userdata1, void* /*userdata2*/) {
if (status == WGPURequestAdapterStatus_Success) {
*(WGPUAdapter*)userdata1 = adapter;
} else {
fprintf(stderr, "Adapter request failed: %.*s\n",
(int)message.length, message.data);
}
}
static void onDeviceReady(WGPURequestDeviceStatus status,
WGPUDevice device,
WGPUStringView message,
void* userdata1, void* /*userdata2*/) {
if (status == WGPURequestDeviceStatus_Success) {
*(WGPUDevice*)userdata1 = device;
} else {
fprintf(stderr, "Device request failed: %.*s\n",
(int)message.length, message.data);
}
}
// ---------------------------------------------------------------------------
// WebGPU init
// ---------------------------------------------------------------------------
static int initWebGPU() {
// Adapter
WGPURequestAdapterOptions adapterOpts = {};
adapterOpts.compatibleSurface = gSurface;
WGPURequestAdapterCallbackInfo adapterCB = {};
adapterCB.mode = WGPUCallbackMode_AllowProcessEvents;
adapterCB.callback = onAdapterReady;
adapterCB.userdata1 = &gAdapter;
wgpuInstanceRequestAdapter(gInstance, &adapterOpts, adapterCB);
while (!gAdapter) { wgpuInstanceProcessEvents(gInstance); }
if (!gAdapter) return error(11, "No adapter");
WGPUUncapturedErrorCallbackInfo errorCB = {};
errorCB.callback = [](WGPUDevice const*, WGPUErrorType type,
WGPUStringView message, void*, void*) {
fprintf(stderr, "[WGPU ERROR] type=%d %.*s\n",
(int)type, (int)message.length, message.data);
};
WGPUDeviceDescriptor deviceDesc = {};
deviceDesc.uncapturedErrorCallbackInfo = errorCB;
TracyWebGPUSetupDeviceDescriptor(deviceDesc);
WGPURequestDeviceCallbackInfo deviceCB = {};
deviceCB.mode = WGPUCallbackMode_AllowProcessEvents;
deviceCB.callback = onDeviceReady;
deviceCB.userdata1 = &gDevice;
wgpuAdapterRequestDevice(gAdapter, &deviceDesc, deviceCB);
while (!gDevice) { wgpuInstanceProcessEvents(gInstance); }
if (!gDevice) return error(12, "No device");
gQueue = wgpuDeviceGetQueue(gDevice);
gTracyCtx = TracyWebGPUContext(gInstance, gDevice, gQueue);
TracyWebGPUContextName(gTracyCtx, "WebGPU", 6);
// Configure surface
WGPUSurfaceConfiguration config = {};
config.device = gDevice;
config.format = gSurfaceFormat;
config.usage = WGPUTextureUsage_RenderAttachment;
config.alphaMode = WGPUCompositeAlphaMode_Opaque;
config.width = kWidth;
config.height = kHeight;
config.presentMode = WGPUPresentMode_Fifo;
wgpuSurfaceConfigure(gSurface, &config);
// Shader module
WGPUShaderSourceWGSL wgslSrc = {};
wgslSrc.chain.sType = WGPUSType_ShaderSourceWGSL;
wgslSrc.code = { kShaderSource, WGPU_STRLEN };
WGPUShaderModuleDescriptor smDesc = {};
smDesc.nextInChain = (WGPUChainedStruct*)&wgslSrc;
WGPUShaderModule shaderMod = wgpuDeviceCreateShaderModule(gDevice, &smDesc);
// Uniform buffer (one f32 for rotation angle)
WGPUBufferDescriptor bufDesc = {};
bufDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
bufDesc.size = sizeof(float);
gUniformBuf = wgpuDeviceCreateBuffer(gDevice, &bufDesc);
// Bind group layout + bind group
WGPUBindGroupLayoutEntry bglEntry = {};
bglEntry.binding = 0;
bglEntry.visibility = WGPUShaderStage_Vertex;
bglEntry.buffer.type = WGPUBufferBindingType_Uniform;
bglEntry.buffer.minBindingSize = sizeof(float);
WGPUBindGroupLayoutDescriptor bglDesc = {};
bglDesc.entryCount = 1;
bglDesc.entries = &bglEntry;
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(gDevice, &bglDesc);
WGPUBindGroupEntry bgEntry = {};
bgEntry.binding = 0;
bgEntry.buffer = gUniformBuf;
bgEntry.size = sizeof(float);
WGPUBindGroupDescriptor bgDesc = {};
bgDesc.layout = bgl;
bgDesc.entryCount = 1;
bgDesc.entries = &bgEntry;
gBindGroup = wgpuDeviceCreateBindGroup(gDevice, &bgDesc);
// Pipeline layout
WGPUPipelineLayoutDescriptor plDesc = {};
plDesc.bindGroupLayoutCount = 1;
plDesc.bindGroupLayouts = &bgl;
WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(gDevice, &plDesc);
// Render pipeline
WGPUColorTargetState colorTarget = {};
colorTarget.format = gSurfaceFormat;
colorTarget.writeMask = WGPUColorWriteMask_All;
WGPUFragmentState fragState = {};
fragState.module = shaderMod;
fragState.entryPoint = { "fs_main", WGPU_STRLEN };
fragState.targetCount = 1;
fragState.targets = &colorTarget;
WGPURenderPipelineDescriptor rpDesc = {};
rpDesc.layout = pipelineLayout;
rpDesc.vertex.module = shaderMod;
rpDesc.vertex.entryPoint = { "vs_main", WGPU_STRLEN };
rpDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
rpDesc.multisample.count = 1;
rpDesc.multisample.mask = 0xFFFFFFFF;
rpDesc.fragment = &fragState;
gPipeline = wgpuDeviceCreateRenderPipeline(gDevice, &rpDesc);
// Cleanup intermediates
wgpuShaderModuleRelease(shaderMod);
wgpuPipelineLayoutRelease(pipelineLayout);
wgpuBindGroupLayoutRelease(bgl);
return 0;
}
// ---------------------------------------------------------------------------
// Frame rendering
// ---------------------------------------------------------------------------
// Returns the surface texture for the current frame, or {.texture=nullptr} on
// a skippable condition (timeout, occlusion) or an error.
static WGPUSurfaceTexture getWindowSurface() {
WGPUSurfaceTexture surfTex = {};
wgpuSurfaceGetCurrentTexture(gSurface, &surfTex);
if (surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal ||
surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal)
return surfTex;
// Timeout and Occluded are normal OS events (window covered / on a different Space).
bool silent = surfTex.status == WGPUSurfaceGetCurrentTextureStatus_Timeout;
#ifdef WGPU_H_
silent = silent || surfTex.status == (WGPUSurfaceGetCurrentTextureStatus)WGPUSurfaceGetCurrentTextureStatus_Occluded;
#endif
if (!silent)
fprintf(stderr, "Failed to get surface texture (status %d)\n", surfTex.status);
if (surfTex.texture) wgpuTextureRelease(surfTex.texture);
surfTex.texture = nullptr;
return surfTex;
}
static void renderFrame() {
ZoneScoped;
// Update rotation angle
float angle = (float)platformGetTime();
wgpuQueueWriteBuffer(gQueue, gUniformBuf, 0, &angle, sizeof(float));
WGPUSurfaceTexture surfTex = getWindowSurface();
if (!surfTex.texture) return;
WGPUTextureView view = wgpuTextureCreateView(surfTex.texture, nullptr);
// Command encoder
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(gDevice, nullptr);
// Render pass
WGPURenderPassColorAttachment colorAtt = {};
colorAtt.view = view;
colorAtt.loadOp = WGPULoadOp_Clear;
colorAtt.storeOp = WGPUStoreOp_Store;
colorAtt.clearValue = { 0.05, 0.05, 0.08, 1.0 };
colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
WGPURenderPassDescriptor passDesc = {};
passDesc.colorAttachmentCount = 1;
passDesc.colorAttachments = &colorAtt;
{
ZoneScopedN("render-pass");
TracyWebGPUNamedZone(gTracyCtx, tracyZone, encoder, passDesc, "triangle draw", true);
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc);
wgpuRenderPassEncoderSetPipeline(pass, gPipeline);
wgpuRenderPassEncoderSetBindGroup(pass, 0, gBindGroup, 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
}
// Submit
WGPUCommandBuffer cmdBuf = wgpuCommandEncoderFinish(encoder, nullptr);
wgpuQueueSubmit(gQueue, 1, &cmdBuf);
// Present
wgpuSurfacePresent(gSurface);
// Process Events
wgpuInstanceProcessEvents(gInstance);
TracyWebGPUCollect(gTracyCtx);
// Cleanup
wgpuCommandBufferRelease(cmdBuf);
wgpuCommandEncoderRelease(encoder);
wgpuTextureViewRelease(view);
wgpuTextureRelease(surfTex.texture);
}
// ---------------------------------------------------------------------------
// Shutdown
// ---------------------------------------------------------------------------
static void shutdown() {
fprintf(stderr, "application is shutting down...\n");
TracyWebGPUDestroy(gTracyCtx);
if (gBindGroup) wgpuBindGroupRelease(gBindGroup);
if (gUniformBuf) wgpuBufferRelease(gUniformBuf);
if (gPipeline) wgpuRenderPipelineRelease(gPipeline);
if (gQueue) wgpuQueueRelease(gQueue);
if (gDevice) wgpuDeviceRelease(gDevice);
if (gAdapter) wgpuAdapterRelease(gAdapter);
if (gSurface) wgpuSurfaceRelease(gSurface);
if (gInstance) wgpuInstanceRelease(gInstance);
}
// ---------------------------------------------------------------------------
// main
// ---------------------------------------------------------------------------
int main(int argc, char* argv[]) {
if (!platformInit(kWidth, kHeight, "WebGPU Spinning Triangle"))
return 1;
gInstance = wgpuCreateInstance(nullptr);
if (!gInstance) return error(2, "Failed to create WebGPU instance.");
gSurface = platformCreateSurface(gInstance);
if (!gSurface) return error(3, "Failed to create surface.");
if (initWebGPU() != 0) return 4;
platformRunLoop(renderFrame, shutdown);
return 0;
}

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,6 +21,9 @@ 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
`{start, end, text}` dicts (start/end in ns).
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
@@ -41,24 +44,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))

View File

@@ -30,6 +30,7 @@ _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")
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
# system prompt; exposing it as an MCP resource keeps analysis guidance in
@@ -258,8 +259,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)
@@ -677,6 +677,13 @@ async def shutdown_server() -> str:
if __name__ == "__main__":
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 +696,17 @@ 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 = (
mcp_server.settings.sse_path
if _TRANSPORT == "sse"
else mcp_server.settings.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
try:
mcp_server.run(transport="sse")
mcp_server.run(transport=_TRANSPORT)
except KeyboardInterrupt:
print("\nTracy MCP server stopped.", file=sys.stderr)
sys.exit(0)

View File

@@ -3,3 +3,151 @@ function Link(el)
el.attributes['reference'] = nil
return el
end
-- Drop Div wrappers (e.g. table/titlepage containers), keeping their content.
function Div(el)
return el.content
end
-- ---------------------------------------------------------------------------
-- LaTeX math -> plain-text approximation.
--
-- The target Markdown renderer has no math support, so a raw "$\frac{1}{2}$"
-- would show verbatim. We turn each math node into the closest Unicode/ASCII
-- equivalent: fractions become "a/b", \times becomes "x", super/subscripts use
-- Unicode digits, and the one multi-line display equation becomes a fenced
-- code block (Markdown collapses plain newlines, a code block keeps them).
-- ---------------------------------------------------------------------------
local sup = {['0']='',['1']='¹',['2']='²',['3']='³',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
local sub = {['0']='',['1']='',['2']='',['3']='',['4']='',['5']='',
['6']='',['7']='',['8']='',['9']='',['+']='',['-']='',
['=']='',['(']='',[')']=''}
-- Symbol replacements, applied as literal substitutions. Longer commands must
-- precede those that are a prefix of them (e.g. \rightarrow before \right).
local symbols = {
{'\\leftrightarrow',''}, {'\\rightarrow',''}, {'\\leftarrow',''},
{'\\Rightarrow',''}, {'\\Leftarrow',''}, {'\\to',''}, {'\\mapsto',''},
{'\\times','×'}, {'\\cdot','·'}, {'\\div','÷'}, {'\\ast','*'}, {'\\star','*'},
{'\\leq',''}, {'\\geq',''}, {'\\neq',''}, {'\\approx',''}, {'\\equiv',''},
{'\\ll','«'}, {'\\gg','»'}, {'\\le',''}, {'\\ge',''},
{'\\ldots',''}, {'\\cdots',''}, {'\\dots',''}, {'\\infty',''},
{'\\pm','±'}, {'\\mp',''}, {'\\propto',''}, {'\\sum','Σ'}, {'\\prod','Π'},
{'\\alpha','α'}, {'\\beta','β'}, {'\\gamma','γ'}, {'\\delta','δ'}, {'\\Delta','Δ'},
{'\\mu','µ'}, {'\\sigma','σ'}, {'\\pi','π'}, {'\\lambda','λ'}, {'\\theta','θ'},
{'\\left',''}, {'\\right',''},
{'\\qquad',' '}, {'\\quad',' '}, {'\\,',' '}, {'\\;',' '}, {'\\:',' '},
{'\\ ',' '}, {'\\!',''},
{'\\%','%'}, {'\\#','#'}, {'\\&','&'}, {'\\_','_'}, {'\\{','{'}, {'\\}','}'},
{'\\$','$'},
}
-- Literal (non-pattern) string replacement; avoids Lua pattern magic in keys.
local function lit_replace(s, a, b)
local out, i = {}, 1
while true do
local p = s:find(a, i, true)
if not p then out[#out + 1] = s:sub(i); break end
out[#out + 1] = s:sub(i, p - 1)
out[#out + 1] = b
i = p + #a
end
return table.concat(out)
end
-- Strip the outer braces of a "%b{}" capture.
local function grp(b) return b:sub(2, #b - 1) end
-- Map a string to Unicode super/subscript, or nil if any char is unsupported.
local function map_script(txt, map)
local res = {}
for i = 1, #txt do
local c = txt:sub(i, i)
if not map[c] then return nil end
res[#res + 1] = map[c]
end
return table.concat(res)
end
local function convert(s)
-- Text/font wrappers: keep the content, recurse to handle nesting.
for _, cmd in ipairs({'text', 'mathrm', 'mathit', 'mathbf', 'mathbb',
'mathsf', 'mathtt', 'mathcal', 'operatorname',
'textbf', 'textit', 'textrm'}) do
s = s:gsub('\\' .. cmd .. '(%b{})', function(b) return convert(grp(b)) end)
end
-- Fractions -> "num/den" (spaced when either side has spaces).
local function frac(a, b)
local n, d = convert(grp(a)), convert(grp(b))
local sep = (n:find(' ', 1, true) or d:find(' ', 1, true)) and ' / ' or '/'
return n .. sep .. d
end
s = s:gsub('\\frac(%b{})(%b{})', frac)
s = s:gsub('\\dfrac(%b{})(%b{})', frac)
s = s:gsub('\\tfrac(%b{})(%b{})', frac)
s = s:gsub('\\sfrac(%b{})(%b{})', frac)
-- Roots.
s = s:gsub('\\sqrt(%b{})', function(b) return '√(' .. convert(grp(b)) .. ')' end)
-- Single-char scripts first, so the braced fallback (e.g. "_native") below
-- is not re-scanned and mangled into Unicode subscripts.
s = s:gsub('%^([%w])', function(c) return sup[c] or ('^' .. c) end)
s = s:gsub('_([%w])', function(c) return sub[c] or ('_' .. c) end)
-- Braced scripts: Unicode when the content is all digits/signs, else keep
-- a readable "^(...)" / "_..." form.
s = s:gsub('%^(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sup) or ('^(' .. inner .. ')')
end)
s = s:gsub('_(%b{})', function(b)
local inner = convert(grp(b))
return map_script(inner, sub) or ('_' .. inner)
end)
-- Remaining symbols.
for _, pair in ipairs(symbols) do s = lit_replace(s, pair[1], pair[2]) end
return s
end
-- Convert a display equation, preserving its line structure for a code block.
local function convert_display(s)
s = convert(s)
for _, env in ipairs({'cases', 'aligned', 'align', 'array', 'matrix',
'gathered', 'split'}) do
s = lit_replace(s, '\\begin{' .. env .. '}', '')
s = lit_replace(s, '\\end{' .. env .. '}', '')
end
s = lit_replace(s, '\\\\', '\n') -- row break
s = s:gsub('%s*&%s*', ' ') -- column separator -> spacing
local lines = {}
for line in (s .. '\n'):gmatch('(.-)\n') do
line = line:gsub('^%s+', ''):gsub('%s+$', '')
if line ~= '' then lines[#lines + 1] = line end
end
for i = 2, #lines do lines[i] = ' ' .. lines[i] end -- indent continuations
return table.concat(lines, '\n')
end
function Math(el)
if el.mathtype == 'DisplayMath' then
return el -- handled at block level by Para, to emit a code block
end
return pandoc.Str(convert(el.text))
end
-- A paragraph that is solely a display equation becomes a fenced code block.
function Para(el)
local maths, only_math = {}, true
for _, x in ipairs(el.content) do
if x.t == 'Math' and x.mathtype == 'DisplayMath' then
maths[#maths + 1] = x
elseif x.t ~= 'Space' and x.t ~= 'SoftBreak' and x.t ~= 'LineBreak' then
only_math = false
end
end
if #maths == 0 or not only_math then return nil end
local parts = {}
for _, m in ipairs(maths) do parts[#parts + 1] = convert_display(m.text) end
return pandoc.CodeBlock(table.concat(parts, '\n\n'))
end

View File

@@ -7,12 +7,18 @@ sed -i -e 's@\\ctrl@Ctrl@g' _tmp.tex
sed -i -e 's@\\shift@Shift@g' _tmp.tex
sed -i -e 's@\\Alt@Alt@g' _tmp.tex
sed -i -e 's@\\del@Delete@g' _tmp.tex
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome6.h _tmp.tex
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome7.h _tmp.tex
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
# 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
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
sed -i -e 's@\\nameref{client}@Client markup@g' _tmp.tex
@@ -26,7 +32,10 @@ sed -i -e 's@\\nameref{configurationfiles}@Configuration files@g' _tmp.tex
awk -f bclogo2quote.awk _tmp.tex > _tmp_quoted.tex
mv _tmp_quoted.tex _tmp.tex
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -s _tmp.tex -o tracy.md
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -t 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables' -s _tmp.tex -o tracy.md
awk -f tablecaption.awk tracy.md > _tmp_caption.md
mv _tmp_caption.md tracy.md
sed -i -e 's/^> \*\*IMPORTANT:\([^*]*\)\*\*/> [!IMPORTANT]\
> **\1**/' tracy.md
@@ -37,6 +46,6 @@ sed -i -e 's/^> \*\*CAUTION:\([^*]*\)\*\*/> [!CAUTION]\
sed -i -e 's/^> \*\*NOTE:\([^*]*\)\*\*/> [!NOTE]\
> **\1**/' tracy.md
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome6.h tracy.md
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome7.h tracy.md
rm -f _tmp.tex

16
manual/tablecaption.awk Normal file
View File

@@ -0,0 +1,16 @@
# Pandoc emits table captions as a line beginning with ": ", which GitHub
# renders literally instead of as a caption. Strip the marker and italicize
# the caption instead. Captions may span several physical lines when they
# contain a hard line break (a trailing backslash). Underscores are used for
# the emphasis so captions that already contain "*...*" markup are left intact.
!incap && /^: / {
incap = 1
$0 = "_" substr($0, 3)
}
incap && !/\\$/ {
print $0 "_"
incap = 0
next
}
incap { print; next }
{ print }

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,7 @@
\usepackage{verbatim}
\usepackage[hyphens]{url}
\usepackage{hyperref} % For hyperlinks in the PDF
\usepackage{fontawesome6}
\usepackage{fontawesome7}
\usepackage[os=win]{menukeys}
\usepackage{xfrac}
\usepackage[euler]{textgreek}
@@ -141,7 +141,7 @@ There's much more Tracy can do, which can be explored by carefully reading this
\section{A quick look at Tracy Profiler}
\label{quicklook}
Tracy is a real-time, nanosecond resolution \emph{hybrid frame and sampling profiler} that you can use for remote or embedded telemetry of games and other applications. It can profile CPU\footnote{Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as Rust, Zig, C\#, OCaml, Odin, etc.}, GPU\footnote{All major graphic APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL.}, memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
Tracy is a real-time, nanosecond resolution \emph{hybrid frame and sampling profiler} that you can use for remote or embedded telemetry of games and other applications. It can profile CPU\footnote{Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as Rust, Zig, C\#, OCaml, Odin, etc.}, GPU\footnote{All major graphics/compute APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA, WebGPU.}, memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
While Tracy can perform statistical analysis of sampled call stack data, just like other \emph{statistical profilers} (such as VTune, perf, or Very Sleepy), it mainly focuses on manual markup of the source code. Such markup allows frame-by-frame inspection of the program execution. For example, you will be able to see exactly which functions are called, how much time they require, and how they interact with each other in a multi-threaded environment. In contrast, the statistical analysis may show you the hot spots in your code, but it cannot accurately pinpoint the underlying cause for semi-random frame stutter that may occur every couple of seconds.
@@ -228,7 +228,7 @@ Tracy aims to give you an understanding of the inner workings of a tight loop of
\subsection{Sampling profiler}
Tracy can periodically sample what the profiled application is doing, which provides detailed performance information at the source line/assembly instruction level. This can give you a deep understanding of how the processor executes the program. Using this information, you can get a coarse view at the call stacks, fine-tune your algorithms, or even 'steal' an optimization performed by one compiler and make it available for the others.
Tracy can periodically sample what the profiled application is doing, which provides detailed performance information at the source line/assembly instruction level. This can give you a deep understanding of how the processor executes the program. Using this information, you can get a coarse view at the call stacks, fine-tune your algorithms, or even \enquote{steal} an optimization performed by one compiler and make it available for the others.
On some platforms, it is possible to sample the hardware performance counters, which will give you information not only \emph{where} your program is running slowly, but also \emph{why}.
@@ -369,7 +369,7 @@ Note that these binary releases require AVX2 instruction set support on the proc
Tracy Profiler supports MSVC, GCC, and clang. You will need to use a reasonably recent version of the compiler due to the C++11 requirement. The following platforms are confirmed to be working (this is not a complete list):
\begin{itemize}
\item Windows (x86, x64, ARM64\footnote{Requires \textbf{"OpenCL, OpenGL, and Vulkan Compatibility Pack"} from Microsoft Store.})
\item Windows (x86, x64, ARM64\footnote{Requires \textbf{\enquote{OpenCL, OpenGL, and Vulkan Compatibility Pack}} from Microsoft Store.})
\item Linux (x86, x64, ARM, ARM64)
\item Android (ARM, ARM64, x86)
\item FreeBSD (x64)
@@ -594,7 +594,7 @@ In the case of some programming environments, you may need to take extra steps t
If you are using MSVC, you will need to disable the \emph{Edit And Continue} feature, as it makes the compiler non-conformant to some aspects of the C++ standard. In order to do so, open the project properties and go to \menu[,]{C/C++,General,Debug Information Format} and make sure \emph{Program Database for Edit And Continue (/ZI)} is \emph{not} selected.
For context, if you experience errors like "error C2131: expression did not evaluate to a constant", "failure was caused by non-constant arguments or reference to a non-constant symbol", and "see usage of '\texttt{\_\_LINE\_\_Var}'", chances are that your project has the \emph{Edit And Continue} feature enabled.
For context, if you experience errors like \enquote{error C2131: expression did not evaluate to a constant}, \enquote{failure was caused by non-constant arguments or reference to a non-constant symbol}, and \enquote{see usage of \enquote{\texttt{\_\_LINE\_\_Var}}}, chances are that your project has the \emph{Edit And Continue} feature enabled.
\paragraph{Universal Windows Platform}
@@ -778,7 +778,7 @@ Nevertheless, let's look at how we can try to stabilize the profiling data.
Also known as: the \emph{spectre} thing we have to deal with now.
You must be aware that most processors available on the market\footnote{Except low-cost ARM CPUs.} \emph{do not} execute machine code linearly, as laid out in the source code. This can lead to counterintuitive timing results reported by Tracy. Trying to get more 'reliable' readings\footnote{And by saying 'reliable,' you do in reality mean: behaving in a way you expect it.} would require a change in the behavior of the code, and this is not a thing a profiler should do. So instead, Tracy shows you what the hardware is \emph{really} doing.
You must be aware that most processors available on the market\footnote{Except low-cost ARM CPUs.} \emph{do not} execute machine code linearly, as laid out in the source code. This can lead to counterintuitive timing results reported by Tracy. Trying to get more \enquote{reliable} readings\footnote{And by saying \enquote{reliable,} you do in reality mean: behaving in a way you expect it.} would require a change in the behavior of the code, and this is not a thing a profiler should do. So instead, Tracy shows you what the hardware is \emph{really} doing.
This is a complex subject, and the details vary from one CPU to another. You can read a brief rundown of the topic at the following address: \url{https://travisdowns.github.io/blog/2019/06/11/speed-limits.html}.
@@ -805,7 +805,7 @@ While the CPU is more-or-less designed always to be able to work at the advertis
\item Do you have complete control over the power profile? Spoiler alert: no. The operating system may run anything at any time on any of the other cores, which will impact the turbo frequency you're able to achieve.
\end{itemize}
As you can see, this feature basically screams 'unreliable results!' Best keep it disabled and run at the base frequency. Otherwise, your timings won't make much sense. A true example: branchless compression function executing multiple times with the same input data was measured executing at \emph{four} different speeds.
As you can see, this feature basically screams \enquote{unreliable results!} Best keep it disabled and run at the base frequency. Otherwise, your timings won't make much sense. A true example: branchless compression function executing multiple times with the same input data was measured executing at \emph{four} different speeds.
Keep in mind that even at the base frequency, you may hit the thermal limits of the silicon and be down throttled.
@@ -940,7 +940,7 @@ Please don't ask about window decorations in Gnome. The current behavior is the
Special considerations must be taken to run the Tracy server/profiler GUI on Windows on ARM.
Ensure that the \textbf{"OpenCL, OpenGL, and Vulkan Compatibility Pack"} is installed (from the Microsoft Store), otherwise the GUI will fail to open.
Ensure that the \textbf{\enquote{OpenCL, OpenGL, and Vulkan Compatibility Pack}} is installed (from the Microsoft Store), otherwise the GUI will fail to open.
\subsubsection{Using an IDE}
@@ -955,7 +955,7 @@ The CMake build configuration will begin immediately. It is likely that you will
After the build configuration phase is over, you may want to make some further adjustments to what is being built. The primary place to do this is in the \emph{Project Status} section of the CMake side panel. The two key settings there are also available in the status bar at the bottom of the window:
\begin{itemize}
\item The \emph{Folder} setting allows you to choose which Tracy utility you want to work with. Select "profiler" for the profiler's GUI.
\item The \emph{Folder} setting allows you to choose which Tracy utility you want to work with. Select \enquote{profiler} for the profiler's GUI.
\item The \emph{Build variant} setting is used to toggle between the debug and release build configurations.
\end{itemize}
@@ -1016,7 +1016,7 @@ void Graphics::Render()
\subsection{Crash handling}
\label{crashhandling}
On selected platforms (see section~\ref{featurematrix}) Tracy will intercept application crashes\footnote{For example, invalid memory accesses ('segmentation faults', 'null pointer exceptions'), divisions by zero, etc.}. This serves two purposes. First, the client application will be able to send the remaining profiling data to the server. Second, the server will receive a crash report with the crash reason, call stack at the time of the crash, etc.
On selected platforms (see section~\ref{featurematrix}) Tracy will intercept application crashes\footnote{For example, invalid memory accesses (\enquote{segmentation faults}, \enquote{null pointer exceptions}), divisions by zero, etc.}. This serves two purposes. First, the client application will be able to send the remaining profiling data to the server. Second, the server will receive a crash report with the crash reason, call stack at the time of the crash, etc.
This is an automatic process, and it doesn't require user interaction. If you are experiencing issues with crash handling you may want to try defining the \texttt{TRACY\_NO\_CRASH\_HANDLER} macro to disable the built in crash handling.
@@ -1050,6 +1050,8 @@ Memory & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faXm
GPU zones (OpenGL) & \faCheck & \faCheck & \faCheck & \faPoo & \faPoo & & \faXmark \\
GPU zones (Vulkan) & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & & \faXmark \\
GPU zones (Metal) & \faXmark & \faXmark & \faXmark & \faCheck\textsuperscript{\emph{b}} & \faCheck\textsuperscript{\emph{b}} & \faXmark & \faXmark \\
GPU zones (CUDA) & \faCheck & \faCheck & \faXmark & \faXmark & \faXmark & \faQuestion & \faXmark \\
GPU zones (WebGPU) & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faQuestion & \faQuestion \\
Call stacks & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faXmark \\
Symbol resolution & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck & \faCheck \\
Crash handling & \faCheck & \faCheck & \faCheck & \faXmark & \faXmark & \faXmark & \faXmark \\
@@ -1108,7 +1110,7 @@ FrameMarkStart("Audio processing");
FrameMarkEnd("Audio processing");
\end{lstlisting}
Here, we pass two string literals with identical contents to two different macros. It is entirely up to the compiler to decide if it will pool these two strings into one pointer or if there will be two instances present in the executable image\footnote{\cite{ISO:2012:III} \S 2.14.5.12: "Whether all string literals are distinct (that is, are stored in nonoverlapping objects) is implementation-defined."}. For example, on MSVC, this is controlled by \menu[,]{Configuration Properties,C/C++,Code Generation,Enable String Pooling} option in the project properties (optimized builds enable it automatically). Note that even if string pooling is used on the compilation unit level, it is still up to the linker to implement pooling across object files.
Here, we pass two string literals with identical contents to two different macros. It is entirely up to the compiler to decide if it will pool these two strings into one pointer or if there will be two instances present in the executable image\footnote{\cite{ISO:2012:III} \S 2.14.5.12: \enquote{Whether all string literals are distinct (that is, are stored in nonoverlapping objects) is implementation-defined.}}. For example, on MSVC, this is controlled by \menu[,]{Configuration Properties,C/C++,Code Generation,Enable String Pooling} option in the project properties (optimized builds enable it automatically). Note that even if string pooling is used on the compilation unit level, it is still up to the linker to implement pooling across object files.
As you can see, making sure that string literals are properly pooled can be surprisingly tricky. To work around this problem, you may employ the following technique. In \emph{one} source file create the unique pointer for a string literal, for example:
@@ -1406,7 +1408,7 @@ It is valid to set the \texttt{Zone1} text or name \emph{only} in places \circle
\subsubsection{Filtering zones}
\label{filteringzones}
Zone logging can be disabled on a per-zone basis by making use of the \texttt{ZoneNamed} macros. Each of the macros takes an \texttt{active} argument ('\texttt{true}' in the example in section~\ref{multizone}), which will determine whether the zone should be logged.
Zone logging can be disabled on a per-zone basis by making use of the \texttt{ZoneNamed} macros. Each of the macros takes an \texttt{active} argument (\enquote{\texttt{true}} in the example in section~\ref{multizone}), which will determine whether the zone should be logged.
Note that this parameter may be a run-time variable, such as a user-controlled switch to enable profiling of a specific part of code only when required.
@@ -1558,14 +1560,24 @@ Fast navigation in large data sets and correlating zones with what was happening
If you want to include color coding of the messages (for example to make critical messages easily visible), you can use \texttt{TracyMessageC(text, size, color)} or \texttt{TracyMessageLC(text, color)} macros.
Messages can also have different severity levels: \texttt{Trace}, \texttt{Debug}, \texttt{Info}, \texttt{Warning}, \texttt{Error} or \texttt{Fatal}.
Messages can also have different severity levels:
\begin{itemize}
\item \emph{Trace} -- Broadly track variable states and events in the software program.
\item \emph{Debug} -- Describes variable states and details about specific internal events in the software, that are useful for investigations.
\item \emph{Info} -- Describes normal events, which inform on the expected progress and state of your software.
\item \emph{Warning} -- Describes potentially dangerous situations caused by unexpected events and states.
\item \emph{Error} -- Describes the occurrence of unexpected behavior. Does not interrupt the execution of the software.
\item \emph{Fatal} -- Describes a critical event that will lead to a software failure/crash.
\end{itemize}
The \texttt{TracyMessage} macros will log messages with the severity \texttt{Info}. To log a message with a different severity, you may use the \texttt{TracyLogString} macro that regroups all the functionalities from the previous macros. We recommend writing your own macros, wrapping the different severities for easier use. You may provide a color of 0 if you do not want to set a color for this message.
Examples:
\begin{lstlisting}
std::string dynStr = "Trace using a dynamic string, blue color, no callstack";
TracyLogString( tracy::MessageSeverity::Trace, 0xFF, 0, dynStr.size(), dynStr.c_str() );
TracyLogString( tracy::MessageSeverity::Warning, 0, TRACY_CALLSTACK, "Warning using a string litteral, no color, capturing the callstack to a depth of TRACY_CALLSTACK" );
TracyLogString( tracy::MessageSeverity::Warning, 0, TRACY_CALLSTACK, "Warning using a string literal, no color, capturing the callstack to a depth of TRACY_CALLSTACK" );
\end{lstlisting}
@@ -1607,8 +1619,6 @@ void operator delete(void* ptr) noexcept
}
\end{lstlisting}
In some rare cases (e.g., destruction of TLS block), events may be reported after the profiler is no longer available, which would lead to a crash. To work around this issue, you may use \texttt{TracySecureAlloc} and \texttt{TracySecureFree} variants of the macros.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -1642,10 +1652,12 @@ Sometimes an application will use more than one memory pool. For example, in add
To mark that a separate memory pool is to be tracked you should use the named version of memory macros, for example \texttt{TracyAllocN(ptr, size, name)} and \texttt{TracyFreeN(ptr, name)}, where \texttt{name} is an unique pointer to a string literal (section~\ref{uniquepointers}) identifying the memory pool.
Certain memory allocator designs (\enquote{arena allocators}) use an always-incrementing pointer to track the next region to allocate and do not support deallocation of individual objects. The only way to free memory with such an allocator is to simultaneously release all the objects that were allocated (reset the allocator state). You can mark such a mass-deallocation event in a memory pool with the \texttt{TracyMemoryDiscard(name)} macro.
\subsection{GPU profiling}
\label{gpuprofiling}
Tracy provides bindings for profiling OpenGL, Vulkan, Direct3D 11, Direct3D 12, Metal, OpenCL and CUDA execution time on GPU.
Tracy provides bindings for profiling OpenGL, Vulkan, Direct3D 11, Direct3D 12, Metal, OpenCL, CUDA and WebGPU execution time on GPU.
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}).
@@ -1701,6 +1713,12 @@ logo=\bcattention
\end{itemize}
\end{bclogo}
\subparagraph{Calibrated context}
By default, the OpenGL context is uncalibrated: the CPU and GPU clocks are aligned only once, when the context is created, so over long captures the two time domains may drift apart (section~\ref{options} describes correcting this drift manually). Defining \texttt{TRACY\_OPENGL\_AUTO\_CALIBRATION} before including \texttt{TracyOpenGL.hpp} enables periodic recalibration instead: roughly once per second Tracy samples the GPU and CPU clocks together and emits a calibration event, allowing the profiler to track and remove the drift automatically.
This is opt-in because OpenGL exposes no atomic CPU+GPU timestamp query (unlike Vulkan's \texttt{VK\_EXT\_calibrated\_timestamps} or Direct3D~12, whose contexts are always calibrated). Recalibration therefore reads the GPU clock with \texttt{glGetInteger64v(GL\_TIMESTAMP)}, which forces a CPU/GPU synchronization (a pipeline stall) each time it runs. Enable it only when the improved long-capture alignment is worth the periodic stall.
\subsubsection{Vulkan}
Similarly, for Vulkan support you should include the \texttt{public/tracy/TracyVulkan.hpp} header file. Tracing Vulkan devices and queues is a bit more involved, and the Vulkan initialization macro \texttt{TracyVkContext(physdev, device, queue, cmdbuf)} returns an instance of \texttt{TracyVkCtx} object, which tracks an associated Vulkan queue. Cleanup is performed using the \texttt{TracyVkDestroy(ctx)} macro. You may create multiple Vulkan contexts. To set a custom name for the context, use the \texttt{TracyVkContextName(ctx, name, size)} macro.
@@ -1785,6 +1803,16 @@ Unlike other GPU backends in Tracy, there is no need to call \texttt{TracyCUDACo
To stop profiling, call the \texttt{TracyCUDAStopProfiling(ctx)} macro.
\subsubsection{WebGPU}
WebGPU support is enabled by including the \texttt{public/tracy/TracyWebGPU.hpp} header file. Both major implementations of WebGPU (Dawn and wgpu-native) are supported.
Before creating the WebGPU device, make sure to call \texttt{TracyWebGPUSetupDeviceDescriptor()} to let Tracy request the necessary device features and extensions necessary for profiling. After the device is created, use the \texttt{TracyWebGPUContext()} macro to instantiate the necessary \texttt{WebGPUQueueCtx} object required for GPU instrumentation. The object should later be cleaned up with the \texttt{TracyWebGPUDestroy()} macro. To set a custom name for the context, use the \texttt{TracyWebGPUContextName()} macro.
To instrument a GPU zone, use the various \texttt{TracyWebGPU*Zone*()} macros. Note that WebGPU only offers command instrumentation at the \enquote{pass}-level. While command-level granularity is possible through implementation-specific WebGPU extensions, Tracy does not support it at the moment. Supply the corresponding WebGPU pass descriptor to the instrumentation macro \textit{before} creating the WebGPU pass encoder.
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}
On Linux, if rocprofiler-sdk is installed, tracy can automatically trace GPU dispatches and collect
@@ -1818,13 +1846,13 @@ sudo amd-smi set -g 0 -l stable_std
Putting more than one GPU zone macro in a single scope features the same issue as with the \texttt{ZoneScoped} macros, described in section~\ref{multizone} (but this time the variable name is \texttt{\_\_\_tracy\_gpu\_zone}).
To solve this problem, in case of OpenGL use the \texttt{TracyGpuNamedZone} macro in place of \texttt{TracyGpuZone} (or the color variant). The same applies to Vulkan, Direct3D 11/12 and Metal -- replace \texttt{TracyVkZone} with \texttt{TracyVkNamedZone}, \texttt{TracyD3D11Zone}/\texttt{TracyD3D12Zone} with \texttt{TracyD3D11NamedZone}/\texttt{TracyD3D12NamedZone}, and \texttt{TracyMetalZone} with \texttt{TracyMetalNamedZone}.
To solve this problem, in case of OpenGL use the \texttt{TracyGpuNamedZone} macro in place of \texttt{TracyGpuZone} (or the color variant). The same applies to Vulkan, Direct3D 11/12, Metal and WebGPU -- replace \texttt{TracyVkZone} with \texttt{TracyVkNamedZone}, \texttt{TracyD3D11Zone}/\texttt{TracyD3D12Zone} with \texttt{TracyD3D11NamedZone}/\texttt{TracyD3D12NamedZone}, \texttt{TracyMetalZone} with \texttt{TracyMetalNamedZone}, and \texttt{TracyWebGPUZone} with \texttt{TracyWebGPUNamedZone}.
Remember to provide your name for the created stack variable as the first parameter to the macros.
\subsubsection{Transient GPU zones}
Transient zones (see section~\ref{transientzones} for details) are available in OpenGL, Vulkan, and Direct3D 11/12 macros. Transient zones are not available for Metal at this moment.
Transient zones (see section~\ref{transientzones} for details) are available in OpenGL, Vulkan, Direct3D 11/12 and WebGPU macros. Transient zones are not available for Metal at this moment.
\subsection{Fibers}
\label{fibers}
@@ -1871,7 +1899,7 @@ As you can see, there are two threads, \texttt{t1} and \texttt{t2}, which are si
\subsection{Collecting call stacks}
\label{collectingcallstacks}
Capture of true calls stacks can be performed by using macros with the \texttt{S} postfix, which require an additional parameter, specifying the depth of call stack to be captured. The greater the depth, the longer it will take to perform capture. Currently you can use the following macros: \texttt{ZoneScopedS}, \texttt{ZoneScopedNS}, \texttt{ZoneScopedCS}, \texttt{ZoneScopedNCS}, \texttt{TracyAllocS}, \texttt{TracyFreeS}, \texttt{TracySecureAllocS}, \texttt{TracySecureFreeS}, \texttt{TracyMessageS}, \texttt{TracyMessageLS}, \texttt{TracyMessageCS}, \texttt{TracyMessageLCS}, \texttt{TracyGpuZoneS}, \texttt{TracyGpuZoneCS}, \texttt{TracyVkZoneS}, \texttt{TracyVkZoneCS}, and the named and transient variants.
Capture of true calls stacks can be performed by using macros with the \texttt{S} postfix, which require an additional parameter, specifying the depth of call stack to be captured. The greater the depth, the longer it will take to perform capture. Currently you can use the following macros: \texttt{ZoneScopedS}, \texttt{ZoneScopedNS}, \texttt{ZoneScopedCS}, \texttt{ZoneScopedNCS}, \texttt{TracyAllocS}, \texttt{TracyFreeS}, \texttt{TracyMessageS}, \texttt{TracyMessageLS}, \texttt{TracyMessageCS}, \texttt{TracyMessageLCS}, \texttt{TracyGpuZoneS}, \texttt{TracyGpuZoneCS}, \texttt{TracyVkZoneS}, \texttt{TracyVkZoneCS}, and the named and transient variants.
Be aware that call stack collection is a relatively slow operation. Table~\ref{CallstackTimes} and figure~\ref{CallstackPlot} show how long it took to perform a single capture of varying depth on multiple CPU architectures.
@@ -2017,7 +2045,7 @@ void DbgHelpUnlock() { ReleaseMutex(dbgHelpLock); }
}
\end{lstlisting}
At initilization time, tracy will attempt to preload symbols for device drivers and process modules. As this process can be slow when a lot of pdbs are involved, you can set the \texttt{TRACY\_NO\_DBGHELP\_INIT\_LOAD} environment variable to "1" to disable this behavior and rely on-demand symbol loading.
At initilization time, tracy will attempt to preload symbols for device drivers and process modules. As this process can be slow when a lot of pdbs are involved, you can set the \texttt{TRACY\_NO\_DBGHELP\_INIT\_LOAD} environment variable to \enquote{1} to disable this behavior and rely on-demand symbol loading.
\paragraph{Disabling resolution of inline frames}
@@ -2041,6 +2069,20 @@ filesystem setup as the one used to run the tracy instrumented application).
You can do path substitution with the \texttt{-p} option to perform any number of path
substitions in order to use symbols located elsewhere.
By default symbol resolution is performed with the platform's native facility: the DbgHelp
library on Windows, and the \texttt{addr2line} tool found in \texttt{PATH} elsewhere. You can
override this with the \texttt{-a} option, passing the path to a custom
\texttt{addr2line}-compatible tool (for instance an \texttt{addr2line} from a cross-compilation
toolchain, or \texttt{llvm-addr2line}). The \texttt{-a} option works on all platforms, including
Windows, and takes precedence over the platform default.
Extra arguments can be passed verbatim to the resolution tool with the \texttt{-A} option. Tracy
records callstack frame offsets relative to the image base, but \texttt{addr2line}-compatible
tools expect a full virtual address for images that have a non-zero preferred image base (such as
PE on Windows or Mach-O on Apple). For these, pass \texttt{-A "--relative-address"} so that
\texttt{llvm-addr2line} or \texttt{llvm-symbolizer} adds the image base back. ELF images need no
such adjustment.
\begin{bclogo}[
noborder=true,
couleur=black!5,
@@ -2253,6 +2295,31 @@ TracyCLockAfterUnlock(tracy_lock_ctx);
You can optionally mark the location of where the lock is held by using the \texttt{TracyCLockMark} macro, this should be done after acquiring the lock.
Similarly, you can use the following macros to mark a shared lock using the C API:
\begin{itemize}
\item \texttt{TracyCSharedLockAnnounce(lock\_ctx)}
\item \texttt{TracyCSharedLockTerminate(lock\_ctx)}
\item \texttt{TracyCSharedLockBeforeLock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterLock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterUnlock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterTryLock(lock\_ctx, acquired)}
\item \texttt{TracyCSharedLockBeforeSharedLock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterSharedLock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterSharedUnlock(lock\_ctx)}
\item \texttt{TracyCSharedLockAfterTrySharedLock(lock\_ctx, acquired)}
\item \texttt{TracyCSharedLockMark(lock\_ctx)}
\item \texttt{TracyCSharedLockCustomName(lock\_ctx, name, size)}
\end{itemize}
A shared lock context has to be defined next to the shared lock that it will be marking:
\begin{lstlisting}
TracyCSharedLockCtx tracy_shared_lock_ctx;
HANDLE shared_lock;
\end{lstlisting}
The same rules apply to shared locks as to regular locks, but you need to use the shared lock macros instead.
Lock implementations in classes \texttt{Lockable} and \texttt{SharedLockable} show how to properly perform context handling.
\subsubsection{Memory profiling}
\label{cmemoryprofiling}
@@ -2261,8 +2328,6 @@ Use the following macros in your implementations of \texttt{malloc} and \texttt{
\begin{itemize}
\item \texttt{TracyCAlloc(ptr, size)}
\item \texttt{TracyCFree(ptr)}
\item \texttt{TracyCSecureAlloc(ptr, size)}
\item \texttt{TracyCSecureFree(ptr)}
\end{itemize}
Correctly using this functionality can be pretty tricky. You also will need to handle all the memory allocations made by external libraries (which typically allow usage of custom memory allocation functions) and the allocations made by system functions. If you can't track such an allocation, you will need to make sure freeing is not reported\footnote{It's not uncommon to see a pattern where a system function returns some allocated memory, which you then need to release.}.
@@ -2324,7 +2389,7 @@ To see how you should use this API, you should look at the reference implementat
couleur=black!5,
logo=\bcbombe
]{Important}
A common mistake is to skip the zone "\texttt{isActive}" check. When using \texttt{TRACY\_ON\_DEMAND}, you need to read the value of \texttt{TracyCIsConnected} once, and check the same value for both \newline \texttt{\_\_\_tracy\_emit\_gpu\_zone\_begin\_alloc} and \texttt{\_\_\_tracy\_emit\_gpu\_zone\_end}. Tracy may otherwise receive a zone end without a zone begin.
A common mistake is to skip the zone \enquote{\texttt{isActive}} check. When using \texttt{TRACY\_ON\_DEMAND}, you need to read the value of \texttt{TracyCIsConnected} once, and check the same value for both \newline \texttt{\_\_\_tracy\_emit\_gpu\_zone\_begin\_alloc} and \texttt{\_\_\_tracy\_emit\_gpu\_zone\_end}. Tracy may otherwise receive a zone end without a zone begin.
\end{bclogo}
\subsubsection{Fibers}
@@ -2355,10 +2420,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
@@ -2537,16 +2599,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:
@@ -2555,7 +2618,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"
}
}
}
@@ -2822,8 +2885,8 @@ logo=\bclampe
Use the following calls in your implementations of allocator/deallocator:
\begin{itemize}
\item \texttt{tracy\_memory\_alloc(ptr, size, name, depth, secure)}
\item \texttt{tracy\_memory\_free(ptr, name, depth, secure)}
\item \texttt{tracy\_memory\_alloc(ptr, size, name, depth)}
\item \texttt{tracy\_memory\_free(ptr, name, depth)}
\end{itemize}
Correctly using this functionality can be pretty tricky especially in Fortran.
@@ -2879,7 +2942,7 @@ Tracy will perform an automatic collection of system data without user intervent
Some profiling data can only be retrieved using the kernel facilities, which are not available to users with normal privilege level. To collect such data, you will need to elevate your rights to the administrator level. You can do so either by running the profiled program from the \texttt{root} account on Unix or through the \emph{Run as administrator} option on Windows\footnote{To make this easier, you can run MSVC with admin privileges, which will be inherited by your program when you start it from within the IDE.}. On Android, you will need to have a rooted device (see section~\ref{androidlunacy} for additional information).
As this system-level tracing functionality is part of the automated collection process, no user intervention is necessary to enable it (assuming that the program was granted the rights needed). However, if, for some reason, you would want to prevent your application from trying to access kernel data, you may recompile your program with the \texttt{TRACY\_NO\_SYSTEM\_TRACING} define. If you want to disable this functionality dynamically at runtime instead, you can set the \texttt{TRACY\_NO\_SYSTEM\_TRACING} environment variable to "1".
As this system-level tracing functionality is part of the automated collection process, no user intervention is necessary to enable it (assuming that the program was granted the rights needed). However, if, for some reason, you would want to prevent your application from trying to access kernel data, you may recompile your program with the \texttt{TRACY\_NO\_SYSTEM\_TRACING} define. If you want to disable this functionality dynamically at runtime instead, you can set the \texttt{TRACY\_NO\_SYSTEM\_TRACING} environment variable to \enquote{1}.
\begin{bclogo}[
noborder=true,
@@ -3031,7 +3094,7 @@ On Linux, Tracy will override the \texttt{dlclose} function call to prevent shar
\subsubsection{Vertical synchronization}
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 '\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.
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.
Use the \texttt{TRACY\_NO\_VSYNC\_CAPTURE} macro to disable capture of Vsync events.
@@ -3044,9 +3107,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,
@@ -3185,7 +3254,7 @@ If you want to look at the profile data in real-time (or load a saved trace file
The \emph{\faWrench{}~Wrench} button opens the about dialog, which also contains a number of global settings you may want to tweak (section~\ref{aboutwindow}).
The client \emph{address entry} field and the \faWifi{}~\emph{Connect} button are used to connect to a running client\footnote{Note that a custom port may be provided here, for example by entering '127.0.0.1:1234'.}. You can use the connection history button~\faCaretDown{} to display a list of commonly used targets, from which you can quickly select an address. You can remove entries from this list by hovering the \faArrowPointer{}~mouse cursor over an entry and pressing the \keys{\del} button on the keyboard.
The client \emph{address entry} field and the \faWifi{}~\emph{Connect} button are used to connect to a running client\footnote{Note that a custom port may be provided here, for example by entering \enquote{127.0.0.1:1234}.}. You can use the connection history button~\faCaretDown{} to display a list of commonly used targets, from which you can quickly select an address. You can remove entries from this list by hovering the \faArrowPointer{}~mouse cursor over an entry and pressing the \keys{\del} button on the keyboard.
If you want to open a trace that you have stored on the disk, you can do so by pressing the \faFolderOpen{}~\emph{Open saved trace} button.
@@ -3270,7 +3339,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}
@@ -3832,10 +3901,10 @@ You will find the zones with locks and their associated threads on this combined
The left-hand side \emph{index area} of the timeline view displays various labels (threads, locks), which can be categorized in the following way:
\begin{itemize}
\item \emph{Light blue label} -- GPU context. Multi-threaded Vulkan, OpenCL, Direct3D 12 and Metal contexts are additionally split into separate threads.
\item \emph{Light blue label} -- GPU context. Multi-threaded Vulkan, OpenCL, Direct3D 12, Metal and WebGPU contexts are additionally split into separate threads.
\item \emph{Pink label} -- CPU data graph.
\item \emph{White label} -- A CPU thread. It will be replaced by a bright red label in a thread that has crashed (section~\ref{crashhandling}). If automated sampling was performed, clicking the~\LMB{}~left mouse button on the \emph{\faGhost{}~ghost zones} button will switch zone display mode between 'instrumented' and 'ghost.'
\item \emph{Green label} -- Fiber, coroutine, or any other sort of cooperative multitasking 'green thread.'
\item \emph{White label} -- A CPU thread. It will be replaced by a bright red label in a thread that has crashed (section~\ref{crashhandling}). If automated sampling was performed, clicking the~\LMB{}~left mouse button on the \emph{\faGhost{}~ghost zones} button will switch zone display mode between \enquote{instrumented} and \enquote{ghost.}
\item \emph{Green label} -- Fiber, coroutine, or any other sort of cooperative multitasking \enquote{green thread.}
\item \emph{Light red label} -- Indicates a lock.
\item \emph{Yellow label} -- Plot.
\end{itemize}
@@ -3854,7 +3923,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 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.
@@ -4026,7 +4095,7 @@ You can freely adjust each time range on the timeline by clicking the \LMB{}~lef
Tracy allows adding custom notes to the trace. For example, you may want to mark a region to ignore because the application was out-of-focus or a region where a new user was connecting to the game, which resulted in a frame drop that needs to be investigated.
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, a settings window is displayed (section~\ref{annotationsettings}), allowing you to enter a description.
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.
@@ -4063,7 +4132,7 @@ 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 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{\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.
@@ -4113,7 +4182,7 @@ You can filter the message list in the following ways:
\begin{itemize}
\item By the originating thread in the \emph{\faShuffle{} Visible threads} drop-down.
\item By matching the message text to the expression in the \emph{\faFilter{}~Filter messages} entry field. Multiple filter expressions can be comma-separated (e.g. 'warn, info' will match messages containing strings 'warn' \emph{or} 'info'). You can exclude matches by preceding the term with a minus character (e.g., '-debug' will hide all messages containing the string 'debug').
\item By matching the message text to the expression in the \emph{\faFilter{}~Filter messages} entry field. Multiple filter expressions can be comma-separated (e.g. \enquote{warn, info} will match messages containing strings \enquote{warn} \emph{or} \enquote{info}). You can exclude matches by preceding the term with a minus character (e.g., \enquote{-debug} will hide all messages containing the string \enquote{debug}).
\item By message source, distinguishing between \emph{\faUser{}~User} messages and internal \emph{\faMicroscope{}~Tracy} diagnostics.
\item By severity level: \emph{\faShoePrints{}~Trace}, \emph{\faBug{}~Debug}, \emph{\faInfo{}~Info}, \emph{\faTriangleExclamation{}~Warning}, \emph{\faCircleXmark{}~Error}, or \emph{\faSkullCrossbones{}~Fatal}.
\end{itemize}
@@ -4537,7 +4606,9 @@ The information about the selected memory allocation is displayed in this window
\subsection{Trace information window}
\label{traceinfo}
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture, and a custom trace description, which you can fill in.
This window contains information about the current trace: captured program name, time of the capture, profiler version which performed the capture.
There's an text entry field for an optional custom description of the trace for you to fill in. This description will appear on the profiler window title bar, or when comparing two traces (section~\ref{compare}), enabling you to quickly recognize what the trace contains. For some people it's fine to just have \emph{any} semi-unique description to be able to identify a specific trace. For such purposes there's an \emph{\faDice{}~Generate name} button, which will set the trace description to an abstract meaningless identifier.
If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containing trace-specific user settings (see section~\ref{tracespecific}) will be saved on disk next to the trace file.
@@ -4576,7 +4647,7 @@ The zone information window displays detailed information about a single zone. T
\begin{itemize}
\item Basic source location information: function name, source file location, and the thread name.
\item Timing information.
\item If the profiler performed context switch capture (section~\ref{contextswitches}) and a thread was suspended during zone execution, a list of wait regions will be displayed, with complete information about the timing, CPU migrations, and wait reasons. If CPU topology data is available (section~\ref{cputopology}), the profiler will mark zone migrations across cores with 'C' and migrations across packages -- with 'P.' In some cases, context switch data might be incomplete\footnote{For example, when capture is ongoing and context switch information has not yet been received.}, in which case a warning message will be displayed.
\item If the profiler performed context switch capture (section~\ref{contextswitches}) and a thread was suspended during zone execution, a list of wait regions will be displayed, with complete information about the timing, CPU migrations, and wait reasons. If CPU topology data is available (section~\ref{cputopology}), the profiler will mark zone migrations across cores with \enquote{C} and migrations across packages -- with \enquote{P.} In some cases, context switch data might be incomplete\footnote{For example, when capture is ongoing and context switch information has not yet been received.}, in which case a warning message will be displayed.
\item Memory events list, both summarized and a list of individual allocation/free events (see section~\ref{memorywindow} for more information on the memory events list).
\item List of messages that the profiler logged in the zone's scope. If the \emph{exclude children} option is disabled, messages emitted in child zones will also be included.
\item Parent zones list, showing the hierarchy of parent zones that contain the current zone. Hovering the \faArrowPointer{}~mouse pointer over a parent zone will highlight it on the timeline view with a red outline. Clicking the \LMB{}~left mouse button on a zone will switch the zone info window to that zone. Clicking the \MMB{}~middle mouse button on a zone will zoom the timeline view to the zone's extent. Clicking the \RMB{}~right mouse button on a source file location will open the source file view window (if applicable, see section~\ref{sourceview}).
@@ -4613,10 +4684,12 @@ Clicking on the \emph{\faClipboard{}~Copy to clipboard} buttons will copy the ap
This window shows the frames contained in the selected call stack. Information about the originating thread is included. Each frame is described by a function name, source file location, and originating image\footnote{Executable images are called \emph{modules} by Microsoft.} name. Function frames originating from the kernel are marked with a red color. Clicking the \LMB{}~left mouse button on either the function name of source file location will copy the name to the clipboard. Clicking the \RMB{}~right mouse button on the source file location will open the source file view window (if applicable, see section~\ref{sourceview}).
A single stack frame may have multiple function call places associated with it. This happens in the case of inlined function calls. Such entries will be displayed in the call stack window, with \emph{inline} in place of frame number\footnote{Or '\faCaretRight{}'~icon in case of call stack tooltips.}.
A single stack frame may have multiple function call places associated with it. This happens in the case of inlined function calls. Such entries will be displayed in the call stack window, with \emph{inline} in place of frame number\footnote{Or \enquote{\faCaretRight{}}~icon in case of call stack tooltips.}.
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}
@@ -4626,7 +4699,7 @@ Stack frame location may be displayed in the following number of ways, depending
\item \emph{Symbol address} -- displays begin address of the function containing the frame address.
\end{itemize}
In some cases, it may not be possible to decode stack frame addresses correctly. Such frames will be presented with a dimmed '\texttt{[ntdll.dll]}' name of the image containing the frame address, or simply '\texttt{[unknown]}' if the profiler cannot retrieve even this information. Additionally, '\texttt{[kernel]}' is used to indicate unknown stack frames within the operating system's internal routines.
In some cases, it may not be possible to decode stack frame addresses correctly. Such frames will be presented with a dimmed \enquote{\texttt{[ntdll.dll]}} name of the image containing the frame address, or simply \enquote{\texttt{[unknown]}} if the profiler cannot retrieve even this information. Additionally, \enquote{\texttt{[kernel]}} is used to indicate unknown stack frames within the operating system's internal routines.
External frames from system libraries are hidden by default. Enabling the \emph{\faShieldHalved{}~External} option will show these frames, which can be useful for debugging issues in external code. When external frames are displayed, they are dimmed out.
@@ -4714,7 +4787,7 @@ Some modes may be unavailable in some circumstances (missing or outdated source
\paragraph{Source mode}
This is pretty much the source file view window, but with the ability to select one of the source files that the compiler used to build the symbol. Additionally, each source file line that produced machine code in the symbol will show a count of associated assembly instructions, displayed with an '\texttt{@}' prefix, and will be marked with grey color on the scroll bar. Due to how optimizing compilers work, some lines may seemingly not produce any machine code, for example, because iterating a loop counter index might have been reduced to advancing a data pointer. Some other lines may have a disproportionate amount of associated instructions, e.g., when the compiler applied a loop unrolling optimization. This varies from case to case and from compiler to compiler.
This is pretty much the source file view window, but with the ability to select one of the source files that the compiler used to build the symbol. Additionally, each source file line that produced machine code in the symbol will show a count of associated assembly instructions, displayed with an \enquote{\texttt{@}} prefix, and will be marked with grey color on the scroll bar. Due to how optimizing compilers work, some lines may seemingly not produce any machine code, for example, because iterating a loop counter index might have been reduced to advancing a data pointer. Some other lines may have a disproportionate amount of associated instructions, e.g., when the compiler applied a loop unrolling optimization. This varies from case to case and from compiler to compiler.
The \emph{Propagate inlines} option, available when sample data is present, will enable propagation of the instruction costs down the local call stack. For example, suppose a base function in the symbol issues a call to an inlined function (which may not be readily visible due to being contained in another source file). In that case, any cost attributed to the inlined function will be visible in the base function. Because the cost information is added to all the entries in the local call stacks, it is possible to see seemingly nonsense total cost values when this feature is enabled. To quickly toggle this on or off, you may also press the \keys{X} key.
@@ -4732,7 +4805,7 @@ logo=\bclampe
]{Local call stack}
In some cases, it may be challenging to understand what is being displayed in the disassembly. For example, calling the \texttt{std::lower\_bound} function may generate multiple levels of inlined functions: first, we enter the search algorithm, then the comparison functions, which in turn may be lambdas that call even more external code, and so on. In such an event, you will most likely see that some external code is taking a long time to execute, and you will be none the wiser on improving things.
The local call stack for an assembly instruction represents all the inline function calls \emph{within the symbol} (hence the 'local' part), which were made to reach the instruction. Deeper inspection of the local call stack, including navigation to the source call site of each participating inline function, can be performed through the context menu accessible by pressing the \RMB{}~right mouse button on the source location.
The local call stack for an assembly instruction represents all the inline function calls \emph{within the symbol} (hence the \enquote{local} part), which were made to reach the instruction. Deeper inspection of the local call stack, including navigation to the source call site of each participating inline function, can be performed through the context menu accessible by pressing the \RMB{}~right mouse button on the source location.
\end{bclogo}
Selecting the \emph{\faGears{}~Raw code} option will enable the display of raw machine code bytes for each line. Individual bytes are displayed with interwoven colors to make reading easier.
@@ -4783,9 +4856,9 @@ In this mode, the source and assembly panes will be displayed together, providin
\paragraph{Instruction pointer cost statistics}
If automated call stack sampling (see chapter~\ref{sampling}) was performed, additional profiling information will be available. The first column of source and assembly views will contain percentage counts of collected instruction pointer samples for each displayed line, both in numerical and graphical bar form. You can use this information to determine which function line takes the most time. The displayed percentage values are heat map color-coded, with the lowest values mapped to dark red and the highest to bright yellow. The color code will appear next to the percentage value and on the scroll bar so that you can identify 'hot' places in the code at a glance.
If automated call stack sampling (see chapter~\ref{sampling}) was performed, additional profiling information will be available. The first column of source and assembly views will contain percentage counts of collected instruction pointer samples for each displayed line, both in numerical and graphical bar form. You can use this information to determine which function line takes the most time. The displayed percentage values are heat map color-coded, with the lowest values mapped to dark red and the highest to bright yellow. The color code will appear next to the percentage value and on the scroll bar so that you can identify \enquote{hot} places in the code at a glance.
By default, samples are displayed only within the selected symbol, in isolation. In some cases, you may, however, want to include samples from functions that the selected symbol called. To do so, enable the \emph{\faRightFromBracket{}~Child calls} option, which you may also temporarily toggle by holding the \keys{Z} key. You can also click the~\faCaretDown{}~drop down control to display a child call distribution list\footnote{The height of the list can be changed by dragging the separator bar.}, which shows each known function\footnote{You should remember that these are results of random sampling. Some function calls may be missing here.} that the symbol called. Make sure to familiarize yourself with section~\ref{readingcallstacks} to be able to read the results correctly. Each child call on the list has an attributed time cost, which is also displayed as a percentage of the child calls ("\%~Calls") and the percentage of the total symbol time ("\%~Total").
By default, samples are displayed only within the selected symbol, in isolation. In some cases, you may, however, want to include samples from functions that the selected symbol called. To do so, enable the \emph{\faRightFromBracket{}~Child calls} option, which you may also temporarily toggle by holding the \keys{Z} key. You can also click the~\faCaretDown{}~drop down control to display a child call distribution list\footnote{The height of the list can be changed by dragging the separator bar.}, which shows each known function\footnote{You should remember that these are results of random sampling. Some function calls may be missing here.} that the symbol called. Make sure to familiarize yourself with section~\ref{readingcallstacks} to be able to read the results correctly. Each child call on the list has an attributed time cost, which is also displayed as a percentage of the child calls (\enquote{\%~Calls}) and the percentage of the total symbol time (\enquote{\%~Total}).
The total number of collected samples is displayed in the UI under the~\emph{\faEyeDropper~Samples} label and converted to a time approximation at the~\emph{\faStopwatch~Time} label. The displayed values show the local count if child calls are disabled and the total count if the option is enabled. In either case, the number of samples attributed only to the child calls is displayed in parentheses with the + or - symbol and as a percentage of the total symbol time.
@@ -4877,7 +4950,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. 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 remove it here.
\subsection{Annotation list window}
\label{annotationlist}
@@ -4951,6 +5024,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
[unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-Q4_K_M]
hf = unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-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.
If you have the resources available you may try replacing \texttt{unsloth/Qwen3.6-35B-A3B-MTP-GGUF:UD-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.
@@ -4962,7 +5071,7 @@ There are no ideal LLM providers, but here are some options:
\begin{itemize}
\item \emph{llama.cpp} (\url{https://github.com/ggml-org/llama.cpp}) -- Recommended as the easiest to use. Clone from git and build it yourself. By default it fits the model automatically to available memory. It is rapidly advancing with new features and model support. Most other providers use it to do the actual work, and they typically use an outdated release. The \url{https://llama.app/} site might provide easy way to install llama.
\item \emph{llama-swap} (\url{https://github.com/mostlygeek/llama-swap}) -- Wrapper for llama.cpp that allows model selection. Recommended to augment the above.
\item \emph{LM Studio} (\url{https://lmstudio.ai/}) -- It is easy to install on all platforms and has a GUI. But it is overwhelming when it comes to the number of options it offers. Some people may question the licensing. Its features lag a behind llama.cpp. Manual configuration of each model is required. To get it to work properly, go to it settings (using the gear icon in the bottom right corner of the program window), then select the Developer tab and enable "When applicable, separate \texttt{reasoning\_content} and \texttt{content} in API responses".
\item \emph{LM Studio} (\url{https://lmstudio.ai/}) -- It is easy to install on all platforms and has a GUI. But it is overwhelming when it comes to the number of options it offers. Some people may question the licensing. Its features lag a behind llama.cpp. Manual configuration of each model is required. To get it to work properly, go to it settings (using the gear icon in the bottom right corner of the program window), then select the Developer tab and enable \enquote{When applicable, separate \texttt{reasoning\_content} and \texttt{content} in API responses}.
\end{itemize}
\subsection{Model selection}
@@ -4982,7 +5091,7 @@ noborder=true,
couleur=black!5,
logo=\bclampe
]{Model quantization}
Running a model with full 32-bit floating-point weights is not feasible due to memory requirements. Instead, the model parameters are quantized, for which 4 bits is typically the sweet spot. In general, the lower the parameter precision, the more "dumbed down" the model becomes. However, the loss of model coherence due to quantization is less than the benefit of being able to run a larger model.
Running a model with full 32-bit floating-point weights is not feasible due to memory requirements. Instead, the model parameters are quantized, for which 4 bits is typically the sweet spot. In general, the lower the parameter precision, the more \enquote{dumbed down} the model becomes. However, the loss of model coherence due to quantization is less than the benefit of being able to run a larger model.
\end{bclogo}
\begin{bclogo}[
@@ -4990,9 +5099,9 @@ noborder=true,
couleur=black!5,
logo=\bclampe
]{Model size}
Another thing to consider when selecting a model is its size, which is typically measured in billions of parameters (weights) and written as 4B, for example. The model size determines how much memory, computation, and time are required to run it. Generally, the larger the model, the "smarter" its responses will be.
Another thing to consider when selecting a model is its size, which is typically measured in billions of parameters (weights) and written as 4B, for example. The model size determines how much memory, computation, and time are required to run it. Generally, the larger the model, the \enquote{smarter} its responses will be.
Most modern models will be "Mixture of Experts", or MoE, and their size will be denoted, for example, 35B-A3B. This means that the model size is 35B, but only 3B parameters are active and used to compute the next token. In practice, this means that the model has knowledge closer to the full, dense 35B model but speed and GPU memory requirements closer to the fast 3B model.
Most modern models will be \enquote{Mixture of Experts}, or \enquote{MoE}, and their size will be denoted, for example, 35B-A3B. This means that the model size is 35B, but only 3B parameters are active and used to compute the next token. In practice, this means that the model has knowledge closer to the full, dense 35B model but speed and GPU memory requirements closer to the fast 3B model.
\end{bclogo}
\begin{bclogo}[
@@ -5000,7 +5109,7 @@ noborder=true,
couleur=black!5,
logo=\bclampe
]{Context size}
The model size only indicates the minimum memory requirement. For the model to operate properly, you also need to set the context size, which determines how much information from the conversation the model can "remember". This size is measured in tokens, and a very rough approximation is that each token is a combination of three or four letters.
The model size only indicates the minimum memory requirement. For the model to operate properly, you also need to set the context size, which determines how much information from the conversation the model can \enquote{remember}. This size is measured in tokens, and a very rough approximation is that each token is a combination of three or four letters.
Each token present in the context window may require a fairly large amount of memory, and that can quickly add up to gigabytes. Some modern models use solutions that greatly reduce context memory requirements, but that varies from model to model. If needed, the KV cache used for context can be quantized, just like model parameters. In this case, the recommended size per weight is 8 bits.
@@ -5011,7 +5120,7 @@ The realistic minimum required context size for Tracy to run the assistant is 10
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 "GPU offload" to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable "Offload KV Cache to GPU Memory" in LM Studio).
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}
@@ -5072,7 +5181,7 @@ The horizontal meter directly below shows how much of the context size has been
The chat section contains the conversation with the automated assistant with alternating user and assistant turns. Clicking on the~\emph{\faUser{}~User} role icon removes the chat content up to the selected question. Similarly, clicking on the~\emph{\faRobot{}~Assistant} role icon removes the conversation content up to this point and generates another response from the assistant.
The assistant may give preliminary replies to the user, for example, \emph{"I will now check the source of function foobar"}, followed by performing the actual check, then a continuation of the reply, such as \emph{"Now I can see that..."}. To make reading these tiered replies easier, only the most recent reply is printed in normal text, while the preliminary responses are dimmed out.
The assistant may give preliminary replies to the user, for example, \enquote{I will now check the source of function foobar}, followed by performing the actual check, then a continuation of the reply, such as \enquote{Now I can see that...}. To make reading these tiered replies easier, only the most recent reply is printed in normal text, while the preliminary responses are dimmed out.
Each assistant reply contains a note about the language model that was used and the time it took to generate the text.
@@ -5140,8 +5249,8 @@ You can customize the output with the following command line options:
\item \texttt{-h, -\hspace{-1.25ex} -help} -- Display a help message
\item \texttt{-f, -\hspace{-1.25ex} -filter <name>} -- Filter the zone names
\item \texttt{-c, -\hspace{-1.25ex} -case} -- Make the name filtering case sensitive
\item \texttt{-s, -\hspace{-1.25ex} -sep <separator>} -- Customize the CSV separator (default is ``\texttt{,}'')
\item \texttt{-e, -\hspace{-1.25ex} -self} -- Use self time (equivalent to the ``Self time'' toggle in the profiler GUI)
\item \texttt{-s, -\hspace{-1.25ex} -sep <separator>} -- Customize the CSV separator (default is \enquote{\texttt{,}})
\item \texttt{-e, -\hspace{-1.25ex} -self} -- Use self time (equivalent to the \enquote{Self time} toggle in the profiler GUI)
\item \texttt{-u, -\hspace{-1.25ex} -unwrap} -- Report each zone individually; this will discard the statistics columns and instead report the timestamp and duration for each zone entry
\item \texttt{-g, -\hspace{-1.25ex} -gpu} -- Report each GPU zone event
\item \texttt{-m, -\hspace{-1.25ex} -messages} -- Report only messages

View File

@@ -135,6 +135,10 @@ if get_option('ignore_memory_faults')
tracy_common_args += ['-DTRACY_IGNORE_MEMORY_FAULTS']
endif
if get_option('opengl_auto_calibration')
tracy_common_args += ['-DTRACY_OPENGL_AUTO_CALIBRATION']
endif
tracy_shared_libs = get_option('default_library') == 'shared'
if tracy_shared_libs

View File

@@ -29,3 +29,4 @@ option('verbose', type : 'boolean', value : false, description : 'Enable verbose
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('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')

View File

@@ -44,10 +44,16 @@ ExternalProject_Add(embed
)
function(Embed LIST NAME FILE)
cmake_parse_arguments(EMBED "TEXT" "" "" ${ARGN})
if(EMBED_TEXT)
set(EMBED_FLAGS -t)
else()
set(EMBED_FLAGS)
endif()
add_custom_command(
OUTPUT data/${NAME}.cpp data/${NAME}.hpp
COMMAND ${CMAKE_COMMAND} -E make_directory data
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
COMMAND ${CMAKE_CURRENT_BINARY_DIR}/embed ${EMBED_FLAGS} ${NAME} ${CMAKE_CURRENT_LIST_DIR}/${FILE} data/${NAME}
DEPENDS embed ${CMAKE_CURRENT_LIST_DIR}/${FILE}
)
list(APPEND ${LIST} data/${NAME}.cpp)
@@ -70,6 +76,7 @@ set(SERVER_FILES
TracyMarkdown.cpp
TracyMicroArchitecture.cpp
TracyMouse.cpp
TracyNameGen.cpp
TracyProtoHistory.cpp
TracySourceContents.cpp
TracySourceTokenizer.cpp
@@ -145,18 +152,33 @@ set(PROFILER_FILES
src/winmainArchDiscovery.cpp
)
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md)
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md)
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md)
Embed(PROFILER_FILES ToolsJson src/llm/tools.json)
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)
Embed(PROFILER_FILES ToolsJson src/llm/tools.json TEXT)
Embed(PROFILER_FILES FontFixed src/font/FiraCode-Retina.ttf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 6\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontIcons src/font/Font\ Awesome\ 7\ Free-Solid-900.otf)
Embed(PROFILER_FILES FontNormal src/font/Roboto-Regular.ttf)
Embed(PROFILER_FILES FontBold src/font/Roboto-Bold.ttf)
Embed(PROFILER_FILES FontItalic src/font/Roboto-Italic.ttf)
Embed(PROFILER_FILES FontBoldItalic src/font/Roboto-BoldItalic.ttf)
Embed(PROFILER_FILES FontEmoji src/font/NotoEmoji-Regular.ttf)
Embed(PROFILER_FILES Manual ../manual/tracy.md)
Embed(PROFILER_FILES Manual ../manual/tracy.md TEXT)
Embed(PROFILER_FILES Text100Million src/achievements/100Million.md TEXT)
Embed(PROFILER_FILES TextConnectToClient src/achievements/ConnectToClient.md TEXT)
Embed(PROFILER_FILES TextFindZone src/achievements/FindZone.md TEXT)
Embed(PROFILER_FILES TextFrameImages src/achievements/FrameImages.md TEXT)
Embed(PROFILER_FILES TextGlobalSettings src/achievements/GlobalSettings.md TEXT)
Embed(PROFILER_FILES TextInstrumentationIntro src/achievements/InstrumentationIntro.md TEXT)
Embed(PROFILER_FILES TextInstrumentationStatistics src/achievements/InstrumentationStatistics.md TEXT)
Embed(PROFILER_FILES TextInstrumentFrames src/achievements/InstrumentFrames.md TEXT)
Embed(PROFILER_FILES TextIntro src/achievements/Intro.md TEXT)
Embed(PROFILER_FILES TextLoadTrace src/achievements/LoadTrace.md TEXT)
Embed(PROFILER_FILES TextSamplingIntro src/achievements/SamplingIntro.md TEXT)
Embed(PROFILER_FILES TextSaveTrace src/achievements/SaveTrace.md TEXT)
set(INCLUDES "${CMAKE_CURRENT_BINARY_DIR}")
set(LIBS "")
@@ -278,7 +300,19 @@ if(NOT EMSCRIPTEN)
endif()
if(EMSCRIPTEN)
target_link_options(${PROJECT_NAME} PRIVATE -pthread -sASSERTIONS=0 -sINITIAL_MEMORY=384mb -sALLOW_MEMORY_GROWTH=1 -sMAXIMUM_MEMORY=4gb -sSTACK_SIZE=1048576 -sWASM_BIGINT=1 -sPTHREAD_POOL_SIZE=8 -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard -sEXPORTED_RUNTIME_METHODS=ccall -sENVIRONMENT=web,worker --preload-file embed.tracy)
target_link_options(${PROJECT_NAME} PRIVATE
-pthread
-sASSERTIONS=0
-sINITIAL_MEMORY=384mb
-sALLOW_MEMORY_GROWTH=1
-sMAXIMUM_MEMORY=4gb
-sSTACK_SIZE=1048576
-sPTHREAD_POOL_SIZE=8
-sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
-sEXPORTED_RUNTIME_METHODS=ccall
-sENVIRONMENT=web,worker
--preload-file embed.tracy
)
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})

View File

@@ -1,17 +1,27 @@
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <string>
#include "../../public/common/tracy_lz4hc.hpp"
static void Usage()
{
fprintf( stderr, "Usage: embed <objectName> <source> <destination>\n" );
fprintf( stderr, "Usage: embed [-t] <objectName> <source> <destination>\n" );
fprintf( stderr, " destination should be without extension, will create cpp, hpp pair\n" );
fprintf( stderr, " -t: treat source as text, convert line endings to unix\n" );
}
int main( int argc, char** argv )
{
bool text = false;
if( argc >= 2 && strcmp( argv[1], "-t" ) == 0 )
{
text = true;
argc--;
argv++;
}
if( argc < 4 )
{
Usage();
@@ -38,6 +48,16 @@ int main( int argc, char** argv )
fread( data, 1, sz, src );
fclose( src );
if( text )
{
size_t pos = 0;
for( size_t i=0; i<sz; i++ )
{
if( data[i] != '\r' ) data[pos++] = data[i];
}
sz = pos;
}
const auto lz4szMax = tracy::LZ4_compressBound( sz );
auto lz4data = new uint8_t[lz4szMax];
const auto lz4sz = tracy::LZ4_compress_HC( (const char*)data, (char*)lz4data, sz, lz4szMax, 6 );

View File

@@ -162,6 +162,15 @@ static ImGuiKey TranslateKeyCode( const char* code )
return ImGuiKey_None;
}
static void UpdateKeyModifiers( const EmscriptenKeyboardEvent* e )
{
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent( ImGuiMod_Ctrl, e->ctrlKey );
io.AddKeyEvent( ImGuiMod_Shift, e->shiftKey );
io.AddKeyEvent( ImGuiMod_Alt, e->altKey );
io.AddKeyEvent( ImGuiMod_Super, e->metaKey );
}
Backend::Backend( const char* title, const std::function<void()>& redraw, const std::function<void(float)>& scaleChanged, const std::function<int(void)>& isBusy, RunQueue* mainThreadTasks )
{
constexpr EGLint eglConfigAttrib[] = {
@@ -243,6 +252,7 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
return EM_TRUE;
} );
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
UpdateKeyModifiers( e );
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, true );
@@ -250,6 +260,7 @@ Backend::Backend( const char* title, const std::function<void()>& redraw, const
return EM_TRUE;
} );
emscripten_set_keyup_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
UpdateKeyModifiers( e );
const auto code = TranslateKeyCode( e->code );
if( code == ImGuiKey_None ) return EM_FALSE;
ImGui::GetIO().AddKeyEvent( code, false );

View File

@@ -4,7 +4,6 @@
#include <misc/freetype/imgui_freetype.h>
#include "Fonts.hpp"
#include "profiler/IconsFontAwesome6.h"
#include "profiler/TracyEmbed.hpp"
#include "data/FontFixed.hpp"

View File

@@ -0,0 +1,12 @@
# It's over 100 million!
Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!
Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it.
Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case.
> [!TIP]
> Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory.
>
> To capture 100 million zones, you will need approximately 4 GB of RAM.

View File

@@ -0,0 +1,10 @@
# First profiling session
Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:
1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project.
2. Make sure that `TracyClient.cpp` (or the Tracy library) is included in your build.
3. Define `TRACY_ENABLE` in your build configuration, for the whole application. Do not do it in a single source file because it won't work.
4. Start your application, and * Connect* to it with the profiler.
Please refer to the [user manual](https://github.com/wolfpld/tracy/releases) for more details.

View File

@@ -0,0 +1,11 @@
# Find some zones
You can search for zones in the trace by opening the search window with the * Find zone* button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code.
The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them.
Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone.
When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!
Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram.

View File

@@ -0,0 +1,11 @@
# A picture is worth a thousand words
Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the `FrameImage` macro.
You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way.
The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen.
You can even view a recording of what your application was doing by clicking the * Tools* icon and then selecting the * Playback* option. Try it out!
The `FrameImage` macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?

View File

@@ -0,0 +1,5 @@
# Global settings
Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the * Wrench* icon on the welcome screen. This will open the about window, where you can expand the * Global settings* menu.
The settings are saved between sessions, so you only need to set them once.

View File

@@ -0,0 +1,22 @@
# Instrumenting frames
In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application.
To instrument frames, you need to add the `FrameMark` macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame.
```c++
#include "Tracy.hpp"
void Render()
{
// Render the frame
SwapBuffers();
FrameMark;
}
```
When you profile your application, you will see a new frame appear on the timeline each time the `FrameMark` macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second.
The `FrameMark` macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames.
Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok.

View File

@@ -0,0 +1,22 @@
# Instrumentating your application
Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up.
To get started, open a source file and include the `Tracy.hpp` header. This will give you access to a variety of macros provided by Tracy. Next, add the `ZoneScoped` macro to the beginning of one of your functions, like this:
```c++
#include "Tracy.hpp"
void SomeFunction()
{
ZoneScoped;
// Your code here
}
```
Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called.
> [!NOTE]
> The `ZoneScoped` macro is just one of the many macros provided by Tracy. See the documentation for more information.
The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information.

View File

@@ -0,0 +1,5 @@
# Show me the stats!
Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called.
To view the statistics, click on the * Statistics* button on the top bar. This will open a new window with a list of all zones in the trace.

View File

@@ -0,0 +1,12 @@
# Click here to discover achievements!
Clicking on the * Achievements* button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done.
As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!
To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements * Achievements* button will glow yellow when there are new things to see.
- New tasks: orange 
- Completed tasks: green 
Good luck!

View File

@@ -0,0 +1,3 @@
# Load a trace
You can open a previously saved trace file (or one received from a friend) with the * Open saved trace* button on the welcome screen.

View File

@@ -0,0 +1,10 @@
# Sampling program execution
Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often.
While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return.
Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work.
> [!WARNING]
> Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information.

View File

@@ -0,0 +1,12 @@
# Save a trace
Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the * Connection* icon in the top left corner of the screen and then clicking on the * Save trace* button.
Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before.
You can also share the trace with your friends or co-workers by sending them the trace file.
> [!WARNING]
> **Warning**
>
> Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others.

Binary file not shown.

View File

@@ -103,6 +103,12 @@ Tracy Profiler can intercept crashes and report them to the user for analysis. T
Each frame in a call stack has an associated instruction pointer, `ip` the return address where the execution will return from the function a frame above. This address is somewhere in the symbol code. The start of the symbol is provided as the `baseAddr` value. This base address identifies the symbol and can be used in various symbol-related tool calls as the symbol address.
# Wait stacks
Some call stacks represent time spent waiting for something to happen. For example, the program may want to read something from the disk. In such cases, program execution will be paused, and the CPU will start running kernel code responsible for filesystem access, I/O routines, or just idling while waiting for a response from the hardware.
A wait stack is identified by the presence of the `wait_time` field, which shows how much time was spent waiting for execution to return to the program. Further information about the wait stack can be inferred from the optional fields `wait_reason` (with an explanation in `wait_reason_hint`) and `wait_state` (with an explanation in `wait_state_hint`).
# Inspecting call stacks
1. Focus on user's code. Ignore standard library boilerplate.

View File

@@ -39,7 +39,7 @@
#include "profiler/TracyTexture.hpp"
#include "profiler/TracyView.hpp"
#include "profiler/TracyWeb.hpp"
#include "profiler/IconsFontAwesome6.h"
#include "profiler/IconsFontAwesome7.h"
#include "../../server/tracy_pdqsort.h"
#include "../../server/tracy_robin_hood.h"
#include "../../server/TracyFileHeader.hpp"
@@ -177,8 +177,8 @@ static void SetupDPIScale()
auto& style = ImGui::GetStyle();
style = ImGuiStyle();
ImGui::StyleColorsDark();
style.WindowBorderSize = 1.f * scale;
style.FrameBorderSize = 1.f * scale;
style.WindowBorderSize = 1.f;
style.FrameBorderSize = 1.f;
style.FrameRounding = 5.f;
style.Colors[ImGuiCol_ScrollbarBg] = ImVec4( 1, 1, 1, 0.03f );
style.Colors[ImGuiCol_Header] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f);
@@ -1466,9 +1466,17 @@ Would you like to enable achievements?
{
ImGui::Columns( 2 );
ImGui::SetColumnWidth( 0, 300 * dpiScale );
ImGui::BeginChild( "##achievementtoc", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
DrawAchievements( c->items );
ImGui::EndChild();
ImGui::NextColumn();
if( s_achievementItem ) s_achievementItem->description();
ImGui::BeginChild( "##achievementtext", ImVec2( 0, 0 ), ImGuiChildFlags_AlwaysUseWindowPadding );
if( s_achievementItem )
{
tracy::Markdown md( nullptr, nullptr );
md.Print( s_achievementItem->text.c_str(), s_achievementItem->text.size() );
}
ImGui::EndChild();
ImGui::EndColumns();
ImGui::EndTabItem();
}

View File

@@ -1,14 +1,17 @@
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++
// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml
// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf
// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py
// for C and C++
// from codepoints https://github.com/FortAwesome/Font-Awesome/raw/7.x/metadata/icons.yml
// for use with font https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-regular-400.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.), https://github.com/FortAwesome/Font-Awesome/blob/7.x/webfonts/fa-solid-900.woff2 (You may need to convert the .woff2 files to .ttf depending upon your loader.)
#pragma once
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf"
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf"
#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.woff2"
#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.woff2"
#define ICON_MIN_FA 0xe005
#define ICON_MAX_16_FA 0xf8ff
#define ICON_MAX_FA 0xf8ff
#define ICON_FA_0 "0" // U+0030
#define ICON_FA_1 "1" // U+0031
#define ICON_FA_2 "2" // U+0032
@@ -22,6 +25,7 @@
#define ICON_FA_A "A" // U+0041
#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9
#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb
#define ICON_FA_ALARM_CLOCK "\xef\x8d\x8e" // U+f34e
#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037
#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039
#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036
@@ -41,7 +45,9 @@
#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102
#define ICON_FA_ANKH "\xef\x99\x84" // U+f644
#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1
#define ICON_FA_AQUARIUS "\xee\xa1\x85" // U+e845
#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557
#define ICON_FA_ARIES "\xee\xa1\x86" // U+e846
#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063
#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162
#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886
@@ -116,6 +122,7 @@
#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e
#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d
#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462
#define ICON_FA_BANGLADESHI_TAKA_SIGN "\xee\x8b\xa6" // U+e2e6
#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a
#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9
#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828
@@ -214,6 +221,7 @@
#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805
#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc
#define ICON_FA_BUS "\xef\x88\x87" // U+f207
#define ICON_FA_BUS_SIDE "\xee\xa0\x9d" // U+e81d
#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e
#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a
#define ICON_FA_C "C" // U+0043
@@ -232,8 +240,10 @@
#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083
#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8
#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb
#define ICON_FA_CANCER "\xee\xa1\x87" // U+e847
#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786
#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f
#define ICON_FA_CAPRICORN "\xee\xa1\x88" // U+e848
#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b
#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9
#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df
@@ -266,6 +276,7 @@
#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe
#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080
#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3
#define ICON_FA_CHART_DIAGRAM "\xee\x9a\x95" // U+e695
#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4
#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201
#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200
@@ -287,9 +298,9 @@
#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054
#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077
#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae
#define ICON_FA_CHILD_COMBATANT "\xee\x93\xa0" // U+e4e0
#define ICON_FA_CHILD_DRESS "\xee\x96\x9c" // U+e59c
#define ICON_FA_CHILD_REACHING "\xee\x96\x9d" // U+e59d
#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0
#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1
#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d
#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111
@@ -334,6 +345,7 @@
#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da
#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d
#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a
#define ICON_FA_CLOSED_CAPTIONING_SLASH "\xee\x84\xb5" // U+e135
#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2
#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed
#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee
@@ -360,6 +372,7 @@
#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651
#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad
#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5
#define ICON_FA_COMMENT_NODES "\xee\x9a\x96" // U+e696
#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3
#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd
#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086
@@ -522,6 +535,8 @@
#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd
#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3
#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e
#define ICON_FA_FILE_FRAGMENT "\xee\x9a\x97" // U+e697
#define ICON_FA_FILE_HALF_DASHED "\xee\x9a\x98" // U+e698
#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5
#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f
#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570
@@ -585,6 +600,7 @@
#define ICON_FA_GEAR "\xef\x80\x93" // U+f013
#define ICON_FA_GEARS "\xef\x82\x85" // U+f085
#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5
#define ICON_FA_GEMINI "\xee\xa1\x89" // U+e849
#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d
#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2
#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b
@@ -642,8 +658,6 @@
#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684
#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5
#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4
#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6
#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f
#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060
#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6
#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0
@@ -657,7 +671,6 @@
#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064
#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc
#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025
#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f
#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590
#define ICON_FA_HEART "\xef\x80\x84" // U+f004
#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc
@@ -672,6 +685,9 @@
#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502
#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807
#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503
#define ICON_FA_HEXAGON "\xef\x8c\x92" // U+f312
#define ICON_FA_HEXAGON_NODES "\xee\x9a\x99" // U+e699
#define ICON_FA_HEXAGON_NODES_BOLT "\xee\x9a\x9a" // U+e69a
#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591
#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507
#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508
@@ -767,8 +783,10 @@
#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a
#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337
#define ICON_FA_LEMON "\xef\x82\x94" // U+f094
#define ICON_FA_LEO "\xee\xa1\x8a" // U+e84a
#define ICON_FA_LESS_THAN "<" // U+003c
#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537
#define ICON_FA_LIBRA "\xee\xa1\x8b" // U+e84b
#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd
#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb
#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e
@@ -842,6 +860,7 @@
#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527
#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf
#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd
#define ICON_FA_MOBILE_VIBRATE "\xee\xa0\x96" // U+e816
#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6
#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1
#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b
@@ -871,6 +890,7 @@
#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff
#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c
#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea
#define ICON_FA_NON_BINARY "\xee\xa0\x87" // U+e807
#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e
#define ICON_FA_NOTDEF "\xee\x87\xbe" // U+e1fe
#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249
@@ -878,6 +898,7 @@
#define ICON_FA_O "O" // U+004f
#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247
#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248
#define ICON_FA_OCTAGON "\xef\x8c\x86" // U+f306
#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613
#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532
#define ICON_FA_OM "\xef\x99\xb9" // U+f679
@@ -906,6 +927,7 @@
#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae
#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044
#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303
#define ICON_FA_PENTAGON "\xee\x9e\x90" // U+e790
#define ICON_FA_PEOPLE_ARROWS "\xee\x81\xa8" // U+e068
#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce
#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533
@@ -968,8 +990,10 @@
#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd
#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0
#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c
#define ICON_FA_PICTURE_IN_PICTURE "\xee\xa0\x8b" // U+e80b
#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3
#define ICON_FA_PILLS "\xef\x92\x84" // U+f484
#define ICON_FA_PISCES "\xee\xa1\x8c" // U+e84c
#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818
#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f
#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072
@@ -1060,6 +1084,7 @@
#define ICON_FA_S "S" // U+0053
#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d
#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a
#define ICON_FA_SAGITTARIUS "\xee\xa1\x8d" // U+e84d
#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445
#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf
#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0
@@ -1073,6 +1098,7 @@
#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e
#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f
#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4
#define ICON_FA_SCORPIO "\xee\xa1\x8e" // U+e84e
#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a
#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9
#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e
@@ -1080,6 +1106,7 @@
#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2
#define ICON_FA_SECTION "\xee\x91\x87" // U+e447
#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8
#define ICON_FA_SEPTAGON "\xee\xa0\xa0" // U+e820
#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233
#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f
#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064
@@ -1108,6 +1135,8 @@
#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7
#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277
#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4
#define ICON_FA_SINGLE_QUOTE_LEFT "\xee\xa0\x9b" // U+e81b
#define ICON_FA_SINGLE_QUOTE_RIGHT "\xee\xa0\x9c" // U+e81c
#define ICON_FA_SINK "\xee\x81\xad" // U+e06d
#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8
#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c
@@ -1131,12 +1160,14 @@
#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891
#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717
#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110
#define ICON_FA_SPIRAL "\xee\xa0\x8a" // U+e80a
#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc
#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5
#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd
#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0
#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8
#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c
#define ICON_FA_SQUARE_BINARY "\xee\x9a\x9b" // U+e69b
#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150
#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191
#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152
@@ -1194,7 +1225,10 @@
#define ICON_FA_T "T" // U+0054
#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce
#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a
#define ICON_FA_TABLE_CELLS_COLUMN_LOCK "\xee\x99\xb8" // U+e678
#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009
#define ICON_FA_TABLE_CELLS_ROW_LOCK "\xee\x99\xba" // U+e67a
#define ICON_FA_TABLE_CELLS_ROW_UNLOCK "\xee\x9a\x91" // U+e691
#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db
#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b
#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d
@@ -1208,6 +1242,7 @@
#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db
#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b
#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c
#define ICON_FA_TAURUS "\xee\xa1\x8f" // U+e84f
#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba
#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e
#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f
@@ -1235,6 +1270,7 @@
#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165
#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164
#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d
#define ICON_FA_THUMBTACK_SLASH "\xee\x9a\x8f" // U+e68f
#define ICON_FA_TICKET "\xef\x85\x85" // U+f145
#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff
#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c
@@ -1310,8 +1346,6 @@
#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501
#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500
#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728
#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406
#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa
#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502
#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503
#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504
@@ -1336,7 +1370,6 @@
#define ICON_FA_V "V" // U+0056
#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6
#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5
#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb
#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221
#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226
#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228
@@ -1349,6 +1382,7 @@
#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d
#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2
#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7
#define ICON_FA_VIRGO "\xee\xa1\x90" // U+e850
#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074
#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8
#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9
@@ -1357,6 +1391,7 @@
#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897
#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770
#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f
#define ICON_FA_VOLUME "\xef\x9a\xa8" // U+f6a8
#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028
#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027
#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026
@@ -1372,6 +1407,7 @@
#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773
#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5
#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e
#define ICON_FA_WEB_AWESOME "\xee\x9a\x82" // U+e682
#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd
#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496
#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd

View File

@@ -1,52 +1,60 @@
#include "IconsFontAwesome6.h"
#include "TracyAchievements.hpp"
#include "TracyImGui.hpp"
#include "TracySourceContents.hpp"
#include "TracyWeb.hpp"
#include "../Fonts.hpp"
#include "TracyEmbed.hpp"
#include "data/Text100Million.hpp"
#include "data/TextConnectToClient.hpp"
#include "data/TextFindZone.hpp"
#include "data/TextFrameImages.hpp"
#include "data/TextGlobalSettings.hpp"
#include "data/TextInstrumentFrames.hpp"
#include "data/TextInstrumentationIntro.hpp"
#include "data/TextInstrumentationStatistics.hpp"
#include "data/TextIntro.hpp"
#include "data/TextLoadTrace.hpp"
#include "data/TextSamplingIntro.hpp"
#include "data/TextSaveTrace.hpp"
namespace tracy::data
{
AchievementItem ai_samplingIntro = { "samplingIntro", "Sampling program execution", [](){
ImGui::TextWrapped( "Sampling program execution is a great way to find out where the hot spots are in your program. It can be used to find out which functions take the most time, or which lines of code are executed the most often." );
ImGui::TextWrapped( "While instrumentation requires changes to your code, sampling does not. However, because of the way it works, the results are coarser and it's not possible to know when functions are called or when they return." );
ImGui::TextWrapped( "Sampling is automatic on Linux. On Windows, you must run the profiled application as an administrator for it to work." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Depending on your system configuration, some additional steps may be required. Please refer to the user manual for more information." );
ImGui::PopStyleColor();
ImGui::PopFont();
} };
static std::string UnpackImpl( size_t size, size_t lz4Size, const uint8_t* data )
{
std::string ret;
const EmbedData unembed( size, lz4Size, data );
ret.assign( unembed.data(), unembed.size() );
return ret;
}
#define Unpack( name ) UnpackImpl( Embed::name##Size, Embed::name##Lz4Size, Embed::name##Data )
AchievementItem ai_samplingIntro = {
.id = "samplingIntro",
.name = "Sampling program execution",
.text = Unpack( TextSamplingIntro ),
};
AchievementItem* ac_samplingItems[] = { &ai_samplingIntro, nullptr };
AchievementCategory ac_sampling = { "sampling", "Sampling", ac_samplingItems };
AchievementItem ai_100million = { "100million", "It's over 100 million!", [](){
ImGui::TextWrapped( "Tracy can handle a lot of data. How about 100 million zones in a single trace? Add a lot of zones to your program and see how it handles it!" );
ImGui::TextWrapped( "Capturing a long-running profile trace is easy. Need to profile an hour of your program execution? You can do it." );
ImGui::TextWrapped( "Note that it doesn't make much sense to instrument every little function you might have. The cost of the instrumentation itself will be higher than the cost of the function in such a case." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Keep in mind that the more zones you have, the more memory and CPU time the profiler will use. Be careful not to run out of memory." );
ImGui::TextWrapped( "To capture 100 million zones, you will need approximately 4 GB of RAM." );
ImGui::PopStyleColor();
ImGui::PopFont();
} };
AchievementItem ai_100million = {
.id = "100million",
.name = "It's over 100 million!",
.text = Unpack( Text100Million )
};
AchievementItem ai_instrumentationStatistics = { "instrumentationStatistics", "Show me the stats!", [](){
ImGui::TextWrapped( "Once you have instrumented your application, you can view the statistics for each zone in the timeline. This allows you to see how much time is spent in each zone and how many times it is called." );
ImGui::TextWrapped( "To view the statistics, click on the \"" ICON_FA_ARROW_UP_WIDE_SHORT " Statistics\" button on the top bar. This will open a new window with a list of all zones in the trace." );
} };
AchievementItem ai_instrumentationStatistics = {
.id = "instrumentationStatistics",
.name = "Show me the stats!",
.text = Unpack( TextInstrumentationStatistics )
};
AchievementItem ai_findZone = { "findZone", "Find some zones", [](){
ImGui::TextWrapped( "You can search for zones in the trace by opening the search window with the \"" ICON_FA_MAGNIFYING_GLASS " Find zone\" button on the top bar. It will ask you for the zone name, which in most cases will be the function name in the code." );
ImGui::TextWrapped( "The search may find more than one zone with the same name. A list of all the zones found is displayed, and you can select any of them." );
ImGui::TextWrapped( "Alternatively, you can open the Statistics window and click an entry there. This will open the Find zone window as if you had searched for that zone." );
ImGui::TextWrapped( "When a zone is selected, a number of statistics are displayed to help you understand the performance of your application. In addition, a histogram of the zone execution times is displayed to make it easier for you to determine the performance of the profiled code. Be sure to select a zone with a large number of calls to make the histogram look interesting!" );
ImGui::TextWrapped( "Note that you can draw a range on the histogram to limit the number of entries displayed in the zone list below. This list allows you to examine each zone individually. There are also a number of zone groupings that you can select. Each group can be selected and the time associated with the selected group will be highlighted on the histogram." );
} };
AchievementItem ai_findZone = {
.id = "findZone",
.name = "Find some zones",
.text = Unpack( TextFindZone )
};
AchievementItem* ac_instrumentationIntroItems[] = {
&ai_100million,
@@ -55,90 +63,46 @@ AchievementItem* ac_instrumentationIntroItems[] = {
nullptr
};
AchievementItem ai_instrumentationIntro = { "instrumentationIntro", "Instrumentating your application", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
AchievementItem ai_instrumentationIntro = {
.id = "instrumentationIntro",
.name = "Instrumentating your application",
.text = Unpack( TextInstrumentationIntro ),
.items = ac_instrumentationIntroItems
};
void SomeFunction()
{
ZoneScoped;
// Your code here
}
)";
static SourceContents sc;
sc.Parse( src );
ImGui::TextWrapped( "Instrumentation is a powerful feature that allows you to see the exact runtime of each call to the selected set of functions. The downside is that it takes a bit of manual work to get it set up." );
ImGui::TextWrapped( "To get started, open a source file and include the Tracy.hpp header. This will give you access to a variety of macros provided by Tracy. Next, add the ZoneScoped macro to the beginning of one of your functions, like this:" );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "Now, when you profile your application, you will see a new zone appear on the timeline for each call to the function. This allows you to see how much time is spent in each call and how many times the function is called." );
ImGui::PushFont( g_fonts.normal, FontSmall );
ImGui::PushStyleColor( ImGuiCol_Text, GImGui->Style.Colors[ImGuiCol_TextDisabled] );
ImGui::TextWrapped( "Note: The ZoneScoped macro is just one of the many macros provided by Tracy. See the documentation for more information." );
ImGui::TextWrapped( "The above description applies to C++ code, but things are done similarly in other programming languages. Refer to the documentation for your language for more information." );
ImGui::PopStyleColor();
ImGui::PopFont();
}, ac_instrumentationIntroItems };
AchievementItem ai_frameImages = { "frameImages", "A picture is worth a thousand words", [](){
ImGui::TextWrapped( "Tracy allows you to add context to each frame, by attaching a screenshot. You can do this with the FrameImage macro." );
ImGui::TextWrapped( "You will have to do the screen capture and resizing yourself, which can be a bit complicated. The manual provides a sample code that shows how to do this in a performant way." );
ImGui::TextWrapped( "The frame images are displayed in the context of a frame, for example, when you hover over the frame in the timeline or in the frame graph at the top of the screen." );
ImGui::TextWrapped( "You can even view a recording of what your application was doing by clicking the " ICON_FA_SCREWDRIVER_WRENCH " icon and then selecting the \"" ICON_FA_PLAY " Playback\" option. Try it out!" );
ImGui::TextWrapped( "The FrameImage macro is a great way to see what happened in your application at a particular time. Maybe you have a performance problem that only occurs when a certain object is on the screen?" );
} };
AchievementItem ai_frameImages = {
.id = "frameImages",
.name = "A picture is worth a thousand words",
.text = Unpack( TextFrameImages )
};
AchievementItem* ac_instrumentFramesItems[] = {
&ai_frameImages,
nullptr
};
AchievementItem ai_instrumentFrames = { "instrumentFrames", "Instrumenting frames", [](){
constexpr const char* src = R"(#include "Tracy.hpp"
void Render()
{
// Render the frame
SwapBuffers();
FrameMark;
}
)";
static SourceContents sc;
sc.Parse( src );
ImGui::TextWrapped( "In addition to instrumenting functions, you can also instrument frames. This allows you to see how much time is spent in each frame of your application." );
ImGui::TextWrapped( "To instrument frames, you need to add the FrameMark macro at the beginning of each frame. This can be done in the main loop of your application, or in a separate function that is called at the beginning of each frame." );
ImGui::PushFont( g_fonts.mono, FontNormal );
PrintSource( sc.get() );
ImGui::PopFont();
ImGui::TextWrapped( "When you profile your application, you will see a new frame appear on the timeline each time the FrameMark macro is called. This allows you to see how much time is spent in each frame and how many frames are rendered per second." );
ImGui::TextWrapped( "The FrameMark macro is a great way to see at a glance how your application is performing over time. Maybe there are some performance problems that only appear after a few minutes of running the application? A frame graph is drawn at the top of the profiler window where you can see the timing of all frames." );
ImGui::TextWrapped( "Note that some applications do not have a frame-based structure, and in such cases, frame instrumentation may not be useful. That's ok." );
}, ac_instrumentFramesItems };
AchievementItem ai_instrumentFrames = {
.id = "instrumentFrames",
.name = "Instrumenting frames",
.text = Unpack( TextInstrumentFrames ),
.items = ac_instrumentFramesItems
};
AchievementItem* ac_instrumentationItems[] = { &ai_instrumentationIntro, &ai_instrumentFrames, nullptr };
AchievementCategory ac_instrumentation = { "instrumentation", "Instrumentation", ac_instrumentationItems };
AchievementItem ai_loadTrace = { "loadTrace", "Load a trace", [](){
ImGui::TextWrapped( "You can open a previously saved trace file (or one received from a friend) with the \"" ICON_FA_FOLDER_OPEN " Open saved trace\" button on the welcome screen." );
} };
AchievementItem ai_loadTrace = {
.id = "loadTrace",
.name = "Load a trace",
.text = Unpack( TextLoadTrace )
};
AchievementItem ai_saveTrace = { "saveTrace", "Save a trace", [](){
ImGui::TextWrapped( "Now that you have traced your application (or are in the process of doing so), you can save it to disk for future reference. You can do this by clicking on the " ICON_FA_WIFI " icon in the top left corner of the screen and then clicking on the \"" ICON_FA_FLOPPY_DISK " Save trace\" button." );
ImGui::TextWrapped( "Keeping old traces on hand can be beneficial, as you can compare the performance of your optimizations with what you had before." );
ImGui::TextWrapped( "You can also share the trace with your friends or co-workers by sending them the trace file." );
ImGui::Spacing();
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::SameLine();
ImGui::TextUnformatted( "Warning" );
ImGui::SameLine();
tracy::TextColoredUnformatted( 0xFF44FFFF, ICON_FA_TRIANGLE_EXCLAMATION );
ImGui::TextWrapped( "Trace files can contain sensitive information about your application, such as program code, or even the contents of source files. Be careful when sharing them with others." );
} };
AchievementItem ai_saveTrace = {
.id = "saveTrace",
.name = "Save a trace",
.text = Unpack( TextSaveTrace )
};
AchievementItem* ac_connectToServerItems[] = {
&ai_saveTrace,
@@ -152,23 +116,19 @@ AchievementItem* ac_connectToServerUnlock[] = {
nullptr
};
AchievementItem ai_connectToServer = { "connectToClient", "First profiling session", [](){
ImGui::TextWrapped( "Let's start our adventure by instrumenting your application and connecting it to the profiler. Here's a quick refresher:" );
ImGui::TextWrapped( " 1. Integrate Tracy Profiler into your application. This can be done using CMake, Meson, or simply by adding the source files to your project." );
ImGui::TextWrapped( " 2. Make sure that TracyClient.cpp (or the Tracy library) is included in your build." );
ImGui::TextWrapped( " 3. Define TRACY_ENABLE in your build configuration, for the whole application. Do not do it in a single source file because it won't work." );
ImGui::TextWrapped( " 4. Start your application, and \"" ICON_FA_WIFI " Connect\" to it with the profiler." );
ImGui::TextWrapped( "Please refer to the user manual for more details." );
if( ImGui::SmallButton( "Download the user manual" ) )
{
tracy::OpenWebpage( "https://github.com/wolfpld/tracy/releases" );
}
}, ac_connectToServerItems, ac_connectToServerUnlock };
AchievementItem ai_connectToServer = {
.id = "connectToClient",
.name = "First profiling session",
.text = Unpack( TextConnectToClient ),
.items = ac_connectToServerItems,
.unlocks = ac_connectToServerUnlock
};
AchievementItem ai_globalSettings = { "globalSettings", "Global settings", [](){
ImGui::TextWrapped( "Tracy has a variety of settings that can be adjusted to suit your needs. These settings can be found by clicking on the " ICON_FA_WRENCH " icon on the welcome screen. This will open the about window, where you can expand the \"" ICON_FA_TOOLBOX " Global settings\" menu." );
ImGui::TextWrapped( "The settings are saved between sessions, so you only need to set them once." );
} };
AchievementItem ai_globalSettings = {
.id = "globalSettings",
.name = "Global settings",
.text = Unpack( TextGlobalSettings )
};
AchievementItem* ac_achievementsIntroItems[] = {
&ai_connectToServer,
@@ -176,18 +136,14 @@ AchievementItem* ac_achievementsIntroItems[] = {
nullptr
};
AchievementItem ai_achievementsIntro = { "achievementsIntro", "Click here to discover achievements!", [](){
ImGui::TextWrapped( "Clicking on the " ICON_FA_STAR " button opens the Achievements List. Here you can see the tasks to be completed along with a short description of what needs to be done." );
ImGui::TextWrapped( "As you complete each Achievement, new Achievements will appear, so be sure to keep checking the list for new ones!" );
ImGui::TextWrapped( "To make the new things easier to spot, the Achievements List will show a marker next to them. The achievements " ICON_FA_STAR " button will glow yellow when there are new things to see." );
ImGui::TextUnformatted( "New tasks:" );
ImGui::SameLine();
TextColoredUnformatted( 0xFF4488FF, ICON_FA_CIRCLE_EXCLAMATION );
ImGui::TextUnformatted( "Completed tasks:" );
ImGui::SameLine();
TextColoredUnformatted( 0xFF44FF44, ICON_FA_CIRCLE_CHECK );
ImGui::TextWrapped( "Good luck!" );
}, ac_achievementsIntroItems, nullptr, true, 1 };
AchievementItem ai_achievementsIntro = {
.id = "achievementsIntro",
.name = "Click here to discover achievements!",
.text = Unpack( TextIntro ),
.items = ac_achievementsIntroItems,
.keepOpen = true,
.unlockTime = 1
};
AchievementItem* ac_firstStepsItems[] = { &ai_achievementsIntro, nullptr };
AchievementCategory ac_firstSteps = { "firstSteps", "First steps", ac_firstStepsItems, 1 };

View File

@@ -20,7 +20,7 @@ struct AchievementItem
{
const char* id;
const char* name;
void(*description)();
std::string text;
AchievementItem** items;
AchievementItem** unlocks;
bool keepOpen;

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