Compare commits
660 Commits
slomp/seq-
...
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 | ||
|
|
ec1d5bd3d7 | ||
|
|
69af195c98 | ||
|
|
60699c4a92 | ||
|
|
cc45cf6046 | ||
|
|
62560a6429 | ||
|
|
f7b4e177ff | ||
|
|
084daf0516 | ||
|
|
a98956f2d9 | ||
|
|
ac6f0f88fa | ||
|
|
33fccb3530 | ||
|
|
45576f6972 | ||
|
|
17e13bc2e0 | ||
|
|
ee0c73bf25 | ||
|
|
343567a3f2 | ||
|
|
20b3535623 | ||
|
|
5298316480 | ||
|
|
83719fb29b | ||
|
|
f7d789eddb | ||
|
|
3816b2485e | ||
|
|
f8aa88d522 | ||
|
|
b5ae187f76 | ||
|
|
3f203806e2 | ||
|
|
15c6b49de2 | ||
|
|
a153f3a562 | ||
|
|
c2998310cf | ||
|
|
a43b74ed8f | ||
|
|
d3047f8069 | ||
|
|
3804b2580a | ||
|
|
329ac6c9f1 | ||
|
|
a091bb4ad2 | ||
|
|
86b5f43959 | ||
|
|
39dc688340 | ||
|
|
832234838b | ||
|
|
daba5acfbc | ||
|
|
07bfe3465e | ||
|
|
0544440a34 | ||
|
|
f287508772 | ||
|
|
f622b97436 | ||
|
|
dfded9d55d | ||
|
|
a2555fbb33 | ||
|
|
7180ea381f | ||
|
|
0c74658dd3 | ||
|
|
debda1df55 | ||
|
|
d98608b022 | ||
|
|
eb88c6eba0 | ||
|
|
e83429c926 | ||
|
|
cc091a99a2 | ||
|
|
1b207d3e2a | ||
|
|
f89709e99e | ||
|
|
a4c5f15312 | ||
|
|
3455fd9f82 | ||
|
|
cfc046abcd | ||
|
|
9ab39d8af3 | ||
|
|
bfab6d03f4 | ||
|
|
0d848c3042 | ||
|
|
54270d3fd5 | ||
|
|
1341f98c61 | ||
|
|
6fc279eef4 | ||
|
|
66e4f5cef7 | ||
|
|
7637971e9e | ||
|
|
4e3cffc4ba | ||
|
|
28d3a91980 | ||
|
|
3956616fc2 | ||
|
|
0fbb2eaaa4 | ||
|
|
b27dab4584 | ||
|
|
75bee5370f | ||
|
|
e7499458e9 | ||
|
|
d34c45fa5a | ||
|
|
8fe5a511c9 | ||
|
|
afdd2e2f81 | ||
|
|
3c1b1b2f80 | ||
|
|
992134f85e | ||
|
|
37bc986584 | ||
|
|
feb4e7c989 | ||
|
|
4a8fe6f56e | ||
|
|
a960a25285 | ||
|
|
958cb8d7f8 | ||
|
|
59f17794a5 | ||
|
|
3b2c7dbacb | ||
|
|
56ed480ed2 | ||
|
|
0572c86551 | ||
|
|
6499e3383b | ||
|
|
8278ace0c1 | ||
|
|
5981eca141 | ||
|
|
1b2856b885 | ||
|
|
118f18cf4b | ||
|
|
bfbc1d3bee | ||
|
|
831779508f | ||
|
|
286309af3f | ||
|
|
3db70a2237 | ||
|
|
da952f3f38 | ||
|
|
efba4685ef | ||
|
|
598984c45d | ||
|
|
860011c604 | ||
|
|
0cdcbfc75d | ||
|
|
e5d4be95df | ||
|
|
7b3863d93d | ||
|
|
de2a18d964 | ||
|
|
9588912aa9 | ||
|
|
7ee4380f64 | ||
|
|
01e639db97 | ||
|
|
030e699eb5 | ||
|
|
16cdf3d645 | ||
|
|
2f143491eb | ||
|
|
796050ac1e | ||
|
|
31dbfef97d | ||
|
|
19519bbeb0 | ||
|
|
fc4f52e61d | ||
|
|
e2ac8f7973 | ||
|
|
e5aa8eba51 | ||
|
|
7437c41514 | ||
|
|
f441a5070b | ||
|
|
00b6abd67b | ||
|
|
e4e3d75eb8 | ||
|
|
fc5318dcad | ||
|
|
661c664b75 | ||
|
|
6dbebca666 | ||
|
|
73d78ad517 | ||
|
|
e5371d7987 | ||
|
|
9806f35714 | ||
|
|
d40289d594 | ||
|
|
86fbe529ed | ||
|
|
7cb98245ce | ||
|
|
55d5436fb9 | ||
|
|
2b11785b05 | ||
|
|
ebd3d9c3e6 | ||
|
|
bc8d8f5302 | ||
|
|
b049746853 |
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
|
||||
9
.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
|
||||
|
||||
@@ -20,7 +27,7 @@ jobs:
|
||||
- name: Setup emscripten
|
||||
uses: emscripten-core/setup-emsdk@v16
|
||||
with:
|
||||
version: 4.0.10
|
||||
version: 5.0.7
|
||||
- name: Trust git repo
|
||||
run: git config --global --add safe.directory '*'
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
29
.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,13 +30,17 @@ 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 --build profiler/build --parallel --config Release
|
||||
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: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
@@ -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
|
||||
|
||||
18
.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,16 +30,14 @@ 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 }}
|
||||
cmake --build profiler/build --parallel --config Release
|
||||
cmake --build profiler/build --parallel 2 --config Release
|
||||
- name: Build update
|
||||
run: |
|
||||
cmake -B update/build -S update -DCMAKE_BUILD_TYPE=Release -DGIT_REV=${{ github.sha }}
|
||||
@@ -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
|
||||
2
.vscode/settings.json
vendored
@@ -6,7 +6,7 @@
|
||||
"${workspaceFolder}/import",
|
||||
"${workspaceFolder}/merge",
|
||||
"${workspaceFolder}/update",
|
||||
"${workspaceFolder}/test",
|
||||
"${workspaceFolder}/tests/tracy",
|
||||
"${workspaceFolder}",
|
||||
],
|
||||
"cmake.buildDirectory": "${sourceDirectory}/build",
|
||||
|
||||
@@ -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,8 @@ 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
|
||||
set_option(TRACY_VERBOSE "[advanced] Verbose output from the profiler" OFF TracyClient)
|
||||
@@ -151,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()
|
||||
@@ -181,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
|
||||
@@ -212,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)
|
||||
|
||||
@@ -273,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)
|
||||
|
||||
|
||||
185
NEWS
@@ -2,12 +2,69 @@ 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.
|
||||
- External frames are now omitted in the single-line call stack list visible
|
||||
in messages list, or in memory allocation info window.
|
||||
- External frames are now hidden by default in various contexts where they
|
||||
@@ -15,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.
|
||||
@@ -29,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
|
||||
@@ -71,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.
|
||||
@@ -101,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
|
||||
@@ -112,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.
|
||||
@@ -141,14 +240,20 @@ 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.
|
||||
- Links to chapters are now properly working.
|
||||
- The "bclogo" blocks are now correctly processed.
|
||||
- The "bclogo" blocks are now correctly processed and displayed as proper
|
||||
admonitions.
|
||||
- The font awesome icons now show as in the rest of the UI.
|
||||
- Footnotes are now rendered as proper footnotes.
|
||||
- Tables are now rendered as intended.
|
||||
- LaTeX math is now converted to readable form.
|
||||
- Added a button to download the full PDF manual to the user manual window.
|
||||
- Call stack window will now show the thread viewed call stack originates
|
||||
from (if possible).
|
||||
- "Visible threads" checkboxes in messages, flame graph and wait stacks
|
||||
@@ -171,7 +276,75 @@ 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
|
||||
addr2line-compatible tools via -a and -A command line parameters.
|
||||
Additionally, it is now possible to reset all call stack frame symbols to
|
||||
unresolved with the -R parameter.
|
||||
- Periodic recalibration of the clock drift in OpenGL contexts can be enabled
|
||||
with the TRACY_OPENGL_AUTO_CALIBRATION compilation define. Note that this
|
||||
requires a full CPU/GPU sync on each calibration event. These events will
|
||||
not fire more often than once every second.
|
||||
- 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)
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
### A real time, nanosecond resolution, remote telemetry, hybrid frame and sampling profiler for games and other applications.
|
||||
|
||||
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphic APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
|
||||
Tracy supports profiling CPU (Direct support is provided for C, C++, Lua, Python and Fortran integration. At the same time, third-party bindings to many other languages exist on the internet, such as [Rust](https://github.com/nagisa/rust_tracy_client), [Zig](https://github.com/tealsnow/zig-tracy), [C#](https://github.com/clibequilibrium/Tracy-CSharp), [OCaml](https://github.com/imandra-ai/ocaml-tracy), [Odin](https://github.com/oskarnp/odin-tracy), etc.), GPU (All major graphics/compute APIs: OpenGL, Vulkan, Direct3D 11/12, Metal, OpenCL, CUDA, WebGPU.), memory allocations, locks, context switches, automatically attribute screenshots to captured frames, and much more.
|
||||
|
||||
- [Documentation](https://github.com/wolfpld/tracy/releases/latest/download/tracy.pdf) for usage and build process instructions
|
||||
- [Releases](https://github.com/wolfpld/tracy/releases) containing the documentation (`tracy.pdf`) and compiled Windows x64 binaries (`Tracy-<version>.7z`) as assets
|
||||
|
||||
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,93 +116,226 @@ CPMAddPackage(
|
||||
EXCLUDE_FROM_ALL TRUE
|
||||
)
|
||||
|
||||
# md4c
|
||||
if(VENDOR_GUI)
|
||||
|
||||
CPMAddPackage(
|
||||
NAME md4c
|
||||
GITHUB_REPOSITORY mity/md4c
|
||||
GIT_TAG 755ce49acdc7cd682d4502b4796db5ed6a1230fb
|
||||
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_;
|
||||
};
|
||||
|
||||
}
|
||||
83
examples/opengl/triangle/CMakeLists.txt
Normal file
@@ -0,0 +1,83 @@
|
||||
# CMakeLists.txt — OpenGL spinning triangle demo
|
||||
#
|
||||
# macOS:
|
||||
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
|
||||
# cmake --build build/ninja
|
||||
#
|
||||
# Linux (requires libsdl3-dev libgl1-mesa-dev):
|
||||
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
|
||||
# cmake --build build/ninja
|
||||
#
|
||||
# Windows:
|
||||
# cmake -G Ninja -DCMAKE_BUILD_TYPE=RelWithDebInfo -B build/ninja .
|
||||
# cmake --build build/ninja
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(gl_spinning_triangle LANGUAGES C CXX)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tracy root — defaults to three directories above this CMakeLists.txt.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
|
||||
option(TRACY_ENABLE "Enable Tracy profiling" ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(SDL3 REQUIRED)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# GL extension loader — GLEW (Windows + Linux, fetched automatically)
|
||||
# ---------------------------------------------------------------------------
|
||||
if(NOT APPLE)
|
||||
include(FetchContent)
|
||||
set(glew-cmake_BUILD_SHARED OFF CACHE BOOL "" FORCE)
|
||||
set(ONLY_LIBS ON CACHE BOOL "" FORCE)
|
||||
FetchContent_Declare(glew
|
||||
GIT_REPOSITORY https://github.com/Perlmint/glew-cmake.git
|
||||
GIT_TAG master # pin to a specific commit for reproducible builds
|
||||
GIT_SHALLOW TRUE
|
||||
)
|
||||
FetchContent_MakeAvailable(glew)
|
||||
endif()
|
||||
|
||||
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
|
||||
|
||||
if(APPLE)
|
||||
set(PLATFORM_LIBS SDL3::SDL3 "-framework OpenGL")
|
||||
elseif(WIN32)
|
||||
set(PLATFORM_LIBS SDL3::SDL3 opengl32 libglew_static)
|
||||
else()
|
||||
set(PLATFORM_LIBS SDL3::SDL3 GL libglew_static)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Target
|
||||
# ---------------------------------------------------------------------------
|
||||
add_executable(gl_spinning_triangle
|
||||
spinning_triangle.cpp
|
||||
"${TRACY_DIR}/public/TracyClient.cpp"
|
||||
${PLATFORM_SOURCES}
|
||||
)
|
||||
|
||||
# Suppress upstream warnings from TracyClient.cpp
|
||||
if(MSVC)
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "/w"
|
||||
)
|
||||
else()
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "-w"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_features(gl_spinning_triangle PRIVATE cxx_std_17)
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
target_compile_definitions(gl_spinning_triangle PRIVATE TRACY_ENABLE)
|
||||
endif()
|
||||
|
||||
target_include_directories(gl_spinning_triangle PRIVATE
|
||||
"${TRACY_DIR}/public"
|
||||
)
|
||||
target_link_libraries(gl_spinning_triangle PRIVATE ${PLATFORM_LIBS})
|
||||
37
examples/opengl/triangle/platform/platform.h
Normal file
@@ -0,0 +1,37 @@
|
||||
// platform.h — interface between platform-agnostic code and platform backends
|
||||
//
|
||||
// Each platform_*.mm / platform_*.cpp file implements these four functions.
|
||||
// Exactly one backend must be linked into the final binary.
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <OpenGL/gl3.h>
|
||||
#else
|
||||
# include <GL/glew.h>
|
||||
#endif
|
||||
|
||||
// Initialize the windowing system, create a window, and make an OpenGL 3.3
|
||||
// Core Profile context current on the calling thread.
|
||||
// Returns true on success.
|
||||
bool platformInit(int width, int height, const char* title);
|
||||
|
||||
// Load OpenGL function pointers (no-op on macOS where the framework exports them directly).
|
||||
// Must be called after platformInit() while the GL context is current.
|
||||
// Returns true on success.
|
||||
bool platformInitGL();
|
||||
|
||||
// Elapsed wall-clock time in seconds since platformInit().
|
||||
double platformGetTime();
|
||||
|
||||
// Swap front and back buffers (present the rendered frame).
|
||||
void platformSwapBuffers();
|
||||
|
||||
// Pixel scaling factor relative to the logical window size (1.0 on non-HiDPI displays).
|
||||
// Must be called after platformInit().
|
||||
void platformGetPixelDensityScale(float* x, float* y);
|
||||
|
||||
// Enter the platform event/render loop.
|
||||
// Calls render() each frame at ~60 fps.
|
||||
// Calls shutdown() exactly once before returning.
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)());
|
||||
85
examples/opengl/triangle/platform/platform_sdl3.cpp
Normal file
@@ -0,0 +1,85 @@
|
||||
// platform_sdl3.cpp — SDL3 windowing backend (cross-platform)
|
||||
#include "platform.h" // GL headers first (gl3.h / glew.h) so SDL sees guards set
|
||||
|
||||
#define SDL_MAIN_HANDLED // we don't want SDL_main
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
static SDL_Window* sWin = nullptr;
|
||||
static SDL_GLContext sCtx = nullptr;
|
||||
static std::chrono::steady_clock::time_point sStartTime;
|
||||
|
||||
bool platformInit(int width, int height, const char* title) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
|
||||
sWin = SDL_CreateWindow(title, width, height, SDL_WINDOW_OPENGL);
|
||||
if (!sWin) {
|
||||
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
sCtx = SDL_GL_CreateContext(sWin);
|
||||
if (!sCtx) {
|
||||
fprintf(stderr, "ERROR: SDL_GL_CreateContext failed: %s\n", SDL_GetError());
|
||||
SDL_DestroyWindow(sWin);
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_GL_SetSwapInterval(1);
|
||||
sStartTime = std::chrono::steady_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool platformInitGL() {
|
||||
#ifndef __APPLE__
|
||||
glewExperimental = GL_TRUE;
|
||||
if (glewInit() != GLEW_OK) {
|
||||
fprintf(stderr, "Failed to initialize GLEW\n");
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
double platformGetTime() {
|
||||
return std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - sStartTime).count();
|
||||
}
|
||||
|
||||
void platformSwapBuffers() { SDL_GL_SwapWindow(sWin); }
|
||||
|
||||
void platformGetPixelDensityScale(float* x, float* y) {
|
||||
int pw, ph, ww, wh;
|
||||
SDL_GetWindowSizeInPixels(sWin, &pw, &ph);
|
||||
SDL_GetWindowSize(sWin, &ww, &wh);
|
||||
*x = (ww > 0) ? (float)pw / (float)ww : 1.0f;
|
||||
*y = (wh > 0) ? (float)ph / (float)wh : 1.0f;
|
||||
}
|
||||
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)()) {
|
||||
bool running = true;
|
||||
while (running) {
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (e.type == SDL_EVENT_QUIT) running = false;
|
||||
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
|
||||
}
|
||||
if (running) render();
|
||||
}
|
||||
shutdown();
|
||||
SDL_GL_DestroyContext(sCtx);
|
||||
SDL_DestroyWindow(sWin);
|
||||
SDL_Quit();
|
||||
}
|
||||
145
examples/opengl/triangle/spinning_triangle.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
// spinning_triangle.cpp — OpenGL spinning triangle demo with Tracy GPU profiling.
|
||||
|
||||
#ifdef __APPLE__
|
||||
// NOTE: OpenGL is only available on MacOS (no iOS support)
|
||||
// Including and using anything related to OpenGL on Apple (like <OpenGL/gl3.h>)
|
||||
// will emit deprecation warnings, unless GL_SILENCE_DEPRECATION is defined
|
||||
#define GL_SILENCE_DEPRECATION
|
||||
// NOTE: TracyOpenGL.hpp will not work as expected even on Apple devices that
|
||||
// support OpenGL, because the OpenGL drivers do not implement ARB_timer_query
|
||||
// properly (querying GL_TIMESTAMP always resolves to 0). TracyOpenGL.hpp will
|
||||
// emit a compiler warning, and a Tracy message to the trace/profiler, but the
|
||||
// program will still run.
|
||||
#endif
|
||||
|
||||
#include "platform/platform.h" // also includes OpenGL headers
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
|
||||
// NOTE: opt-in toggle for periodic recalibrations during Collect()
|
||||
#define TRACY_OPENGL_AUTO_CALIBRATION
|
||||
#include <tracy/TracyOpenGL.hpp>
|
||||
|
||||
static const int kWidth = 800;
|
||||
static const int kHeight = 600;
|
||||
|
||||
static GLuint gProgram = 0;
|
||||
static GLuint gVao = 0;
|
||||
static GLint gAngleLoc = -1;
|
||||
|
||||
// Vertex colors and positions are baked in; rotation is driven by a uniform.
|
||||
static const char* kVertSrc = R"(
|
||||
#version 150 core
|
||||
uniform float uAngle;
|
||||
const vec2 kPos[3] = vec2[3](
|
||||
vec2( 0.0, 0.5 ),
|
||||
vec2(-0.433, -0.25 ),
|
||||
vec2( 0.433, -0.25 )
|
||||
);
|
||||
const vec3 kCol[3] = vec3[3](
|
||||
vec3(1.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
vec3(0.0, 0.0, 1.0)
|
||||
);
|
||||
out vec3 vColor;
|
||||
void main() {
|
||||
float c = cos(uAngle);
|
||||
float s = sin(uAngle);
|
||||
vec2 p = kPos[gl_VertexID];
|
||||
gl_Position = vec4(p.x*c - p.y*s, p.x*s + p.y*c, 0.0, 1.0);
|
||||
vColor = kCol[gl_VertexID];
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* kFragSrc = R"(
|
||||
#version 150 core
|
||||
in vec3 vColor;
|
||||
out vec4 fragColor;
|
||||
void main() { fragColor = vec4(vColor, 1.0); }
|
||||
)";
|
||||
|
||||
static GLuint compileShader(GLenum type, const char* src) {
|
||||
GLuint s = glCreateShader(type);
|
||||
glShaderSource(s, 1, &src, nullptr);
|
||||
glCompileShader(s);
|
||||
GLint ok = 0;
|
||||
glGetShaderiv(s, GL_COMPILE_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[512];
|
||||
glGetShaderInfoLog(s, sizeof(log), nullptr, log);
|
||||
fprintf(stderr, "Shader compile error: %s\n", log);
|
||||
glDeleteShader(s);
|
||||
return 0;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
static int initGL() {
|
||||
if (!platformInitGL()) return 1;
|
||||
|
||||
TracyGpuContext;
|
||||
TracyGpuContextName("OpenGL", 6);
|
||||
|
||||
GLuint vert = compileShader(GL_VERTEX_SHADER, kVertSrc);
|
||||
GLuint frag = compileShader(GL_FRAGMENT_SHADER, kFragSrc);
|
||||
if (!vert || !frag) return 1;
|
||||
|
||||
gProgram = glCreateProgram();
|
||||
glAttachShader(gProgram, vert);
|
||||
glAttachShader(gProgram, frag);
|
||||
glLinkProgram(gProgram);
|
||||
glDeleteShader(vert);
|
||||
glDeleteShader(frag);
|
||||
|
||||
GLint ok = 0;
|
||||
glGetProgramiv(gProgram, GL_LINK_STATUS, &ok);
|
||||
if (!ok) {
|
||||
char log[512];
|
||||
glGetProgramInfoLog(gProgram, sizeof(log), nullptr, log);
|
||||
fprintf(stderr, "Program link error: %s\n", log);
|
||||
return 1;
|
||||
}
|
||||
|
||||
gAngleLoc = glGetUniformLocation(gProgram, "uAngle");
|
||||
|
||||
// Core profile requires a bound VAO even with no vertex attributes.
|
||||
glGenVertexArrays(1, &gVao);
|
||||
glBindVertexArray(gVao);
|
||||
|
||||
glClearColor(0.05f, 0.05f, 0.08f, 1.0f);
|
||||
float scaleX, scaleY;
|
||||
platformGetPixelDensityScale(&scaleX, &scaleY);
|
||||
glViewport(0, 0, (int)(kWidth * scaleX), (int)(kHeight * scaleY));
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void renderFrame() {
|
||||
ZoneScoped;
|
||||
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
glUseProgram(gProgram);
|
||||
|
||||
{
|
||||
TracyGpuZone("triangle draw");
|
||||
glUniform1f(gAngleLoc, (float)platformGetTime());
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
}
|
||||
|
||||
platformSwapBuffers();
|
||||
TracyGpuCollect;
|
||||
}
|
||||
|
||||
static void shutdown() {
|
||||
fprintf(stderr, "application is shutting down...\n");
|
||||
glDeleteVertexArrays(1, &gVao);
|
||||
glDeleteProgram(gProgram);
|
||||
}
|
||||
|
||||
int main() {
|
||||
if (!platformInit(kWidth, kHeight, "OpenGL Spinning Triangle"))
|
||||
return 1;
|
||||
if (initGL() != 0)
|
||||
return 2;
|
||||
platformRunLoop(renderFrame, shutdown);
|
||||
return 0;
|
||||
}
|
||||
157
examples/webgpu/triangle/CMakeLists.txt
Normal file
@@ -0,0 +1,157 @@
|
||||
# CMakeLists.txt — WebGPU spinning triangle demo
|
||||
#
|
||||
# macOS:
|
||||
# clang++ -std=c++17 -ObjC++ spinning_triangle.cpp platform/platform_macos.mm \
|
||||
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
# -Wl,-rpath,@executable_path \
|
||||
# -framework Cocoa -framework Metal -framework QuartzCore \
|
||||
# -framework Foundation -framework IOKit -framework IOSurface \
|
||||
# -o spinning_triangle
|
||||
#
|
||||
# Windows (MSVC):
|
||||
# cl /std:c++17 spinning_triangle.cpp platform/platform_windows.cpp \
|
||||
# /I\path\to\wgpu\include \path\to\wgpu\lib\wgpu_native.lib \
|
||||
# user32.lib gdi32.lib /Fe:spinning_triangle.exe
|
||||
#
|
||||
# Linux (requires libsdl3-dev):
|
||||
# g++ -std=c++17 spinning_triangle.cpp platform/platform_wayland.cpp \
|
||||
# xdg-shell-protocol.c \
|
||||
# -I/path/to/wgpu/include -L/path/to/wgpu/lib -lwgpu_native \
|
||||
# -lwayland-client -o spinning_triangle
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(spinning_triangle LANGUAGES C CXX)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# WebGPU backend — set WGPU_PATH to your wgpu-native or Dawn installation.
|
||||
# The library name differs between backends:
|
||||
# wgpu-native → wgpu_native
|
||||
# Dawn → webgpu_dawn
|
||||
# ---------------------------------------------------------------------------
|
||||
set(WGPU_PATH "" CACHE PATH "Root of the WebGPU native installation (contains include/ and lib/)")
|
||||
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
|
||||
|
||||
if(NOT WGPU_PATH)
|
||||
message(FATAL_ERROR "Set WGPU_PATH to the root of your WebGPU native installation.")
|
||||
endif()
|
||||
|
||||
# When WGPU_PATH changes, discard any previously auto-detected WGPU_LIB so
|
||||
# detection re-runs against the new path.
|
||||
if(NOT "${WGPU_PATH}" STREQUAL "${_WGPU_PATH_LAST}")
|
||||
unset(WGPU_LIB CACHE)
|
||||
set(WGPU_LIB "" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty")
|
||||
endif()
|
||||
set(_WGPU_PATH_LAST "${WGPU_PATH}" CACHE INTERNAL "")
|
||||
|
||||
if(NOT WGPU_LIB)
|
||||
unset(_WGPU_NATIVE_LIB CACHE)
|
||||
unset(_WEBGPU_DAWN_LIB CACHE)
|
||||
find_library(_WGPU_NATIVE_LIB NAMES wgpu_native wgpu_native.dll PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
|
||||
find_library(_WEBGPU_DAWN_LIB NAMES webgpu_dawn PATHS "${WGPU_PATH}/lib" NO_DEFAULT_PATH)
|
||||
if(_WGPU_NATIVE_LIB)
|
||||
set(WGPU_LIB "wgpu_native" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
|
||||
elseif(_WEBGPU_DAWN_LIB)
|
||||
set(WGPU_LIB "webgpu_dawn" CACHE STRING "WebGPU library name (wgpu_native or webgpu_dawn); auto-detected if empty" FORCE)
|
||||
else()
|
||||
message(FATAL_ERROR "Could not detect a WebGPU library in ${WGPU_PATH}/lib. Set WGPU_LIB explicitly (wgpu_native or webgpu_dawn).")
|
||||
endif()
|
||||
message(STATUS "WebGPU library auto-detected: ${WGPU_LIB}")
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tracy root — defaults to two directories above this CMakeLists.txt.
|
||||
# ---------------------------------------------------------------------------
|
||||
set(TRACY_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../..")
|
||||
option(TRACY_ENABLE "Enable Tracy profiling" ON)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# macOS quarantine — pre-built WebGPU binaries downloaded from the internet
|
||||
# carry a com.apple.quarantine extended attribute that prevents dyld from
|
||||
# loading them ("damaged or incomplete" / Gatekeeper block). Strip it once
|
||||
# at configure time so the linker and the runtime loader can both access the
|
||||
# library directory without further user intervention.
|
||||
# ---------------------------------------------------------------------------
|
||||
if(APPLE)
|
||||
execute_process(
|
||||
COMMAND xattr -dr com.apple.quarantine "${WGPU_PATH}/lib"
|
||||
)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Platform — SDL3 (cross-platform windowing, must be installed on the system)
|
||||
# ---------------------------------------------------------------------------
|
||||
find_package(SDL3 REQUIRED)
|
||||
|
||||
set(PLATFORM_SOURCES platform/platform_sdl3.cpp)
|
||||
|
||||
if(APPLE)
|
||||
set(PLATFORM_LIBS
|
||||
SDL3::SDL3
|
||||
"-framework Cocoa"
|
||||
"-framework Metal"
|
||||
"-framework QuartzCore"
|
||||
"-framework Foundation"
|
||||
"-framework IOKit"
|
||||
"-framework IOSurface"
|
||||
)
|
||||
elseif(WIN32)
|
||||
# wgpu-native (Rust stdlib) pull-ins: NtReadFile, GetUserProfileDirectoryW, ...
|
||||
set(WGPU_NATIVE_WIN32_LIBS ntdll userenv)
|
||||
# Dawn pull-ins: WKPDID_D3DDebugObjectName GUID, CompareObjectHandles, ...
|
||||
set(WEBGPU_DAWN_WIN32_LIBS dxguid onecore)
|
||||
set(PLATFORM_LIBS SDL3::SDL3 ${WGPU_NATIVE_WIN32_LIBS} ${WEBGPU_DAWN_WIN32_LIBS})
|
||||
else()
|
||||
set(PLATFORM_LIBS SDL3::SDL3)
|
||||
endif()
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Target
|
||||
# ---------------------------------------------------------------------------
|
||||
add_executable(spinning_triangle
|
||||
spinning_triangle.cpp
|
||||
"${TRACY_DIR}/public/TracyClient.cpp"
|
||||
${PLATFORM_SOURCES}
|
||||
)
|
||||
|
||||
# Treat TracyClient.cpp as third-party code — suppress all warnings so that
|
||||
# upstream changes don't pollute our build output.
|
||||
if(MSVC)
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "/w"
|
||||
)
|
||||
else()
|
||||
set_source_files_properties("${TRACY_DIR}/public/TracyClient.cpp"
|
||||
PROPERTIES COMPILE_FLAGS "-w"
|
||||
)
|
||||
endif()
|
||||
|
||||
target_compile_features(spinning_triangle PRIVATE cxx_std_17)
|
||||
|
||||
if(TRACY_ENABLE)
|
||||
target_compile_definitions(spinning_triangle PRIVATE TRACY_ENABLE)
|
||||
endif()
|
||||
|
||||
target_include_directories(spinning_triangle PRIVATE
|
||||
"${WGPU_PATH}/include"
|
||||
"${TRACY_DIR}/public"
|
||||
)
|
||||
|
||||
target_link_directories(spinning_triangle PRIVATE "${WGPU_PATH}/lib")
|
||||
|
||||
target_link_libraries(spinning_triangle PRIVATE
|
||||
${WGPU_LIB}
|
||||
${PLATFORM_LIBS}
|
||||
)
|
||||
|
||||
# Embed the rpath so the binary finds the WebGPU dylib/so next to itself.
|
||||
if(APPLE)
|
||||
set_target_properties(spinning_triangle PROPERTIES
|
||||
BUILD_RPATH "${WGPU_PATH}/lib"
|
||||
INSTALL_RPATH "@executable_path"
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(spinning_triangle PROPERTIES
|
||||
BUILD_RPATH "${WGPU_PATH}/lib"
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
)
|
||||
endif()
|
||||
23
examples/webgpu/triangle/platform/platform.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// platform.h — interface between platform-agnostic code and platform backends
|
||||
//
|
||||
// Each platform_*.mm / platform_*.cpp file implements these five functions.
|
||||
// Exactly one backend must be linked into the final binary.
|
||||
|
||||
#pragma once
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
// Initialize the windowing system and create a window of the given dimensions.
|
||||
// Returns true on success.
|
||||
bool platformInit(int width, int height, const char* title);
|
||||
|
||||
// Create a WebGPU surface backed by the platform window.
|
||||
// Must be called after wgpuCreateInstance() and platformInit().
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance);
|
||||
|
||||
// Elapsed wall-clock time in seconds since platformInit().
|
||||
double platformGetTime();
|
||||
|
||||
// Enter the platform event/render loop.
|
||||
// Calls render() each frame at ~60 fps.
|
||||
// Calls shutdown() exactly once before returning.
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)());
|
||||
95
examples/webgpu/triangle/platform/platform_sdl3.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// platform_sdl3.cpp — SDL3 windowing backend for the WebGPU example
|
||||
#include "platform.h" // webgpu/webgpu.h first
|
||||
|
||||
#define SDL_MAIN_HANDLED // we don't want SDL_main
|
||||
#include <SDL3/SDL.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
# include <SDL3/SDL_metal.h>
|
||||
#endif
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
|
||||
static SDL_Window* sWin = nullptr;
|
||||
static std::chrono::steady_clock::time_point sStartTime;
|
||||
#ifdef __APPLE__
|
||||
static SDL_MetalView sMetalView = nullptr;
|
||||
#endif
|
||||
|
||||
bool platformInit(int width, int height, const char* title) {
|
||||
if (!SDL_Init(SDL_INIT_VIDEO)) {
|
||||
fprintf(stderr, "ERROR: SDL_Init failed: %s\n", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
SDL_WindowFlags flags = 0;
|
||||
#ifdef __APPLE__
|
||||
flags |= SDL_WINDOW_METAL;
|
||||
#endif
|
||||
|
||||
sWin = SDL_CreateWindow(title, width, height, flags);
|
||||
if (!sWin) {
|
||||
fprintf(stderr, "ERROR: SDL_CreateWindow failed: %s\n", SDL_GetError());
|
||||
SDL_Quit();
|
||||
return false;
|
||||
}
|
||||
SDL_SetWindowPosition(sWin, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
|
||||
|
||||
sStartTime = std::chrono::steady_clock::now();
|
||||
return true;
|
||||
}
|
||||
|
||||
WGPUSurface platformCreateSurface(WGPUInstance instance) {
|
||||
WGPUSurfaceDescriptor desc = {};
|
||||
SDL_PropertiesID props = SDL_GetWindowProperties(sWin);
|
||||
|
||||
#if defined(__APPLE__)
|
||||
sMetalView = SDL_Metal_CreateView(sWin);
|
||||
if (!sMetalView) {
|
||||
fprintf(stderr, "ERROR: SDL_Metal_CreateView failed\n");
|
||||
return nullptr;
|
||||
}
|
||||
WGPUSurfaceSourceMetalLayer metalDesc = {};
|
||||
metalDesc.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
|
||||
metalDesc.layer = SDL_Metal_GetLayer(sMetalView);
|
||||
desc.nextInChain = &metalDesc.chain;
|
||||
#elif defined(_WIN32)
|
||||
WGPUSurfaceSourceWindowsHWND hwndDesc = {};
|
||||
hwndDesc.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
|
||||
hwndDesc.hinstance = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_INSTANCE_POINTER, nullptr);
|
||||
hwndDesc.hwnd = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
|
||||
desc.nextInChain = &hwndDesc.chain;
|
||||
#else // Linux / X11
|
||||
WGPUSurfaceSourceXlibWindow x11Desc = {};
|
||||
x11Desc.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
|
||||
x11Desc.display = SDL_GetPointerProperty(props, SDL_PROP_WINDOW_X11_DISPLAY_POINTER, nullptr);
|
||||
x11Desc.window = (uint32_t)SDL_GetNumberProperty(props, SDL_PROP_WINDOW_X11_WINDOW_NUMBER, 0);
|
||||
desc.nextInChain = &x11Desc.chain;
|
||||
#endif
|
||||
|
||||
return wgpuInstanceCreateSurface(instance, &desc);
|
||||
}
|
||||
|
||||
double platformGetTime() {
|
||||
return std::chrono::duration<double>(
|
||||
std::chrono::steady_clock::now() - sStartTime).count();
|
||||
}
|
||||
|
||||
void platformRunLoop(void (*render)(), void (*shutdown)()) {
|
||||
bool running = true;
|
||||
while (running) {
|
||||
SDL_Event e;
|
||||
while (SDL_PollEvent(&e)) {
|
||||
if (e.type == SDL_EVENT_QUIT) running = false;
|
||||
if (e.type == SDL_EVENT_KEY_DOWN && e.key.key == SDLK_ESCAPE) running = false;
|
||||
}
|
||||
if (running) render();
|
||||
}
|
||||
shutdown();
|
||||
#ifdef __APPLE__
|
||||
SDL_Metal_DestroyView(sMetalView);
|
||||
#endif
|
||||
SDL_DestroyWindow(sWin);
|
||||
SDL_Quit();
|
||||
}
|
||||
352
examples/webgpu/triangle/spinning_triangle.cpp
Normal file
@@ -0,0 +1,352 @@
|
||||
// spinning_triangle.cpp — platform-agnostic WebGPU spinning triangle demo.
|
||||
|
||||
#include "platform/platform.h"
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <webgpu/webgpu.h>
|
||||
|
||||
#include <tracy/Tracy.hpp>
|
||||
#include <tracy/TracyWebGPU.hpp>
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Globals
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const int kWidth = 800;
|
||||
static const int kHeight = 600;
|
||||
|
||||
static WGPUInstance gInstance = nullptr;
|
||||
static WGPUSurface gSurface = nullptr;
|
||||
static WGPUAdapter gAdapter = nullptr;
|
||||
static WGPUDevice gDevice = nullptr;
|
||||
static WGPUQueue gQueue = nullptr;
|
||||
static WGPURenderPipeline gPipeline = nullptr;
|
||||
static WGPUBuffer gUniformBuf = nullptr;
|
||||
static WGPUBindGroup gBindGroup = nullptr;
|
||||
|
||||
static TracyWebGPUCtx gTracyCtx = nullptr;
|
||||
|
||||
static WGPUTextureFormat gSurfaceFormat = WGPUTextureFormat_BGRA8Unorm;
|
||||
|
||||
// TODO: this can become platformError() instead
|
||||
int error(int code, const char* message) {
|
||||
fprintf(stderr, "ERROR: %s (code: %d)\n", message, code);
|
||||
return code;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WGSL shader — vertex colours baked in, rotation via a uniform float.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static const char* kShaderSource = R"(
|
||||
struct Uniforms {
|
||||
angle: f32,
|
||||
};
|
||||
@group(0) @binding(0) var<uniform> u: Uniforms;
|
||||
|
||||
struct VSOut {
|
||||
@builtin(position) pos: vec4f,
|
||||
@location(0) color: vec3f,
|
||||
};
|
||||
|
||||
@vertex
|
||||
fn vs_main(@builtin(vertex_index) vi: u32) -> VSOut {
|
||||
var positions = array<vec2f, 3>(
|
||||
vec2f( 0.0, 0.5),
|
||||
vec2f(-0.433, -0.25),
|
||||
vec2f( 0.433, -0.25),
|
||||
);
|
||||
var colors = array<vec3f, 3>(
|
||||
vec3f(1.0, 0.0, 0.0),
|
||||
vec3f(0.0, 1.0, 0.0),
|
||||
vec3f(0.0, 0.0, 1.0),
|
||||
);
|
||||
|
||||
let c = cos(u.angle);
|
||||
let s = sin(u.angle);
|
||||
let p = positions[vi];
|
||||
let rotated = vec2f(p.x * c - p.y * s, p.x * s + p.y * c);
|
||||
|
||||
var out: VSOut;
|
||||
out.pos = vec4f(rotated, 0.0, 1.0);
|
||||
out.color = colors[vi];
|
||||
return out;
|
||||
}
|
||||
|
||||
@fragment
|
||||
fn fs_main(@location(0) color: vec3f) -> @location(0) vec4f {
|
||||
return vec4f(color, 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Adapter / Device request callbacks (current wgpu-native API)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void onAdapterReady(WGPURequestAdapterStatus status,
|
||||
WGPUAdapter adapter,
|
||||
WGPUStringView message,
|
||||
void* userdata1, void* /*userdata2*/) {
|
||||
if (status == WGPURequestAdapterStatus_Success) {
|
||||
*(WGPUAdapter*)userdata1 = adapter;
|
||||
} else {
|
||||
fprintf(stderr, "Adapter request failed: %.*s\n",
|
||||
(int)message.length, message.data);
|
||||
}
|
||||
}
|
||||
|
||||
static void onDeviceReady(WGPURequestDeviceStatus status,
|
||||
WGPUDevice device,
|
||||
WGPUStringView message,
|
||||
void* userdata1, void* /*userdata2*/) {
|
||||
if (status == WGPURequestDeviceStatus_Success) {
|
||||
*(WGPUDevice*)userdata1 = device;
|
||||
} else {
|
||||
fprintf(stderr, "Device request failed: %.*s\n",
|
||||
(int)message.length, message.data);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// WebGPU init
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static int initWebGPU() {
|
||||
// Adapter
|
||||
WGPURequestAdapterOptions adapterOpts = {};
|
||||
adapterOpts.compatibleSurface = gSurface;
|
||||
|
||||
WGPURequestAdapterCallbackInfo adapterCB = {};
|
||||
adapterCB.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
adapterCB.callback = onAdapterReady;
|
||||
adapterCB.userdata1 = &gAdapter;
|
||||
wgpuInstanceRequestAdapter(gInstance, &adapterOpts, adapterCB);
|
||||
while (!gAdapter) { wgpuInstanceProcessEvents(gInstance); }
|
||||
if (!gAdapter) return error(11, "No adapter");
|
||||
|
||||
WGPUUncapturedErrorCallbackInfo errorCB = {};
|
||||
errorCB.callback = [](WGPUDevice const*, WGPUErrorType type,
|
||||
WGPUStringView message, void*, void*) {
|
||||
fprintf(stderr, "[WGPU ERROR] type=%d %.*s\n",
|
||||
(int)type, (int)message.length, message.data);
|
||||
};
|
||||
|
||||
WGPUDeviceDescriptor deviceDesc = {};
|
||||
deviceDesc.uncapturedErrorCallbackInfo = errorCB;
|
||||
|
||||
TracyWebGPUSetupDeviceDescriptor(deviceDesc);
|
||||
|
||||
WGPURequestDeviceCallbackInfo deviceCB = {};
|
||||
deviceCB.mode = WGPUCallbackMode_AllowProcessEvents;
|
||||
deviceCB.callback = onDeviceReady;
|
||||
deviceCB.userdata1 = &gDevice;
|
||||
wgpuAdapterRequestDevice(gAdapter, &deviceDesc, deviceCB);
|
||||
while (!gDevice) { wgpuInstanceProcessEvents(gInstance); }
|
||||
if (!gDevice) return error(12, "No device");
|
||||
|
||||
gQueue = wgpuDeviceGetQueue(gDevice);
|
||||
gTracyCtx = TracyWebGPUContext(gInstance, gDevice, gQueue);
|
||||
TracyWebGPUContextName(gTracyCtx, "WebGPU", 6);
|
||||
|
||||
// Configure surface
|
||||
WGPUSurfaceConfiguration config = {};
|
||||
config.device = gDevice;
|
||||
config.format = gSurfaceFormat;
|
||||
config.usage = WGPUTextureUsage_RenderAttachment;
|
||||
config.alphaMode = WGPUCompositeAlphaMode_Opaque;
|
||||
config.width = kWidth;
|
||||
config.height = kHeight;
|
||||
config.presentMode = WGPUPresentMode_Fifo;
|
||||
wgpuSurfaceConfigure(gSurface, &config);
|
||||
|
||||
// Shader module
|
||||
WGPUShaderSourceWGSL wgslSrc = {};
|
||||
wgslSrc.chain.sType = WGPUSType_ShaderSourceWGSL;
|
||||
wgslSrc.code = { kShaderSource, WGPU_STRLEN };
|
||||
|
||||
WGPUShaderModuleDescriptor smDesc = {};
|
||||
smDesc.nextInChain = (WGPUChainedStruct*)&wgslSrc;
|
||||
WGPUShaderModule shaderMod = wgpuDeviceCreateShaderModule(gDevice, &smDesc);
|
||||
|
||||
// Uniform buffer (one f32 for rotation angle)
|
||||
WGPUBufferDescriptor bufDesc = {};
|
||||
bufDesc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
|
||||
bufDesc.size = sizeof(float);
|
||||
gUniformBuf = wgpuDeviceCreateBuffer(gDevice, &bufDesc);
|
||||
|
||||
// Bind group layout + bind group
|
||||
WGPUBindGroupLayoutEntry bglEntry = {};
|
||||
bglEntry.binding = 0;
|
||||
bglEntry.visibility = WGPUShaderStage_Vertex;
|
||||
bglEntry.buffer.type = WGPUBufferBindingType_Uniform;
|
||||
bglEntry.buffer.minBindingSize = sizeof(float);
|
||||
|
||||
WGPUBindGroupLayoutDescriptor bglDesc = {};
|
||||
bglDesc.entryCount = 1;
|
||||
bglDesc.entries = &bglEntry;
|
||||
WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(gDevice, &bglDesc);
|
||||
|
||||
WGPUBindGroupEntry bgEntry = {};
|
||||
bgEntry.binding = 0;
|
||||
bgEntry.buffer = gUniformBuf;
|
||||
bgEntry.size = sizeof(float);
|
||||
|
||||
WGPUBindGroupDescriptor bgDesc = {};
|
||||
bgDesc.layout = bgl;
|
||||
bgDesc.entryCount = 1;
|
||||
bgDesc.entries = &bgEntry;
|
||||
gBindGroup = wgpuDeviceCreateBindGroup(gDevice, &bgDesc);
|
||||
|
||||
// Pipeline layout
|
||||
WGPUPipelineLayoutDescriptor plDesc = {};
|
||||
plDesc.bindGroupLayoutCount = 1;
|
||||
plDesc.bindGroupLayouts = &bgl;
|
||||
WGPUPipelineLayout pipelineLayout = wgpuDeviceCreatePipelineLayout(gDevice, &plDesc);
|
||||
|
||||
// Render pipeline
|
||||
WGPUColorTargetState colorTarget = {};
|
||||
colorTarget.format = gSurfaceFormat;
|
||||
colorTarget.writeMask = WGPUColorWriteMask_All;
|
||||
|
||||
WGPUFragmentState fragState = {};
|
||||
fragState.module = shaderMod;
|
||||
fragState.entryPoint = { "fs_main", WGPU_STRLEN };
|
||||
fragState.targetCount = 1;
|
||||
fragState.targets = &colorTarget;
|
||||
|
||||
WGPURenderPipelineDescriptor rpDesc = {};
|
||||
rpDesc.layout = pipelineLayout;
|
||||
rpDesc.vertex.module = shaderMod;
|
||||
rpDesc.vertex.entryPoint = { "vs_main", WGPU_STRLEN };
|
||||
rpDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
|
||||
rpDesc.multisample.count = 1;
|
||||
rpDesc.multisample.mask = 0xFFFFFFFF;
|
||||
rpDesc.fragment = &fragState;
|
||||
|
||||
gPipeline = wgpuDeviceCreateRenderPipeline(gDevice, &rpDesc);
|
||||
|
||||
// Cleanup intermediates
|
||||
wgpuShaderModuleRelease(shaderMod);
|
||||
wgpuPipelineLayoutRelease(pipelineLayout);
|
||||
wgpuBindGroupLayoutRelease(bgl);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Frame rendering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Returns the surface texture for the current frame, or {.texture=nullptr} on
|
||||
// a skippable condition (timeout, occlusion) or an error.
|
||||
static WGPUSurfaceTexture getWindowSurface() {
|
||||
WGPUSurfaceTexture surfTex = {};
|
||||
wgpuSurfaceGetCurrentTexture(gSurface, &surfTex);
|
||||
if (surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal ||
|
||||
surfTex.status == WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal)
|
||||
return surfTex;
|
||||
|
||||
// Timeout and Occluded are normal OS events (window covered / on a different Space).
|
||||
bool silent = surfTex.status == WGPUSurfaceGetCurrentTextureStatus_Timeout;
|
||||
#ifdef WGPU_H_
|
||||
silent = silent || surfTex.status == (WGPUSurfaceGetCurrentTextureStatus)WGPUSurfaceGetCurrentTextureStatus_Occluded;
|
||||
#endif
|
||||
if (!silent)
|
||||
fprintf(stderr, "Failed to get surface texture (status %d)\n", surfTex.status);
|
||||
if (surfTex.texture) wgpuTextureRelease(surfTex.texture);
|
||||
surfTex.texture = nullptr;
|
||||
return surfTex;
|
||||
}
|
||||
|
||||
static void renderFrame() {
|
||||
ZoneScoped;
|
||||
|
||||
// Update rotation angle
|
||||
float angle = (float)platformGetTime();
|
||||
wgpuQueueWriteBuffer(gQueue, gUniformBuf, 0, &angle, sizeof(float));
|
||||
|
||||
WGPUSurfaceTexture surfTex = getWindowSurface();
|
||||
if (!surfTex.texture) return;
|
||||
|
||||
WGPUTextureView view = wgpuTextureCreateView(surfTex.texture, nullptr);
|
||||
|
||||
// Command encoder
|
||||
WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(gDevice, nullptr);
|
||||
|
||||
// Render pass
|
||||
WGPURenderPassColorAttachment colorAtt = {};
|
||||
colorAtt.view = view;
|
||||
colorAtt.loadOp = WGPULoadOp_Clear;
|
||||
colorAtt.storeOp = WGPUStoreOp_Store;
|
||||
colorAtt.clearValue = { 0.05, 0.05, 0.08, 1.0 };
|
||||
colorAtt.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
|
||||
|
||||
WGPURenderPassDescriptor passDesc = {};
|
||||
passDesc.colorAttachmentCount = 1;
|
||||
passDesc.colorAttachments = &colorAtt;
|
||||
|
||||
{
|
||||
ZoneScopedN("render-pass");
|
||||
TracyWebGPUNamedZone(gTracyCtx, tracyZone, encoder, passDesc, "triangle draw", true);
|
||||
WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &passDesc);
|
||||
wgpuRenderPassEncoderSetPipeline(pass, gPipeline);
|
||||
wgpuRenderPassEncoderSetBindGroup(pass, 0, gBindGroup, 0, nullptr);
|
||||
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
|
||||
wgpuRenderPassEncoderEnd(pass);
|
||||
wgpuRenderPassEncoderRelease(pass);
|
||||
}
|
||||
|
||||
// Submit
|
||||
WGPUCommandBuffer cmdBuf = wgpuCommandEncoderFinish(encoder, nullptr);
|
||||
wgpuQueueSubmit(gQueue, 1, &cmdBuf);
|
||||
|
||||
// Present
|
||||
wgpuSurfacePresent(gSurface);
|
||||
|
||||
// Process Events
|
||||
wgpuInstanceProcessEvents(gInstance);
|
||||
TracyWebGPUCollect(gTracyCtx);
|
||||
|
||||
// Cleanup
|
||||
wgpuCommandBufferRelease(cmdBuf);
|
||||
wgpuCommandEncoderRelease(encoder);
|
||||
wgpuTextureViewRelease(view);
|
||||
wgpuTextureRelease(surfTex.texture);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Shutdown
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
static void shutdown() {
|
||||
fprintf(stderr, "application is shutting down...\n");
|
||||
TracyWebGPUDestroy(gTracyCtx);
|
||||
if (gBindGroup) wgpuBindGroupRelease(gBindGroup);
|
||||
if (gUniformBuf) wgpuBufferRelease(gUniformBuf);
|
||||
if (gPipeline) wgpuRenderPipelineRelease(gPipeline);
|
||||
if (gQueue) wgpuQueueRelease(gQueue);
|
||||
if (gDevice) wgpuDeviceRelease(gDevice);
|
||||
if (gAdapter) wgpuAdapterRelease(gAdapter);
|
||||
if (gSurface) wgpuSurfaceRelease(gSurface);
|
||||
if (gInstance) wgpuInstanceRelease(gInstance);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// main
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
if (!platformInit(kWidth, kHeight, "WebGPU Spinning Triangle"))
|
||||
return 1;
|
||||
|
||||
gInstance = wgpuCreateInstance(nullptr);
|
||||
if (!gInstance) return error(2, "Failed to create WebGPU instance.");
|
||||
|
||||
gSurface = platformCreateSurface(gInstance);
|
||||
if (!gSurface) return error(3, "Failed to create surface.");
|
||||
|
||||
if (initWebGPU() != 0) return 4;
|
||||
|
||||
platformRunLoop(renderFrame, shutdown);
|
||||
return 0;
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -3,3 +3,151 @@ function Link(el)
|
||||
el.attributes['reference'] = nil
|
||||
return el
|
||||
end
|
||||
|
||||
-- Drop Div wrappers (e.g. table/titlepage containers), keeping their content.
|
||||
function Div(el)
|
||||
return el.content
|
||||
end
|
||||
|
||||
-- ---------------------------------------------------------------------------
|
||||
-- LaTeX math -> plain-text approximation.
|
||||
--
|
||||
-- The target Markdown renderer has no math support, so a raw "$\frac{1}{2}$"
|
||||
-- would show verbatim. We turn each math node into the closest Unicode/ASCII
|
||||
-- equivalent: fractions become "a/b", \times becomes "x", super/subscripts use
|
||||
-- Unicode digits, and the one multi-line display equation becomes a fenced
|
||||
-- code block (Markdown collapses plain newlines, a code block keeps them).
|
||||
-- ---------------------------------------------------------------------------
|
||||
|
||||
local sup = {['0']='⁰',['1']='¹',['2']='²',['3']='³',['4']='⁴',['5']='⁵',
|
||||
['6']='⁶',['7']='⁷',['8']='⁸',['9']='⁹',['+']='⁺',['-']='⁻',
|
||||
['=']='⁼',['(']='⁽',[')']='⁾'}
|
||||
local sub = {['0']='₀',['1']='₁',['2']='₂',['3']='₃',['4']='₄',['5']='₅',
|
||||
['6']='₆',['7']='₇',['8']='₈',['9']='₉',['+']='₊',['-']='₋',
|
||||
['=']='₌',['(']='₍',[')']='₎'}
|
||||
|
||||
-- Symbol replacements, applied as literal substitutions. Longer commands must
|
||||
-- precede those that are a prefix of them (e.g. \rightarrow before \right).
|
||||
local symbols = {
|
||||
{'\\leftrightarrow','↔'}, {'\\rightarrow','→'}, {'\\leftarrow','←'},
|
||||
{'\\Rightarrow','⇒'}, {'\\Leftarrow','⇐'}, {'\\to','→'}, {'\\mapsto','↦'},
|
||||
{'\\times','×'}, {'\\cdot','·'}, {'\\div','÷'}, {'\\ast','*'}, {'\\star','*'},
|
||||
{'\\leq','≤'}, {'\\geq','≥'}, {'\\neq','≠'}, {'\\approx','≈'}, {'\\equiv','≡'},
|
||||
{'\\ll','«'}, {'\\gg','»'}, {'\\le','≤'}, {'\\ge','≥'},
|
||||
{'\\ldots','…'}, {'\\cdots','…'}, {'\\dots','…'}, {'\\infty','∞'},
|
||||
{'\\pm','±'}, {'\\mp','∓'}, {'\\propto','∝'}, {'\\sum','Σ'}, {'\\prod','Π'},
|
||||
{'\\alpha','α'}, {'\\beta','β'}, {'\\gamma','γ'}, {'\\delta','δ'}, {'\\Delta','Δ'},
|
||||
{'\\mu','µ'}, {'\\sigma','σ'}, {'\\pi','π'}, {'\\lambda','λ'}, {'\\theta','θ'},
|
||||
{'\\left',''}, {'\\right',''},
|
||||
{'\\qquad',' '}, {'\\quad',' '}, {'\\,',' '}, {'\\;',' '}, {'\\:',' '},
|
||||
{'\\ ',' '}, {'\\!',''},
|
||||
{'\\%','%'}, {'\\#','#'}, {'\\&','&'}, {'\\_','_'}, {'\\{','{'}, {'\\}','}'},
|
||||
{'\\$','$'},
|
||||
}
|
||||
|
||||
-- Literal (non-pattern) string replacement; avoids Lua pattern magic in keys.
|
||||
local function lit_replace(s, a, b)
|
||||
local out, i = {}, 1
|
||||
while true do
|
||||
local p = s:find(a, i, true)
|
||||
if not p then out[#out + 1] = s:sub(i); break end
|
||||
out[#out + 1] = s:sub(i, p - 1)
|
||||
out[#out + 1] = b
|
||||
i = p + #a
|
||||
end
|
||||
return table.concat(out)
|
||||
end
|
||||
|
||||
-- Strip the outer braces of a "%b{}" capture.
|
||||
local function grp(b) return b:sub(2, #b - 1) end
|
||||
|
||||
-- Map a string to Unicode super/subscript, or nil if any char is unsupported.
|
||||
local function map_script(txt, map)
|
||||
local res = {}
|
||||
for i = 1, #txt do
|
||||
local c = txt:sub(i, i)
|
||||
if not map[c] then return nil end
|
||||
res[#res + 1] = map[c]
|
||||
end
|
||||
return table.concat(res)
|
||||
end
|
||||
|
||||
local function convert(s)
|
||||
-- Text/font wrappers: keep the content, recurse to handle nesting.
|
||||
for _, cmd in ipairs({'text', 'mathrm', 'mathit', 'mathbf', 'mathbb',
|
||||
'mathsf', 'mathtt', 'mathcal', 'operatorname',
|
||||
'textbf', 'textit', 'textrm'}) do
|
||||
s = s:gsub('\\' .. cmd .. '(%b{})', function(b) return convert(grp(b)) end)
|
||||
end
|
||||
-- Fractions -> "num/den" (spaced when either side has spaces).
|
||||
local function frac(a, b)
|
||||
local n, d = convert(grp(a)), convert(grp(b))
|
||||
local sep = (n:find(' ', 1, true) or d:find(' ', 1, true)) and ' / ' or '/'
|
||||
return n .. sep .. d
|
||||
end
|
||||
s = s:gsub('\\frac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\dfrac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\tfrac(%b{})(%b{})', frac)
|
||||
s = s:gsub('\\sfrac(%b{})(%b{})', frac)
|
||||
-- Roots.
|
||||
s = s:gsub('\\sqrt(%b{})', function(b) return '√(' .. convert(grp(b)) .. ')' end)
|
||||
-- Single-char scripts first, so the braced fallback (e.g. "_native") below
|
||||
-- is not re-scanned and mangled into Unicode subscripts.
|
||||
s = s:gsub('%^([%w])', function(c) return sup[c] or ('^' .. c) end)
|
||||
s = s:gsub('_([%w])', function(c) return sub[c] or ('_' .. c) end)
|
||||
-- Braced scripts: Unicode when the content is all digits/signs, else keep
|
||||
-- a readable "^(...)" / "_..." form.
|
||||
s = s:gsub('%^(%b{})', function(b)
|
||||
local inner = convert(grp(b))
|
||||
return map_script(inner, sup) or ('^(' .. inner .. ')')
|
||||
end)
|
||||
s = s:gsub('_(%b{})', function(b)
|
||||
local inner = convert(grp(b))
|
||||
return map_script(inner, sub) or ('_' .. inner)
|
||||
end)
|
||||
-- Remaining symbols.
|
||||
for _, pair in ipairs(symbols) do s = lit_replace(s, pair[1], pair[2]) end
|
||||
return s
|
||||
end
|
||||
|
||||
-- Convert a display equation, preserving its line structure for a code block.
|
||||
local function convert_display(s)
|
||||
s = convert(s)
|
||||
for _, env in ipairs({'cases', 'aligned', 'align', 'array', 'matrix',
|
||||
'gathered', 'split'}) do
|
||||
s = lit_replace(s, '\\begin{' .. env .. '}', '')
|
||||
s = lit_replace(s, '\\end{' .. env .. '}', '')
|
||||
end
|
||||
s = lit_replace(s, '\\\\', '\n') -- row break
|
||||
s = s:gsub('%s*&%s*', ' ') -- column separator -> spacing
|
||||
local lines = {}
|
||||
for line in (s .. '\n'):gmatch('(.-)\n') do
|
||||
line = line:gsub('^%s+', ''):gsub('%s+$', '')
|
||||
if line ~= '' then lines[#lines + 1] = line end
|
||||
end
|
||||
for i = 2, #lines do lines[i] = ' ' .. lines[i] end -- indent continuations
|
||||
return table.concat(lines, '\n')
|
||||
end
|
||||
|
||||
function Math(el)
|
||||
if el.mathtype == 'DisplayMath' then
|
||||
return el -- handled at block level by Para, to emit a code block
|
||||
end
|
||||
return pandoc.Str(convert(el.text))
|
||||
end
|
||||
|
||||
-- A paragraph that is solely a display equation becomes a fenced code block.
|
||||
function Para(el)
|
||||
local maths, only_math = {}, true
|
||||
for _, x in ipairs(el.content) do
|
||||
if x.t == 'Math' and x.mathtype == 'DisplayMath' then
|
||||
maths[#maths + 1] = x
|
||||
elseif x.t ~= 'Space' and x.t ~= 'SoftBreak' and x.t ~= 'LineBreak' then
|
||||
only_math = false
|
||||
end
|
||||
end
|
||||
if #maths == 0 or not only_math then return nil end
|
||||
local parts = {}
|
||||
for _, m in ipairs(maths) do parts[#parts + 1] = convert_display(m.text) end
|
||||
return pandoc.CodeBlock(table.concat(parts, '\n\n'))
|
||||
end
|
||||
|
||||
@@ -7,11 +7,20 @@ sed -i -e 's@\\ctrl@Ctrl@g' _tmp.tex
|
||||
sed -i -e 's@\\shift@Shift@g' _tmp.tex
|
||||
sed -i -e 's@\\Alt@Alt@g' _tmp.tex
|
||||
sed -i -e 's@\\del@Delete@g' _tmp.tex
|
||||
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome6.h _tmp.tex
|
||||
python3 fa-icons.py ../profiler/src/profiler/IconsFontAwesome7.h _tmp.tex
|
||||
sed -i -e 's@\\LMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\MMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\RMB{}~@@g' _tmp.tex
|
||||
sed -i -e 's@\\Scroll{}~@@g' _tmp.tex
|
||||
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
|
||||
@@ -26,7 +35,10 @@ sed -i -e 's@\\nameref{configurationfiles}@Configuration files@g' _tmp.tex
|
||||
awk -f bclogo2quote.awk _tmp.tex > _tmp_quoted.tex
|
||||
mv _tmp_quoted.tex _tmp.tex
|
||||
|
||||
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -s _tmp.tex -o tracy.md
|
||||
pandoc --wrap=none --reference-location=block --number-sections -L filter.lua -t 'markdown-simple_tables-multiline_tables-grid_tables+pipe_tables' -s _tmp.tex -o tracy.md
|
||||
|
||||
awk -f tablecaption.awk tracy.md > _tmp_caption.md
|
||||
mv _tmp_caption.md tracy.md
|
||||
|
||||
sed -i -e 's/^> \*\*IMPORTANT:\([^*]*\)\*\*/> [!IMPORTANT]\
|
||||
> **\1**/' tracy.md
|
||||
@@ -37,6 +49,6 @@ sed -i -e 's/^> \*\*CAUTION:\([^*]*\)\*\*/> [!CAUTION]\
|
||||
sed -i -e 's/^> \*\*NOTE:\([^*]*\)\*\*/> [!NOTE]\
|
||||
> **\1**/' tracy.md
|
||||
|
||||
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome6.h tracy.md
|
||||
python3 icon-explain.py ../profiler/src/profiler/IconsFontAwesome7.h tracy.md
|
||||
|
||||
rm -f _tmp.tex
|
||||
|
||||
16
manual/tablecaption.awk
Normal file
@@ -0,0 +1,16 @@
|
||||
# Pandoc emits table captions as a line beginning with ": ", which GitHub
|
||||
# renders literally instead of as a caption. Strip the marker and italicize
|
||||
# the caption instead. Captions may span several physical lines when they
|
||||
# contain a hard line break (a trailing backslash). Underscores are used for
|
||||
# the emphasis so captions that already contain "*...*" markup are left intact.
|
||||
!incap && /^: / {
|
||||
incap = 1
|
||||
$0 = "_" substr($0, 3)
|
||||
}
|
||||
incap && !/\\$/ {
|
||||
print $0 "_"
|
||||
incap = 0
|
||||
next
|
||||
}
|
||||
incap { print; next }
|
||||
{ print }
|
||||