Compare commits
776 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd2feb6873 | ||
|
|
e73c53fad1 | ||
|
|
63d23835ef | ||
|
|
0f60ccdde2 | ||
|
|
f9e748b721 | ||
|
|
1c7d106aac | ||
|
|
83b1b259bd | ||
|
|
95cd2e17ab | ||
|
|
77bdd09aa2 | ||
|
|
5311b562aa | ||
|
|
9c2773409a | ||
|
|
713e204a8a | ||
|
|
3c7a115afe | ||
|
|
ec13746e4d | ||
|
|
4e2464a713 | ||
|
|
2a87ddcce2 | ||
|
|
dff8f1f9f8 | ||
|
|
e7c77b90c4 | ||
|
|
6ae0b83442 | ||
|
|
35f7740b6c | ||
|
|
2af7e40ce6 | ||
|
|
1db10b8f4c | ||
|
|
155c8ba04c | ||
|
|
40cf9288e9 | ||
|
|
7bd3558c64 | ||
|
|
b700d657d7 | ||
|
|
fa4d6df4c5 | ||
|
|
4f678284e2 | ||
|
|
be06b21f7b | ||
|
|
76bd617b2d | ||
|
|
c0dc8f9d25 | ||
|
|
52882a93a5 | ||
|
|
9f8e17cb18 | ||
|
|
f829932ed1 | ||
|
|
d00972fce1 | ||
|
|
3096adb347 | ||
|
|
e69d715e3d | ||
|
|
41a333321a | ||
|
|
8a1cfcc8b7 | ||
|
|
639cf169b2 | ||
|
|
98fcd3ba5d | ||
|
|
064b7bfa5f | ||
|
|
b13fd9e65a | ||
|
|
af787e451b | ||
|
|
6e1639c7b0 | ||
|
|
6b7c8e469b | ||
|
|
c58e21cd76 | ||
|
|
f6cb128eac | ||
|
|
34a590e509 | ||
|
|
28aa697528 | ||
|
|
24c2930012 | ||
|
|
15a04fb704 | ||
|
|
d5dfe34c49 | ||
|
|
0b954a3305 | ||
|
|
fb13f053bb | ||
|
|
beb172e9fc | ||
|
|
c91710a82f | ||
|
|
00a1d106bd | ||
|
|
3bb63bc0b3 | ||
|
|
c6b0353c4d | ||
|
|
424540d9e8 | ||
|
|
f1b1238e2d | ||
|
|
3b1683a152 | ||
|
|
a76e11e7f0 | ||
|
|
a4f48efea4 | ||
|
|
977a3ab352 | ||
|
|
dfdf87d844 | ||
|
|
1b31755ba6 | ||
|
|
d217017c9e | ||
|
|
7c970132b5 | ||
|
|
fa8e35c49f | ||
|
|
dda90ce1c2 | ||
|
|
e6085ea35f | ||
|
|
10eca7f651 | ||
|
|
765e37aa35 | ||
|
|
f84cbf6429 | ||
|
|
8562b516c0 | ||
|
|
9ff3401133 | ||
|
|
b05795d704 | ||
|
|
8596a12772 | ||
|
|
db824b59c8 | ||
|
|
d67c7d9a82 | ||
|
|
4a5f6c03b0 | ||
|
|
d3a306b7a6 | ||
|
|
477979dd43 | ||
|
|
b478b61dcf | ||
|
|
00aa9f6a53 | ||
|
|
26e243d2d7 | ||
|
|
bd590895cf | ||
|
|
e978f05ed1 | ||
|
|
4a35df337b | ||
|
|
dcf345febe | ||
|
|
78d80d99a0 | ||
|
|
f0baf2abf2 | ||
|
|
2f8947583f | ||
|
|
e486c66850 | ||
|
|
626cc44d7a | ||
|
|
4105f38022 | ||
|
|
ce350e7ce0 | ||
|
|
b8df61c642 | ||
|
|
22ceaca2f4 | ||
|
|
ec11918323 | ||
|
|
e3d561fb84 | ||
|
|
7fd7d05838 | ||
|
|
272a87127a | ||
|
|
39ddce1ab5 | ||
|
|
3165152396 | ||
|
|
71c4779211 | ||
|
|
0a8013d71e | ||
|
|
7b1804dab4 | ||
|
|
d240143588 | ||
|
|
0e4c35c591 | ||
|
|
467d43570e | ||
|
|
3714f2e471 | ||
|
|
dda6147b0d | ||
|
|
71f1c0f9f9 | ||
|
|
cdf900f1e3 | ||
|
|
528b19a842 | ||
|
|
7b44187fd2 | ||
|
|
ad92865497 | ||
|
|
bfb5244cf8 | ||
|
|
bece52cd81 | ||
|
|
48fa3b7a0f | ||
|
|
c8bf47dcfb | ||
|
|
54e304fe2a | ||
|
|
19165cd859 | ||
|
|
7e2ce2f7f4 | ||
|
|
a7ecd9e19c | ||
|
|
f5e32935ca | ||
|
|
9bde28d7b1 | ||
|
|
5974e3ea33 | ||
|
|
1567824d2d | ||
|
|
1b82ccec22 | ||
|
|
534466754f | ||
|
|
7b18174eef | ||
|
|
87c49aa7be | ||
|
|
f2ade343e2 | ||
|
|
5d3a2be04f | ||
|
|
25565dffd5 | ||
|
|
1a5f2e290b | ||
|
|
db68fba4a6 | ||
|
|
7020d498c5 | ||
|
|
e6f8bde74b | ||
|
|
f370de84b1 | ||
|
|
d494b074e8 | ||
|
|
a439cdf22e | ||
|
|
8a78fc2580 | ||
|
|
ab9aae28bf | ||
|
|
391f5f360d | ||
|
|
9e9c287f8b | ||
|
|
6515c6e8c6 | ||
|
|
4217e58a10 | ||
|
|
445b4342b3 | ||
|
|
cd1c38be8d | ||
|
|
535c542d84 | ||
|
|
725bac1404 | ||
|
|
38c6c497f6 | ||
|
|
83c7ba1ef7 | ||
|
|
a66c61ca2d | ||
|
|
44cb9997b3 | ||
|
|
6ed28e15fa | ||
|
|
3de33348e4 | ||
|
|
19847d4d42 | ||
|
|
a33fbaddec | ||
|
|
1c672128c4 | ||
|
|
c43e559734 | ||
|
|
a6bce296ad | ||
|
|
e4b175ade2 | ||
|
|
1e987dbe8d | ||
|
|
07993ac8c8 | ||
|
|
b4dc01f16a | ||
|
|
75e10578f1 | ||
|
|
de5b521c09 | ||
|
|
2a8837ff51 | ||
|
|
04e0f8776c | ||
|
|
bd36eaf15d | ||
|
|
cc3aa95926 | ||
|
|
3902067008 | ||
|
|
d4ebb510a0 | ||
|
|
5b6a67f849 | ||
|
|
3d9fbb8226 | ||
|
|
15a79eb0d7 | ||
|
|
203e706302 | ||
|
|
3b5b950c89 | ||
|
|
ef4afa9760 | ||
|
|
589f9cf108 | ||
|
|
49ba563995 | ||
|
|
18dcb09342 | ||
|
|
6bf137a0e8 | ||
|
|
dc58058203 | ||
|
|
a5c58a25ef | ||
|
|
9e4b9f1afd | ||
|
|
e31425df38 | ||
|
|
0ea4a8bcc4 | ||
|
|
23515e0ccc | ||
|
|
c56414b80d | ||
|
|
b829643946 | ||
|
|
5219424226 | ||
|
|
b9be484a51 | ||
|
|
a8d17adb7c | ||
|
|
73b035d2e2 | ||
|
|
64fa9b9f57 | ||
|
|
af88767508 | ||
|
|
323eb86c9f | ||
|
|
6205965df9 | ||
|
|
9eaccac674 | ||
|
|
6f16f54f10 | ||
|
|
bd697bef03 | ||
|
|
a91f92a89d | ||
|
|
705cccb1e4 | ||
|
|
efef7b1517 | ||
|
|
b32b7c6e74 | ||
|
|
2d82b7f1a1 | ||
|
|
7c07941882 | ||
|
|
bc51a9365b | ||
|
|
0b23ce6ef2 | ||
|
|
032b7d490d | ||
|
|
6e14edbcc2 | ||
|
|
e6fee39ae7 | ||
|
|
ca25a3845e | ||
|
|
b5d54619cc | ||
|
|
98bbab8030 | ||
|
|
a6b5528e9c | ||
|
|
0f4a611129 | ||
|
|
3828aa4927 | ||
|
|
b1f4de7874 | ||
|
|
db96b5ee3b | ||
|
|
d3e9b45ce0 | ||
|
|
9d55e5586f | ||
|
|
8584a77eb2 | ||
|
|
1a84209418 | ||
|
|
c94b398f14 | ||
|
|
a6db8e5f4d | ||
|
|
c387a72c76 | ||
|
|
7527402a46 | ||
|
|
bb2cbdf704 | ||
|
|
3579db425e | ||
|
|
78d3b54ea7 | ||
|
|
1d6be9febf | ||
|
|
6481d91e4a | ||
|
|
9e1975a166 | ||
|
|
0b86c7eb6a | ||
|
|
f8247826fa | ||
|
|
7b893c7963 | ||
|
|
72f14ae21f | ||
|
|
3017be7855 | ||
|
|
02a33feef4 | ||
|
|
3eb8efc095 | ||
|
|
c4fe83a7bc | ||
|
|
5872fc21fe | ||
|
|
ecbc813711 | ||
|
|
3f739514e3 | ||
|
|
7d08d548a9 | ||
|
|
b9f0b9dd06 | ||
|
|
6582857356 | ||
|
|
0f4512291a | ||
|
|
a46df40939 | ||
|
|
9e7ac6a009 | ||
|
|
5c6adea222 | ||
|
|
04497d9e4a | ||
|
|
5f6e44552a | ||
|
|
57b93395a8 | ||
|
|
a980b4cbc1 | ||
|
|
ae1f709658 | ||
|
|
d11baf8bf8 | ||
|
|
a927d8be52 | ||
|
|
38868e5750 | ||
|
|
bc8d3b8f82 | ||
|
|
2c9308954c | ||
|
|
0e1268e507 | ||
|
|
c18dc9c707 | ||
|
|
2f2df9a4a7 | ||
|
|
510838759f | ||
|
|
2f0b322e72 | ||
|
|
32343d820d | ||
|
|
82ecdd0104 | ||
|
|
32a2dcd415 | ||
|
|
39c97f1b65 | ||
|
|
41a4090320 | ||
|
|
32485917ba | ||
|
|
73fb3a304b | ||
|
|
00c66a7289 | ||
|
|
da79ec9565 | ||
|
|
b66ccaa376 | ||
|
|
380fbd8cb7 | ||
|
|
f5e2aec422 | ||
|
|
52340e94ac | ||
|
|
fabdf5d385 | ||
|
|
74b4334d51 | ||
|
|
6ce5f3120b | ||
|
|
e3d2b646e0 | ||
|
|
5226610a15 | ||
|
|
3bab5a5e4a | ||
|
|
9f54cd4dad | ||
|
|
1d1489da80 | ||
|
|
743e1f02a0 | ||
|
|
bfe2e40882 | ||
|
|
15aeae3dd3 | ||
|
|
60da57b518 | ||
|
|
d4e07fd95e | ||
|
|
2ce3aedda8 | ||
|
|
b8798a995b | ||
|
|
45dac6bc5c | ||
|
|
901bc09dd7 | ||
|
|
39ab33ee1c | ||
|
|
fcc23139f6 | ||
|
|
8ccc8cb252 | ||
|
|
f5d2a1e8bd | ||
|
|
e193aa3f53 | ||
|
|
25174afa79 | ||
|
|
544b15e8e4 | ||
|
|
8cf6efe677 | ||
|
|
f2df941e8d | ||
|
|
e0af4cdf98 | ||
|
|
406d298457 | ||
|
|
2f09c7ddd3 | ||
|
|
28046ae3a9 | ||
|
|
0f729ef078 | ||
|
|
14a1181a97 | ||
|
|
c27c76ed43 | ||
|
|
e8855ed0fc | ||
|
|
46b4461fbb | ||
|
|
968d631aa5 | ||
|
|
5f0fa4cb82 | ||
|
|
121af3646d | ||
|
|
0235915baa | ||
|
|
c94583d867 | ||
|
|
0076a08d04 | ||
|
|
18a42d3815 | ||
|
|
0248614add | ||
|
|
409fedaf97 | ||
|
|
182ac8a504 | ||
|
|
23d57ed4f7 | ||
|
|
d11b04ed46 | ||
|
|
606e833d26 | ||
|
|
b81af6ae9b | ||
|
|
f77c897b8d | ||
|
|
1e65da971b | ||
|
|
f98bf1025f | ||
|
|
20ed33b53b | ||
|
|
a8674a7b86 | ||
|
|
c6ba7a228d | ||
|
|
c12eb814b4 | ||
|
|
ef53b2fd08 | ||
|
|
928e9c09aa | ||
|
|
2bd903e021 | ||
|
|
a5ed0c3df7 | ||
|
|
9bb5bf0b2b | ||
|
|
97ba520434 | ||
|
|
d6474b4aca | ||
|
|
480e1fa987 | ||
|
|
7d1dca4c98 | ||
|
|
5865a10885 | ||
|
|
a6e5b84d1f | ||
|
|
5a4e89b901 | ||
|
|
140f953b6a | ||
|
|
8ea749c1ca | ||
|
|
479605b3e5 | ||
|
|
9c5ed4408d | ||
|
|
a7fe6dc232 | ||
|
|
811bff009e | ||
|
|
4514b80b3e | ||
|
|
7daea551c0 | ||
|
|
8434630dcc | ||
|
|
c6a963c48e | ||
|
|
8272f53cf9 | ||
|
|
7236393114 | ||
|
|
c7ed7d9427 | ||
|
|
977ceb4056 | ||
|
|
0f7822acb1 | ||
|
|
5a092fb61e | ||
|
|
cf137ea40b | ||
|
|
de644d506f | ||
|
|
7d763f060e | ||
|
|
45d29436b3 | ||
|
|
e77ee67bfa | ||
|
|
b6655aa2e4 | ||
|
|
59ce6e6d06 | ||
|
|
18f54f7486 | ||
|
|
137d862d9b | ||
|
|
41013381d6 | ||
|
|
7d2b1a6ec4 | ||
|
|
8f6ffcd5c4 | ||
|
|
96b2d8419c | ||
|
|
49a7e0984a | ||
|
|
1f7dd36499 | ||
|
|
3398f701ee | ||
|
|
1bd706344e | ||
|
|
03e4f5dac4 | ||
|
|
e3524d1142 | ||
|
|
234b5ff6a9 | ||
|
|
0f8e5a1465 | ||
|
|
62e35ffc0e | ||
|
|
be320a9e10 | ||
|
|
8d0e3c5422 | ||
|
|
bc266a9d98 | ||
|
|
13ed9438fb | ||
|
|
bfa6193eb9 | ||
|
|
778043a44c | ||
|
|
778f86989a | ||
|
|
b19fe55f84 | ||
|
|
4f09f0aea4 | ||
|
|
69f38355ed | ||
|
|
b1eada6079 | ||
|
|
442e48ef4c | ||
|
|
8ae7154541 | ||
|
|
684dfbf209 | ||
|
|
2f420618ea | ||
|
|
be660e7749 | ||
|
|
8868fb745f | ||
|
|
edb2114bac | ||
|
|
1394a581f2 | ||
|
|
44f10d9b9f | ||
|
|
52bcfac116 | ||
|
|
109df7705f | ||
|
|
32a127faaa | ||
|
|
a8ac99b619 | ||
|
|
c409722435 | ||
|
|
a6ddd7c382 | ||
|
|
7a59eeb5be | ||
|
|
b660ef6c8a | ||
|
|
0f57bbfa3f | ||
|
|
2a3d4cad63 | ||
|
|
798ec003ce | ||
|
|
bd9495c9ab | ||
|
|
c0d6162050 | ||
|
|
f22d02083c | ||
|
|
4b71bf654d | ||
|
|
e2f5d16540 | ||
|
|
ed2cd9d8f3 | ||
|
|
0bd9bc7201 | ||
|
|
256cb2979b | ||
|
|
cf0aad7d6a | ||
|
|
1799f4e774 | ||
|
|
c09a9e5cc7 | ||
|
|
79aa7b3ace | ||
|
|
35db6e1c68 | ||
|
|
389549b80d | ||
|
|
a6a43a5ae0 | ||
|
|
7c6203dc5e | ||
|
|
36362e9695 | ||
|
|
d563017dfe | ||
|
|
4417770ba9 | ||
|
|
5c9dee2c94 | ||
|
|
6e5d8aac4d | ||
|
|
55ebf68636 | ||
|
|
39213b1c59 | ||
|
|
198b0fa790 | ||
|
|
1520d7865d | ||
|
|
761d206049 | ||
|
|
151a3fe7b3 | ||
|
|
89d11f2268 | ||
|
|
92021a344c | ||
|
|
c2ea1e1bcb | ||
|
|
42250427c5 | ||
|
|
5633887569 | ||
|
|
e1911e5c8b | ||
|
|
87011a97f9 | ||
|
|
a63a0daa5e | ||
|
|
d89888389d | ||
|
|
b15cbf9bcf | ||
|
|
74ff1db758 | ||
|
|
83e3b77ed7 | ||
|
|
6508cdd003 | ||
|
|
7646f2c21d | ||
|
|
6e122f0b2c | ||
|
|
20a779299a | ||
|
|
3626254f48 | ||
|
|
98d2461529 | ||
|
|
bd43c05470 | ||
|
|
c99f5d405b | ||
|
|
3c2ae53b4c | ||
|
|
c95c254f3e | ||
|
|
2293e8a11a | ||
|
|
abcea1bb18 | ||
|
|
678f95e4f8 | ||
|
|
b96f65b62b | ||
|
|
dc27252352 | ||
|
|
03fad5ebe8 | ||
|
|
03fd5aa384 | ||
|
|
e78d681a6c | ||
|
|
354fbe701e | ||
|
|
98671b4cfe | ||
|
|
5b2b6d594c | ||
|
|
e1438f8e91 | ||
|
|
22369df357 | ||
|
|
624def4f38 | ||
|
|
34d4abc4f9 | ||
|
|
c0d2e3212f | ||
|
|
eca3d16e54 | ||
|
|
1b64fae912 | ||
|
|
3d99b449d3 | ||
|
|
c647999c61 | ||
|
|
411f5527d4 | ||
|
|
aaa4822fcb | ||
|
|
623b93a2b3 | ||
|
|
23d3e4a3c4 | ||
|
|
597d8b4bd4 | ||
|
|
dfd1badc12 | ||
|
|
9a0c1456e3 | ||
|
|
c5de3c1059 | ||
|
|
3a20e74f40 | ||
|
|
866c1165af | ||
|
|
4a6b9a1a71 | ||
|
|
3a59e724c9 | ||
|
|
4511502ca6 | ||
|
|
bb6d93630f | ||
|
|
74f2e5f1a4 | ||
|
|
70188d69b0 | ||
|
|
3a6714ab7f | ||
|
|
6c0b1a9ee2 | ||
|
|
8c84a7e7ec | ||
|
|
4d10d3113f | ||
|
|
0ee310ebdc | ||
|
|
ee21e4ecd3 | ||
|
|
e68ee43a1a | ||
|
|
104b334e40 | ||
|
|
0ac8848eae | ||
|
|
edbf3144d2 | ||
|
|
f7debcaa04 | ||
|
|
a280822c82 | ||
|
|
a087b3365a | ||
|
|
2b1cc232bc | ||
|
|
bb8ef38152 | ||
|
|
058ec22787 | ||
|
|
f2d1aa97ad | ||
|
|
1ee1a5d3d6 | ||
|
|
8118ea160b | ||
|
|
b032ebdfee | ||
|
|
9d9ffe0f94 | ||
|
|
d0bdd26c26 | ||
|
|
e454f7e7a7 | ||
|
|
dd70e097cc | ||
|
|
87b272699f | ||
|
|
5616be12be | ||
|
|
5b37cecd76 | ||
|
|
43bf860b22 | ||
|
|
1bb3122c1f | ||
|
|
5242b21524 | ||
|
|
9b06e823ee | ||
|
|
fc153f6bcd | ||
|
|
f57cbd9f24 | ||
|
|
326403518d | ||
|
|
099ac9c2a8 | ||
|
|
136c563f76 | ||
|
|
640f0d1cec | ||
|
|
3b2dee88e6 | ||
|
|
b8b6f94ba9 | ||
|
|
630fc12d4e | ||
|
|
d2b2557542 | ||
|
|
f3f056c3b6 | ||
|
|
31eb658fea | ||
|
|
b2af304918 | ||
|
|
32e6727dae | ||
|
|
b2c4521a91 | ||
|
|
b17fe82973 | ||
|
|
8bba84a401 | ||
|
|
606a62d4c7 | ||
|
|
efe7b7483b | ||
|
|
508242c267 | ||
|
|
623d9c47a2 | ||
|
|
c13e2f1b75 | ||
|
|
86345c126a | ||
|
|
1adabdac7f | ||
|
|
325e7eed3c | ||
|
|
487dd05170 | ||
|
|
6a5cf1473e | ||
|
|
d0a9caa08f | ||
|
|
1306608b2a | ||
|
|
5d0986a53b | ||
|
|
103809a0ca | ||
|
|
e2b67a868b | ||
|
|
ed4e324991 | ||
|
|
434856c636 | ||
|
|
ea14af2164 | ||
|
|
ebaace294f | ||
|
|
a2dcc642c1 | ||
|
|
19d4f28001 | ||
|
|
891236124c | ||
|
|
3b0baf746e | ||
|
|
599274e3f0 | ||
|
|
cb75ccc1f7 | ||
|
|
235805edf3 | ||
|
|
ae61e47cba | ||
|
|
9f82a9a244 | ||
|
|
420cc13248 | ||
|
|
47a7c4f4fe | ||
|
|
cf4ee279c6 | ||
|
|
d0fc12684a | ||
|
|
4cff5dd194 | ||
|
|
9a36d8600c | ||
|
|
65010607b7 | ||
|
|
3c378a31b5 | ||
|
|
74d5c0ed2f | ||
|
|
17455b7222 | ||
|
|
6f0360690b | ||
|
|
4b9504028d | ||
|
|
55c0dd1cb3 | ||
|
|
7a27b7f3a3 | ||
|
|
b73f678ee8 | ||
|
|
024c84d2db | ||
|
|
0e2ded049d | ||
|
|
670a7f51e8 | ||
|
|
b1a1bd12ca | ||
|
|
91dddca26e | ||
|
|
cf6a40fc12 | ||
|
|
bba54e1880 | ||
|
|
1585981eec | ||
|
|
a79f060ea2 | ||
|
|
1beaebe666 | ||
|
|
3c8cd62b0d | ||
|
|
33441fa728 | ||
|
|
988e42a3f5 | ||
|
|
5dec81fcbd | ||
|
|
16c0373adc | ||
|
|
c4e7ec7a99 | ||
|
|
9de860a419 | ||
|
|
47b97b9577 | ||
|
|
551c61bf27 | ||
|
|
ab9ddab0a2 | ||
|
|
9c065c013e | ||
|
|
ecc8ccc9d3 | ||
|
|
6ec6cb50dd | ||
|
|
8b329ddcc9 | ||
|
|
1887afaf9e | ||
|
|
8d15f8b28e | ||
|
|
0a4be73b9b | ||
|
|
d4e1633fb7 | ||
|
|
e8baf07136 | ||
|
|
290bc20e79 | ||
|
|
c96a8867f0 | ||
|
|
0e2a7ca91b | ||
|
|
ef1f596595 | ||
|
|
c5c60e04dd | ||
|
|
7e376af8fc | ||
|
|
4e57f9d5cf | ||
|
|
a9ee6e346b | ||
|
|
dd449ce462 | ||
|
|
bc30a591ba | ||
|
|
50c27d5ae1 | ||
|
|
1121960f0e | ||
|
|
028f6fdbf6 | ||
|
|
3c39a87146 | ||
|
|
9e5af4ad30 | ||
|
|
41682e0888 | ||
|
|
88141bb2d4 | ||
|
|
eb2c50c5e6 | ||
|
|
ea09930196 | ||
|
|
227278098a | ||
|
|
929acd4d1e | ||
|
|
2dbf5290f2 | ||
|
|
f813cd3ff7 | ||
|
|
9b8e962368 | ||
|
|
500e9c5969 | ||
|
|
0d735a905d | ||
|
|
fc04108c77 | ||
|
|
29fce1a187 | ||
|
|
4620580245 | ||
|
|
1ecaa86a52 | ||
|
|
a2e61e6542 | ||
|
|
26aee55aef | ||
|
|
dfa582169b | ||
|
|
93c083aef1 | ||
|
|
0ec07e8763 | ||
|
|
3cfa7a89e0 | ||
|
|
2aff0b4733 | ||
|
|
a925ae79ff | ||
|
|
9222b3c960 | ||
|
|
ba02ff230a | ||
|
|
3309a6cdf1 | ||
|
|
8db4feb5b4 | ||
|
|
7459215933 | ||
|
|
c49f51eaf1 | ||
|
|
3d1e741645 | ||
|
|
aabf5b2059 | ||
|
|
30bd77c6e7 | ||
|
|
5ddcc1b2f5 | ||
|
|
f4ca8e0d3e | ||
|
|
02910953d3 | ||
|
|
0128901102 | ||
|
|
3b668e1210 | ||
|
|
e6b4311178 | ||
|
|
5f2a9a4c86 | ||
|
|
1bcdf5e61c | ||
|
|
a290aac5b3 | ||
|
|
fd005585f6 | ||
|
|
224cf3075c | ||
|
|
b4d0724a63 | ||
|
|
3f4cabfd4d | ||
|
|
d5a69ecb68 | ||
|
|
3a13552db8 | ||
|
|
8850d85341 | ||
|
|
96ee1b42bc | ||
|
|
4b16e93f1a | ||
|
|
f88d2f5739 | ||
|
|
ecf1f2e054 | ||
|
|
0a0b676286 | ||
|
|
07552d4615 | ||
|
|
e6f4251988 | ||
|
|
7909860d16 | ||
|
|
96cd5dce85 | ||
|
|
bf3f030a0d | ||
|
|
c7b5a87c90 | ||
|
|
da2b8295e1 | ||
|
|
6572660fde | ||
|
|
78e5f162e2 | ||
|
|
55e423c8b6 | ||
|
|
738140fdd8 | ||
|
|
10804d6d0e | ||
|
|
38ec6e14d9 | ||
|
|
3e9cafbee5 | ||
|
|
b7a69501cd | ||
|
|
973c40b63e | ||
|
|
9acd336422 | ||
|
|
8bddc750e2 | ||
|
|
1417849a2b | ||
|
|
1fbc341aba | ||
|
|
b6538c3e7c | ||
|
|
6c53edd4d3 | ||
|
|
8e64fb3225 | ||
|
|
c3c3e07263 | ||
|
|
5cb531b6cf | ||
|
|
b4dbf1b9c7 | ||
|
|
9df67b2095 | ||
|
|
37c690576f | ||
|
|
72b73d22ab | ||
|
|
961427037c | ||
|
|
2261cf24af | ||
|
|
fdbeb90bd0 | ||
|
|
5e19691e41 | ||
|
|
0f6064e5c9 | ||
|
|
1276e425d2 | ||
|
|
a17813c4f4 | ||
|
|
fb51a655b8 | ||
|
|
d6a24b4a5b | ||
|
|
fe931ac976 | ||
|
|
b87422a86f | ||
|
|
90e5694230 | ||
|
|
bb1ed66d99 | ||
|
|
4dca2298f9 | ||
|
|
1517cba8ca | ||
|
|
a65e9ad552 | ||
|
|
e8e04a4b80 | ||
|
|
6c7d8073be | ||
|
|
8da16cf9fb | ||
|
|
378aed07e9 | ||
|
|
11e1629d89 | ||
|
|
7e38dd580f | ||
|
|
ddd82ef42b | ||
|
|
9bd70c52e5 | ||
|
|
ccda5ffa58 | ||
|
|
c1ffaa8b29 | ||
|
|
dffcff9fec | ||
|
|
527a1574c3 | ||
|
|
a19c6317ef | ||
|
|
d8a961cd6c | ||
|
|
03a6f3b0f4 | ||
|
|
cc84b48ce5 | ||
|
|
4fb921ff6b | ||
|
|
72deb773fd | ||
|
|
3e35101895 | ||
|
|
8c37cd1af6 | ||
|
|
f665c92114 | ||
|
|
8af62c9997 | ||
|
|
5c9feaebb6 | ||
|
|
b8aef40c56 | ||
|
|
18a88d19dc | ||
|
|
3fedcc2f6e | ||
|
|
bcd0444bb9 | ||
|
|
dd43d725c6 | ||
|
|
8d917e14f8 | ||
|
|
6f47bd9641 | ||
|
|
488ed8bd02 | ||
|
|
0bbae63300 | ||
|
|
99eaa2e6f2 | ||
|
|
e7664b7a4f |
@@ -1,4 +1,7 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
chmod a+x ./.ci/scripts/format/docker.sh
|
||||
# the UID for the container yuzu user is 1027
|
||||
sudo chown -R 1027 ./
|
||||
docker run -v $(pwd):/yuzu yuzuemu/build-environments:linux-clang-format /bin/bash -ex /yuzu/.ci/scripts/format/docker.sh
|
||||
sudo chown -R $UID ./
|
||||
|
||||
2
.ci/scripts/linux/docker.sh
Normal file → Executable file
2
.ci/scripts/linux/docker.sh
Normal file → Executable file
@@ -5,7 +5,7 @@ cd /yuzu
|
||||
ccache -s
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_VULKAN=No
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=/usr/lib/ccache/gcc -DCMAKE_CXX_COMPILER=/usr/lib/ccache/g++ -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DUSE_DISCORD_PRESENCE=ON
|
||||
|
||||
ninja
|
||||
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/linux/docker.sh
|
||||
# the UID for the container yuzu user is 1027
|
||||
sudo chown -R 1027 ./
|
||||
docker run -e ENABLE_COMPATIBILITY_REPORTING -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-fresh /bin/bash /yuzu/.ci/scripts/linux/docker.sh $1
|
||||
sudo chown -R $UID ./
|
||||
|
||||
22
.ci/scripts/windows/docker.sh
Normal file → Executable file
22
.ci/scripts/windows/docker.sh
Normal file → Executable file
@@ -4,22 +4,10 @@ cd /yuzu
|
||||
|
||||
ccache -s
|
||||
|
||||
# Dirty hack to trick unicorn makefile into believing we are in a MINGW system
|
||||
mv /bin/uname /bin/uname1 && echo -e '#!/bin/sh\necho MINGW64' >> /bin/uname
|
||||
chmod +x /bin/uname
|
||||
|
||||
# Dirty hack to trick unicorn makefile into believing we have cmd
|
||||
echo '' >> /bin/cmd
|
||||
chmod +x /bin/cmd
|
||||
|
||||
mkdir build || true && cd build
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_VULKAN=No
|
||||
cmake .. -G Ninja -DDISPLAY_VERSION=$1 -DCMAKE_TOOLCHAIN_FILE="$(pwd)/../CMakeModules/MinGWCross.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_UNICORN=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DCMAKE_BUILD_TYPE=Release
|
||||
ninja
|
||||
|
||||
# Clean up the dirty hacks
|
||||
rm /bin/uname && mv /bin/uname1 /bin/uname
|
||||
rm /bin/cmd
|
||||
|
||||
ccache -s
|
||||
|
||||
echo "Tests skipped"
|
||||
@@ -29,7 +17,13 @@ echo 'Prepare binaries...'
|
||||
cd ..
|
||||
mkdir package
|
||||
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
|
||||
if [ -d "/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/" ]; then
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt5/plugins/platforms/'
|
||||
else
|
||||
#fallback to qt
|
||||
QT_PLATFORM_DLL_PATH='/usr/x86_64-w64-mingw32/lib/qt/plugins/platforms/'
|
||||
fi
|
||||
|
||||
find build/ -name "yuzu*.exe" -exec cp {} 'package' \;
|
||||
|
||||
# copy Qt plugins
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
|
||||
mkdir -p "ccache" || true
|
||||
chmod a+x ./.ci/scripts/windows/docker.sh
|
||||
# the UID for the container yuzu user is 1027
|
||||
sudo chown -R 1027 ./
|
||||
docker run -e CCACHE_DIR=/yuzu/ccache -v $(pwd):/yuzu yuzuemu/build-environments:linux-mingw /bin/bash -ex /yuzu/.ci/scripts/windows/docker.sh $1
|
||||
sudo chown -R $UID ./
|
||||
|
||||
@@ -4,7 +4,9 @@ parameters:
|
||||
version: ''
|
||||
|
||||
steps:
|
||||
- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
|
||||
- script: python -m pip install --upgrade pip conan
|
||||
displayName: 'Install conan'
|
||||
- script: mkdir build && cd build && cmake -G "Visual Studio 16 2019" -A x64 --config Release -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON -DDISPLAY_VERSION=${{ parameters['version'] }} .. && cd ..
|
||||
displayName: 'Configure CMake'
|
||||
- task: MSBuild@1
|
||||
displayName: 'Build'
|
||||
|
||||
@@ -5,18 +5,7 @@ variables:
|
||||
DisplayVersion: $[counter(variables['DisplayPrefix'], 1)]
|
||||
|
||||
stages:
|
||||
- stage: format
|
||||
displayName: 'format'
|
||||
jobs:
|
||||
- job: format
|
||||
displayName: 'clang'
|
||||
continueOnError: true
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
steps:
|
||||
- template: ./templates/format-check.yml
|
||||
- stage: build
|
||||
dependsOn: format
|
||||
displayName: 'build'
|
||||
jobs:
|
||||
- job: build
|
||||
|
||||
@@ -1,4 +1,13 @@
|
||||
<!--
|
||||
---
|
||||
name: Bug Report / Feature Request
|
||||
about: Tech support does not belong here. You should only file an issue here if you think you have experienced an actual bug with yuzu or you are requesting a feature you believe would make yuzu better.
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!---
|
||||
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
|
||||
|
||||
Please read the FAQ:
|
||||
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
8
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: yuzu Discord
|
||||
url: https://discord.com/invite/u77vRWY
|
||||
about: If you are experiencing an issue with yuzu, and you need tech support, or if you have a general question, try asking in the official yuzu Discord linked here. Piracy is not allowed.
|
||||
- name: Community forums
|
||||
url: https://community.citra-emu.org
|
||||
about: This is an alternative place for tech support, however helpers there are not as active.
|
||||
36
.gitmodules
vendored
36
.gitmodules
vendored
@@ -1,33 +1,15 @@
|
||||
[submodule "inih"]
|
||||
path = externals/inih/inih
|
||||
url = https://github.com/svn2github/inih
|
||||
[submodule "boost"]
|
||||
path = externals/boost
|
||||
url = https://github.com/yuzu-emu/ext-boost.git
|
||||
[submodule "catch"]
|
||||
path = externals/catch
|
||||
url = https://github.com/philsquared/Catch.git
|
||||
[submodule "cubeb"]
|
||||
path = externals/cubeb
|
||||
url = https://github.com/kinetiknz/cubeb.git
|
||||
[submodule "dynarmic"]
|
||||
path = externals/dynarmic
|
||||
url = https://github.com/MerryMage/dynarmic.git
|
||||
[submodule "fmt"]
|
||||
path = externals/fmt
|
||||
url = https://github.com/fmtlib/fmt.git
|
||||
[submodule "lz4"]
|
||||
path = externals/lz4
|
||||
url = https://github.com/lz4/lz4.git
|
||||
[submodule "unicorn"]
|
||||
path = externals/unicorn
|
||||
url = https://github.com/yuzu-emu/unicorn
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/DarkLordZach/mbedtls
|
||||
[submodule "opus"]
|
||||
path = externals/opus/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
[submodule "soundtouch"]
|
||||
path = externals/soundtouch
|
||||
url = https://github.com/citra-emu/ext-soundtouch.git
|
||||
@@ -40,15 +22,21 @@
|
||||
[submodule "Vulkan-Headers"]
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
[submodule "externals/zstd"]
|
||||
path = externals/zstd
|
||||
url = https://github.com/facebook/zstd
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/ReinUsesLisp/sirit
|
||||
[submodule "mbedtls"]
|
||||
path = externals/mbedtls
|
||||
url = https://github.com/DarkLordZach/mbedtls
|
||||
[submodule "libzip"]
|
||||
path = externals/libzip/libzip
|
||||
url = https://github.com/nih-at/libzip.git
|
||||
[submodule "zlib"]
|
||||
path = externals/zlib/zlib
|
||||
url = https://github.com/madler/zlib.git
|
||||
[submodule "xbyak"]
|
||||
path = externals/xbyak
|
||||
url = https://github.com/herumi/xbyak.git
|
||||
[submodule "externals/libusb"]
|
||||
path = externals/libusb
|
||||
url = https://github.com/ameerj/libusb
|
||||
[submodule "opus"]
|
||||
path = externals/opus/opus
|
||||
url = https://github.com/xiph/opus.git
|
||||
|
||||
309
CMakeLists.txt
309
CMakeLists.txt
@@ -1,7 +1,8 @@
|
||||
cmake_minimum_required(VERSION 3.7)
|
||||
cmake_minimum_required(VERSION 3.15)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/find-modules")
|
||||
include(DownloadExternals)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
@@ -10,7 +11,6 @@ project(yuzu)
|
||||
# Set bundled sdl2/qt as dependent options.
|
||||
# OFF by default, but if ENABLE_SDL2 and MSVC are true then ON
|
||||
option(ENABLE_SDL2 "Enable the SDL2 frontend" ON)
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_SDL2 "Download bundled SDL2 binaries" ON "ENABLE_SDL2;MSVC" OFF)
|
||||
|
||||
option(ENABLE_QT "Enable the Qt frontend" ON)
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_QT "Download bundled Qt binaries" ON "ENABLE_QT;MSVC" OFF)
|
||||
@@ -29,6 +29,13 @@ option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
# Default to a Release build
|
||||
get_property(IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG)
|
||||
if (NOT IS_MULTI_CONFIG AND NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||
message(STATUS "Defaulting to a Release build")
|
||||
endif()
|
||||
|
||||
if(EXISTS ${PROJECT_SOURCE_DIR}/hooks/pre-commit AND NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
message(STATUS "Copying pre-commit hook")
|
||||
file(COPY hooks/pre-commit
|
||||
@@ -53,7 +60,6 @@ endfunction()
|
||||
if(EXISTS ${PROJECT_SOURCE_DIR}/.gitmodules)
|
||||
check_submodules_present()
|
||||
endif()
|
||||
|
||||
configure_file(${PROJECT_SOURCE_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc
|
||||
COPYONLY)
|
||||
@@ -115,63 +121,226 @@ message(STATUS "Target architecture: ${ARCHITECTURE}")
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# System imported libraries
|
||||
# ======================
|
||||
|
||||
find_package(Boost 1.66.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
find_package(Boost QUIET REQUIRED)
|
||||
endif()
|
||||
|
||||
# Output binaries to bin/
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin)
|
||||
|
||||
# System imported libraries
|
||||
# If not found, download any missing through Conan
|
||||
# =======================================================================
|
||||
set(CONAN_CMAKE_SILENT_OUTPUT TRUE)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG TRUE)
|
||||
if (YUZU_CONAN_INSTALLED)
|
||||
if (IS_MULTI_CONFIG)
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
|
||||
else()
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
|
||||
conan_basic_setup()
|
||||
message(STATUS "Adding conan installed libraries to the search path")
|
||||
endif()
|
||||
|
||||
macro(yuzu_find_packages)
|
||||
set(options FORCE_REQUIRED)
|
||||
cmake_parse_arguments(FN "${options}" "" "" ${ARGN})
|
||||
|
||||
# Cmake has a *serious* lack of 2D array or associative array...
|
||||
# Capitalization matters here. We need the naming to match the generated paths from Conan
|
||||
set(REQUIRED_LIBS
|
||||
# Cmake Pkg Prefix Version Conan Pkg
|
||||
"Boost 1.71 boost/1.72.0"
|
||||
"Catch2 2.11 catch2/2.11.0"
|
||||
"fmt 6.2 fmt/6.2.0"
|
||||
# can't use until https://github.com/bincrafters/community/issues/1173
|
||||
#"libzip 1.5 libzip/1.5.2@bincrafters/stable"
|
||||
"lz4 1.8 lz4/1.9.2"
|
||||
"nlohmann_json 3.7 nlohmann_json/3.7.3"
|
||||
"ZLIB 1.2 zlib/1.2.11"
|
||||
"zstd 1.4 zstd/1.4.4"
|
||||
)
|
||||
|
||||
foreach(PACKAGE ${REQUIRED_LIBS})
|
||||
string(REGEX REPLACE "[ \t\r\n]+" ";" PACKAGE_SPLIT ${PACKAGE})
|
||||
list(GET PACKAGE_SPLIT 0 PACKAGE_PREFIX)
|
||||
list(GET PACKAGE_SPLIT 1 PACKAGE_VERSION)
|
||||
list(GET PACKAGE_SPLIT 2 PACKAGE_CONAN)
|
||||
# This function is called twice, once to check if the packages exist on the system already
|
||||
# and a second time to check if conan installed them properly. The second check passes in FORCE_REQUIRED
|
||||
if (NOT ${PACKAGE_PREFIX}_FOUND)
|
||||
if (FN_FORCE_REQUIRED)
|
||||
find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION} REQUIRED)
|
||||
else()
|
||||
find_package(${PACKAGE_PREFIX} ${PACKAGE_VERSION})
|
||||
endif()
|
||||
endif()
|
||||
if (NOT ${PACKAGE_PREFIX}_FOUND)
|
||||
list(APPEND CONAN_REQUIRED_LIBS ${PACKAGE_CONAN})
|
||||
else()
|
||||
# Set a legacy findPackage.cmake style PACKAGE_LIBRARIES variable for subprojects that rely on this
|
||||
set(${PACKAGE_PREFIX}_LIBRARIES "${PACKAGE_PREFIX}::${PACKAGE_PREFIX}")
|
||||
endif()
|
||||
endforeach()
|
||||
unset(FN_FORCE_REQUIRED)
|
||||
endmacro()
|
||||
|
||||
# Attempt to locate any packages that are required and report the missing ones in CONAN_REQUIRED_LIBS
|
||||
yuzu_find_packages()
|
||||
|
||||
# Qt5 requires that we find components, so it doesn't fit our pretty little find package function
|
||||
if(ENABLE_QT)
|
||||
# We want to load the generated conan qt config so that we get the QT_ROOT var so that we can use the official
|
||||
# Qt5Config inside the root folder instead of the conan generated one.
|
||||
if(EXISTS ${CMAKE_BINARY_DIR}/qtConfig.cmake)
|
||||
include(${CMAKE_BINARY_DIR}/qtConfig.cmake)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
|
||||
endif()
|
||||
# Workaround for an issue where conan tries to build Qt from scratch instead of download prebuilt binaries
|
||||
set(QT_PREFIX_HINT)
|
||||
if(YUZU_USE_BUNDLED_QT)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.12.0-msvc2017_64)
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
|
||||
endif()
|
||||
|
||||
if (DEFINED QT_VER)
|
||||
download_bundled_external("qt/" ${QT_VER} QT_PREFIX)
|
||||
endif()
|
||||
|
||||
set(QT_PREFIX_HINT HINTS "${QT_PREFIX}")
|
||||
endif()
|
||||
find_package(Qt5 5.9 COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
find_package(Qt5 COMPONENTS WebEngineCore WebEngineWidgets)
|
||||
endif()
|
||||
if (NOT Qt5_FOUND)
|
||||
list(APPEND CONAN_REQUIRED_LIBS "qt/5.14.1@bincrafters/stable")
|
||||
endif()
|
||||
endif()
|
||||
# find SDL2 exports a bunch of variables that are needed, so its easier to do this outside of the yuzu_find_package
|
||||
if(ENABLE_SDL2)
|
||||
if(EXISTS ${CMAKE_BINARY_DIR}/sdl2Config.cmake)
|
||||
include(${CMAKE_BINARY_DIR}/sdl2Config.cmake)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CONAN_SDL2_ROOT_RELEASE}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CONAN_SDL2_ROOT_RELEASE}")
|
||||
endif()
|
||||
find_package(SDL2)
|
||||
if (NOT SDL2_FOUND)
|
||||
# otherwise add this to the list of libraries to install
|
||||
list(APPEND CONAN_REQUIRED_LIBS "sdl2/2.0.12@bincrafters/stable")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Install any missing dependencies with conan install
|
||||
if (CONAN_REQUIRED_LIBS)
|
||||
message(STATUS "Packages ${CONAN_REQUIRED_LIBS} not found!")
|
||||
# Use Conan to fetch the libraries that aren't found
|
||||
# Download conan.cmake automatically, you can also just copy the conan.cmake file
|
||||
if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
|
||||
message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
|
||||
file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake"
|
||||
"${CMAKE_BINARY_DIR}/conan.cmake")
|
||||
endif()
|
||||
include(${CMAKE_BINARY_DIR}/conan.cmake)
|
||||
|
||||
set(CONAN_LIB_OPTIONS
|
||||
libzip:with_openssl=False
|
||||
libzip:enable_windows_crypto=False
|
||||
)
|
||||
conan_check(VERSION 1.24.0 REQUIRED)
|
||||
# Add the bincrafters remote
|
||||
conan_add_remote(NAME bincrafters
|
||||
URL https://api.bintray.com/conan/bincrafters/public-conan)
|
||||
|
||||
# Manually add iconv to fix a dep conflict between qt and sdl2
|
||||
# We don't need to add it through find_package or anything since the other two can find it just fine
|
||||
if ("${CONAN_REQUIRED_LIBS}" MATCHES "qt" AND "${CONAN_REQUIRED_LIBS}" MATCHES "sdl")
|
||||
list(APPEND CONAN_REQUIRED_LIBS "libiconv/1.16")
|
||||
endif()
|
||||
if (IS_MULTI_CONFIG)
|
||||
conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
|
||||
OPTIONS ${CONAN_LIB_OPTIONS}
|
||||
BUILD missing
|
||||
CONFIGURATION_TYPES "Release;Debug"
|
||||
GENERATORS cmake_multi cmake_find_package_multi)
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo_multi.cmake)
|
||||
else()
|
||||
conan_cmake_run(REQUIRES ${CONAN_REQUIRED_LIBS}
|
||||
OPTIONS ${CONAN_LIB_OPTIONS}
|
||||
BUILD missing
|
||||
GENERATORS cmake cmake_find_package_multi)
|
||||
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
|
||||
endif()
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_BINARY_DIR}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CMAKE_BINARY_DIR}")
|
||||
conan_basic_setup()
|
||||
|
||||
set(YUZU_CONAN_INSTALLED TRUE CACHE BOOL "If true, the following builds will add conan to the lib search path" FORCE)
|
||||
|
||||
# Now that we've installed what we are missing, try to locate them again,
|
||||
# this time with required, so we bail if its not found.
|
||||
yuzu_find_packages(FORCE_REQUIRED)
|
||||
|
||||
# Due to issues with variable scopes in functions, we need to also find_package(qt5) outside of the function
|
||||
if(ENABLE_QT)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CONAN_QT_ROOT_RELEASE}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CONAN_QT_ROOT_RELEASE}")
|
||||
find_package(Qt5 5.9 REQUIRED COMPONENTS Widgets OpenGL)
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets)
|
||||
endif()
|
||||
endif()
|
||||
if(ENABLE_SDL2)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CONAN_SDL2_ROOT_RELEASE}")
|
||||
list(APPEND CMAKE_PREFIX_PATH "${CONAN_SDL2_ROOT_RELEASE}")
|
||||
find_package(SDL2 REQUIRED)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
||||
# Reexport some targets that are named differently when using the upstream CmakeConfig vs the generated Conan config
|
||||
# In order to ALIAS targets to a new name, they first need to be IMPORTED_GLOBAL
|
||||
# Dynarmic checks for target `boost` and so we want to make sure it can find it through our system instead of using their external
|
||||
if (TARGET Boost::Boost)
|
||||
set_target_properties(Boost::Boost PROPERTIES IMPORTED_GLOBAL TRUE)
|
||||
add_library(Boost::boost ALIAS Boost::Boost)
|
||||
add_library(boost ALIAS Boost::Boost)
|
||||
elseif (TARGET Boost::boost)
|
||||
set_target_properties(Boost::boost PROPERTIES IMPORTED_GLOBAL TRUE)
|
||||
add_library(boost ALIAS Boost::boost)
|
||||
endif()
|
||||
|
||||
if (TARGET sdl2::sdl2)
|
||||
# imported from the conan generated sdl2Config.cmake
|
||||
set_target_properties(sdl2::sdl2 PROPERTIES IMPORTED_GLOBAL TRUE)
|
||||
add_library(SDL2 ALIAS sdl2::sdl2)
|
||||
elseif(SDL2_FOUND)
|
||||
# found through the system package manager
|
||||
# Some installations don't set SDL2_LIBRARIES
|
||||
if("${SDL2_LIBRARIES}" STREQUAL "")
|
||||
message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
|
||||
set(SDL2_LIBRARIES "SDL2::SDL2")
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${SDL2_INCLUDE_DIRS})
|
||||
add_library(SDL2 INTERFACE)
|
||||
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
|
||||
endif()
|
||||
|
||||
# Ensure libusb is properly configured (based on dolphin libusb include)
|
||||
find_package(LibUSB)
|
||||
if (NOT LIBUSB_FOUND)
|
||||
add_subdirectory(externals/libusb)
|
||||
set(LIBUSB_LIBRARIES usb)
|
||||
endif()
|
||||
|
||||
|
||||
# Prefer the -pthread flag on Linux.
|
||||
set(THREADS_PREFER_PTHREAD_FLAG ON)
|
||||
find_package(Threads REQUIRED)
|
||||
|
||||
if (ENABLE_SDL2)
|
||||
if (YUZU_USE_BUNDLED_SDL2)
|
||||
# Detect toolchain and platform
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
|
||||
set(SDL2_VER "SDL2-2.0.8")
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled SDL2 binaries for your toolchain. Disable YUZU_USE_BUNDLED_SDL2 and provide your own.")
|
||||
endif()
|
||||
|
||||
if (DEFINED SDL2_VER)
|
||||
download_bundled_external("sdl2/" ${SDL2_VER} SDL2_PREFIX)
|
||||
endif()
|
||||
|
||||
set(SDL2_FOUND YES)
|
||||
set(SDL2_INCLUDE_DIR "${SDL2_PREFIX}/include" CACHE PATH "Path to SDL2 headers")
|
||||
set(SDL2_LIBRARY "${SDL2_PREFIX}/lib/x64/SDL2.lib" CACHE PATH "Path to SDL2 library")
|
||||
set(SDL2_DLL_DIR "${SDL2_PREFIX}/lib/x64/" CACHE PATH "Path to SDL2.dll")
|
||||
|
||||
add_library(SDL2 INTERFACE)
|
||||
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARY}")
|
||||
target_include_directories(SDL2 INTERFACE "${SDL2_INCLUDE_DIR}")
|
||||
else()
|
||||
find_package(SDL2 REQUIRED)
|
||||
|
||||
# Some installations don't set SDL2_LIBRARIES
|
||||
if("${SDL2_LIBRARIES}" STREQUAL "")
|
||||
message(WARNING "SDL2_LIBRARIES wasn't set, manually setting to SDL2::SDL2")
|
||||
set(SDL2_LIBRARIES "SDL2::SDL2")
|
||||
endif()
|
||||
|
||||
include_directories(SYSTEM ${SDL2_INCLUDE_DIRS})
|
||||
add_library(SDL2 INTERFACE)
|
||||
target_link_libraries(SDL2 INTERFACE "${SDL2_LIBRARIES}")
|
||||
endif()
|
||||
else()
|
||||
set(SDL2_FOUND NO)
|
||||
endif()
|
||||
|
||||
# If unicorn isn't found, msvc -> download bundled unicorn; everyone else -> build external
|
||||
if (YUZU_USE_BUNDLED_UNICORN)
|
||||
if (MSVC)
|
||||
@@ -212,8 +381,12 @@ if (YUZU_USE_BUNDLED_UNICORN)
|
||||
find_package(PythonInterp 2.7 REQUIRED)
|
||||
|
||||
if (MINGW)
|
||||
# Intentionally call the unicorn makefile directly instead of using make.sh so that we can override the
|
||||
# UNAME_S makefile variable to MINGW. This way we don't have to hack at the uname binary to build
|
||||
# Additionally, overriding DO_WINDOWS_EXPORT prevents unicorn from patching the static unicorn.a by using msvc and cmd,
|
||||
# which are both things we don't have in a mingw cross compiling environment.
|
||||
add_custom_command(OUTPUT ${LIBUNICORN_LIBRARY}
|
||||
COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" /bin/sh make.sh cross-win64
|
||||
COMMAND ${CMAKE_COMMAND} -E env UNICORN_ARCHS="aarch64" PYTHON="${PYTHON_EXECUTABLE}" CC=x86_64-w64-mingw32-gcc AR=x86_64-w64-mingw32-gcc-ar RANLIB=x86_64-w64-mingw32-gcc-ranlib make UNAME_S=MINGW DO_WINDOWS_EXPORT=0
|
||||
WORKING_DIRECTORY ${UNICORN_PREFIX}
|
||||
)
|
||||
else()
|
||||
@@ -243,32 +416,6 @@ else()
|
||||
message(FATAL_ERROR "Could not find or build unicorn which is required.")
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT)
|
||||
if (YUZU_USE_BUNDLED_QT)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1930) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.12.0-msvc2017_64)
|
||||
else()
|
||||
message(FATAL_ERROR "No bundled Qt binaries for your toolchain. Disable YUZU_USE_BUNDLED_QT and provide your own.")
|
||||
endif()
|
||||
|
||||
if (DEFINED QT_VER)
|
||||
download_bundled_external("qt/" ${QT_VER} QT_PREFIX)
|
||||
endif()
|
||||
|
||||
set(QT_PREFIX_HINT HINTS "${QT_PREFIX}")
|
||||
else()
|
||||
# Passing an empty HINTS seems to cause default system paths to get ignored in CMake 2.8 so
|
||||
# make sure to not pass anything if we don't have one.
|
||||
set(QT_PREFIX_HINT)
|
||||
endif()
|
||||
|
||||
find_package(Qt5 REQUIRED COMPONENTS Widgets OpenGL ${QT_PREFIX_HINT})
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
find_package(Qt5 REQUIRED COMPONENTS WebEngineCore WebEngineWidgets ${QT_PREFIX_HINT})
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
# Platform-specific library requirements
|
||||
# ======================================
|
||||
|
||||
@@ -299,7 +446,7 @@ find_program(CLANG_FORMAT
|
||||
PATHS ${PROJECT_BINARY_DIR}/externals)
|
||||
# if find_program doesn't find it, try to download from externals
|
||||
if (NOT CLANG_FORMAT)
|
||||
if (WIN32)
|
||||
if (WIN32 AND NOT CMAKE_CROSSCOMPILING)
|
||||
message(STATUS "Clang format not found! Downloading...")
|
||||
set(CLANG_FORMAT "${PROJECT_BINARY_DIR}/externals/clang-format${CLANG_FORMAT_POSTFIX}.exe")
|
||||
file(DOWNLOAD
|
||||
|
||||
@@ -51,6 +51,8 @@ endif()
|
||||
# The variable SRC_DIR must be passed into the script (since it uses the current build directory for all values of CMAKE_*_DIR)
|
||||
set(VIDEO_CORE "${SRC_DIR}/src/video_core")
|
||||
set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
|
||||
|
||||
@@ -10,8 +10,8 @@ set(SDL2_PATH ${MINGW_PREFIX})
|
||||
set(MINGW_TOOL_PREFIX ${CMAKE_SYSTEM_PROCESSOR}-w64-mingw32-)
|
||||
|
||||
# Specify the cross compiler
|
||||
set(CMAKE_C_COMPILER ${MINGW_TOOL_PREFIX}gcc-posix)
|
||||
set(CMAKE_CXX_COMPILER ${MINGW_TOOL_PREFIX}g++-posix)
|
||||
set(CMAKE_C_COMPILER ${MINGW_TOOL_PREFIX}gcc)
|
||||
set(CMAKE_CXX_COMPILER ${MINGW_TOOL_PREFIX}g++)
|
||||
set(CMAKE_RC_COMPILER ${MINGW_TOOL_PREFIX}windres)
|
||||
|
||||
# Mingw tools
|
||||
|
||||
4
dist/qt_themes/qdarkstyle/style.qss
vendored
4
dist/qt_themes/qdarkstyle/style.qss
vendored
@@ -673,10 +673,6 @@ QTabWidget::pane {
|
||||
border-bottom-left-radius: 2px;
|
||||
}
|
||||
|
||||
QTabWidget::tab-bar {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
QTabBar {
|
||||
qproperty-drawBase: 0;
|
||||
border-radius: 3px;
|
||||
|
||||
80
dist/yuzu.manifest
vendored
80
dist/yuzu.manifest
vendored
@@ -1,24 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
||||
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<windowsSettings>
|
||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
|
||||
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
|
||||
</windowsSettings>
|
||||
</application>
|
||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
</assembly>
|
||||
<assembly manifestVersion="1.0"
|
||||
xmlns="urn:schemas-microsoft-com:asm.v1"
|
||||
xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
|
||||
<asmv3:application>
|
||||
<asmv3:windowsSettings>
|
||||
<!-- Windows 7/8/8.1/10 -->
|
||||
<dpiAware
|
||||
xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
|
||||
true/pm
|
||||
</dpiAware>
|
||||
<!-- Windows 10, version 1607 or later -->
|
||||
<dpiAwareness
|
||||
xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
PerMonitorV2
|
||||
</dpiAwareness>
|
||||
<!-- Windows 10, version 1703 or later -->
|
||||
<gdiScaling
|
||||
xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">
|
||||
true
|
||||
</gdiScaling>
|
||||
<ws2:longPathAware
|
||||
xmlns:ws3="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
|
||||
true
|
||||
</ws2:longPathAware>
|
||||
</asmv3:windowsSettings>
|
||||
</asmv3:application>
|
||||
<compatibility
|
||||
xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
||||
<application>
|
||||
<!-- Windows 10 -->
|
||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
||||
<!-- Windows 8.1 -->
|
||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
||||
<!-- Windows 8 -->
|
||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
||||
<!-- Windows 7 -->
|
||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
||||
</application>
|
||||
</compatibility>
|
||||
<trustInfo
|
||||
xmlns="urn:schemas-microsoft-com:asm.v3">
|
||||
<security>
|
||||
<requestedPrivileges>
|
||||
<!--
|
||||
UAC settings:
|
||||
- app should run at same integrity level as calling process
|
||||
- app does not need to manipulate windows belonging to
|
||||
higher-integrity-level processes
|
||||
-->
|
||||
<requestedExecutionLevel
|
||||
level="asInvoker"
|
||||
uiAccess="false"
|
||||
/>
|
||||
</requestedPrivileges>
|
||||
</security>
|
||||
</trustInfo>
|
||||
</assembly>
|
||||
|
||||
96
externals/CMakeLists.txt
vendored
96
externals/CMakeLists.txt
vendored
@@ -1,30 +1,20 @@
|
||||
# Definitions for all external bundled libraries
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/CMakeModules")
|
||||
list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/externals/find-modules")
|
||||
include(DownloadExternals)
|
||||
|
||||
# pkgconfig -- it is used to find shared libraries without cmake modules on linux systems
|
||||
find_package(PkgConfig)
|
||||
if (NOT PkgConfig_FOUND)
|
||||
function(pkg_check_modules)
|
||||
# STUB
|
||||
endfunction()
|
||||
# xbyak
|
||||
if (ARCHITECTURE_x86 OR ARCHITECTURE_x86_64)
|
||||
add_library(xbyak INTERFACE)
|
||||
target_include_directories(xbyak SYSTEM INTERFACE ./xbyak/xbyak)
|
||||
target_compile_definitions(xbyak INTERFACE XBYAK_NO_OP_NAMES)
|
||||
endif()
|
||||
|
||||
# Catch
|
||||
add_library(catch-single-include INTERFACE)
|
||||
target_include_directories(catch-single-include INTERFACE catch/single_include)
|
||||
|
||||
# libfmt
|
||||
pkg_check_modules(FMT IMPORTED_TARGET GLOBAL fmt>=6.2.0)
|
||||
if (FMT_FOUND)
|
||||
add_library(fmt::fmt ALIAS PkgConfig::FMT)
|
||||
else()
|
||||
message(STATUS "fmt 6.2.0 or newer not found, falling back to externals")
|
||||
add_subdirectory(fmt)
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
endif()
|
||||
|
||||
# Dynarmic
|
||||
if (ARCHITECTURE_x86_64)
|
||||
set(DYNARMIC_TESTS OFF)
|
||||
@@ -43,17 +33,6 @@ add_subdirectory(glad)
|
||||
# inih
|
||||
add_subdirectory(inih)
|
||||
|
||||
# lz4
|
||||
pkg_check_modules(LIBLZ4 IMPORTED_TARGET GLOBAL liblz4>=1.8.0)
|
||||
if (LIBLZ4_FOUND)
|
||||
add_library(lz4_static ALIAS PkgConfig::LIBLZ4)
|
||||
else()
|
||||
message(STATUS "liblz4 1.8.0 or newer not found, falling back to externals")
|
||||
set(LZ4_BUNDLED_MODE ON)
|
||||
add_subdirectory(lz4/contrib/cmake_unofficial EXCLUDE_FROM_ALL)
|
||||
target_include_directories(lz4_static INTERFACE ./lz4/lib)
|
||||
endif()
|
||||
|
||||
# mbedtls
|
||||
add_subdirectory(mbedtls EXCLUDE_FROM_ALL)
|
||||
target_include_directories(mbedtls PUBLIC ./mbedtls/include)
|
||||
@@ -66,29 +45,9 @@ target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
add_library(unicorn-headers INTERFACE)
|
||||
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
|
||||
|
||||
# Zstandard
|
||||
pkg_check_modules(LIBZSTD IMPORTED_TARGET GLOBAL libzstd>=1.3.8)
|
||||
if (LIBZSTD_FOUND)
|
||||
add_library(libzstd_static ALIAS PkgConfig::LIBZSTD)
|
||||
else()
|
||||
message(STATUS "libzstd 1.3.8 or newer not found, falling back to externals")
|
||||
add_subdirectory(zstd/build/cmake EXCLUDE_FROM_ALL)
|
||||
target_include_directories(libzstd_static INTERFACE ./zstd/lib)
|
||||
endif()
|
||||
|
||||
# SoundTouch
|
||||
add_subdirectory(soundtouch)
|
||||
|
||||
# Opus
|
||||
pkg_check_modules(OPUS IMPORTED_TARGET GLOBAL opus>=1.3.1)
|
||||
if (OPUS_FOUND)
|
||||
add_library(opus ALIAS PkgConfig::OPUS)
|
||||
else()
|
||||
message(STATUS "opus 1.3.1 or newer not found, falling back to externals")
|
||||
add_subdirectory(opus)
|
||||
target_include_directories(opus INTERFACE ./opus/include)
|
||||
endif()
|
||||
|
||||
# Cubeb
|
||||
if(ENABLE_CUBEB)
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
@@ -106,36 +65,22 @@ if (ENABLE_VULKAN)
|
||||
add_subdirectory(sirit)
|
||||
endif()
|
||||
|
||||
# zlib
|
||||
find_package(ZLIB 1.2.11)
|
||||
if (NOT ZLIB_FOUND)
|
||||
message(STATUS "zlib 1.2.11 or newer not found, falling back to externals")
|
||||
add_subdirectory(zlib EXCLUDE_FROM_ALL)
|
||||
set(ZLIB_LIBRARIES z)
|
||||
endif()
|
||||
|
||||
# libzip
|
||||
pkg_check_modules(LIBZIP IMPORTED_TARGET GLOBAL libzip>=1.5.3)
|
||||
if (LIBZIP_FOUND)
|
||||
add_library(zip ALIAS PkgConfig::LIBZIP)
|
||||
else()
|
||||
message(STATUS "libzip 1.5.3 or newer not found, falling back to externals")
|
||||
find_package(Libzip 1.5)
|
||||
if (NOT LIBZIP_FOUND)
|
||||
message(STATUS "libzip 1.5 or newer not found, falling back to externals")
|
||||
add_subdirectory(libzip EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
# LibreSSL
|
||||
find_package(OpenSSL COMPONENTS Crypto SSL)
|
||||
if (NOT OpenSSL_FOUND)
|
||||
message(STATUS "OpenSSL not found, falling back to externals")
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||
get_directory_property(OPENSSL_LIBRARIES
|
||||
DIRECTORY libressl
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
endif()
|
||||
set(LIBRESSL_SKIP_INSTALL ON CACHE BOOL "")
|
||||
add_subdirectory(libressl EXCLUDE_FROM_ALL)
|
||||
target_include_directories(ssl INTERFACE ./libressl/include)
|
||||
target_compile_definitions(ssl PRIVATE -DHAVE_INET_NTOP)
|
||||
get_directory_property(OPENSSL_LIBRARIES
|
||||
DIRECTORY libressl
|
||||
DEFINITION OPENSSL_LIBS)
|
||||
|
||||
# lurlparser
|
||||
add_subdirectory(lurlparser EXCLUDE_FROM_ALL)
|
||||
@@ -145,8 +90,7 @@ if (ENABLE_WEB_SERVICE)
|
||||
target_include_directories(httplib INTERFACE ./httplib)
|
||||
target_compile_definitions(httplib INTERFACE -DCPPHTTPLIB_OPENSSL_SUPPORT)
|
||||
target_link_libraries(httplib INTERFACE ${OPENSSL_LIBRARIES})
|
||||
|
||||
# JSON
|
||||
add_library(json-headers INTERFACE)
|
||||
target_include_directories(json-headers INTERFACE ./json)
|
||||
endif()
|
||||
|
||||
# Opus
|
||||
add_subdirectory(opus)
|
||||
|
||||
2
externals/Vulkan-Headers
vendored
2
externals/Vulkan-Headers
vendored
Submodule externals/Vulkan-Headers updated: 0e78ffd1dc...8188e3fbbc
1
externals/boost
vendored
1
externals/boost
vendored
Submodule externals/boost deleted from 5e8300b76a
1
externals/catch
vendored
1
externals/catch
vendored
Submodule externals/catch deleted from 15cf3caace
2
externals/cubeb
vendored
2
externals/cubeb
vendored
Submodule externals/cubeb updated: 6f2420de8f...616d773441
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: a3cd05577c...4f967387c0
49
externals/find-modules/FindCatch2.cmake
vendored
Normal file
49
externals/find-modules/FindCatch2.cmake
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_Catch2 QUIET Catch2)
|
||||
|
||||
find_path(Catch2_INCLUDE_DIR
|
||||
NAMES catch.hpp
|
||||
PATHS ${PC_Catch2_INCLUDE_DIRS} ${CONAN_CATCH2_ROOT}
|
||||
PATH_SUFFIXES catch2
|
||||
)
|
||||
|
||||
if(Catch2_INCLUDE_DIR)
|
||||
file(STRINGS "${Catch2_INCLUDE_DIR}/catch.hpp" _Catch2_version_lines
|
||||
REGEX "#define[ \t]+CATCH_VERSION_(MAJOR|MINOR|PATCH)")
|
||||
string(REGEX REPLACE ".*CATCH_VERSION_MAJOR +\([0-9]+\).*" "\\1" _Catch2_version_major "${_Catch2_version_lines}")
|
||||
string(REGEX REPLACE ".*CATCH_VERSION_MINOR +\([0-9]+\).*" "\\1" _Catch2_version_minor "${_Catch2_version_lines}")
|
||||
string(REGEX REPLACE ".*CATCH_VERSION_PATCH +\([0-9]+\).*" "\\1" _Catch2_version_patch "${_Catch2_version_lines}")
|
||||
set(Catch2_VERSION "${_Catch2_version_major}.${_Catch2_version_minor}.${_Catch2_version_patch}")
|
||||
unset(_Catch2_version_major)
|
||||
unset(_Catch2_version_minor)
|
||||
unset(_Catch2_version_patch)
|
||||
unset(_Catch2_version_lines)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Catch2
|
||||
FOUND_VAR Catch2_FOUND
|
||||
REQUIRED_VARS
|
||||
Catch2_INCLUDE_DIR
|
||||
Catch2_VERSION
|
||||
VERSION_VAR Catch2_VERSION
|
||||
)
|
||||
|
||||
if(Catch2_FOUND)
|
||||
set(Catch2_INCLUDE_DIRS ${Catch2_INCLUDE_DIR})
|
||||
set(Catch2_DEFINITIONS ${PC_Catch2_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(Catch2_FOUND AND NOT TARGET Catch2::Catch2)
|
||||
add_library(Catch2::Catch2 UNKNOWN IMPORTED)
|
||||
set_target_properties(Catch2::Catch2 PROPERTIES
|
||||
IMPORTED_LOCATION "${Catch2_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_Catch2_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${Catch2_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
Catch2_INCLUDE_DIR
|
||||
)
|
||||
43
externals/find-modules/FindLibUSB.cmake
vendored
Normal file
43
externals/find-modules/FindLibUSB.cmake
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# - Find libusb-1.0 library
|
||||
# This module defines
|
||||
# LIBUSB_INCLUDE_DIR, where to find bluetooth.h
|
||||
# LIBUSB_LIBRARIES, the libraries needed to use libusb-1.0.
|
||||
# LIBUSB_FOUND, If false, do not try to use libusb-1.0.
|
||||
#
|
||||
# Copyright (c) 2009, Michal Cihar, <michal@cihar.com>
|
||||
#
|
||||
# vim: expandtab sw=4 ts=4 sts=4:
|
||||
|
||||
if(ANDROID)
|
||||
set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
|
||||
message(STATUS "libusb-1.0 not found.")
|
||||
elseif (NOT LIBUSB_FOUND)
|
||||
pkg_check_modules (LIBUSB_PKG libusb-1.0)
|
||||
|
||||
find_path(LIBUSB_INCLUDE_DIR NAMES libusb.h
|
||||
PATHS
|
||||
${LIBUSB_PKG_INCLUDE_DIRS}
|
||||
/usr/include/libusb-1.0
|
||||
/usr/include
|
||||
/usr/local/include/libusb-1.0
|
||||
/usr/local/include
|
||||
)
|
||||
|
||||
find_library(LIBUSB_LIBRARIES NAMES usb-1.0 usb
|
||||
PATHS
|
||||
${LIBUSB_PKG_LIBRARY_DIRS}
|
||||
/usr/lib
|
||||
/usr/local/lib
|
||||
)
|
||||
|
||||
if(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||
set(LIBUSB_FOUND TRUE CACHE INTERNAL "libusb-1.0 found")
|
||||
message(STATUS "Found libusb-1.0: ${LIBUSB_INCLUDE_DIR}, ${LIBUSB_LIBRARIES}")
|
||||
else(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||
set(LIBUSB_FOUND FALSE CACHE INTERNAL "libusb-1.0 found")
|
||||
message(STATUS "libusb-1.0 not found.")
|
||||
endif(LIBUSB_INCLUDE_DIR AND LIBUSB_LIBRARIES)
|
||||
|
||||
mark_as_advanced(LIBUSB_INCLUDE_DIR LIBUSB_LIBRARIES)
|
||||
endif ()
|
||||
|
||||
72
externals/find-modules/FindLibzip.cmake
vendored
Normal file
72
externals/find-modules/FindLibzip.cmake
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_LIBZIP QUIET libzip)
|
||||
|
||||
find_path(LIBZIP_INCLUDE_DIR
|
||||
NAMES zip.h
|
||||
PATHS ${PC_LIBZIP_INCLUDE_DIRS}
|
||||
"$ENV{LIB_DIR}/include"
|
||||
"$ENV{INCLUDE}"
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
)
|
||||
find_path(LIBZIP_INCLUDE_DIR_ZIPCONF
|
||||
NAMES zipconf.h
|
||||
HINTS ${PC_LIBZIP_INCLUDE_DIRS}
|
||||
"$ENV{LIB_DIR}/include"
|
||||
"$ENV{LIB_DIR}/lib/libzip/include"
|
||||
"$ENV{LIB}/lib/libzip/include"
|
||||
/usr/local/lib/libzip/include
|
||||
/usr/lib/libzip/include
|
||||
/usr/local/include
|
||||
/usr/include
|
||||
"$ENV{INCLUDE}"
|
||||
)
|
||||
find_library(LIBZIP_LIBRARY
|
||||
NAMES zip
|
||||
PATHS ${PC_LIBZIP_LIBRARY_DIRS}
|
||||
"$ENV{LIB_DIR}/lib" "$ENV{LIB}" /usr/local/lib /usr/lib
|
||||
)
|
||||
|
||||
if (LIBZIP_INCLUDE_DIR_ZIPCONF)
|
||||
FILE(READ "${LIBZIP_INCLUDE_DIR_ZIPCONF}/zipconf.h" _LIBZIP_VERSION_CONTENTS)
|
||||
if (_LIBZIP_VERSION_CONTENTS)
|
||||
STRING(REGEX REPLACE ".*#define LIBZIP_VERSION \"([0-9.]+)\".*" "\\1" LIBZIP_VERSION "${_LIBZIP_VERSION_CONTENTS}")
|
||||
endif()
|
||||
unset(_LIBZIP_VERSION_CONTENTS)
|
||||
endif()
|
||||
|
||||
set(LIBZIP_VERSION ${LIBZIP_VERSION} CACHE STRING "Version number of libzip")
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(Libzip
|
||||
FOUND_VAR LIBZIP_FOUND
|
||||
REQUIRED_VARS
|
||||
LIBZIP_LIBRARY
|
||||
LIBZIP_INCLUDE_DIR
|
||||
LIBZIP_INCLUDE_DIR_ZIPCONF
|
||||
LIBZIP_VERSION
|
||||
VERSION_VAR LIBZIP_VERSION
|
||||
)
|
||||
|
||||
if(LIBZIP_FOUND)
|
||||
set(LIBZIP_LIBRARIES ${LIBZIP_LIBRARY})
|
||||
set(LIBZIP_INCLUDE_DIRS ${LIBZIP_INCLUDE_DIR})
|
||||
set(LIBZIP_DEFINITIONS ${PC_LIBZIP_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(LIBZIP_FOUND AND NOT TARGET libzip::libzip)
|
||||
add_library(libzip::libzip UNKNOWN IMPORTED)
|
||||
set_target_properties(libzip::libzip PROPERTIES
|
||||
IMPORTED_LOCATION "${LIBZIP_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_LIBZIP_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LIBZIP_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
LIBZIP_INCLUDE_DIR
|
||||
LIBZIP_INCLUDE_DIR_ZIPCONF
|
||||
LIBZIP_LIBRARY
|
||||
LIBZIP_VERSION
|
||||
)
|
||||
69
externals/find-modules/Findfmt.cmake
vendored
Normal file
69
externals/find-modules/Findfmt.cmake
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_fmt QUIET fmt)
|
||||
|
||||
find_path(fmt_INCLUDE_DIR
|
||||
NAMES format.h
|
||||
PATHS ${PC_fmt_INCLUDE_DIRS} ${CONAN_INCLUDE_DIRS_fmt}
|
||||
PATH_SUFFIXES fmt
|
||||
)
|
||||
|
||||
find_library(fmt_LIBRARY
|
||||
NAMES fmt
|
||||
PATHS ${PC_fmt_LIBRARY_DIRS} ${CONAN_LIB_DIRS_fmt}
|
||||
)
|
||||
|
||||
if(fmt_INCLUDE_DIR)
|
||||
set(_fmt_version_file "${fmt_INCLUDE_DIR}/core.h")
|
||||
if(NOT EXISTS "${_fmt_version_file}")
|
||||
set(_fmt_version_file "${fmt_INCLUDE_DIR}/format.h")
|
||||
endif()
|
||||
if(EXISTS "${_fmt_version_file}")
|
||||
# parse "#define FMT_VERSION 60200" to 6.2.0
|
||||
file(STRINGS "${_fmt_version_file}" fmt_VERSION_LINE
|
||||
REGEX "^#define[ \t]+FMT_VERSION[ \t]+[0-9]+$")
|
||||
string(REGEX REPLACE "^#define[ \t]+FMT_VERSION[ \t]+([0-9]+)$"
|
||||
"\\1" fmt_VERSION "${fmt_VERSION_LINE}")
|
||||
foreach(ver "fmt_VERSION_PATCH" "fmt_VERSION_MINOR" "fmt_VERSION_MAJOR")
|
||||
math(EXPR ${ver} "${fmt_VERSION} % 100")
|
||||
math(EXPR fmt_VERSION "(${fmt_VERSION} - ${${ver}}) / 100")
|
||||
endforeach()
|
||||
set(fmt_VERSION
|
||||
"${fmt_VERSION_MAJOR}.${fmt_VERSION_MINOR}.${fmt_VERSION_PATCH}")
|
||||
endif()
|
||||
unset(_fmt_version_file)
|
||||
unset(fmt_VERSION_LINE)
|
||||
unset(fmt_VERSION_MAJOR)
|
||||
unset(fmt_VERSION_MINOR)
|
||||
unset(fmt_VERSION_PATCH)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(fmt
|
||||
FOUND_VAR fmt_FOUND
|
||||
REQUIRED_VARS
|
||||
fmt_LIBRARY
|
||||
fmt_INCLUDE_DIR
|
||||
fmt_VERSION
|
||||
VERSION_VAR fmt_VERSION
|
||||
)
|
||||
|
||||
if(fmt_FOUND)
|
||||
set(fmt_LIBRARIES ${fmt_LIBRARY})
|
||||
set(fmt_INCLUDE_DIRS ${fmt_INCLUDE_DIR})
|
||||
set(fmt_DEFINITIONS ${PC_fmt_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(fmt_FOUND AND NOT TARGET fmt::fmt)
|
||||
add_library(fmt::fmt UNKNOWN IMPORTED)
|
||||
set_target_properties(fmt::fmt PROPERTIES
|
||||
IMPORTED_LOCATION "${fmt_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_fmt_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${fmt_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
fmt_INCLUDE_DIR
|
||||
fmt_LIBRARY
|
||||
)
|
||||
54
externals/find-modules/Findlz4.cmake
vendored
Normal file
54
externals/find-modules/Findlz4.cmake
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_lz4 QUIET lz4)
|
||||
|
||||
find_path(lz4_INCLUDE_DIR
|
||||
NAMES lz4.h
|
||||
PATHS ${PC_lz4_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(lz4_LIBRARY
|
||||
NAMES lz4
|
||||
PATHS ${PC_lz4_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(lz4_INCLUDE_DIR)
|
||||
file(STRINGS "${lz4_INCLUDE_DIR}/lz4.h" _lz4_version_lines
|
||||
REGEX "#define[ \t]+LZ4_VERSION_(MAJOR|MINOR|RELEASE)")
|
||||
string(REGEX REPLACE ".*LZ4_VERSION_MAJOR *\([0-9]*\).*" "\\1" _lz4_version_major "${_lz4_version_lines}")
|
||||
string(REGEX REPLACE ".*LZ4_VERSION_MINOR *\([0-9]*\).*" "\\1" _lz4_version_minor "${_lz4_version_lines}")
|
||||
string(REGEX REPLACE ".*LZ4_VERSION_RELEASE *\([0-9]*\).*" "\\1" _lz4_version_release "${_lz4_version_lines}")
|
||||
set(lz4_VERSION "${_lz4_version_major}.${_lz4_version_minor}.${_lz4_version_release}")
|
||||
unset(_lz4_version_major)
|
||||
unset(_lz4_version_minor)
|
||||
unset(_lz4_version_release)
|
||||
unset(_lz4_version_lines)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(lz4
|
||||
FOUND_VAR lz4_FOUND
|
||||
REQUIRED_VARS
|
||||
lz4_LIBRARY
|
||||
lz4_INCLUDE_DIR
|
||||
VERSION_VAR lz4_VERSION
|
||||
)
|
||||
|
||||
if(lz4_FOUND)
|
||||
set(lz4_LIBRARIES ${lz4_LIBRARY})
|
||||
set(lz4_INCLUDE_DIRS ${lz4_INCLUDE_DIR})
|
||||
set(lz4_DEFINITIONS ${PC_lz4_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(lz4_FOUND AND NOT TARGET lz4::lz4)
|
||||
add_library(lz4::lz4 UNKNOWN IMPORTED)
|
||||
set_target_properties(lz4::lz4 PROPERTIES
|
||||
IMPORTED_LOCATION "${lz4_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_lz4_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${lz4_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
lz4_INCLUDE_DIR
|
||||
lz4_LIBRARY
|
||||
)
|
||||
49
externals/find-modules/Findnlohmann_json.cmake
vendored
Normal file
49
externals/find-modules/Findnlohmann_json.cmake
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_nlohmann_json QUIET nlohmann_json)
|
||||
|
||||
find_path(nlohmann_json_INCLUDE_DIR
|
||||
NAMES json.hpp
|
||||
PATHS ${PC_nlohmann_json_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES nlohmann
|
||||
)
|
||||
|
||||
if(nlohmann_json_INCLUDE_DIR)
|
||||
file(STRINGS "${nlohmann_json_INCLUDE_DIR}/json.hpp" _nlohmann_json_version_lines
|
||||
REGEX "#define[ \t]+NLOHMANN_JSON_VERSION_(MAJOR|MINOR|PATCH)")
|
||||
string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_MAJOR +\([0-9]+\).*" "\\1" _nlohmann_json_version_major "${_nlohmann_json_version_lines}")
|
||||
string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_MINOR +\([0-9]+\).*" "\\1" _nlohmann_json_version_minor "${_nlohmann_json_version_lines}")
|
||||
string(REGEX REPLACE ".*NLOHMANN_JSON_VERSION_PATCH +\([0-9]+\).*" "\\1" _nlohmann_json_version_patch "${_nlohmann_json_version_lines}")
|
||||
set(nlohmann_json_VERSION "${_nlohmann_json_version_major}.${_nlohmann_json_version_minor}.${_nlohmann_json_version_patch}")
|
||||
unset(_nlohmann_json_version_major)
|
||||
unset(_nlohmann_json_version_minor)
|
||||
unset(_nlohmann_json_version_patch)
|
||||
unset(_nlohmann_json_version_lines)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(nlohmann_json
|
||||
FOUND_VAR nlohmann_json_FOUND
|
||||
REQUIRED_VARS
|
||||
nlohmann_json_INCLUDE_DIR
|
||||
nlohmann_json_VERSION
|
||||
VERSION_VAR nlohmann_json_VERSION
|
||||
)
|
||||
|
||||
if(nlohmann_json_FOUND)
|
||||
set(nlohmann_json_INCLUDE_DIRS ${nlohmann_json_INCLUDE_DIR})
|
||||
set(nlohmann_json_DEFINITIONS ${PC_nlohmann_json_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(nlohmann_json_FOUND AND NOT TARGET nlohmann_json::nlohmann_json)
|
||||
add_library(nlohmann_json::nlohmann_json UNKNOWN IMPORTED)
|
||||
set_target_properties(nlohmann_json::nlohmann_json PROPERTIES
|
||||
IMPORTED_LOCATION "${nlohmann_json_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_nlohmann_json_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${nlohmann_json_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
nlohmann_json_INCLUDE_DIR
|
||||
)
|
||||
42
externals/find-modules/Findopus.cmake
vendored
Normal file
42
externals/find-modules/Findopus.cmake
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_opus QUIET opus)
|
||||
|
||||
find_path(opus_INCLUDE_DIR
|
||||
NAMES opus.h
|
||||
PATHS ${PC_opus_INCLUDE_DIRS}
|
||||
PATH_SUFFIXES opus
|
||||
)
|
||||
find_library(opus_LIBRARY
|
||||
NAMES opus
|
||||
PATHS ${PC_opus_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(opus
|
||||
FOUND_VAR opus_FOUND
|
||||
REQUIRED_VARS
|
||||
opus_LIBRARY
|
||||
opus_INCLUDE_DIR
|
||||
VERSION_VAR opus_VERSION
|
||||
)
|
||||
|
||||
if(opus_FOUND)
|
||||
set(Opus_LIBRARIES ${opus_LIBRARY})
|
||||
set(Opus_INCLUDE_DIRS ${opus_INCLUDE_DIR})
|
||||
set(Opus_DEFINITIONS ${PC_opus_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(opus_FOUND AND NOT TARGET Opus::Opus)
|
||||
add_library(Opus::Opus UNKNOWN IMPORTED)
|
||||
set_target_properties(Opus::Opus PROPERTIES
|
||||
IMPORTED_LOCATION "${opus_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_opus_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${opus_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
opus_INCLUDE_DIR
|
||||
opus_LIBRARY
|
||||
)
|
||||
55
externals/find-modules/Findzstd.cmake
vendored
Normal file
55
externals/find-modules/Findzstd.cmake
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
|
||||
find_package(PkgConfig QUIET)
|
||||
pkg_check_modules(PC_zstd QUIET libzstd)
|
||||
|
||||
find_path(zstd_INCLUDE_DIR
|
||||
NAMES zstd.h
|
||||
PATHS ${PC_zstd_INCLUDE_DIRS}
|
||||
)
|
||||
find_library(zstd_LIBRARY
|
||||
NAMES zstd
|
||||
PATHS ${PC_zstd_LIBRARY_DIRS}
|
||||
)
|
||||
|
||||
if(zstd_INCLUDE_DIR)
|
||||
file(STRINGS "${zstd_INCLUDE_DIR}/zstd.h" _zstd_version_lines
|
||||
REGEX "#define[ \t]+ZSTD_VERSION_(MAJOR|MINOR|RELEASE)")
|
||||
string(REGEX REPLACE ".*ZSTD_VERSION_MAJOR *\([0-9]*\).*" "\\1" _zstd_version_major "${_zstd_version_lines}")
|
||||
string(REGEX REPLACE ".*ZSTD_VERSION_MINOR *\([0-9]*\).*" "\\1" _zstd_version_minor "${_zstd_version_lines}")
|
||||
string(REGEX REPLACE ".*ZSTD_VERSION_RELEASE *\([0-9]*\).*" "\\1" _zstd_version_release "${_zstd_version_lines}")
|
||||
set(zstd_VERSION "${_zstd_version_major}.${_zstd_version_minor}.${_zstd_version_release}")
|
||||
unset(_zstd_version_major)
|
||||
unset(_zstd_version_minor)
|
||||
unset(_zstd_version_release)
|
||||
unset(_zstd_version_lines)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(zstd
|
||||
FOUND_VAR zstd_FOUND
|
||||
REQUIRED_VARS
|
||||
zstd_LIBRARY
|
||||
zstd_INCLUDE_DIR
|
||||
zstd_VERSION
|
||||
VERSION_VAR zstd_VERSION
|
||||
)
|
||||
|
||||
if(zstd_FOUND)
|
||||
set(zstd_LIBRARIES ${zstd_LIBRARY})
|
||||
set(zstd_INCLUDE_DIRS ${zstd_INCLUDE_DIR})
|
||||
set(zstd_DEFINITIONS ${PC_zstd_CFLAGS_OTHER})
|
||||
endif()
|
||||
|
||||
if(zstd_FOUND AND NOT TARGET zstd::zstd)
|
||||
add_library(zstd::zstd UNKNOWN IMPORTED)
|
||||
set_target_properties(zstd::zstd PROPERTIES
|
||||
IMPORTED_LOCATION "${zstd_LIBRARY}"
|
||||
INTERFACE_COMPILE_OPTIONS "${PC_zstd_CFLAGS_OTHER}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${zstd_INCLUDE_DIR}"
|
||||
)
|
||||
endif()
|
||||
|
||||
mark_as_advanced(
|
||||
zstd_INCLUDE_DIR
|
||||
zstd_LIBRARY
|
||||
)
|
||||
1
externals/fmt
vendored
1
externals/fmt
vendored
Submodule externals/fmt deleted from 9bdd1596ce
9
externals/json/README.md
vendored
9
externals/json/README.md
vendored
@@ -1,9 +0,0 @@
|
||||
JSON for Modern C++
|
||||
===================
|
||||
|
||||
v3.1.2
|
||||
|
||||
This is a mirror providing the single required header file.
|
||||
|
||||
The original repository can be found at:
|
||||
https://github.com/nlohmann/json/commit/d2dd27dc3b8472dbaa7d66f83619b3ebcd9185fe
|
||||
17300
externals/json/json.hpp
vendored
17300
externals/json/json.hpp
vendored
File diff suppressed because it is too large
Load Diff
1
externals/libusb
vendored
Submodule
1
externals/libusb
vendored
Submodule
Submodule externals/libusb added at 3406d72cda
1
externals/lz4
vendored
1
externals/lz4
vendored
Submodule externals/lz4 deleted from 4db65c1d99
2
externals/sirit
vendored
2
externals/sirit
vendored
Submodule externals/sirit updated: 414fc4dbd2...eefca56afd
1
externals/xbyak
vendored
Submodule
1
externals/xbyak
vendored
Submodule
Submodule externals/xbyak added at 82b70e6659
81
externals/zlib/CMakeLists.txt
vendored
81
externals/zlib/CMakeLists.txt
vendored
@@ -1,81 +0,0 @@
|
||||
project(zlib C)
|
||||
|
||||
include(CheckTypeSize)
|
||||
include(CheckFunctionExists)
|
||||
include(CheckIncludeFile)
|
||||
|
||||
check_include_file(sys/types.h HAVE_SYS_TYPES_H)
|
||||
check_include_file(stdint.h HAVE_STDINT_H)
|
||||
check_include_file(stddef.h HAVE_STDDEF_H)
|
||||
|
||||
# Check to see if we have large file support
|
||||
set(CMAKE_REQUIRED_DEFINITIONS -D_LARGEFILE64_SOURCE=1)
|
||||
# We add these other definitions here because CheckTypeSize.cmake
|
||||
# in CMake 2.4.x does not automatically do so and we want
|
||||
# compatibility with CMake 2.4.x.
|
||||
if(HAVE_SYS_TYPES_H)
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_SYS_TYPES_H)
|
||||
endif()
|
||||
if(HAVE_STDINT_H)
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDINT_H)
|
||||
endif()
|
||||
if(HAVE_STDDEF_H)
|
||||
list(APPEND CMAKE_REQUIRED_DEFINITIONS -DHAVE_STDDEF_H)
|
||||
endif()
|
||||
check_type_size(off64_t OFF64_T)
|
||||
if(HAVE_OFF64_T)
|
||||
add_definitions(-D_LARGEFILE64_SOURCE=1)
|
||||
endif()
|
||||
set(CMAKE_REQUIRED_DEFINITIONS) # clear variable
|
||||
|
||||
# Check for fseeko
|
||||
check_function_exists(fseeko HAVE_FSEEKO)
|
||||
if(NOT HAVE_FSEEKO)
|
||||
add_definitions(-DNO_FSEEKO)
|
||||
endif()
|
||||
|
||||
# Check for unistd.h
|
||||
check_include_file(unistd.h HAVE_UNISTD_H)
|
||||
if(HAVE_UNISTD_H)
|
||||
add_definitions(-DHAVE_UNISTD_H)
|
||||
endif()
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-D_CRT_SECURE_NO_DEPRECATE)
|
||||
add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE)
|
||||
endif()
|
||||
|
||||
add_library(z STATIC
|
||||
zlib/adler32.c
|
||||
zlib/compress.c
|
||||
zlib/crc32.c
|
||||
zlib/crc32.h
|
||||
zlib/deflate.c
|
||||
zlib/deflate.h
|
||||
zlib/gzclose.c
|
||||
zlib/gzguts.h
|
||||
zlib/gzlib.c
|
||||
zlib/gzread.c
|
||||
zlib/gzwrite.c
|
||||
zlib/inffast.h
|
||||
zlib/inffixed.h
|
||||
zlib/inflate.c
|
||||
zlib/inflate.h
|
||||
zlib/infback.c
|
||||
zlib/inftrees.c
|
||||
zlib/inftrees.h
|
||||
zlib/inffast.c
|
||||
zlib/trees.c
|
||||
zlib/trees.h
|
||||
zlib/uncompr.c
|
||||
zlib/zconf.h
|
||||
zlib/zlib.h
|
||||
zlib/zutil.c
|
||||
zlib/zutil.h
|
||||
)
|
||||
add_library(ZLIB::ZLIB ALIAS z)
|
||||
|
||||
target_include_directories(z
|
||||
PUBLIC
|
||||
zlib/
|
||||
)
|
||||
1
externals/zlib/zlib
vendored
1
externals/zlib/zlib
vendored
Submodule externals/zlib/zlib deleted from cacf7f1d4e
1
externals/zstd
vendored
1
externals/zstd
vendored
Submodule externals/zstd deleted from 470344d33e
@@ -62,6 +62,10 @@ else()
|
||||
-Wno-unused-parameter
|
||||
)
|
||||
|
||||
if (ARCHITECTURE_x86_64)
|
||||
add_compile_options("-mcx16")
|
||||
endif()
|
||||
|
||||
if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
|
||||
add_compile_options("-stdlib=libc++")
|
||||
endif()
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace AudioCore {
|
||||
|
||||
constexpr u32 STREAM_SAMPLE_RATE{48000};
|
||||
constexpr u32 STREAM_NUM_CHANNELS{2};
|
||||
|
||||
using VoiceChannelHolder = std::array<VoiceResourceInformation*, 6>;
|
||||
class AudioRenderer::VoiceState {
|
||||
public:
|
||||
bool IsPlaying() const {
|
||||
@@ -37,9 +37,10 @@ public:
|
||||
}
|
||||
|
||||
void SetWaveIndex(std::size_t index);
|
||||
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory);
|
||||
std::vector<s16> DequeueSamples(std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources);
|
||||
void UpdateState();
|
||||
void RefreshBuffer(Core::Memory::Memory& memory);
|
||||
void RefreshBuffer(Core::Memory::Memory& memory, const VoiceChannelHolder& voice_resources);
|
||||
|
||||
private:
|
||||
bool is_in_use{};
|
||||
@@ -79,7 +80,7 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event,
|
||||
std::size_t instance_number)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
effects(params.effect_count), memory{memory_} {
|
||||
voice_resources(params.voice_count), effects(params.effect_count), memory{memory_} {
|
||||
behavior_info.SetUserRevision(params.revision);
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
|
||||
@@ -127,6 +128,12 @@ ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<
|
||||
input_params.data() + sizeof(UpdateDataHeader) + config.behavior_size,
|
||||
memory_pool_count * sizeof(MemoryPoolInfo));
|
||||
|
||||
// Copy voice resources
|
||||
const std::size_t voice_resource_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size};
|
||||
std::memcpy(voice_resources.data(), input_params.data() + voice_resource_offset,
|
||||
sizeof(VoiceResourceInformation) * voice_resources.size());
|
||||
|
||||
// Copy VoiceInfo structs
|
||||
std::size_t voice_offset{sizeof(UpdateDataHeader) + config.behavior_size +
|
||||
config.memory_pools_size + config.voice_resource_size};
|
||||
@@ -173,11 +180,12 @@ ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<
|
||||
|
||||
// Copy output header
|
||||
UpdateDataHeader response_data{worker_params};
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
response_data.frame_count = 0x10;
|
||||
response_data.total_size += 0x10;
|
||||
response_data.render_info = sizeof(RendererInfo);
|
||||
response_data.total_size += sizeof(RendererInfo);
|
||||
}
|
||||
|
||||
std::vector<u8> output_params(response_data.total_size);
|
||||
std::memcpy(output_params.data(), &response_data, sizeof(UpdateDataHeader));
|
||||
|
||||
// Copy output memory pool entries
|
||||
@@ -212,6 +220,17 @@ ResultVal<std::vector<u8>> AudioRenderer::UpdateAudioRenderer(const std::vector<
|
||||
return Audren::ERR_INVALID_PARAMETERS;
|
||||
}
|
||||
|
||||
if (behavior_info.IsElapsedFrameCountSupported()) {
|
||||
const std::size_t renderer_info_offset{
|
||||
sizeof(UpdateDataHeader) + response_data.memory_pools_size + response_data.voices_size +
|
||||
response_data.effects_size + response_data.sinks_size +
|
||||
response_data.performance_manager_size + response_data.behavior_size};
|
||||
RendererInfo renderer_info{};
|
||||
renderer_info.elasped_frame_count = elapsed_frame_count;
|
||||
std::memcpy(output_params.data() + renderer_info_offset, &renderer_info,
|
||||
sizeof(RendererInfo));
|
||||
}
|
||||
|
||||
return MakeResult(output_params);
|
||||
}
|
||||
|
||||
@@ -220,14 +239,15 @@ void AudioRenderer::VoiceState::SetWaveIndex(std::size_t index) {
|
||||
is_refresh_pending = true;
|
||||
}
|
||||
|
||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(std::size_t sample_count,
|
||||
Core::Memory::Memory& memory) {
|
||||
std::vector<s16> AudioRenderer::VoiceState::DequeueSamples(
|
||||
std::size_t sample_count, Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
if (!IsPlaying()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is_refresh_pending) {
|
||||
RefreshBuffer(memory);
|
||||
RefreshBuffer(memory, voice_resources);
|
||||
}
|
||||
|
||||
const std::size_t max_size{samples.size() - offset};
|
||||
@@ -271,7 +291,8 @@ void AudioRenderer::VoiceState::UpdateState() {
|
||||
is_in_use = info.is_in_use;
|
||||
}
|
||||
|
||||
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory) {
|
||||
void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory,
|
||||
const VoiceChannelHolder& voice_resources) {
|
||||
const auto wave_buffer_address = info.wave_buffer[wave_index].buffer_addr;
|
||||
const auto wave_buffer_size = info.wave_buffer[wave_index].buffer_sz;
|
||||
std::vector<s16> new_samples(wave_buffer_size / sizeof(s16));
|
||||
@@ -296,17 +317,77 @@ void AudioRenderer::VoiceState::RefreshBuffer(Core::Memory::Memory& memory) {
|
||||
}
|
||||
|
||||
switch (info.channel_count) {
|
||||
case 1:
|
||||
case 1: {
|
||||
// 1 channel is upsampled to 2 channel
|
||||
samples.resize(new_samples.size() * 2);
|
||||
|
||||
for (std::size_t index = 0; index < new_samples.size(); ++index) {
|
||||
samples[index * 2] = new_samples[index];
|
||||
samples[index * 2 + 1] = new_samples[index];
|
||||
auto sample = static_cast<float>(new_samples[index]);
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
samples[index * 2] = static_cast<s16>(sample * info.volume);
|
||||
samples[index * 2 + 1] = static_cast<s16>(sample * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 2: {
|
||||
// 2 channel is played as is
|
||||
samples = std::move(new_samples);
|
||||
const std::size_t sample_count = (samples.size() / 2);
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
const std::size_t index_l = index * 2;
|
||||
const std::size_t index_r = index * 2 + 1;
|
||||
|
||||
auto sample_l = static_cast<float>(samples[index_l]);
|
||||
auto sample_r = static_cast<float>(samples[index_r]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
sample_l *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
|
||||
if (voice_resources[1]->in_use) {
|
||||
sample_r *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
|
||||
samples[index_l] = static_cast<s16>(sample_l * info.volume);
|
||||
samples[index_r] = static_cast<s16>(sample_r * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 6: {
|
||||
samples.resize((new_samples.size() / 6) * 2);
|
||||
const std::size_t sample_count = samples.size() / 2;
|
||||
|
||||
for (std::size_t index = 0; index < sample_count; ++index) {
|
||||
auto FL = static_cast<float>(new_samples[index * 6]);
|
||||
auto FR = static_cast<float>(new_samples[index * 6 + 1]);
|
||||
auto FC = static_cast<float>(new_samples[index * 6 + 2]);
|
||||
auto BL = static_cast<float>(new_samples[index * 6 + 4]);
|
||||
auto BR = static_cast<float>(new_samples[index * 6 + 5]);
|
||||
|
||||
if (voice_resources[0]->in_use) {
|
||||
FL *= voice_resources[0]->mix_volumes[0];
|
||||
}
|
||||
if (voice_resources[1]->in_use) {
|
||||
FR *= voice_resources[1]->mix_volumes[1];
|
||||
}
|
||||
if (voice_resources[2]->in_use) {
|
||||
FC *= voice_resources[2]->mix_volumes[2];
|
||||
}
|
||||
if (voice_resources[4]->in_use) {
|
||||
BL *= voice_resources[4]->mix_volumes[4];
|
||||
}
|
||||
if (voice_resources[5]->in_use) {
|
||||
BR *= voice_resources[5]->mix_volumes[5];
|
||||
}
|
||||
|
||||
samples[index * 2] =
|
||||
static_cast<s16>((0.3694f * FL + 0.2612f * FC + 0.3694f * BL) * info.volume);
|
||||
samples[index * 2 + 1] =
|
||||
static_cast<s16>((0.3694f * FR + 0.2612f * FC + 0.3694f * BR) * info.volume);
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
@@ -352,11 +433,17 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
if (!voice.IsPlaying()) {
|
||||
continue;
|
||||
}
|
||||
VoiceChannelHolder resources{};
|
||||
for (u32 channel = 0; channel < voice.GetInfo().channel_count; channel++) {
|
||||
const auto channel_resource_id = voice.GetInfo().voice_channel_resource_ids[channel];
|
||||
resources[channel] = &voice_resources[channel_resource_id];
|
||||
}
|
||||
|
||||
std::size_t offset{};
|
||||
s64 samples_remaining{BUFFER_SIZE};
|
||||
while (samples_remaining > 0) {
|
||||
const std::vector<s16> samples{voice.DequeueSamples(samples_remaining, memory)};
|
||||
const std::vector<s16> samples{
|
||||
voice.DequeueSamples(samples_remaining, memory, resources)};
|
||||
|
||||
if (samples.empty()) {
|
||||
break;
|
||||
@@ -372,6 +459,7 @@ void AudioRenderer::QueueMixedBuffer(Buffer::Tag tag) {
|
||||
}
|
||||
}
|
||||
audio_out->QueueBuffer(stream, tag, std::move(buffer));
|
||||
elapsed_frame_count++;
|
||||
}
|
||||
|
||||
void AudioRenderer::ReleaseAndQueueBuffers() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/behavior_info.h"
|
||||
#include "audio_core/common.h"
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -116,6 +117,14 @@ struct WaveBuffer {
|
||||
};
|
||||
static_assert(sizeof(WaveBuffer) == 0x38, "WaveBuffer has wrong size");
|
||||
|
||||
struct VoiceResourceInformation {
|
||||
s32_le id{};
|
||||
std::array<float_le, MAX_MIX_BUFFERS> mix_volumes{};
|
||||
bool in_use{};
|
||||
INSERT_PADDING_BYTES(11);
|
||||
};
|
||||
static_assert(sizeof(VoiceResourceInformation) == 0x70, "VoiceResourceInformation has wrong size");
|
||||
|
||||
struct VoiceInfo {
|
||||
u32_le id;
|
||||
u32_le node_id;
|
||||
@@ -187,6 +196,12 @@ struct EffectOutStatus {
|
||||
};
|
||||
static_assert(sizeof(EffectOutStatus) == 0x10, "EffectOutStatus is an invalid size");
|
||||
|
||||
struct RendererInfo {
|
||||
u64_le elasped_frame_count{};
|
||||
INSERT_PADDING_WORDS(2);
|
||||
};
|
||||
static_assert(sizeof(RendererInfo) == 0x10, "RendererInfo is an invalid size");
|
||||
|
||||
struct UpdateDataHeader {
|
||||
UpdateDataHeader() {}
|
||||
|
||||
@@ -200,7 +215,7 @@ struct UpdateDataHeader {
|
||||
mixes_size = 0x0;
|
||||
sinks_size = config.sink_count * 0x20;
|
||||
performance_manager_size = 0x10;
|
||||
frame_count = 0;
|
||||
render_info = 0;
|
||||
total_size = sizeof(UpdateDataHeader) + behavior_size + memory_pools_size + voices_size +
|
||||
effects_size + sinks_size + performance_manager_size;
|
||||
}
|
||||
@@ -214,8 +229,8 @@ struct UpdateDataHeader {
|
||||
u32_le mixes_size{};
|
||||
u32_le sinks_size{};
|
||||
u32_le performance_manager_size{};
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le frame_count{};
|
||||
u32_le splitter_size{};
|
||||
u32_le render_info{};
|
||||
INSERT_PADDING_WORDS(4);
|
||||
u32_le total_size{};
|
||||
};
|
||||
@@ -244,10 +259,12 @@ private:
|
||||
AudioRendererParameter worker_params;
|
||||
std::shared_ptr<Kernel::WritableEvent> buffer_event;
|
||||
std::vector<VoiceState> voices;
|
||||
std::vector<VoiceResourceInformation> voice_resources;
|
||||
std::vector<EffectState> effects;
|
||||
std::unique_ptr<AudioOut> audio_out;
|
||||
StreamPtr stream;
|
||||
Core::Memory::Memory& memory;
|
||||
std::size_t elapsed_frame_count{};
|
||||
};
|
||||
|
||||
} // namespace AudioCore
|
||||
|
||||
@@ -14,6 +14,7 @@ constexpr ResultCode ERR_INVALID_PARAMETERS{ErrorModule::Audio, 41};
|
||||
}
|
||||
|
||||
constexpr u32_le CURRENT_PROCESS_REVISION = Common::MakeMagic('R', 'E', 'V', '8');
|
||||
constexpr std::size_t MAX_MIX_BUFFERS = 24;
|
||||
|
||||
static constexpr u32 VersionFromRevision(u32_le rev) {
|
||||
// "REV7" -> 7
|
||||
|
||||
@@ -193,7 +193,7 @@ long CubebSinkStream::DataCallback(cubeb_stream* stream, void* user_data, const
|
||||
const std::size_t samples_to_write = num_channels * num_frames;
|
||||
std::size_t samples_written;
|
||||
|
||||
if (Settings::values.enable_audio_stretching) {
|
||||
if (Settings::values.enable_audio_stretching.GetValue()) {
|
||||
const std::vector<s16> in{impl->queue.Pop()};
|
||||
const std::size_t num_in{in.size() / num_channels};
|
||||
s16* const out{reinterpret_cast<s16*>(buffer)};
|
||||
|
||||
@@ -38,7 +38,7 @@ Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format fo
|
||||
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
|
||||
|
||||
release_event = Core::Timing::CreateEvent(
|
||||
name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(); });
|
||||
name, [this](u64 userdata, s64 cycles_late) { ReleaseActiveBuffer(cycles_late); });
|
||||
}
|
||||
|
||||
void Stream::Play() {
|
||||
@@ -59,15 +59,15 @@ Stream::State Stream::GetState() const {
|
||||
return state;
|
||||
}
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
s64 Stream::GetBufferReleaseNS(const Buffer& buffer) const {
|
||||
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
|
||||
const auto us =
|
||||
std::chrono::microseconds((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
return Core::Timing::usToCycles(us);
|
||||
const auto ns =
|
||||
std::chrono::nanoseconds((static_cast<u64>(num_samples) * 1000000000ULL) / sample_rate);
|
||||
return ns.count();
|
||||
}
|
||||
|
||||
static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
|
||||
const float volume{std::clamp(Settings::values.volume - (1.0f - game_volume), 0.0f, 1.0f)};
|
||||
const float volume{std::clamp(Settings::Volume() - (1.0f - game_volume), 0.0f, 1.0f)};
|
||||
|
||||
if (volume == 1.0f) {
|
||||
return;
|
||||
@@ -80,7 +80,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples, float game_volume) {
|
||||
}
|
||||
}
|
||||
|
||||
void Stream::PlayNextBuffer() {
|
||||
void Stream::PlayNextBuffer(s64 cycles_late) {
|
||||
if (!IsPlaying()) {
|
||||
// Ensure we are in playing state before playing the next buffer
|
||||
sink_stream.Flush();
|
||||
@@ -105,14 +105,17 @@ void Stream::PlayNextBuffer() {
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
core_timing.ScheduleEvent(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
core_timing.ScheduleEvent(
|
||||
GetBufferReleaseNS(*active_buffer) -
|
||||
(Settings::values.enable_audio_stretching.GetValue() ? 0 : cycles_late),
|
||||
release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
void Stream::ReleaseActiveBuffer(s64 cycles_late) {
|
||||
ASSERT(active_buffer);
|
||||
released_buffers.push(std::move(active_buffer));
|
||||
release_callback();
|
||||
PlayNextBuffer();
|
||||
PlayNextBuffer(cycles_late);
|
||||
}
|
||||
|
||||
bool Stream::QueueBuffer(BufferPtr&& buffer) {
|
||||
|
||||
@@ -90,13 +90,16 @@ public:
|
||||
|
||||
private:
|
||||
/// Plays the next queued buffer in the audio stream, starting playback if necessary
|
||||
void PlayNextBuffer();
|
||||
void PlayNextBuffer(s64 cycles_late = 0);
|
||||
|
||||
/// Releases the actively playing buffer, signalling that it has been completed
|
||||
void ReleaseActiveBuffer();
|
||||
void ReleaseActiveBuffer(s64 cycles_late = 0);
|
||||
|
||||
/// Gets the number of core cycles when the specified buffer will be released
|
||||
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
|
||||
s64 GetBufferReleaseNS(const Buffer& buffer) const;
|
||||
|
||||
/// Gets the number of core cycles when the specified buffer will be released
|
||||
s64 GetBufferReleaseNSHostTiming(const Buffer& buffer) const;
|
||||
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
Format format; ///< Format of the stream
|
||||
|
||||
@@ -32,6 +32,8 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
DEPENDS
|
||||
# WARNING! It was too much work to try and make a common location for this list,
|
||||
# so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_arb_decompiler.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
|
||||
@@ -96,6 +98,8 @@ add_library(common STATIC
|
||||
algorithm.h
|
||||
alignment.h
|
||||
assert.h
|
||||
atomic_ops.cpp
|
||||
atomic_ops.h
|
||||
detached_tasks.cpp
|
||||
detached_tasks.h
|
||||
bit_field.h
|
||||
@@ -108,6 +112,8 @@ add_library(common STATIC
|
||||
common_types.h
|
||||
dynamic_library.cpp
|
||||
dynamic_library.h
|
||||
fiber.cpp
|
||||
fiber.h
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
hash.h
|
||||
@@ -123,6 +129,8 @@ add_library(common STATIC
|
||||
lz4_compression.cpp
|
||||
lz4_compression.h
|
||||
math_util.h
|
||||
memory_detect.cpp
|
||||
memory_detect.h
|
||||
memory_hook.cpp
|
||||
memory_hook.h
|
||||
microprofile.cpp
|
||||
@@ -139,6 +147,8 @@ add_library(common STATIC
|
||||
scm_rev.cpp
|
||||
scm_rev.h
|
||||
scope_exit.h
|
||||
spin_lock.cpp
|
||||
spin_lock.h
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
swap.h
|
||||
@@ -148,6 +158,8 @@ add_library(common STATIC
|
||||
thread.h
|
||||
thread_queue_list.h
|
||||
threadsafe_queue.h
|
||||
time_zone.cpp
|
||||
time_zone.h
|
||||
timer.cpp
|
||||
timer.h
|
||||
uint128.cpp
|
||||
@@ -157,6 +169,8 @@ add_library(common STATIC
|
||||
vector_math.h
|
||||
virtual_buffer.cpp
|
||||
virtual_buffer.h
|
||||
wall_clock.cpp
|
||||
wall_clock.h
|
||||
web_result.h
|
||||
zstd_compression.cpp
|
||||
zstd_compression.h
|
||||
@@ -167,10 +181,15 @@ if(ARCHITECTURE_x86_64)
|
||||
PRIVATE
|
||||
x64/cpu_detect.cpp
|
||||
x64/cpu_detect.h
|
||||
x64/native_clock.cpp
|
||||
x64/native_clock.h
|
||||
x64/xbyak_abi.h
|
||||
x64/xbyak_util.h
|
||||
)
|
||||
endif()
|
||||
|
||||
create_target_directory_groups(common)
|
||||
find_package(Boost 1.71 COMPONENTS context headers REQUIRED)
|
||||
|
||||
target_link_libraries(common PUBLIC Boost::boost fmt microprofile)
|
||||
target_link_libraries(common PRIVATE lz4_static libzstd_static)
|
||||
target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile)
|
||||
target_link_libraries(common PRIVATE lz4::lz4 zstd::zstd xbyak)
|
||||
|
||||
70
src/common/atomic_ops.cpp
Normal file
70
src/common/atomic_ops.cpp
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include "common/atomic_ops.h"
|
||||
|
||||
#if _MSC_VER
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
#if _MSC_VER
|
||||
|
||||
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
|
||||
u8 result = _InterlockedCompareExchange8((char*)pointer, value, expected);
|
||||
return result == expected;
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
|
||||
u16 result = _InterlockedCompareExchange16((short*)pointer, value, expected);
|
||||
return result == expected;
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
|
||||
u32 result = _InterlockedCompareExchange((long*)pointer, value, expected);
|
||||
return result == expected;
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
|
||||
u64 result = _InterlockedCompareExchange64((__int64*)pointer, value, expected);
|
||||
return result == expected;
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
|
||||
return _InterlockedCompareExchange128((__int64*)pointer, value[1], value[0],
|
||||
(__int64*)expected.data()) != 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected) {
|
||||
return __sync_bool_compare_and_swap(pointer, expected, value);
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected) {
|
||||
return __sync_bool_compare_and_swap(pointer, expected, value);
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected) {
|
||||
return __sync_bool_compare_and_swap(pointer, expected, value);
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected) {
|
||||
return __sync_bool_compare_and_swap(pointer, expected, value);
|
||||
}
|
||||
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected) {
|
||||
unsigned __int128 value_a;
|
||||
unsigned __int128 expected_a;
|
||||
std::memcpy(&value_a, value.data(), sizeof(u128));
|
||||
std::memcpy(&expected_a, expected.data(), sizeof(u128));
|
||||
return __sync_bool_compare_and_swap((unsigned __int128*)pointer, expected_a, value_a);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
17
src/common/atomic_ops.h
Normal file
17
src/common/atomic_ops.h
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
bool AtomicCompareAndSwap(u8 volatile* pointer, u8 value, u8 expected);
|
||||
bool AtomicCompareAndSwap(u16 volatile* pointer, u16 value, u16 expected);
|
||||
bool AtomicCompareAndSwap(u32 volatile* pointer, u32 value, u32 expected);
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u64 value, u64 expected);
|
||||
bool AtomicCompareAndSwap(u64 volatile* pointer, u128 value, u128 expected);
|
||||
|
||||
} // namespace Common
|
||||
222
src/common/fiber.cpp
Normal file
222
src/common/fiber.cpp
Normal file
@@ -0,0 +1,222 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/fiber.h"
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <boost/context/detail/fcontext.hpp>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
constexpr std::size_t default_stack_size = 256 * 1024; // 256kb
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
|
||||
struct Fiber::FiberImpl {
|
||||
LPVOID handle = nullptr;
|
||||
LPVOID rewind_handle = nullptr;
|
||||
};
|
||||
|
||||
void Fiber::Start() {
|
||||
ASSERT(previous_fiber != nullptr);
|
||||
previous_fiber->guard.unlock();
|
||||
previous_fiber.reset();
|
||||
entry_point(start_parameter);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Fiber::OnRewind() {
|
||||
ASSERT(impl->handle != nullptr);
|
||||
DeleteFiber(impl->handle);
|
||||
impl->handle = impl->rewind_handle;
|
||||
impl->rewind_handle = nullptr;
|
||||
rewind_point(rewind_parameter);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Fiber::FiberStartFunc(void* fiber_parameter) {
|
||||
auto fiber = static_cast<Fiber*>(fiber_parameter);
|
||||
fiber->Start();
|
||||
}
|
||||
|
||||
void Fiber::RewindStartFunc(void* fiber_parameter) {
|
||||
auto fiber = static_cast<Fiber*>(fiber_parameter);
|
||||
fiber->OnRewind();
|
||||
}
|
||||
|
||||
Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
|
||||
: entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
|
||||
impl = std::make_unique<FiberImpl>();
|
||||
impl->handle = CreateFiber(default_stack_size, &FiberStartFunc, this);
|
||||
}
|
||||
|
||||
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
|
||||
|
||||
Fiber::~Fiber() {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
// Make sure the Fiber is not being used
|
||||
const bool locked = guard.try_lock();
|
||||
ASSERT_MSG(locked, "Destroying a fiber that's still running");
|
||||
if (locked) {
|
||||
guard.unlock();
|
||||
}
|
||||
DeleteFiber(impl->handle);
|
||||
}
|
||||
|
||||
void Fiber::Exit() {
|
||||
ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
|
||||
if (!is_thread_fiber) {
|
||||
return;
|
||||
}
|
||||
ConvertFiberToThread();
|
||||
guard.unlock();
|
||||
released = true;
|
||||
}
|
||||
|
||||
void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
|
||||
rewind_point = std::move(rewind_func);
|
||||
rewind_parameter = start_parameter;
|
||||
}
|
||||
|
||||
void Fiber::Rewind() {
|
||||
ASSERT(rewind_point);
|
||||
ASSERT(impl->rewind_handle == nullptr);
|
||||
impl->rewind_handle = CreateFiber(default_stack_size, &RewindStartFunc, this);
|
||||
SwitchToFiber(impl->rewind_handle);
|
||||
}
|
||||
|
||||
void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
|
||||
ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
|
||||
ASSERT_MSG(to != nullptr, "Next fiber is null!");
|
||||
to->guard.lock();
|
||||
to->previous_fiber = from;
|
||||
SwitchToFiber(to->impl->handle);
|
||||
ASSERT(from->previous_fiber != nullptr);
|
||||
from->previous_fiber->guard.unlock();
|
||||
from->previous_fiber.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
|
||||
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
|
||||
fiber->guard.lock();
|
||||
fiber->impl->handle = ConvertThreadToFiber(nullptr);
|
||||
fiber->is_thread_fiber = true;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
struct Fiber::FiberImpl {
|
||||
alignas(64) std::array<u8, default_stack_size> stack;
|
||||
alignas(64) std::array<u8, default_stack_size> rewind_stack;
|
||||
u8* stack_limit;
|
||||
u8* rewind_stack_limit;
|
||||
boost::context::detail::fcontext_t context;
|
||||
boost::context::detail::fcontext_t rewind_context;
|
||||
};
|
||||
|
||||
void Fiber::Start(boost::context::detail::transfer_t& transfer) {
|
||||
ASSERT(previous_fiber != nullptr);
|
||||
previous_fiber->impl->context = transfer.fctx;
|
||||
previous_fiber->guard.unlock();
|
||||
previous_fiber.reset();
|
||||
entry_point(start_parameter);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Fiber::OnRewind([[maybe_unused]] boost::context::detail::transfer_t& transfer) {
|
||||
ASSERT(impl->context != nullptr);
|
||||
impl->context = impl->rewind_context;
|
||||
impl->rewind_context = nullptr;
|
||||
u8* tmp = impl->stack_limit;
|
||||
impl->stack_limit = impl->rewind_stack_limit;
|
||||
impl->rewind_stack_limit = tmp;
|
||||
rewind_point(rewind_parameter);
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
void Fiber::FiberStartFunc(boost::context::detail::transfer_t transfer) {
|
||||
auto fiber = static_cast<Fiber*>(transfer.data);
|
||||
fiber->Start(transfer);
|
||||
}
|
||||
|
||||
void Fiber::RewindStartFunc(boost::context::detail::transfer_t transfer) {
|
||||
auto fiber = static_cast<Fiber*>(transfer.data);
|
||||
fiber->OnRewind(transfer);
|
||||
}
|
||||
|
||||
Fiber::Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter)
|
||||
: entry_point{std::move(entry_point_func)}, start_parameter{start_parameter} {
|
||||
impl = std::make_unique<FiberImpl>();
|
||||
impl->stack_limit = impl->stack.data();
|
||||
impl->rewind_stack_limit = impl->rewind_stack.data();
|
||||
u8* stack_base = impl->stack_limit + default_stack_size;
|
||||
impl->context =
|
||||
boost::context::detail::make_fcontext(stack_base, impl->stack.size(), FiberStartFunc);
|
||||
}
|
||||
|
||||
void Fiber::SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter) {
|
||||
rewind_point = std::move(rewind_func);
|
||||
rewind_parameter = start_parameter;
|
||||
}
|
||||
|
||||
Fiber::Fiber() : impl{std::make_unique<FiberImpl>()} {}
|
||||
|
||||
Fiber::~Fiber() {
|
||||
if (released) {
|
||||
return;
|
||||
}
|
||||
// Make sure the Fiber is not being used
|
||||
const bool locked = guard.try_lock();
|
||||
ASSERT_MSG(locked, "Destroying a fiber that's still running");
|
||||
if (locked) {
|
||||
guard.unlock();
|
||||
}
|
||||
}
|
||||
|
||||
void Fiber::Exit() {
|
||||
|
||||
ASSERT_MSG(is_thread_fiber, "Exitting non main thread fiber");
|
||||
if (!is_thread_fiber) {
|
||||
return;
|
||||
}
|
||||
guard.unlock();
|
||||
released = true;
|
||||
}
|
||||
|
||||
void Fiber::Rewind() {
|
||||
ASSERT(rewind_point);
|
||||
ASSERT(impl->rewind_context == nullptr);
|
||||
u8* stack_base = impl->rewind_stack_limit + default_stack_size;
|
||||
impl->rewind_context =
|
||||
boost::context::detail::make_fcontext(stack_base, impl->stack.size(), RewindStartFunc);
|
||||
boost::context::detail::jump_fcontext(impl->rewind_context, this);
|
||||
}
|
||||
|
||||
void Fiber::YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to) {
|
||||
ASSERT_MSG(from != nullptr, "Yielding fiber is null!");
|
||||
ASSERT_MSG(to != nullptr, "Next fiber is null!");
|
||||
to->guard.lock();
|
||||
to->previous_fiber = from;
|
||||
auto transfer = boost::context::detail::jump_fcontext(to->impl->context, to.get());
|
||||
ASSERT(from->previous_fiber != nullptr);
|
||||
from->previous_fiber->impl->context = transfer.fctx;
|
||||
from->previous_fiber->guard.unlock();
|
||||
from->previous_fiber.reset();
|
||||
}
|
||||
|
||||
std::shared_ptr<Fiber> Fiber::ThreadToFiber() {
|
||||
std::shared_ptr<Fiber> fiber = std::shared_ptr<Fiber>{new Fiber()};
|
||||
fiber->guard.lock();
|
||||
fiber->is_thread_fiber = true;
|
||||
return fiber;
|
||||
}
|
||||
|
||||
#endif
|
||||
} // namespace Common
|
||||
92
src/common/fiber.h
Normal file
92
src/common/fiber.h
Normal file
@@ -0,0 +1,92 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/spin_lock.h"
|
||||
|
||||
#if !defined(_WIN32) && !defined(WIN32)
|
||||
namespace boost::context::detail {
|
||||
struct transfer_t;
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* Fiber class
|
||||
* a fiber is a userspace thread with it's own context. They can be used to
|
||||
* implement coroutines, emulated threading systems and certain asynchronous
|
||||
* patterns.
|
||||
*
|
||||
* This class implements fibers at a low level, thus allowing greater freedom
|
||||
* to implement such patterns. This fiber class is 'threadsafe' only one fiber
|
||||
* can be running at a time and threads will be locked while trying to yield to
|
||||
* a running fiber until it yields. WARNING exchanging two running fibers between
|
||||
* threads will cause a deadlock. In order to prevent a deadlock, each thread should
|
||||
* have an intermediary fiber, you switch to the intermediary fiber of the current
|
||||
* thread and then from it switch to the expected fiber. This way you can exchange
|
||||
* 2 fibers within 2 different threads.
|
||||
*/
|
||||
class Fiber {
|
||||
public:
|
||||
Fiber(std::function<void(void*)>&& entry_point_func, void* start_parameter);
|
||||
~Fiber();
|
||||
|
||||
Fiber(const Fiber&) = delete;
|
||||
Fiber& operator=(const Fiber&) = delete;
|
||||
|
||||
Fiber(Fiber&&) = default;
|
||||
Fiber& operator=(Fiber&&) = default;
|
||||
|
||||
/// Yields control from Fiber 'from' to Fiber 'to'
|
||||
/// Fiber 'from' must be the currently running fiber.
|
||||
static void YieldTo(std::shared_ptr<Fiber>& from, std::shared_ptr<Fiber>& to);
|
||||
static std::shared_ptr<Fiber> ThreadToFiber();
|
||||
|
||||
void SetRewindPoint(std::function<void(void*)>&& rewind_func, void* start_parameter);
|
||||
|
||||
void Rewind();
|
||||
|
||||
/// Only call from main thread's fiber
|
||||
void Exit();
|
||||
|
||||
/// Changes the start parameter of the fiber. Has no effect if the fiber already started
|
||||
void SetStartParameter(void* new_parameter) {
|
||||
start_parameter = new_parameter;
|
||||
}
|
||||
|
||||
private:
|
||||
Fiber();
|
||||
|
||||
#if defined(_WIN32) || defined(WIN32)
|
||||
void OnRewind();
|
||||
void Start();
|
||||
static void FiberStartFunc(void* fiber_parameter);
|
||||
static void RewindStartFunc(void* fiber_parameter);
|
||||
#else
|
||||
void OnRewind(boost::context::detail::transfer_t& transfer);
|
||||
void Start(boost::context::detail::transfer_t& transfer);
|
||||
static void FiberStartFunc(boost::context::detail::transfer_t transfer);
|
||||
static void RewindStartFunc(boost::context::detail::transfer_t transfer);
|
||||
#endif
|
||||
|
||||
struct FiberImpl;
|
||||
|
||||
SpinLock guard{};
|
||||
std::function<void(void*)> entry_point;
|
||||
std::function<void(void*)> rewind_point;
|
||||
void* rewind_parameter{};
|
||||
void* start_parameter{};
|
||||
std::shared_ptr<Fiber> previous_fiber;
|
||||
std::unique_ptr<FiberImpl> impl;
|
||||
bool is_thread_fiber{};
|
||||
bool released{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
73
src/common/memory_detect.cpp
Normal file
73
src/common/memory_detect.cpp
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef _WIN32
|
||||
// clang-format off
|
||||
#include <windows.h>
|
||||
#include <sysinfoapi.h>
|
||||
// clang-format on
|
||||
#else
|
||||
#include <sys/types.h>
|
||||
#if defined(__APPLE__) || defined(__FreeBSD__)
|
||||
#include <sys/sysctl.h>
|
||||
#elif defined(__linux__)
|
||||
#include <sys/sysinfo.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include "common/memory_detect.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
// Detects the RAM and Swapfile sizes
|
||||
static MemoryInfo Detect() {
|
||||
MemoryInfo mem_info{};
|
||||
|
||||
#ifdef _WIN32
|
||||
MEMORYSTATUSEX memorystatus;
|
||||
memorystatus.dwLength = sizeof(memorystatus);
|
||||
GlobalMemoryStatusEx(&memorystatus);
|
||||
mem_info.TotalPhysicalMemory = memorystatus.ullTotalPhys;
|
||||
mem_info.TotalSwapMemory = memorystatus.ullTotalPageFile - mem_info.TotalPhysicalMemory;
|
||||
#elif defined(__APPLE__)
|
||||
u64 ramsize;
|
||||
struct xsw_usage vmusage;
|
||||
std::size_t sizeof_ramsize = sizeof(ramsize);
|
||||
std::size_t sizeof_vmusage = sizeof(vmusage);
|
||||
// hw and vm are defined in sysctl.h
|
||||
// https://github.com/apple/darwin-xnu/blob/master/bsd/sys/sysctl.h#L471
|
||||
// sysctlbyname(const char *, void *, size_t *, void *, size_t);
|
||||
sysctlbyname("hw.memsize", &ramsize, &sizeof_ramsize, nullptr, 0);
|
||||
sysctlbyname("vm.swapusage", &vmusage, &sizeof_vmusage, nullptr, 0);
|
||||
mem_info.TotalPhysicalMemory = ramsize;
|
||||
mem_info.TotalSwapMemory = vmusage.xsu_total;
|
||||
#elif defined(__FreeBSD__)
|
||||
u_long physmem, swap_total;
|
||||
std::size_t sizeof_u_long = sizeof(u_long);
|
||||
// sysctlbyname(const char *, void *, size_t *, const void *, size_t);
|
||||
sysctlbyname("hw.physmem", &physmem, &sizeof_u_long, nullptr, 0);
|
||||
sysctlbyname("vm.swap_total", &swap_total, &sizeof_u_long, nullptr, 0);
|
||||
mem_info.TotalPhysicalMemory = physmem;
|
||||
mem_info.TotalSwapMemory = swap_total;
|
||||
#elif defined(__linux__)
|
||||
struct sysinfo meminfo;
|
||||
sysinfo(&meminfo);
|
||||
mem_info.TotalPhysicalMemory = meminfo.totalram;
|
||||
mem_info.TotalSwapMemory = meminfo.totalswap;
|
||||
#else
|
||||
mem_info.TotalPhysicalMemory = sysconf(_SC_PHYS_PAGES) * sysconf(_SC_PAGE_SIZE);
|
||||
mem_info.TotalSwapMemory = 0;
|
||||
#endif
|
||||
|
||||
return mem_info;
|
||||
}
|
||||
|
||||
const MemoryInfo& GetMemInfo() {
|
||||
static MemoryInfo mem_info = Detect();
|
||||
return mem_info;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
22
src/common/memory_detect.h
Normal file
22
src/common/memory_detect.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
struct MemoryInfo {
|
||||
u64 TotalPhysicalMemory{};
|
||||
u64 TotalSwapMemory{};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the memory info of the host system
|
||||
* @return Reference to a MemoryInfo struct with the physical and swap memory sizes in bytes
|
||||
*/
|
||||
const MemoryInfo& GetMemInfo();
|
||||
|
||||
} // namespace Common
|
||||
54
src/common/spin_lock.cpp
Normal file
54
src/common/spin_lock.cpp
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/spin_lock.h"
|
||||
|
||||
#if _MSC_VER
|
||||
#include <intrin.h>
|
||||
#if _M_AMD64
|
||||
#define __x86_64__ 1
|
||||
#endif
|
||||
#if _M_ARM64
|
||||
#define __aarch64__ 1
|
||||
#endif
|
||||
#else
|
||||
#if __x86_64__
|
||||
#include <xmmintrin.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
void ThreadPause() {
|
||||
#if __x86_64__
|
||||
_mm_pause();
|
||||
#elif __aarch64__ && _MSC_VER
|
||||
__yield();
|
||||
#elif __aarch64__
|
||||
asm("yield");
|
||||
#endif
|
||||
}
|
||||
|
||||
} // Anonymous namespace
|
||||
|
||||
namespace Common {
|
||||
|
||||
void SpinLock::lock() {
|
||||
while (lck.test_and_set(std::memory_order_acquire)) {
|
||||
ThreadPause();
|
||||
}
|
||||
}
|
||||
|
||||
void SpinLock::unlock() {
|
||||
lck.clear(std::memory_order_release);
|
||||
}
|
||||
|
||||
bool SpinLock::try_lock() {
|
||||
if (lck.test_and_set(std::memory_order_acquire)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
26
src/common/spin_lock.h
Normal file
26
src/common/spin_lock.h
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace Common {
|
||||
|
||||
/**
|
||||
* SpinLock class
|
||||
* a lock similar to mutex that forces a thread to spin wait instead calling the
|
||||
* supervisor. Should be used on short sequences of code.
|
||||
*/
|
||||
class SpinLock {
|
||||
public:
|
||||
void lock();
|
||||
void unlock();
|
||||
bool try_lock();
|
||||
|
||||
private:
|
||||
std::atomic_flag lck = ATOMIC_FLAG_INIT;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -60,6 +60,7 @@ void AppendCPUInfo(FieldCollection& fc) {
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AES", Common::GetCPUCaps().aes);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX", Common::GetCPUCaps().avx);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX2", Common::GetCPUCaps().avx2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_AVX512", Common::GetCPUCaps().avx512);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI1", Common::GetCPUCaps().bmi1);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_BMI2", Common::GetCPUCaps().bmi2);
|
||||
fc.AddField(FieldType::UserSystem, "CPU_Extension_x64_FMA", Common::GetCPUCaps().fma);
|
||||
|
||||
@@ -25,6 +25,52 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
auto handle = GetCurrentThread();
|
||||
int windows_priority = 0;
|
||||
switch (new_priority) {
|
||||
case ThreadPriority::Low:
|
||||
windows_priority = THREAD_PRIORITY_BELOW_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::Normal:
|
||||
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::High:
|
||||
windows_priority = THREAD_PRIORITY_ABOVE_NORMAL;
|
||||
break;
|
||||
case ThreadPriority::VeryHigh:
|
||||
windows_priority = THREAD_PRIORITY_HIGHEST;
|
||||
break;
|
||||
default:
|
||||
windows_priority = THREAD_PRIORITY_NORMAL;
|
||||
break;
|
||||
}
|
||||
SetThreadPriority(handle, windows_priority);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority) {
|
||||
pthread_t this_thread = pthread_self();
|
||||
|
||||
s32 max_prio = sched_get_priority_max(SCHED_OTHER);
|
||||
s32 min_prio = sched_get_priority_min(SCHED_OTHER);
|
||||
u32 level = static_cast<u32>(new_priority) + 1;
|
||||
|
||||
struct sched_param params;
|
||||
if (max_prio > min_prio) {
|
||||
params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4;
|
||||
} else {
|
||||
params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4;
|
||||
}
|
||||
|
||||
pthread_setschedparam(this_thread, SCHED_OTHER, ¶ms);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
// Sets the debugger-visible name of the current thread.
|
||||
@@ -70,6 +116,12 @@ void SetCurrentThreadName(const char* name) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32)
|
||||
void SetCurrentThreadName(const char* name) {
|
||||
// Do Nothing on MingW
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -28,8 +29,7 @@ public:
|
||||
is_set = false;
|
||||
}
|
||||
|
||||
template <class Duration>
|
||||
bool WaitFor(const std::chrono::duration<Duration>& time) {
|
||||
bool WaitFor(const std::chrono::nanoseconds& time) {
|
||||
std::unique_lock lk{mutex};
|
||||
if (!condvar.wait_for(lk, time, [this] { return is_set; }))
|
||||
return false;
|
||||
@@ -86,6 +86,15 @@ private:
|
||||
std::size_t generation = 0; // Incremented once each time the barrier is used
|
||||
};
|
||||
|
||||
enum class ThreadPriority : u32 {
|
||||
Low = 0,
|
||||
Normal = 1,
|
||||
High = 2,
|
||||
VeryHigh = 3,
|
||||
};
|
||||
|
||||
void SetCurrentThreadPriority(ThreadPriority new_priority);
|
||||
|
||||
void SetCurrentThreadName(const char* name);
|
||||
|
||||
} // namespace Common
|
||||
|
||||
49
src/common/time_zone.cpp
Normal file
49
src/common/time_zone.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <sstream>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/time_zone.h"
|
||||
|
||||
namespace Common::TimeZone {
|
||||
|
||||
std::string GetDefaultTimeZone() {
|
||||
return "GMT";
|
||||
}
|
||||
|
||||
static std::string GetOsTimeZoneOffset() {
|
||||
const std::time_t t{std::time(nullptr)};
|
||||
const std::tm tm{*std::localtime(&t)};
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::put_time(&tm, "%z"); // Get the current timezone offset, e.g. "-400", as a string
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) {
|
||||
try {
|
||||
return std::stoi(timezone);
|
||||
} catch (const std::invalid_argument&) {
|
||||
LOG_CRITICAL(Common, "invalid_argument with {}!", timezone);
|
||||
return 0;
|
||||
} catch (const std::out_of_range&) {
|
||||
LOG_CRITICAL(Common, "out_of_range with {}!", timezone);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::seconds GetCurrentOffsetSeconds() {
|
||||
const int offset{ConvertOsTimeZoneOffsetToInt(GetOsTimeZoneOffset())};
|
||||
|
||||
int seconds{(offset / 100) * 60 * 60}; // Convert hour component to seconds
|
||||
seconds += (offset % 100) * 60; // Convert minute component to seconds
|
||||
|
||||
return std::chrono::seconds{seconds};
|
||||
}
|
||||
|
||||
} // namespace Common::TimeZone
|
||||
18
src/common/time_zone.h
Normal file
18
src/common/time_zone.h
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <string>
|
||||
|
||||
namespace Common::TimeZone {
|
||||
|
||||
/// Gets the default timezone, i.e. "GMT"
|
||||
std::string GetDefaultTimeZone();
|
||||
|
||||
/// Gets the offset of the current timezone (from the default), in seconds
|
||||
std::chrono::seconds GetCurrentOffsetSeconds();
|
||||
|
||||
} // namespace Common::TimeZone
|
||||
@@ -6,12 +6,38 @@
|
||||
#include <intrin.h>
|
||||
|
||||
#pragma intrinsic(_umul128)
|
||||
#pragma intrinsic(_udiv128)
|
||||
#endif
|
||||
#include <cstring>
|
||||
#include "common/uint128.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
#ifdef _MSC_VER
|
||||
|
||||
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
|
||||
u128 r{};
|
||||
r[0] = _umul128(a, b, &r[1]);
|
||||
u64 remainder;
|
||||
#if _MSC_VER < 1923
|
||||
return udiv128(r[1], r[0], d, &remainder);
|
||||
#else
|
||||
return _udiv128(r[1], r[0], d, &remainder);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d) {
|
||||
const u64 diva = a / d;
|
||||
const u64 moda = a % d;
|
||||
const u64 divb = b / d;
|
||||
const u64 modb = b % d;
|
||||
return diva * b + moda * divb + moda * modb / d;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
u128 Multiply64Into128(u64 a, u64 b) {
|
||||
u128 result;
|
||||
#ifdef _MSC_VER
|
||||
|
||||
@@ -9,6 +9,9 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
// This function multiplies 2 u64 values and divides it by a u64 value.
|
||||
u64 MultiplyAndDivide64(u64 a, u64 b, u64 d);
|
||||
|
||||
// This function multiplies 2 u64 values and produces a u128 value;
|
||||
u128 Multiply64Into128(u64 a, u64 b);
|
||||
|
||||
|
||||
@@ -40,6 +40,11 @@ struct UUID {
|
||||
uuid = INVALID_UUID;
|
||||
}
|
||||
|
||||
// TODO(ogniK): Properly generate a Nintendo ID
|
||||
constexpr u64 GetNintendoID() const {
|
||||
return uuid[0];
|
||||
}
|
||||
|
||||
std::string Format() const;
|
||||
std::string FormatSwitch() const;
|
||||
};
|
||||
|
||||
91
src/common/wall_clock.cpp
Normal file
91
src/common/wall_clock.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/uint128.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "common/x64/cpu_detect.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
using base_timer = std::chrono::steady_clock;
|
||||
using base_time_point = std::chrono::time_point<base_timer>;
|
||||
|
||||
class StandardWallClock : public WallClock {
|
||||
public:
|
||||
StandardWallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency)
|
||||
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, false) {
|
||||
start_time = base_timer::now();
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed);
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::microseconds>(elapsed);
|
||||
}
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override {
|
||||
base_time_point current = base_timer::now();
|
||||
auto elapsed = current - start_time;
|
||||
return std::chrono::duration_cast<std::chrono::milliseconds>(elapsed);
|
||||
}
|
||||
|
||||
u64 GetClockCycles() override {
|
||||
std::chrono::nanoseconds time_now = GetTimeNS();
|
||||
const u128 temporary =
|
||||
Common::Multiply64Into128(time_now.count(), emulated_clock_frequency);
|
||||
return Common::Divide128On32(temporary, 1000000000).first;
|
||||
}
|
||||
|
||||
u64 GetCPUCycles() override {
|
||||
std::chrono::nanoseconds time_now = GetTimeNS();
|
||||
const u128 temporary = Common::Multiply64Into128(time_now.count(), emulated_cpu_frequency);
|
||||
return Common::Divide128On32(temporary, 1000000000).first;
|
||||
}
|
||||
|
||||
void Pause(bool is_paused) override {
|
||||
// Do nothing in this clock type.
|
||||
}
|
||||
|
||||
private:
|
||||
base_time_point start_time;
|
||||
};
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
|
||||
u32 emulated_clock_frequency) {
|
||||
const auto& caps = GetCPUCaps();
|
||||
u64 rtsc_frequency = 0;
|
||||
if (caps.invariant_tsc) {
|
||||
rtsc_frequency = EstimateRDTSCFrequency();
|
||||
}
|
||||
if (rtsc_frequency == 0) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency,
|
||||
emulated_clock_frequency);
|
||||
} else {
|
||||
return std::make_unique<X64::NativeClock>(emulated_cpu_frequency, emulated_clock_frequency,
|
||||
rtsc_frequency);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
|
||||
u32 emulated_clock_frequency) {
|
||||
return std::make_unique<StandardWallClock>(emulated_cpu_frequency, emulated_clock_frequency);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Common
|
||||
53
src/common/wall_clock.h
Normal file
53
src/common/wall_clock.h
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
class WallClock {
|
||||
public:
|
||||
/// Returns current wall time in nanoseconds
|
||||
virtual std::chrono::nanoseconds GetTimeNS() = 0;
|
||||
|
||||
/// Returns current wall time in microseconds
|
||||
virtual std::chrono::microseconds GetTimeUS() = 0;
|
||||
|
||||
/// Returns current wall time in milliseconds
|
||||
virtual std::chrono::milliseconds GetTimeMS() = 0;
|
||||
|
||||
/// Returns current wall time in emulated clock cycles
|
||||
virtual u64 GetClockCycles() = 0;
|
||||
|
||||
/// Returns current wall time in emulated cpu cycles
|
||||
virtual u64 GetCPUCycles() = 0;
|
||||
|
||||
virtual void Pause(bool is_paused) = 0;
|
||||
|
||||
/// Tells if the wall clock, uses the host CPU's hardware clock
|
||||
bool IsNative() const {
|
||||
return is_native;
|
||||
}
|
||||
|
||||
protected:
|
||||
WallClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, bool is_native)
|
||||
: emulated_cpu_frequency{emulated_cpu_frequency},
|
||||
emulated_clock_frequency{emulated_clock_frequency}, is_native{is_native} {}
|
||||
|
||||
u64 emulated_cpu_frequency;
|
||||
u64 emulated_clock_frequency;
|
||||
|
||||
private:
|
||||
bool is_native;
|
||||
};
|
||||
|
||||
std::unique_ptr<WallClock> CreateBestMatchingClock(u32 emulated_cpu_frequency,
|
||||
u32 emulated_clock_frequency);
|
||||
|
||||
} // namespace Common
|
||||
@@ -62,6 +62,17 @@ static CPUCaps Detect() {
|
||||
std::memcpy(&caps.brand_string[0], &cpu_id[1], sizeof(int));
|
||||
std::memcpy(&caps.brand_string[4], &cpu_id[3], sizeof(int));
|
||||
std::memcpy(&caps.brand_string[8], &cpu_id[2], sizeof(int));
|
||||
if (cpu_id[1] == 0x756e6547 && cpu_id[2] == 0x6c65746e && cpu_id[3] == 0x49656e69)
|
||||
caps.manufacturer = Manufacturer::Intel;
|
||||
else if (cpu_id[1] == 0x68747541 && cpu_id[2] == 0x444d4163 && cpu_id[3] == 0x69746e65)
|
||||
caps.manufacturer = Manufacturer::AMD;
|
||||
else if (cpu_id[1] == 0x6f677948 && cpu_id[2] == 0x656e6975 && cpu_id[3] == 0x6e65476e)
|
||||
caps.manufacturer = Manufacturer::Hygon;
|
||||
else
|
||||
caps.manufacturer = Manufacturer::Unknown;
|
||||
|
||||
u32 family = {};
|
||||
u32 model = {};
|
||||
|
||||
__cpuid(cpu_id, 0x80000000);
|
||||
|
||||
@@ -73,6 +84,14 @@ static CPUCaps Detect() {
|
||||
// Detect family and other miscellaneous features
|
||||
if (max_std_fn >= 1) {
|
||||
__cpuid(cpu_id, 0x00000001);
|
||||
family = (cpu_id[0] >> 8) & 0xf;
|
||||
model = (cpu_id[0] >> 4) & 0xf;
|
||||
if (family == 0xf) {
|
||||
family += (cpu_id[0] >> 20) & 0xff;
|
||||
}
|
||||
if (family >= 6) {
|
||||
model += ((cpu_id[0] >> 16) & 0xf) << 4;
|
||||
}
|
||||
|
||||
if ((cpu_id[3] >> 25) & 1)
|
||||
caps.sse = true;
|
||||
@@ -110,6 +129,11 @@ static CPUCaps Detect() {
|
||||
caps.bmi1 = true;
|
||||
if ((cpu_id[1] >> 8) & 1)
|
||||
caps.bmi2 = true;
|
||||
// Checks for AVX512F, AVX512CD, AVX512VL, AVX512DQ, AVX512BW (Intel Skylake-X/SP)
|
||||
if ((cpu_id[1] >> 16) & 1 && (cpu_id[1] >> 28) & 1 && (cpu_id[1] >> 31) & 1 &&
|
||||
(cpu_id[1] >> 17) & 1 && (cpu_id[1] >> 30) & 1) {
|
||||
caps.avx512 = caps.avx2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +154,20 @@ static CPUCaps Detect() {
|
||||
caps.fma4 = true;
|
||||
}
|
||||
|
||||
if (max_ex_fn >= 0x80000007) {
|
||||
__cpuid(cpu_id, 0x80000007);
|
||||
if (cpu_id[3] & (1 << 8)) {
|
||||
caps.invariant_tsc = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (max_std_fn >= 0x16) {
|
||||
__cpuid(cpu_id, 0x16);
|
||||
caps.base_frequency = cpu_id[0];
|
||||
caps.max_frequency = cpu_id[1];
|
||||
caps.bus_frequency = cpu_id[2];
|
||||
}
|
||||
|
||||
return caps;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,16 @@
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class Manufacturer : u32 {
|
||||
Intel = 0,
|
||||
AMD = 1,
|
||||
Hygon = 2,
|
||||
Unknown = 3,
|
||||
};
|
||||
|
||||
/// x86/x64 CPU capabilities that may be detected by this module
|
||||
struct CPUCaps {
|
||||
Manufacturer manufacturer;
|
||||
char cpu_string[0x21];
|
||||
char brand_string[0x41];
|
||||
bool sse;
|
||||
@@ -19,11 +27,16 @@ struct CPUCaps {
|
||||
bool lzcnt;
|
||||
bool avx;
|
||||
bool avx2;
|
||||
bool avx512;
|
||||
bool bmi1;
|
||||
bool bmi2;
|
||||
bool fma;
|
||||
bool fma4;
|
||||
bool aes;
|
||||
bool invariant_tsc;
|
||||
u32 base_frequency;
|
||||
u32 max_frequency;
|
||||
u32 bus_frequency;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
103
src/common/x64/native_clock.cpp
Normal file
103
src/common/x64/native_clock.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <x86intrin.h>
|
||||
#endif
|
||||
|
||||
#include "common/uint128.h"
|
||||
#include "common/x64/native_clock.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
u64 EstimateRDTSCFrequency() {
|
||||
const auto milli_10 = std::chrono::milliseconds{10};
|
||||
// get current time
|
||||
_mm_mfence();
|
||||
const u64 tscStart = __rdtsc();
|
||||
const auto startTime = std::chrono::high_resolution_clock::now();
|
||||
// wait roughly 3 seconds
|
||||
while (true) {
|
||||
auto milli = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::high_resolution_clock::now() - startTime);
|
||||
if (milli.count() >= 3000)
|
||||
break;
|
||||
std::this_thread::sleep_for(milli_10);
|
||||
}
|
||||
const auto endTime = std::chrono::high_resolution_clock::now();
|
||||
_mm_mfence();
|
||||
const u64 tscEnd = __rdtsc();
|
||||
// calculate difference
|
||||
const u64 timer_diff =
|
||||
std::chrono::duration_cast<std::chrono::nanoseconds>(endTime - startTime).count();
|
||||
const u64 tsc_diff = tscEnd - tscStart;
|
||||
const u64 tsc_freq = MultiplyAndDivide64(tsc_diff, 1000000000ULL, timer_diff);
|
||||
return tsc_freq;
|
||||
}
|
||||
|
||||
namespace X64 {
|
||||
NativeClock::NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency,
|
||||
u64 rtsc_frequency)
|
||||
: WallClock(emulated_cpu_frequency, emulated_clock_frequency, true), rtsc_frequency{
|
||||
rtsc_frequency} {
|
||||
_mm_mfence();
|
||||
last_measure = __rdtsc();
|
||||
accumulated_ticks = 0U;
|
||||
}
|
||||
|
||||
u64 NativeClock::GetRTSC() {
|
||||
std::scoped_lock scope{rtsc_serialize};
|
||||
_mm_mfence();
|
||||
const u64 current_measure = __rdtsc();
|
||||
u64 diff = current_measure - last_measure;
|
||||
diff = diff & ~static_cast<u64>(static_cast<s64>(diff) >> 63); // max(diff, 0)
|
||||
if (current_measure > last_measure) {
|
||||
last_measure = current_measure;
|
||||
}
|
||||
accumulated_ticks += diff;
|
||||
/// The clock cannot be more precise than the guest timer, remove the lower bits
|
||||
return accumulated_ticks & inaccuracy_mask;
|
||||
}
|
||||
|
||||
void NativeClock::Pause(bool is_paused) {
|
||||
if (!is_paused) {
|
||||
_mm_mfence();
|
||||
last_measure = __rdtsc();
|
||||
}
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds NativeClock::GetTimeNS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::nanoseconds{MultiplyAndDivide64(rtsc_value, 1000000000, rtsc_frequency)};
|
||||
}
|
||||
|
||||
std::chrono::microseconds NativeClock::GetTimeUS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::microseconds{MultiplyAndDivide64(rtsc_value, 1000000, rtsc_frequency)};
|
||||
}
|
||||
|
||||
std::chrono::milliseconds NativeClock::GetTimeMS() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return std::chrono::milliseconds{MultiplyAndDivide64(rtsc_value, 1000, rtsc_frequency)};
|
||||
}
|
||||
|
||||
u64 NativeClock::GetClockCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyAndDivide64(rtsc_value, emulated_clock_frequency, rtsc_frequency);
|
||||
}
|
||||
|
||||
u64 NativeClock::GetCPUCycles() {
|
||||
const u64 rtsc_value = GetRTSC();
|
||||
return MultiplyAndDivide64(rtsc_value, emulated_cpu_frequency, rtsc_frequency);
|
||||
}
|
||||
|
||||
} // namespace X64
|
||||
|
||||
} // namespace Common
|
||||
48
src/common/x64/native_clock.h
Normal file
48
src/common/x64/native_clock.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
|
||||
#include "common/spin_lock.h"
|
||||
#include "common/wall_clock.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
namespace X64 {
|
||||
class NativeClock : public WallClock {
|
||||
public:
|
||||
NativeClock(u64 emulated_cpu_frequency, u64 emulated_clock_frequency, u64 rtsc_frequency);
|
||||
|
||||
std::chrono::nanoseconds GetTimeNS() override;
|
||||
|
||||
std::chrono::microseconds GetTimeUS() override;
|
||||
|
||||
std::chrono::milliseconds GetTimeMS() override;
|
||||
|
||||
u64 GetClockCycles() override;
|
||||
|
||||
u64 GetCPUCycles() override;
|
||||
|
||||
void Pause(bool is_paused) override;
|
||||
|
||||
private:
|
||||
u64 GetRTSC();
|
||||
|
||||
/// value used to reduce the native clocks accuracy as some apss rely on
|
||||
/// undefined behavior where the level of accuracy in the clock shouldn't
|
||||
/// be higher.
|
||||
static constexpr u64 inaccuracy_mask = ~(0x400 - 1);
|
||||
|
||||
SpinLock rtsc_serialize{};
|
||||
u64 last_measure{};
|
||||
u64 accumulated_ticks{};
|
||||
u64 rtsc_frequency;
|
||||
};
|
||||
} // namespace X64
|
||||
|
||||
u64 EstimateRDTSCFrequency();
|
||||
|
||||
} // namespace Common
|
||||
229
src/common/x64/xbyak_abi.h
Normal file
229
src/common/x64/xbyak_abi.h
Normal file
@@ -0,0 +1,229 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <xbyak.h>
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
inline std::size_t RegToIndex(const Xbyak::Reg& reg) {
|
||||
using Kind = Xbyak::Reg::Kind;
|
||||
ASSERT_MSG((reg.getKind() & (Kind::REG | Kind::XMM)) != 0,
|
||||
"RegSet only support GPRs and XMM registers.");
|
||||
ASSERT_MSG(reg.getIdx() < 16, "RegSet only supports XXM0-15.");
|
||||
return reg.getIdx() + (reg.getKind() == Kind::REG ? 0 : 16);
|
||||
}
|
||||
|
||||
inline Xbyak::Reg64 IndexToReg64(std::size_t reg_index) {
|
||||
ASSERT(reg_index < 16);
|
||||
return Xbyak::Reg64(static_cast<int>(reg_index));
|
||||
}
|
||||
|
||||
inline Xbyak::Xmm IndexToXmm(std::size_t reg_index) {
|
||||
ASSERT(reg_index >= 16 && reg_index < 32);
|
||||
return Xbyak::Xmm(static_cast<int>(reg_index - 16));
|
||||
}
|
||||
|
||||
inline Xbyak::Reg IndexToReg(std::size_t reg_index) {
|
||||
if (reg_index < 16) {
|
||||
return IndexToReg64(reg_index);
|
||||
} else {
|
||||
return IndexToXmm(reg_index);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::bitset<32> BuildRegSet(std::initializer_list<Xbyak::Reg> regs) {
|
||||
std::bitset<32> bits;
|
||||
for (const Xbyak::Reg& reg : regs) {
|
||||
bits[RegToIndex(reg)] = true;
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
const std::bitset<32> ABI_ALL_GPRS(0x0000FFFF);
|
||||
const std::bitset<32> ABI_ALL_XMMS(0xFFFF0000);
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
// Microsoft x64 ABI
|
||||
const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
|
||||
const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rcx;
|
||||
const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rdx;
|
||||
const Xbyak::Reg ABI_PARAM3 = Xbyak::util::r8;
|
||||
const Xbyak::Reg ABI_PARAM4 = Xbyak::util::r9;
|
||||
|
||||
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
|
||||
// GPRs
|
||||
Xbyak::util::rcx,
|
||||
Xbyak::util::rdx,
|
||||
Xbyak::util::r8,
|
||||
Xbyak::util::r9,
|
||||
Xbyak::util::r10,
|
||||
Xbyak::util::r11,
|
||||
// XMMs
|
||||
Xbyak::util::xmm0,
|
||||
Xbyak::util::xmm1,
|
||||
Xbyak::util::xmm2,
|
||||
Xbyak::util::xmm3,
|
||||
Xbyak::util::xmm4,
|
||||
Xbyak::util::xmm5,
|
||||
});
|
||||
|
||||
const std::bitset<32> ABI_ALL_CALLEE_SAVED = BuildRegSet({
|
||||
// GPRs
|
||||
Xbyak::util::rbx,
|
||||
Xbyak::util::rsi,
|
||||
Xbyak::util::rdi,
|
||||
Xbyak::util::rbp,
|
||||
Xbyak::util::r12,
|
||||
Xbyak::util::r13,
|
||||
Xbyak::util::r14,
|
||||
Xbyak::util::r15,
|
||||
// XMMs
|
||||
Xbyak::util::xmm6,
|
||||
Xbyak::util::xmm7,
|
||||
Xbyak::util::xmm8,
|
||||
Xbyak::util::xmm9,
|
||||
Xbyak::util::xmm10,
|
||||
Xbyak::util::xmm11,
|
||||
Xbyak::util::xmm12,
|
||||
Xbyak::util::xmm13,
|
||||
Xbyak::util::xmm14,
|
||||
Xbyak::util::xmm15,
|
||||
});
|
||||
|
||||
constexpr size_t ABI_SHADOW_SPACE = 0x20;
|
||||
|
||||
#else
|
||||
|
||||
// System V x86-64 ABI
|
||||
const Xbyak::Reg ABI_RETURN = Xbyak::util::rax;
|
||||
const Xbyak::Reg ABI_PARAM1 = Xbyak::util::rdi;
|
||||
const Xbyak::Reg ABI_PARAM2 = Xbyak::util::rsi;
|
||||
const Xbyak::Reg ABI_PARAM3 = Xbyak::util::rdx;
|
||||
const Xbyak::Reg ABI_PARAM4 = Xbyak::util::rcx;
|
||||
|
||||
const std::bitset<32> ABI_ALL_CALLER_SAVED = BuildRegSet({
|
||||
// GPRs
|
||||
Xbyak::util::rcx,
|
||||
Xbyak::util::rdx,
|
||||
Xbyak::util::rdi,
|
||||
Xbyak::util::rsi,
|
||||
Xbyak::util::r8,
|
||||
Xbyak::util::r9,
|
||||
Xbyak::util::r10,
|
||||
Xbyak::util::r11,
|
||||
// XMMs
|
||||
Xbyak::util::xmm0,
|
||||
Xbyak::util::xmm1,
|
||||
Xbyak::util::xmm2,
|
||||
Xbyak::util::xmm3,
|
||||
Xbyak::util::xmm4,
|
||||
Xbyak::util::xmm5,
|
||||
Xbyak::util::xmm6,
|
||||
Xbyak::util::xmm7,
|
||||
Xbyak::util::xmm8,
|
||||
Xbyak::util::xmm9,
|
||||
Xbyak::util::xmm10,
|
||||
Xbyak::util::xmm11,
|
||||
Xbyak::util::xmm12,
|
||||
Xbyak::util::xmm13,
|
||||
Xbyak::util::xmm14,
|
||||
Xbyak::util::xmm15,
|
||||
});
|
||||
|
||||
const std::bitset<32> ABI_ALL_CALLEE_SAVED = BuildRegSet({
|
||||
// GPRs
|
||||
Xbyak::util::rbx,
|
||||
Xbyak::util::rbp,
|
||||
Xbyak::util::r12,
|
||||
Xbyak::util::r13,
|
||||
Xbyak::util::r14,
|
||||
Xbyak::util::r15,
|
||||
});
|
||||
|
||||
constexpr size_t ABI_SHADOW_SPACE = 0;
|
||||
|
||||
#endif
|
||||
|
||||
struct ABIFrameInfo {
|
||||
s32 subtraction;
|
||||
s32 xmm_offset;
|
||||
};
|
||||
|
||||
inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<32> regs, size_t rsp_alignment,
|
||||
size_t needed_frame_size) {
|
||||
const auto count = (regs & ABI_ALL_GPRS).count();
|
||||
rsp_alignment -= count * 8;
|
||||
size_t subtraction = 0;
|
||||
const auto xmm_count = (regs & ABI_ALL_XMMS).count();
|
||||
if (xmm_count) {
|
||||
// If we have any XMMs to save, we must align the stack here.
|
||||
subtraction = rsp_alignment & 0xF;
|
||||
}
|
||||
subtraction += 0x10 * xmm_count;
|
||||
size_t xmm_base_subtraction = subtraction;
|
||||
subtraction += needed_frame_size;
|
||||
subtraction += ABI_SHADOW_SPACE;
|
||||
// Final alignment.
|
||||
rsp_alignment -= subtraction;
|
||||
subtraction += rsp_alignment & 0xF;
|
||||
|
||||
return ABIFrameInfo{static_cast<s32>(subtraction),
|
||||
static_cast<s32>(subtraction - xmm_base_subtraction)};
|
||||
}
|
||||
|
||||
inline size_t ABI_PushRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
|
||||
|
||||
for (std::size_t i = 0; i < regs.size(); ++i) {
|
||||
if (regs[i] && ABI_ALL_GPRS[i]) {
|
||||
code.push(IndexToReg64(i));
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_info.subtraction != 0) {
|
||||
code.sub(code.rsp, frame_info.subtraction);
|
||||
}
|
||||
|
||||
for (std::size_t i = 0; i < regs.size(); ++i) {
|
||||
if (regs[i] && ABI_ALL_XMMS[i]) {
|
||||
code.movaps(code.xword[code.rsp + frame_info.xmm_offset], IndexToXmm(i));
|
||||
frame_info.xmm_offset += 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
return ABI_SHADOW_SPACE;
|
||||
}
|
||||
|
||||
inline void ABI_PopRegistersAndAdjustStack(Xbyak::CodeGenerator& code, std::bitset<32> regs,
|
||||
size_t rsp_alignment, size_t needed_frame_size = 0) {
|
||||
auto frame_info = ABI_CalculateFrameSize(regs, rsp_alignment, needed_frame_size);
|
||||
|
||||
for (std::size_t i = 0; i < regs.size(); ++i) {
|
||||
if (regs[i] && ABI_ALL_XMMS[i]) {
|
||||
code.movaps(IndexToXmm(i), code.xword[code.rsp + frame_info.xmm_offset]);
|
||||
frame_info.xmm_offset += 0x10;
|
||||
}
|
||||
}
|
||||
|
||||
if (frame_info.subtraction != 0) {
|
||||
code.add(code.rsp, frame_info.subtraction);
|
||||
}
|
||||
|
||||
// GPRs need to be popped in reverse order
|
||||
for (std::size_t j = 0; j < regs.size(); ++j) {
|
||||
const std::size_t i = regs.size() - j - 1;
|
||||
if (regs[i] && ABI_ALL_GPRS[i]) {
|
||||
code.pop(IndexToReg64(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::X64
|
||||
47
src/common/x64/xbyak_util.h
Normal file
47
src/common/x64/xbyak_util.h
Normal file
@@ -0,0 +1,47 @@
|
||||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
#include <xbyak.h>
|
||||
#include "common/x64/xbyak_abi.h"
|
||||
|
||||
namespace Common::X64 {
|
||||
|
||||
// Constants for use with cmpps/cmpss
|
||||
enum {
|
||||
CMP_EQ = 0,
|
||||
CMP_LT = 1,
|
||||
CMP_LE = 2,
|
||||
CMP_UNORD = 3,
|
||||
CMP_NEQ = 4,
|
||||
CMP_NLT = 5,
|
||||
CMP_NLE = 6,
|
||||
CMP_ORD = 7,
|
||||
};
|
||||
|
||||
constexpr bool IsWithin2G(uintptr_t ref, uintptr_t target) {
|
||||
const u64 distance = target - (ref + 5);
|
||||
return !(distance >= 0x8000'0000ULL && distance <= ~0x8000'0000ULL);
|
||||
}
|
||||
|
||||
inline bool IsWithin2G(const Xbyak::CodeGenerator& code, uintptr_t target) {
|
||||
return IsWithin2G(reinterpret_cast<uintptr_t>(code.getCurr()), target);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void CallFarFunction(Xbyak::CodeGenerator& code, const T f) {
|
||||
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
|
||||
size_t addr = reinterpret_cast<size_t>(f);
|
||||
if (IsWithin2G(code, addr)) {
|
||||
code.call(f);
|
||||
} else {
|
||||
// ABI_RETURN is a safe temp register to use before a call
|
||||
code.mov(ABI_RETURN, addr);
|
||||
code.call(ABI_RETURN);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::X64
|
||||
@@ -7,6 +7,16 @@ endif()
|
||||
add_library(core STATIC
|
||||
arm/arm_interface.h
|
||||
arm/arm_interface.cpp
|
||||
arm/cpu_interrupt_handler.cpp
|
||||
arm/cpu_interrupt_handler.h
|
||||
arm/dynarmic/arm_dynarmic_32.cpp
|
||||
arm/dynarmic/arm_dynarmic_32.h
|
||||
arm/dynarmic/arm_dynarmic_64.cpp
|
||||
arm/dynarmic/arm_dynarmic_64.h
|
||||
arm/dynarmic/arm_dynarmic_cp15.cpp
|
||||
arm/dynarmic/arm_dynarmic_cp15.h
|
||||
arm/dynarmic/arm_exclusive_monitor.cpp
|
||||
arm/dynarmic/arm_exclusive_monitor.h
|
||||
arm/exclusive_monitor.cpp
|
||||
arm/exclusive_monitor.h
|
||||
arm/unicorn/arm_unicorn.cpp
|
||||
@@ -15,8 +25,6 @@ add_library(core STATIC
|
||||
constants.h
|
||||
core.cpp
|
||||
core.h
|
||||
core_manager.cpp
|
||||
core_manager.h
|
||||
core_timing.cpp
|
||||
core_timing.h
|
||||
core_timing_util.cpp
|
||||
@@ -606,11 +614,11 @@ endif()
|
||||
create_target_directory_groups(core)
|
||||
|
||||
target_link_libraries(core PUBLIC common PRIVATE audio_core video_core)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt json-headers mbedtls opus unicorn)
|
||||
target_link_libraries(core PUBLIC Boost::boost PRIVATE fmt::fmt nlohmann_json::nlohmann_json mbedtls opus unicorn zip)
|
||||
|
||||
if (YUZU_ENABLE_BOXCAT)
|
||||
target_compile_definitions(core PRIVATE -DYUZU_ENABLE_BOXCAT)
|
||||
target_link_libraries(core PRIVATE httplib json-headers zip)
|
||||
target_link_libraries(core PRIVATE httplib nlohmann_json::nlohmann_json)
|
||||
endif()
|
||||
|
||||
if (ENABLE_WEB_SERVICE)
|
||||
|
||||
@@ -139,6 +139,63 @@ std::optional<std::string> GetSymbolName(const Symbols& symbols, VAddr func_addr
|
||||
|
||||
constexpr u64 SEGMENT_BASE = 0x7100000000ull;
|
||||
|
||||
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktraceFromContext(
|
||||
System& system, const ThreadContext64& ctx) {
|
||||
std::vector<BacktraceEntry> out;
|
||||
auto& memory = system.Memory();
|
||||
|
||||
auto fp = ctx.cpu_registers[29];
|
||||
auto lr = ctx.cpu_registers[30];
|
||||
while (true) {
|
||||
out.push_back({"", 0, lr, 0});
|
||||
if (!fp) {
|
||||
break;
|
||||
}
|
||||
lr = memory.Read64(fp + 8) - 4;
|
||||
fp = memory.Read64(fp);
|
||||
}
|
||||
|
||||
std::map<VAddr, std::string> modules;
|
||||
auto& loader{system.GetAppLoader()};
|
||||
if (loader.ReadNSOModules(modules) != Loader::ResultStatus::Success) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::map<std::string, Symbols> symbols;
|
||||
for (const auto& module : modules) {
|
||||
symbols.insert_or_assign(module.second, GetSymbols(module.first, memory));
|
||||
}
|
||||
|
||||
for (auto& entry : out) {
|
||||
VAddr base = 0;
|
||||
for (auto iter = modules.rbegin(); iter != modules.rend(); ++iter) {
|
||||
const auto& module{*iter};
|
||||
if (entry.original_address >= module.first) {
|
||||
entry.module = module.second;
|
||||
base = module.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
entry.offset = entry.original_address - base;
|
||||
entry.address = SEGMENT_BASE + entry.offset;
|
||||
|
||||
if (entry.module.empty())
|
||||
entry.module = "unknown";
|
||||
|
||||
const auto symbol_set = symbols.find(entry.module);
|
||||
if (symbol_set != symbols.end()) {
|
||||
const auto symbol = GetSymbolName(symbol_set->second, entry.offset);
|
||||
if (symbol.has_value()) {
|
||||
// TODO(DarkLordZach): Add demangling of symbol names.
|
||||
entry.name = *symbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<ARM_Interface::BacktraceEntry> ARM_Interface::GetBacktrace() const {
|
||||
std::vector<BacktraceEntry> out;
|
||||
auto& memory = system.Memory();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
@@ -18,25 +19,29 @@ enum class VMAPermission : u8;
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
class CPUInterruptHandler;
|
||||
|
||||
using CPUInterrupts = std::array<CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES>;
|
||||
|
||||
/// Generic ARMv8 CPU interface
|
||||
class ARM_Interface : NonCopyable {
|
||||
public:
|
||||
explicit ARM_Interface(System& system_) : system{system_} {}
|
||||
explicit ARM_Interface(System& system_, CPUInterrupts& interrupt_handlers, bool uses_wall_clock)
|
||||
: system{system_}, interrupt_handlers{interrupt_handlers}, uses_wall_clock{
|
||||
uses_wall_clock} {}
|
||||
virtual ~ARM_Interface() = default;
|
||||
|
||||
struct ThreadContext32 {
|
||||
std::array<u32, 16> cpu_registers{};
|
||||
std::array<u32, 64> extension_registers{};
|
||||
u32 cpsr{};
|
||||
std::array<u8, 4> padding{};
|
||||
std::array<u64, 32> fprs{};
|
||||
u32 fpscr{};
|
||||
u32 fpexc{};
|
||||
u32 tpidr{};
|
||||
};
|
||||
// Internally within the kernel, it expects the AArch32 version of the
|
||||
// thread context to be 344 bytes in size.
|
||||
static_assert(sizeof(ThreadContext32) == 0x158);
|
||||
static_assert(sizeof(ThreadContext32) == 0x150);
|
||||
|
||||
struct ThreadContext64 {
|
||||
std::array<u64, 31> cpu_registers{};
|
||||
@@ -143,6 +148,8 @@ public:
|
||||
*/
|
||||
virtual void SetTPIDR_EL0(u64 value) = 0;
|
||||
|
||||
virtual void ChangeProcessorID(std::size_t new_core_id) = 0;
|
||||
|
||||
virtual void SaveContext(ThreadContext32& ctx) = 0;
|
||||
virtual void SaveContext(ThreadContext64& ctx) = 0;
|
||||
virtual void LoadContext(const ThreadContext32& ctx) = 0;
|
||||
@@ -162,6 +169,9 @@ public:
|
||||
std::string name;
|
||||
};
|
||||
|
||||
static std::vector<BacktraceEntry> GetBacktraceFromContext(System& system,
|
||||
const ThreadContext64& ctx);
|
||||
|
||||
std::vector<BacktraceEntry> GetBacktrace() const;
|
||||
|
||||
/// fp (= r29) points to the last frame record.
|
||||
@@ -175,6 +185,8 @@ public:
|
||||
protected:
|
||||
/// System context that this ARM interface is running under.
|
||||
System& system;
|
||||
CPUInterrupts& interrupt_handlers;
|
||||
bool uses_wall_clock;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
27
src/core/arm/cpu_interrupt_handler.cpp
Normal file
27
src/core/arm/cpu_interrupt_handler.cpp
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/thread.h"
|
||||
#include "core/arm/cpu_interrupt_handler.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
CPUInterruptHandler::CPUInterruptHandler() : is_interrupted{} {
|
||||
interrupt_event = std::make_unique<Common::Event>();
|
||||
}
|
||||
|
||||
CPUInterruptHandler::~CPUInterruptHandler() = default;
|
||||
|
||||
void CPUInterruptHandler::SetInterrupt(bool is_interrupted_) {
|
||||
if (is_interrupted_) {
|
||||
interrupt_event->Set();
|
||||
}
|
||||
this->is_interrupted = is_interrupted_;
|
||||
}
|
||||
|
||||
void CPUInterruptHandler::AwaitInterrupt() {
|
||||
interrupt_event->Wait();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
39
src/core/arm/cpu_interrupt_handler.h
Normal file
39
src/core/arm/cpu_interrupt_handler.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace Common {
|
||||
class Event;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class CPUInterruptHandler {
|
||||
public:
|
||||
CPUInterruptHandler();
|
||||
~CPUInterruptHandler();
|
||||
|
||||
CPUInterruptHandler(const CPUInterruptHandler&) = delete;
|
||||
CPUInterruptHandler& operator=(const CPUInterruptHandler&) = delete;
|
||||
|
||||
CPUInterruptHandler(CPUInterruptHandler&&) = default;
|
||||
CPUInterruptHandler& operator=(CPUInterruptHandler&&) = default;
|
||||
|
||||
bool IsInterrupted() const {
|
||||
return is_interrupted;
|
||||
}
|
||||
|
||||
void SetInterrupt(bool is_interrupted);
|
||||
|
||||
void AwaitInterrupt();
|
||||
|
||||
private:
|
||||
bool is_interrupted{};
|
||||
std::unique_ptr<Common::Event> interrupt_event;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -7,15 +7,17 @@
|
||||
#include <dynarmic/A32/a32.h>
|
||||
#include <dynarmic/A32/config.h>
|
||||
#include <dynarmic/A32/context.h>
|
||||
#include "common/microprofile.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/page_table.h"
|
||||
#include "core/arm/cpu_interrupt_handler.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_manager.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/svc.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -49,8 +51,22 @@ public:
|
||||
parent.system.Memory().Write64(vaddr, value);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(u32 vaddr, u8 value, u8 expected) override {
|
||||
return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive16(u32 vaddr, u16 value, u16 expected) override {
|
||||
return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive32(u32 vaddr, u32 value, u32 expected) override {
|
||||
return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive64(u32 vaddr, u64 value, u64 expected) override {
|
||||
return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
|
||||
}
|
||||
|
||||
void InterpreterFallback(u32 pc, std::size_t num_instructions) override {
|
||||
UNIMPLEMENTED();
|
||||
UNIMPLEMENTED_MSG("This should never happen, pc = {:08X}, code = {:08X}", pc,
|
||||
MemoryReadCode(pc));
|
||||
}
|
||||
|
||||
void ExceptionRaised(u32 pc, Dynarmic::A32::Exception exception) override {
|
||||
@@ -61,7 +77,7 @@ public:
|
||||
case Dynarmic::A32::Exception::Breakpoint:
|
||||
break;
|
||||
}
|
||||
LOG_CRITICAL(HW_GPU, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
||||
LOG_CRITICAL(Core_ARM, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
||||
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
@@ -71,26 +87,36 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
if (parent.uses_wall_clock) {
|
||||
return;
|
||||
}
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
// if not all cores are doing a similar amount of work. Instead of doing this, we should
|
||||
// device a way so that timing is consistent across all cores without increasing the ticks 4
|
||||
// times.
|
||||
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
|
||||
u64 amortized_ticks =
|
||||
(ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
parent.system.CoreTiming().AddTicks(amortized_ticks);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
|
||||
u64 GetTicksRemaining() override {
|
||||
return std::max(parent.system.CoreTiming().GetDowncount(), {});
|
||||
if (parent.uses_wall_clock) {
|
||||
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
|
||||
return minimum_run_cycles;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
||||
}
|
||||
|
||||
ARM_Dynarmic_32& parent;
|
||||
std::size_t num_interpreted_instructions{};
|
||||
u64 tpidrro_el0{};
|
||||
u64 tpidr_el0{};
|
||||
static constexpr u64 minimum_run_cycles = 1000U;
|
||||
};
|
||||
|
||||
std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable& page_table,
|
||||
@@ -99,26 +125,46 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable&
|
||||
config.callbacks = cb.get();
|
||||
// TODO(bunnei): Implement page table for 32-bit
|
||||
// config.page_table = &page_table.pointers;
|
||||
config.coprocessors[15] = std::make_shared<DynarmicCP15>((u32*)&CP15_regs[0]);
|
||||
config.coprocessors[15] = cp15;
|
||||
config.define_unpredictable_behaviour = true;
|
||||
static constexpr std::size_t PAGE_BITS = 12;
|
||||
static constexpr std::size_t NUM_PAGE_TABLE_ENTRIES = 1 << (32 - PAGE_BITS);
|
||||
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
|
||||
page_table.pointers.data());
|
||||
config.absolute_offset_page_table = true;
|
||||
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
||||
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
|
||||
|
||||
// Multi-process state
|
||||
config.processor_id = core_index;
|
||||
config.global_monitor = &exclusive_monitor.monitor;
|
||||
|
||||
// Timing
|
||||
config.wall_clock_cntpct = uses_wall_clock;
|
||||
|
||||
// Optimizations
|
||||
if (Settings::values.disable_cpu_opt) {
|
||||
config.enable_optimizations = false;
|
||||
config.enable_fast_dispatch = false;
|
||||
}
|
||||
|
||||
return std::make_unique<Dynarmic::A32::Jit>(config);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_32, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
|
||||
|
||||
void ARM_Dynarmic_32::Run() {
|
||||
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_32);
|
||||
jit->Run();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::Step() {
|
||||
cb->InterpreterFallback(jit->Regs()[15], 1);
|
||||
jit->Step();
|
||||
}
|
||||
|
||||
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor,
|
||||
ARM_Dynarmic_32::ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers,
|
||||
bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
: ARM_Interface{system},
|
||||
cb(std::make_unique<DynarmicCallbacks32>(*this)), core_index{core_index},
|
||||
: ARM_Interface{system, interrupt_handlers, uses_wall_clock},
|
||||
cb(std::make_unique<DynarmicCallbacks32>(*this)),
|
||||
cp15(std::make_shared<DynarmicCP15>(*this)), core_index{core_index},
|
||||
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
|
||||
|
||||
ARM_Dynarmic_32::~ARM_Dynarmic_32() = default;
|
||||
@@ -154,32 +200,40 @@ void ARM_Dynarmic_32::SetPSTATE(u32 cpsr) {
|
||||
}
|
||||
|
||||
u64 ARM_Dynarmic_32::GetTlsAddress() const {
|
||||
return CP15_regs[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)];
|
||||
return cp15->uro;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::SetTlsAddress(VAddr address) {
|
||||
CP15_regs[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)] = static_cast<u32>(address);
|
||||
cp15->uro = static_cast<u32>(address);
|
||||
}
|
||||
|
||||
u64 ARM_Dynarmic_32::GetTPIDR_EL0() const {
|
||||
return cb->tpidr_el0;
|
||||
return cp15->uprw;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::SetTPIDR_EL0(u64 value) {
|
||||
cb->tpidr_el0 = value;
|
||||
cp15->uprw = static_cast<u32>(value);
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::ChangeProcessorID(std::size_t new_core_id) {
|
||||
jit->ChangeProcessorID(new_core_id);
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::SaveContext(ThreadContext32& ctx) {
|
||||
Dynarmic::A32::Context context;
|
||||
jit->SaveContext(context);
|
||||
ctx.cpu_registers = context.Regs();
|
||||
ctx.extension_registers = context.ExtRegs();
|
||||
ctx.cpsr = context.Cpsr();
|
||||
ctx.fpscr = context.Fpscr();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::LoadContext(const ThreadContext32& ctx) {
|
||||
Dynarmic::A32::Context context;
|
||||
context.Regs() = ctx.cpu_registers;
|
||||
context.ExtRegs() = ctx.extension_registers;
|
||||
context.SetCpsr(ctx.cpsr);
|
||||
context.SetFpscr(ctx.fpscr);
|
||||
jit->LoadContext(context);
|
||||
}
|
||||
|
||||
@@ -188,10 +242,15 @@ void ARM_Dynarmic_32::PrepareReschedule() {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::ClearInstructionCache() {
|
||||
if (!jit) {
|
||||
return;
|
||||
}
|
||||
jit->ClearCache();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::ClearExclusiveState() {}
|
||||
void ARM_Dynarmic_32::ClearExclusiveState() {
|
||||
jit->ClearExclusiveState();
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_32::PageTableChanged(Common::PageTable& page_table,
|
||||
std::size_t new_address_space_size_in_bits) {
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <dynarmic/A32/a32.h>
|
||||
#include <dynarmic/A64/a64.h>
|
||||
#include <dynarmic/A64/exclusive_monitor.h>
|
||||
#include <dynarmic/exclusive_monitor.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
@@ -21,13 +21,16 @@ class Memory;
|
||||
|
||||
namespace Core {
|
||||
|
||||
class CPUInterruptHandler;
|
||||
class DynarmicCallbacks32;
|
||||
class DynarmicCP15;
|
||||
class DynarmicExclusiveMonitor;
|
||||
class System;
|
||||
|
||||
class ARM_Dynarmic_32 final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic_32(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic_32(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
|
||||
ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ARM_Dynarmic_32() override;
|
||||
|
||||
void SetPC(u64 pc) override;
|
||||
@@ -44,6 +47,7 @@ public:
|
||||
void SetTlsAddress(VAddr address) override;
|
||||
void SetTPIDR_EL0(u64 value) override;
|
||||
u64 GetTPIDR_EL0() const override;
|
||||
void ChangeProcessorID(std::size_t new_core_id) override;
|
||||
|
||||
void SaveContext(ThreadContext32& ctx) override;
|
||||
void SaveContext(ThreadContext64& ctx) override {}
|
||||
@@ -66,12 +70,14 @@ private:
|
||||
std::unordered_map<JitCacheKey, std::shared_ptr<Dynarmic::A32::Jit>, Common::PairHash>;
|
||||
|
||||
friend class DynarmicCallbacks32;
|
||||
friend class DynarmicCP15;
|
||||
|
||||
std::unique_ptr<DynarmicCallbacks32> cb;
|
||||
JitCacheType jit_cache;
|
||||
std::shared_ptr<Dynarmic::A32::Jit> jit;
|
||||
std::shared_ptr<DynarmicCP15> cp15;
|
||||
std::size_t core_index;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
std::array<u32, 84> CP15_regs{};
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -7,11 +7,11 @@
|
||||
#include <dynarmic/A64/a64.h>
|
||||
#include <dynarmic/A64/config.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/page_table.h"
|
||||
#include "core/arm/cpu_interrupt_handler.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_manager.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
@@ -65,6 +65,22 @@ public:
|
||||
memory.Write64(vaddr + 8, value[1]);
|
||||
}
|
||||
|
||||
bool MemoryWriteExclusive8(u64 vaddr, std::uint8_t value, std::uint8_t expected) override {
|
||||
return parent.system.Memory().WriteExclusive8(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive16(u64 vaddr, std::uint16_t value, std::uint16_t expected) override {
|
||||
return parent.system.Memory().WriteExclusive16(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive32(u64 vaddr, std::uint32_t value, std::uint32_t expected) override {
|
||||
return parent.system.Memory().WriteExclusive32(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive64(u64 vaddr, std::uint64_t value, std::uint64_t expected) override {
|
||||
return parent.system.Memory().WriteExclusive64(vaddr, value, expected);
|
||||
}
|
||||
bool MemoryWriteExclusive128(u64 vaddr, Vector value, Vector expected) override {
|
||||
return parent.system.Memory().WriteExclusive128(vaddr, value, expected);
|
||||
}
|
||||
|
||||
void InterpreterFallback(u64 pc, std::size_t num_instructions) override {
|
||||
LOG_INFO(Core_ARM, "Unicorn fallback @ 0x{:X} for {} instructions (instr = {:08X})", pc,
|
||||
num_instructions, MemoryReadCode(pc));
|
||||
@@ -98,8 +114,8 @@ public:
|
||||
}
|
||||
[[fallthrough]];
|
||||
default:
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:X})",
|
||||
static_cast<std::size_t>(exception), pc);
|
||||
ASSERT_MSG(false, "ExceptionRaised(exception = {}, pc = {:08X}, code = {:08X})",
|
||||
static_cast<std::size_t>(exception), pc, MemoryReadCode(pc));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,29 +124,42 @@ public:
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) override {
|
||||
if (parent.uses_wall_clock) {
|
||||
return;
|
||||
}
|
||||
// Divide the number of ticks by the amount of CPU cores. TODO(Subv): This yields only a
|
||||
// rough approximation of the amount of executed ticks in the system, it may be thrown off
|
||||
// if not all cores are doing a similar amount of work. Instead of doing this, we should
|
||||
// device a way so that timing is consistent across all cores without increasing the ticks 4
|
||||
// times.
|
||||
u64 amortized_ticks = (ticks - num_interpreted_instructions) / Core::NUM_CPU_CORES;
|
||||
u64 amortized_ticks =
|
||||
(ticks - num_interpreted_instructions) / Core::Hardware::NUM_CPU_CORES;
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
parent.system.CoreTiming().AddTicks(amortized_ticks);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
|
||||
u64 GetTicksRemaining() override {
|
||||
return std::max(parent.system.CoreTiming().GetDowncount(), s64{0});
|
||||
if (parent.uses_wall_clock) {
|
||||
if (!parent.interrupt_handlers[parent.core_index].IsInterrupted()) {
|
||||
return minimum_run_cycles;
|
||||
}
|
||||
return 0U;
|
||||
}
|
||||
return std::max<s64>(parent.system.CoreTiming().GetDowncount(), 0);
|
||||
}
|
||||
|
||||
u64 GetCNTPCT() override {
|
||||
return Timing::CpuCyclesToClockCycles(parent.system.CoreTiming().GetTicks());
|
||||
return parent.system.CoreTiming().GetClockTicks();
|
||||
}
|
||||
|
||||
ARM_Dynarmic_64& parent;
|
||||
std::size_t num_interpreted_instructions = 0;
|
||||
u64 tpidrro_el0 = 0;
|
||||
u64 tpidr_el0 = 0;
|
||||
static constexpr u64 minimum_run_cycles = 1000U;
|
||||
};
|
||||
|
||||
std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable& page_table,
|
||||
@@ -168,14 +197,13 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable&
|
||||
config.enable_fast_dispatch = false;
|
||||
}
|
||||
|
||||
// Timing
|
||||
config.wall_clock_cntpct = uses_wall_clock;
|
||||
|
||||
return std::make_shared<Dynarmic::A64::Jit>(config);
|
||||
}
|
||||
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_64, "ARM JIT", "Dynarmic", MP_RGB(255, 64, 64));
|
||||
|
||||
void ARM_Dynarmic_64::Run() {
|
||||
MICROPROFILE_SCOPE(ARM_Jit_Dynarmic_64);
|
||||
|
||||
jit->Run();
|
||||
}
|
||||
|
||||
@@ -183,10 +211,14 @@ void ARM_Dynarmic_64::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor,
|
||||
ARM_Dynarmic_64::ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers,
|
||||
bool uses_wall_clock, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
: ARM_Interface{system},
|
||||
cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system},
|
||||
: ARM_Interface{system, interrupt_handlers, uses_wall_clock},
|
||||
cb(std::make_unique<DynarmicCallbacks64>(*this)), inner_unicorn{system, interrupt_handlers,
|
||||
uses_wall_clock,
|
||||
ARM_Unicorn::Arch::AArch64,
|
||||
core_index},
|
||||
core_index{core_index}, exclusive_monitor{
|
||||
dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {}
|
||||
|
||||
@@ -240,6 +272,10 @@ void ARM_Dynarmic_64::SetTPIDR_EL0(u64 value) {
|
||||
cb->tpidr_el0 = value;
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_64::ChangeProcessorID(std::size_t new_core_id) {
|
||||
jit->ChangeProcessorID(new_core_id);
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_64::SaveContext(ThreadContext64& ctx) {
|
||||
ctx.cpu_registers = jit->GetRegisters();
|
||||
ctx.sp = jit->GetSP();
|
||||
@@ -267,6 +303,9 @@ void ARM_Dynarmic_64::PrepareReschedule() {
|
||||
}
|
||||
|
||||
void ARM_Dynarmic_64::ClearInstructionCache() {
|
||||
if (!jit) {
|
||||
return;
|
||||
}
|
||||
jit->ClearCache();
|
||||
}
|
||||
|
||||
@@ -286,44 +325,4 @@ void ARM_Dynarmic_64::PageTableChanged(Common::PageTable& page_table,
|
||||
jit_cache.emplace(key, jit);
|
||||
}
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
|
||||
: monitor(core_count), memory{memory} {}
|
||||
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
void DynarmicExclusiveMonitor::SetExclusive(std::size_t core_index, VAddr addr) {
|
||||
// Size doesn't actually matter.
|
||||
monitor.Mark(core_index, addr, 16);
|
||||
}
|
||||
|
||||
void DynarmicExclusiveMonitor::ClearExclusive() {
|
||||
monitor.Clear();
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 1, [&] { memory.Write8(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 2,
|
||||
[&] { memory.Write16(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 4,
|
||||
[&] { memory.Write32(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 8,
|
||||
[&] { memory.Write64(vaddr, value); });
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
|
||||
return monitor.DoExclusiveOperation(core_index, vaddr, 16, [&] {
|
||||
memory.Write64(vaddr + 0, value[0]);
|
||||
memory.Write64(vaddr + 8, value[1]);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include <dynarmic/A64/a64.h>
|
||||
#include <dynarmic/A64/exclusive_monitor.h>
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
@@ -22,12 +21,14 @@ class Memory;
|
||||
namespace Core {
|
||||
|
||||
class DynarmicCallbacks64;
|
||||
class CPUInterruptHandler;
|
||||
class DynarmicExclusiveMonitor;
|
||||
class System;
|
||||
|
||||
class ARM_Dynarmic_64 final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic_64(System& system, ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic_64(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
|
||||
ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
~ARM_Dynarmic_64() override;
|
||||
|
||||
void SetPC(u64 pc) override;
|
||||
@@ -44,6 +45,7 @@ public:
|
||||
void SetTlsAddress(VAddr address) override;
|
||||
void SetTPIDR_EL0(u64 value) override;
|
||||
u64 GetTPIDR_EL0() const override;
|
||||
void ChangeProcessorID(std::size_t new_core_id) override;
|
||||
|
||||
void SaveContext(ThreadContext32& ctx) override {}
|
||||
void SaveContext(ThreadContext64& ctx) override;
|
||||
@@ -75,24 +77,4 @@ private:
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
public:
|
||||
explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
|
||||
~DynarmicExclusiveMonitor() override;
|
||||
|
||||
void SetExclusive(std::size_t core_index, VAddr addr) override;
|
||||
void ClearExclusive() override;
|
||||
|
||||
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
|
||||
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
|
||||
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
|
||||
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
|
||||
bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
|
||||
|
||||
private:
|
||||
friend class ARM_Dynarmic_64;
|
||||
Dynarmic::A64::ExclusiveMonitor monitor;
|
||||
Core::Memory::Memory& memory;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -2,79 +2,132 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <fmt/format.h>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
using Callback = Dynarmic::A32::Coprocessor::Callback;
|
||||
using CallbackOrAccessOneWord = Dynarmic::A32::Coprocessor::CallbackOrAccessOneWord;
|
||||
using CallbackOrAccessTwoWords = Dynarmic::A32::Coprocessor::CallbackOrAccessTwoWords;
|
||||
|
||||
template <>
|
||||
struct fmt::formatter<Dynarmic::A32::CoprocReg> {
|
||||
constexpr auto parse(format_parse_context& ctx) {
|
||||
return ctx.begin();
|
||||
}
|
||||
template <typename FormatContext>
|
||||
auto format(const Dynarmic::A32::CoprocReg& reg, FormatContext& ctx) {
|
||||
return format_to(ctx.out(), "cp{}", static_cast<size_t>(reg));
|
||||
}
|
||||
};
|
||||
|
||||
namespace Core {
|
||||
|
||||
static u32 dummy_value;
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1,
|
||||
CoprocReg CRd, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: cdp{} p15, {}, {}, {}, {}, {}", two ? "2" : "", opc1, CRd, CRn,
|
||||
CRm, opc2);
|
||||
return {};
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
// TODO(merry): Privileged CP15 registers
|
||||
|
||||
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
|
||||
// CP15_FLUSH_PREFETCH_BUFFER
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_FLUSH_PREFETCH_BUFFER)];
|
||||
return &dummy_value;
|
||||
}
|
||||
|
||||
if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
|
||||
switch (opc2) {
|
||||
case 4:
|
||||
// CP15_DATA_SYNC_BARRIER
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_DATA_SYNC_BARRIER)];
|
||||
return &dummy_value;
|
||||
case 5:
|
||||
// CP15_DATA_MEMORY_BARRIER
|
||||
// This is a dummy write, we ignore the value written here.
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_DATA_MEMORY_BARRIER)];
|
||||
default:
|
||||
return {};
|
||||
return &dummy_value;
|
||||
}
|
||||
}
|
||||
|
||||
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_UPRW)];
|
||||
// CP15_THREAD_UPRW
|
||||
return &uprw;
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mcr{} p15, {}, <Rt>, {}, {}, {}", two ? "2" : "", opc1, CRn, CRm,
|
||||
opc2);
|
||||
return {};
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mcrr{} p15, {}, <Rt>, <Rt2>, {}", two ? "2" : "", opc, CRm);
|
||||
return {};
|
||||
}
|
||||
|
||||
CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
|
||||
CoprocReg CRm, unsigned opc2) {
|
||||
// TODO(merry): Privileged CP15 registers
|
||||
|
||||
if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
|
||||
switch (opc2) {
|
||||
case 2:
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_UPRW)];
|
||||
// CP15_THREAD_UPRW
|
||||
return &uprw;
|
||||
case 3:
|
||||
return &CP15[static_cast<std::size_t>(CP15Register::CP15_THREAD_URO)];
|
||||
default:
|
||||
return {};
|
||||
// CP15_THREAD_URO
|
||||
return &uro;
|
||||
}
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrc{} p15, {}, <Rt>, {}, {}, {}", two ? "2" : "", opc1, CRn, CRm,
|
||||
opc2);
|
||||
return {};
|
||||
}
|
||||
|
||||
CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
|
||||
if (!two && opc == 0 && CRm == CoprocReg::C14) {
|
||||
// CNTPCT
|
||||
const auto callback = static_cast<u64 (*)(Dynarmic::A32::Jit*, void*, u32, u32)>(
|
||||
[](Dynarmic::A32::Jit*, void* arg, u32, u32) -> u64 {
|
||||
ARM_Dynarmic_32& parent = *(ARM_Dynarmic_32*)arg;
|
||||
return parent.system.CoreTiming().GetClockTicks();
|
||||
});
|
||||
return Dynarmic::A32::Coprocessor::Callback{callback, (void*)&parent};
|
||||
}
|
||||
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrrc{} p15, {}, <Rt>, <Rt2>, {}", two ? "2" : "", opc, CRm);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) {
|
||||
if (option) {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...], {}", two ? "2" : "",
|
||||
long_transfer ? "l" : "", CRd, *option);
|
||||
} else {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
|
||||
long_transfer ? "l" : "", CRd);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) {
|
||||
if (option) {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...], {}", two ? "2" : "",
|
||||
long_transfer ? "l" : "", CRd, *option);
|
||||
} else {
|
||||
LOG_CRITICAL(Core_ARM, "CP15: mrrc{}{} p15, {}, [...]", two ? "2" : "",
|
||||
long_transfer ? "l" : "", CRd);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -10,128 +10,15 @@
|
||||
#include <dynarmic/A32/coprocessor.h>
|
||||
#include "common/common_types.h"
|
||||
|
||||
enum class CP15Register {
|
||||
// c0 - Information registers
|
||||
CP15_MAIN_ID,
|
||||
CP15_CACHE_TYPE,
|
||||
CP15_TCM_STATUS,
|
||||
CP15_TLB_TYPE,
|
||||
CP15_CPU_ID,
|
||||
CP15_PROCESSOR_FEATURE_0,
|
||||
CP15_PROCESSOR_FEATURE_1,
|
||||
CP15_DEBUG_FEATURE_0,
|
||||
CP15_AUXILIARY_FEATURE_0,
|
||||
CP15_MEMORY_MODEL_FEATURE_0,
|
||||
CP15_MEMORY_MODEL_FEATURE_1,
|
||||
CP15_MEMORY_MODEL_FEATURE_2,
|
||||
CP15_MEMORY_MODEL_FEATURE_3,
|
||||
CP15_ISA_FEATURE_0,
|
||||
CP15_ISA_FEATURE_1,
|
||||
CP15_ISA_FEATURE_2,
|
||||
CP15_ISA_FEATURE_3,
|
||||
CP15_ISA_FEATURE_4,
|
||||
namespace Core {
|
||||
|
||||
// c1 - Control registers
|
||||
CP15_CONTROL,
|
||||
CP15_AUXILIARY_CONTROL,
|
||||
CP15_COPROCESSOR_ACCESS_CONTROL,
|
||||
|
||||
// c2 - Translation table registers
|
||||
CP15_TRANSLATION_BASE_TABLE_0,
|
||||
CP15_TRANSLATION_BASE_TABLE_1,
|
||||
CP15_TRANSLATION_BASE_CONTROL,
|
||||
CP15_DOMAIN_ACCESS_CONTROL,
|
||||
CP15_RESERVED,
|
||||
|
||||
// c5 - Fault status registers
|
||||
CP15_FAULT_STATUS,
|
||||
CP15_INSTR_FAULT_STATUS,
|
||||
CP15_COMBINED_DATA_FSR = CP15_FAULT_STATUS,
|
||||
CP15_INST_FSR,
|
||||
|
||||
// c6 - Fault Address registers
|
||||
CP15_FAULT_ADDRESS,
|
||||
CP15_COMBINED_DATA_FAR = CP15_FAULT_ADDRESS,
|
||||
CP15_WFAR,
|
||||
CP15_IFAR,
|
||||
|
||||
// c7 - Cache operation registers
|
||||
CP15_WAIT_FOR_INTERRUPT,
|
||||
CP15_PHYS_ADDRESS,
|
||||
CP15_INVALIDATE_INSTR_CACHE,
|
||||
CP15_INVALIDATE_INSTR_CACHE_USING_MVA,
|
||||
CP15_INVALIDATE_INSTR_CACHE_USING_INDEX,
|
||||
CP15_FLUSH_PREFETCH_BUFFER,
|
||||
CP15_FLUSH_BRANCH_TARGET_CACHE,
|
||||
CP15_FLUSH_BRANCH_TARGET_CACHE_ENTRY,
|
||||
CP15_INVALIDATE_DATA_CACHE,
|
||||
CP15_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
|
||||
CP15_INVALIDATE_DATA_AND_INSTR_CACHE,
|
||||
CP15_CLEAN_DATA_CACHE,
|
||||
CP15_CLEAN_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_CLEAN_DATA_CACHE_LINE_USING_INDEX,
|
||||
CP15_DATA_SYNC_BARRIER,
|
||||
CP15_DATA_MEMORY_BARRIER,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_MVA,
|
||||
CP15_CLEAN_AND_INVALIDATE_DATA_CACHE_LINE_USING_INDEX,
|
||||
|
||||
// c8 - TLB operations
|
||||
CP15_INVALIDATE_ITLB,
|
||||
CP15_INVALIDATE_ITLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_ITLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_ITLB_ENTRY_ON_MVA,
|
||||
CP15_INVALIDATE_DTLB,
|
||||
CP15_INVALIDATE_DTLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_DTLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_DTLB_ENTRY_ON_MVA,
|
||||
CP15_INVALIDATE_UTLB,
|
||||
CP15_INVALIDATE_UTLB_SINGLE_ENTRY,
|
||||
CP15_INVALIDATE_UTLB_ENTRY_ON_ASID_MATCH,
|
||||
CP15_INVALIDATE_UTLB_ENTRY_ON_MVA,
|
||||
|
||||
// c9 - Data cache lockdown register
|
||||
CP15_DATA_CACHE_LOCKDOWN,
|
||||
|
||||
// c10 - TLB/Memory map registers
|
||||
CP15_TLB_LOCKDOWN,
|
||||
CP15_PRIMARY_REGION_REMAP,
|
||||
CP15_NORMAL_REGION_REMAP,
|
||||
|
||||
// c13 - Thread related registers
|
||||
CP15_PID,
|
||||
CP15_CONTEXT_ID,
|
||||
CP15_THREAD_UPRW, // Thread ID register - User/Privileged Read/Write
|
||||
CP15_THREAD_URO, // Thread ID register - User Read Only (Privileged R/W)
|
||||
CP15_THREAD_PRW, // Thread ID register - Privileged R/W only.
|
||||
|
||||
// c15 - Performance and TLB lockdown registers
|
||||
CP15_PERFORMANCE_MONITOR_CONTROL,
|
||||
CP15_CYCLE_COUNTER,
|
||||
CP15_COUNT_0,
|
||||
CP15_COUNT_1,
|
||||
CP15_READ_MAIN_TLB_LOCKDOWN_ENTRY,
|
||||
CP15_WRITE_MAIN_TLB_LOCKDOWN_ENTRY,
|
||||
CP15_MAIN_TLB_LOCKDOWN_VIRT_ADDRESS,
|
||||
CP15_MAIN_TLB_LOCKDOWN_PHYS_ADDRESS,
|
||||
CP15_MAIN_TLB_LOCKDOWN_ATTRIBUTE,
|
||||
CP15_TLB_DEBUG_CONTROL,
|
||||
|
||||
// Skyeye defined
|
||||
CP15_TLB_FAULT_ADDR,
|
||||
CP15_TLB_FAULT_STATUS,
|
||||
|
||||
// Not an actual register.
|
||||
// All registers should be defined above this.
|
||||
CP15_REGISTER_COUNT,
|
||||
};
|
||||
class ARM_Dynarmic_32;
|
||||
|
||||
class DynarmicCP15 final : public Dynarmic::A32::Coprocessor {
|
||||
public:
|
||||
using CoprocReg = Dynarmic::A32::CoprocReg;
|
||||
|
||||
explicit DynarmicCP15(u32* cp15) : CP15(cp15){};
|
||||
explicit DynarmicCP15(ARM_Dynarmic_32& parent) : parent(parent) {}
|
||||
|
||||
std::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
|
||||
CoprocReg CRn, CoprocReg CRm,
|
||||
@@ -147,6 +34,9 @@ public:
|
||||
std::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
|
||||
std::optional<u8> option) override;
|
||||
|
||||
private:
|
||||
u32* CP15{};
|
||||
ARM_Dynarmic_32& parent;
|
||||
u32 uprw;
|
||||
u32 uro;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
76
src/core/arm/dynarmic/arm_exclusive_monitor.cpp
Normal file
76
src/core/arm/dynarmic/arm_exclusive_monitor.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <cinttypes>
|
||||
#include <memory>
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count)
|
||||
: monitor(core_count), memory{memory} {}
|
||||
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
|
||||
}
|
||||
|
||||
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
|
||||
}
|
||||
|
||||
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
|
||||
}
|
||||
|
||||
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
|
||||
}
|
||||
|
||||
u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
|
||||
u128 result;
|
||||
result[0] = memory.Read64(addr);
|
||||
result[1] = memory.Read64(addr + 8);
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
void DynarmicExclusiveMonitor::ClearExclusive() {
|
||||
monitor.Clear();
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
|
||||
return memory.WriteExclusive8(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
|
||||
return memory.WriteExclusive16(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
|
||||
return memory.WriteExclusive32(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
|
||||
return memory.WriteExclusive64(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
|
||||
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
|
||||
return memory.WriteExclusive128(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
48
src/core/arm/dynarmic/arm_exclusive_monitor.h
Normal file
48
src/core/arm/dynarmic/arm_exclusive_monitor.h
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2020 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <dynarmic/exclusive_monitor.h>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_32.h"
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
public:
|
||||
explicit DynarmicExclusiveMonitor(Memory::Memory& memory, std::size_t core_count);
|
||||
~DynarmicExclusiveMonitor() override;
|
||||
|
||||
u8 ExclusiveRead8(std::size_t core_index, VAddr addr) override;
|
||||
u16 ExclusiveRead16(std::size_t core_index, VAddr addr) override;
|
||||
u32 ExclusiveRead32(std::size_t core_index, VAddr addr) override;
|
||||
u64 ExclusiveRead64(std::size_t core_index, VAddr addr) override;
|
||||
u128 ExclusiveRead128(std::size_t core_index, VAddr addr) override;
|
||||
void ClearExclusive() override;
|
||||
|
||||
bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) override;
|
||||
bool ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) override;
|
||||
bool ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) override;
|
||||
bool ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) override;
|
||||
bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) override;
|
||||
|
||||
private:
|
||||
friend class ARM_Dynarmic_32;
|
||||
friend class ARM_Dynarmic_64;
|
||||
Dynarmic::ExclusiveMonitor monitor;
|
||||
Core::Memory::Memory& memory;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -3,7 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
#include "core/arm/dynarmic/arm_dynarmic_64.h"
|
||||
#include "core/arm/dynarmic/arm_exclusive_monitor.h"
|
||||
#endif
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -18,7 +18,11 @@ class ExclusiveMonitor {
|
||||
public:
|
||||
virtual ~ExclusiveMonitor();
|
||||
|
||||
virtual void SetExclusive(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u8 ExclusiveRead8(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u16 ExclusiveRead16(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u32 ExclusiveRead32(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u64 ExclusiveRead64(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual u128 ExclusiveRead128(std::size_t core_index, VAddr addr) = 0;
|
||||
virtual void ClearExclusive() = 0;
|
||||
|
||||
virtual bool ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) = 0;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include <unicorn/arm64.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/arm/cpu_interrupt_handler.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
@@ -62,8 +63,11 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
|
||||
return false;
|
||||
}
|
||||
|
||||
ARM_Unicorn::ARM_Unicorn(System& system) : ARM_Interface{system} {
|
||||
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
|
||||
ARM_Unicorn::ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
|
||||
Arch architecture, std::size_t core_index)
|
||||
: ARM_Interface{system, interrupt_handlers, uses_wall_clock}, core_index{core_index} {
|
||||
const auto arch = architecture == Arch::AArch32 ? UC_ARCH_ARM : UC_ARCH_ARM64;
|
||||
CHECKED(uc_open(arch, UC_MODE_ARM, &uc));
|
||||
|
||||
auto fpv = 3 << 20;
|
||||
CHECKED(uc_reg_write(uc, UC_ARM64_REG_CPACR_EL1, &fpv));
|
||||
@@ -155,12 +159,20 @@ void ARM_Unicorn::SetTPIDR_EL0(u64 value) {
|
||||
CHECKED(uc_reg_write(uc, UC_ARM64_REG_TPIDR_EL0, &value));
|
||||
}
|
||||
|
||||
void ARM_Unicorn::ChangeProcessorID(std::size_t new_core_id) {
|
||||
core_index = new_core_id;
|
||||
}
|
||||
|
||||
void ARM_Unicorn::Run() {
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
ExecuteInstructions(std::max(4000000U, 0U));
|
||||
} else {
|
||||
ExecuteInstructions(
|
||||
std::max(std::size_t(system.CoreTiming().GetDowncount()), std::size_t{0}));
|
||||
while (true) {
|
||||
if (interrupt_handlers[core_index].IsInterrupted()) {
|
||||
return;
|
||||
}
|
||||
ExecuteInstructions(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -182,8 +194,6 @@ void ARM_Unicorn::ExecuteInstructions(std::size_t num_instructions) {
|
||||
UC_PROT_READ | UC_PROT_WRITE | UC_PROT_EXEC, page_buffer.data()));
|
||||
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
|
||||
CHECKED(uc_mem_unmap(uc, map_addr, page_buffer.size()));
|
||||
|
||||
system.CoreTiming().AddTicks(num_instructions);
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
if (last_bkpt_hit && last_bkpt.type == GDBStub::BreakpointType::Execute) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
|
||||
@@ -15,7 +15,13 @@ class System;
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
explicit ARM_Unicorn(System& system);
|
||||
enum class Arch {
|
||||
AArch32, // 32-bit ARM
|
||||
AArch64, // 64-bit ARM
|
||||
};
|
||||
|
||||
explicit ARM_Unicorn(System& system, CPUInterrupts& interrupt_handlers, bool uses_wall_clock,
|
||||
Arch architecture, std::size_t core_index);
|
||||
~ARM_Unicorn() override;
|
||||
|
||||
void SetPC(u64 pc) override;
|
||||
@@ -30,6 +36,7 @@ public:
|
||||
void SetTlsAddress(VAddr address) override;
|
||||
void SetTPIDR_EL0(u64 value) override;
|
||||
u64 GetTPIDR_EL0() const override;
|
||||
void ChangeProcessorID(std::size_t new_core_id) override;
|
||||
void PrepareReschedule() override;
|
||||
void ClearExclusiveState() override;
|
||||
void ExecuteInstructions(std::size_t num_instructions);
|
||||
@@ -50,6 +57,7 @@ private:
|
||||
uc_engine* uc{};
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit = false;
|
||||
std::size_t core_index;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_manager.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/device_memory.h"
|
||||
@@ -51,6 +51,11 @@
|
||||
#include "video_core/renderer_base.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU0, "ARM JIT", "Dynarmic CPU 0", MP_RGB(255, 64, 64));
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU1, "ARM JIT", "Dynarmic CPU 1", MP_RGB(255, 64, 64));
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU2, "ARM JIT", "Dynarmic CPU 2", MP_RGB(255, 64, 64));
|
||||
MICROPROFILE_DEFINE(ARM_Jit_Dynarmic_CPU3, "ARM JIT", "Dynarmic CPU 3", MP_RGB(255, 64, 64));
|
||||
|
||||
namespace Core {
|
||||
|
||||
namespace {
|
||||
@@ -117,23 +122,22 @@ struct System::Impl {
|
||||
: kernel{system}, fs_controller{system}, memory{system},
|
||||
cpu_manager{system}, reporter{system}, applet_manager{system} {}
|
||||
|
||||
CoreManager& CurrentCoreManager() {
|
||||
return cpu_manager.GetCurrentCoreManager();
|
||||
}
|
||||
|
||||
Kernel::PhysicalCore& CurrentPhysicalCore() {
|
||||
const auto index = cpu_manager.GetActiveCoreIndex();
|
||||
return kernel.PhysicalCore(index);
|
||||
}
|
||||
|
||||
Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
|
||||
return kernel.PhysicalCore(index);
|
||||
}
|
||||
|
||||
ResultStatus RunLoop(bool tight_loop) {
|
||||
ResultStatus Run() {
|
||||
status = ResultStatus::Success;
|
||||
|
||||
cpu_manager.RunLoop(tight_loop);
|
||||
kernel.Suspend(false);
|
||||
core_timing.SyncPause(false);
|
||||
cpu_manager.Pause(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
ResultStatus Pause() {
|
||||
status = ResultStatus::Success;
|
||||
|
||||
core_timing.SyncPause(true);
|
||||
kernel.Suspend(true);
|
||||
cpu_manager.Pause(true);
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -143,14 +147,22 @@ struct System::Impl {
|
||||
|
||||
device_memory = std::make_unique<Core::DeviceMemory>(system);
|
||||
|
||||
core_timing.Initialize();
|
||||
is_multicore = Settings::values.use_multi_core.GetValue();
|
||||
is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue();
|
||||
|
||||
kernel.SetMulticore(is_multicore);
|
||||
cpu_manager.SetMulticore(is_multicore);
|
||||
cpu_manager.SetAsyncGpu(is_async_gpu);
|
||||
core_timing.SetMulticore(is_multicore);
|
||||
|
||||
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
|
||||
kernel.Initialize();
|
||||
cpu_manager.Initialize();
|
||||
|
||||
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
|
||||
std::chrono::system_clock::now().time_since_epoch());
|
||||
Settings::values.custom_rtc_differential =
|
||||
Settings::values.custom_rtc.value_or(current_time) - current_time;
|
||||
Settings::values.custom_rtc.GetValue().value_or(current_time) - current_time;
|
||||
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr)
|
||||
@@ -180,6 +192,11 @@ struct System::Impl {
|
||||
is_powered_on = true;
|
||||
exit_lock = false;
|
||||
|
||||
microprofile_dynarmic[0] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU0);
|
||||
microprofile_dynarmic[1] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU1);
|
||||
microprofile_dynarmic[2] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU2);
|
||||
microprofile_dynarmic[3] = MICROPROFILE_TOKEN(ARM_Jit_Dynarmic_CPU3);
|
||||
|
||||
LOG_DEBUG(Core, "Initialized OK");
|
||||
|
||||
return ResultStatus::Success;
|
||||
@@ -277,8 +294,6 @@ struct System::Impl {
|
||||
service_manager.reset();
|
||||
cheat_engine.reset();
|
||||
telemetry_session.reset();
|
||||
perf_stats.reset();
|
||||
gpu_core.reset();
|
||||
device_memory.reset();
|
||||
|
||||
// Close all CPU/threading state
|
||||
@@ -290,6 +305,8 @@ struct System::Impl {
|
||||
|
||||
// Close app loader
|
||||
app_loader.reset();
|
||||
gpu_core.reset();
|
||||
perf_stats.reset();
|
||||
|
||||
// Clear all applets
|
||||
applet_manager.ClearAll();
|
||||
@@ -382,25 +399,35 @@ struct System::Impl {
|
||||
|
||||
std::unique_ptr<Core::PerfStats> perf_stats;
|
||||
Core::FrameLimiter frame_limiter;
|
||||
|
||||
bool is_multicore{};
|
||||
bool is_async_gpu{};
|
||||
|
||||
std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
|
||||
std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_dynarmic{};
|
||||
};
|
||||
|
||||
System::System() : impl{std::make_unique<Impl>(*this)} {}
|
||||
System::~System() = default;
|
||||
|
||||
CoreManager& System::CurrentCoreManager() {
|
||||
return impl->CurrentCoreManager();
|
||||
CpuManager& System::GetCpuManager() {
|
||||
return impl->cpu_manager;
|
||||
}
|
||||
|
||||
const CoreManager& System::CurrentCoreManager() const {
|
||||
return impl->CurrentCoreManager();
|
||||
const CpuManager& System::GetCpuManager() const {
|
||||
return impl->cpu_manager;
|
||||
}
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
return impl->RunLoop(tight_loop);
|
||||
System::ResultStatus System::Run() {
|
||||
return impl->Run();
|
||||
}
|
||||
|
||||
System::ResultStatus System::Pause() {
|
||||
return impl->Pause();
|
||||
}
|
||||
|
||||
System::ResultStatus System::SingleStep() {
|
||||
return RunLoop(false);
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
void System::InvalidateCpuInstructionCaches() {
|
||||
@@ -416,7 +443,7 @@ bool System::IsPoweredOn() const {
|
||||
}
|
||||
|
||||
void System::PrepareReschedule() {
|
||||
impl->CurrentPhysicalCore().Stop();
|
||||
// Deprecated, does nothing, kept for backward compatibility.
|
||||
}
|
||||
|
||||
void System::PrepareReschedule(const u32 core_index) {
|
||||
@@ -436,31 +463,41 @@ const TelemetrySession& System::TelemetrySession() const {
|
||||
}
|
||||
|
||||
ARM_Interface& System::CurrentArmInterface() {
|
||||
return impl->CurrentPhysicalCore().ArmInterface();
|
||||
return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
|
||||
}
|
||||
|
||||
const ARM_Interface& System::CurrentArmInterface() const {
|
||||
return impl->CurrentPhysicalCore().ArmInterface();
|
||||
return impl->kernel.CurrentScheduler().GetCurrentThread()->ArmInterface();
|
||||
}
|
||||
|
||||
std::size_t System::CurrentCoreIndex() const {
|
||||
return impl->cpu_manager.GetActiveCoreIndex();
|
||||
std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
||||
ASSERT(core < Core::Hardware::NUM_CPU_CORES);
|
||||
return core;
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::CurrentScheduler() {
|
||||
return impl->CurrentPhysicalCore().Scheduler();
|
||||
return impl->kernel.CurrentScheduler();
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::CurrentScheduler() const {
|
||||
return impl->CurrentPhysicalCore().Scheduler();
|
||||
return impl->kernel.CurrentScheduler();
|
||||
}
|
||||
|
||||
Kernel::PhysicalCore& System::CurrentPhysicalCore() {
|
||||
return impl->kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
||||
const Kernel::PhysicalCore& System::CurrentPhysicalCore() const {
|
||||
return impl->kernel.CurrentPhysicalCore();
|
||||
}
|
||||
|
||||
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
|
||||
return impl->GetPhysicalCore(core_index).Scheduler();
|
||||
return impl->kernel.Scheduler(core_index);
|
||||
}
|
||||
|
||||
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
|
||||
return impl->GetPhysicalCore(core_index).Scheduler();
|
||||
return impl->kernel.Scheduler(core_index);
|
||||
}
|
||||
|
||||
/// Gets the global scheduler
|
||||
@@ -490,20 +527,15 @@ const Kernel::Process* System::CurrentProcess() const {
|
||||
}
|
||||
|
||||
ARM_Interface& System::ArmInterface(std::size_t core_index) {
|
||||
return impl->GetPhysicalCore(core_index).ArmInterface();
|
||||
auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
|
||||
ASSERT(thread && !thread->IsHLEThread());
|
||||
return thread->ArmInterface();
|
||||
}
|
||||
|
||||
const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
|
||||
return impl->GetPhysicalCore(core_index).ArmInterface();
|
||||
}
|
||||
|
||||
CoreManager& System::GetCoreManager(std::size_t core_index) {
|
||||
return impl->cpu_manager.GetCoreManager(core_index);
|
||||
}
|
||||
|
||||
const CoreManager& System::GetCoreManager(std::size_t core_index) const {
|
||||
ASSERT(core_index < NUM_CPU_CORES);
|
||||
return impl->cpu_manager.GetCoreManager(core_index);
|
||||
auto* thread = impl->kernel.Scheduler(core_index).GetCurrentThread();
|
||||
ASSERT(thread && !thread->IsHLEThread());
|
||||
return thread->ArmInterface();
|
||||
}
|
||||
|
||||
ExclusiveMonitor& System::Monitor() {
|
||||
@@ -722,4 +754,18 @@ void System::RegisterHostThread() {
|
||||
impl->kernel.RegisterHostThread();
|
||||
}
|
||||
|
||||
void System::EnterDynarmicProfile() {
|
||||
std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
||||
impl->dynarmic_ticks[core] = MicroProfileEnter(impl->microprofile_dynarmic[core]);
|
||||
}
|
||||
|
||||
void System::ExitDynarmicProfile() {
|
||||
std::size_t core = impl->kernel.GetCurrentHostThreadID();
|
||||
MicroProfileLeave(impl->microprofile_dynarmic[core], impl->dynarmic_ticks[core]);
|
||||
}
|
||||
|
||||
bool System::IsMulticore() const {
|
||||
return impl->is_multicore;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -27,6 +27,7 @@ class VfsFilesystem;
|
||||
namespace Kernel {
|
||||
class GlobalScheduler;
|
||||
class KernelCore;
|
||||
class PhysicalCore;
|
||||
class Process;
|
||||
class Scheduler;
|
||||
} // namespace Kernel
|
||||
@@ -90,7 +91,7 @@ class InterruptManager;
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
class CoreManager;
|
||||
class CpuManager;
|
||||
class DeviceMemory;
|
||||
class ExclusiveMonitor;
|
||||
class FrameLimiter;
|
||||
@@ -136,16 +137,16 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* Run the core CPU loop
|
||||
* This function runs the core for the specified number of CPU instructions before trying to
|
||||
* update hardware. This is much faster than SingleStep (and should be equivalent), as the CPU
|
||||
* is not required to do a full dispatch with each instruction. NOTE: the number of instructions
|
||||
* requested is not guaranteed to run, as this will be interrupted preemptively if a hardware
|
||||
* update is requested (e.g. on a thread switch).
|
||||
* @param tight_loop If false, the CPU single-steps.
|
||||
* @return Result status, indicating whether or not the operation succeeded.
|
||||
* Run the OS and Application
|
||||
* This function will start emulation and run the relevant devices
|
||||
*/
|
||||
ResultStatus RunLoop(bool tight_loop = true);
|
||||
ResultStatus Run();
|
||||
|
||||
/**
|
||||
* Pause the OS and Application
|
||||
* This function will pause emulation and stop the relevant devices
|
||||
*/
|
||||
ResultStatus Pause();
|
||||
|
||||
/**
|
||||
* Step the CPU one instruction
|
||||
@@ -209,17 +210,21 @@ public:
|
||||
/// Gets the scheduler for the CPU core that is currently running
|
||||
const Kernel::Scheduler& CurrentScheduler() const;
|
||||
|
||||
/// Gets the physical core for the CPU core that is currently running
|
||||
Kernel::PhysicalCore& CurrentPhysicalCore();
|
||||
|
||||
/// Gets the physical core for the CPU core that is currently running
|
||||
const Kernel::PhysicalCore& CurrentPhysicalCore() const;
|
||||
|
||||
/// Gets a reference to an ARM interface for the CPU core with the specified index
|
||||
ARM_Interface& ArmInterface(std::size_t core_index);
|
||||
|
||||
/// Gets a const reference to an ARM interface from the CPU core with the specified index
|
||||
const ARM_Interface& ArmInterface(std::size_t core_index) const;
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
CoreManager& GetCoreManager(std::size_t core_index);
|
||||
CpuManager& GetCpuManager();
|
||||
|
||||
/// Gets a CPU interface to the CPU core with the specified index
|
||||
const CoreManager& GetCoreManager(std::size_t core_index) const;
|
||||
const CpuManager& GetCpuManager() const;
|
||||
|
||||
/// Gets a reference to the exclusive monitor
|
||||
ExclusiveMonitor& Monitor();
|
||||
@@ -370,15 +375,18 @@ public:
|
||||
/// Register a host thread as an auxiliary thread.
|
||||
void RegisterHostThread();
|
||||
|
||||
/// Enter Dynarmic Microprofile
|
||||
void EnterDynarmicProfile();
|
||||
|
||||
/// Exit Dynarmic Microprofile
|
||||
void ExitDynarmicProfile();
|
||||
|
||||
/// Tells if system is running on multicore.
|
||||
bool IsMulticore() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
/// Returns the currently running CPU core
|
||||
CoreManager& CurrentCoreManager();
|
||||
|
||||
/// Returns the currently running CPU core
|
||||
const CoreManager& CurrentCoreManager() const;
|
||||
|
||||
/**
|
||||
* Initialize the emulated system.
|
||||
* @param emu_window Reference to the host-system window used for video output and keyboard
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_manager.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
CoreManager::CoreManager(System& system, std::size_t core_index)
|
||||
: global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
|
||||
core_index)},
|
||||
core_timing{system.CoreTiming()}, core_index{core_index} {}
|
||||
|
||||
CoreManager::~CoreManager() = default;
|
||||
|
||||
void CoreManager::RunLoop(bool tight_loop) {
|
||||
Reschedule();
|
||||
|
||||
// If we don't have a currently active thread then don't execute instructions,
|
||||
// instead advance to the next event and try to yield to the next thread
|
||||
if (Kernel::GetCurrentThread() == nullptr) {
|
||||
LOG_TRACE(Core, "Core-{} idling", core_index);
|
||||
core_timing.Idle();
|
||||
} else {
|
||||
if (tight_loop) {
|
||||
physical_core.Run();
|
||||
} else {
|
||||
physical_core.Step();
|
||||
}
|
||||
}
|
||||
core_timing.Advance();
|
||||
|
||||
Reschedule();
|
||||
}
|
||||
|
||||
void CoreManager::SingleStep() {
|
||||
return RunLoop(false);
|
||||
}
|
||||
|
||||
void CoreManager::PrepareReschedule() {
|
||||
physical_core.Stop();
|
||||
}
|
||||
|
||||
void CoreManager::Reschedule() {
|
||||
// Lock the global kernel mutex when we manipulate the HLE state
|
||||
std::lock_guard lock(HLE::g_hle_lock);
|
||||
|
||||
global_scheduler.SelectThread(core_index);
|
||||
|
||||
physical_core.Scheduler().TryDoContextSwitch();
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Kernel {
|
||||
class GlobalScheduler;
|
||||
class PhysicalCore;
|
||||
} // namespace Kernel
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core::Memory {
|
||||
class Memory;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
constexpr unsigned NUM_CPU_CORES{4};
|
||||
|
||||
class CoreManager {
|
||||
public:
|
||||
CoreManager(System& system, std::size_t core_index);
|
||||
~CoreManager();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
|
||||
void SingleStep();
|
||||
|
||||
void PrepareReschedule();
|
||||
|
||||
bool IsMainCore() const {
|
||||
return core_index == 0;
|
||||
}
|
||||
|
||||
std::size_t CoreIndex() const {
|
||||
return core_index;
|
||||
}
|
||||
|
||||
private:
|
||||
void Reschedule();
|
||||
|
||||
Kernel::GlobalScheduler& global_scheduler;
|
||||
Kernel::PhysicalCore& physical_core;
|
||||
Timing::CoreTiming& core_timing;
|
||||
|
||||
std::atomic<bool> reschedule_pending = false;
|
||||
std::size_t core_index;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
@@ -1,29 +1,27 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core_timing.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
constexpr int MAX_SLICE_LENGTH = 10000;
|
||||
constexpr u64 MAX_SLICE_LENGTH = 4000;
|
||||
|
||||
std::shared_ptr<EventType> CreateEvent(std::string name, TimedCallback&& callback) {
|
||||
return std::make_shared<EventType>(std::move(callback), std::move(name));
|
||||
}
|
||||
|
||||
struct CoreTiming::Event {
|
||||
s64 time;
|
||||
u64 time;
|
||||
u64 fifo_order;
|
||||
u64 userdata;
|
||||
std::weak_ptr<EventType> type;
|
||||
@@ -39,51 +37,90 @@ struct CoreTiming::Event {
|
||||
}
|
||||
};
|
||||
|
||||
CoreTiming::CoreTiming() = default;
|
||||
CoreTiming::CoreTiming() {
|
||||
clock =
|
||||
Common::CreateBestMatchingClock(Core::Hardware::BASE_CLOCK_RATE, Core::Hardware::CNTFREQ);
|
||||
}
|
||||
|
||||
CoreTiming::~CoreTiming() = default;
|
||||
|
||||
void CoreTiming::Initialize() {
|
||||
downcounts.fill(MAX_SLICE_LENGTH);
|
||||
time_slice.fill(MAX_SLICE_LENGTH);
|
||||
slice_length = MAX_SLICE_LENGTH;
|
||||
global_timer = 0;
|
||||
idled_cycles = 0;
|
||||
current_context = 0;
|
||||
|
||||
// The time between CoreTiming being initialized and the first call to Advance() is considered
|
||||
// the slice boundary between slice -1 and slice 0. Dispatcher loops must call Advance() before
|
||||
// executing the first cycle of each slice to prepare the slice length and downcount for
|
||||
// that slice.
|
||||
is_global_timer_sane = true;
|
||||
void CoreTiming::ThreadEntry(CoreTiming& instance) {
|
||||
constexpr char name[] = "yuzu:HostTiming";
|
||||
MicroProfileOnThreadCreate(name);
|
||||
Common::SetCurrentThreadName(name);
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh);
|
||||
instance.on_thread_init();
|
||||
instance.ThreadLoop();
|
||||
}
|
||||
|
||||
void CoreTiming::Initialize(std::function<void(void)>&& on_thread_init_) {
|
||||
on_thread_init = std::move(on_thread_init_);
|
||||
event_fifo_id = 0;
|
||||
|
||||
shutting_down = false;
|
||||
ticks = 0;
|
||||
const auto empty_timed_callback = [](u64, s64) {};
|
||||
ev_lost = CreateEvent("_lost_event", empty_timed_callback);
|
||||
if (is_multicore) {
|
||||
timer_thread = std::make_unique<std::thread>(ThreadEntry, std::ref(*this));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::Shutdown() {
|
||||
paused = true;
|
||||
shutting_down = true;
|
||||
pause_event.Set();
|
||||
event.Set();
|
||||
if (timer_thread) {
|
||||
timer_thread->join();
|
||||
}
|
||||
ClearPendingEvents();
|
||||
timer_thread.reset();
|
||||
has_started = false;
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type,
|
||||
u64 userdata) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
const s64 timeout = GetTicks() + cycles_into_future;
|
||||
void CoreTiming::Pause(bool is_paused) {
|
||||
paused = is_paused;
|
||||
pause_event.Set();
|
||||
}
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!is_global_timer_sane) {
|
||||
ForceExceptionCheck(cycles_into_future);
|
||||
void CoreTiming::SyncPause(bool is_paused) {
|
||||
if (is_paused == paused && paused_set == paused) {
|
||||
return;
|
||||
}
|
||||
Pause(is_paused);
|
||||
if (timer_thread) {
|
||||
if (!is_paused) {
|
||||
pause_event.Set();
|
||||
}
|
||||
event.Set();
|
||||
while (paused_set != is_paused)
|
||||
;
|
||||
}
|
||||
}
|
||||
|
||||
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
|
||||
bool CoreTiming::IsRunning() const {
|
||||
return !paused_set;
|
||||
}
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
bool CoreTiming::HasPendingEvents() const {
|
||||
return !(wait_set && event_queue.empty());
|
||||
}
|
||||
|
||||
void CoreTiming::ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
|
||||
u64 userdata) {
|
||||
{
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const u64 timeout = static_cast<u64>(GetGlobalTimeNs().count() + ns_into_future);
|
||||
|
||||
event_queue.emplace_back(Event{timeout, event_fifo_id++, userdata, event_type});
|
||||
|
||||
std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
}
|
||||
event.Set();
|
||||
}
|
||||
|
||||
void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
|
||||
std::scoped_lock scope{basic_lock};
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get() && e.userdata == userdata;
|
||||
});
|
||||
@@ -95,21 +132,39 @@ void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u
|
||||
}
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetTicks() const {
|
||||
u64 ticks = static_cast<u64>(global_timer);
|
||||
if (!is_global_timer_sane) {
|
||||
ticks += accumulated_ticks;
|
||||
void CoreTiming::AddTicks(u64 ticks) {
|
||||
this->ticks += ticks;
|
||||
downcount -= ticks;
|
||||
}
|
||||
|
||||
void CoreTiming::Idle() {
|
||||
if (!event_queue.empty()) {
|
||||
const u64 next_event_time = event_queue.front().time;
|
||||
const u64 next_ticks = nsToCycles(std::chrono::nanoseconds(next_event_time)) + 10U;
|
||||
if (next_ticks > ticks) {
|
||||
ticks = next_ticks;
|
||||
}
|
||||
return;
|
||||
}
|
||||
ticks += 1000U;
|
||||
}
|
||||
|
||||
void CoreTiming::ResetTicks() {
|
||||
downcount = MAX_SLICE_LENGTH;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetCPUTicks() const {
|
||||
if (is_multicore) {
|
||||
return clock->GetCPUCycles();
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetIdleTicks() const {
|
||||
return static_cast<u64>(idled_cycles);
|
||||
}
|
||||
|
||||
void CoreTiming::AddTicks(u64 ticks) {
|
||||
accumulated_ticks += ticks;
|
||||
downcounts[current_context] -= static_cast<s64>(ticks);
|
||||
u64 CoreTiming::GetClockTicks() const {
|
||||
if (is_multicore) {
|
||||
return clock->GetClockCycles();
|
||||
}
|
||||
return CpuCyclesToClockCycles(ticks);
|
||||
}
|
||||
|
||||
void CoreTiming::ClearPendingEvents() {
|
||||
@@ -117,7 +172,7 @@ void CoreTiming::ClearPendingEvents() {
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
std::lock_guard guard{inner_mutex};
|
||||
std::scoped_lock lock{basic_lock};
|
||||
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type.lock().get() == event_type.get();
|
||||
@@ -130,97 +185,68 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::ForceExceptionCheck(s64 cycles) {
|
||||
cycles = std::max<s64>(0, cycles);
|
||||
if (downcounts[current_context] <= cycles) {
|
||||
return;
|
||||
}
|
||||
|
||||
// downcount is always (much) smaller than MAX_INT so we can safely cast cycles to an int
|
||||
// here. Account for cycles already executed by adjusting the g.slice_length
|
||||
downcounts[current_context] = static_cast<int>(cycles);
|
||||
}
|
||||
|
||||
std::optional<u64> CoreTiming::NextAvailableCore(const s64 needed_ticks) const {
|
||||
const u64 original_context = current_context;
|
||||
u64 next_context = (original_context + 1) % num_cpu_cores;
|
||||
while (next_context != original_context) {
|
||||
if (time_slice[next_context] >= needed_ticks) {
|
||||
return {next_context};
|
||||
} else if (time_slice[next_context] >= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
next_context = (next_context + 1) % num_cpu_cores;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void CoreTiming::Advance() {
|
||||
std::unique_lock<std::mutex> guard(inner_mutex);
|
||||
|
||||
const u64 cycles_executed = accumulated_ticks;
|
||||
time_slice[current_context] = std::max<s64>(0, time_slice[current_context] - accumulated_ticks);
|
||||
global_timer += cycles_executed;
|
||||
|
||||
is_global_timer_sane = true;
|
||||
std::optional<s64> CoreTiming::Advance() {
|
||||
std::scoped_lock lock{advance_lock, basic_lock};
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
|
||||
while (!event_queue.empty() && event_queue.front().time <= global_timer) {
|
||||
Event evt = std::move(event_queue.front());
|
||||
std::pop_heap(event_queue.begin(), event_queue.end(), std::greater<>());
|
||||
event_queue.pop_back();
|
||||
inner_mutex.unlock();
|
||||
basic_lock.unlock();
|
||||
|
||||
if (auto event_type{evt.type.lock()}) {
|
||||
event_type->callback(evt.userdata, global_timer - evt.time);
|
||||
}
|
||||
|
||||
inner_mutex.lock();
|
||||
basic_lock.lock();
|
||||
global_timer = GetGlobalTimeNs().count();
|
||||
}
|
||||
|
||||
is_global_timer_sane = false;
|
||||
|
||||
// Still events left (scheduled in the future)
|
||||
if (!event_queue.empty()) {
|
||||
const s64 needed_ticks =
|
||||
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH);
|
||||
const auto next_core = NextAvailableCore(needed_ticks);
|
||||
if (next_core) {
|
||||
downcounts[*next_core] = needed_ticks;
|
||||
const s64 next_time = event_queue.front().time - global_timer;
|
||||
return next_time;
|
||||
} else {
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTiming::ThreadLoop() {
|
||||
has_started = true;
|
||||
while (!shutting_down) {
|
||||
while (!paused) {
|
||||
paused_set = false;
|
||||
const auto next_time = Advance();
|
||||
if (next_time) {
|
||||
if (*next_time > 0) {
|
||||
std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time);
|
||||
event.WaitFor(next_time_ns);
|
||||
}
|
||||
} else {
|
||||
wait_set = true;
|
||||
event.Wait();
|
||||
}
|
||||
wait_set = false;
|
||||
}
|
||||
paused_set = true;
|
||||
clock->Pause(true);
|
||||
pause_event.Wait();
|
||||
clock->Pause(false);
|
||||
}
|
||||
|
||||
accumulated_ticks = 0;
|
||||
|
||||
downcounts[current_context] = time_slice[current_context];
|
||||
}
|
||||
|
||||
void CoreTiming::ResetRun() {
|
||||
downcounts.fill(MAX_SLICE_LENGTH);
|
||||
time_slice.fill(MAX_SLICE_LENGTH);
|
||||
current_context = 0;
|
||||
// Still events left (scheduled in the future)
|
||||
if (!event_queue.empty()) {
|
||||
const s64 needed_ticks =
|
||||
std::min<s64>(event_queue.front().time - global_timer, MAX_SLICE_LENGTH);
|
||||
downcounts[current_context] = needed_ticks;
|
||||
std::chrono::nanoseconds CoreTiming::GetGlobalTimeNs() const {
|
||||
if (is_multicore) {
|
||||
return clock->GetTimeNS();
|
||||
}
|
||||
|
||||
is_global_timer_sane = false;
|
||||
accumulated_ticks = 0;
|
||||
}
|
||||
|
||||
void CoreTiming::Idle() {
|
||||
accumulated_ticks += downcounts[current_context];
|
||||
idled_cycles += downcounts[current_context];
|
||||
downcounts[current_context] = 0;
|
||||
return CyclesToNs(ticks);
|
||||
}
|
||||
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
|
||||
return std::chrono::microseconds{GetTicks() * 1000000 / Hardware::BASE_CLOCK_RATE};
|
||||
}
|
||||
|
||||
s64 CoreTiming::GetDowncount() const {
|
||||
return downcounts[current_context];
|
||||
if (is_multicore) {
|
||||
return clock->GetTimeUS();
|
||||
}
|
||||
return CyclesToUs(ticks);
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
// Copyright 2008 Dolphin Emulator Project / 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Copyright 2020 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/spin_lock.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "common/wall_clock.h"
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
@@ -56,16 +62,40 @@ public:
|
||||
|
||||
/// CoreTiming begins at the boundary of timing slice -1. An initial call to Advance() is
|
||||
/// required to end slice - 1 and start slice 0 before the first cycle of code is executed.
|
||||
void Initialize();
|
||||
void Initialize(std::function<void(void)>&& on_thread_init_);
|
||||
|
||||
/// Tears down all timing related functionality.
|
||||
void Shutdown();
|
||||
|
||||
/// After the first Advance, the slice lengths and the downcount will be reduced whenever an
|
||||
/// event is scheduled earlier than the current values.
|
||||
///
|
||||
/// Scheduling from a callback will not update the downcount until the Advance() completes.
|
||||
void ScheduleEvent(s64 cycles_into_future, const std::shared_ptr<EventType>& event_type,
|
||||
/// Sets if emulation is multicore or single core, must be set before Initialize
|
||||
void SetMulticore(bool is_multicore) {
|
||||
this->is_multicore = is_multicore;
|
||||
}
|
||||
|
||||
/// Check if it's using host timing.
|
||||
bool IsHostTiming() const {
|
||||
return is_multicore;
|
||||
}
|
||||
|
||||
/// Pauses/Unpauses the execution of the timer thread.
|
||||
void Pause(bool is_paused);
|
||||
|
||||
/// Pauses/Unpauses the execution of the timer thread and waits until paused.
|
||||
void SyncPause(bool is_paused);
|
||||
|
||||
/// Checks if core timing is running.
|
||||
bool IsRunning() const;
|
||||
|
||||
/// Checks if the timer thread has started.
|
||||
bool HasStarted() const {
|
||||
return has_started;
|
||||
}
|
||||
|
||||
/// Checks if there are any pending time events.
|
||||
bool HasPendingEvents() const;
|
||||
|
||||
/// Schedules an event in core timing
|
||||
void ScheduleEvent(s64 ns_into_future, const std::shared_ptr<EventType>& event_type,
|
||||
u64 userdata = 0);
|
||||
|
||||
void UnscheduleEvent(const std::shared_ptr<EventType>& event_type, u64 userdata);
|
||||
@@ -73,41 +103,30 @@ public:
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const std::shared_ptr<EventType>& event_type);
|
||||
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
|
||||
/// This should only be called from the emu thread, if you are calling it any other thread,
|
||||
/// you are doing something evil
|
||||
u64 GetTicks() const;
|
||||
|
||||
u64 GetIdleTicks() const;
|
||||
|
||||
void AddTicks(u64 ticks);
|
||||
|
||||
/// Advance must be called at the beginning of dispatcher loops, not the end. Advance() ends
|
||||
/// the previous timing slice and begins the next one, you must Advance from the previous
|
||||
/// slice to the current one before executing any cycles. CoreTiming starts in slice -1 so an
|
||||
/// Advance() is required to initialize the slice length before the first cycle of emulated
|
||||
/// instructions is executed.
|
||||
void Advance();
|
||||
void ResetTicks();
|
||||
|
||||
/// Pretend that the main CPU has executed enough cycles to reach the next event.
|
||||
void Idle();
|
||||
|
||||
s64 GetDowncount() const {
|
||||
return downcount;
|
||||
}
|
||||
|
||||
/// Returns current time in emulated CPU cycles
|
||||
u64 GetCPUTicks() const;
|
||||
|
||||
/// Returns current time in emulated in Clock cycles
|
||||
u64 GetClockTicks() const;
|
||||
|
||||
/// Returns current time in microseconds.
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
|
||||
void ResetRun();
|
||||
/// Returns current time in nanoseconds.
|
||||
std::chrono::nanoseconds GetGlobalTimeNs() const;
|
||||
|
||||
s64 GetDowncount() const;
|
||||
|
||||
void SwitchContext(u64 new_context) {
|
||||
current_context = new_context;
|
||||
}
|
||||
|
||||
bool CanCurrentContextRun() const {
|
||||
return time_slice[current_context] > 0;
|
||||
}
|
||||
|
||||
std::optional<u64> NextAvailableCore(const s64 needed_ticks) const;
|
||||
/// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
|
||||
std::optional<s64> Advance();
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
@@ -115,21 +134,14 @@ private:
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
|
||||
static constexpr u64 num_cpu_cores = 4;
|
||||
static void ThreadEntry(CoreTiming& instance);
|
||||
void ThreadLoop();
|
||||
|
||||
s64 global_timer = 0;
|
||||
s64 idled_cycles = 0;
|
||||
s64 slice_length = 0;
|
||||
u64 accumulated_ticks = 0;
|
||||
std::array<s64, num_cpu_cores> downcounts{};
|
||||
// Slice of time assigned to each core per run.
|
||||
std::array<s64, num_cpu_cores> time_slice{};
|
||||
u64 current_context = 0;
|
||||
std::unique_ptr<Common::WallClock> clock;
|
||||
|
||||
// Are we in a function that has been called from Advance()
|
||||
// If events are scheduled from a function that gets called from Advance(),
|
||||
// don't change slice_length and downcount.
|
||||
bool is_global_timer_sane = false;
|
||||
u64 global_timer = 0;
|
||||
|
||||
std::chrono::nanoseconds start_point;
|
||||
|
||||
// The queue is a min-heap using std::make_heap/push_heap/pop_heap.
|
||||
// We don't use std::priority_queue because we need to be able to serialize, unserialize and
|
||||
@@ -139,8 +151,23 @@ private:
|
||||
u64 event_fifo_id = 0;
|
||||
|
||||
std::shared_ptr<EventType> ev_lost;
|
||||
Common::Event event{};
|
||||
Common::Event pause_event{};
|
||||
Common::SpinLock basic_lock{};
|
||||
Common::SpinLock advance_lock{};
|
||||
std::unique_ptr<std::thread> timer_thread;
|
||||
std::atomic<bool> paused{};
|
||||
std::atomic<bool> paused_set{};
|
||||
std::atomic<bool> wait_set{};
|
||||
std::atomic<bool> shutting_down{};
|
||||
std::atomic<bool> has_started{};
|
||||
std::function<void(void)> on_thread_init{};
|
||||
|
||||
std::mutex inner_mutex;
|
||||
bool is_multicore{};
|
||||
|
||||
/// Cycle timing
|
||||
u64 ticks{};
|
||||
s64 downcount{};
|
||||
};
|
||||
|
||||
/// Creates a core timing event with the given name and callback.
|
||||
|
||||
@@ -38,15 +38,23 @@ s64 usToCycles(std::chrono::microseconds us) {
|
||||
}
|
||||
|
||||
s64 nsToCycles(std::chrono::nanoseconds ns) {
|
||||
if (static_cast<u64>(ns.count() / 1000000000) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_ERROR(Core_Timing, "Integer overflow, use max value");
|
||||
return std::numeric_limits<s64>::max();
|
||||
}
|
||||
if (static_cast<u64>(ns.count()) > MAX_VALUE_TO_MULTIPLY) {
|
||||
LOG_DEBUG(Core_Timing, "Time very big, do rounding");
|
||||
return Hardware::BASE_CLOCK_RATE * (ns.count() / 1000000000);
|
||||
}
|
||||
return (Hardware::BASE_CLOCK_RATE * ns.count()) / 1000000000;
|
||||
const u128 temporal = Common::Multiply64Into128(ns.count(), Hardware::BASE_CLOCK_RATE);
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(1000000000)).first;
|
||||
}
|
||||
|
||||
u64 msToClockCycles(std::chrono::milliseconds ns) {
|
||||
const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
|
||||
return Common::Divide128On32(temp, 1000).first;
|
||||
}
|
||||
|
||||
u64 usToClockCycles(std::chrono::microseconds ns) {
|
||||
const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
|
||||
return Common::Divide128On32(temp, 1000000).first;
|
||||
}
|
||||
|
||||
u64 nsToClockCycles(std::chrono::nanoseconds ns) {
|
||||
const u128 temp = Common::Multiply64Into128(ns.count(), Hardware::CNTFREQ);
|
||||
return Common::Divide128On32(temp, 1000000000).first;
|
||||
}
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
@@ -54,4 +62,22 @@ u64 CpuCyclesToClockCycles(u64 ticks) {
|
||||
return Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
|
||||
}
|
||||
|
||||
std::chrono::milliseconds CyclesToMs(s64 cycles) {
|
||||
const u128 temporal = Common::Multiply64Into128(cycles, 1000);
|
||||
u64 ms = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
|
||||
return std::chrono::milliseconds(ms);
|
||||
}
|
||||
|
||||
std::chrono::nanoseconds CyclesToNs(s64 cycles) {
|
||||
const u128 temporal = Common::Multiply64Into128(cycles, 1000000000);
|
||||
u64 ns = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
|
||||
return std::chrono::nanoseconds(ns);
|
||||
}
|
||||
|
||||
std::chrono::microseconds CyclesToUs(s64 cycles) {
|
||||
const u128 temporal = Common::Multiply64Into128(cycles, 1000000);
|
||||
u64 us = Common::Divide128On32(temporal, static_cast<u32>(Hardware::BASE_CLOCK_RATE)).first;
|
||||
return std::chrono::microseconds(us);
|
||||
}
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -13,18 +13,12 @@ namespace Core::Timing {
|
||||
s64 msToCycles(std::chrono::milliseconds ms);
|
||||
s64 usToCycles(std::chrono::microseconds us);
|
||||
s64 nsToCycles(std::chrono::nanoseconds ns);
|
||||
|
||||
inline std::chrono::milliseconds CyclesToMs(s64 cycles) {
|
||||
return std::chrono::milliseconds(cycles * 1000 / Hardware::BASE_CLOCK_RATE);
|
||||
}
|
||||
|
||||
inline std::chrono::nanoseconds CyclesToNs(s64 cycles) {
|
||||
return std::chrono::nanoseconds(cycles * 1000000000 / Hardware::BASE_CLOCK_RATE);
|
||||
}
|
||||
|
||||
inline std::chrono::microseconds CyclesToUs(s64 cycles) {
|
||||
return std::chrono::microseconds(cycles * 1000000 / Hardware::BASE_CLOCK_RATE);
|
||||
}
|
||||
u64 msToClockCycles(std::chrono::milliseconds ns);
|
||||
u64 usToClockCycles(std::chrono::microseconds ns);
|
||||
u64 nsToClockCycles(std::chrono::nanoseconds ns);
|
||||
std::chrono::milliseconds CyclesToMs(s64 cycles);
|
||||
std::chrono::nanoseconds CyclesToNs(s64 cycles);
|
||||
std::chrono::microseconds CyclesToUs(s64 cycles);
|
||||
|
||||
u64 CpuCyclesToClockCycles(u64 ticks);
|
||||
|
||||
|
||||
@@ -2,80 +2,372 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/fiber.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/thread.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_manager.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/cpu_manager.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/physical_core.h"
|
||||
#include "core/hle/kernel/scheduler.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "video_core/gpu.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
CpuManager::CpuManager(System& system) : system{system} {}
|
||||
CpuManager::~CpuManager() = default;
|
||||
|
||||
void CpuManager::ThreadStart(CpuManager& cpu_manager, std::size_t core) {
|
||||
cpu_manager.RunThread(core);
|
||||
}
|
||||
|
||||
void CpuManager::Initialize() {
|
||||
for (std::size_t index = 0; index < core_managers.size(); ++index) {
|
||||
core_managers[index] = std::make_unique<CoreManager>(system, index);
|
||||
running_mode = true;
|
||||
if (is_multicore) {
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
core_data[core].host_thread =
|
||||
std::make_unique<std::thread>(ThreadStart, std::ref(*this), core);
|
||||
}
|
||||
} else {
|
||||
core_data[0].host_thread = std::make_unique<std::thread>(ThreadStart, std::ref(*this), 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::Shutdown() {
|
||||
for (auto& cpu_core : core_managers) {
|
||||
cpu_core.reset();
|
||||
running_mode = false;
|
||||
Pause(false);
|
||||
if (is_multicore) {
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
core_data[core].host_thread->join();
|
||||
core_data[core].host_thread.reset();
|
||||
}
|
||||
} else {
|
||||
core_data[0].host_thread->join();
|
||||
core_data[0].host_thread.reset();
|
||||
}
|
||||
}
|
||||
|
||||
CoreManager& CpuManager::GetCoreManager(std::size_t index) {
|
||||
return *core_managers.at(index);
|
||||
std::function<void(void*)> CpuManager::GetGuestThreadStartFunc() {
|
||||
return std::function<void(void*)>(GuestThreadFunction);
|
||||
}
|
||||
|
||||
const CoreManager& CpuManager::GetCoreManager(std::size_t index) const {
|
||||
return *core_managers.at(index);
|
||||
std::function<void(void*)> CpuManager::GetIdleThreadStartFunc() {
|
||||
return std::function<void(void*)>(IdleThreadFunction);
|
||||
}
|
||||
|
||||
CoreManager& CpuManager::GetCurrentCoreManager() {
|
||||
// Otherwise, use single-threaded mode active_core variable
|
||||
return *core_managers[active_core];
|
||||
std::function<void(void*)> CpuManager::GetSuspendThreadStartFunc() {
|
||||
return std::function<void(void*)>(SuspendThreadFunction);
|
||||
}
|
||||
|
||||
const CoreManager& CpuManager::GetCurrentCoreManager() const {
|
||||
// Otherwise, use single-threaded mode active_core variable
|
||||
return *core_managers[active_core];
|
||||
void CpuManager::GuestThreadFunction(void* cpu_manager_) {
|
||||
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
|
||||
if (cpu_manager->is_multicore) {
|
||||
cpu_manager->MultiCoreRunGuestThread();
|
||||
} else {
|
||||
cpu_manager->SingleCoreRunGuestThread();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::RunLoop(bool tight_loop) {
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::HandlePacket();
|
||||
void CpuManager::GuestRewindFunction(void* cpu_manager_) {
|
||||
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
|
||||
if (cpu_manager->is_multicore) {
|
||||
cpu_manager->MultiCoreRunGuestLoop();
|
||||
} else {
|
||||
cpu_manager->SingleCoreRunGuestLoop();
|
||||
}
|
||||
}
|
||||
|
||||
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
|
||||
// execute. Otherwise, get out of the loop function.
|
||||
if (GDBStub::GetCpuHaltFlag()) {
|
||||
if (GDBStub::GetCpuStepFlag()) {
|
||||
tight_loop = false;
|
||||
} else {
|
||||
return;
|
||||
void CpuManager::IdleThreadFunction(void* cpu_manager_) {
|
||||
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
|
||||
if (cpu_manager->is_multicore) {
|
||||
cpu_manager->MultiCoreRunIdleThread();
|
||||
} else {
|
||||
cpu_manager->SingleCoreRunIdleThread();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SuspendThreadFunction(void* cpu_manager_) {
|
||||
CpuManager* cpu_manager = static_cast<CpuManager*>(cpu_manager_);
|
||||
if (cpu_manager->is_multicore) {
|
||||
cpu_manager->MultiCoreRunSuspendThread();
|
||||
} else {
|
||||
cpu_manager->SingleCoreRunSuspendThread();
|
||||
}
|
||||
}
|
||||
|
||||
void* CpuManager::GetStartFuncParamater() {
|
||||
return static_cast<void*>(this);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// MultiCore ///
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void CpuManager::MultiCoreRunGuestThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
MultiCoreRunGuestLoop();
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunGuestLoop() {
|
||||
auto& kernel = system.Kernel();
|
||||
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
|
||||
while (true) {
|
||||
auto* physical_core = &kernel.CurrentPhysicalCore();
|
||||
auto& arm_interface = thread->ArmInterface();
|
||||
system.EnterDynarmicProfile();
|
||||
while (!physical_core->IsInterrupted()) {
|
||||
arm_interface.Run();
|
||||
physical_core = &kernel.CurrentPhysicalCore();
|
||||
}
|
||||
system.ExitDynarmicProfile();
|
||||
arm_interface.ClearExclusiveState();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunIdleThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
while (true) {
|
||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
||||
physical_core.Idle();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::MultiCoreRunSuspendThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
while (true) {
|
||||
auto core = kernel.GetCurrentHostThreadID();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[core].host_context);
|
||||
ASSERT(scheduler.ContextSwitchPending());
|
||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::MultiCorePause(bool paused) {
|
||||
if (!paused) {
|
||||
bool all_not_barrier = false;
|
||||
while (!all_not_barrier) {
|
||||
all_not_barrier = true;
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
all_not_barrier &=
|
||||
!core_data[core].is_running.load() && core_data[core].initialized.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto& core_timing = system.CoreTiming();
|
||||
core_timing.ResetRun();
|
||||
bool keep_running{};
|
||||
do {
|
||||
keep_running = false;
|
||||
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
|
||||
core_timing.SwitchContext(active_core);
|
||||
if (core_timing.CanCurrentContextRun()) {
|
||||
core_managers[active_core]->RunLoop(tight_loop);
|
||||
}
|
||||
keep_running |= core_timing.CanCurrentContextRun();
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
core_data[core].enter_barrier->Set();
|
||||
}
|
||||
} while (keep_running);
|
||||
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
GDBStub::SetCpuStepFlag(false);
|
||||
if (paused_state.load()) {
|
||||
bool all_barrier = false;
|
||||
while (!all_barrier) {
|
||||
all_barrier = true;
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
all_barrier &=
|
||||
core_data[core].is_paused.load() && core_data[core].initialized.load();
|
||||
}
|
||||
}
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
core_data[core].exit_barrier->Set();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/// Wait until all cores are paused.
|
||||
bool all_barrier = false;
|
||||
while (!all_barrier) {
|
||||
all_barrier = true;
|
||||
for (std::size_t core = 0; core < Core::Hardware::NUM_CPU_CORES; core++) {
|
||||
all_barrier &=
|
||||
core_data[core].is_paused.load() && core_data[core].initialized.load();
|
||||
}
|
||||
}
|
||||
/// Don't release the barrier
|
||||
}
|
||||
paused_state = paused;
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
/// SingleCore ///
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void CpuManager::SingleCoreRunGuestThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
SingleCoreRunGuestLoop();
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunGuestLoop() {
|
||||
auto& kernel = system.Kernel();
|
||||
auto* thread = kernel.CurrentScheduler().GetCurrentThread();
|
||||
while (true) {
|
||||
auto* physical_core = &kernel.CurrentPhysicalCore();
|
||||
auto& arm_interface = thread->ArmInterface();
|
||||
system.EnterDynarmicProfile();
|
||||
if (!physical_core->IsInterrupted()) {
|
||||
arm_interface.Run();
|
||||
physical_core = &kernel.CurrentPhysicalCore();
|
||||
}
|
||||
system.ExitDynarmicProfile();
|
||||
thread->SetPhantomMode(true);
|
||||
system.CoreTiming().Advance();
|
||||
thread->SetPhantomMode(false);
|
||||
arm_interface.ClearExclusiveState();
|
||||
PreemptSingleCore();
|
||||
auto& scheduler = kernel.Scheduler(current_core);
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunIdleThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
while (true) {
|
||||
auto& physical_core = kernel.CurrentPhysicalCore();
|
||||
PreemptSingleCore(false);
|
||||
system.CoreTiming().AddTicks(1000U);
|
||||
idle_count++;
|
||||
auto& scheduler = physical_core.Scheduler();
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SingleCoreRunSuspendThread() {
|
||||
auto& kernel = system.Kernel();
|
||||
{
|
||||
auto& sched = kernel.CurrentScheduler();
|
||||
sched.OnThreadStart();
|
||||
}
|
||||
while (true) {
|
||||
auto core = kernel.GetCurrentHostThreadID();
|
||||
auto& scheduler = kernel.CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), core_data[0].host_context);
|
||||
ASSERT(scheduler.ContextSwitchPending());
|
||||
ASSERT(core == kernel.GetCurrentHostThreadID());
|
||||
scheduler.TryDoContextSwitch();
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::PreemptSingleCore(bool from_running_enviroment) {
|
||||
std::size_t old_core = current_core;
|
||||
auto& scheduler = system.Kernel().Scheduler(old_core);
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
if (idle_count >= 4 || from_running_enviroment) {
|
||||
if (!from_running_enviroment) {
|
||||
system.CoreTiming().Idle();
|
||||
idle_count = 0;
|
||||
}
|
||||
current_thread->SetPhantomMode(true);
|
||||
system.CoreTiming().Advance();
|
||||
current_thread->SetPhantomMode(false);
|
||||
}
|
||||
current_core.store((current_core + 1) % Core::Hardware::NUM_CPU_CORES);
|
||||
system.CoreTiming().ResetTicks();
|
||||
scheduler.Unload();
|
||||
auto& next_scheduler = system.Kernel().Scheduler(current_core);
|
||||
Common::Fiber::YieldTo(current_thread->GetHostContext(), next_scheduler.ControlContext());
|
||||
/// May have changed scheduler
|
||||
auto& current_scheduler = system.Kernel().Scheduler(current_core);
|
||||
current_scheduler.Reload();
|
||||
auto* currrent_thread2 = current_scheduler.GetCurrentThread();
|
||||
if (!currrent_thread2->IsIdleThread()) {
|
||||
idle_count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::SingleCorePause(bool paused) {
|
||||
if (!paused) {
|
||||
bool all_not_barrier = false;
|
||||
while (!all_not_barrier) {
|
||||
all_not_barrier = !core_data[0].is_running.load() && core_data[0].initialized.load();
|
||||
}
|
||||
core_data[0].enter_barrier->Set();
|
||||
if (paused_state.load()) {
|
||||
bool all_barrier = false;
|
||||
while (!all_barrier) {
|
||||
all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
|
||||
}
|
||||
core_data[0].exit_barrier->Set();
|
||||
}
|
||||
} else {
|
||||
/// Wait until all cores are paused.
|
||||
bool all_barrier = false;
|
||||
while (!all_barrier) {
|
||||
all_barrier = core_data[0].is_paused.load() && core_data[0].initialized.load();
|
||||
}
|
||||
/// Don't release the barrier
|
||||
}
|
||||
paused_state = paused;
|
||||
}
|
||||
|
||||
void CpuManager::Pause(bool paused) {
|
||||
if (is_multicore) {
|
||||
MultiCorePause(paused);
|
||||
} else {
|
||||
SingleCorePause(paused);
|
||||
}
|
||||
}
|
||||
|
||||
void CpuManager::RunThread(std::size_t core) {
|
||||
/// Initialization
|
||||
system.RegisterCoreThread(core);
|
||||
std::string name;
|
||||
if (is_multicore) {
|
||||
name = "yuzu:CoreCPUThread_" + std::to_string(core);
|
||||
} else {
|
||||
name = "yuzu:CPUThread";
|
||||
}
|
||||
MicroProfileOnThreadCreate(name.c_str());
|
||||
Common::SetCurrentThreadName(name.c_str());
|
||||
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
|
||||
auto& data = core_data[core];
|
||||
data.enter_barrier = std::make_unique<Common::Event>();
|
||||
data.exit_barrier = std::make_unique<Common::Event>();
|
||||
data.host_context = Common::Fiber::ThreadToFiber();
|
||||
data.is_running = false;
|
||||
data.initialized = true;
|
||||
const bool sc_sync = !is_async_gpu && !is_multicore;
|
||||
bool sc_sync_first_use = sc_sync;
|
||||
/// Running
|
||||
while (running_mode) {
|
||||
data.is_running = false;
|
||||
data.enter_barrier->Wait();
|
||||
if (sc_sync_first_use) {
|
||||
system.GPU().ObtainContext();
|
||||
sc_sync_first_use = false;
|
||||
}
|
||||
auto& scheduler = system.Kernel().CurrentScheduler();
|
||||
Kernel::Thread* current_thread = scheduler.GetCurrentThread();
|
||||
data.is_running = true;
|
||||
Common::Fiber::YieldTo(data.host_context, current_thread->GetHostContext());
|
||||
data.is_running = false;
|
||||
data.is_paused = true;
|
||||
data.exit_barrier->Wait();
|
||||
data.is_paused = false;
|
||||
}
|
||||
/// Time to cleanup
|
||||
data.host_context->Exit();
|
||||
data.enter_barrier.reset();
|
||||
data.exit_barrier.reset();
|
||||
data.initialized = false;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include "core/hardware_properties.h"
|
||||
|
||||
namespace Common {
|
||||
class Event;
|
||||
class Fiber;
|
||||
} // namespace Common
|
||||
|
||||
namespace Core {
|
||||
|
||||
class CoreManager;
|
||||
class System;
|
||||
|
||||
class CpuManager {
|
||||
@@ -24,24 +31,75 @@ public:
|
||||
CpuManager& operator=(const CpuManager&) = delete;
|
||||
CpuManager& operator=(CpuManager&&) = delete;
|
||||
|
||||
/// Sets if emulation is multicore or single core, must be set before Initialize
|
||||
void SetMulticore(bool is_multicore) {
|
||||
this->is_multicore = is_multicore;
|
||||
}
|
||||
|
||||
/// Sets if emulation is using an asynchronous GPU.
|
||||
void SetAsyncGpu(bool is_async_gpu) {
|
||||
this->is_async_gpu = is_async_gpu;
|
||||
}
|
||||
|
||||
void Initialize();
|
||||
void Shutdown();
|
||||
|
||||
CoreManager& GetCoreManager(std::size_t index);
|
||||
const CoreManager& GetCoreManager(std::size_t index) const;
|
||||
void Pause(bool paused);
|
||||
|
||||
CoreManager& GetCurrentCoreManager();
|
||||
const CoreManager& GetCurrentCoreManager() const;
|
||||
std::function<void(void*)> GetGuestThreadStartFunc();
|
||||
std::function<void(void*)> GetIdleThreadStartFunc();
|
||||
std::function<void(void*)> GetSuspendThreadStartFunc();
|
||||
void* GetStartFuncParamater();
|
||||
|
||||
std::size_t GetActiveCoreIndex() const {
|
||||
return active_core;
|
||||
void PreemptSingleCore(bool from_running_enviroment = true);
|
||||
|
||||
std::size_t CurrentCore() const {
|
||||
return current_core.load();
|
||||
}
|
||||
|
||||
void RunLoop(bool tight_loop);
|
||||
|
||||
private:
|
||||
std::array<std::unique_ptr<CoreManager>, Hardware::NUM_CPU_CORES> core_managers;
|
||||
std::size_t active_core{}; ///< Active core, only used in single thread mode
|
||||
static void GuestThreadFunction(void* cpu_manager);
|
||||
static void GuestRewindFunction(void* cpu_manager);
|
||||
static void IdleThreadFunction(void* cpu_manager);
|
||||
static void SuspendThreadFunction(void* cpu_manager);
|
||||
|
||||
void MultiCoreRunGuestThread();
|
||||
void MultiCoreRunGuestLoop();
|
||||
void MultiCoreRunIdleThread();
|
||||
void MultiCoreRunSuspendThread();
|
||||
void MultiCorePause(bool paused);
|
||||
|
||||
void SingleCoreRunGuestThread();
|
||||
void SingleCoreRunGuestLoop();
|
||||
void SingleCoreRunIdleThread();
|
||||
void SingleCoreRunSuspendThread();
|
||||
void SingleCorePause(bool paused);
|
||||
|
||||
static void ThreadStart(CpuManager& cpu_manager, std::size_t core);
|
||||
|
||||
void RunThread(std::size_t core);
|
||||
|
||||
struct CoreData {
|
||||
std::shared_ptr<Common::Fiber> host_context;
|
||||
std::unique_ptr<Common::Event> enter_barrier;
|
||||
std::unique_ptr<Common::Event> exit_barrier;
|
||||
std::atomic<bool> is_running;
|
||||
std::atomic<bool> is_paused;
|
||||
std::atomic<bool> initialized;
|
||||
std::unique_ptr<std::thread> host_thread;
|
||||
};
|
||||
|
||||
std::atomic<bool> running_mode{};
|
||||
std::atomic<bool> paused_state{};
|
||||
|
||||
std::array<CoreData, Core::Hardware::NUM_CPU_CORES> core_data{};
|
||||
|
||||
bool is_async_gpu{};
|
||||
bool is_multicore{};
|
||||
std::atomic<std::size_t> current_core{};
|
||||
std::size_t preemption_count{};
|
||||
std::size_t idle_count{};
|
||||
static constexpr std::size_t max_cycle_runs = 5;
|
||||
|
||||
System& system;
|
||||
};
|
||||
|
||||
@@ -223,7 +223,16 @@ bool operator<(const KeyIndex<KeyType>& lhs, const KeyIndex<KeyType>& rhs) {
|
||||
|
||||
class KeyManager {
|
||||
public:
|
||||
KeyManager();
|
||||
static KeyManager& Instance() {
|
||||
static KeyManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
KeyManager(const KeyManager&) = delete;
|
||||
KeyManager& operator=(const KeyManager&) = delete;
|
||||
|
||||
KeyManager(KeyManager&&) = delete;
|
||||
KeyManager& operator=(KeyManager&&) = delete;
|
||||
|
||||
bool HasKey(S128KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
bool HasKey(S256KeyType id, u64 field1 = 0, u64 field2 = 0) const;
|
||||
@@ -257,6 +266,8 @@ public:
|
||||
bool AddTicketPersonalized(Ticket raw);
|
||||
|
||||
private:
|
||||
KeyManager();
|
||||
|
||||
std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
|
||||
std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user