Compare commits
532 Commits
slomp/stra
...
v0.14.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
30997d5ca6 | ||
|
|
aa53e377e7 | ||
|
|
cae9bf0bef | ||
|
|
4d3a84a97e | ||
|
|
10926caed4 | ||
|
|
073152a10a | ||
|
|
2ec2c2c8ac | ||
|
|
dce2c710e5 | ||
|
|
e670bd18f7 | ||
|
|
1dfe828cdf | ||
|
|
23ffe89d24 | ||
|
|
f5938e0336 | ||
|
|
86ea59bbe3 | ||
|
|
56f5d1239d | ||
|
|
cc04578f68 | ||
|
|
39722e1e3f | ||
|
|
78a7553c6f | ||
|
|
cdd5940b44 | ||
|
|
246139fb04 | ||
|
|
5257132d97 | ||
|
|
db851737e0 | ||
|
|
73917aaef5 | ||
|
|
f0fce23bb6 | ||
|
|
ad7a1744fa | ||
|
|
62bffd085c | ||
|
|
f8a75703f9 | ||
|
|
e3cde055cd | ||
|
|
0e37b94e6c | ||
|
|
6b58f7d8d0 | ||
|
|
05f0508684 | ||
|
|
e2c05210e7 | ||
|
|
275fef576a | ||
|
|
2a3792aaa3 | ||
|
|
ad99139f8e | ||
|
|
fee9285d68 | ||
|
|
a4b51631ed | ||
|
|
433ac3629f | ||
|
|
16d9c15663 | ||
|
|
4e9121eb2f | ||
|
|
7f7bd69195 | ||
|
|
2a7542ccd8 | ||
|
|
328f50211f | ||
|
|
f5c53af367 | ||
|
|
b751c1c8d0 | ||
|
|
b6cfdd2d2e | ||
|
|
453d649c1a | ||
|
|
254f0ad210 | ||
|
|
3f9714059a | ||
|
|
8fdfd08af0 | ||
|
|
54ecee1f74 | ||
|
|
b10d466a38 | ||
|
|
86fc7f02cb | ||
|
|
16805b47ef | ||
|
|
756411d802 | ||
|
|
d579ed3cce | ||
|
|
edc09a4e97 | ||
|
|
821d43c6b5 | ||
|
|
16329e0a0c | ||
|
|
099df3de3d | ||
|
|
a7926283a8 | ||
|
|
6ce8ad7aff | ||
|
|
37a7c2c197 | ||
|
|
a8f9faf496 | ||
|
|
f57986e17d | ||
|
|
5f647e7eaf | ||
|
|
e06fc5211b | ||
|
|
01a9e6d3af | ||
|
|
c228c662bb | ||
|
|
30d14caab6 | ||
|
|
5d2c353335 | ||
|
|
92f0e0e951 | ||
|
|
7dafaca602 | ||
|
|
18eaa221d3 | ||
|
|
6bca1141fe | ||
|
|
a5d155250b | ||
|
|
63c94bae7d | ||
|
|
8163734842 | ||
|
|
c119f1c8de | ||
|
|
dcc86d343b | ||
|
|
507c77ba41 | ||
|
|
eacb64a714 | ||
|
|
bdb0f3c3d7 | ||
|
|
757c2fff30 | ||
|
|
a6d96d4201 | ||
|
|
1c2a8b4c40 | ||
|
|
6dd09e3ed8 | ||
|
|
96dcd7a38b | ||
|
|
0290f0ba54 | ||
|
|
1866cf25b9 | ||
|
|
d91769b72e | ||
|
|
d0ffd8dc86 | ||
|
|
5df9ac73c7 | ||
|
|
facbd3c0a7 | ||
|
|
df29943d78 | ||
|
|
9209bcdafd | ||
|
|
8c8d451a2d | ||
|
|
bfdf647114 | ||
|
|
0558301e49 | ||
|
|
71098f707c | ||
|
|
a1fb20e620 | ||
|
|
9e163ad6b9 | ||
|
|
93a8f86850 | ||
|
|
5698b45820 | ||
|
|
95811a658a | ||
|
|
37468811c7 | ||
|
|
a6352890f4 | ||
|
|
07b7219498 | ||
|
|
370a41225e | ||
|
|
1ad64f19a7 | ||
|
|
93af7f987a | ||
|
|
144fec96d3 | ||
|
|
3448832b72 | ||
|
|
c136232c97 | ||
|
|
7ee384630b | ||
|
|
5ef415619d | ||
|
|
8985887128 | ||
|
|
bf21d8ea11 | ||
|
|
bc85c4faa5 | ||
|
|
8fd29b1038 | ||
|
|
d46fe6fd9f | ||
|
|
613524247f | ||
|
|
a418ec6a03 | ||
|
|
00c2245c49 | ||
|
|
e4fab6af3a | ||
|
|
b418abd8de | ||
|
|
1673a305cf | ||
|
|
3baa2f0a09 | ||
|
|
46b21c0c75 | ||
|
|
a02513c1a2 | ||
|
|
ead931ef3c | ||
|
|
6b2abcb47a | ||
|
|
e0862e8534 | ||
|
|
ec0a1c0606 | ||
|
|
372b83d590 | ||
|
|
4779856174 | ||
|
|
c4b1308c21 | ||
|
|
488270c728 | ||
|
|
ff81feedca | ||
|
|
b677b352ee | ||
|
|
8b836a5d56 | ||
|
|
ee54c04d3b | ||
|
|
fa8bd1adf8 | ||
|
|
987d440d74 | ||
|
|
064d400cf1 | ||
|
|
d3af48990c | ||
|
|
36d93e27c4 | ||
|
|
3a65b27838 | ||
|
|
71c98c9dfb | ||
|
|
12fdc7d8d2 | ||
|
|
9481319d4c | ||
|
|
3c88115926 | ||
|
|
ed5f333f90 | ||
|
|
a1ef58efac | ||
|
|
24b2702977 | ||
|
|
9b3c6784fb | ||
|
|
26d61dfdba | ||
|
|
c2c1c4e2b7 | ||
|
|
a429242f0f | ||
|
|
2d5e397c9f | ||
|
|
b743c13695 | ||
|
|
52e31be7f4 | ||
|
|
f51b984edb | ||
|
|
8da27d6042 | ||
|
|
74b08cf5c6 | ||
|
|
b3de7f34fb | ||
|
|
dd8eff0de6 | ||
|
|
46c70fcbb8 | ||
|
|
97d6b5f9fa | ||
|
|
b68fe43335 | ||
|
|
5563461201 | ||
|
|
851b1f80f1 | ||
|
|
507f2ae8b5 | ||
|
|
941669c1c6 | ||
|
|
c880163f96 | ||
|
|
3293e263c6 | ||
|
|
6063e22bc7 | ||
|
|
8711f87cb5 | ||
|
|
34587e6b4c | ||
|
|
d5760a05ae | ||
|
|
bacfbccb97 | ||
|
|
2e9e0e2643 | ||
|
|
fc26cc0f79 | ||
|
|
849bc3ae8c | ||
|
|
67ae7d849f | ||
|
|
1dfcacd802 | ||
|
|
2e23c112bd | ||
|
|
d74d77226c | ||
|
|
8f52e0e5f5 | ||
|
|
9db60de03b | ||
|
|
e53c2849fa | ||
|
|
7c30fedaaa | ||
|
|
9314b8cbce | ||
|
|
ff3e31f564 | ||
|
|
ac159d6127 | ||
|
|
bcddc1d9f1 | ||
|
|
d231c2e95f | ||
|
|
b94a1e22c1 | ||
|
|
cf82fb9bb0 | ||
|
|
f30d32a908 | ||
|
|
b7eda34332 | ||
|
|
5036ec9128 | ||
|
|
59a8ae2d7d | ||
|
|
ea3d0b4d8d | ||
|
|
917c4baffb | ||
|
|
faab64018a | ||
|
|
fb296ba1a4 | ||
|
|
877efe4eda | ||
|
|
90c6ad80e1 | ||
|
|
67c4102bf3 | ||
|
|
c1fa33a875 | ||
|
|
1d6f43d92f | ||
|
|
c2b489a0ff | ||
|
|
7986dc18d0 | ||
|
|
f9413c268d | ||
|
|
6e23d220d7 | ||
|
|
158ab87bee | ||
|
|
d2cfc7b5d1 | ||
|
|
b0d75acdef | ||
|
|
c482f1e25d | ||
|
|
8221a80b47 | ||
|
|
afe249b6d3 | ||
|
|
72b1edc39d | ||
|
|
2247f1d14b | ||
|
|
b0be3a5776 | ||
|
|
dc261fe799 | ||
|
|
21f0d15f5f | ||
|
|
51f2b1da87 | ||
|
|
b32d454bcd | ||
|
|
fb4dcd4e96 | ||
|
|
d67cf385ff | ||
|
|
4ffc8b0dd2 | ||
|
|
5f2d14338e | ||
|
|
bfcec08e93 | ||
|
|
15bc3fce1a | ||
|
|
dc9dfb2e65 | ||
|
|
ef099b0555 | ||
|
|
956e082e5b | ||
|
|
be6377f81b | ||
|
|
249b76f175 | ||
|
|
6a916a6c9f | ||
|
|
0f56c8ac7e | ||
|
|
8a4876a071 | ||
|
|
7539daa602 | ||
|
|
ab46247a9c | ||
|
|
7bc32a1536 | ||
|
|
6858a664dc | ||
|
|
3eae7a6d65 | ||
|
|
64c0c5c85f | ||
|
|
74e1ddfbeb | ||
|
|
f472b92f13 | ||
|
|
6c8dd9fd93 | ||
|
|
8184964c2d | ||
|
|
eef8fac58b | ||
|
|
2d2487e2e0 | ||
|
|
562fea5564 | ||
|
|
3608d0c271 | ||
|
|
f08792f217 | ||
|
|
87b838f553 | ||
|
|
ed39de8025 | ||
|
|
84c6548c94 | ||
|
|
0b2e7dd13c | ||
|
|
eb12b62af6 | ||
|
|
91bababef7 | ||
|
|
f13bb0bf52 | ||
|
|
0caee2147c | ||
|
|
3e91ee15b1 | ||
|
|
f7c63ac40c | ||
|
|
3d85d0468e | ||
|
|
ff3c57c8d3 | ||
|
|
a501117392 | ||
|
|
916e62de17 | ||
|
|
88c491e0f9 | ||
|
|
1a6b7781bd | ||
|
|
71810c4265 | ||
|
|
15fecdbf79 | ||
|
|
c1e8c7bac9 | ||
|
|
03af70fd78 | ||
|
|
7933c00447 | ||
|
|
eb53d28358 | ||
|
|
2e0367cd1f | ||
|
|
c2fa2d4d7b | ||
|
|
f86105c600 | ||
|
|
2b9e7a53ff | ||
|
|
172f32d075 | ||
|
|
7dab948434 | ||
|
|
e49b96ec03 | ||
|
|
42fc8d8d8f | ||
|
|
ece976e820 | ||
|
|
a8d5f12f16 | ||
|
|
aacc82df3d | ||
|
|
3e1de56e15 | ||
|
|
859709a98a | ||
|
|
50c3c89c16 | ||
|
|
60ce6fb25e | ||
|
|
9ec0535e03 | ||
|
|
81ccf5149a | ||
|
|
7b36ab9793 | ||
|
|
2b49e91804 | ||
|
|
6bd6483181 | ||
|
|
22a5a5fb78 | ||
|
|
580a43c05f | ||
|
|
3b2d97ab21 | ||
|
|
b76468885b | ||
|
|
c35e0f07c5 | ||
|
|
7979404a8f | ||
|
|
7d1a37de2d | ||
|
|
0350df11f8 | ||
|
|
59013f2a8c | ||
|
|
a208c3c665 | ||
|
|
655b8295cf | ||
|
|
e20c716b80 | ||
|
|
ac4defb4f3 | ||
|
|
c6f5cf50fb | ||
|
|
96a91da63a | ||
|
|
e0929e9cd8 | ||
|
|
05f620ab97 | ||
|
|
e40ce11ba9 | ||
|
|
66e5b81804 | ||
|
|
d565afde82 | ||
|
|
bd8246cba0 | ||
|
|
2205d7972f | ||
|
|
e842bf223f | ||
|
|
6495aab062 | ||
|
|
8555514d59 | ||
|
|
3e0ded59a5 | ||
|
|
ba8e4bdcbf | ||
|
|
9bbbec42f4 | ||
|
|
69fead1c16 | ||
|
|
df463fcbfe | ||
|
|
467ba60cdf | ||
|
|
cbbfd13fa2 | ||
|
|
16a148f0be | ||
|
|
9131f3600b | ||
|
|
b2d5d9bc52 | ||
|
|
ec884b2d60 | ||
|
|
81cb4acbe6 | ||
|
|
f2256cf260 | ||
|
|
ae83b00951 | ||
|
|
d2c69868d1 | ||
|
|
18cfcd7e90 | ||
|
|
71d88fd198 | ||
|
|
17bbc4049a | ||
|
|
2b0b7293a1 | ||
|
|
07cb1030e7 | ||
|
|
af8dc30912 | ||
|
|
8f2bbc6116 | ||
|
|
f01607714e | ||
|
|
533e1af389 | ||
|
|
25139748b2 | ||
|
|
853fff614e | ||
|
|
5be36d732b | ||
|
|
836bf9d2eb | ||
|
|
db383f738b | ||
|
|
a8c642f42b | ||
|
|
641bd09b6f | ||
|
|
280af2e7bd | ||
|
|
83298a3459 | ||
|
|
21f9c00e9f | ||
|
|
b0a094bb42 | ||
|
|
70a09f5663 | ||
|
|
467be67807 | ||
|
|
80476da597 | ||
|
|
d94d8f11ac | ||
|
|
da495be8de | ||
|
|
fef901f595 | ||
|
|
74650af7a0 | ||
|
|
e740bc652c | ||
|
|
7da72e51d2 | ||
|
|
ac5682bc75 | ||
|
|
570a06dced | ||
|
|
00c079cec9 | ||
|
|
80a1772d0b | ||
|
|
f65949cc26 | ||
|
|
b40ef0ff29 | ||
|
|
aca0e39190 | ||
|
|
ba9388cd9f | ||
|
|
1f2190a44a | ||
|
|
b11fa7caa2 | ||
|
|
ed1a4b06b6 | ||
|
|
d34659f57b | ||
|
|
a67a29e9cd | ||
|
|
dc4b51e522 | ||
|
|
0a0cd27db4 | ||
|
|
472c84ac78 | ||
|
|
bde9e50c84 | ||
|
|
dba0b6263e | ||
|
|
27f4e00aea | ||
|
|
6d6a4beda3 | ||
|
|
bbb43549a6 | ||
|
|
83a703640c | ||
|
|
7b4a128080 | ||
|
|
547891f044 | ||
|
|
e4acd2db77 | ||
|
|
e7bd441ebb | ||
|
|
4cd6c38932 | ||
|
|
331881ca36 | ||
|
|
9aa48a62cb | ||
|
|
50982fe7a3 | ||
|
|
549226751e | ||
|
|
6921fbfdf8 | ||
|
|
90d4463ef0 | ||
|
|
323659d4f7 | ||
|
|
279f7ba0df | ||
|
|
0b120ba84f | ||
|
|
04a8ba4c2b | ||
|
|
576d03e414 | ||
|
|
88702dad39 | ||
|
|
30572e4810 | ||
|
|
57c143c9b7 | ||
|
|
2221138299 | ||
|
|
4bc37ccb4d | ||
|
|
e9ad49a846 | ||
|
|
64a43ef3d3 | ||
|
|
8aae924d51 | ||
|
|
9370d7a379 | ||
|
|
e10c865fbf | ||
|
|
bd2106db68 | ||
|
|
99c13e53bf | ||
|
|
7fbe1d1345 | ||
|
|
5745b489a2 | ||
|
|
7e2e1829c9 | ||
|
|
1ca38e2b70 | ||
|
|
1a420d0f5e | ||
|
|
7eae48c1a8 | ||
|
|
1c712e41d9 | ||
|
|
a03e5e8968 | ||
|
|
3e5310cac6 | ||
|
|
606cd7bdaf | ||
|
|
d924b05ab7 | ||
|
|
d0c7596977 | ||
|
|
0bee59ff85 | ||
|
|
963a968a9e | ||
|
|
f700597067 | ||
|
|
a237b009ec | ||
|
|
5970e90233 | ||
|
|
5fc4ae8eef | ||
|
|
a35f8a738d | ||
|
|
3714e36652 | ||
|
|
565f3e5a72 | ||
|
|
17720a0cd8 | ||
|
|
a7b0907d6c | ||
|
|
552159ccf6 | ||
|
|
7e8180a2a7 | ||
|
|
05d34e4daa | ||
|
|
72d45dfdad | ||
|
|
280475ff3d | ||
|
|
922604fe6a | ||
|
|
5b29550ded | ||
|
|
56eb3b776f | ||
|
|
98ad778495 | ||
|
|
fa4c28af8a | ||
|
|
a73a733644 | ||
|
|
d4cceb3e0d | ||
|
|
b715c0c32e | ||
|
|
f71620c8c8 | ||
|
|
51e467d7b9 | ||
|
|
eba8c1e99a | ||
|
|
9a785976c0 | ||
|
|
711e3a7bcf | ||
|
|
d84a085b71 | ||
|
|
ae38665b95 | ||
|
|
800e415400 | ||
|
|
f1dfedffaf | ||
|
|
67bb927d61 | ||
|
|
415197bd5d | ||
|
|
5177e587e9 | ||
|
|
ee7d2b4fd8 | ||
|
|
1d0806c374 | ||
|
|
9cc8f3752e | ||
|
|
79e0efac96 | ||
|
|
8531e879a9 | ||
|
|
89a75d5c71 | ||
|
|
e9e85ca6ee | ||
|
|
3a6a23ca98 | ||
|
|
3157dfae3e | ||
|
|
978b720759 | ||
|
|
77c655eb7d | ||
|
|
bee6ac566e | ||
|
|
6e4041b14d | ||
|
|
1706ac57ac | ||
|
|
72d62bea25 | ||
|
|
0ae37172dc | ||
|
|
d1373d3f4c | ||
|
|
9502ab728d | ||
|
|
7dd4dbc8a5 | ||
|
|
96c681cf61 | ||
|
|
2052aa2ab8 | ||
|
|
977218ea04 | ||
|
|
fcd5944d5e | ||
|
|
34395f97ed | ||
|
|
c897729b74 | ||
|
|
f8208ce5ef | ||
|
|
3516c96afd | ||
|
|
3bb5027565 | ||
|
|
eefc7aee4b | ||
|
|
3ccda1d3c4 | ||
|
|
802e88585a | ||
|
|
74bbe5988b | ||
|
|
066cb89f75 | ||
|
|
8c289104d6 | ||
|
|
274e453bb6 | ||
|
|
d89055908b | ||
|
|
801a34ea66 | ||
|
|
0cdb6d365e | ||
|
|
e3f4c72c85 | ||
|
|
eeab1bf1a9 | ||
|
|
d26176baae | ||
|
|
021aa6a9e6 | ||
|
|
8b86ada40e | ||
|
|
616f33ff65 | ||
|
|
023cb20ba9 | ||
|
|
073cd266ac | ||
|
|
2e252d3988 | ||
|
|
77978b68ca | ||
|
|
d9939362f5 | ||
|
|
bba05bd3ad | ||
|
|
83a4a13cbd | ||
|
|
9e9aabe9a1 | ||
|
|
382a887ce9 | ||
|
|
fbd1c55151 | ||
|
|
398bab8041 | ||
|
|
69245e751b | ||
|
|
22abcd272f | ||
|
|
32b712c120 | ||
|
|
704b5c4030 | ||
|
|
dd6962b42c | ||
|
|
d571f2bd59 | ||
|
|
781938317d | ||
|
|
16cfd25320 | ||
|
|
f9365abe4f | ||
|
|
3f91f35d59 | ||
|
|
1de94aa856 |
4
.github/actions/test-tracy/action.yml
vendored
@@ -24,8 +24,8 @@ runs:
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
|
||||
# same with TRACY_DELAYED_INIT and TRACY_MANUAL_LIFETIME
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_DELAYED_INIT=ON -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
|
||||
# same with TRACY_MANUAL_LIFETIME
|
||||
cmake -B tests/tracy/build -S tests/tracy -DCMAKE_BUILD_TYPE=Release -DTRACY_MANUAL_LIFETIME=ON ${{ inputs.extra_cmake_flags }}
|
||||
cmake --build tests/tracy/build --parallel
|
||||
cmake -E rm -rf tests/tracy/build
|
||||
|
||||
|
||||
49
.github/workflows/android.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
name: android
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: android-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-android:
|
||||
runs-on: ubuntu-latest
|
||||
container: archlinux:base-devel
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed cmake git ninja nodejs unzip
|
||||
- name: Setup NDK
|
||||
uses: nttld/setup-ndk@v1
|
||||
id: setup-ndk
|
||||
with:
|
||||
ndk-version: r29
|
||||
- run: echo "ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }}" >> $GITHUB_ENV
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Library v7
|
||||
run: |
|
||||
cmake -B build-v7 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=armeabi-v7a
|
||||
cmake --build build-v7
|
||||
- name: Library v8
|
||||
run: |
|
||||
cmake -B build-v8 -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON -DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a
|
||||
cmake --build build-v8
|
||||
55
.github/workflows/appimage.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: appimage
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: appimage-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-appimage:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential cmake ninja-build meson pkg-config git ca-certificates \
|
||||
curl file desktop-file-utils nodejs patchelf \
|
||||
libegl-dev libxkbcommon-dev libdbus-1-dev libssl-dev \
|
||||
libffi-dev libexpat1-dev
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Cache wayland
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: profiler/build-appimage/wayland
|
||||
key: ${{ runner.os }}-wayland-${{ hashFiles('profiler/appimage/build.sh') }}
|
||||
- name: Build AppImage
|
||||
run: profiler/appimage/build.sh
|
||||
env:
|
||||
GIT_REV: ${{ github.sha }}
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: appimage
|
||||
path: profiler/build-appimage/tracy-profiler-x86_64.AppImage
|
||||
7
.github/workflows/emscripten.yml
vendored
@@ -7,6 +7,13 @@ on:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: emscripten-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
|
||||
7
.github/workflows/latex.yml
vendored
@@ -8,6 +8,13 @@ on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: latex-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-manual:
|
||||
|
||||
|
||||
74
.github/workflows/linux-cli.yml
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
name: linux-cli
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: linux-cli-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-linux-cli:
|
||||
runs-on: ubuntu-latest
|
||||
container: ubuntu:24.04
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
|
||||
build-essential cmake git ca-certificates curl nodejs
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build update/build --parallel
|
||||
- name: Build capture
|
||||
run: |
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build capture/build --parallel
|
||||
- name: Build csvexport
|
||||
run: |
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build csvexport/build --parallel
|
||||
- name: Build import
|
||||
run: |
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build import/build --parallel
|
||||
- name: Build merge
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DNO_ISA_EXTENSIONS=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel
|
||||
- name: Stage release files
|
||||
run: |
|
||||
mkdir -p linux-cli
|
||||
cp update/build/tracy-update linux-cli/
|
||||
cp capture/build/tracy-capture linux-cli/
|
||||
cp capture/build/tracy-capture-daemon linux-cli/
|
||||
cp csvexport/build/tracy-csvexport linux-cli/
|
||||
cp import/build/tracy-import-chrome linux-cli/
|
||||
cp import/build/tracy-import-fuchsia linux-cli/
|
||||
cp merge/build/tracy-merge linux-cli/
|
||||
strip linux-cli/tracy-*
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: linux-cli
|
||||
path: linux-cli
|
||||
44
.github/workflows/linux.yml
vendored
@@ -7,6 +7,13 @@ on:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: linux-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
@@ -16,7 +23,7 @@ jobs:
|
||||
container: archlinux:base-devel
|
||||
steps:
|
||||
- name: Install dependencies
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua
|
||||
run: pacman -Syu --noconfirm && pacman -S --noconfirm --needed freetype2 debuginfod wayland dbus libxkbcommon libglvnd meson cmake git wayland-protocols nodejs lua clang sdl3_image python-jinja
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
@@ -26,39 +33,49 @@ jobs:
|
||||
path: ${{ env.CPM_SOURCE_CACHE }}
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Determine build parallelism
|
||||
run: |
|
||||
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
|
||||
echo "CMAKE_PARALLEL=--parallel 2" >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "CMAKE_PARALLEL=--parallel" >> "$GITHUB_ENV"
|
||||
fi
|
||||
- name: Dyna example / clang
|
||||
run: |
|
||||
CC=clang CXX=clang++ cmake -B examples/dyna/build-clang -S examples/dyna -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build examples/dyna/build-clang $CMAKE_PARALLEL
|
||||
- name: Dyna example / gcc
|
||||
run: |
|
||||
CC=gcc CXX=g++ cmake -B examples/dyna/build-gcc -S examples/dyna -DCMAKE_BUILD_TYPE=Release
|
||||
cmake --build examples/dyna/build-gcc $CMAKE_PARALLEL
|
||||
- name: Profiler GUI
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
if [ "${ACT:-}" != "true" ] && [ "${FORGEJO_ACTIONS:-}" != "true" ]; then
|
||||
cmake --build profiler/build
|
||||
else
|
||||
cmake --build profiler/build --parallel 2
|
||||
fi
|
||||
cmake --build profiler/build $CMAKE_PARALLEL
|
||||
- name: Update utility
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build update/build --parallel
|
||||
cmake --build update/build $CMAKE_PARALLEL
|
||||
- name: Capture utility
|
||||
run: |
|
||||
cmake -B capture/build -S capture -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build capture/build --parallel
|
||||
cmake --build capture/build $CMAKE_PARALLEL
|
||||
- name: Csvexport utility
|
||||
run: |
|
||||
cmake -B csvexport/build -S csvexport -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build csvexport/build --parallel
|
||||
cmake --build csvexport/build $CMAKE_PARALLEL
|
||||
- name: Import utilities
|
||||
run: |
|
||||
cmake -B import/build -S import -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build import/build --parallel
|
||||
cmake --build import/build $CMAKE_PARALLEL
|
||||
- name: Merge utility
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel
|
||||
cmake --build merge/build $CMAKE_PARALLEL
|
||||
- name: Library (cmake)
|
||||
run: |
|
||||
cmake -B build -DCMAKE_BUILD_TYPE=Release -DTRACY_ENABLE=ON
|
||||
cmake --build build
|
||||
cmake --install build
|
||||
cmake --build build $CMAKE_PARALLEL
|
||||
env:
|
||||
CMAKE_INSTALL_PREFIX: ${{ github.workspace }}/bin
|
||||
- name: Library (meson)
|
||||
@@ -74,6 +91,7 @@ jobs:
|
||||
cp profiler/build/tracy-profiler bin
|
||||
cp update/build/tracy-update bin
|
||||
cp capture/build/tracy-capture bin
|
||||
cp capture/build/tracy-capture-daemon bin
|
||||
cp csvexport/build/tracy-csvexport bin
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
|
||||
27
.github/workflows/macos.yml
vendored
@@ -6,13 +6,21 @@ on:
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: macos-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
jobs:
|
||||
build-macos:
|
||||
runs-on: macos-15
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Cache CPM packages
|
||||
@@ -22,12 +30,16 @@ jobs:
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- name: Install dependencies
|
||||
run: brew install pkg-config glfw meson
|
||||
run: brew install pkg-config meson
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- name: Build library
|
||||
run: meson setup -Dtracy_enable=true build && meson compile -C build
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Build profiler
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DBUNDLE=ON -DGIT_REV=${{ github.sha }}
|
||||
cmake --build profiler/build --parallel 2 --config Release
|
||||
- name: Build update
|
||||
run: |
|
||||
@@ -49,20 +61,19 @@ jobs:
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel --config Release
|
||||
- name: Build library
|
||||
run: meson setup -Dprefix=$GITHUB_WORKSPACE/bin/lib -Dtracy_enable=true build && meson compile -C build && meson install -C build
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir -p bin
|
||||
cp profiler/build/tracy-profiler bin
|
||||
cp -r profiler/build/tracy-profiler.app bin
|
||||
cp update/build/tracy-update bin
|
||||
cp capture/build/tracy-capture bin
|
||||
cp capture/build/tracy-capture-daemon bin
|
||||
cp csvexport/build/tracy-csvexport bin
|
||||
cp import/build/tracy-import-chrome bin
|
||||
cp import/build/tracy-import-fuchsia bin
|
||||
cp merge/build/tracy-merge bin
|
||||
strip bin/tracy-profiler.app/Contents/MacOS/tracy-profiler
|
||||
strip bin/tracy-update bin/tracy-capture bin/tracy-capture-daemon bin/tracy-csvexport bin/tracy-import-chrome bin/tracy-import-fuchsia bin/tracy-merge
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: macos
|
||||
|
||||
7
.github/workflows/mingw.yml
vendored
@@ -7,6 +7,13 @@ on:
|
||||
branches: [ master ]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: mingw-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
|
||||
40
.github/workflows/release.yml
vendored
@@ -4,26 +4,62 @@ on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-${{ github.event.release.tag_name }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
release-build-windows:
|
||||
uses: ./.github/workflows/windows.yml
|
||||
|
||||
release-build-macos:
|
||||
uses: ./.github/workflows/macos.yml
|
||||
|
||||
release-build-manual:
|
||||
uses: ./.github/workflows/latex.yml
|
||||
|
||||
release-build-appimage:
|
||||
uses: ./.github/workflows/appimage.yml
|
||||
|
||||
release-build-linux-cli:
|
||||
uses: ./.github/workflows/linux-cli.yml
|
||||
|
||||
attach-to-release:
|
||||
needs: [release-build-windows, release-build-manual]
|
||||
needs: [release-build-windows, release-build-macos, release-build-manual, release-build-appimage, release-build-linux-cli]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Create versioned zip
|
||||
- name: Create versioned Windows zip
|
||||
run: |
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
cd windows
|
||||
zip -r ../windows-$VERSION_NO_V.zip .
|
||||
- name: Create versioned macOS zip
|
||||
run: |
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
cd macos
|
||||
chmod +x tracy-* tracy-profiler.app/Contents/MacOS/*
|
||||
zip -r ../macos-$VERSION_NO_V.zip .
|
||||
- name: Create versioned Linux zip
|
||||
run: |
|
||||
VERSION="${{ github.event.release.tag_name }}"
|
||||
VERSION_NO_V="${VERSION#v}"
|
||||
mkdir linux
|
||||
cp appimage/* linux/
|
||||
cp linux-cli/* linux/
|
||||
cd linux
|
||||
zip -r ../linux-$VERSION_NO_V.zip .
|
||||
- uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
windows-*.zip
|
||||
macos-*.zip
|
||||
linux-*.zip
|
||||
manual/tracy.pdf
|
||||
|
||||
16
.github/workflows/windows.yml
vendored
@@ -8,6 +8,13 @@ on:
|
||||
workflow_dispatch:
|
||||
workflow_call:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: windows-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm-cache
|
||||
|
||||
@@ -23,12 +30,10 @@ jobs:
|
||||
key: ${{ runner.os }}-cpm-${{ hashFiles('**/vendor.cmake', '**/CMakeLists.txt') }}
|
||||
restore-keys: ${{ runner.os }}-cpm-
|
||||
- uses: microsoft/setup-msbuild@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.x'
|
||||
- run: pip install meson ninja
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Build profiler
|
||||
run: |
|
||||
cmake -B profiler/build -S profiler -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
@@ -53,14 +58,13 @@ jobs:
|
||||
run: |
|
||||
cmake -B merge/build -S merge -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
cmake --build merge/build --parallel --config Release
|
||||
- name: Test application
|
||||
uses: ./.github/actions/test-tracy
|
||||
- name: Package artifacts
|
||||
run: |
|
||||
mkdir bin
|
||||
copy profiler\build\Release\tracy-profiler.exe bin
|
||||
copy update\build\Release\tracy-update.exe bin
|
||||
copy capture\build\Release\tracy-capture.exe bin
|
||||
copy capture\build\Release\tracy-capture-daemon.exe bin
|
||||
copy csvexport\build\Release\tracy-csvexport.exe bin
|
||||
copy import\build\Release\tracy-import-chrome.exe bin
|
||||
copy import\build\Release\tracy-import-fuchsia.exe bin
|
||||
|
||||
13
.gitignore
vendored
@@ -1,16 +1,12 @@
|
||||
.vs
|
||||
_build
|
||||
_compiler
|
||||
tools/*
|
||||
*.d
|
||||
*.o
|
||||
*.so
|
||||
*.swp
|
||||
*.obj
|
||||
imgui.ini
|
||||
test/tracy_test
|
||||
test/tracy_test.exe
|
||||
*/build/unix/*-*
|
||||
manual/t*.aux
|
||||
manual/t*.log
|
||||
manual/t*.out
|
||||
@@ -21,19 +17,16 @@ manual/t*.bbl
|
||||
manual/t*.blg
|
||||
manual/t*.fdb_latexmk
|
||||
manual/t*.fls
|
||||
profiler/build/win32/packages
|
||||
profiler/build/win32/Tracy.aps
|
||||
profiler/build-appimage
|
||||
.deps/
|
||||
.dirstamp
|
||||
/_*/**
|
||||
/**/__pycache__/**
|
||||
extra/vswhere.exe
|
||||
extra/tracy-build
|
||||
.cache
|
||||
.uv-cache/
|
||||
.venv/
|
||||
compile_commands.json
|
||||
profiler/build/wasm/Tracy-release.*
|
||||
profiler/build/wasm/Tracy-debug.*
|
||||
profiler/build/wasm/embed.tracy
|
||||
examples/ToyPathTracer/Windows/TestCpu
|
||||
examples/ToyPathTracer/Windows/x64
|
||||
*.user
|
||||
|
||||
340
.omp/skills/tracy-gui-verify/SKILL.md
Normal file
@@ -0,0 +1,340 @@
|
||||
---
|
||||
name: tracy-gui-verify
|
||||
description: Build, serve, and drive the Tracy Profiler's emscripten (web) GUI in a headless browser with verified mouse/keyboard semantics. Use when developing or testing the profiler GUI. No X11/Wayland/DISPLAY required — the browser is the display server.
|
||||
---
|
||||
|
||||
# Test the Tracy web GUI in a browser
|
||||
|
||||
Build the emscripten profiler GUI into an instrumented temporary workspace,
|
||||
serve it, and drive it with the browser tool. The harness exports
|
||||
(`debug_snapshot`/`debug_clear`) expose the app-side mouse/ID state — they
|
||||
are the ground truth for targeting and verification, not screenshots.
|
||||
|
||||
Applies to this repo's emscripten build with ImGui 1.92.9b-docking and
|
||||
Emscripten 5.0.7. Constants marked M are workstation-dependent — re-verify
|
||||
them. Everything else is stable; do not re-derive it.
|
||||
|
||||
## Setup
|
||||
|
||||
```sh
|
||||
sh <this-skill-dir>/setup.sh # workspace=/tmp/tracy-protocol
|
||||
# or: TRACY_WEB_WS=/elsewhere TRACY_WEB_REPO=<repo> sh setup.sh
|
||||
```
|
||||
|
||||
`<this-skill-dir>` is the project-scoped `.omp/skills/tracy-gui-verify/`
|
||||
under the active project root — not a fixed home directory. If multiple
|
||||
copies exist (one per project that has used this skill), use the copy
|
||||
inside the repo being tested; `TRACY_WEB_REPO` controls which repo is
|
||||
copied into the workspace.
|
||||
|
||||
The script: fresh source copy (excludes `.git` and build dirs) → applies
|
||||
`harness.patch` (adds `debug_snapshot`/`debug_clear` exports + event/frame
|
||||
recording to the emscripten backend, in the COPY only) → configures → builds
|
||||
Release with the emsdk at `$EMSDK_DIR` (default `~/emsdk`) and the CPM cache at
|
||||
`~/.cache/cpm`.
|
||||
|
||||
**Pitfall:** if the workspace is deleted while a `tracy-web` server is running,
|
||||
stop the hub process first — a stale server keeps the *deleted* cwd and silently
|
||||
returns empty responses while the port still accepts connections.
|
||||
|
||||
**Pitfall:** a `tracy-web` that died in an earlier session leaves the daemon in
|
||||
`failed` state: `stop` only reports the old failure, and `start` keeps returning
|
||||
"Daemon tracy-web has unacknowledged completion notifications" while the port
|
||||
stays free. Drain the daemon with `hub op=logs name=tracy-web` (the log also
|
||||
shows the cause), then relaunch with `hub op=restart name=tracy-web` (retained
|
||||
spec). If the port is NOT free, a stale process owns it — often one serving an
|
||||
earlier (now-stale) build directory: the new `httpd.py` dies on the bind and
|
||||
your requests silently hit the old directory. Check `ss -tlnp | grep 8000` and
|
||||
kill the owner. After any start/restart, verify the server is actually
|
||||
serving: `curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:8000/index.html`
|
||||
must return 200 — the daemon message is not proof of a server.
|
||||
|
||||
**After editing the GUI in the repo:** copy the changed files into
|
||||
`$WS/src/` (same relative path), re-run `cmake --build $WS/build -j$(nproc)`,
|
||||
reload the page. If a change touches `profiler/src/BackendEmscripten.cpp` or
|
||||
`profiler/CMakeLists.txt`, the harness patch may no longer apply — re-apply it
|
||||
manually (keep the `Dbg*` harness section, the `DbgPushEvent`/`DbgPushFrame`
|
||||
calls in the mouse callbacks + `NewFrame`, and the two `debug_*` exports in
|
||||
`-sEXPORTED_FUNCTIONS`).
|
||||
|
||||
## Serve + open + ready
|
||||
|
||||
Server (long-running → `hub`):
|
||||
|
||||
```
|
||||
hub op=start name=tracy-web application=python3 args=["$WS/build/httpd.py"]
|
||||
cwd=$WS/build ready={port:8000} persist=true
|
||||
```
|
||||
|
||||
`httpd.py` sends the COOP/COEP headers required for pthreads/SharedArrayBuffer —
|
||||
use it, not a bare http.server.
|
||||
|
||||
Browser:
|
||||
|
||||
```
|
||||
browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
|
||||
```
|
||||
|
||||
**Ready signal** (in a `run` cell; `run` code executes in Node scope — page JS
|
||||
goes through `tab.evaluate`):
|
||||
|
||||
```js
|
||||
let title = null;
|
||||
for (let i = 0; i < 360; i++) {
|
||||
title = await tab.evaluate(() => document.title);
|
||||
if (/ - Tracy Profiler/.test(title)) break;
|
||||
await new Promise(r => setTimeout(r, 250));
|
||||
}
|
||||
```
|
||||
|
||||
Title is `Tracy Profiler X.Y.Z` until the preloaded `embed.tracy` (DarkRL
|
||||
capture) finishes loading, then `<trace> (embed.tracy) - Tracy Profiler X.Y.Z`
|
||||
(`profiler/src/main.cpp` `SetWindowTitleCallback`). Wait ~1.5 s after, then
|
||||
confirm the app loop lives: `snap().frames.at(-1).tick > 0`.
|
||||
|
||||
## Harness API (ground truth)
|
||||
|
||||
```js
|
||||
const snap = () => tab.evaluate(() => JSON.parse(Module.ccall('debug_snapshot','string',[],[])));
|
||||
const clear = () => tab.evaluate(() => Module.ccall('debug_clear','void',[],[]));
|
||||
```
|
||||
|
||||
`snap()` → `{ dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH,
|
||||
title, cursor, frames:[…64], events:[…64] }` (newest last).
|
||||
|
||||
- `frames[]`: one sample **per rAF tick, including idle ticks**. `tick` = main-loop
|
||||
count (always advances), `f` = `ImGui::GetFrameCount()` (advances only when a
|
||||
frame actually renders — the idle gate, §Idle), `mx/my` = app mouse position,
|
||||
`md[3]` = mouse button states, `q` = pending input events, `wa` =
|
||||
`tracy::s_wasActive` at tick start, `hi`/`ai` = ImGui `HoveredId`/`ActiveId`.
|
||||
- `events[]`: raw callback data as the app received it. `type` 0=move 1=down
|
||||
2=up 3=enter 4=leave 5=wheel; `tx/ty` = Emscripten int `targetX/Y`; `ax/ay` =
|
||||
position passed to ImGui (`-1` for non-move); `t` = DOMHighResTimeStamp.
|
||||
|
||||
## Coordinate mapping
|
||||
|
||||
```
|
||||
appX = floor( trunc(cssX - rectL) * dpr ) // device px, what ImGui hit-tests
|
||||
cssX = appX / dpr // inverse for targeting
|
||||
screenshot_px = cssX * dpr
|
||||
```
|
||||
|
||||
Chain: DOM `clientX` → Emscripten `targetX = int(clientX - (rect.left|0))`
|
||||
(`src/lib/libhtml5.js`, `EmscriptenMouseEvent.targetX` is an `int`) → Tracy
|
||||
`AddMousePosEvent(targetX * dpr, …)` (`BackendEmscripten.cpp`) → ImGui `ImFloor`
|
||||
(`imgui.cpp` `AddMousePosEvent`). Canvas fills the viewport, so `rectL/rectT`
|
||||
are 0 (re-read anyway).
|
||||
|
||||
**M: `dpr` on this workstation's headless browser is 1.25** (canvas buffer
|
||||
2000×1125 for a 1600×900 viewport; screenshots are captured at device px).
|
||||
Never assume 1. App positions come in multiples of `dpr`; verify targeting
|
||||
within ±1 px via `frames[].mx/my`.
|
||||
|
||||
## Click protocol
|
||||
|
||||
Click = move, press, refresh through the hold, release, verify — all
|
||||
against rendered frames. The idle gate (§Idle) renders only 3 frames per
|
||||
wake, and a hold with no mouse events at all refreshes nothing, so a
|
||||
fixed-wait down/up from a cold idle can be swallowed (both events received,
|
||||
zero frames rendered) or released after the wake budget expires. Issuing
|
||||
mouse moves while the button is down keeps the budget alive: a same-coord
|
||||
re-move is delivered (verified: it reaches the canvas handler, wakes the
|
||||
app, and a same-coord hold click straddles cleanly) and is the safest
|
||||
refresh — zero hover drift; a ±`dx` px wiggle works too, but must stay
|
||||
inside the hit area:
|
||||
|
||||
```js
|
||||
async function click(cssX, cssY, holdMs = 400) {
|
||||
await page.mouse.move(cssX, cssY); // 1. ALWAYS move first
|
||||
await new Promise(r => setTimeout(r, 150)); // 2. >= 1 rendered frame
|
||||
const pre = await snap();
|
||||
const pf = pre.frames.at(-1);
|
||||
await clear();
|
||||
await page.mouse.down(); // 3. press
|
||||
let df = null, hiDuring = null;
|
||||
const t0 = Date.now();
|
||||
while (Date.now() - t0 < holdMs) {
|
||||
await new Promise(r => setTimeout(r, 70));
|
||||
await page.mouse.move(cssX, cssY); // 4. same-coord re-move refreshes the wake budget
|
||||
const f = (await snap()).frames.at(-1);
|
||||
if (f.md[0] === 1) { df ??= f.f; hiDuring = f.hi; }
|
||||
}
|
||||
await page.mouse.up(); // 5. release
|
||||
let uf = null;
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await new Promise(r => setTimeout(r, 40));
|
||||
const f = (await snap()).frames.at(-1);
|
||||
if (f.md[0] === 0) { uf = f.f; break; }
|
||||
}
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
const s = await snap();
|
||||
const d = s.dpr, ex = Math.floor(Math.trunc(cssX - s.rectL) * d), ey = Math.floor(Math.trunc(cssY - s.rectT) * d);
|
||||
return { hoverID: pf.hi, hiDuring, appPos: [pf.mx, pf.my], expected: [ex, ey],
|
||||
straddle: { df, uf },
|
||||
ok: df !== null && uf !== null && uf > df && hiDuring === pf.hi &&
|
||||
Math.abs(pf.mx - ex) <= 1 && Math.abs(pf.my - ey) <= 1 };
|
||||
}
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
1. **Move before every click.** The emscripten mousemove handler is the only
|
||||
thing that sets the app mouse position; mousedown/mouseup only queue button
|
||||
state. Fresh page position is invalid (`-FLT_MAX`) — a click without a prior
|
||||
move activates nothing.
|
||||
2. **Press and release must each render** in separate frames with the item
|
||||
hovered (ImGui's `PressedOnClickRelease` — doc table at
|
||||
`imgui_widgets.cpp:489-497`; input trickling,
|
||||
`ConfigInputTrickleEventQueue`, defers a same-frame release). The
|
||||
same-coord refresh is what guarantees rendered frames across the hold
|
||||
from a cold idle.
|
||||
3. **Verify every click:** `ok: true` (position landed, press and release each
|
||||
rendered, hover held — `hiDuring === hoverID`) plus a screenshot diff.
|
||||
Non-ImGui targets (timeline zone bars) have `hi = 0` by design — for those
|
||||
verify by screenshot, not hover.
|
||||
4. **Double-click trap:** two same-position clicks <300 ms apart = double-click
|
||||
(`MouseDoubleClickTime = 0.3 s`) → zoom/thread-info actions fire. Wait
|
||||
≥350 ms between repeated clicks at one spot.
|
||||
5. **Drag:** move → down → (move + wait 70 ms)×n → up. **Wheel:**
|
||||
`page.mouse.wheel({ deltaY: ±500 })` after positioning. **Keys:**
|
||||
`page.keyboard.type(..., { delay: 50 })` after focusing the input by click;
|
||||
`page.keyboard.press('Enter')`.
|
||||
6. **Danger:** toolbar device x 10–32 (css 8–26) = red power button (leftmost
|
||||
toolbar icon, hover ID 3313137574), closes the trace view (back to "Get
|
||||
started"). Never click it unless intended.
|
||||
|
||||
## Idle and power saving
|
||||
|
||||
`main.cpp DrawContents` keeps an `activeFrames = 3` budget refreshed on any
|
||||
input event (`tracy::s_wasActive`), a connected client, view animation, or a
|
||||
queued input; exhausted → `sleep 16 ms` with **no `ImGui::NewFrame` and no GL
|
||||
draw** (canvas keeps the last frame). Expect ~97 % of ticks to skip rendering
|
||||
while idle.
|
||||
|
||||
- Clicks from a cold idle land only via the click protocol's hold refresh
|
||||
(§Click protocol) — a fixed-wait down/up can be swallowed by the idle gate
|
||||
(both events received, zero frames rendered). **Before interacting, assert
|
||||
`tick`/`f` is advancing**; a stalled main loop (initial trace parse, hidden
|
||||
tab) renders nothing and drops everything. During the initial load the
|
||||
cursor style is `wait`.
|
||||
- `focusLostLimit` (reduce render rate on focus loss) is desktop-only; it does
|
||||
not exist in the emscripten path.
|
||||
- `canvas.style.cursor` is **not** a hover oracle: this ImGui build sets the
|
||||
Hand cursor only in `TextLink`, not on buttons.
|
||||
|
||||
## Targeting
|
||||
|
||||
Screenshots never resolve targeting. Full-page shots arrive downscaled JPEG
|
||||
(1024 px wide for a 2000-px device buffer; the factor varies), and
|
||||
`page.screenshot({ clip })` returns a PNG at device scale — a W×H CSS clip
|
||||
returns W·dpr × H·dpr px (CSS = clip offset + px/dpr). Icon buttons are
|
||||
15–25 device px wide, so visual estimates carry ±20 px error, enough to hit
|
||||
the neighboring button or a gap. Target from the app's own hit-testing —
|
||||
sweep the row and click the run's center:
|
||||
|
||||
```js
|
||||
async function sweepRow(dpr, cssY, devFrom, devTo, step = 4) {
|
||||
const ranges = []; let cur = null;
|
||||
for (let dev = devFrom; dev <= devTo; dev += step) {
|
||||
await page.mouse.move(dev / dpr, cssY);
|
||||
await new Promise(r => setTimeout(r, 50));
|
||||
const s = await snap();
|
||||
const hi = s.frames[s.frames.length - 1].hi;
|
||||
if (cur && hi === cur.id) cur.end = dev;
|
||||
else { if (cur) ranges.push(cur); cur = { id: hi, start: dev, end: dev }; }
|
||||
}
|
||||
ranges.push(cur);
|
||||
return ranges.filter(r => r.id !== 0 && r.end - r.start >= 4);
|
||||
}
|
||||
```
|
||||
|
||||
- Same non-zero `hi` across consecutive steps = one widget; `hi = 0` = gap.
|
||||
Click the run's center: `click((r.start + r.end) / 2 / dpr, cssY)`.
|
||||
- **Layout is state-dependent:** docked panels (Statistics, Flame, …) resize the
|
||||
main window and shift the toolbar — **re-sweep after any state change.**
|
||||
- **Close popups before sweeping:** while a popup is open, hovers outside the
|
||||
popup can read `hi = 0` even over live widgets (verified with ZoomPopup over
|
||||
the toolbar). A % click in ZoomPopup does not close the popup — click an
|
||||
empty area first.
|
||||
- Hover IDs are stable across sessions (label hashes) — the map below holds for
|
||||
the default state (fresh load, no panels, 1600×900, dpr 1.25).
|
||||
- Zoomed region shots (for correlating IDs with icons): `page.screenshot({clip})`
|
||||
returns a Buffer — write it with Node `fs`, then `read` the file:
|
||||
|
||||
```js
|
||||
const fs = require('fs');
|
||||
const buf = await page.screenshot({ clip: { x: 0, y: 0, width: 720, height: 28 } });
|
||||
fs.writeFileSync('/tmp/toolbar-zoom.png', buf);
|
||||
```
|
||||
|
||||
### Toolbar map — default state (device px; css = device/1.25; row y css 15)
|
||||
|
||||
| device x | hover ID | function |
|
||||
|---|---|---|
|
||||
| 10–32 | 3313137574 | ⏻ power (red, leftmost) — **closes the trace view (DANGER)** |
|
||||
| 44–68 | 608241489 | ⚙ Options gear — opens the Options window (clickable; do not confuse with the power button) |
|
||||
| 81–185 | 2246346107 | 💬 Messages panel toggle |
|
||||
| 199–259 | 3177774389 | 🔍 Find zone toggle (search input hover ID 3637961452, css ~x 100–440, y ~90) |
|
||||
| 271–367 | 1419322276 | 📊 Statistics toggle |
|
||||
| 383–451 | 322727105 | 🔥 Flame graph toggle |
|
||||
| 463–551 | 37993378 | 💾 Memory graph toggle |
|
||||
| 567–667 | 2133895361 | ⚖ Compare toggle |
|
||||
| 679–735 | 1808024465 | ⌗ Info (trace information) toggle |
|
||||
| 751–775 | 2535445525 | 🛠 wrench → ToolsPopup (Playback, CPU data, Annotations, Limits, Wait stacks, Frame statistics) |
|
||||
| 791–811 | 3101356487 | 📖 book → User manual toggle |
|
||||
| 827–851 | 2335673581 | 🔍+ → ZoomPopup (50–300 % user scale) |
|
||||
| 864–888 | 1263603982 | ◀ previous frame — focuses the timeline on the previous frame (view range shrinks to the frame's timespan) |
|
||||
| ~890–1010 | 0 | frame set name + count text ("Frames: N") — LMB jumps to a specified frame (custom hit-test, like timeline zones) |
|
||||
| 1012–1036 | 2142681495 | ▶ next frame — focuses the timeline on the next frame (verified: view range 25.82 s → 203.38 ms) |
|
||||
| 1052–1072 | 2961929287 | ▼ frame set selection (switch the active frame set) |
|
||||
|
||||
Button names and functions: the user manual, *Control menu* (`manual/tracy.md`). The
|
||||
web build omits *Connection* (live capture only) and *Tracy Assist* (desktop only).
|
||||
|
||||
## User scale (DPI zoom)
|
||||
|
||||
The 🔍+ button (hover ID 2335673581) — *Display scale* in the user manual —
|
||||
opens a ZoomPopup with 50–300 % steps.
|
||||
In the web build this scales the UI **layout only**: fonts,
|
||||
`Style::ScaleAllSizes`, and window sizes (`main.cpp SetupDPIScale`,
|
||||
`scale = devicePixelRatio × userScale`). The canvas buffer and the mouse→app
|
||||
mapping stay at `devicePixelRatio` (`BackendEmscripten.cpp`) — hit regions
|
||||
become physically larger in the same coordinate space, targeting precision is
|
||||
unchanged, and the standard click protocol works as-is.
|
||||
|
||||
Use it when small buttons are hard to hit or details are hard to read in
|
||||
screenshots. At 200 % every toolbar hit region is roughly 2× (verified: power
|
||||
16→40 px, gear 25→50 px, Messages 105→190 px wide; row height 24→46 device
|
||||
px). The layout **reflows completely** at any scale — re-sweep after changing
|
||||
it and after setting it back. `userScale` is session-only unless Options →
|
||||
"Save UI scale" is on; a fresh page load starts at 100 %.
|
||||
|
||||
## Limitations
|
||||
|
||||
- A *connected* live-streaming client cannot be tested in the web build (the
|
||||
browser GUI cannot open a TCP server). The embedded-trace GUI is the
|
||||
live-app proxy: real trace data, full GUI.
|
||||
- `embed.tracy` (10 MB) is downloaded from share.nereid.pl at configure time —
|
||||
network needed once per fresh workspace; the file then lives in `$WS/build`.
|
||||
|
||||
## References
|
||||
|
||||
Sources for the behavior the rules above encode — consult them when re-verifying
|
||||
a constant or a rule fails:
|
||||
|
||||
- `profiler/src/BackendEmscripten.cpp` — emscripten input handlers, canvas/dpr
|
||||
sizing, cursor mapping.
|
||||
- `profiler/src/main.cpp:540-574` — idle gate (`activeFrames` budget).
|
||||
- `profiler/src/profiler/TracyMouse.cpp` — per-frame click state cache.
|
||||
- `profiler/CMakeLists.txt:334-353` — emscripten link options.
|
||||
- ImGui 1.92.9b (vendored copy in the CPM cache, `~/.cache/cpm/imgui/`):
|
||||
`imgui_widgets.cpp:483-545` (click semantics table), `imgui.cpp:11278-11403`
|
||||
(event trickling), `imgui.cpp:1736` (trickle default on), `imgui.cpp:1992-2016`
|
||||
(AddMousePosEvent floor/dedup).
|
||||
- Emscripten 5.0.7: `src/lib/libhtml5.js` (`fillMouseEventData`,
|
||||
`targetX = e.clientX - (rect.left|0)` into HEAP32), `system/include/emscripten/html5.h`
|
||||
(`EmscriptenMouseEvent.targetX` is `int`; `EmscriptenWheelEvent` embeds a
|
||||
`mouse` sub-struct).
|
||||
- Upstream: imgui issue #4921 (new input event API — events spread over
|
||||
multiple frames), PR #2525 (same-frame click/release), issue #1992 (rapid
|
||||
down/up "soft clicks" dropped).
|
||||
221
.omp/skills/tracy-gui-verify/harness.patch
Normal file
@@ -0,0 +1,221 @@
|
||||
diff -r a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt
|
||||
--- a/profiler/CMakeLists.txt
|
||||
+++ b/profiler/CMakeLists.txt
|
||||
@@ -340,7 +340,7 @@
|
||||
-sMAXIMUM_MEMORY=4gb
|
||||
-sSTACK_SIZE=1048576
|
||||
-sPTHREAD_POOL_SIZE=8
|
||||
- -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard
|
||||
+ -sEXPORTED_FUNCTIONS=_main,_nativeOpenFile,_tracy_paste_clipboard,_debug_snapshot,_debug_clear
|
||||
-sEXPORTED_RUNTIME_METHODS=ccall
|
||||
-sENVIRONMENT=web,worker
|
||||
--preload-file embed.tracy
|
||||
diff -r a/profiler/src/BackendEmscripten.cpp b/profiler/src/BackendEmscripten.cpp
|
||||
--- a/profiler/src/BackendEmscripten.cpp
|
||||
+++ b/profiler/src/BackendEmscripten.cpp
|
||||
@@ -1,6 +1,8 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
+#include <cstdarg>
|
||||
+#include <algorithm>
|
||||
#include <EGL/egl.h>
|
||||
#include <EGL/eglext.h>
|
||||
#include <GLES2/gl2.h>
|
||||
@@ -10,7 +12,146 @@
|
||||
#include "Backend.hpp"
|
||||
#include "RunQueue.hpp"
|
||||
#include "profiler/TracyImGui.hpp"
|
||||
+#include "imgui_internal.h"
|
||||
|
||||
+// ================= DEBUG HARNESS (temporary copy, not for upstream) =================
|
||||
+struct DbgEvent
|
||||
+{
|
||||
+ int type; // 0=move 1=down 2=up 3=enter 4=leave 5=wheel
|
||||
+ int button;
|
||||
+ int targetX;
|
||||
+ int targetY;
|
||||
+ double appX; // what was passed to ImGui (targetX * dpr), -1 if n/a
|
||||
+ double appY;
|
||||
+ double t; // DOMHighResTimeStamp
|
||||
+ int wheelX;
|
||||
+ int wheelY;
|
||||
+};
|
||||
+struct DbgFrame
|
||||
+{
|
||||
+ double t;
|
||||
+ int tick; // emscripten main-loop tick count (rAF)
|
||||
+ int imguiFrame; // ImGui::GetFrameCount()
|
||||
+ float mx, my; // io.MousePos
|
||||
+ int md0, md1, md2;
|
||||
+ int queue; // pending ImGui input events
|
||||
+ int wasActive; // tracy::s_wasActive at tick start
|
||||
+ unsigned hi; // ImGui hovered item ID
|
||||
+ unsigned ai; // ImGui active item ID
|
||||
+};
|
||||
+static DbgEvent s_dbgEvents[512];
|
||||
+static int s_dbgEventHead = 0, s_dbgEventN = 0;
|
||||
+static DbgFrame s_dbgFrames[512];
|
||||
+static int s_dbgFrameHead = 0, s_dbgFrameN = 0;
|
||||
+static int s_dbgTick = 0;
|
||||
+
|
||||
+static void DbgPushEvent( int type, int button, int tx, int ty, double ax, double ay, double t, int wx = 0, int wy = 0 )
|
||||
+{
|
||||
+ auto& e = s_dbgEvents[s_dbgEventHead];
|
||||
+ e.type = type; e.button = button; e.targetX = tx; e.targetY = ty;
|
||||
+ e.appX = ax; e.appY = ay; e.t = t; e.wheelX = wx; e.wheelY = wy;
|
||||
+ s_dbgEventHead = ( s_dbgEventHead + 1 ) % 512;
|
||||
+ if ( s_dbgEventN < 512 ) s_dbgEventN++;
|
||||
+}
|
||||
+
|
||||
+static void DbgPushFrame()
|
||||
+{
|
||||
+ auto& f = s_dbgFrames[s_dbgFrameHead];
|
||||
+ auto& io = ImGui::GetIO();
|
||||
+ f.t = EM_ASM_DOUBLE( { return performance.now(); } );
|
||||
+ f.tick = s_dbgTick;
|
||||
+ f.imguiFrame = ImGui::GetFrameCount();
|
||||
+ f.mx = io.MousePos.x; f.my = io.MousePos.y;
|
||||
+ f.md0 = io.MouseDown[0]; f.md1 = io.MouseDown[1]; f.md2 = io.MouseDown[2];
|
||||
+ auto ctx = ImGui::GetCurrentContext();
|
||||
+ f.queue = ctx ? int( ctx->InputEventsQueue.Size ) : -1;
|
||||
+ f.wasActive = tracy::s_wasActive;
|
||||
+ f.hi = ctx ? ctx->HoveredId : 0;
|
||||
+ f.ai = ctx ? ctx->ActiveId : 0;
|
||||
+ s_dbgFrameHead = ( s_dbgFrameHead + 1 ) % 512;
|
||||
+ if ( s_dbgFrameN < 512 ) s_dbgFrameN++;
|
||||
+}
|
||||
+
|
||||
+extern "C" EMSCRIPTEN_KEEPALIVE void debug_clear()
|
||||
+{
|
||||
+ s_dbgEventHead = s_dbgEventN = s_dbgFrameHead = s_dbgFrameN = 0;
|
||||
+}
|
||||
+
|
||||
+// Escape a C string for embedding in a JSON string literal (", \, control chars).
|
||||
+static void JsonEscape( char* out, size_t outsz, const char* in )
|
||||
+{
|
||||
+ size_t o = 0;
|
||||
+ for( const unsigned char* p = ( const unsigned char* )in; *p; p++ )
|
||||
+ {
|
||||
+ if ( *p == '"' || *p == '\\' )
|
||||
+ {
|
||||
+ if ( o + 2 >= outsz ) break;
|
||||
+ out[o++] = '\\';
|
||||
+ out[o++] = char( *p );
|
||||
+ }
|
||||
+ else if ( *p < 0x20 )
|
||||
+ {
|
||||
+ if ( o + 6 >= outsz ) break;
|
||||
+ o += size_t( snprintf( out + o, outsz - o, "\\u%04x", *p ) );
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ if ( o + 1 >= outsz ) break;
|
||||
+ out[o++] = char( *p );
|
||||
+ }
|
||||
+ }
|
||||
+ out[o] = 0;
|
||||
+}
|
||||
+
|
||||
+extern "C" EMSCRIPTEN_KEEPALIVE const char* debug_snapshot()
|
||||
+{
|
||||
+ static char buf[512 * 1024];
|
||||
+ size_t off = 0;
|
||||
+ auto put = [ & ]( const char* fmt, ... )
|
||||
+ {
|
||||
+ if ( off + 1 >= sizeof( buf ) ) return;
|
||||
+ va_list args;
|
||||
+ va_start( args, fmt );
|
||||
+ int n = vsnprintf( buf + off, sizeof( buf ) - off - 1, fmt, args );
|
||||
+ va_end( args );
|
||||
+ if ( n > 0 ) off = std::min( off + size_t( n ), sizeof( buf ) - 1 );
|
||||
+ };
|
||||
+ const auto dpr = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
|
||||
+ const auto innerW = EM_ASM_INT( { return window.innerWidth; } );
|
||||
+ const auto innerH = EM_ASM_INT( { return window.innerHeight; } );
|
||||
+ const auto bufW = EM_ASM_INT( { return document.getElementById( 'canvas' ).width; } );
|
||||
+ const auto bufH = EM_ASM_INT( { return document.getElementById( 'canvas' ).height; } );
|
||||
+ const auto rectL = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.left; } );
|
||||
+ const auto rectT = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.top; } );
|
||||
+ const auto rectW = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.width; } );
|
||||
+ const auto rectH = EM_ASM_DOUBLE( { var r = document.getElementById( 'canvas' ).getBoundingClientRect(); return r.height; } );
|
||||
+ char title[256];
|
||||
+ EM_ASM( { stringToUTF8( document.title, $0, 256 ); }, title );
|
||||
+ char cursor[64];
|
||||
+ EM_ASM( { stringToUTF8( document.getElementById( 'canvas' ).style.cursor || "", $0, 64 ); }, cursor );
|
||||
+ char title_e[1536];
|
||||
+ char cursor_e[384];
|
||||
+ JsonEscape( title_e, sizeof( title_e ), title );
|
||||
+ JsonEscape( cursor_e, sizeof( cursor_e ), cursor );
|
||||
+ put( "{\"dpr\":%.6f,\"innerW\":%d,\"innerH\":%d,\"bufW\":%d,\"bufH\":%d,\"rectL\":%.3f,\"rectT\":%.3f,\"rectW\":%.3f,\"rectH\":%.3f,\"title\":\"%s\",\"cursor\":\"%s\",\"frames\":[", dpr, innerW, innerH, bufW, bufH, rectL, rectT, rectW, rectH, title_e, cursor_e );
|
||||
+ const int fn = std::min( s_dbgFrameN, 64 );
|
||||
+ for ( int i = 0; i < fn; i++ )
|
||||
+ {
|
||||
+ auto& f = s_dbgFrames[ ( s_dbgFrameHead - fn + i + 512 ) % 512 ];
|
||||
+ put( "%s{\"t\":%.3f,\"tick\":%d,\"f\":%d,\"mx\":%.1f,\"my\":%.1f,\"md\":[%d,%d,%d],\"q\":%d,\"wa\":%d,\"hi\":%u,\"ai\":%u}", i ? "," : "", f.t, f.tick, f.imguiFrame, f.mx, f.my, f.md0, f.md1, f.md2, f.queue, f.wasActive, f.hi, f.ai );
|
||||
+ }
|
||||
+ put( "],\"events\":[");
|
||||
+ const int en = std::min( s_dbgEventN, 64 );
|
||||
+ for ( int i = 0; i < en; i++ )
|
||||
+ {
|
||||
+ auto& e = s_dbgEvents[ ( s_dbgEventHead - en + i + 512 ) % 512 ];
|
||||
+ put( "%s{\"type\":%d,\"btn\":%d,\"tx\":%d,\"ty\":%d,\"ax\":%.3f,\"ay\":%.3f,\"t\":%.3f,\"wx\":%d,\"wy\":%d}", i ? "," : "", e.type, e.button, e.targetX, e.targetY, e.appX, e.appY, e.t, e.wheelX, e.wheelY );
|
||||
+ }
|
||||
+ put( "]}");
|
||||
+ buf[off] = 0;
|
||||
+ return buf;
|
||||
+}
|
||||
+
|
||||
static std::function<void()> s_redraw;
|
||||
static std::function<void(float)> s_scaleChanged;
|
||||
static std::function<int(void)> s_isBusy;
|
||||
@@ -223,32 +364,38 @@
|
||||
emscripten_set_mousedown_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, true );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 1, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
emscripten_set_mouseup_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddMouseButtonEvent( e->button == 0 ? 0 : 3 - e->button, false );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 2, e->button, e->targetX, e->targetY, -1, -1, e->timestamp );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
emscripten_set_mousemove_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
const auto scale = EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
|
||||
ImGui::GetIO().AddMousePosEvent( e->targetX * scale, e->targetY * scale );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 0, -1, e->targetX, e->targetY, e->targetX * scale, e->targetY * scale, e->timestamp );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
- emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
|
||||
+ emscripten_set_mouseleave_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddFocusEvent( false );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 4, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
- emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent*, void* ) -> EM_BOOL {
|
||||
+ emscripten_set_mouseenter_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenMouseEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddFocusEvent( true );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 3, -1, e->targetX, e->targetY, -1, -1, e->timestamp );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
emscripten_set_wheel_callback( "#canvas", nullptr, EM_TRUE, []( int, const EmscriptenWheelEvent* e, void* ) -> EM_BOOL {
|
||||
ImGui::GetIO().AddMouseWheelEvent( e->deltaX * -0.05, e->deltaY * -0.05 );
|
||||
tracy::s_wasActive = true;
|
||||
+ DbgPushEvent( 5, -1, e->mouse.targetX, e->mouse.targetY, -1, -1, e->mouse.timestamp, int( e->deltaX ), int( e->deltaY ) );
|
||||
return EM_TRUE;
|
||||
} );
|
||||
emscripten_set_keydown_callback( EMSCRIPTEN_EVENT_TARGET_WINDOW, nullptr, EM_TRUE, [] ( int, const EmscriptenKeyboardEvent* e, void* ) -> EM_BOOL {
|
||||
@@ -298,6 +445,8 @@
|
||||
|
||||
void Backend::NewFrame( int& w, int& h )
|
||||
{
|
||||
+ s_dbgTick++;
|
||||
+ DbgPushFrame();
|
||||
const auto scale = GetDpiScale();
|
||||
if( scale != s_prevScale )
|
||||
{
|
||||
102
.omp/skills/tracy-gui-verify/setup.sh
Executable file
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
# setup.sh - build the instrumented Tracy web GUI workspace (see SKILL.md).
|
||||
#
|
||||
# Usage:
|
||||
# sh setup.sh
|
||||
# TRACY_WEB_WS=/custom/ws TRACY_WEB_REPO=<repo> sh setup.sh
|
||||
#
|
||||
# Idempotent: reuses an existing source copy and build dir, skips
|
||||
# already-applied harness patch hunks, rebuilds.
|
||||
set -e
|
||||
|
||||
WS="${TRACY_WEB_WS:-/tmp/tracy-protocol}"
|
||||
# The skill is project-scoped: <repo>/.omp/skills/tracy-gui-verify/
|
||||
SKILL_DIR=$(cd "$(dirname "$0")" && pwd)
|
||||
if [ -n "$TRACY_WEB_REPO" ]; then
|
||||
REPO="$TRACY_WEB_REPO"
|
||||
else
|
||||
REPO=$(cd "$SKILL_DIR/../../.." 2>/dev/null && pwd) || REPO=""
|
||||
fi
|
||||
EMSDK="${EMSDK_DIR:-$HOME/emsdk}"
|
||||
|
||||
[ -x "$EMSDK/emsdk" ] || { echo "error: emsdk not found at $EMSDK (set EMSDK_DIR)" >&2; exit 1; }
|
||||
[ -f "$REPO/profiler/CMakeLists.txt" ] || { echo "error: not a Tracy repo: '$REPO' (set TRACY_WEB_REPO)" >&2; exit 1; }
|
||||
|
||||
. "$EMSDK/emsdk_env.sh" >/dev/null 2>&1 || true
|
||||
command -v emcc >/dev/null || { echo "error: emcc not on PATH after emsdk_env.sh" >&2; exit 1; }
|
||||
|
||||
if [ ! -f "$WS/src/profiler/CMakeLists.txt" ]; then
|
||||
echo "[setup] fresh source copy -> $WS/src (from $REPO)"
|
||||
mkdir -p "$WS/src"
|
||||
# -C applies to both archive creation and extraction; exclude all build dirs
|
||||
tar -C "$REPO" \
|
||||
--exclude=.git \
|
||||
--exclude='*/build*' \
|
||||
-cf - . | tar -xf - -C "$WS/src"
|
||||
else
|
||||
echo "[setup] reusing source copy in $WS/src (copy changed repo files into it first)"
|
||||
fi
|
||||
|
||||
# The harness is fully in place iff each patch hunk's signature line is present.
|
||||
# patch --forward skips a whole file once its first hunk is detected as already
|
||||
# applied, so a failed later hunk is invisible in patch's output and exit code —
|
||||
# verify the content instead of trusting either.
|
||||
harness_complete()
|
||||
{
|
||||
grep -q '_debug_snapshot' "$WS/src/profiler/CMakeLists.txt" \
|
||||
&& grep -qF 'EMSCRIPTEN_KEEPALIVE const char* debug_snapshot' "$WS/src/profiler/src/BackendEmscripten.cpp" \
|
||||
&& grep -qF 'DbgPushEvent( 1,' "$WS/src/profiler/src/BackendEmscripten.cpp" \
|
||||
&& grep -qF 's_dbgTick++;' "$WS/src/profiler/src/BackendEmscripten.cpp"
|
||||
}
|
||||
|
||||
echo "[setup] applying instrumentation harness"
|
||||
if harness_complete; then
|
||||
echo "[setup] harness already applied"
|
||||
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
|
||||
else
|
||||
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
|
||||
patch -p1 -d "$WS/src" --forward --no-backup-if-mismatch < "$SKILL_DIR/harness.patch" \
|
||||
|| {
|
||||
if harness_complete; then
|
||||
# mixed run: some hunks already present (skipped), the rest applied
|
||||
rm -f "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej"
|
||||
else
|
||||
rej=$(ls "$WS/src/profiler/CMakeLists.txt.rej" "$WS/src/profiler/src/BackendEmscripten.cpp.rej" 2>/dev/null || true)
|
||||
if [ -n "$rej" ]; then
|
||||
kept="The unapplied hunks are kept in:
|
||||
$(printf '%s\n' $rej | sed 's/^/ /')"
|
||||
else
|
||||
kept="No .rej files were produced (fatal patch error); the patch output above is the only evidence."
|
||||
fi
|
||||
cat >&2 <<EOF
|
||||
error: harness patch did not fully apply — $WS/src has drifted from the repo.
|
||||
$kept
|
||||
Do not build from this workspace. Either re-apply the harness by hand
|
||||
(SKILL.md, "After editing the GUI in the repo"), or start fresh:
|
||||
rm -rf $WS && sh <this-skill-dir>/setup.sh
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
fi
|
||||
|
||||
if [ ! -f "$WS/build/CMakeCache.txt" ]; then
|
||||
echo "[setup] configuring"
|
||||
cmake -S "$WS/src/profiler" -B "$WS/build" \
|
||||
-DCMAKE_TOOLCHAIN_FILE="$EMSDK/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake" \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCPM_SOURCE_CACHE="$HOME/.cache/cpm"
|
||||
fi
|
||||
|
||||
echo "[setup] building (first full build ~3-5 min, incremental ~30 s)"
|
||||
cmake --build "$WS/build" -j "$(nproc)"
|
||||
|
||||
cat <<EOF
|
||||
[setup] done.
|
||||
workspace : $WS
|
||||
repo : $REPO
|
||||
emcc : $(emcc --version | sed -n 1p)
|
||||
serve : hub op=start name=tracy-web application=python3 args=[$WS/build/httpd.py] cwd=$WS/build ready={port:8000} persist=true
|
||||
open : browser open url=http://127.0.0.1:8000/index.html viewport={width:1600,height:900}
|
||||
ready : document.title matches / - Tracy Profiler/ (see SKILL.md)
|
||||
EOF
|
||||
@@ -121,12 +121,12 @@ set_option(TRACY_NO_CODE_TRANSFER "Disable collection of source code" OFF TracyC
|
||||
set_option(TRACY_NO_CONTEXT_SWITCH "Disable capture of context switches" OFF TracyClient)
|
||||
set_option(TRACY_NO_EXIT "Client executable does not exit until all profile data is sent to server" OFF TracyClient)
|
||||
set_option(TRACY_NO_SAMPLING "Disable call stack sampling" OFF TracyClient)
|
||||
set_option(TRACY_NO_WAIT_STACKS "Disable capture of context switch call stacks" OFF TracyClient)
|
||||
set_option(TRACY_NO_VERIFY "Disable zone validation for C API" OFF TracyClient)
|
||||
set_option(TRACY_NO_VSYNC_CAPTURE "Disable capture of hardware Vsync events" OFF TracyClient)
|
||||
set_option(TRACY_NO_FRAME_IMAGE "Disable the frame image support and its thread" OFF TracyClient)
|
||||
set_option(TRACY_NO_SYSTEM_TRACING "Disable systrace sampling" OFF TracyClient)
|
||||
set_option(TRACY_PATCHABLE_NOPSLEDS "Enable nopsleds for efficient patching by system-level tools (e.g. rr)" OFF TracyClient)
|
||||
set_option(TRACY_DELAYED_INIT "Enable delayed initialization of the library (init on first call)" OFF TracyClient)
|
||||
set_option(TRACY_MANUAL_LIFETIME "Enable the manual lifetime management of the profile" OFF TracyClient)
|
||||
set_option(TRACY_FIBERS "Enable fibers support" OFF TracyClient)
|
||||
set_option(TRACY_NO_CRASH_HANDLER "Disable crash handling" OFF TracyClient)
|
||||
@@ -136,7 +136,7 @@ set_option(TRACY_LIBUNWIND_BACKTRACE "Use libunwind backtracing where supported"
|
||||
set_option(TRACY_SYMBOL_OFFLINE_RESOLVE "Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution" OFF TracyClient)
|
||||
set_option(TRACY_LIBBACKTRACE_ELF_DYNLOAD_SUPPORT "Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation" OFF TracyClient)
|
||||
set_option(TRACY_DEBUGINFOD "Enable debuginfod support" OFF TracyClient)
|
||||
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation" OFF TracyClient)
|
||||
set_option(TRACY_IGNORE_MEMORY_FAULTS "Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address" OFF TracyClient)
|
||||
set_option(TRACY_OPENGL_AUTO_CALIBRATION "Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)" OFF TracyClient)
|
||||
|
||||
# advanced
|
||||
@@ -152,9 +152,6 @@ if(rocprofiler-sdk_FOUND)
|
||||
endif()
|
||||
|
||||
# handle incompatible combinations
|
||||
if(TRACY_MANUAL_LIFETIME AND NOT TRACY_DELAYED_INIT)
|
||||
message(FATAL_ERROR "TRACY_MANUAL_LIFETIME can not be activated with disabled TRACY_DELAYED_INIT")
|
||||
endif()
|
||||
if(TRACY_DISALLOW_HW_TIMER AND NOT TRACY_TIMER_FALLBACK)
|
||||
message(FATAL_ERROR "TRACY_DISALLOW_HW_TIMER can not be activated with disabled TRACY_TIMER_FALLBACK")
|
||||
endif()
|
||||
@@ -182,7 +179,8 @@ set(tracy_includes
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyMetal.hmm
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyOpenCL.hpp
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyOpenGL.hpp
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp)
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyVulkan.hpp
|
||||
${TRACY_PUBLIC_DIR}/tracy/TracyWebGPU.hpp)
|
||||
|
||||
set(client_includes
|
||||
${TRACY_PUBLIC_DIR}/client/tracy_concurrentqueue.h
|
||||
@@ -213,15 +211,19 @@ set(common_includes
|
||||
${TRACY_PUBLIC_DIR}/common/TracyAlign.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyAlloc.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyApi.h
|
||||
${TRACY_PUBLIC_DIR}/common/TracyAssert.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyColor.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyForceInline.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyFormat.h
|
||||
${TRACY_PUBLIC_DIR}/common/TracyMutex.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyProtocol.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyQueue.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracySocket.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyStackFrames.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyString.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracySystem.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyTaggedUserlandAddress.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyVersion.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyWinFamily.hpp
|
||||
${TRACY_PUBLIC_DIR}/common/TracyYield.hpp)
|
||||
|
||||
@@ -274,6 +276,11 @@ configure_package_config_file(${CMAKE_CURRENT_SOURCE_DIR}/Config.cmake.in
|
||||
INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/TracyConfig.cmake
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
write_basic_package_version_file(
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
|
||||
COMPATIBILITY SameMinorVersion)
|
||||
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/TracyConfigVersion.cmake"
|
||||
DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
||||
|
||||
option(TRACY_CLIENT_PYTHON "Whether to build Tracy python client library" OFF)
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
@PACKAGE_INIT@
|
||||
|
||||
set(TRACY_VERSION_STRING "@TRACY_VERSION_STRING@")
|
||||
|
||||
include(CMakeFindDependencyMacro)
|
||||
find_dependency(Threads REQUIRED)
|
||||
|
||||
|
||||
156
NEWS
@@ -2,16 +2,66 @@ Note: There is no guarantee that version mismatched client and server will
|
||||
be able to talk with each other. Network protocol breakages won't be listed
|
||||
here.
|
||||
|
||||
vx.xx.x (2026-xx-xx)
|
||||
v0.14.1 (2026-08-22)
|
||||
--------------------
|
||||
|
||||
- Dropped support for loading pre-0.11 traces.
|
||||
- Silenced MSVC warning C4366: The result of the unary '&' operator may be
|
||||
unaligned.
|
||||
- Fixed section setup in on-demand mode.
|
||||
- Hardened string length encoding in protocol.
|
||||
- Fixed compilation failure related to VSync when targeting 32-bit on
|
||||
Windows.
|
||||
- Added TRACY_NO_WAIT_STACKS compile time macro.
|
||||
- Improved documentation of requirements in edge cases:
|
||||
- Memory allocation reporting needs to be done under a lock.
|
||||
- Manual lifetime management requires shutdown of the profiler.
|
||||
- Documented how zones behave when crossing the on-demand connection
|
||||
boundary.
|
||||
- Mention that statistics time range limit also affects source view.
|
||||
- Memory events are now attributed to virtual fiber threads.
|
||||
- Added TRACY_ASSERT macro allowing user-defined handling of asserts in the
|
||||
profiler.
|
||||
- Fixed csvexport options: -t, --sep, --filter.
|
||||
- Configuration of plot color now matches the documented RGB channels order.
|
||||
- Link profiler executable with libdl (required by old glibc versions).
|
||||
- Improve coverage of link-time macro mismatch detection.
|
||||
- Removed TRACY_DELAYED_INIT option. It was no-op since 2020.
|
||||
- Fixed illegal instruction in rpmalloc on ARMv6 and older.
|
||||
- Fixed handling of source code queries in no-callstack builds. Previously
|
||||
the profiling session would hang at exit.
|
||||
- Hardened Lua on-demand handling.
|
||||
- Fixed system tracing on Linux kernels older than 4.1.
|
||||
- Linux system tracing setup failures are now reported via Tracy debug
|
||||
message functionality.
|
||||
- Improved version handling in CMake / Meson / Python build systems.
|
||||
- Fixed D3D12 readback buffer error.
|
||||
- Child zone list in zone info window can be now sorted by name or by time.
|
||||
- File dialog failures (e.g. related to the XDG portal) are now reported
|
||||
in the modal dialog.
|
||||
- Fixed NO_FILESELECTOR builds.
|
||||
- Fixed possible profiler hang when there was not enough space to draw
|
||||
a plot on the timeline.
|
||||
- Update check can be now interrupted, e.g. when exiting the profiler.
|
||||
- Patched the file selector library to use modern Wayland protocol for
|
||||
setting parent-child window relation.
|
||||
|
||||
|
||||
v0.14.0 (2026-08-09)
|
||||
--------------------
|
||||
|
||||
- API break: removed "secure" variants of memory alloc and free macros. The
|
||||
secure code path is now always enabled. Migrate by removing "Secure" from
|
||||
the macros you use, e.g. TracySecureAlloc(...) -> TracyAlloc(...).
|
||||
- Added printf-style variants of the C API zone text and name macros:
|
||||
TracyCZoneTextF and TracyCZoneNameF now accept a format string and
|
||||
arguments, instead of a fixed-length text buffer.
|
||||
- Added tracy-capture-daemon for automated multi-client trace capture.
|
||||
- Added tracy-merge utility for combining multiple trace files into one.
|
||||
- Added support for Windows on ARM64 with MSVC.
|
||||
- Added support for WebGPU.
|
||||
- Binary releases are now also provided for macOS (needs manual quarantine
|
||||
handling) and Linux (AppImage with Ubuntu 24.04 base, requires Wayland).
|
||||
- Trace-specific settings storage has been completely overhauled. It is now
|
||||
possible to make the settings sidecar file public, saved next to the trace
|
||||
file.
|
||||
@@ -22,9 +72,44 @@ vx.xx.x (2026-xx-xx)
|
||||
- Flame graph window.
|
||||
- Call stack window.
|
||||
- Statistics window (sampling mode).
|
||||
- Wait stacks have a separate setting for external frames, as you would
|
||||
typically want to know what caused the wait state, which can be inferred
|
||||
from the external code.
|
||||
- External frames are now dimmed out in call stacks in various parts of UI.
|
||||
- Sample markers on the timeline are now colored according to sample type:
|
||||
- Green for samples in the profiled program,
|
||||
- Blue for samples in external code,
|
||||
- Red for samples in kernel.
|
||||
- Single-line call stacks now have ellipsis at the end, if there are frames
|
||||
remaining.
|
||||
- Sample entry stacks window can now also show the stacks by which a symbol
|
||||
was reached, i.e. it was present anywhere on the call stack, not only when
|
||||
it was executing. Recursive re-entries can be either ignored or counted
|
||||
separately.
|
||||
- Entry stacks are now also available for symbols which were only reached,
|
||||
but never directly sampled, e.g. in the assembly view of a call site with
|
||||
purely child sample costs, or in the statistics window for symbols which
|
||||
only have inclusive counts.
|
||||
- Fixed inclusive sample counts of symbols with inline functions. Each
|
||||
sample was counted multiple times: once for the symbol itself and once for
|
||||
every of its inline functions present on the call stack.
|
||||
- Fixed context switch samples leaking into per-symbol sample lists and
|
||||
child sample data on trace load. This inflated range-limited sample counts
|
||||
and child call costs, and made loaded traces disagree with live sessions,
|
||||
where such samples are excluded from sampling statistics.
|
||||
- Fixed wrong results of range-limited child call queries during live
|
||||
capture, when some samples had to wait for context switch data to arrive.
|
||||
- Fixed crashes when opening the sample entry stacks window for symbols
|
||||
without any sampling data.
|
||||
- Sampling statistics percentages are now calculated against the number of
|
||||
samples that were actually collected. Previously, enabling the range
|
||||
filter switched the calculation to a theoretical single-thread estimate,
|
||||
which changed both the meaning and the scale of the displayed values.
|
||||
- Find zone sample count percentages are now relative to the matched zones,
|
||||
consistent with the time percentages in the same list, instead of being
|
||||
relative to the whole trace.
|
||||
- Context switch samples are now consistently excluded from all sampling
|
||||
statistics views.
|
||||
- System tracing on Windows has been refactored to be more robust.
|
||||
- Tracing on Arm macOS will now have more precise timer readings.
|
||||
- Extended CUDA support to track some previously missing memory operations.
|
||||
@@ -36,7 +121,8 @@ vx.xx.x (2026-xx-xx)
|
||||
- Added inline call stack list to the zone information window.
|
||||
- The "call stack" button for opening the call stack in a separate window
|
||||
is still there.
|
||||
- Call stacks are now also displayed in zone tooltips (single-line).
|
||||
- Call stacks are now also displayed in zone and memory allocation tooltips
|
||||
(single-line).
|
||||
- Implemented heuristic reconstruction of zone call stacks for zones that
|
||||
were captured without call stacks.
|
||||
- Requires sampling to be enabled, and at least one sample in the zone
|
||||
@@ -78,13 +164,16 @@ vx.xx.x (2026-xx-xx)
|
||||
- Chat topic description is now provided, based on the first user question.
|
||||
- Each assistant reply is now labeled with used model and reply time.
|
||||
- Follow-up questions can be automatically suggested.
|
||||
- It is now possible to alter the assistant personality.
|
||||
- Expanded LLM attachments.
|
||||
- You can now attach complete symbol assembly.
|
||||
- Entry call stacks can be now attached (previously it was only regular
|
||||
call stacks).
|
||||
- Crash call stack attachments are now annotated with crash info.
|
||||
- Wait stack attachments now contain wait metadata.
|
||||
- Source code can be attached (also with execution costs in symbol view).
|
||||
- Zone histogram data can be attached for analysis.
|
||||
- Added MCP server.
|
||||
- Markdown renderer improvements.
|
||||
- Tables are now properly rendered.
|
||||
- Tasklist rendering has been implemented.
|
||||
@@ -108,6 +197,7 @@ vx.xx.x (2026-xx-xx)
|
||||
locations won't be fixed.
|
||||
- Call stack window will now display notification if viewing a crash call
|
||||
stack.
|
||||
- Call stack window will now show a proper label if viewing a wait stack.
|
||||
- Removal of Tracy crash handler stack from the reported crash call stack
|
||||
should now work again on Linux.
|
||||
- In disassembly line view, source file names are now displayed instead of
|
||||
@@ -119,6 +209,8 @@ vx.xx.x (2026-xx-xx)
|
||||
- Added TRACY_DISALLOW_HW_TIMER define for virtualized environments and WSL2,
|
||||
which may not have reliable access to hardware timer registers. Falls back
|
||||
to standard library timer with reduced resolution.
|
||||
- Fixed TRACY_NO_CODE_TRANSFER define. It didn't prevent source code transfer
|
||||
before.
|
||||
- Fixed DPI scaling on macOS.
|
||||
- Thread names are no longer truncated to 15 characters on Apple.
|
||||
- Executable path is now inspected when looking for PDB files on Windows.
|
||||
@@ -148,8 +240,9 @@ vx.xx.x (2026-xx-xx)
|
||||
- Mismatched versions will break at linking.
|
||||
- As a reminder, Tracy *always* required using the same set of compilation
|
||||
options for the entire program.
|
||||
- Message windows will now properly show full message in a tooltip for
|
||||
multi-line messages.
|
||||
- Message windows were improved.
|
||||
- Full message will be now displayed in a tooltip for multi-line messages.
|
||||
- Added right click context menu with option to copy message to clipboard.
|
||||
- Greatly improved the in-profiler user manual.
|
||||
- There is now chapter tree and the manual contents are displayed section
|
||||
by section.
|
||||
@@ -183,7 +276,12 @@ vx.xx.x (2026-xx-xx)
|
||||
- The profiler can no longer be built with the statistics disabled.
|
||||
- Fixed NVCC builds.
|
||||
- Fixed possible lockups in Vulkan timer calibration loop.
|
||||
- The flame graph view now supports zooming in and panning with the mouse.
|
||||
- Flame graph improvements.
|
||||
- The flame graph view now supports zooming in and panning with the mouse.
|
||||
- Sampling flame graph items can be now also grouped by name, in addition
|
||||
to symbol address.
|
||||
- Sampling flame graph no longer omits display of unknown symbols. These
|
||||
are now aggregated into blocks named after the executable image.
|
||||
- General application crash information polish in the profiler UI.
|
||||
- The achievements system has been converted to use markdown renderer.
|
||||
- Offline symbol resolution with the update utility now supports custom
|
||||
@@ -194,11 +292,59 @@ vx.xx.x (2026-xx-xx)
|
||||
with the TRACY_OPENGL_AUTO_CALIBRATION compilation define. Note that this
|
||||
requires a full CPU/GPU sync on each calibration event. These events will
|
||||
not fire more often than once every second.
|
||||
- Additional hardening of OpenGL tracing.
|
||||
- Added missing C API for shared locks.
|
||||
- Implemented semi-unique, nonsense random name generator.
|
||||
- Can be used to set a trace description.
|
||||
- Will be used to provide default description for newly added annotations.
|
||||
- Polished look and feel of annotation regions on the timeline.
|
||||
- Annotations can now be hidden.
|
||||
- It is now possible to create annotations from time range limits.
|
||||
- Expanded available tests.
|
||||
- The previously existing "test" program now lives in "tests/tracy".
|
||||
- The tests directory also contains some repro cases for solved problems.
|
||||
- Expanded examples showing how to use the profiler.
|
||||
- Includes "Dyna.net", a small 2D game that is a variation of Bomberman.
|
||||
- Made numeric input field for trace parameters usable again.
|
||||
- Added new type of trace parameter that exposes a trigger button.
|
||||
- Added "sections" feature for marking high-level sections of program
|
||||
execution.
|
||||
- For example, a game with multiple levels may have each of the levels
|
||||
marked as a section.
|
||||
- Each section is a distinct region not restricted by other sections.
|
||||
- Multiple sections can overlap.
|
||||
- Sections can be assigned to user-defined categories.
|
||||
- Can be used to set time range limits.
|
||||
- Exposed via Lua bindings.
|
||||
- Find zone window now also displays coefficient of variation, in addition
|
||||
to standard deviation.
|
||||
- Moved frame statistics from trace information window to its own window.
|
||||
- Added various statistical data to bring frame statistics up to par with
|
||||
the find zone window.
|
||||
- Added a new time range limit for frame statistics. This accompanies the
|
||||
previously existing "limit to view" option.
|
||||
- More robust statistics for functions that recursively call themselves.
|
||||
- General uplift of the compare traces window.
|
||||
- Added all the statistical parameters displayed in find zone window that
|
||||
were missing here.
|
||||
- Frame comparison now also displays FPS values.
|
||||
- The "more" and "less" indicators are now color coded to show their
|
||||
importance. The amount of color these keywords are printed with is now
|
||||
dependant on how divergent the numeric values are.
|
||||
- It is now possible to limit time range for both traces that are compared
|
||||
to their respective annotations or sections.
|
||||
- Frame image playback improvements.
|
||||
- Playback can be looped.
|
||||
- The range of played frame images can be limited, either manually, or by
|
||||
copying annotation, section, or time range limit. There's an "require
|
||||
frame coverage" option to only include frames that are at least 75%
|
||||
within the source region.
|
||||
- Most windows now have a minimum width, in order to make sure all controls
|
||||
provided in the window are visible. Previously it was easy to oversee some
|
||||
button that was out of the window draw area.
|
||||
- The ignore memory faults functionality (enabled by default if profiling
|
||||
programs on Apple platforms) will now also ignore allocating memory at the
|
||||
same location twice, without an intermediate free. Thanks, Tim Apple!
|
||||
|
||||
|
||||
v0.13.1 (2025-12-11)
|
||||
|
||||
31
capture/CMakePresets.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 10,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/build-${presetName}",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug configuration",
|
||||
"inherits": "common",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release configuration",
|
||||
"inherits": "common",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -137,6 +137,7 @@ int main( int argc, char** argv )
|
||||
{
|
||||
worker.Disconnect();
|
||||
s_disconnect.store(false, std::memory_order_relaxed );
|
||||
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -168,7 +168,13 @@ void CaptureThread( ClientSession* session, const std::string& address, uint16_t
|
||||
|
||||
std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
}
|
||||
|
||||
|
||||
if( worker.IsConnected() )
|
||||
{
|
||||
worker.Disconnect();
|
||||
while( worker.IsConnected() ) std::this_thread::sleep_for( std::chrono::milliseconds( 100 ) );
|
||||
}
|
||||
|
||||
printf( "\nSaving %s...", outputFile.c_str() );
|
||||
fflush( stdout );
|
||||
|
||||
|
||||
13
cmake/imgui-macos-clipboard.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git i/imconfig.h w/imconfig.h
|
||||
index b40db3898..561ab7782 100644
|
||||
--- i/imconfig.h
|
||||
+++ w/imconfig.h
|
||||
@@ -42,7 +42,7 @@
|
||||
//#define IMGUI_ENABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with Visual Studio] Implement default IME handler (require imm32.lib/.a, auto-link for Visual Studio, -limm32 on command-line for MinGW)
|
||||
//#define IMGUI_DISABLE_WIN32_DEFAULT_IME_FUNCTIONS // [Win32] [Default with non-Visual Studio compilers] Don't implement default IME handler (won't require imm32.lib/.a)
|
||||
//#define IMGUI_DISABLE_WIN32_FUNCTIONS // [Win32] Won't use and link with any Win32 function (clipboard, IME).
|
||||
-//#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
|
||||
+#define IMGUI_ENABLE_OSX_DEFAULT_CLIPBOARD_FUNCTIONS // [OSX] Implement default OSX clipboard handler (need to link with '-framework ApplicationServices', this is why this is not the default).
|
||||
//#define IMGUI_DISABLE_TIME_FUNCTIONS // Don't setup default platform_io.Platform_SessionDate value using time(), localtime_r().
|
||||
//#define IMGUI_DISABLE_DEFAULT_SHELL_FUNCTIONS // Don't implement default platform_io.Platform_OpenInShellFn() handler (Win32: ShellExecute(), require shell32.lib/.a, Mac/Linux: use system("")).
|
||||
//#define IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS // Don't implement ImFormatString/ImFormatStringV so you can implement them yourself (e.g. if you don't want to link with vsnprintf)
|
||||
13
cmake/imgui-no-default-font.patch
Normal file
@@ -0,0 +1,13 @@
|
||||
diff --git i/imconfig.h w/imconfig.h
|
||||
index b40db3898..f7f864394 100644
|
||||
--- i/imconfig.h
|
||||
+++ w/imconfig.h
|
||||
@@ -50,7 +50,7 @@
|
||||
//#define IMGUI_DISABLE_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle at all (replace them with dummies)
|
||||
//#define IMGUI_DISABLE_DEFAULT_FILE_FUNCTIONS // Don't implement ImFileOpen/ImFileClose/ImFileRead/ImFileWrite and ImFileHandle so you can implement them yourself if you don't want to link with fopen/fclose/fread/fwrite. This will also disable the LogToTTY() function.
|
||||
//#define IMGUI_DISABLE_DEFAULT_ALLOCATORS // Don't implement default allocators calling malloc()/free() to avoid linking with them. You will need to call ImGui::SetAllocatorFunctions().
|
||||
-//#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
|
||||
+#define IMGUI_DISABLE_DEFAULT_FONT // Disable default embedded fonts (ProggyClean + ProggyForever). Remove ~9 KB + ~14 KB from output binary. AddFontDefaultXXX() functions will assert.
|
||||
//#define IMGUI_DISABLE_DEFAULT_FONT_BITMAP // Disable default embedded bitmap font (ProggyClean). Remove ~9 KB from output binary. AddFontDefaultBitmap() will assert.
|
||||
//#define IMGUI_DISABLE_DEFAULT_FONT_VECTOR // Disable default embedded vector font (ProggyForever), Remove ~14 KB from output binary. AddFontDefaultVector() will assert.
|
||||
//#define IMGUI_DISABLE_SSE // Disable use of SSE intrinsics even if available
|
||||
13
cmake/imgui-no-samplers.patch
Normal 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
|
||||
14
cmake/imgui-phantom-column.patch
Normal file
@@ -0,0 +1,14 @@
|
||||
diff --git a/imgui_tables.cpp b/imgui_tables.cpp
|
||||
--- a/imgui_tables.cpp
|
||||
+++ b/imgui_tables.cpp
|
||||
@@ -4082,7 +4082,9 @@
|
||||
}
|
||||
if (load_flags & ImGuiTableFlags_Reorderable)
|
||||
column->DisplayOrder = column_settings->DisplayOrder;
|
||||
- else
|
||||
+ else if (column_settings->Index != -1)
|
||||
+ // An Index of -1 means this column had no "Column N" line in the .ini file (columns without saved
|
||||
+ // data are not written). Keep the display order as initialized (identity for non-reorderable tables). (#9519)
|
||||
column->DisplayOrder = column_settings->Index; // Because default depends on previous Index, we need to set that up and cannot rely on TableInitColumnDefaults()
|
||||
if ((load_flags & ImGuiTableFlags_Hideable) && column_settings->IsEnabled != -1)
|
||||
column->IsUserEnabled = column->IsUserEnabledNextFrame = (column_settings->IsEnabled == 1);
|
||||
203
cmake/nfd-xdg-foreign-v2.patch
Normal file
@@ -0,0 +1,203 @@
|
||||
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
|
||||
index d6a9edc..abef8d7 100644
|
||||
--- a/src/CMakeLists.txt
|
||||
+++ b/src/CMakeLists.txt
|
||||
@@ -32,18 +32,18 @@ if(nfd_PLATFORM STREQUAL PLATFORM_LINUX)
|
||||
if(NFD_WAYLAND)
|
||||
pkg_check_modules(WAYLAND REQUIRED wayland-client)
|
||||
message(STATUS "Using Wayland version: ${WAYLAND_VERSION}")
|
||||
- set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v1.xml)
|
||||
+ set(NFD_WAYLAND_PROTOCOL_XDG_FOREIGN ${CMAKE_CURRENT_SOURCE_DIR}/../3ps/wayland-protocols/unstable/xdg-foreign/xdg-foreign-unstable-v2.xml)
|
||||
add_custom_command(
|
||||
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
|
||||
- COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h
|
||||
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
|
||||
+ COMMAND wayland-scanner client-header < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h
|
||||
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
|
||||
)
|
||||
add_custom_command(
|
||||
- OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
|
||||
- COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c
|
||||
+ OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
|
||||
+ COMMAND wayland-scanner private-code < ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN} > ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c
|
||||
MAIN_DEPENDENCY ${NFD_WAYLAND_PROTOCOL_XDG_FOREIGN}
|
||||
)
|
||||
- list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v1.c)
|
||||
+ list(APPEND SOURCE_FILES ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.h ${CMAKE_CURRENT_BINARY_DIR}/xdg-foreign-unstable-v2.c)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
diff --git a/src/nfd_gtk.cpp b/src/nfd_gtk.cpp
|
||||
index 76fdcd7..cec691d 100644
|
||||
--- a/src/nfd_gtk.cpp
|
||||
+++ b/src/nfd_gtk.cpp
|
||||
@@ -433,17 +433,17 @@ gint RunDialogWithFocus(GtkDialog* dialog) {
|
||||
|
||||
#if defined(NFD_WAYLAND)
|
||||
void DestroyXdgExported(void* context) {
|
||||
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
|
||||
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
|
||||
}
|
||||
|
||||
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
|
||||
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
|
||||
GdkWindow* childWindow = static_cast<GdkWindow*>(context);
|
||||
// GDK doesn't modify the string, even though it takes char*, so we can cast away the constness
|
||||
gdk_wayland_window_set_transient_for_exported(childWindow, const_cast<char*>(handle));
|
||||
}
|
||||
|
||||
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
|
||||
- &zxdg_exported_v1_handle};
|
||||
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
|
||||
+ &zxdg_exported_v2_handle};
|
||||
#endif
|
||||
|
||||
// This is an RAII class that wraps the parenting of a GtkWidget (the file dialog).
|
||||
@@ -645,17 +645,17 @@ struct NativeWindowParenter {
|
||||
|
||||
#if defined(NFD_WAYLAND)
|
||||
void SetParentWayland(GdkWindow* childWindow) {
|
||||
- if (wayland_display && wayland_xdg_exporter_v1) {
|
||||
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
|
||||
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindowHandle));
|
||||
+ if (wayland_display && wayland_xdg_exporter_v2) {
|
||||
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
|
||||
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindowHandle));
|
||||
if (!exported) {
|
||||
// if we fail to export the wl_surface, act as if the window has no parent
|
||||
return;
|
||||
}
|
||||
- zxdg_exported_v1_add_listener(
|
||||
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(childWindow));
|
||||
+ zxdg_exported_v2_add_listener(
|
||||
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(childWindow));
|
||||
wl_display_roundtrip(wayland_display);
|
||||
- zxdg_exported_v1_set_user_data(exported, nullptr);
|
||||
+ zxdg_exported_v2_set_user_data(exported, nullptr);
|
||||
destroy.fn = &DestroyXdgExported;
|
||||
destroy.context = static_cast<void*>(exported);
|
||||
}
|
||||
diff --git a/src/nfd_linux_shared.hpp b/src/nfd_linux_shared.hpp
|
||||
index 1413bac..e826f1e 100644
|
||||
--- a/src/nfd_linux_shared.hpp
|
||||
+++ b/src/nfd_linux_shared.hpp
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#ifdef NFD_WAYLAND
|
||||
#include <wayland-client.h>
|
||||
-#include "xdg-foreign-unstable-v1.h"
|
||||
+#include "xdg-foreign-unstable-v2.h"
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
@@ -81,9 +81,9 @@ nfdnchar_t* emit_case_insensitive_glob(const nfdnchar_t* begin,
|
||||
#ifdef NFD_WAYLAND
|
||||
struct wl_display* wayland_display;
|
||||
struct wl_registry* wayland_registry;
|
||||
-uint32_t wayland_xdg_exporter_v1_name;
|
||||
-struct zxdg_exporter_v1* wayland_xdg_exporter_v1;
|
||||
-constexpr const char* XDG_EXPORTER_V1 = "zxdg_exporter_v1";
|
||||
+uint32_t wayland_xdg_exporter_v2_name;
|
||||
+struct zxdg_exporter_v2* wayland_xdg_exporter_v2;
|
||||
+constexpr const char* XDG_EXPORTER_V2 = "zxdg_exporter_v2";
|
||||
#endif
|
||||
|
||||
void EmptyFn(void*) {}
|
||||
@@ -103,19 +103,19 @@ void registry_handle_global(void* context,
|
||||
uint32_t version) {
|
||||
(void)context;
|
||||
(void)version;
|
||||
- if (strcmp(interface, XDG_EXPORTER_V1) == 0) {
|
||||
- wayland_xdg_exporter_v1_name = name;
|
||||
- wayland_xdg_exporter_v1 = static_cast<struct zxdg_exporter_v1*>(wl_registry_bind(
|
||||
- registry, name, &zxdg_exporter_v1_interface, zxdg_exporter_v1_interface.version));
|
||||
+ if (strcmp(interface, XDG_EXPORTER_V2) == 0) {
|
||||
+ wayland_xdg_exporter_v2_name = name;
|
||||
+ wayland_xdg_exporter_v2 = static_cast<struct zxdg_exporter_v2*>(wl_registry_bind(
|
||||
+ registry, name, &zxdg_exporter_v2_interface, zxdg_exporter_v2_interface.version));
|
||||
}
|
||||
}
|
||||
|
||||
void registry_handle_global_remove(void* context, struct wl_registry* registry, uint32_t name) {
|
||||
(void)context;
|
||||
(void)registry;
|
||||
- if (wayland_xdg_exporter_v1 && name == wayland_xdg_exporter_v1_name) {
|
||||
- zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
|
||||
- wayland_xdg_exporter_v1 = nullptr;
|
||||
+ if (wayland_xdg_exporter_v2 && name == wayland_xdg_exporter_v2_name) {
|
||||
+ zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
|
||||
+ wayland_xdg_exporter_v2 = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ void NFD_Wayland_Init(void) {
|
||||
|
||||
void NFD_Wayland_Quit(void) {
|
||||
if (wayland_display) {
|
||||
- if (wayland_xdg_exporter_v1) zxdg_exporter_v1_destroy(wayland_xdg_exporter_v1);
|
||||
+ if (wayland_xdg_exporter_v2) zxdg_exporter_v2_destroy(wayland_xdg_exporter_v2);
|
||||
wl_registry_destroy(wayland_registry);
|
||||
}
|
||||
}
|
||||
@@ -143,7 +143,7 @@ nfdresult_t NFD_SetWaylandDisplay(wl_display* display) {
|
||||
wayland_display = display;
|
||||
if (wayland_display) {
|
||||
wayland_registry = wl_display_get_registry(wayland_display);
|
||||
- wayland_xdg_exporter_v1 = nullptr;
|
||||
+ wayland_xdg_exporter_v2 = nullptr;
|
||||
// seems like registry can't be null
|
||||
wl_registry_add_listener(wayland_registry, &wayland_registry_listener, nullptr);
|
||||
wl_display_roundtrip(wayland_display);
|
||||
diff --git a/src/nfd_portal.cpp b/src/nfd_portal.cpp
|
||||
index df840e0..e66ca7f 100644
|
||||
--- a/src/nfd_portal.cpp
|
||||
+++ b/src/nfd_portal.cpp
|
||||
@@ -148,10 +148,10 @@ constexpr const char* DBUS_REQUEST_IFACE = "org.freedesktop.portal.Request";
|
||||
constexpr const char* WAYLAND_PREFIX = "wayland:";
|
||||
|
||||
void DestroyXdgExported(void* context) {
|
||||
- zxdg_exported_v1_destroy(static_cast<struct zxdg_exported_v1*>(context));
|
||||
+ zxdg_exported_v2_destroy(static_cast<struct zxdg_exported_v2*>(context));
|
||||
}
|
||||
|
||||
-void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char* handle) {
|
||||
+void zxdg_exported_v2_handle(void* context, struct zxdg_exported_v2*, const char* handle) {
|
||||
DBusMessageIter& iter = *static_cast<DBusMessageIter*>(context);
|
||||
const size_t handle_len = strlen(handle);
|
||||
const size_t prefix_len = strlen(WAYLAND_PREFIX);
|
||||
@@ -163,8 +163,8 @@ void zxdg_exported_v1_handle(void* context, struct zxdg_exported_v1*, const char
|
||||
NFDi_Free(buf);
|
||||
}
|
||||
|
||||
-constexpr struct zxdg_exported_v1_listener wayland_xdg_exported_v1_listener{
|
||||
- &zxdg_exported_v1_handle};
|
||||
+constexpr struct zxdg_exported_v2_listener wayland_xdg_exported_v2_listener{
|
||||
+ &zxdg_exported_v2_handle};
|
||||
#endif
|
||||
|
||||
void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
|
||||
@@ -194,18 +194,18 @@ void AppendOpenFileQueryParentWindow(DBusMessageIter& iter,
|
||||
#endif
|
||||
#ifdef NFD_WAYLAND
|
||||
case NFD_WINDOW_HANDLE_TYPE_WAYLAND: {
|
||||
- if (wayland_display && wayland_xdg_exporter_v1) {
|
||||
- struct zxdg_exported_v1* exported = zxdg_exporter_v1_export(
|
||||
- wayland_xdg_exporter_v1, static_cast<struct wl_surface*>(parentWindow.handle));
|
||||
+ if (wayland_display && wayland_xdg_exporter_v2) {
|
||||
+ struct zxdg_exported_v2* exported = zxdg_exporter_v2_export_toplevel(
|
||||
+ wayland_xdg_exporter_v2, static_cast<struct wl_surface*>(parentWindow.handle));
|
||||
if (!exported) {
|
||||
// if we fail to export the wl_surface, act as if the window has no parent
|
||||
dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING, &STR_EMPTY);
|
||||
return;
|
||||
}
|
||||
- zxdg_exported_v1_add_listener(
|
||||
- exported, &wayland_xdg_exported_v1_listener, static_cast<void*>(&iter));
|
||||
+ zxdg_exported_v2_add_listener(
|
||||
+ exported, &wayland_xdg_exported_v2_listener, static_cast<void*>(&iter));
|
||||
wl_display_roundtrip(wayland_display);
|
||||
- zxdg_exported_v1_set_user_data(exported, nullptr);
|
||||
+ zxdg_exported_v2_set_user_data(exported, nullptr);
|
||||
destroy.fn = &DestroyXdgExported;
|
||||
destroy.context = static_cast<void*>(exported);
|
||||
return;
|
||||
@@ -8,11 +8,20 @@ set (ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../")
|
||||
include(FindPkgConfig)
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/CPM.cmake)
|
||||
|
||||
if(APPLE AND BUNDLE)
|
||||
set(DLOPT ON)
|
||||
else()
|
||||
set(DLOPT OFF)
|
||||
endif()
|
||||
|
||||
option(DOWNLOAD_CAPSTONE "Force download capstone" ON)
|
||||
option(DOWNLOAD_GLFW "Force download glfw" OFF)
|
||||
option(DOWNLOAD_FREETYPE "Force download freetype" OFF)
|
||||
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
|
||||
option(DOWNLOAD_PUGIXML "Force download pugixml" OFF)
|
||||
|
||||
if(VENDOR_GUI)
|
||||
option(DOWNLOAD_GLFW "Force download glfw" ${DLOPT})
|
||||
option(DOWNLOAD_FREETYPE "Force download freetype" ${DLOPT})
|
||||
option(DOWNLOAD_LIBCURL "Force download libcURL" OFF)
|
||||
option(DOWNLOAD_PUGIXML "Force download pugixml" ${DLOPT})
|
||||
endif()
|
||||
|
||||
# capstone
|
||||
|
||||
@@ -26,7 +35,7 @@ else()
|
||||
CPMAddPackage(
|
||||
NAME capstone
|
||||
GITHUB_REPOSITORY capstone-engine/capstone
|
||||
GIT_TAG 6.0.0-Alpha9
|
||||
GIT_TAG 6.0.0-Alpha10
|
||||
OPTIONS
|
||||
"CAPSTONE_X86_ATT_DISABLE ON"
|
||||
"CAPSTONE_ALPHA_SUPPORT OFF"
|
||||
@@ -58,52 +67,6 @@ else()
|
||||
target_link_libraries(TracyCapstone INTERFACE capstone_static)
|
||||
endif()
|
||||
|
||||
# GLFW
|
||||
|
||||
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
|
||||
pkg_check_modules(GLFW glfw3)
|
||||
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
|
||||
add_library(TracyGlfw3 INTERFACE)
|
||||
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME glfw
|
||||
GITHUB_REPOSITORY glfw/glfw
|
||||
GIT_TAG 3.4
|
||||
OPTIONS
|
||||
"GLFW_BUILD_EXAMPLES OFF"
|
||||
"GLFW_BUILD_TESTS OFF"
|
||||
"GLFW_BUILD_DOCS OFF"
|
||||
"GLFW_INSTALL OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyGlfw3 INTERFACE)
|
||||
target_link_libraries(TracyGlfw3 INTERFACE glfw)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# freetype
|
||||
|
||||
pkg_check_modules(FREETYPE freetype2)
|
||||
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
|
||||
add_library(TracyFreetype INTERFACE)
|
||||
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME freetype
|
||||
GITHUB_REPOSITORY freetype/freetype
|
||||
GIT_TAG VER-2-14-3
|
||||
OPTIONS
|
||||
"FT_DISABLE_HARFBUZZ ON"
|
||||
"FT_WITH_HARFBUZZ OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyFreetype INTERFACE)
|
||||
target_link_libraries(TracyFreetype INTERFACE freetype)
|
||||
endif()
|
||||
|
||||
# Zstd
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -132,65 +95,6 @@ set(GETOPT_HEADERS ${GETOPT_DIR}/getopt.h)
|
||||
add_library(TracyGetOpt STATIC EXCLUDE_FROM_ALL ${GETOPT_SOURCES} ${GETOPT_HEADERS})
|
||||
target_include_directories(TracyGetOpt PUBLIC ${GETOPT_DIR})
|
||||
|
||||
# ImGui
|
||||
|
||||
CPMAddPackage(
|
||||
NAME ImGui
|
||||
GITHUB_REPOSITORY ocornut/imgui
|
||||
GIT_TAG v1.92.8-docking
|
||||
DOWNLOAD_ONLY TRUE
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
|
||||
)
|
||||
|
||||
set(IMGUI_SOURCES
|
||||
imgui_widgets.cpp
|
||||
imgui_draw.cpp
|
||||
imgui_demo.cpp
|
||||
imgui.cpp
|
||||
imgui_tables.cpp
|
||||
misc/freetype/imgui_freetype.cpp
|
||||
backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
|
||||
|
||||
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
|
||||
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
|
||||
target_link_libraries(TracyImGui PUBLIC TracyFreetype)
|
||||
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
|
||||
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
|
||||
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
|
||||
find_package(X11 REQUIRED)
|
||||
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
|
||||
endif()
|
||||
|
||||
# NFD
|
||||
|
||||
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
|
||||
if(GTK_FILESELECTOR)
|
||||
set(NFD_PORTAL OFF)
|
||||
else()
|
||||
set(NFD_PORTAL ON)
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME nfd
|
||||
GITHUB_REPOSITORY btzy/nativefiledialog-extended
|
||||
GIT_TAG v1.3.0
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
OPTIONS
|
||||
"NFD_PORTAL ${NFD_PORTAL}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# PPQSort
|
||||
|
||||
CPMAddPackage(
|
||||
@@ -212,95 +116,226 @@ CPMAddPackage(
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# md4c
|
||||
if(VENDOR_GUI)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME md4c
|
||||
GITHUB_REPOSITORY mity/md4c
|
||||
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
# GLFW
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
|
||||
# base64
|
||||
|
||||
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
CPMAddPackage(
|
||||
NAME base64
|
||||
GITHUB_REPOSITORY aklomp/base64
|
||||
GIT_TAG v0.5.2
|
||||
OPTIONS
|
||||
"BASE64_BUILD_CLI OFF"
|
||||
"BASE64_WITH_OpenMP OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
|
||||
|
||||
# tidy
|
||||
|
||||
CPMAddPackage(
|
||||
NAME tidy
|
||||
GITHUB_REPOSITORY htacg/tidy-html5
|
||||
GIT_TAG 5.8.0
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# usearch
|
||||
|
||||
CPMAddPackage(
|
||||
NAME usearch
|
||||
GITHUB_REPOSITORY unum-cloud/usearch
|
||||
GIT_TAG v2.25.2
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# pugixml
|
||||
|
||||
pkg_check_modules(PUGIXML pugixml)
|
||||
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
|
||||
add_library(TracyPugixml INTERFACE)
|
||||
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME pugixml
|
||||
GITHUB_REPOSITORY zeux/pugixml
|
||||
GIT_TAG v1.15
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyPugixml INTERFACE)
|
||||
target_link_libraries(TracyPugixml INTERFACE pugixml)
|
||||
if(NOT USE_WAYLAND AND NOT EMSCRIPTEN)
|
||||
pkg_check_modules(GLFW glfw3)
|
||||
if (GLFW_FOUND AND NOT DOWNLOAD_GLFW)
|
||||
add_library(TracyGlfw3 INTERFACE)
|
||||
target_include_directories(TracyGlfw3 INTERFACE ${GLFW_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyGlfw3 INTERFACE ${GLFW_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME glfw
|
||||
GITHUB_REPOSITORY glfw/glfw
|
||||
GIT_TAG 3.4
|
||||
OPTIONS
|
||||
"GLFW_BUILD_EXAMPLES OFF"
|
||||
"GLFW_BUILD_TESTS OFF"
|
||||
"GLFW_BUILD_DOCS OFF"
|
||||
"GLFW_INSTALL OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyGlfw3 INTERFACE)
|
||||
target_link_libraries(TracyGlfw3 INTERFACE glfw)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# libcurl
|
||||
# freetype
|
||||
|
||||
pkg_check_modules(LIBCURL libcurl>=7.87.0)
|
||||
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
|
||||
add_library(TracyLibcurl INTERFACE)
|
||||
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
|
||||
pkg_check_modules(FREETYPE freetype2)
|
||||
if (FREETYPE_FOUND AND NOT DOWNLOAD_FREETYPE)
|
||||
add_library(TracyFreetype INTERFACE)
|
||||
target_include_directories(TracyFreetype INTERFACE ${FREETYPE_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyFreetype INTERFACE ${FREETYPE_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME libcurl
|
||||
GITHUB_REPOSITORY curl/curl
|
||||
GIT_TAG curl-8_20_0
|
||||
NAME freetype
|
||||
GITHUB_REPOSITORY freetype/freetype
|
||||
GIT_TAG VER-2-14-3
|
||||
OPTIONS
|
||||
"BUILD_STATIC_LIBS ON"
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
"HTTP_ONLY ON"
|
||||
"CURL_ZSTD OFF"
|
||||
"CURL_USE_LIBPSL OFF"
|
||||
"FT_DISABLE_HARFBUZZ ON"
|
||||
"FT_WITH_HARFBUZZ OFF"
|
||||
"FT_DISABLE_ZLIB ON"
|
||||
"FT_DISABLE_BZIP2 ON"
|
||||
"FT_DISABLE_PNG ON"
|
||||
"FT_DISABLE_BROTLI ON"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyLibcurl INTERFACE)
|
||||
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
|
||||
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
|
||||
add_library(TracyFreetype INTERFACE)
|
||||
target_link_libraries(TracyFreetype INTERFACE freetype)
|
||||
endif()
|
||||
|
||||
# ImGui
|
||||
|
||||
CPMAddPackage(
|
||||
NAME ImGui
|
||||
GITHUB_REPOSITORY ocornut/imgui
|
||||
GIT_TAG v1.92.9b-docking
|
||||
DOWNLOAD_ONLY TRUE
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-emscripten.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-loader.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-samplers.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-no-default-font.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-macos-clipboard.patch"
|
||||
"${CMAKE_CURRENT_LIST_DIR}/imgui-phantom-column.patch"
|
||||
)
|
||||
|
||||
set(IMGUI_SOURCES
|
||||
imgui_widgets.cpp
|
||||
imgui_draw.cpp
|
||||
imgui_demo.cpp
|
||||
imgui.cpp
|
||||
imgui_tables.cpp
|
||||
misc/freetype/imgui_freetype.cpp
|
||||
backends/imgui_impl_opengl3.cpp
|
||||
)
|
||||
|
||||
list(TRANSFORM IMGUI_SOURCES PREPEND "${ImGui_SOURCE_DIR}/")
|
||||
|
||||
add_library(TracyImGui STATIC EXCLUDE_FROM_ALL ${IMGUI_SOURCES})
|
||||
target_include_directories(TracyImGui PUBLIC ${ImGui_SOURCE_DIR})
|
||||
target_link_libraries(TracyImGui PUBLIC
|
||||
TracyFreetype
|
||||
${CMAKE_DL_LIBS}
|
||||
)
|
||||
target_compile_definitions(TracyImGui PRIVATE "IMGUI_ENABLE_FREETYPE")
|
||||
target_compile_definitions(TracyImGui PUBLIC "IMGUI_USE_WCHAR32")
|
||||
#target_compile_definitions(TracyImGui PUBLIC "IMGUI_DISABLE_OBSOLETE_FUNCTIONS")
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND LEGACY)
|
||||
find_package(X11 REQUIRED)
|
||||
target_link_libraries(TracyImGui PUBLIC ${X11_LIBRARIES})
|
||||
endif()
|
||||
|
||||
if(NOT CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
target_compile_definitions(TracyImGui PRIVATE "IMGUI_DISABLE_DEBUG_TOOLS" "IMGUI_DISABLE_DEMO_WINDOWS")
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
target_link_libraries(TracyImGui PUBLIC "-framework ApplicationServices")
|
||||
endif()
|
||||
|
||||
# NFD
|
||||
|
||||
if(NOT NO_FILESELECTOR AND NOT EMSCRIPTEN)
|
||||
if(GTK_FILESELECTOR)
|
||||
set(NFD_PORTAL OFF)
|
||||
else()
|
||||
set(NFD_PORTAL ON)
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME nfd
|
||||
GITHUB_REPOSITORY btzy/nativefiledialog-extended
|
||||
GIT_TAG 3cd252a8f7ca32419b1ca235c2990ba6a0ecba7c
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/nfd-xdg-foreign-v2.patch"
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
"NFD_PORTAL ${NFD_PORTAL}"
|
||||
)
|
||||
endif()
|
||||
|
||||
# md4c
|
||||
|
||||
CPMAddPackage(
|
||||
NAME md4c
|
||||
GITHUB_REPOSITORY mity/md4c
|
||||
GIT_TAG 65c6c9d72cebd9a731aaa5597414ce04d9ea5de3
|
||||
OPTIONS
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
|
||||
# base64
|
||||
|
||||
set(BUILD_SHARED_LIBS_SAVE ${BUILD_SHARED_LIBS})
|
||||
set(BUILD_SHARED_LIBS OFF)
|
||||
CPMAddPackage(
|
||||
NAME base64
|
||||
GITHUB_REPOSITORY aklomp/base64
|
||||
GIT_TAG v0.5.2
|
||||
OPTIONS
|
||||
"BASE64_BUILD_CLI OFF"
|
||||
"BASE64_WITH_OpenMP OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
set(BUILD_SHARED_LIBS ${BUILD_SHARED_LIBS_SAVE})
|
||||
|
||||
# tidy
|
||||
|
||||
CPMAddPackage(
|
||||
NAME tidy
|
||||
GITHUB_REPOSITORY htacg/tidy-html5
|
||||
GIT_TAG 5.8.0
|
||||
PATCHES
|
||||
"${CMAKE_CURRENT_LIST_DIR}/tidy-cmake.patch"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# usearch
|
||||
|
||||
CPMAddPackage(
|
||||
NAME usearch
|
||||
GITHUB_REPOSITORY unum-cloud/usearch
|
||||
GIT_TAG v2.26.0
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# pugixml
|
||||
|
||||
pkg_check_modules(PUGIXML pugixml)
|
||||
if (PUGIXML_FOUND AND NOT DOWNLOAD_PUGIXML)
|
||||
add_library(TracyPugixml INTERFACE)
|
||||
target_include_directories(TracyPugixml INTERFACE ${PUGIXML_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyPugixml INTERFACE ${PUGIXML_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME pugixml
|
||||
GITHUB_REPOSITORY zeux/pugixml
|
||||
GIT_TAG v1.16
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyPugixml INTERFACE)
|
||||
target_link_libraries(TracyPugixml INTERFACE pugixml)
|
||||
endif()
|
||||
|
||||
# libcurl
|
||||
|
||||
pkg_check_modules(LIBCURL libcurl>=7.87.0)
|
||||
if (LIBCURL_FOUND AND NOT DOWNLOAD_LIBCURL)
|
||||
add_library(TracyLibcurl INTERFACE)
|
||||
target_include_directories(TracyLibcurl INTERFACE ${LIBCURL_INCLUDE_DIRS})
|
||||
target_link_libraries(TracyLibcurl INTERFACE ${LIBCURL_LINK_LIBRARIES})
|
||||
else()
|
||||
CPMAddPackage(
|
||||
NAME libcurl
|
||||
GITHUB_REPOSITORY curl/curl
|
||||
GIT_TAG curl-8_21_0
|
||||
OPTIONS
|
||||
"BUILD_STATIC_LIBS ON"
|
||||
"BUILD_SHARED_LIBS OFF"
|
||||
"HTTP_ONLY ON"
|
||||
"CURL_ZSTD OFF"
|
||||
"CURL_USE_LIBPSL OFF"
|
||||
"CURL_USE_LIBSSH2 OFF"
|
||||
"CURL_BROTLI OFF"
|
||||
"USE_NGHTTP2 OFF"
|
||||
"USE_LIBIDN2 OFF"
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
add_library(TracyLibcurl INTERFACE)
|
||||
target_link_libraries(TracyLibcurl INTERFACE libcurl_static)
|
||||
target_include_directories(TracyLibcurl INTERFACE ${libcurl_SOURCE_DIR}/include)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
31
csvexport/CMakePresets.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 10,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/build-${presetName}",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug configuration",
|
||||
"inherits": "common",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release configuration",
|
||||
"inherits": "common",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -26,17 +26,17 @@ void print_usage_exit(int e)
|
||||
fprintf(stderr, "Usage:\n");
|
||||
fprintf(stderr, " extract [OPTION...] <trace file>\n");
|
||||
fprintf(stderr, "\n");
|
||||
fprintf(stderr, " -h, --help Print usage\n");
|
||||
fprintf(stderr, " -V, --version Show version information\n");
|
||||
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
|
||||
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
|
||||
fprintf(stderr, " -c, --case Case sensitive filtering\n");
|
||||
fprintf(stderr, " -e, --self Get self times\n");
|
||||
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
|
||||
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
|
||||
fprintf(stderr, " -m, --messages Report only messages\n");
|
||||
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
|
||||
fprintf(stderr, " -t, --truncated_mean arg Report truncated mean (arg is the percentile. Default is 90)\n");
|
||||
fprintf(stderr, " -h, --help Print usage\n");
|
||||
fprintf(stderr, " -V, --version Show version information\n");
|
||||
fprintf(stderr, " -f, --filter arg Filter zone names (default: "")\n");
|
||||
fprintf(stderr, " -s, --sep arg CSV separator (default: ,)\n");
|
||||
fprintf(stderr, " -c, --case Case sensitive filtering\n");
|
||||
fprintf(stderr, " -e, --self Get self times\n");
|
||||
fprintf(stderr, " -u, --unwrap Report each cpu zone event\n");
|
||||
fprintf(stderr, " -g, --gpu Report each gpu zone event\n" );
|
||||
fprintf(stderr, " -m, --messages Report only messages\n");
|
||||
fprintf(stderr, " -p, --plot Report plot data (only with -u)\n");
|
||||
fprintf(stderr, " -t, --truncated_mean[=arg] Report truncated mean (arg is the percentile. Default is 90)\n");
|
||||
|
||||
exit(e);
|
||||
}
|
||||
@@ -66,8 +66,8 @@ Args parse_args(int argc, char** argv)
|
||||
struct option long_opts[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, 'V' },
|
||||
{ "filter", optional_argument, NULL, 'f' },
|
||||
{ "sep", optional_argument, NULL, 's' },
|
||||
{ "filter", required_argument, NULL, 'f' },
|
||||
{ "sep", required_argument, NULL, 's' },
|
||||
{ "case", no_argument, NULL, 'c' },
|
||||
{ "self", no_argument, NULL, 'e' },
|
||||
{ "unwrap", no_argument, NULL, 'u' },
|
||||
@@ -79,7 +79,7 @@ Args parse_args(int argc, char** argv)
|
||||
};
|
||||
|
||||
int c;
|
||||
while ((c = getopt_long(argc, argv, "hf:s:ceugmpV", long_opts, NULL)) != -1)
|
||||
while ((c = getopt_long(argc, argv, "hf:s:t:ceugmpV", long_opts, NULL)) != -1)
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
|
||||
71
examples/dyna/CMakeLists.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
project(dyna LANGUAGES C CXX)
|
||||
|
||||
option(TRACY_ENABLE "Enable Tracy" ON)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_COLOR_DIAGNOSTICS ON)
|
||||
|
||||
include(cmake/CPM.cmake)
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
if(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
add_compile_options(-fno-omit-frame-pointer)
|
||||
else()
|
||||
message(WARNING "Tracy builds should be RelWithDebInfo!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
CPMAddPackage(
|
||||
NAME glad
|
||||
VERSION 2.0.8
|
||||
GIT_REPOSITORY https://github.com/Dav1dde/glad.git
|
||||
GIT_TAG glad2
|
||||
)
|
||||
|
||||
add_subdirectory(${glad_SOURCE_DIR}/cmake ${CMAKE_CURRENT_BINARY_DIR}/glad)
|
||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../.. client/)
|
||||
|
||||
glad_add_library(glad_gl_core_33 STATIC API gl:core=3.3)
|
||||
|
||||
find_package(SDL3 REQUIRED)
|
||||
find_package(SDL3_image REQUIRED)
|
||||
|
||||
add_executable(dyna
|
||||
src/main.cpp
|
||||
src/datapath.cpp
|
||||
src/timer.cpp
|
||||
src/gfx.cpp
|
||||
src/texture.cpp
|
||||
src/entity.cpp
|
||||
src/world.cpp
|
||||
src/map.cpp
|
||||
src/player.cpp
|
||||
src/monster.cpp
|
||||
src/bomb.cpp
|
||||
src/bonus.cpp
|
||||
src/game.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(dyna
|
||||
PRIVATE
|
||||
glad_gl_core_33
|
||||
SDL3::SDL3
|
||||
SDL3_image::SDL3_image
|
||||
Tracy::TracyClient
|
||||
)
|
||||
|
||||
target_include_directories(dyna PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||
|
||||
# Mirror the data/ tree next to the executable so the game finds its assets
|
||||
# when launched from the build directory (paths are resolved via SDL_GetBasePath).
|
||||
add_custom_command(TARGET dyna POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data
|
||||
$<TARGET_FILE_DIR:dyna>/data
|
||||
COMMENT "Copying data/ next to dyna executable"
|
||||
)
|
||||
|
||||
file(GENERATE OUTPUT .gitignore CONTENT "*")
|
||||
7
examples/dyna/LICENSE
Normal file
@@ -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.
|
||||
24
examples/dyna/cmake/CPM.cmake
Normal 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})
|
||||
BIN
examples/dyna/data/gfx/Bomb.png
Normal file
|
After Width: | Height: | Size: 78 KiB |
BIN
examples/dyna/data/gfx/Player.png
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
examples/dyna/data/gfx/bonusy.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
examples/dyna/data/gfx/crate.png
Normal file
|
After Width: | Height: | Size: 768 B |
BIN
examples/dyna/data/gfx/menu.png
Normal file
|
After Width: | Height: | Size: 144 KiB |
BIN
examples/dyna/data/gfx/monster1.png
Normal file
|
After Width: | Height: | Size: 42 KiB |
BIN
examples/dyna/data/gfx/monster2.png
Normal file
|
After Width: | Height: | Size: 56 KiB |
BIN
examples/dyna/data/gfx/monster3.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
examples/dyna/data/gfx/portal.png
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
examples/dyna/data/gfx/sand.png
Normal file
|
After Width: | Height: | Size: 667 B |
BIN
examples/dyna/data/gfx/wall.png
Normal file
|
After Width: | Height: | Size: 490 B |
12
examples/dyna/data/levels/1
Normal file
@@ -0,0 +1,12 @@
|
||||
10 1 0 0
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/2
Normal file
@@ -0,0 +1,12 @@
|
||||
20 4 0 0
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#@#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/3
Normal file
@@ -0,0 +1,12 @@
|
||||
40 3 2 0
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/4
Normal file
@@ -0,0 +1,12 @@
|
||||
40 3 3 0
|
||||
@............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
12
examples/dyna/data/levels/5
Normal file
@@ -0,0 +1,12 @@
|
||||
40 2 4 1
|
||||
@............
|
||||
.###.#.#.###.
|
||||
.#.........#.
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.#.........#.
|
||||
.###.#.#.###.
|
||||
.............
|
||||
12
examples/dyna/data/levels/6
Normal file
@@ -0,0 +1,12 @@
|
||||
50 2 2 3
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.....#.#.....
|
||||
.#.###.###.#.
|
||||
.............
|
||||
.#.###.###.#.
|
||||
.....#.#.....
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#.#.#.#.#.#@
|
||||
12
examples/dyna/data/levels/7
Normal file
@@ -0,0 +1,12 @@
|
||||
60 3 3 3
|
||||
@............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
.###.#.#.###.
|
||||
.............
|
||||
.#.#.###.#.#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/8
Normal file
@@ -0,0 +1,12 @@
|
||||
60 5 3 3
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/9
Normal file
@@ -0,0 +1,12 @@
|
||||
90 5 5 5
|
||||
@............
|
||||
.#.........#.
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.............
|
||||
.#.........#.
|
||||
.............
|
||||
12
examples/dyna/data/levels/menu
Normal file
@@ -0,0 +1,12 @@
|
||||
30 4 4 4
|
||||
@............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
.#...#.#...#.
|
||||
.............
|
||||
.#.#.#.#.#.#.
|
||||
.............
|
||||
143
examples/dyna/src/bomb.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
44
examples/dyna/src/bomb.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
58
examples/dyna/src/bonus.cpp
Normal 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& ) {}
|
||||
|
||||
}
|
||||
20
examples/dyna/src/bonus.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
25
examples/dyna/src/datapath.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
14
examples/dyna/src/datapath.hpp
Normal 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 );
|
||||
|
||||
}
|
||||
39
examples/dyna/src/entity.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
52
examples/dyna/src/entity.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace dyna
|
||||
{
|
||||
|
||||
class Map;
|
||||
class World;
|
||||
|
||||
// Movement/state verbs shared by the player and monsters. In the C# source this
|
||||
// lived as Entity.Action; promoted to namespace scope so Game can refer to it.
|
||||
enum class Action
|
||||
{
|
||||
wait,
|
||||
up,
|
||||
down,
|
||||
left,
|
||||
right,
|
||||
death,
|
||||
place_bomb,
|
||||
appear
|
||||
};
|
||||
|
||||
// Base for everything that moves on the grid. Coordinates are in pixels
|
||||
// (64 per tile) and laid out top-left origin, matching entity.cs.
|
||||
class Entity
|
||||
{
|
||||
public:
|
||||
virtual ~Entity() = default;
|
||||
|
||||
virtual void set_action( Action a );
|
||||
|
||||
int getx() const { return x; }
|
||||
int gety() const { return y; }
|
||||
|
||||
virtual void draw() = 0;
|
||||
virtual void tick( World& world ) = 0;
|
||||
virtual void die( World& world ) = 0;
|
||||
|
||||
protected:
|
||||
bool can_move( Action a, const Map& map ) const;
|
||||
virtual bool killed( const Map& map ) const;
|
||||
|
||||
int x = 0, y = 0;
|
||||
std::int64_t action_start = 0;
|
||||
int delta = 0;
|
||||
Action action = Action::wait;
|
||||
int left = 0;
|
||||
};
|
||||
|
||||
}
|
||||
211
examples/dyna/src/game.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "game.hpp"
|
||||
|
||||
#include "datapath.hpp"
|
||||
#include "gfx.hpp"
|
||||
#include "map.hpp"
|
||||
#include "player.hpp"
|
||||
#include "timer.hpp"
|
||||
#include "world.hpp"
|
||||
|
||||
#include <SDL3/SDL.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace dyna
|
||||
{
|
||||
|
||||
namespace Game
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
struct TracySection
|
||||
{
|
||||
explicit TracySection( const char* name ) { Enter( name ); }
|
||||
explicit TracySection( const char* name, uint16_t category ) : idx( TracySectionEnterCategory( category, "%s", name ) ) {}
|
||||
~TracySection() { Leave(); }
|
||||
|
||||
void Enter( const char* name )
|
||||
{
|
||||
idx = TracySectionEnter( "%s", name );
|
||||
}
|
||||
|
||||
void Leave()
|
||||
{
|
||||
if( idx > 0 )
|
||||
{
|
||||
TracySectionLeave( idx );
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t idx;
|
||||
};
|
||||
|
||||
SDL_Keycode key = 0; // most recently pressed movement key
|
||||
bool help = false;
|
||||
|
||||
// Run one level to completion. Returns true if the player asked to quit the
|
||||
// whole application (window close), false if the level simply ended (death,
|
||||
// escape, or reaching the exit) and control should return to the caller.
|
||||
bool level_loop( World& world )
|
||||
{
|
||||
TracySection section( ( std::string( "Level " ) + world.name() ).c_str(), 1 );
|
||||
|
||||
Player* p = world.player();
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
SDL_Event ev;
|
||||
while( SDL_PollEvent( &ev ) )
|
||||
{
|
||||
if( ev.type == SDL_EVENT_QUIT )
|
||||
return true;
|
||||
|
||||
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
|
||||
{
|
||||
switch( ev.key.key )
|
||||
{
|
||||
case SDLK_ESCAPE:
|
||||
world.killed = true;
|
||||
return false;
|
||||
case SDLK_LEFT:
|
||||
key = SDLK_LEFT;
|
||||
p->move( Action::left );
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
key = SDLK_RIGHT;
|
||||
p->move( Action::right );
|
||||
break;
|
||||
case SDLK_UP:
|
||||
key = SDLK_UP;
|
||||
p->move( Action::up );
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
key = SDLK_DOWN;
|
||||
p->move( Action::down );
|
||||
break;
|
||||
case SDLK_SPACE:
|
||||
world.map().place_bomb( ( p->getx() + 32 ) / 64, ( p->gety() + 32 ) / 64 );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if( ev.type == SDL_EVENT_KEY_UP )
|
||||
{
|
||||
switch( ev.key.key )
|
||||
{
|
||||
case SDLK_LEFT:
|
||||
if( key == SDLK_LEFT ) p->move( Action::wait );
|
||||
break;
|
||||
case SDLK_RIGHT:
|
||||
if( key == SDLK_RIGHT ) p->move( Action::wait );
|
||||
break;
|
||||
case SDLK_UP:
|
||||
if( key == SDLK_UP ) p->move( Action::wait );
|
||||
break;
|
||||
case SDLK_DOWN:
|
||||
if( key == SDLK_DOWN ) p->move( Action::wait );
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::clear();
|
||||
|
||||
Timer::tick();
|
||||
|
||||
world.tick();
|
||||
world.draw();
|
||||
|
||||
Gfx::swap();
|
||||
|
||||
if( world.killed || world.next_level )
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Play through the levels in order. Returns true if the application should quit.
|
||||
bool new_game()
|
||||
{
|
||||
TracySection section( "In-game" );
|
||||
|
||||
int level = 1;
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
World world( data_path( "data/levels/" + std::to_string( level ) ), true );
|
||||
|
||||
if( level_loop( world ) )
|
||||
return true; // window closed
|
||||
|
||||
if( world.killed )
|
||||
return false; // died or escaped to the menu
|
||||
if( ++level >= 10 )
|
||||
return false; // cleared the last level
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void menu_loop()
|
||||
{
|
||||
constexpr const char* sectionName = "Main menu";
|
||||
TracySection section( sectionName );
|
||||
|
||||
World world( data_path( "data/levels/menu" ), false );
|
||||
|
||||
for( ;; )
|
||||
{
|
||||
SDL_Event ev;
|
||||
while( SDL_PollEvent( &ev ) )
|
||||
{
|
||||
if( ev.type == SDL_EVENT_QUIT )
|
||||
return;
|
||||
|
||||
if( ev.type == SDL_EVENT_KEY_DOWN && !ev.key.repeat )
|
||||
{
|
||||
switch( ev.key.key )
|
||||
{
|
||||
case SDLK_ESCAPE:
|
||||
return;
|
||||
case SDLK_SPACE:
|
||||
section.Leave();
|
||||
if( new_game() )
|
||||
return; // window closed during play
|
||||
section.Enter( sectionName );
|
||||
break;
|
||||
case SDLK_H:
|
||||
help = !help;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Gfx::clear();
|
||||
|
||||
Timer::tick();
|
||||
world.tick();
|
||||
world.draw();
|
||||
|
||||
if( help )
|
||||
Gfx::show_help();
|
||||
else
|
||||
Gfx::show_menu();
|
||||
|
||||
Gfx::swap();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Game
|
||||
|
||||
}
|
||||
14
examples/dyna/src/game.hpp
Normal 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
@@ -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
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
41
examples/dyna/src/main.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#include "game.hpp"
|
||||
#include "gfx.hpp"
|
||||
|
||||
#include <SDL3/SDL_main.h>
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <new>
|
||||
|
||||
// Route every heap allocation through Tracy so the profiler can track memory
|
||||
// usage. The default array forms (operator new[]/delete[]) and the nothrow
|
||||
// forms forward to these, so overriding the scalar operators covers them too.
|
||||
void* operator new( std::size_t count )
|
||||
{
|
||||
void* ptr = std::malloc( count );
|
||||
if( !ptr ) throw std::bad_alloc();
|
||||
TracyAlloc( ptr, count );
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void operator delete( void* ptr ) noexcept
|
||||
{
|
||||
TracyFree( ptr );
|
||||
std::free( ptr );
|
||||
}
|
||||
|
||||
int main( int /*argc*/, char* /*argv*/[] )
|
||||
{
|
||||
TracyNoop;
|
||||
|
||||
TracySectionSetup( 0, "Game state" );
|
||||
TracySectionSetup( 1, "Level progression" );
|
||||
|
||||
if( !dyna::Init::all() )
|
||||
return 1;
|
||||
|
||||
dyna::Game::menu_loop();
|
||||
|
||||
dyna::Init::shutdown();
|
||||
return 0;
|
||||
}
|
||||
334
examples/dyna/src/map.cpp
Normal file
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
227
examples/dyna/src/monster.cpp
Normal 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 );
|
||||
}
|
||||
|
||||
}
|
||||
41
examples/dyna/src/monster.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
127
examples/dyna/src/player.cpp
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
27
examples/dyna/src/player.hpp
Normal 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;
|
||||
};
|
||||
|
||||
}
|
||||
221
examples/dyna/src/texture.cpp
Normal 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
91
examples/dyna/src/texture.hpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
51
examples/dyna/src/timer.cpp
Normal 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() );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
25
examples/dyna/src/timer.hpp
Normal 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)
|
||||
}
|
||||
|
||||
}
|
||||
40
examples/dyna/src/world.cpp
Normal 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();
|
||||
}
|
||||
|
||||
}
|
||||
46
examples/dyna/src/world.hpp
Normal 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_;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -2,7 +2,20 @@
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/tracy">
|
||||
<comment>Tracy Profiler trace file</comment>
|
||||
<comment xml:lang="ar">ملف تتبع لمحلل الأداء Tracy</comment>
|
||||
<comment xml:lang="de">Trace-Datei des Tracy-Profilers</comment>
|
||||
<comment xml:lang="es">Archivo de traza del perfilador Tracy</comment>
|
||||
<comment xml:lang="fr">Fichier de trace du profileur Tracy</comment>
|
||||
<comment xml:lang="hi">Tracy प्रोफ़ाइलर ट्रेस फ़ाइल</comment>
|
||||
<comment xml:lang="it">File di traccia del profiler Tracy</comment>
|
||||
<comment xml:lang="ja">Tracy プロファイラーのトレースファイル</comment>
|
||||
<comment xml:lang="pl">Zrzut sesji profilera Tracy</comment>
|
||||
<comment xml:lang="pt">Ficheiro de rastreio do perfilador Tracy</comment>
|
||||
<comment xml:lang="pt_BR">Arquivo de rastreamento do perfilador Tracy</comment>
|
||||
<comment xml:lang="ru">Файл трассировки профилировщика Tracy</comment>
|
||||
<comment xml:lang="uk">Файл трасування профілювальника Tracy</comment>
|
||||
<comment xml:lang="zh_CN">Tracy 性能分析器跟踪文件</comment>
|
||||
<comment xml:lang="zh_TW">Tracy 效能分析器追蹤檔案</comment>
|
||||
<icon name="application-tracy"/>
|
||||
<magic>
|
||||
<match type="string" value="tlZ\x04" offset="0"/>
|
||||
|
||||
@@ -1,12 +1,53 @@
|
||||
[Desktop Entry]
|
||||
Version=1.0
|
||||
Version=1.5
|
||||
Type=Application
|
||||
Name=Tracy Profiler
|
||||
GenericName=Code profiler
|
||||
GenericName[ar]=محلل أداء الشيفرة البرمجية
|
||||
GenericName[de]=Code-Profiler
|
||||
GenericName[es]=Perfilador de código
|
||||
GenericName[fr]=Profileur de code
|
||||
GenericName[hi]=कोड प्रोफ़ाइलर
|
||||
GenericName[it]=Profiler del codice
|
||||
GenericName[ja]=コードプロファイラー
|
||||
GenericName[pl]=Profiler kodu
|
||||
GenericName[pt]=Analisador de desempenho de código
|
||||
GenericName[pt_BR]=Analisador de desempenho de código
|
||||
GenericName[ru]=Профилировщик кода
|
||||
GenericName[uk]=Профілювальник коду
|
||||
GenericName[zh_CN]=代码性能分析器
|
||||
GenericName[zh_TW]=程式碼效能分析器
|
||||
Comment=Examine code to see where it is slow
|
||||
Comment[pl]=Znajdowanie wolno wykonującego się kodu
|
||||
Exec=/usr/bin/tracy %f
|
||||
Comment[ar]=افحص الشيفرة البرمجية لمعرفة مواضع البطء
|
||||
Comment[de]=Code untersuchen, um langsame Stellen zu finden
|
||||
Comment[es]=Examine el código para ver dónde es lento
|
||||
Comment[fr]=Examiner le code pour voir où il est lent
|
||||
Comment[hi]=कोड की जाँच करें कि वह कहाँ धीमा है
|
||||
Comment[it]=Esamina il codice per vedere dove è lento
|
||||
Comment[ja]=コードを調べて遅い箇所を見つける
|
||||
Comment[pl]=Przeanalizuj kod, aby znaleźć wolne miejsca
|
||||
Comment[pt]=Examine o código para ver onde é lento
|
||||
Comment[pt_BR]=Examine o código para ver onde ele é lento
|
||||
Comment[ru]=Изучите код, чтобы найти медленные места
|
||||
Comment[uk]=Досліджуйте код, щоб знайти повільні місця
|
||||
Comment[zh_CN]=检查代码,找出运行缓慢的部分
|
||||
Comment[zh_TW]=檢查程式碼,找出執行緩慢的部分
|
||||
Keywords=profiler;performance;tracing;frame;instrumentation;
|
||||
Keywords[ar]=محلل;أداء;تتبع;إطار;قياس;
|
||||
Keywords[de]=Profiler;Leistung;Tracing;Frame;Instrumentierung;
|
||||
Keywords[es]=perfilador;rendimiento;trazado;fotograma;instrumentación;
|
||||
Keywords[fr]=profileur;performance;traçage;trame;instrumentation;
|
||||
Keywords[hi]=प्रोफ़ाइलर;प्रदर्शन;ट्रेसिंग;फ़्रेम;इंस्ट्रुमेंटेशन;
|
||||
Keywords[it]=profiler;prestazioni;tracciamento;frame;strumentazione;
|
||||
Keywords[ja]=プロファイラー;パフォーマンス;トレース;フレーム;計測;
|
||||
Keywords[pl]=profiler;wydajność;śledzenie;klatka;instrumentacja;
|
||||
Keywords[pt]=perfilador;desempenho;rastreio;fotograma;instrumentação;
|
||||
Keywords[pt_BR]=perfilador;desempenho;rastreamento;quadro;instrumentação;
|
||||
Keywords[ru]=профилировщик;производительность;трассировка;кадр;инструментирование;
|
||||
Keywords[uk]=профілювальник;продуктивність;трасування;кадр;інструментування;
|
||||
Keywords[zh_CN]=分析器;性能;跟踪;帧;插桩;
|
||||
Keywords[zh_TW]=分析器;效能;追蹤;影格;插樁;
|
||||
Exec=tracy-profiler %f
|
||||
Icon=tracy
|
||||
Terminal=false
|
||||
Categories=Development;Profiling;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Tracy MCP eval guide
|
||||
|
||||
This document covers the bindings-layer detail that the curated catalog
|
||||
(`tracy://catalog`) and analysis guidance (`tracy://prompt`) do not.
|
||||
This document covers the bindings-layer detail that the analysis
|
||||
guidance (`tracy://prompt`) does not.
|
||||
|
||||
## ctx
|
||||
|
||||
@@ -21,8 +21,15 @@ data surface. Common entry points:
|
||||
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
|
||||
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
|
||||
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
|
||||
- Sections: `get_sections()` — timed code sections from
|
||||
`TracySectionEnter`/`TracySectionLeave` instrumentation. Returns a list of
|
||||
`{category, start, end, text}` dicts (start/end in ns); `category` is the
|
||||
section's named group (multiple unrelated section sets can coexist in one
|
||||
capture).
|
||||
- Capture metadata: `get_capture_name()`, `get_capture_program()`,
|
||||
`get_first_time()`, `get_last_time()`, `get_resolution()`, `get_host_info()`
|
||||
- Readiness: `is_background_done()` — see "Instance lifecycle" below before
|
||||
querying zone/symbol stats right after `load_capture`.
|
||||
|
||||
Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
|
||||
|
||||
@@ -41,24 +48,23 @@ Run `print([m for m in dir(ctx) if not m.startswith('_')])` for the full list.
|
||||
- Source-location IDs from `get_all_zone_source_locations()` are the join key
|
||||
between zone-name lookups and per-callsite queries.
|
||||
|
||||
## Translating catalog entries to ctx Python
|
||||
## Common query patterns
|
||||
|
||||
The catalog (`tracy://catalog`) lists curated queries. Each maps to a small
|
||||
Python snippet:
|
||||
Small Python snippets for the queries you'll reach for most often:
|
||||
|
||||
```python
|
||||
# zone_list — top 10 hottest zones by total time
|
||||
# top 10 hottest zones by total time
|
||||
top = sorted(ctx.get_all_zone_stats().items(),
|
||||
key=lambda kv: kv[1].total, reverse=True)[:10]
|
||||
for k, v in top:
|
||||
print(f"{v.total/1e6:.2f}ms count={v.count} {k}")
|
||||
|
||||
# frame_list — primary frame set timing
|
||||
# primary frame set timing
|
||||
times = ctx.get_frame_times() # ns per frame
|
||||
print(f"frames={len(times)} avg={sum(times)/len(times)/1e6:.2f}ms "
|
||||
f"p99={sorted(times)[int(len(times)*0.99)]/1e6:.2f}ms")
|
||||
|
||||
# zone_stats for a named zone — find the srcloc id, then drill in
|
||||
# stats for a named zone — find the srcloc id, then drill in
|
||||
import re
|
||||
matches = [k for k in ctx.get_all_zone_stats() if k.startswith("MyFunc ")]
|
||||
sid = int(re.search(r"<(\d+)>$", matches[0]).group(1))
|
||||
@@ -70,3 +76,40 @@ stats = ctx.get_zone_stats(sid)
|
||||
For long-running queries pass `async_mode=True` to `eval`; it returns
|
||||
`{task_id, status: "running"}`. Poll with the `task` tool
|
||||
(`action="poll", task_id=...`).
|
||||
|
||||
## Instance lifecycle
|
||||
|
||||
`tracy_mcp.py` runs as a long-lived singleton process shared across every
|
||||
MCP client (all VS Code windows), and every `live_connect`/`load_capture`
|
||||
instance holds its *entire* trace — zones, messages, callstacks, memory
|
||||
events — resident in memory for as long as that process runs. Nothing
|
||||
frees it just because your conversation ends.
|
||||
|
||||
- `load_capture` returns before zone/symbol statistics finish building — a
|
||||
background thread populates them after the file itself is read. Check
|
||||
`is_background_done()` (or `background_done` in `list_instances`) before
|
||||
relying on `get_all_zone_stats()` and similar stats-based queries; they
|
||||
can return empty or partial results until it's true. For small captures
|
||||
this finishes before you'd notice; for multi-GB ones it can take seconds.
|
||||
- Call `unload_capture(instance_id)` as soon as you're done analyzing a
|
||||
capture. Don't rely on automatic eviction as your primary cleanup path —
|
||||
treat it as a backstop for forgotten sessions, not a substitute.
|
||||
- `live_connect` caps a live session at `TRACY_MCP_LIVE_MEMORY_LIMIT_MB`
|
||||
(default 8192, override per-call or set 0 to disable) — a long-lived
|
||||
session on a busy target otherwise grows unbounded and can OOM-kill the
|
||||
whole server process. Hitting the cap disconnects that instance cleanly;
|
||||
data collected so far stays queryable and `save_trace`-able.
|
||||
- `list_instances` reports `idle_seconds`, `connected`, and `background_done`
|
||||
per instance; use it to spot stale ones before starting a new session,
|
||||
especially if you're about to load several captures for comparison.
|
||||
- The server provides three backstops, all configurable via environment
|
||||
variables at startup: it evicts the least-recently-used *evictable*
|
||||
instance (disconnected live sessions, or any loaded file capture — never
|
||||
a still-connected live one) once the instance count reaches
|
||||
`TRACY_MCP_MAX_INSTANCES` (default 4); it drops a disconnected live
|
||||
instance that's sat idle for `TRACY_MCP_DISCONNECTED_TTL_S` (default
|
||||
1800s / 30 min) so a session is still there for analysis right after the
|
||||
target disconnects, but doesn't linger forever if forgotten; and it drops
|
||||
a file-loaded capture that's sat idle for `TRACY_MCP_FILE_IDLE_TTL_S`
|
||||
(default 1800s) — safe to let expire since it's already durably on disk,
|
||||
just `load_capture` it again.
|
||||
|
||||
1
extra/mcp/requirements.txt
Normal file
@@ -0,0 +1 @@
|
||||
mcp>=2.0.0,<3
|
||||
@@ -1,6 +1,8 @@
|
||||
#!/bin/sh
|
||||
# Start the Tracy MCP server.
|
||||
#
|
||||
# First run: pip install -r requirements.txt (next to this script).
|
||||
#
|
||||
# Set PYTHONPATH to the directory containing TracyServerBindings.so/.pyd.
|
||||
# Adjust the Release/Debug suffix to match your CMake build configuration.
|
||||
PYTHONPATH="${PYTHONPATH:+$PYTHONPATH:}$(dirname "$0")/../../build/python/Release"
|
||||
|
||||
@@ -5,6 +5,7 @@ import asyncio
|
||||
import atexit
|
||||
import builtins
|
||||
import concurrent.futures
|
||||
import faulthandler
|
||||
import glob
|
||||
import io
|
||||
import os
|
||||
@@ -14,10 +15,12 @@ import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import uuid
|
||||
from contextlib import redirect_stdout
|
||||
from contextlib import asynccontextmanager, redirect_stdout
|
||||
|
||||
import mcp.server.fastmcp as fastmcp
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
|
||||
# Suppress noisy ASGI shutdown errors known to occur with SSE and Control-C.
|
||||
# These occur when Starlette attempts to send a 500 error after the loop is cancelled
|
||||
@@ -29,7 +32,13 @@ logging.getLogger("starlette").setLevel(logging.CRITICAL)
|
||||
_HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
_PORT_FILE = os.path.join(_HERE, "tracy_mcp.port")
|
||||
_PID_FILE = os.path.join(_HERE, "tracy_mcp.pid")
|
||||
_CRASH_LOG_FILE = os.path.join(_HERE, "tracy_mcp.crash.log")
|
||||
_PREFERRED_PORT = int(os.environ.get("TRACY_MCP_PORT", "47380"))
|
||||
_TRANSPORT = os.environ.get("TRACY_MCP_TRANSPORT", "streamable-http").strip().lower()
|
||||
# MCPServer.run()'s own defaults -- tracked here too since v2's Settings no
|
||||
# longer exposes them for us to read back (SDK v1 -> v2 migration).
|
||||
_SSE_PATH = "/sse"
|
||||
_STREAMABLE_HTTP_PATH = "/mcp"
|
||||
|
||||
# Shared documentation surfaces. system.prompt.md is Tracy Assist's source
|
||||
# system prompt; exposing it as an MCP resource keeps analysis guidance in
|
||||
@@ -155,10 +164,26 @@ async def _listen_broadcasts(timeout_s: float = 1.5) -> list[dict]:
|
||||
return list(seen.values())
|
||||
|
||||
|
||||
def _is_our_server_running() -> tuple[bool, int]:
|
||||
def _http_ping(port: int, timeout_s: float = 2.0) -> bool:
|
||||
"""Confirms the server answers HTTP, not just that its PID exists — a
|
||||
deadlocked process still passes os.kill(pid, 0). Any response, even an
|
||||
error one, proves the transport is alive; only a timeout or refused
|
||||
connection means it's not.
|
||||
"""
|
||||
Check the PID file to see if our server is already running.
|
||||
Returns (running, port). Uses os.kill(pid, 0) to confirm the process is alive.
|
||||
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
|
||||
try:
|
||||
urllib.request.urlopen(f"http://127.0.0.1:{port}{path}", timeout=timeout_s)
|
||||
return True
|
||||
except urllib.error.HTTPError:
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _is_our_server_running() -> tuple[bool, int]:
|
||||
"""Check whether our server is running: PID alive AND responding to
|
||||
an HTTP self-ping (a hung process still passes os.kill(pid, 0)).
|
||||
Returns (running, port).
|
||||
"""
|
||||
try:
|
||||
with open(_PID_FILE) as f:
|
||||
@@ -166,9 +191,19 @@ def _is_our_server_running() -> tuple[bool, int]:
|
||||
with open(_PORT_FILE) as f:
|
||||
port = int(f.read().strip())
|
||||
os.kill(pid, 0) # raises OSError if process is gone
|
||||
return True, port
|
||||
except Exception:
|
||||
return False, 0
|
||||
if not _http_ping(port):
|
||||
print(
|
||||
f"Tracy MCP process {pid} is alive (PID check passed) but isn't "
|
||||
f"responding on port {port} within {2.0:.0f}s -- likely hung/deadlocked, "
|
||||
f"not just busy. Starting a fresh server on a new port; kill PID {pid} "
|
||||
f"manually once you're able to (it's still holding the port and the "
|
||||
f"TracyServerBindings.pyd file lock).",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return False, 0
|
||||
return True, port
|
||||
|
||||
|
||||
def _find_free_port() -> int:
|
||||
@@ -218,8 +253,11 @@ except ImportError:
|
||||
except ImportError:
|
||||
tracy_server = None
|
||||
|
||||
mcp_server = fastmcp.FastMCP("Tracy Profiler")
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
||||
_MAX_INSTANCES = int(os.environ.get("TRACY_MCP_MAX_INSTANCES", "4"))
|
||||
_DISCONNECTED_TTL_S = float(os.environ.get("TRACY_MCP_DISCONNECTED_TTL_S", "1800"))
|
||||
_FILE_IDLE_TTL_S = float(os.environ.get("TRACY_MCP_FILE_IDLE_TTL_S", "1800"))
|
||||
_SWEEP_INTERVAL_S = float(os.environ.get("TRACY_MCP_SWEEP_INTERVAL_S", "300"))
|
||||
_DEFAULT_LIVE_MEMORY_LIMIT_MB = int(os.environ.get("TRACY_MCP_LIVE_MEMORY_LIMIT_MB", "8192"))
|
||||
|
||||
|
||||
class Task:
|
||||
@@ -239,7 +277,126 @@ class TracyInstance:
|
||||
self.worker = worker
|
||||
self.path = None
|
||||
self.mtime = None
|
||||
self.last_used = time.time()
|
||||
# Wall-clock time the instance was first observed disconnected (live
|
||||
# instances only). None while connected or for file-loaded captures.
|
||||
self.disconnected_since: float | None = None
|
||||
|
||||
def touch(self) -> None:
|
||||
self.last_used = time.time()
|
||||
|
||||
def is_evictable(self) -> bool:
|
||||
"""True if this instance is safe to drop without losing in-progress data.
|
||||
|
||||
A live instance still connected is actively recording — never evict it
|
||||
automatically. Everything else (file captures, disconnected live
|
||||
instances) is fair game for the LRU cap.
|
||||
"""
|
||||
if self.worker is not None and self.path is None:
|
||||
try:
|
||||
return not self.worker.is_connected()
|
||||
except Exception:
|
||||
return True
|
||||
return True
|
||||
|
||||
|
||||
def _shutdown_worker(worker: object | None) -> None:
|
||||
if worker is None:
|
||||
return
|
||||
try:
|
||||
worker.shutdown()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def _evict_idle() -> list[str]:
|
||||
"""Unload instances idle past their TTL: disconnected live instances
|
||||
past _DISCONNECTED_TTL_S, file-loaded captures past _FILE_IDLE_TTL_S
|
||||
(safe — already durably on disk). Connected live instances are never
|
||||
touched here.
|
||||
"""
|
||||
now = time.time()
|
||||
evicted = []
|
||||
for name, inst in list(instances.items()):
|
||||
if inst.path is not None:
|
||||
if now - inst.last_used >= _FILE_IDLE_TTL_S:
|
||||
del instances[name]
|
||||
_shutdown_worker(inst.worker)
|
||||
evicted.append(name)
|
||||
continue
|
||||
if inst.worker is None:
|
||||
continue
|
||||
try:
|
||||
connected = inst.worker.is_connected()
|
||||
except Exception:
|
||||
connected = False
|
||||
if connected:
|
||||
inst.disconnected_since = None
|
||||
continue
|
||||
if inst.disconnected_since is None:
|
||||
inst.disconnected_since = now
|
||||
continue
|
||||
if now - inst.disconnected_since >= _DISCONNECTED_TTL_S:
|
||||
del instances[name]
|
||||
_shutdown_worker(inst.worker)
|
||||
evicted.append(name)
|
||||
return evicted
|
||||
|
||||
|
||||
def _evict_for_capacity(exclude: str | None = None) -> str | None:
|
||||
"""If at/over _MAX_INSTANCES, drop the least-recently-used evictable
|
||||
instance to make room for a new one. Returns the evicted name, if any."""
|
||||
if len(instances) < _MAX_INSTANCES:
|
||||
return None
|
||||
candidates = [
|
||||
inst for name, inst in instances.items()
|
||||
if name != exclude and inst.is_evictable()
|
||||
]
|
||||
if not candidates:
|
||||
return None
|
||||
victim = min(candidates, key=lambda inst: inst.last_used)
|
||||
del instances[victim.name]
|
||||
_shutdown_worker(victim.worker)
|
||||
return victim.name
|
||||
|
||||
|
||||
async def _sweep_loop() -> None:
|
||||
"""Periodic eviction sweep, doubling as a heartbeat — distinguishes a
|
||||
crashed process (see faulthandler) from a hung one (stops producing
|
||||
these lines). flush=True since a hang is exactly when buffering would
|
||||
hide the last known-good timestamp.
|
||||
"""
|
||||
start = time.time()
|
||||
while True:
|
||||
await asyncio.sleep(_SWEEP_INTERVAL_S)
|
||||
try:
|
||||
evicted = _evict_idle()
|
||||
except Exception:
|
||||
evicted = None
|
||||
print(
|
||||
f"[heartbeat] uptime={int(time.time() - start)}s "
|
||||
f"instances={len(instances)} tasks={len(tasks)} "
|
||||
f"evicted={evicted or 'none'}",
|
||||
file=sys.stderr,
|
||||
flush=True,
|
||||
)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
async def _lifespan(_server: MCPServer):
|
||||
sweep_task = asyncio.create_task(_sweep_loop())
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
sweep_task.cancel()
|
||||
try:
|
||||
await sweep_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
|
||||
|
||||
mcp_server = MCPServer("Tracy Profiler", lifespan=_lifespan)
|
||||
executor = concurrent.futures.ThreadPoolExecutor(max_workers=4)
|
||||
|
||||
instances: dict[str, TracyInstance] = {}
|
||||
tasks: dict[str, Task] = {}
|
||||
@@ -258,8 +415,7 @@ def _prompt_resource() -> str:
|
||||
@mcp_server.resource("tracy://eval-guide")
|
||||
def _eval_guide_resource() -> str:
|
||||
"""Bindings-layer guide for the eval tool: ctx object model, time units,
|
||||
source-location ID semantics, and worked examples translating catalog
|
||||
entries into ctx Python."""
|
||||
source-location ID semantics, and worked examples of common ctx queries."""
|
||||
return _read_text(_EVAL_GUIDE_PATH)
|
||||
|
||||
|
||||
@@ -273,13 +429,34 @@ async def list_captures() -> list[str]:
|
||||
|
||||
@mcp_server.tool()
|
||||
async def list_instances() -> list[dict]:
|
||||
"""List all loaded Tracy instances and captures with metadata."""
|
||||
"""List all loaded Tracy instances and captures with metadata.
|
||||
|
||||
Each full trace stays resident in memory for as long as this server
|
||||
process runs (it's a singleton shared across all MCP clients) until
|
||||
unloaded. `idle_seconds` is how long since this instance was last used
|
||||
via `eval`/`save_trace` — call `unload_capture` on ones you no longer
|
||||
need instead of waiting for automatic eviction. Automatic eviction kicks
|
||||
in at the `TRACY_MCP_MAX_INSTANCES` cap (LRU, connected live instances
|
||||
are never evicted), after a disconnected live instance sits idle past
|
||||
`TRACY_MCP_DISCONNECTED_TTL_S`, or after a file-loaded capture sits idle
|
||||
past `TRACY_MCP_FILE_IDLE_TTL_S` — it's already durably on disk, so
|
||||
nothing is lost; just `load_capture` it again.
|
||||
|
||||
`background_done: false` means a just-loaded capture is still building
|
||||
zone/symbol statistics on a background thread — `get_all_zone_stats`
|
||||
and similar stats-based `eval` queries can return empty or partial
|
||||
results until this flips to true.
|
||||
"""
|
||||
now = time.time()
|
||||
return [
|
||||
{
|
||||
"id": name,
|
||||
"path": inst.path,
|
||||
"mtime": inst.mtime,
|
||||
"live": inst.path is None
|
||||
"live": inst.path is None,
|
||||
"connected": inst.worker.is_connected() if inst.path is None and inst.worker else None,
|
||||
"background_done": inst.worker.is_background_done() if inst.worker else None,
|
||||
"idle_seconds": now - inst.last_used,
|
||||
}
|
||||
for name, inst in instances.items()
|
||||
]
|
||||
@@ -311,11 +488,25 @@ async def discover_instances(port_range: str = "8086-8095") -> list[dict]:
|
||||
|
||||
|
||||
@mcp_server.tool()
|
||||
async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str | None = None) -> str:
|
||||
async def live_connect(
|
||||
address: str = "127.0.0.1",
|
||||
port: int = 8086,
|
||||
alias: str | None = None,
|
||||
memory_limit_mb: int | None = None,
|
||||
) -> str:
|
||||
"""
|
||||
Connect to a live running Tracy-instrumented application.
|
||||
|
||||
Wraps Worker(addr, port, memoryLimit=-1). Returns the instance_id.
|
||||
Wraps Worker(addr, port, memoryLimit). A long-lived session on a busy
|
||||
target grows unbounded otherwise -- every zone/message/memory event
|
||||
stays resident until disconnect, which can OOM-kill this whole process
|
||||
rather than just the one instance. memory_limit_mb defaults to
|
||||
TRACY_MCP_LIVE_MEMORY_LIMIT_MB (8192 if unset); pass 0 to disable.
|
||||
Hitting the limit disconnects that Worker cleanly (Tracy's own
|
||||
QueryTerminate path) -- already-collected data stays queryable and
|
||||
save_trace-able, it just stops growing.
|
||||
|
||||
Returns the instance_id.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
@@ -337,8 +528,10 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
|
||||
f"the target against a matching Tracy version."
|
||||
)
|
||||
|
||||
limit_mb = _DEFAULT_LIVE_MEMORY_LIMIT_MB if memory_limit_mb is None else memory_limit_mb
|
||||
memory_limit = limit_mb * 1024 * 1024 if limit_mb > 0 else -1
|
||||
try:
|
||||
w = tracy_server.Worker(address, port)
|
||||
w = tracy_server.Worker(address, port, memory_limit)
|
||||
except Exception as e:
|
||||
return f"Failed to connect: {str(e)}"
|
||||
|
||||
@@ -382,9 +575,14 @@ async def live_connect(address: str = "127.0.0.1", port: int = 8086, alias: str
|
||||
)
|
||||
|
||||
name = alias or f"live_{address}_{port}"
|
||||
if name in instances:
|
||||
_shutdown_worker(instances[name].worker)
|
||||
evicted = _evict_for_capacity(exclude=name)
|
||||
instances[name] = TracyInstance(name, w)
|
||||
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
|
||||
limit_note = f"{limit_mb}MB memory limit" if limit_mb > 0 else "no memory limit"
|
||||
return (
|
||||
f"Connected to live instance as '{name}'. "
|
||||
f"Connected to live instance as '{name}'{note} ({limit_note}). "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
@@ -404,6 +602,12 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
|
||||
If you don't already have a path, call `list_captures` first — it lists
|
||||
.tracy files in the TRACY_CAPTURES_DIR environment directory.
|
||||
|
||||
Loading returns before zone/symbol statistics finish building — a
|
||||
background thread populates them after the file itself is read. Check
|
||||
`background_done` in `list_instances` (or poll it) before relying on
|
||||
`get_all_zone_stats` and similar stats-based `eval` queries; they can
|
||||
return empty or partial results until it's true.
|
||||
"""
|
||||
if not tracy_server:
|
||||
return "Error: Tracy Server bindings not found."
|
||||
@@ -418,7 +622,11 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
if name in instances:
|
||||
inst = instances[name]
|
||||
if inst.path == path and inst.mtime == mtime:
|
||||
inst.touch()
|
||||
return f"Instance '{name}' is already loaded and up to date."
|
||||
_shutdown_worker(inst.worker)
|
||||
|
||||
evicted = _evict_for_capacity(exclude=name)
|
||||
|
||||
f = tracy_server.open_file(path)
|
||||
w = tracy_server.create_worker_from_file(f)
|
||||
@@ -426,8 +634,9 @@ async def load_capture(path: str, alias: str | None = None) -> str:
|
||||
inst.path = path
|
||||
inst.mtime = mtime
|
||||
instances[name] = inst
|
||||
note = f" (evicted idle instance '{evicted}' to stay under the {_MAX_INSTANCES}-instance cap)" if evicted else ""
|
||||
return (
|
||||
f"Loaded as '{name}'. "
|
||||
f"Loaded as '{name}'{note}. "
|
||||
f"Before your first eval, read resources tracy://prompt "
|
||||
f"(analysis guidance) and tracy://eval-guide (ctx object model, "
|
||||
f"ns time units, srcloc IDs)."
|
||||
@@ -476,6 +685,7 @@ async def save_trace(
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
instance.touch()
|
||||
|
||||
if not os.path.isabs(path):
|
||||
if captures_dir and os.path.basename(path) == path:
|
||||
@@ -542,9 +752,18 @@ async def _execute_save(worker: object, path: str, level: int, streams: int, fi_
|
||||
|
||||
@mcp_server.tool()
|
||||
async def unload_capture(instance_id: str) -> str:
|
||||
"""Unload a Tracy instance and release its memory."""
|
||||
"""Unload a Tracy instance and release its memory.
|
||||
|
||||
Prefer calling this as soon as you're done analyzing a capture — every
|
||||
loaded instance holds the *entire* trace (zones, messages, callstacks,
|
||||
memory events) in memory for as long as this server process runs, and
|
||||
the server is a long-lived singleton shared across all MCP clients.
|
||||
Automatic eviction exists as a backstop (see `list_instances`), not a
|
||||
substitute for unloading what you no longer need.
|
||||
"""
|
||||
if instance_id in instances:
|
||||
del instances[instance_id]
|
||||
inst = instances.pop(instance_id)
|
||||
_shutdown_worker(inst.worker)
|
||||
return f"Instance '{instance_id}' unloaded."
|
||||
return f"Instance '{instance_id}' not found."
|
||||
|
||||
@@ -566,6 +785,7 @@ async def tracy_eval(code: str, instance_id: str, async_mode: bool = False) -> o
|
||||
instance = instances[instance_id]
|
||||
if not instance.worker:
|
||||
return f"Error: Instance '{instance_id}' has no worker."
|
||||
instance.touch()
|
||||
|
||||
if not async_mode:
|
||||
return await _execute_eval(code, instance.worker)
|
||||
@@ -675,8 +895,22 @@ async def shutdown_server() -> str:
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# Enabled before anything touches the native bindings, so a genuine
|
||||
# fatal crash (segfault etc.) writes a thread-state traceback to disk
|
||||
# before the process dies — works on Windows via
|
||||
# SetUnhandledExceptionFilter, same mechanism as POSIX signals.
|
||||
_crash_log_fh = open(_CRASH_LOG_FILE, "a", encoding="utf-8")
|
||||
faulthandler.enable(file=_crash_log_fh, all_threads=True)
|
||||
|
||||
atexit.register(_cleanup_pid_files)
|
||||
|
||||
if _TRANSPORT not in ("sse", "streamable-http"):
|
||||
print(
|
||||
"TRACY_MCP_TRANSPORT must be 'sse' or 'streamable-http'.",
|
||||
file=sys.stderr,
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
running, existing_port = _is_our_server_running()
|
||||
if running:
|
||||
print(
|
||||
@@ -689,12 +923,18 @@ if __name__ == "__main__":
|
||||
port = _find_free_port()
|
||||
_write_pid_and_port(port)
|
||||
|
||||
print(f"Tracy MCP listening on http://127.0.0.1:{port}/sse", file=sys.stderr)
|
||||
path = _SSE_PATH if _TRANSPORT == "sse" else _STREAMABLE_HTTP_PATH
|
||||
print(f"Tracy MCP listening on http://127.0.0.1:{port}{path}", file=sys.stderr)
|
||||
|
||||
mcp_server.settings.host = "127.0.0.1"
|
||||
mcp_server.settings.port = port
|
||||
# v2's MCPServer takes transport options on run(), not the constructor
|
||||
# or a mutable settings object (SDK v1 -> v2 migration).
|
||||
run_kwargs = {"host": "127.0.0.1", "port": port}
|
||||
if _TRANSPORT == "sse":
|
||||
run_kwargs["sse_path"] = _SSE_PATH
|
||||
else:
|
||||
run_kwargs["streamable_http_path"] = _STREAMABLE_HTTP_PATH
|
||||
try:
|
||||
mcp_server.run(transport="sse")
|
||||
mcp_server.run(transport=_TRANSPORT, **run_kwargs)
|
||||
except KeyboardInterrupt:
|
||||
print("\nTracy MCP server stopped.", file=sys.stderr)
|
||||
sys.exit(0)
|
||||
|
||||
21
extra/tracy-version.sh
Executable file
@@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
# Derive the project version from the single source of truth:
|
||||
# public/common/TracyVersion.hpp. Prints X.Y.Z, exits nonzero on failure.
|
||||
|
||||
header="$1"
|
||||
|
||||
[ -r "$header" ] || {
|
||||
echo "tracy-version: cannot read $header" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
major=$(sed -n 's/.*Major = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
minor=$(sed -n 's/.*Minor = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
patch=$(sed -n 's/.*Patch = \([0-9][0-9]*\).*/\1/p' "$header")
|
||||
|
||||
if [ -n "$major" ] && [ -n "$minor" ] && [ -n "$patch" ]; then
|
||||
printf '%s.%s.%s\n' "$major" "$minor" "$patch"
|
||||
else
|
||||
echo "tracy-version: could not parse version from $header" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
version_header="../public/common/TracyVersion.hpp"
|
||||
|
||||
major=$(grep -o -E 'Major = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
|
||||
minor=$(grep -o -E 'Minor = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
|
||||
patch=$(grep -o -E 'Patch = [0-9]+' "$version_header" | awk -F '= ' '{print $2}')
|
||||
|
||||
version="${major}.${minor}.${patch}"
|
||||
|
||||
# the extension is required for macOS's outdated sed
|
||||
sed -i.bak "s/version: '[0-9]*\.[0-9]*\.[0-9]*'/version: '$version'/g" ../meson.build
|
||||
rm ../meson.build.bak
|
||||
31
import/CMakePresets.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 10,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/build-${presetName}",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug configuration",
|
||||
"inherits": "common",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release configuration",
|
||||
"inherits": "common",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -12,12 +12,15 @@ sed -i -e 's@\\LMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\textsigma@σ@g' _tmp.tex
|
||||
|
||||
# Resolve \circled{} markers and lstlisting escapeinside (@...@) snippets, which
|
||||
# pandoc would otherwise emit verbatim or drop, to their Unicode equivalents.
|
||||
sed -i -e 's|@\\circled{a}@|(a)|g' -e 's|@\\circled{b}@|(b)|g' -e 's|@\\circled{c}@|(c)|g' _tmp.tex
|
||||
sed -i -e 's|\\circled{a}|(a)|g' -e 's|\\circled{b}|(b)|g' -e 's|\\circled{c}|(c)|g' _tmp.tex
|
||||
sed -i -e 's|@\\ldots@|…|g' _tmp.tex
|
||||
# pandoc doens't recognize this lstlisting option and emits it verbatim
|
||||
sed -i -e 's|,\?escapeinside={}{}||g' _tmp.tex
|
||||
|
||||
sed -i -e 's@\\nameref{quicklook}@A quick look at Tracy Profiler@g' _tmp.tex
|
||||
sed -i -e 's@\\nameref{firststeps}@First steps@g' _tmp.tex
|
||||
|
||||
839
manual/tracy.md
445
manual/tracy.tex
@@ -21,6 +21,7 @@
|
||||
\usepackage{nameref}
|
||||
\usepackage{diagbox}
|
||||
\usepackage{csquotes}
|
||||
\usepackage{longtable}
|
||||
|
||||
\usepackage[hmarginratio=1:1,top=32mm,columnsep=20pt]{geometry} % Document margins
|
||||
\geometry{a4paper,textwidth=6.5in,hmarginratio=1:1,
|
||||
@@ -357,11 +358,19 @@ Tracy can be found at the following web addresses:
|
||||
|
||||
\subsubsection{Binary distribution}
|
||||
|
||||
The version releases of the profiler are provided as precompiled Windows binaries for download at \url{https://github.com/wolfpld/tracy/releases}, along with the user manual. You will need to install the latest Visual C++ redistributable package to use them.
|
||||
Prebuilt binary releases of the profiler and the user manual can be found at \url{https://github.com/wolfpld/tracy/releases}. The following platforms are supported:
|
||||
|
||||
Development builds of Windows binaries, and the user manual are available as artifacts created by the automated Continuous Integration system on GitHub.
|
||||
\begin{itemize}
|
||||
\item \textbf{Windows} -- Requires the latest Visual C++ redistributable package to be installed.
|
||||
\item \textbf{macOS} -- Needs manual quarantine handling before any provided binary can be executed, e.g.:
|
||||
\begin{lstlisting}
|
||||
xattr -d com.apple.quarantine tracy-profiler.app
|
||||
\end{lstlisting}
|
||||
Thanks, Tim Apple!
|
||||
\item \textbf{Linux} -- AppImage with Ubuntu 24.04 used as a baseline. Requires Wayland and an implementation of an XDG desktop portal to be installed.
|
||||
\end{itemize}
|
||||
|
||||
Note that these binary releases require AVX2 instruction set support on the processor. If you have an older CPU, you will need to set a proper instruction set architecture in the project properties and build the executables yourself.
|
||||
Development builds of binaries and the user manual are available as artifacts created by the automated continuous integration system on GitHub.
|
||||
|
||||
\section{First steps}
|
||||
\label{firststeps}
|
||||
@@ -486,6 +495,78 @@ target_link_libraries(<TARGET> PUBLIC TracyClient)
|
||||
While using \texttt{set(CMAKE\_INTERPROCEDURAL\_OPTIMIZATION ON)} is a convenient way to enable Link-Time Optimization (LTO) for an entire project, there are situations in which this may not work due to excessive compilation times, linking issues, compiler bugs, or other reasons.
|
||||
For such cases, Tracy provides an option to enable Link-Time Optimization for itself using the \texttt{TRACY\_LTO} variable during the CMake configuration stage.
|
||||
|
||||
\subsubsection{Build options}
|
||||
\label{buildoptions}
|
||||
|
||||
Table~\ref{cmakeoptions} lists the options you may want to set in the program you are profiling. All of them are boolean switches defaulting to \texttt{OFF}, with three exceptions: \texttt{TRACY\_STATIC}, which defaults to the opposite of \texttt{BUILD\_SHARED\_LIBS}, and \texttt{TRACY\_CALLSTACK} and \texttt{TRACY\_PLATFORM\_HEADER}, which take a value instead of a boolean.
|
||||
|
||||
Each enabled option is passed down to your program as a preprocessor macro of the same name. If you are not using CMake, you can achieve the same results by defining these macros yourself, as described in section~\ref{initialsetup}. Note that some of the listed options additionally affect how the client library itself is built, so they cannot be replaced with a macro definition alone.
|
||||
|
||||
\begin{footnotesize}
|
||||
\begin{longtable}{@{}p{0.4\textwidth}p{0.56\textwidth}@{}}
|
||||
\caption{Tracy CMake options}\label{cmakeoptions} \\
|
||||
\hline
|
||||
\textbf{Option} & \textbf{Description} \\ \hline
|
||||
\endfirsthead
|
||||
\hline
|
||||
\endhead
|
||||
\hline
|
||||
\endfoot
|
||||
\hline
|
||||
\endlastfoot
|
||||
\textbf{Basic setup} & \\
|
||||
\texttt{TRACY\_ENABLE} & Enable profiling. Without it, the instrumentation is compiled out and the client does nothing (section~\ref{initialsetup}). \\
|
||||
\texttt{TRACY\_ON\_DEMAND} & Collect data only while the profiler is connected, instead of starting at program launch (section~\ref{ondemand}). \\
|
||||
\texttt{TRACY\_NO\_EXIT} & Don't let the client exit until all the profiling data has been sent to the server. Useful for short-lived applications. \\
|
||||
\texttt{TRACY\_STATIC} & Build the client as a static (\texttt{ON}) or shared (\texttt{OFF}) library. Multi-DLL projects need a shared library (section~\ref{multidll}). \\
|
||||
\texttt{TRACY\_LTO} & Enable link-time optimization of the client library itself. \\
|
||||
& \\
|
||||
\textbf{Networking} & \\
|
||||
\texttt{TRACY\_NO\_BROADCAST} & Don't announce the client's presence on the local network. Manual connections are still possible. \\
|
||||
\texttt{TRACY\_ONLY\_LOCALHOST} & Listen for connections only on the localhost interface. \\
|
||||
\texttt{TRACY\_ONLY\_IPV4} & Accept connections only on IPv4 addresses, disabling IPv6 support. \\
|
||||
& \\
|
||||
\textbf{Data collection} & \\
|
||||
\texttt{TRACY\_NO\_SYSTEM\_TRACING} & Prevent the client from accessing kernel data, which disables all system-level tracing (section~\ref{automated}). \\
|
||||
\texttt{TRACY\_NO\_CONTEXT\_SWITCH} & Disable capture of context switch data (section~\ref{contextswitches}). \\
|
||||
\texttt{TRACY\_NO\_SAMPLING} & Disable call stack sampling (section~\ref{sampling}). \\
|
||||
\texttt{TRACY\_NO\_WAIT\_STACKS} & Disable capture of context switch call stacks, while keeping the context switch events themselves (section~\ref{waitstacks}). \\
|
||||
\texttt{TRACY\_NO\_VSYNC\_CAPTURE} & Disable capture of hardware Vsync events (section~\ref{vsync}). \\
|
||||
\texttt{TRACY\_NO\_CODE\_TRANSFER} & Disable retrieval of the program's machine code and source files (section~\ref{executableretrieval}). \\
|
||||
\texttt{TRACY\_NO\_FRAME\_IMAGE} & Disable frame image support, along with the compression thread it needs (section~\ref{frameimages}). \\
|
||||
\texttt{TRACY\_NO\_CRASH\_HANDLER} & Don't install the crash handler (section~\ref{crashhandling}). \\
|
||||
\texttt{TRACY\_NO\_VERIFY} & Disable validation of the C API instrumentation correctness (section~\ref{capi}). \\
|
||||
\texttt{TRACY\_FIBERS} & Enable fiber support, which is opt-in due to the additional cost it adds to zone capture (section~\ref{fibers}). \\
|
||||
\texttt{TRACY\_IGNORE\_MEMORY\_FAULTS} & Don't report instrumentation failures for free events with no matching allocation, or for repeated allocations of the same address (section~\ref{memoryprofiling}). \\
|
||||
\texttt{TRACY\_OPENGL\_AUTO\_CALIBRATION} & Periodically correct the OpenGL GPU/CPU clock drift, at the cost of a CPU/GPU synchronization each time (section~\ref{gpuprofiling}). \\
|
||||
\texttt{TRACY\_ROCPROF\_CALIBRATION} & Periodically correct the ROCm GPU/CPU clock drift. Available only if rocprofiler-sdk was found (section~\ref{rocm}). \\
|
||||
& \\
|
||||
\textbf{Call stacks and symbols} & \\
|
||||
\texttt{TRACY\_CALLSTACK} & Call stack capture depth, which makes the zone macros without the \texttt{S} postfix collect call stacks (section~\ref{collectingcallstacks}). \\
|
||||
\texttt{TRACY\_NO\_CALLSTACK} & Disable all call stack related functionality (section~\ref{collectingcallstacks}). \\
|
||||
\texttt{TRACY\_NO\_CALLSTACK\_INLINES} & Don't resolve inline frames, retrieval of which may be very slow on Windows. \\
|
||||
\texttt{TRACY\_LIBUNWIND\_BACKTRACE} & Capture call stacks using libunwind, which may be faster than the default implementation. Links with libunwind. \\
|
||||
\texttt{TRACY\_DEBUGINFOD} & Enable on-demand delivery of debugging symbols through debuginfod. Links with libdebuginfod. \\
|
||||
\texttt{TRACY\_SYMBOL\_OFFLINE\_RESOLVE} & Record only image paths and offsets, leaving symbol resolution to be performed later by the \texttt{update} utility. \\
|
||||
\texttt{TRACY\_LIBBACKTRACE\_ELF\_DYNLOAD\_SUPPORT} & Let libbacktrace resolve symbols in ELF objects that were loaded after the first symbol resolution took place. \\
|
||||
\texttt{TRACY\_DEMANGLE} & Don't use the built-in symbol demangling function. You will have to provide your own \texttt{\_\_tracy\_demangle} implementation. \\
|
||||
& \\
|
||||
\textbf{Timers} & \\
|
||||
\texttt{TRACY\_TIMER\_FALLBACK} & Compile in a lower resolution timer, to be used when the hardware timer is unavailable (table~\ref{timeroptions}). \\
|
||||
\texttt{TRACY\_DISALLOW\_HW\_TIMER} & Never use the hardware timer, even if it \emph{appears} to be available. Requires \texttt{TRACY\_TIMER\_FALLBACK}. \\
|
||||
& \\
|
||||
\textbf{Special environments} & \\
|
||||
\texttt{TRACY\_MANUAL\_LIFETIME} & Provide the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions for manual profiler lifetime management (section~\ref{multidll}). \\
|
||||
\texttt{TRACY\_PLATFORM\_HEADER} & Path to a header providing the \texttt{TRACY\_HAS\_CUSTOM\_*} hooks for an unsupported platform (section~\ref{customplatform}). \\
|
||||
\texttt{TRACY\_PATCHABLE\_NOPSLEDS} & Emit nopsleds in front of the timer reads, so that system-level tools such as \emph{rr} can patch them. \\
|
||||
& \\
|
||||
\textbf{Diagnostics} & \\
|
||||
\texttt{TRACY\_VERBOSE} & Print detailed information about the detected features to the standard error output (section~\ref{troubleshooting}). \\
|
||||
\texttt{TRACY\_NO\_INTERNAL\_MESSAGE} & Don't send the profiler's own diagnostic messages to the server (section~\ref{troubleshooting}). \\
|
||||
\texttt{TRACY\_ASSERT} & Route the client's internal checks through a custom assert implementation, instead of the standard \texttt{assert}. Define \texttt{TRACY\_ASSERT(condition)} before including any Tracy header; the macro must accept a single condition argument. \\
|
||||
\end{longtable}
|
||||
\end{footnotesize}
|
||||
|
||||
\subsubsection{Meson integration}
|
||||
|
||||
If you are using the Meson build system, you can add Tracy using the Wrap dependency system. To do this, place the \texttt{tracy.wrap} file in the \texttt{subprojects} directory of your project, with the following content. The \texttt{head} \texttt{revision} field tracks Tracy's \texttt{master} branch. If you want to lock to a specific version of Tracy instead, you can just set the \texttt{revision} field to an appropriate git tag.
|
||||
@@ -523,6 +604,8 @@ Here's a sample command to set up a build directory with profiling enabled. The
|
||||
meson setup build --buildtype=debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true
|
||||
\end{lstlisting}
|
||||
|
||||
Most of the options listed in table~\ref{cmakeoptions} are also available in the Meson build. Their names are lowercased, with the \texttt{TRACY\_} prefix dropped, the only exception being \texttt{tracy\_enable}.
|
||||
|
||||
\subsubsection{Short-lived applications}
|
||||
|
||||
In case you want to profile a short-lived program (for example, a compression utility that finishes its work in one second), set the \texttt{TRACY\_NO\_EXIT} environment variable to $1$. With this option enabled, Tracy will not exit until an incoming connection is made, even if the application has already finished executing. If your platform doesn't support an easy setup of environment variables, you may also add the \texttt{TRACY\_NO\_EXIT} define to your build configuration, which has the same effect.
|
||||
@@ -534,6 +617,8 @@ By default, Tracy will begin profiling even before the program enters the \textt
|
||||
|
||||
You should note that if on-demand profiling is \emph{disabled} (which is the default), then the recorded events will be stored in the system memory until a server connection is made and the data can be uploaded\footnote{This memory is never released, but the profiler reuses it for collection of other events.}. Depending on the amount of the things profiled, the requirements for event storage can quickly grow up to a couple of gigabytes. Furthermore, since this data is no longer available after the initial connection, you won't be able to perform a second connection to a client unless the on-demand mode is used.
|
||||
|
||||
In on-demand mode, each connection to the server is a separate capture. A zone is included in a capture only if both its begin and end events occur during that connection: if the connection is lost and re-established while a zone is open, the end event is dropped and the zone remains unclosed in the previous capture. Zones that begin and end during a later connection, even while an older zone is still open, appear in the new capture as top-level zones.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
@@ -557,6 +642,7 @@ If you need to use a specific Tracy client address, such as QNX requires, define
|
||||
By default, the Tracy client will listen on IPv6 interfaces, falling back to IPv4 only if IPv6 is unavailable. If you want to restrict it to only listening on IPv4 interfaces, define the \texttt{TRACY\_ONLY\_IPV4} macro at compile-time, or set the \texttt{TRACY\_ONLY\_IPV4} environment variable to $1$ at runtime.
|
||||
|
||||
\subsubsection{Setup for multi-DLL projects}
|
||||
\label{multidll}
|
||||
|
||||
Things are a bit different in projects that consist of multiple DLLs/shared objects. Compiling \texttt{TracyClient.cpp} into every DLL is not an option because this would result in several instances of Tracy objects lying around in the process. We instead need to pass their instances to the different DLLs to be reused there.
|
||||
|
||||
@@ -564,9 +650,9 @@ For that, you need a \emph{profiler DLL} to which your executable and the other
|
||||
|
||||
If you are targeting Windows with Microsoft Visual Studio or MinGW, add the \texttt{TRACY\_IMPORTS} define to your application.
|
||||
|
||||
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining both \texttt{TRACY\_DELAYED\_INIT} and \texttt{TRACY\_MANUAL\_LIFETIME} macros.
|
||||
If you are experiencing crashes or freezes when manually loading/unloading a separate DLL with Tracy integration, you might want to try defining the \texttt{TRACY\_MANUAL\_LIFETIME} macro.
|
||||
|
||||
\texttt{TRACY\_DELAYED\_INIT} enables a path where profiler data is gathered into one structure and initialized on the first request rather than statically at the DLL load at the expense of atomic load on each request to the profiler data. \texttt{TRACY\_MANUAL\_LIFETIME} flag augments this behavior to provide manual \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually. This manual management removes the need to do an atomic load on each call and lets you define an appropriate place to free the resources.
|
||||
\texttt{TRACY\_MANUAL\_LIFETIME} provides the \texttt{StartupProfiler} and \texttt{ShutdownProfiler} functions that allow you to create and destroy the profiler data manually, letting you define an appropriate place to free the resources. Under \texttt{TRACY\_MANUAL\_LIFETIME} the profiler does not exist until \texttt{StartupProfiler()} is called and ceases to exist after \texttt{ShutdownProfiler()}. Using any instrumentation, zones, locks, plots, messages, and so on before startup or after shutdown is an error. Use \texttt{TracyIsStarted} to guard instrumentation that may run conditionally. If the profiler has been initialized, it must be shut down before the program exits.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
@@ -705,6 +791,7 @@ The platform header is intended only for the \texttt{TRACY\_HAS\_CUSTOM\_*} hook
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Troubleshooting}
|
||||
\label{troubleshooting}
|
||||
|
||||
By default, Tracy's diagnostics will be sent as Message logs (section~\ref{messagelog}) to the server.
|
||||
Setting the \texttt{TRACY\_NO\_INTERNAL\_MESSAGE} define will disable this feature, but setting the \texttt{TRACY\_VERBOSE} will make the client print advanced information about the detected features to the standard error output. By matching those debug prints to the source code, you might be able to uncover why some of the features are missing on your platform.
|
||||
@@ -1474,6 +1561,32 @@ Exiting the profiled application from inside a zone is not supported. When the c
|
||||
|
||||
As a workaround, you may add a \texttt{try}/\texttt{catch} pair at the bottom of the function stack (for example in the \texttt{main()} function) and replace \texttt{exit()} calls with throwing a custom exception. When this exception is caught, you may call \texttt{exit()}, knowing that the application's data structures (including profiling zones) were properly cleaned up.
|
||||
|
||||
\subsection{Marking sections}
|
||||
\label{markingsections}
|
||||
|
||||
To provide a high-level overview of what your program is doing, you may mark sections of execution. For example, in a puzzle game, the main menu would be a section, and each of the levels would be a section on its own.
|
||||
|
||||
To mark when a section starts, use the \texttt{TracySectionEnter(fmt, ...)} macro with printf-like formatting. The macro will return an \texttt{uint32\_t} identifier, which you must store and pass to the corresponding section-end macro, \texttt{TracySectionLeave(id)}. Here's an example of a simple RAII wrapper:
|
||||
|
||||
\begin{lstlisting}
|
||||
struct TracySection
|
||||
{
|
||||
explicit TracySection(const char* name) : idx(TracySectionEnter("%s", name)) {}
|
||||
~TracySection() { TracySectionLeave(idx); }
|
||||
uint32_t idx;
|
||||
};
|
||||
\end{lstlisting}
|
||||
|
||||
Each of the sections you add is handled independently of any other section. Multiple sections may overlap, either with a hierarchical structure or with each section having a part that does not coincide with other sections.
|
||||
|
||||
\subsubsection{Section categories}
|
||||
|
||||
In some use cases, you will want to label your sections with specific category labels. For example, you may want to mark which level is currently loaded in your game and also mark when the inventory screen is open. When you are only interested in tracking the level progression, sifting through all the times the inventory was on the screen would be quite cumbersome. Section categories let you filter out uninteresting sections.
|
||||
|
||||
To set a category for the section, start the section with the \texttt{TracySectionEnterCategory(category, fmt, ...)} macro, where \texttt{category} is an \texttt{uint16\_t} identifier. Sections without an explicit category assignment (i.e., made with the \texttt{TracySectionEnter} macro) are automatically in the $0$ category.
|
||||
|
||||
You can describe each of the categories you want to use with the \texttt{TracySectionSetup(category, fmt, ...)} macro. Section categories with no name set will automatically be assigned a name based on the identifier (e.g., \emph{Category 0}).
|
||||
|
||||
\subsection{Marking locks}
|
||||
|
||||
Modern programs must use multi-threading to achieve the full performance capability of the CPU. However, correct execution requires claiming exclusive access to data shared between threads. When many threads want to simultaneously enter the same critical section, the application's multi-threaded performance advantage nullifies. To help solve this problem, Tracy can collect and display lock interactions in threads.
|
||||
@@ -1602,11 +1715,14 @@ Tracy can monitor the memory usage of your application. Knowledge about each per
|
||||
\item Memory allocation hot-spot tree.
|
||||
\end{itemize}
|
||||
|
||||
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically you would do that in overloads of \texttt{operator new} and \texttt{operator delete}, for example:
|
||||
To mark memory events, use the \texttt{TracyAlloc(ptr, size)} and \texttt{TracyFree(ptr)} macros. Typically, you would place them in overloads of \texttt{operator new} and \texttt{operator delete}. The order in which memory events actually occur must match the order reported to the profiler, so a mutex or similar synchronization primitive is required to make the whole operation atomic. Here is an example implementation:
|
||||
|
||||
\begin{lstlisting}
|
||||
std::mutex memoryLock;
|
||||
|
||||
void* operator new(std::size_t count)
|
||||
{
|
||||
std::lock_guard lock(memoryLock);
|
||||
auto ptr = malloc(count);
|
||||
TracyAlloc(ptr, count);
|
||||
return ptr;
|
||||
@@ -1614,6 +1730,7 @@ void* operator new(std::size_t count)
|
||||
|
||||
void operator delete(void* ptr) noexcept
|
||||
{
|
||||
std::lock_guard lock(memoryLock);
|
||||
TracyFree(ptr);
|
||||
free(ptr);
|
||||
}
|
||||
@@ -1661,6 +1778,8 @@ Tracy provides bindings for profiling OpenGL, Vulkan, Direct3D 11, Direct3D 12,
|
||||
|
||||
Note that the CPU and GPU timers may be unsynchronized unless you create a calibrated context, but the availability of calibrated contexts is limited. You can try to correct the desynchronization of uncalibrated contexts in the profiler's options (section~\ref{options}).
|
||||
|
||||
GPU contexts are identified by a process-wide unique id, assigned when the context is created (the context creation macros do this automatically). Ids are 8 bits wide (0--255) and are allocated sequentially, so a process can create at most 256 GPU contexts.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
@@ -1814,6 +1933,7 @@ To instrument a GPU zone, use the various \texttt{TracyWebGPU*Zone*()} macros. N
|
||||
You are required to periodically collect the GPU events using the \texttt{TracyWebGPUCollect()} macro. Good places for collection are: after synchronous waits, after event processing \texttt{wgpuInstanceProcessEvents}, after present drawable calls (\texttt{wgpuSurfacePresent}), and inside the completion callback of command queues (\texttt{wgpuQueueOnSubmittedWorkDone}).
|
||||
|
||||
\subsubsection{ROCm}
|
||||
\label{rocm}
|
||||
|
||||
On Linux, if rocprofiler-sdk is installed, tracy can automatically trace GPU dispatches and collect
|
||||
performance counter values. If CMake can't find rocprofiler-sdk, you can set the CMake variable
|
||||
@@ -2221,7 +2341,7 @@ Refer to sections~\ref{markingzones} and~\ref{multizone} for description of macr
|
||||
|
||||
Unlike C++, there's no automatic destruction mechanism in C, so you will need to mark where the zone ends manually. To do so use the \texttt{TracyCZoneEnd(ctx)} macro.\footnote{GCC and Clang provide \texttt{\_\_attribute\_\_((cleanup))} which can used to run a function when a variable goes out of scope.}
|
||||
|
||||
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
|
||||
Zone text and name may be set by using the \texttt{TracyCZoneText(ctx, txt, size)}, \texttt{TracyCZoneValue(ctx, value)} and \texttt{TracyCZoneName(ctx, txt, size)} macros. For printf-style formatting, use the \texttt{TracyCZoneTextF(ctx, fmt, ...)} and \texttt{TracyCZoneNameF(ctx, fmt, ...)} variants, which accept a format string and arguments instead of a fixed-length text buffer. Make sure you are following the zone stack rules, as described in section~\ref{multizone}!
|
||||
|
||||
\paragraph{Zone context data structure}
|
||||
\label{zonectx}
|
||||
@@ -2364,7 +2484,7 @@ Moreover, there are two sets of functions described below. The standard set send
|
||||
A GPU context can be created with the \texttt{\_\_\_tracy\_emit\_gpu\_new\_context} function (or the serialized variant). You'll need to specify:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{context} -- a unique context id.
|
||||
\item \texttt{context} -- a unique context id (8-bit, 0--255; see section~\ref{gpuprofiling}).
|
||||
\item \texttt{gpuTime} -- an initial GPU timestamp.
|
||||
\item \texttt{period} -- the timestamp period of the GPU.
|
||||
\item \texttt{flags} -- the flags to use.
|
||||
@@ -2420,10 +2540,7 @@ Tracy C API exposes functions with the \texttt{\_\_\_tracy} prefix that you may
|
||||
\item \texttt{\_\_\_tracy\_alloc\_srcloc\_name(uint32\_t line, const char* source, size\_t sourceSz, const char* function, size\_t functionSz, const char* name, size\_t nameSz)}
|
||||
\end{itemize}
|
||||
|
||||
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the
|
||||
name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the
|
||||
size of the corresponding string arguments in bytes. You may additionally specify an optional zone
|
||||
name, by providing it in the \texttt{name} variable, and specifying its size in \texttt{nameSz}.
|
||||
Here \texttt{line} is line number in the \texttt{source} source file and \texttt{function} is the name of a function in which the zone is created. \texttt{sourceSz} and \texttt{functionSz} are the sizes of the corresponding string arguments in bytes. You may additionally specify an optional zone name by providing it in the \texttt{name} variable and specifying its size in \texttt{nameSz}. If the passed strings contain null-terminating characters, these characters must be excluded from the provided sizes.
|
||||
|
||||
The \texttt{\_\_\_tracy\_alloc\_srcloc} and \texttt{\_\_\_tracy\_alloc\_srcloc\_name} functions
|
||||
return an \texttt{uint64\_t} source location identifier corresponding to an \emph{allocated source
|
||||
@@ -2467,7 +2584,7 @@ An especially powerful feature is the ability to profile Python code and any oth
|
||||
|
||||
An example of how to use the Tracy-Client bindings is shown below:
|
||||
|
||||
\begin{lstlisting}
|
||||
\begin{lstlisting}[language=Python,escapeinside={}{}]
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
@@ -2544,7 +2661,7 @@ if __name__ == "__main__":
|
||||
main()
|
||||
\end{lstlisting}
|
||||
|
||||
Please not the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
|
||||
Please note the use of ids as way to cope with the need for unique pointers for certain features of the Tracy profiler, see section~\ref{uniquepointers}.
|
||||
|
||||
\subsubsection{Building the Python package}
|
||||
|
||||
@@ -2602,16 +2719,17 @@ Set the following environment variables before launching (or export them in your
|
||||
PYTHONPATH=/path/to/tracy/build/python/Release
|
||||
TRACY_CAPTURES_DIR=/path/to/captures # enables list_captures
|
||||
TRACY_MCP_PORT=47380 # optional; default 47380
|
||||
TRACY_MCP_TRANSPORT=streamable-http # optional; streamable-http or sse
|
||||
\end{lstlisting}
|
||||
|
||||
\subsubsection{Integrating with an AI assistant}
|
||||
|
||||
The server runs as a singleton on SSE transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately.
|
||||
The server runs as a singleton on Streamable HTTP transport (port 47380 by default). Only one process loads \texttt{TracyServerBindings} regardless of how many editor windows are open; subsequent launches detect the port is taken and exit immediately. Set \texttt{TRACY\_MCP\_TRANSPORT=sse} before launching to use the legacy SSE transport instead.
|
||||
|
||||
The server prints its URL on startup and writes it to \texttt{extra/mcp/tracy\_mcp.port}:
|
||||
|
||||
\begin{lstlisting}
|
||||
Tracy MCP listening on http://127.0.0.1:47380/sse
|
||||
Tracy MCP listening on http://127.0.0.1:47380/mcp
|
||||
\end{lstlisting}
|
||||
|
||||
Configure your AI assistant using that URL. For example, for a JSON-based MCP configuration:
|
||||
@@ -2620,7 +2738,7 @@ Configure your AI assistant using that URL. For example, for a JSON-based MCP co
|
||||
{
|
||||
"mcpServers": {
|
||||
"tracy": {
|
||||
"url": "http://127.0.0.1:47380/sse"
|
||||
"url": "http://127.0.0.1:47380/mcp"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2719,7 +2837,6 @@ You can integrate Tracy with CMake by adding the git submodule folder as a subdi
|
||||
option(TRACY_ENABLE "" ON)
|
||||
# must be enabled
|
||||
option(TRACY_Fortran "" ON)
|
||||
option(TRACY_DELAYED_INIT "" ON)
|
||||
option(TRACY_MANUAL_LIFETIME "" ON)
|
||||
add_subdirectory(3rdparty/tracy) # target: TracyClientF90 or alias Tracy::TracyClientF90
|
||||
\end{lstlisting}
|
||||
@@ -2753,7 +2870,6 @@ When using CMake 3.11 or newer, you can use Tracy via CMake FetchContent. In thi
|
||||
|
||||
\begin{lstlisting}
|
||||
option(TRACY_Fortran "" ON)
|
||||
option(TRACY_DELAYED_INIT "" ON)
|
||||
option(TRACY_MANUAL_LIFETIME "" ON)
|
||||
|
||||
FetchContent_Declare(
|
||||
@@ -3042,6 +3158,10 @@ The sampling functionality also captures call stacks for context switch events.
|
||||
\item Unexpected waits, which should be immediately taken care of. After all, what's the point of profiling and optimizing your program if it is constantly waiting for something? An example of such an unexpected wait may be some anti-virus service interfering with each of your file read operations. In this case, you could have assumed that the system would buffer a large chunk of the data after the first read to make it immediately available to the application in the following calls.
|
||||
\end{enumerate}
|
||||
|
||||
Since context switch samples are captured at scheduling events rather than by the statistical sampling timer, they are not an unbiased measure of program execution. They are therefore only displayed on the timeline and in the wait stacks window, and are excluded from the sampling statistics, the per-symbol sample data, child calls, entry stacks, flame graph, zone call stack reconstruction, and so on.
|
||||
|
||||
Collection of wait stacks makes the kernel walk the stack at context switch events, which may add considerable overhead on weaker hardware. Use the \texttt{TRACY\_NO\_WAIT\_STACKS} macro to disable capture of wait stacks, while still keeping the context switch events themselves.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
@@ -3095,6 +3215,7 @@ On Linux, Tracy will override the \texttt{dlclose} function call to prevent shar
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Vertical synchronization}
|
||||
\label{vsync}
|
||||
|
||||
On Windows and Linux, Tracy will automatically capture hardware Vsync events, provided that the application has access to the kernel data (privilege elevation may be needed, see section~\ref{privilegeelevation}). These events will be reported as \enquote{\texttt{[x] Vsync}} frame sets, where \texttt{x} is the identifier of a specific monitor. Note that hardware vertical synchronization might not correspond to the one seen by your application due to desktop composition, command queue buffering, and so on. Also, in some instances, when there is nothing to update on the screen, the graphic driver may choose to stop issuing screen refresh. As a result, there may be periods where no vertical synchronization events are reported.
|
||||
|
||||
@@ -3109,9 +3230,15 @@ Sometimes it is desired to change how the profiled application behaves during th
|
||||
void Callback(void* data, uint32_t idx, int32_t val)
|
||||
\end{lstlisting}
|
||||
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface.
|
||||
The \texttt{data} parameter will have the same value as was specified in the macro. The \texttt{idx} argument is an user-defined parameter index and \texttt{val} is the value set in the profiler user interface (in the connection information popup, see section~\ref{connectionpopup}).
|
||||
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, isBool, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to). \texttt{Name} is the parameter label, displayed on the list of parameters. Finally, \texttt{isBool} determines if \texttt{val} should be interpreted as a boolean value, or as an integer number.
|
||||
To specify individual parameters, use the \texttt{TracyParameterSetup(idx, name, type, val)} macro. The \texttt{idx} value will be passed to the callback function for identification purposes (Tracy doesn't care what it's set to), \texttt{name} is the parameter label, displayed on the list of parameters, and \texttt{val} is the initial value. Finally, \texttt{type} determines how to interpret the \texttt{val} value, and can be selected from:
|
||||
|
||||
\begin{itemize}
|
||||
\item \texttt{TracyParamTypeInt} -- \texttt{val} is an integer value, with the profiler UI displaying a numerical entry field.
|
||||
\item \texttt{TracyParamTypeBool} -- \texttt{val} is a boolean value, with a checkbox in the user interface.
|
||||
\item \texttt{TracyParamTypeTrigger} -- a~\emph{\faCircleDot{}~Trigger} button is displayed, for cases when you just want some action to happen. Repeats the initial \texttt{val} value provided to the setup function.
|
||||
\end{itemize}
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
@@ -3312,7 +3439,7 @@ You can also adjust some settings that affect global profiler behavior in this w
|
||||
|
||||
If this is a real-time capture, you will also have access to the connection information pop-up (figure~\ref{connectioninfo}) through the \emph{\faWifi{}~Connection} button, with the capture status similar to the one displayed by the command-line utility. This dialog also shows the connection speed graphed over time and the profiled application's current frames per second and frame time measurements. The \emph{Query backlog} consists of two numbers. The first represents the number of queries that were held back due to the bandwidth volume overwhelming the available network send buffer. The second one shows how many queries are in-flight, meaning requests sent to the client but not yet answered. While these numbers drain down to zero, the performance of real time profiling may be temporarily compromised. The circle displayed next to the bandwidth graph signals the connection status. If it's red, the connection is active. If it's gray, the client has disconnected.
|
||||
|
||||
You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
|
||||
You can use the \faFloppyDisk{}~\emph{Save trace...} button to save the current profile data to a file\footnote{You should take this literally. If a live capture is in progress and a save is performed, some data may be missing from the capture and won't be saved.}. The available compression modes are discussed in sections~\ref{archival} and~\ref{fidict}. Use the \faPlug{}~\emph{Stop} button to disconnect from the client\footnote{While requesting disconnect stops retrieval of any new events, the profiler will wait for any data that is still pending for the current set of events.}. The \faTriangleExclamation{}~\emph{Discard} button is used to discard current trace.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
@@ -3335,7 +3462,7 @@ You can use the \faFloppyDisk{}~\emph{Save trace} button to save the current pro
|
||||
|
||||
If frame image capture has been implemented (chapter~\ref{frameimages}), a thumbnail of the last received frame image will be provided for reference.
|
||||
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
Suppose the profiled application opted to provide trace parameters (see section~\ref{traceparameters}) and the connection is still active. In that case, this pop-up will also contain a \emph{Trace parameters} section, listing all the provided options. A callback function will be executed on the client when you change any value here.
|
||||
|
||||
\subsubsection{Automatic loading or connecting}
|
||||
|
||||
@@ -3591,7 +3718,7 @@ The main profiler window is split into three sections, as seen in figure~\ref{ma
|
||||
\draw[rounded corners=5pt] (1.3, -0.1) rectangle+(2.2, -0.5) node [midway] {\faComment{} Messages};
|
||||
\draw[rounded corners=5pt] (3.6, -0.1) rectangle+(1.3, -0.5) node [midway] {\faMagnifyingGlass{} Find};
|
||||
\draw[rounded corners=5pt] (5.0, -0.1) rectangle+(2, -0.5) node [midway] {\faArrowUpWideShort{} Statistics};
|
||||
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFire{} Flame};
|
||||
\draw[rounded corners=5pt] (7.1, -0.1) rectangle+(1.5, -0.5) node [midway] {\faFireFlameCurved{} Flame};
|
||||
\draw[rounded corners=5pt] (8.7, -0.1) rectangle+(2.1, -0.5) node [midway] {\faMemory{} Memory};
|
||||
\draw[rounded corners=5pt] (10.9, -0.1) rectangle+(2.1, -0.5) node [midway] {\faScaleBalanced{} Compare};
|
||||
\draw[rounded corners=5pt] (13.1, -0.1) rectangle+(1.2, -0.5) node [midway] {\faFingerprint{} Info};
|
||||
@@ -3627,7 +3754,7 @@ The control menu (top row of buttons) provides access to various profiler featur
|
||||
\item \emph{\faComment{} Messages} -- Toggles the message log window (section~\ref{messages}), which displays custom messages sent by the client, as described in section~\ref{messagelog}.
|
||||
\item \emph{\faMagnifyingGlass{} Find} -- This buttons toggles the find zone window, which allows inspection of zone behavior statistics (section~\ref{findzone}).
|
||||
\item \emph{\faArrowUpWideShort{} Statistics} -- Toggles the statistics window, which displays zones sorted by their total time cost (section~\ref{statistics}).
|
||||
\item \emph{\faFire{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
|
||||
\item \emph{\faFireFlameCurved{} Flame} -- Enables the flame graph window (section~\ref{flamegraph}).
|
||||
\item \emph{\faMemory{} Memory} -- Various memory profiling options may be accessed here (section~\ref{memorywindow}).
|
||||
\item \emph{\faScaleBalanced{} Compare} -- Toggles the trace compare window, which allows you to see the performance difference between two profiling runs (section~\ref{compare}).
|
||||
\item \emph{\faFingerprint{} Info} -- Show general information about the trace (section~\ref{traceinfo}).
|
||||
@@ -3638,6 +3765,7 @@ The control menu (top row of buttons) provides access to various profiler featur
|
||||
\item \emph{\faNoteSticky{}~Annotations} -- If annotations have been made (section~\ref{annotatingtrace}), you can open a list of all annotations, described in chapter~\ref{annotationlist}.
|
||||
\item \emph{\faRuler{}~Limits} -- Displays time range limits window (section~\ref{timeranges}).
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- If sampling was performed, an option to display wait stacks may be available. See chapter~\ref{waitstacks} for more details.
|
||||
\item \emph{\faImages{}~Frame statistics} -- Display frame statistics window (section~\ref{framestatistics}).
|
||||
\end{itemize}
|
||||
\item \emph{ \faBook{}~User manual} -- Opens the user manual for quick reference. Note that the version of the user manual available directly in the profiler is an inferior quality version compared to the proper PDF.
|
||||
\item \emph{\faMagnifyingGlassPlus{}~Display scale} -- Enables run-time resizing of the displayed content. This may be useful in environments with potentially reduced visibility, e.g. during a presentation. Note that this setting is independent to the UI scaling coming from the system DPI settings. The scale will be preserved across multiple profiler sessions if the \emph{Save UI scale} option is selected in global settings.
|
||||
@@ -3737,7 +3865,7 @@ Clicking the \LMB{}~left mouse button on the graph while the \keys{\ctrl}~key is
|
||||
|
||||
\subsubsection{Timeline view}
|
||||
|
||||
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into three parts: the time scale, the frame sets, and the combined zones, locks, and plots display.
|
||||
The timeline is the most crucial element of the profiler UI. All the captured data is displayed there, laid out on the horizontal axis, according to time flow. Where there was no profiling performed, the timeline is dimmed out. The view is split into four parts: time scale, frame sets, sections, and the combined zones, locks, and plots display.
|
||||
|
||||
\subparagraph{Collapsed items}
|
||||
\label{collapseditems}
|
||||
@@ -3824,6 +3952,40 @@ If a frame has an associated frame image (see chapter~\ref{frameimages}), you ca
|
||||
|
||||
If the \emph{\faFlagCheckered{}~Draw frame targets} option is enabled (see section~\ref{options}), time regions in frames exceeding the set target value will be marked with a red background.
|
||||
|
||||
\paragraph{Sections}
|
||||
\label{sections}
|
||||
|
||||
If sections of program execution were marked (see chapter~\ref{markingsections}), the profiler will try to reconstruct a child-parent hierarchy between the sections, as presented on figure~\ref{figsections}.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw(0, 0) rectangle+(2, -0.5) node[midway] {Menu};
|
||||
\draw(2, 0) rectangle+(10, -0.5) node[midway] {In-game};
|
||||
\draw(12, 0) rectangle+(1.5, -0.5) node[midway] {Menu};
|
||||
|
||||
\draw(2, -0.5) rectangle+(3, -0.5) node[midway] {Level 1};
|
||||
\draw(5, -0.5) rectangle+(5, -0.5) node[midway] {Level 2};
|
||||
\draw(10, -0.5) rectangle+(2, -0.5) node[midway] {Level 3};
|
||||
\end{tikzpicture}
|
||||
\caption{Sections showing a hierarchy.}
|
||||
\label{figsections}
|
||||
\end{figure}
|
||||
|
||||
With some data sets such reconstruction will not be possible, in which case sections may look as on figure~\ref{figsections2}.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw(0, 0) rectangle+(8, -0.5) node[midway] {First section};
|
||||
\draw(3, -0.5) rectangle+(8, -0.5) node[midway] {Second section};
|
||||
\end{tikzpicture}
|
||||
\caption{Sections that cannot form a hierarchy.}
|
||||
\label{figsections2}
|
||||
\end{figure}
|
||||
|
||||
Clicking the \MMB{}~middle mouse button on a section will zoom the view to the extent of the section.
|
||||
|
||||
Section hierarchy reconstruction is not affected by section categories. There is only a single lane for all sections. Enabling or disabling specific section categories in the options window, however, will affect which sections are visible here.
|
||||
|
||||
\paragraph{Zones, locks and plots display}
|
||||
\label{zoneslocksplots}
|
||||
|
||||
@@ -3919,7 +4081,7 @@ In an example in figure~\ref{zoneslocks} you can see that there are two threads:
|
||||
|
||||
Meanwhile, the \emph{Streaming thread} is performing some \emph{Streaming jobs}. The first \emph{Streaming job} sent a message (section~\ref{messagelog}). In addition to being listed in the message log, it is indicated by a triangle over the thread separator. When multiple messages are in one place, the triangle outline shape changes to a filled triangle.
|
||||
|
||||
The GPU zones are displayed just like CPU zones, with an OpenGL/Vulkan/Direct3D/Metal/OpenCL/CUDA/WebGPU context in place of a thread name.
|
||||
The GPU zones are displayed just like CPU zones, with an OpenGL / Vulkan / Direct3D / Metal / OpenCL / CUDA / WebGPU context in place of a thread name.
|
||||
|
||||
Hovering the \faArrowPointer{} mouse pointer over a zone will highlight all other zones that have the exact source location with a white outline. Clicking the \LMB{}~left mouse button on a zone will open the zone information window (section~\ref{zoneinfo}). Holding the \keys{\ctrl} key and clicking the \LMB{}~left mouse button on a zone will open the zone statistics window (section~\ref{findzone}). Clicking the \MMB{}~middle mouse button on a zone will zoom the view to the extent of the zone.
|
||||
|
||||
@@ -3943,7 +4105,15 @@ Clicking the \LMB{}~left mouse button on a ghost zone will open the correspondin
|
||||
|
||||
\subparagraph{Call stack samples}
|
||||
|
||||
The row of dots right below the \emph{Main thread} label shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Hovering the \faArrowPointer{}~mouse pointer over each dot will display a short call stack summary while clicking on the dot with the \LMB{}~left mouse button will open a more detailed call stack information window (see section~\ref{callstackwindow}).
|
||||
The row of dots right below the thread label (\emph{Main thread} in the example) shows call stack sample points, which may have been automatically captured (see chapter~\ref{sampling} for more detail). Each sample is color-coded as follows:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{Green} -- shows samples inside the profiled program.
|
||||
\item \emph{Blue} -- indicates samples belonging to external libraries.
|
||||
\item \emph{Red} -- used for samples captured in the kernel.
|
||||
\end{itemize}
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse pointer over each dot displays a short call stack summary, while clicking the dot with the \LMB{}~left mouse button opens a more detailed call stack information window (see section~\ref{callstackwindow}).
|
||||
|
||||
\subparagraph{Context switches}
|
||||
|
||||
@@ -4027,7 +4197,7 @@ Mutual exclusion zones are displayed in each thread that tries to acquire them.
|
||||
\item \emph{Red region} -- The thread wants to acquire the lock but is blocked by other thread or threads in case of a shared lock.
|
||||
\end{itemize}
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
|
||||
Hovering the \faArrowPointer{}~mouse pointer over a lock timeline will highlight the lock in all threads to help read the lock behavior. Hovering the \faArrowPointer{}~mouse pointer over a lock event will display important information, for example, a list of threads that are currently blocking or which are blocked by the lock. Hovering the \faArrowPointer{}~mouse pointer over a thread label shows that thread's aggregate lock wait time across all locks (see above). Clicking the \LMB{}~left mouse button on a lock event or a lock label will open the lock information window, as described in section~\ref{lockwindow}. Clicking the \MMB{}~middle mouse button on a lock event will zoom the view to the extent of the event.
|
||||
|
||||
\subparagraph{Plots}
|
||||
\label{plots}
|
||||
@@ -4072,17 +4242,21 @@ Sometimes, you may want to specify a time range, such as limiting some statistic
|
||||
To define a time range, drag the \LMB{}~left mouse button over the timeline view while holding the \keys{\ctrl} key. When the mouse key is released, the profiler will mark the selected time extent with a blue striped pattern, and it will display a context menu with the following options:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{\faMagnifyingGlass{}~Limit find zone time range} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
|
||||
\item \emph{\faArrowUpWideShort{}~Limit statistics time range} -- selecting this option will limit statistics results. See chapter~\ref{statistics} for more details.
|
||||
\item \emph{\faFire{}~Limit flame graph time range} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
|
||||
\item \emph{\faHourglassHalf{}~Limit wait stacks time range} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
|
||||
\item \emph{\faMemory{}~Limit memory time range} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
|
||||
\item \faRuler{}~Set time range for:
|
||||
\begin{itemize}
|
||||
\item \emph{\faMagnifyingGlass{}~Find zone} -- this will limit find zone results. See chapter~\ref{findzone} for more details.
|
||||
\item \emph{\faArrowUpWideShort{}~Statistics} -- selecting this option will limit statistics results (section~\ref{statistics}), as well as the sample statistics shown in the source view (section~\ref{sourceview}).
|
||||
\item \emph{\faFireFlameCurved{}~Flame graph} -- limits flame graph results. Refer to chapter~\ref{flamegraph}.
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- limits wait stacks results. Refer to chapter~\ref{stackwindows}.
|
||||
\item \emph{\faMemory{}~Memory} -- limits memory results. Read more about this in chapter~\ref{memorywindow}.
|
||||
\item \emph{\faImages{}~Frame statistics} -- limits frame statistics. Section~\ref{framestatistics} has more information.
|
||||
\end{itemize}
|
||||
\item \emph{\faNoteSticky{}~Add annotation} -- use to annotate regions of interest, as described in chapter~\ref{annotatingtrace}.
|
||||
\end{itemize}
|
||||
|
||||
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone or a frame. The resulting time extent will match the selected item.
|
||||
Alternatively, you may specify the time range by clicking the \RMB{}~right mouse button on a zone, section or a frame. The resulting time extent will match the selected item.
|
||||
|
||||
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
To reduce clutter, time range regions are only displayed if the windows they affect are open or if the time range limits control window is open (section~\ref{timerangelimits}). You can access the time range limits window through the \emph{\faScrewdriverWrench{} Tools} button on the control menu. Time range limits can also be disabled or enabled in the windows showing the data they affect.
|
||||
|
||||
You can freely adjust each time range on the timeline by clicking the \LMB{}~left mouse button on the range's edge and dragging the mouse.
|
||||
|
||||
@@ -4093,15 +4267,14 @@ Tracy allows adding custom notes to the trace. For example, you may want to mark
|
||||
|
||||
Methods of specifying the annotation region are described in section~\ref{timeranges}. When a new annotation is added, it is assigned a semi-unique random name to make it distinguishable. The settings window is also opened (section~\ref{annotationsettings}), allowing you to enter your own description of the annotation.
|
||||
|
||||
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the circle next to the text description will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
Annotations are displayed on the timeline, as presented in figure~\ref{annotation}. Clicking on the text description area will open the annotation settings window, in which you can modify or remove the region. List of all annotations in the trace is available in the annotations list window described in section~\ref{annotationlist}, which is accessible through the \emph{\faScrewdriverWrench{} Tools} button on the control menu.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw (0, 0.25) -- (0, 1) -- (5, 1) -- (5, 0.25);
|
||||
\draw[dotted] (0, -0.2) -- (0, 0.25);
|
||||
\draw[dotted] (5, -0.2) -- (5, 0.25);
|
||||
\draw (0.25, 0.75) circle(0.15);
|
||||
\draw (0.4, 0.7) node[anchor=west] {Description};
|
||||
\draw (0, -0.25) -- (0, -1) -- (5, -1) -- (5, -0.25);
|
||||
\draw[dotted] (0, 0.2) -- (0, -0.25);
|
||||
\draw[dotted] (5, 0.2) -- (5, -0.25);
|
||||
\draw (0, -0.8) node[anchor=west] {\faNoteSticky{}~Description};
|
||||
\end{tikzpicture}
|
||||
\caption{Annotation region.}
|
||||
\label{annotation}
|
||||
@@ -4128,7 +4301,9 @@ In this window, you can set various trace-related options. For example, the time
|
||||
\begin{itemize}
|
||||
\item \emph{\faSignature{} Draw CPU usage graph} -- You can disable drawing of the CPU usage graph here.
|
||||
\end{itemize}
|
||||
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL/Vulkan/Metal/Direct3D/OpenCL/CUDA/WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
|
||||
\item \emph{\faEyeDropper{}~Draw stack samples} -- Controls if stack samples for each thread are displayed on the timeline.
|
||||
\item \emph{\faArrowsLeftRightToLine{}~Draw sections} -- Allows disabling display of sections (see chapter~\ref{sections}). If there are multiple section categories, there's also an expandable list of categories that can be disabled or enabled.
|
||||
\item \emph{\faEye{} Draw GPU zones} -- Allows disabling display of OpenGL / Vulkan / Metal / Direct3D / OpenCL / CUDA / WebGPU zones. The \emph{GPU zones} drop-down allows disabling individual GPU contexts and setting CPU/GPU drift offsets of uncalibrated contexts (see section~\ref{gpuprofiling} for more information). The \emph{\faRobot~Auto} button automatically measures the GPU drift value\footnote{There is an assumption that drift is linear. Automated measurement calculates and removes change over time in delay-to-execution of GPU zones. Resulting value may still be incorrect.}.
|
||||
\item \emph{\faMicrochip{} Draw CPU zones} -- Determines whether CPU zones are displayed.
|
||||
\begin{itemize}
|
||||
\item \emph{\faGhost{} Draw ghost zones} -- Controls if ghost zones should be displayed in threads which don't have any instrumented zones available.
|
||||
@@ -4166,7 +4341,7 @@ For now, restoring the defaults can be done by deleting the configuration file.
|
||||
\subsection{Messages window}
|
||||
\label{messages}
|
||||
|
||||
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message.
|
||||
In this window, you can see all the messages that were sent by the client application, as described in section~\ref{messagelog}. The window is split into four columns: \emph{time}, \emph{thread}, \emph{message} and \emph{call stack}. Hovering the \faArrowPointer{}~mouse cursor over a message will highlight it on the timeline view. Clicking the \LMB{}~left mouse button on a message will center the timeline view on the selected message. Clicking the \RMB{}~right mouse button on a message will display context menu with a \emph{\faClipboard{}~Copy message} option, which copies the message text to clipboard.
|
||||
|
||||
The \emph{call stack} column is filled only if a call stack capture was requested, as described in section~\ref{collectingcallstacks}. A single entry consists of the \emph{\faAlignJustify{}~Show} button, which opens the call stack information window (chapter~\ref{callstackwindow}) and of abbreviated information about the call path.
|
||||
|
||||
@@ -4217,7 +4392,7 @@ The \emph{Name} column contains name of the symbol in which the sampling was don
|
||||
|
||||
By default, each inlining of a function is listed separately. If you prefer to combine the measurements for functions that are inlined multiple times within a function, you can do so by enabling the \emph{\faLayerGroup{}~Aggregate} option. You cannot view sample entry stacks of inlined functions when this grouping method is enabled.
|
||||
|
||||
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire*~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
|
||||
In some cases it may be more interesting to see the most time consuming inline within the symbol rather than the symbol name. If you enable the \emph{\faFire{}~Top inline} option, the name of the busiest inline function will be displayed in the \emph{Name} column.
|
||||
|
||||
If the \emph{\faSitemap{}~Inlines} option is enabled, the list will show all functions without grouping them by symbol. In this mode, inline functions are preceded by a \faCaretRight{} symbol and their parent function name is displayed in parentheses.
|
||||
|
||||
@@ -4225,9 +4400,9 @@ The \emph{Location} column displays the corresponding source file name and line
|
||||
|
||||
The location data is complemented by the originating executable image name, contained in the \emph{Image} column. If the \emph{\faScissors{}~Short images} option is selected, the image path will be shortened to just the image file name, with the full path available in the tooltip.
|
||||
|
||||
The profiler may not find some function locations due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
|
||||
The profiler may not find some function names due to insufficient debugging data available on the client-side. To filter out such entries, use the \emph{\faEyeSlash{}~Hide unknown} option.
|
||||
|
||||
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the total number of collected samples. You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
|
||||
The \emph{Time} or \emph{Count} column (depending on the \emph{\faStopwatch{}~Show time} option selection) shows number of taken samples, either as a raw count, or in an easier to understand time format. Note that the percentage value of time is calculated relative to the wall-clock time. The percentage value of sample counts is relative to the number of collected samples, excluding context switch samples, which do not participate in the sampling statistics (section~\ref{waitstacks}). You can also make the percentages of inline functions relative to the base symbol measurements by enabling the \emph{\faLink{}~Base relative} option.
|
||||
|
||||
The last column, \emph{Code size}, displays the size of the symbol in the executable image of the program. Since inlined routines are directly embedded into other functions, their symbol size will be based on the parent symbol and displayed as 'less than'. In some cases, this data won't be available. If the symbol code has been retrieved\footnote{Symbols larger than 128~KB are not captured.} symbol size will be prepended with the \texttt{\faDatabase}~icon, and clicking the \RMB{}~right mouse button on the location column entry will open symbol view window (section~\ref{symbolview}).
|
||||
|
||||
@@ -4363,7 +4538,21 @@ Clicking the \LMB{}~left mouse button on the group name will highlight the group
|
||||
|
||||
The call stack grouping mode has a different way of listing groups. Here only one group is displayed at any time due to the need to display the call stack frames. You can switch between call stack groups by using the~\faCaretLeft{}~and~\faCaretRight{} buttons. You can select the group by clicking on the~\emph{\faCheck{}~Select} button. You can open the call stack window (section~\ref{callstackwindow}) by pressing the~\emph{\faAlignJustify{}~Call~stack} button.
|
||||
|
||||
Tracy displays a variety of statistical values regarding the selected function: mean (average value), median (middle value), mode (most common value, quantized using histogram bins), and \textsigma{} (standard deviation). The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
|
||||
Tracy displays a variety of statistical values regarding the selected function:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{mean} -- average time value,
|
||||
\item \emph{median} -- middle time value (P50),
|
||||
\item \emph{mode} -- most common time value, quantized using histogram bins,
|
||||
\item \emph{\textsigma} -- standard deviation,
|
||||
\item \emph{coefficient of variation} -- relation of \textsigma{} to mean,
|
||||
\item \emph{P75} -- 75th percentile time value,
|
||||
\item \emph{P90} -- 90th percentile time value,
|
||||
\item \emph{P99} -- 99th percentile time value,
|
||||
\item \emph{P99.9} -- 99.9th percentile time value.
|
||||
\end{itemize}
|
||||
|
||||
The mean and median zone times are also displayed on the histogram as red (mean) and blue (median) vertical bars. Additional bars will indicate the mean group time (orange) and median group time (green). You can disable the drawing of either set of markers by clicking on the check-box next to the color legend.
|
||||
|
||||
Hovering the \faArrowPointer{}~mouse cursor over a zone on the timeline, which is currently selected in the find zone window, will display a pulsing vertical bar on the histogram, highlighting the bin to which the hovered zone has been assigned. In addition, it will also highlight zone entry on the zone list.
|
||||
|
||||
@@ -4419,11 +4608,27 @@ You can further narrow down the list of samples by selecting a time range on the
|
||||
|
||||
Comparing the performance impact of the optimization work is not an easy thing to do. Benchmarking is often inconclusive, if even possible, in the case of interactive applications, where the benchmarked function might not have a visible impact on frame render time. Furthermore, doing isolated micro-benchmarks loses the application's execution environment, in which many different parts compete for limited system resources.
|
||||
|
||||
Tracy solves this problem by providing a compare traces functionality, very similar to the find zone window, described in section~\ref{findzone}. You can compare traces either by zone or frame timing data.
|
||||
Tracy solves this problem by providing a compare traces functionality, which is very similar to the find zone window. There are three tabs in this window, for three modes of comparison:
|
||||
|
||||
You would begin your work by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
|
||||
\begin{itemize}
|
||||
\item \emph{Zones} -- closely matches the find zone window, described in section~\ref{findzone}.
|
||||
\item \emph{Frames} -- shows frame rate data, following the frame statistics window described in section~\ref{framestatistics}.
|
||||
\item \emph{Source diff} -- displays differences in source code, covered in section~\ref{sourcefilesdiff}.
|
||||
\end{itemize}
|
||||
|
||||
Now things start to get familiar. You search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram\footnote{When comparing frame times you are presented with a list of available frame sets, without the search box.}. This time there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
|
||||
To gather the data, you would start by recording a reference trace that represents the usual behavior of the program. Then, after the optimization of the code is completed, you record another trace, doing roughly what you did for the reference one. Finally, having the optimized trace open, you select the \emph{\faFolderOpen{}~Open second trace} option in the compare traces window and load the reference trace.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
logo=\bclampe
|
||||
]{Trace descriptions}
|
||||
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
|
||||
\end{bclogo}
|
||||
|
||||
Now things start to get familiar. In the zone comparison mode, you search for a zone, similarly like in the find zone window, choose the one you want in the \emph{matched source locations} drop-down, and then you look at the histogram. When comparing frame times you are presented with a list of available frame sets, without the search box.
|
||||
|
||||
In the compare traces window there are two overlaid graphs, one representing the current trace and the second one representing the external (reference) trace (figure~\ref{comparehistogram}). You can easily see how the performance characteristics of the zone were affected by your modifications.
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
@@ -4460,15 +4665,12 @@ When searching for source locations it's not uncommon to match more than one zon
|
||||
|
||||
It may be difficult, if not impossible, to perform identical runs of a program. This means that the number of collected zones may differ in both traces, influencing the displayed results. To fix this problem, enable the \emph{Normalize values} option, which will adjust the displayed results as if both traces had the same number of recorded zones.
|
||||
|
||||
\begin{bclogo}[
|
||||
noborder=true,
|
||||
couleur=black!5,
|
||||
logo=\bclampe
|
||||
]{Trace descriptions}
|
||||
Set custom trace descriptions (see section~\ref{traceinfo}) to easily differentiate the two loaded traces. If no trace description is set, the name of the profiled program will be displayed along with the capture time.
|
||||
\end{bclogo}
|
||||
The window shows statistical data for both traces, with the values described in chapter~\ref{findzone}. Each value is also compared against the other trace, showing both the absolute and relative differences. The keywords \emph{less} and \emph{more} are color coded to note significance. When the two readings are almost the same, the keywords will be printed in gray. As the values diverge, the amount of color (either green or red) increases, maxing out at twice the difference. This allows quick eyeballing of where the difference is.
|
||||
|
||||
In some cases you may want to compare only specific parts of traces, not the whole performance profile. To do so, you can enable the \emph{Limit range} option, which will then present you with time range limiting options for both traces. Use the \emph{\faCopy{}~Copy from...} button to choose a time range limit from a list of annotations (chapter~\ref{annotatingtrace}) or sections (chapter~\ref{sections}). To remove the limit, you can click on the \emph{Reset} button.
|
||||
|
||||
\subsubsection{Source files diff}
|
||||
\label{sourcefilesdiff}
|
||||
|
||||
To see what changes were made in the source code between the two compared traces, select the \emph{Source diff} compare mode. This will display a list of deleted, added, and changed files. By default, the difference is calculated from the older trace to the newer one. You can reverse this by clicking on the \emph{Switch} button.
|
||||
|
||||
@@ -4535,6 +4737,10 @@ Similar to the statistics window (section~\ref{statistics}), the flame graph can
|
||||
|
||||
In the sampling mode, external frames from system libraries are hidden by default. These typically include internal implementation details of starting threads, handling smart pointers, and other such things that are quick to execute and not really interesting. Enabling the \emph{\faShieldHalved{}~External} option will show these frames. One exception is \emph{external tails}, or calls that your code makes that do not eventually land in your application down the call chain. Think of functions that write to a file or send data on the network. These can be time-consuming, and you may want to see them. There is a separate option to disable these.
|
||||
|
||||
The sampling mode, by default, groups the displayed zones by symbol address. In some cases, for example, when using templates, the same function may exist at multiple different symbol addresses. Enabling the \emph{Group by name} option will group the graph items by name instead of by symbol address.
|
||||
|
||||
Sometimes a stack frame cannot be resolved to a symbol, as described in section~\ref{callstackwindow}. Such frames are aggregated into a single graph item with the label of the executable image the frame address belongs to, or with \enquote{\texttt{[unknown]}} if even this information is unavailable. The label is slightly dimmed to set it apart from the resolved items. The \emph{\faScissors{}~Short images} option shortens the displayed image name to only the file name.
|
||||
|
||||
The flame graph can be restricted to a specific time extent using the \emph{Limit range} option (chapter~\ref{timeranges}). You can access more options through the \emph{\faRuler{}~Limits} button, which will open the time range limits window, described in section~\ref{timerangelimits}.
|
||||
|
||||
\subsection{Memory window}
|
||||
@@ -4610,8 +4816,6 @@ If the \emph{\faUserGear{}~Public sidecar} option is selected, the file containi
|
||||
|
||||
Open the \emph{Trace statistics} section to see information about the trace, such as achieved timer resolution, number of captured zones, lock events, plot data points, memory allocations, etc.
|
||||
|
||||
There's also a section containing the selected frame set timing statistics and histogram\footnote{See section~\ref{findzone} for a description of the histogram. Note that there are subtle differences in the available functionality.}. As a convenience, you can switch the active frame set here and limit the displayed frame statistics to the frame range visible on the screen.
|
||||
|
||||
If \emph{CPU topology} data is available (see section~\ref{cputopology}), you will be able to view the package, core, and thread hierarchy.
|
||||
|
||||
The \emph{Source location substitutions} section allows adapting the source file paths, as captured by the profiler, to the actual on-disk locations\footnote{This does not affect source files cached during the profiling run.}. You can create a new substitution by clicking the \emph{Add new substitution} button. This will add a new entry, with input fields for ECMAScript-conforming regular expression pattern and its corresponding replacement string. You can quickly test the outcome of substitutions in the \emph{example source location} input field, which will be transformed and displayed below, as \emph{result}.
|
||||
@@ -4635,6 +4839,13 @@ In this window, you can view the information about the machine on which the prof
|
||||
|
||||
If an application should crash during profiling (section~\ref{crashhandling}), the profiler will display the crash information in this window. It provides you information about the thread that has crashed, the crash reason, and the crash call stack (section~\ref{callstackwindow}).
|
||||
|
||||
\subsection{Frame statistics window}
|
||||
\label{framestatistics}
|
||||
|
||||
This windows shows statistical information about the selected frame set timing, and a histogram. See section~\ref{findzone} for a description of the displayed data. As a convenience, you can switch the active frame set here.
|
||||
|
||||
You can restrict the displayed frame statistics to the frame range currently visible on the screen with the \emph{Limit to view} option, while the \emph{Limit range} switch limits the active range to a preset time limit, as described in chapter~\ref{timerangelimits}. If both limit options are active, the resulting frame range is the intersection of both ranges.
|
||||
|
||||
\subsection{Zone information window}
|
||||
\label{zoneinfo}
|
||||
|
||||
@@ -4684,6 +4895,8 @@ A single stack frame may have multiple function call places associated with it.
|
||||
|
||||
If the call stack shows a crash (see section~\ref{crashhandling}), a red \emph{\faSkull{}~Crash} label will be displayed. Clicking it will center the timeline on the crash. Note that the crash stack may contain OS or Tracy frames where the crash was intercepted and processed.
|
||||
|
||||
If the call stack shows a wait stack (see section~\ref{waitstacks}), a blue \emph{\faHourglassHalf{}~Wait stack} label will be displayed. Hovering the \faArrowPointer{}~mouse pointer over it will display a tooltip displaying how much time was spent waiting in the stack, what was the wait reason and status.
|
||||
|
||||
Stack frame location may be displayed in the following number of ways, depending on the \emph{Frame~at} option selection:
|
||||
|
||||
\begin{itemize}
|
||||
@@ -4699,7 +4912,7 @@ External frames from system libraries are hidden by default. Enabling the \emph{
|
||||
|
||||
The \emph{\faScissors{}~Short images} option shortens the displayed executable image name to only the file name. The full path is available in the tooltip.
|
||||
|
||||
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faDoorOpen{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
|
||||
If the displayed call stack is a sampled call stack (chapter~\ref{sampling}), an additional button may be available, \emph{\faArrowDownShortWide{}~Entry stacks}. Clicking it will open the sample entry stacks window (chapter~\ref{stackwindows}) for the current call stack.
|
||||
|
||||
Clicking on the \emph{\faClipboard{}~Copy to clipboard} button will copy call stack to the clipboard.
|
||||
|
||||
@@ -4767,7 +4980,7 @@ Nevertheless, \textbf{the displayed source files might still not reflect the cod
|
||||
|
||||
A much more capable symbol view mode is available if the inspected source location has an associated symbol context (i.e., if it comes from a call stack capture, from call stack sampling, etc.). A symbol is a unit of machine code, basically a callable function. It may be generated using multiple source files and may consist of numerous inlined functions. A list of all captured symbols is available in the statistics window, as described in chapter~\ref{statisticssampling}.
|
||||
|
||||
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{Entry stacks} button.
|
||||
The header of symbol view window contains a name of the selected \emph{\faPuzzlePiece{}~symbol}, a list of \emph{\faSitemap{}~functions} that contribute to the symbol, and information such as count of probed \emph{\faEyeDropper{}~Samples}. The entry stacks (section~\ref{stackwindows}) of the symbol can be viewed by clicking on the \emph{\faArrowDownShortWide{}~Entry stacks} button.
|
||||
|
||||
Additionally, you may use the \emph{Mode} selector to decide what content should be displayed in the panels below:
|
||||
|
||||
@@ -4902,8 +5115,13 @@ The percentage values when \emph{\faCarBurst{}~Impact} option is not selected wi
|
||||
The profiler can group call stacks leading to certain events and display the resulting information in a variety of ways. In essence, this shows the code paths that lead to these events and the distribution of these paths. At this moment, these events include:
|
||||
|
||||
\begin{itemize}
|
||||
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active.
|
||||
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information.
|
||||
\item \textbf{Sample entry stacks} -- this window shows all the paths that lead to execution of the selected symbol. Requires sampling (chapter~\ref{sampling}) to be active. The displayed data set can be selected with the following options:
|
||||
\begin{itemize}
|
||||
\item \emph{Was reached} -- counts samples in which the symbol was present anywhere on the call stack. Each sample is counted only once, at the outermost occurrence of the symbol, so recursive re-entries are ignored.
|
||||
\item \emph{Was reached, recursive} -- as above, but each occurrence of the symbol on the call stack is counted separately, showing also the paths by which the symbol re-entered itself.
|
||||
\item \emph{Was executing} -- counts samples taken while the symbol was executing, i.e.\ it was at the top of the call stack.
|
||||
\end{itemize}
|
||||
\item \textbf{Wait stacks} -- this windows shows all the places where the application was sleeping. See chapter~\ref{waitstacks} for more information. Note that wait stacks have separate setting for the \emph{\faShieldHalved{}~External} option, as it is generally beneficial to see the external code when determining the cause of program interruption.
|
||||
\end{itemize}
|
||||
|
||||
The call stack paths may be displayed in the following ways:
|
||||
@@ -4924,9 +5142,11 @@ This window presents information and statistics about a lock. The lock events co
|
||||
\subsection{Frame image playback window}
|
||||
\label{playback}
|
||||
|
||||
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons to change single frame back or forward.
|
||||
You may view a live replay of the profiled application screen captures (see section~\ref{frameimages}) using this window. Playback is controlled by the \emph{\faPlay~Play} and \emph{\faPause~Pause} buttons, and the \emph{Frame image} slider can be used to scrub to the desired timestamp. Alternatively, you may use the \emph{\faCaretLeft} and \emph{\faCaretRight} buttons, or use the \Scroll{}~mouse wheel on either the \emph{Frame image} slider or the displayed frame image to change a single frame backward or forward.
|
||||
|
||||
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing.
|
||||
If the \emph{Sync timeline} option is selected, the profiler will focus the timeline view on the frame corresponding to the currently displayed screenshot. The \emph{Zoom 2$\times$} option enlarges the image for easier viewing. With the \emph{Loop} option enabled, playback will start from the beginning after it reaches the end.
|
||||
|
||||
Enabling the \emph{Limit frame range} option allows you to manually set the frame image playback region. Adjustments can be made using the start and end sliders or by copying the time range from either an annotation (section~\ref{annotationlist}), a section (chapter~\ref{sections}), or one of the time range limits (section~\ref{timerangelimits}). If the \emph{Require frame coverage} option is enabled, source regions must cover at least 75\% of the frame. Otherwise any amount of overlap is sufficient.
|
||||
|
||||
The following parameters also accompany each displayed frame image: \emph{timestamp}, showing at which time the image was captured, \emph{frame}, displaying the numerical value of the corresponding frame, and \emph{ratio}, telling how well the in-memory loss-less compression was able to reduce the image data size.
|
||||
|
||||
@@ -4944,7 +5164,7 @@ The profiled program is highlighted using green color. Furthermore, the yellow h
|
||||
\subsection{Annotation settings window}
|
||||
\label{annotationsettings}
|
||||
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also remove it here.
|
||||
In this window, you may modify how a timeline annotation (section~\ref{annotatingtrace}) is presented by setting its text description or selecting region highlight color. A random annotation description can be set with the \emph{\faDice{}~Generate name} button. If the note is no longer needed, you may also temporarily hide or remove it here.
|
||||
|
||||
\subsection{Annotation list window}
|
||||
\label{annotationlist}
|
||||
@@ -4952,15 +5172,17 @@ In this window, you may modify how a timeline annotation (section~\ref{annotatin
|
||||
This window lists all annotations marked on the timeline. Each annotation is presented, as shown on figure~\ref{figannlist}. From left to right the elements are:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{\faPenToSquare{} Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
|
||||
\item \emph{\faMicroscope{} Zoom} -- Zooms timeline to the annotation extent.
|
||||
\item \emph{\faTrashCan{} Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
|
||||
\item \emph{\faCheck{}~Visible} -- Makes the annotation visible or not on the timeline.
|
||||
\item \emph{\faPenToSquare{}~Edit} -- Opens the annotation settings window (section~\ref{annotationsettings}).
|
||||
\item \emph{\faMicroscope{}~Zoom} -- Zooms timeline to the annotation extent.
|
||||
\item \emph{\faTrashCan{}~Remove} -- Removes the annotation. You must press the \keys{\ctrl} key to enable this button.
|
||||
\item Colored box -- Color of the annotation.
|
||||
\item Text description of the annotation.
|
||||
\end{itemize}
|
||||
|
||||
\begin{figure}[h]
|
||||
\centering\begin{tikzpicture}
|
||||
\draw[rounded corners=5pt] (-0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faCheck};
|
||||
\draw[rounded corners=5pt] (0.0, 0) rectangle+(0.5, -0.5) node [midway] {\faPenToSquare};
|
||||
\draw[rounded corners=5pt] (0.6, 0) rectangle+(0.5, -0.5) node [midway] {\faMicroscope};
|
||||
\draw[rounded corners=5pt] (1.2, 0) rectangle+(0.5, -0.5) node [midway] {\faTrashCan};
|
||||
@@ -4971,22 +5193,28 @@ This window lists all annotations marked on the timeline. Each annotation is pre
|
||||
\label{figannlist}
|
||||
\end{figure}
|
||||
|
||||
A new view-sized annotation can be added in this window by pressing the \emph{\faPlus{}~Add annotation} button. This effectively saves your current viewport for further reference.
|
||||
A new view-sized annotation can be added in this window by pressing the \emph{\faNoteSticky{}~Add annotation} button. This effectively saves your current viewport for further reference.
|
||||
|
||||
\subsection{Time range limits}
|
||||
\label{timerangelimits}
|
||||
|
||||
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), memory (section~\ref{memorywindow}) and wait stacks (section~\ref{stackwindows}) results. Each limit can be enabled or disabled and adjusted through the following options:
|
||||
This window displays information about time range limits (section~\ref{timeranges}) for find zone (section~\ref{findzone}), statistics (section~\ref{statistics}), flame graph (section~\ref{flamegraph}), wait stacks (section~\ref{stackwindows}), memory (section~\ref{memorywindow}) and frame statistics (section~\ref{framestatistics}) results. Each limit can be enabled or disabled and adjusted through the following options:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{Limit to view} -- Set the time range limit to current view.
|
||||
\item \emph{\faMicroscope{}~Focus} -- Set the timeline view to the time range extent.
|
||||
\item \emph{\faNoteSticky{}~From annotation} -- Allows using the annotation region for limiting purposes.
|
||||
\item \emph{\faMagnifyingGlass{}~Copy find zone} -- Copies the find zone time range limit.
|
||||
\item \emph{\faArrowUpWideShort{}~Copy statistics} -- Copies the statistics time range limit.
|
||||
\item \emph{\faFireFlameCurved{}~Copy flame} -- Copies the flame graph time range limit.
|
||||
\item \emph{\faHourglassHalf{}~Copy wait stacks} -- Copies the wait stacks time range limit.
|
||||
\item \emph{\faMemory{}~Copy memory} -- Copies the memory time range limit.
|
||||
\item \emph{Limit to view} -- Set the time range limit to current view.
|
||||
\item \emph{\faNoteSticky{}~Add annotation} -- Create a new annotation matching the time range. See section~\ref{annotatingtrace} for more information.
|
||||
\item \faCopy{}~Copy from...:
|
||||
\begin{itemize}
|
||||
\item \emph{\faNoteSticky{}~Annotation} -- Allows using the annotation region for limiting purposes.
|
||||
\item \emph{\faArrowsLeftRightToLine{}~Sections} -- Copies time range of a section. See section~\ref{sections} for more information.
|
||||
\item \emph{\faMagnifyingGlass{}~Find zone} -- Copies the find zone time range limit.
|
||||
\item \emph{\faArrowUpWideShort{}~Statistics} -- Copies the statistics time range limit.
|
||||
\item \emph{\faFireFlameCurved{}~Flame graph} -- Copies the flame graph time range limit.
|
||||
\item \emph{\faHourglassHalf{}~Wait stacks} -- Copies the wait stacks time range limit.
|
||||
\item \emph{\faMemory{}~Memory} -- Copies the memory time range limit.
|
||||
\item \emph{\faImages{}~Frame statistics} -- Copies the frame statistics range limit.
|
||||
\end{itemize}
|
||||
\end{itemize}
|
||||
|
||||
Note that ranges displayed in the window have color hints that match the color of the striped regions on the timeline.
|
||||
@@ -5018,6 +5246,42 @@ You do not. Tracy is not a money funnel for Silicon Valley tech bros to get rich
|
||||
The only way to access the assistant is to run everything locally on your system. This ensures that everything you do stays private and that you won't be subject to forced changes in features or terms and conditions. You should own the tools you work with instead of renting them from someone else.
|
||||
\end{bclogo}
|
||||
|
||||
If you just want to get things running and have a reasonably powerful hardware, follow the steps below.
|
||||
|
||||
\begin{enumerate}
|
||||
\item Go to \url{https://llama.app/} and follow instructions to install llama.cpp.
|
||||
\item Create the following \texttt{llama.ini} configuration file:
|
||||
\begin{lstlisting}
|
||||
; Launch with: llama-server --models-preset llama.ini
|
||||
|
||||
[*]
|
||||
version = 1
|
||||
cache-type-k = q8_0
|
||||
cache-type-v = q8_0
|
||||
|
||||
[bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M]
|
||||
hf = bartowski/Qwen_Qwen3.6-35B-A3B-GGUF:Q4_K_M
|
||||
parallel = 1
|
||||
spec-default = true
|
||||
spec-type = draft-mtp
|
||||
chat-template-kwargs = {"preserve_thinking": true}
|
||||
ctx-size = 100000
|
||||
|
||||
[nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M]
|
||||
hf = nomic-ai/nomic-embed-text-v1.5-GGUF:Q4_K_M
|
||||
embedding = true
|
||||
parallel = 4
|
||||
ctx-size = 8192
|
||||
cache-ram = 0
|
||||
\end{lstlisting}
|
||||
\item Run \texttt{llama serve -{}-models-preset llama.ini}
|
||||
\item Connect to llama.cpp inside the profiler!
|
||||
\end{enumerate}
|
||||
|
||||
The models will be automatically downloaded when trying to access them for the first time. It may take some time (the main model is 22+ GB).
|
||||
|
||||
If you have the resources available you may try replacing \texttt{bartowski/Qwen\_Qwen3.6-35B-A3B-GGUF:Q4\_K\_M} with \texttt{unsloth/Qwen3.6-27B-MTP-GGUF:Q4\_K\_M} in the configuration file to get a more capable model.
|
||||
|
||||
\subsection{Service provider}
|
||||
|
||||
To get started, you will need to install an LLM\footnote{Large Language Model.} provider on your system. Any service that's compatible with the standard API should work, but some may work better than others. The LLM field is advancing quickly, with new models frequently being released that often require specific support from provider services to deliver the best experience.
|
||||
@@ -5074,18 +5338,18 @@ Each token present in the context window may require a fairly large amount of me
|
||||
The realistic minimum required context size for Tracy to run the assistant is 100K tokens, but feel free to experiment.
|
||||
\end{bclogo}
|
||||
|
||||
\subsubsection{Fast model}
|
||||
|
||||
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
|
||||
|
||||
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
|
||||
|
||||
\subsubsection{Embedding model}
|
||||
|
||||
This is a small model used for semantic search in the user manual. This should be \textbf{nomic-embed-text-1.5}, which is provided by default by LM Studio, or which you can download on your own for llama.cpp.
|
||||
|
||||
LM Studio properly labels the model's capabilities. This is not the case with the llama.cpp/llama-swap setup. To make it work, your embedding model's name must contain the word \texttt{embed}.
|
||||
|
||||
\subsubsection{Fast model}
|
||||
|
||||
Sometimes Tracy needs to do some language processing where speed is more important than the smarts. The default setting is to use the chat model with the reasoning disabled, which is fine for most applications.
|
||||
|
||||
It may be more convenient to use a small, quick model instead, in which case enable the \emph{Fast model} checkbox and choose the second model. To save precious GPU resources for the chat model, you may want to keep this model entirely in system RAM (set \texttt{-ngl 0} for llama.cpp or set \enquote{GPU offload} to 0 in LM Studio) and disable the KV cache offload to GPU (set \texttt{-nkvo} for llama.cpp or disable \enquote{Offload KV Cache to GPU Memory} in LM Studio).
|
||||
|
||||
\subsubsection{In practice}
|
||||
|
||||
So, which model should you run and what hardware you need to be able to do so? Let's take look at some example systems.
|
||||
@@ -5115,16 +5379,23 @@ The chat window is divided into three sections:
|
||||
The control section allows you to clear the chat contents, reconnect to the LLM provider and open the settings panel consisting of:
|
||||
|
||||
\begin{itemize}
|
||||
\item \emph{API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
|
||||
\item \emph{\faPlug{}~API} -- Enter the endpoint URL of the LLM provider here. A drop-down list is provided as a convenient way to select the default configuration of various providers. Note that the drop-down list is only used to fill in the endpoint URL. While Tracy does adapt to different ways each provider behaves, the feature detection is performed based on the endpoint conversation, not the drop-down selection.
|
||||
\item \emph{\faComments{}~Chat model} -- Here you can select one of the models you have configured in the LLM provider for chat.
|
||||
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
|
||||
\item \emph{\faBookBookmark{}~Embeddings model} -- Select the vector embeddings model.
|
||||
\item \emph{Personality} -- Choose how the assistant should behave. Note that the actual behavior will vary depending on the language model you have chosen. The following options are available:
|
||||
\begin{itemize}
|
||||
\item \emph{\faFaceMeh{}~Assistant} -- The default behavior of the model with Tracy Assist role guidance.
|
||||
\item \emph{\faFaceGrin{}~Emotion} -- The model will behave like a human with feelings and emotions.
|
||||
\item \emph{\faFaceFrown{}~Annoyed} -- Does not tolerate nonsense and will respond with sarcasm.
|
||||
\end{itemize}
|
||||
Personality changes do not take effect until you start a new chat.
|
||||
\item \emph{\faEarthAmericas{}~Internet access} -- Determines whether the model can access network resources such as Wikipedia queries, web searches, and web page retrievals.
|
||||
\item \emph{\faTag{}~Annotate call stacks} -- Enables automatic annotation of call stacks (see section~\ref{callstackwindow}). Disabled by default, as it requires proper configuration of the fast model.
|
||||
\item \emph{\faHandPointRight{}~Show summary} -- Shows a short conversation topic after the initial question is asked.
|
||||
\item \emph{\faCommentDots{}~Chat suggestions} -- Suggests the next question the user may want to ask.
|
||||
\item \emph{Advanced} -- More advanced options are hidden here.
|
||||
\begin{itemize}
|
||||
\item \emph{\faBoltLightning{}~Fast model} -- Select the fast model.
|
||||
\item \emph{\faTemperatureHalf{}~Temperature} -- Allows changing default model temperature setting.
|
||||
\item \emph{\faLightbulb{}~Show all thinking regions} -- Always shows all reasoning sections and all tool calls made by model.
|
||||
\item \emph{Tool reply size limit} -- Configurable maximum size for tool responses.
|
||||
|
||||
31
merge/CMakePresets.json
Normal file
@@ -0,0 +1,31 @@
|
||||
{
|
||||
"version": 10,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/build-${presetName}",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug configuration",
|
||||
"inherits": "common",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release configuration",
|
||||
"inherits": "common",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
17
meson.build
@@ -1,4 +1,4 @@
|
||||
project('tracy', ['cpp'], version: '0.13.1', meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
project('tracy', ['cpp'], version: run_command('sh', files('extra/tracy-version.sh'), 'public/common/TracyVersion.hpp', check: true).stdout().strip(), meson_version: '>=1.3.0', default_options : ['cpp_std=c++11'])
|
||||
|
||||
# internal compiler flags
|
||||
tracy_compile_args = []
|
||||
@@ -61,6 +61,10 @@ if get_option('no_sampling')
|
||||
tracy_common_args += ['-DTRACY_NO_SAMPLING']
|
||||
endif
|
||||
|
||||
if get_option('no_wait_stacks')
|
||||
tracy_common_args += ['-DTRACY_NO_WAIT_STACKS']
|
||||
endif
|
||||
|
||||
if get_option('no_verify')
|
||||
tracy_common_args += ['-DTRACY_NO_VERIFY']
|
||||
endif
|
||||
@@ -81,10 +85,6 @@ if get_option('patchable_nopsleds')
|
||||
tracy_common_args += ['-DTRACY_PATCHABLE_NOPSLEDS']
|
||||
endif
|
||||
|
||||
if get_option('delayed_init')
|
||||
tracy_common_args += ['-DTRACY_DELAYED_INIT']
|
||||
endif
|
||||
|
||||
if get_option('manual_lifetime')
|
||||
tracy_common_args += ['-DTRACY_MANUAL_LIFETIME']
|
||||
endif
|
||||
@@ -158,7 +158,8 @@ includes = [
|
||||
'public/tracy/TracyLua.hpp',
|
||||
'public/tracy/TracyOpenCL.hpp',
|
||||
'public/tracy/TracyOpenGL.hpp',
|
||||
'public/tracy/TracyVulkan.hpp'
|
||||
'public/tracy/TracyVulkan.hpp',
|
||||
'public/tracy/TracyWebGPU.hpp'
|
||||
]
|
||||
|
||||
client_includes = [
|
||||
@@ -190,15 +191,19 @@ common_includes = [
|
||||
'public/common/TracyAlign.hpp',
|
||||
'public/common/TracyAlloc.hpp',
|
||||
'public/common/TracyApi.h',
|
||||
'public/common/TracyAssert.hpp',
|
||||
'public/common/TracyColor.hpp',
|
||||
'public/common/TracyForceInline.hpp',
|
||||
'public/common/TracyFormat.h',
|
||||
'public/common/TracyMutex.hpp',
|
||||
'public/common/TracyProtocol.hpp',
|
||||
'public/common/TracyQueue.hpp',
|
||||
'public/common/TracySocket.hpp',
|
||||
'public/common/TracyStackFrames.hpp',
|
||||
'public/common/TracyString.hpp',
|
||||
'public/common/TracySystem.hpp',
|
||||
'public/common/TracyTaggedUserlandAddress.hpp',
|
||||
'public/common/TracyVersion.hpp',
|
||||
'public/common/TracyWinFamily.hpp',
|
||||
'public/common/TracyYield.hpp'
|
||||
]
|
||||
|
||||
@@ -11,6 +11,7 @@ option('no_code_transfer', type : 'boolean', value : false, description : 'Disab
|
||||
option('no_context_switch', type : 'boolean', value : false, description : 'Disable capture of context switches')
|
||||
option('no_exit', type : 'boolean', value : false, description : 'Client executable does not exit until all profile data is sent to server')
|
||||
option('no_sampling', type : 'boolean', value : false, description : 'Disable call stack sampling')
|
||||
option('no_wait_stacks', type : 'boolean', value : false, description : 'Disable capture of context switch call stacks')
|
||||
option('no_verify', type : 'boolean', value : false, description : 'Disable zone validation for C API')
|
||||
option('no_vsync_capture', type : 'boolean', value : false, description : 'Disable capture of hardware Vsync events')
|
||||
option('no_frame_image', type : 'boolean', value : false, description : 'Disable the frame image support and its thread')
|
||||
@@ -21,12 +22,11 @@ option('disallow_hw_timer', type : 'boolean', value : false, description : 'Disa
|
||||
option('libunwind_backtrace', type : 'boolean', value : false, description : 'Use libunwind backtracing where supported')
|
||||
option('symbol_offline_resolve', type : 'boolean', value : false, description : 'Instead of full runtime symbol resolution, only resolve the image path and offset to enable offline symbol resolution')
|
||||
option('libbacktrace_elf_dynload_support', type : 'boolean', value : false, description : 'Enable libbacktrace to support dynamically loaded elfs in symbol resolution resolution after the first symbol resolve operation')
|
||||
option('delayed_init', type : 'boolean', value : false, description : 'Enable delayed initialization of the library (init on first call)')
|
||||
option('manual_lifetime', type : 'boolean', value : false, description : 'Enable the manual lifetime management of the profile')
|
||||
option('fibers', type : 'boolean', value : false, description : 'Enable fibers support')
|
||||
option('no_crash_handler', type : 'boolean', value : false, description : 'Disable crash handling')
|
||||
option('verbose', type : 'boolean', value : false, description : 'Enable verbose logging')
|
||||
option('no_internal_message', type : 'boolean', value : false, description : 'Prevent the profiler from logging messages')
|
||||
option('debuginfod', type : 'boolean', value : false, description : 'Enable debuginfod support')
|
||||
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation')
|
||||
option('ignore_memory_faults', type : 'boolean', value : false, description : 'Ignore instrumentation errors from memory free events that do not have a matching allocation, or from repeated allocations of the same address')
|
||||
option('opengl_auto_calibration', type : 'boolean', value : false, description : 'Periodically recalibrate OpenGL GPU/CPU clock drift (forces a CPU/GPU sync each time)')
|
||||
|
||||
@@ -28,7 +28,6 @@ target_include_directories(${PROJECT_NAME} SYSTEM PUBLIC
|
||||
$<INSTALL_INTERFACE:include/tracy>)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||
TRACY_ENABLE
|
||||
TRACY_DELAYED_INIT
|
||||
TRACY_MANUAL_LIFETIME
|
||||
TRACY_NO_FRAME_IMAGE
|
||||
)
|
||||
|
||||
@@ -8,7 +8,12 @@ set_option(LEGACY "Instead of Wayland, use the legacy X11 backend on Linux" OFF)
|
||||
set_option(SELF_PROFILE "Enable self-profiling" OFF)
|
||||
set_option_value(SANITIZE "Sanitizer parameters" "")
|
||||
|
||||
if(APPLE)
|
||||
set_option(BUNDLE "Build a macOS application bundle" OFF)
|
||||
endif()
|
||||
|
||||
set(NO_STATISTICS OFF)
|
||||
set(VENDOR_GUI ON)
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/../cmake/version.cmake)
|
||||
|
||||
@@ -101,6 +106,7 @@ set(SERVER_FILES
|
||||
TracyView_FindZone.cpp
|
||||
TracyView_FlameGraph.cpp
|
||||
TracyView_FrameOverview.cpp
|
||||
TracyView_FrameStatistics.cpp
|
||||
TracyView_FrameTimeline.cpp
|
||||
TracyView_FrameTree.cpp
|
||||
TracyView_GpuTimeline.cpp
|
||||
@@ -122,6 +128,7 @@ set(SERVER_FILES
|
||||
TracyView_ZoneInfo.cpp
|
||||
TracyView_ZoneTimeline.cpp
|
||||
TracyWeb.cpp
|
||||
TracyWindowConstraints.cpp
|
||||
)
|
||||
|
||||
if(NOT EMSCRIPTEN)
|
||||
@@ -152,6 +159,8 @@ set(PROFILER_FILES
|
||||
src/winmainArchDiscovery.cpp
|
||||
)
|
||||
|
||||
Embed(PROFILER_FILES PersonalityAnnoyed src/llm/personality.annoyed.md TEXT)
|
||||
Embed(PROFILER_FILES PersonalityEmotion src/llm/personality.emotion.md TEXT)
|
||||
Embed(PROFILER_FILES SystemPrompt src/llm/system.prompt.md TEXT)
|
||||
Embed(PROFILER_FILES SkillCallstack src/llm/skill.callstack.md TEXT)
|
||||
Embed(PROFILER_FILES SkillOptimization src/llm/skill.optimization.md TEXT)
|
||||
@@ -185,8 +194,12 @@ set(LIBS "")
|
||||
|
||||
if(USE_WAYLAND)
|
||||
pkg_check_modules(WAYLAND REQUIRED egl wayland-egl wayland-cursor xkbcommon)
|
||||
pkg_check_modules(WAYLAND_CLIENT REQUIRED wayland-client)
|
||||
if(WAYLAND_CLIENT_VERSION VERSION_LESS 1.22)
|
||||
message(WARNING "libwayland ${WAYLAND_CLIENT_VERSION} is too old and does not provide version 6 of the core protocol. Building in this configuration is not supported and will result in reduced functionality.")
|
||||
endif()
|
||||
set(INCLUDES "${INCLUDES};${WAYLAND_INCLUDE_DIRS}")
|
||||
set(LIBS "${LIBS};${WAYLAND_LIBRARIES}")
|
||||
set(LIBS "${LIBS};${WAYLAND_LINK_LIBRARIES}")
|
||||
set(PROFILER_FILES ${PROFILER_FILES}
|
||||
src/BackendWayland.cpp
|
||||
)
|
||||
@@ -259,6 +272,25 @@ if(WIN32)
|
||||
)
|
||||
add_executable(${PROJECT_NAME} WIN32 ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
set_property(DIRECTORY ${CMAKE_CURRENT_LIST_DIR} PROPERTY VS_STARTUP_PROJECT ${PROJECT_NAME})
|
||||
elseif(APPLE AND BUNDLE)
|
||||
set(ICON_SRC ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.png)
|
||||
set(ICON_ICNS ${CMAKE_CURRENT_BINARY_DIR}/icon.icns)
|
||||
add_custom_command(
|
||||
OUTPUT ${ICON_ICNS}
|
||||
COMMAND sips -s format icns ${ICON_SRC} --out ${ICON_ICNS}
|
||||
DEPENDS ${ICON_SRC}
|
||||
)
|
||||
set_source_files_properties(${ICON_ICNS} PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
|
||||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES} ${ICON_ICNS})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_LIST_DIR}/macos/Info.plist.in
|
||||
MACOSX_BUNDLE_BUNDLE_NAME "Tracy Profiler"
|
||||
MACOSX_BUNDLE_GUI_IDENTIFIER "pl.nereid.tracy"
|
||||
MACOSX_BUNDLE_BUNDLE_VERSION ${TRACY_VERSION_STRING}
|
||||
MACOSX_BUNDLE_SHORT_VERSION_STRING ${TRACY_VERSION_STRING}
|
||||
MACOSX_BUNDLE_ICON_FILE icon.icns
|
||||
MACOSX_BUNDLE_COPYRIGHT "Bartosz Taudul"
|
||||
)
|
||||
else()
|
||||
add_executable(${PROJECT_NAME} ${PROFILER_FILES} ${COMMON_FILES} ${SERVER_FILES})
|
||||
endif()
|
||||
@@ -293,6 +325,8 @@ add_git_ref(${PROJECT_NAME})
|
||||
if(NOT EMSCRIPTEN)
|
||||
if(NOT NO_FILESELECTOR)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE nfd::nfd)
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE TRACY_NO_FILESELECTOR)
|
||||
endif()
|
||||
if(NOT USE_WAYLAND)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE TracyGlfw3)
|
||||
@@ -314,7 +348,7 @@ if(EMSCRIPTEN)
|
||||
--preload-file embed.tracy
|
||||
)
|
||||
|
||||
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 ca0fa4f01e7b8ca5581daa16b16c768d)
|
||||
file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 f018919fcd34de2fb349be8be354048b)
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/index.html DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY ${CMAKE_CURRENT_LIST_DIR}/wasm/httpd.py DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
|
||||
file(COPY_FILE ${CMAKE_CURRENT_LIST_DIR}/../icon/icon.svg ${CMAKE_CURRENT_BINARY_DIR}/favicon.svg)
|
||||
|
||||
47
profiler/CMakePresets.json
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"version": 10,
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "common",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/build-${presetName}",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "debug",
|
||||
"displayName": "Debug configuration",
|
||||
"inherits": "common",
|
||||
"binaryDir": "${sourceDir}/build",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Debug"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "release",
|
||||
"displayName": "Release configuration",
|
||||
"inherits": "common",
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "Release"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "profile",
|
||||
"displayName": "Self-profiling configuration",
|
||||
"inherits": "release",
|
||||
"cacheVariables": {
|
||||
"SELF_PROFILE": "1"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "web",
|
||||
"displayName": "Web build",
|
||||
"inherits": "release",
|
||||
"cacheVariables": {
|
||||
"CMAKE_TOOLCHAIN_FILE": "~/emsdk/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
72
profiler/appimage/build.sh
Executable file
@@ -0,0 +1,72 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
# Builds the profiler as an AppImage. The Wayland client libraries are built
|
||||
# from a pinned release and bundled, so the core protocol version does not
|
||||
# depend on what the build host provides. Everything else that is host-coupled
|
||||
# (glibc, EGL, dbus, xkbcommon, OpenSSL) is left to the target system, which
|
||||
# is expected to be reasonably current. The build host sets the glibc floor,
|
||||
# so this should run on the oldest distro release that is still supported.
|
||||
|
||||
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
|
||||
BUILD="$ROOT/profiler/build-appimage"
|
||||
PREFIX="$BUILD/wayland"
|
||||
APPDIR="$BUILD/AppDir"
|
||||
|
||||
WAYLAND_VERSION=1.25.0
|
||||
APPIMAGETOOL_URL="https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage"
|
||||
|
||||
mkdir -p "$BUILD"
|
||||
|
||||
if [ ! -e "$PREFIX/lib/pkgconfig/wayland-client.pc" ]; then
|
||||
git clone --depth 1 --branch $WAYLAND_VERSION https://gitlab.freedesktop.org/wayland/wayland.git "$BUILD/wayland-src"
|
||||
meson setup "$BUILD/wayland-build" "$BUILD/wayland-src" \
|
||||
--prefix "$PREFIX" --libdir lib --buildtype release \
|
||||
-Ddocumentation=false -Dtests=false -Ddtd_validation=false
|
||||
meson install -C "$BUILD/wayland-build"
|
||||
fi
|
||||
export PKG_CONFIG_PATH="$PREFIX/lib/pkgconfig"
|
||||
export PATH="$PREFIX/bin:$PATH"
|
||||
|
||||
cmake -B "$BUILD/profiler" -S "$ROOT/profiler" -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DNO_ISA_EXTENSIONS=ON \
|
||||
-DCMAKE_BUILD_RPATH='$ORIGIN/../lib' \
|
||||
-DDOWNLOAD_CAPSTONE=ON \
|
||||
-DDOWNLOAD_FREETYPE=ON \
|
||||
-DDOWNLOAD_LIBCURL=ON \
|
||||
-DDOWNLOAD_PUGIXML=ON \
|
||||
${GIT_REV:+-DGIT_REV=$GIT_REV}
|
||||
cmake --build "$BUILD/profiler" --parallel "$(nproc)"
|
||||
|
||||
rm -rf "$APPDIR"
|
||||
mkdir -p "$APPDIR/usr/bin" "$APPDIR/usr/lib" \
|
||||
"$APPDIR/usr/share/applications" \
|
||||
"$APPDIR/usr/share/mime/packages" \
|
||||
"$APPDIR/usr/share/icons/hicolor/scalable/apps" \
|
||||
"$APPDIR/usr/share/icons/hicolor/scalable/mimetypes"
|
||||
|
||||
cp "$BUILD/profiler/tracy-profiler" "$APPDIR/usr/bin/"
|
||||
strip "$APPDIR/usr/bin/tracy-profiler"
|
||||
patchelf --set-rpath '$ORIGIN/../lib' "$APPDIR/usr/bin/tracy-profiler"
|
||||
cp "$PREFIX/lib/libwayland-client.so.0" \
|
||||
"$PREFIX/lib/libwayland-cursor.so.0" \
|
||||
"$PREFIX/lib/libwayland-egl.so.1" \
|
||||
"$APPDIR/usr/lib/"
|
||||
cp "$ROOT/extra/desktop/tracy.desktop" "$APPDIR/usr/share/applications/"
|
||||
cp "$ROOT/extra/desktop/application-tracy.xml" "$APPDIR/usr/share/mime/packages/"
|
||||
cp "$ROOT/icon/icon.svg" "$APPDIR/usr/share/icons/hicolor/scalable/apps/tracy.svg"
|
||||
cp "$ROOT/icon/application-tracy.svg" "$APPDIR/usr/share/icons/hicolor/scalable/mimetypes/"
|
||||
ln -s usr/share/applications/tracy.desktop "$APPDIR/tracy.desktop"
|
||||
ln -s usr/share/icons/hicolor/scalable/apps/tracy.svg "$APPDIR/tracy.svg"
|
||||
ln -s tracy.svg "$APPDIR/.DirIcon"
|
||||
ln -s usr/bin/tracy-profiler "$APPDIR/AppRun"
|
||||
|
||||
ldd "$APPDIR/usr/bin/tracy-profiler"
|
||||
|
||||
if [ ! -x "$BUILD/appimagetool" ]; then
|
||||
curl -Lf -o "$BUILD/appimagetool" "$APPIMAGETOOL_URL"
|
||||
chmod +x "$BUILD/appimagetool"
|
||||
fi
|
||||
ARCH=x86_64 "$BUILD/appimagetool" --appimage-extract-and-run \
|
||||
"$APPDIR" "$BUILD/tracy-profiler-x86_64.AppImage"
|
||||
30
profiler/macos/Info.plist.in
Normal file
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>NSHighResolutionCapable</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -26,6 +26,9 @@ public:
|
||||
|
||||
float GetDpiScale();
|
||||
|
||||
size_t HandleType();
|
||||
void* Handle();
|
||||
|
||||
private:
|
||||
WindowPosition m_winPos;
|
||||
int m_w, m_h;
|
||||
|
||||
@@ -388,3 +388,13 @@ float Backend::GetDpiScale()
|
||||
{
|
||||
return EM_ASM_DOUBLE( { return window.devicePixelRatio; } );
|
||||
}
|
||||
|
||||
size_t Backend::HandleType()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void* Backend::Handle()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||