Compare commits
490 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
42dc73157c | ||
|
|
49c6d21b31 | ||
|
|
1b13859af8 | ||
|
|
1f5d6a8fed | ||
|
|
66f4fd4c81 | ||
|
|
7ea097e5c2 | ||
|
|
5a9204dbd7 | ||
|
|
d6b9b51606 | ||
|
|
e64fa4d2ea | ||
|
|
3558c88442 | ||
|
|
e9d84ef22c | ||
|
|
5bc82d124c | ||
|
|
8932001610 | ||
|
|
44ea2810e4 | ||
|
|
d583fc1e97 | ||
|
|
45b6d2d349 | ||
|
|
f15e2dd881 | ||
|
|
ef84c70d22 | ||
|
|
532dda0499 | ||
|
|
1068c1b06f | ||
|
|
6335bf136f | ||
|
|
42f7c11021 | ||
|
|
14430f7df9 | ||
|
|
eb5a3dd1c7 | ||
|
|
be1a1584fc | ||
|
|
66e023fba2 | ||
|
|
b27e6ad912 | ||
|
|
16ea93c11e | ||
|
|
a6a783b3dc | ||
|
|
e7eff72e83 | ||
|
|
46b3209abb | ||
|
|
0e1b5acc6a | ||
|
|
b9238edd0d | ||
|
|
e56f32a071 | ||
|
|
1b855efd5e | ||
|
|
a1574aabd5 | ||
|
|
10d1d58390 | ||
|
|
d91e35a50a | ||
|
|
5219edd715 | ||
|
|
730eb1dad7 | ||
|
|
c3471bf618 | ||
|
|
da1b45de34 | ||
|
|
1cffd3848b | ||
|
|
93c1630570 | ||
|
|
d29f9e9709 | ||
|
|
5167d1577d | ||
|
|
4f8cd74061 | ||
|
|
0220862ba5 | ||
|
|
04d7b7e09d | ||
|
|
8250f9bb1c | ||
|
|
7cdeec20ec | ||
|
|
a12f4efa2f | ||
|
|
c1b2e35625 | ||
|
|
be7dad5e7e | ||
|
|
c07987dfab | ||
|
|
c4243c07cc | ||
|
|
c6170565b5 | ||
|
|
57985fb16a | ||
|
|
517933adcb | ||
|
|
030814b1cb | ||
|
|
90c780e6f3 | ||
|
|
33a0597603 | ||
|
|
281a8bf259 | ||
|
|
f7090bacc5 | ||
|
|
d062991643 | ||
|
|
4ab978d670 | ||
|
|
92050c4d86 | ||
|
|
abef11a540 | ||
|
|
f546fb35ed | ||
|
|
94b27bb8a5 | ||
|
|
90528f1326 | ||
|
|
fd15730767 | ||
|
|
fa4dc2cf42 | ||
|
|
9539c4203b | ||
|
|
ae437320c8 | ||
|
|
3273f93cd5 | ||
|
|
ef559f5741 | ||
|
|
8d5d369b54 | ||
|
|
6dd40976d0 | ||
|
|
717394c980 | ||
|
|
b675c97cdd | ||
|
|
4bce08d497 | ||
|
|
2bb02a0b78 | ||
|
|
e869c5ef1a | ||
|
|
4699fdca8f | ||
|
|
cd7e1183e2 | ||
|
|
b009bda67a | ||
|
|
0113c36300 | ||
|
|
a8fa5019b5 | ||
|
|
99da6362c4 | ||
|
|
bd983414f6 | ||
|
|
2195f10d15 | ||
|
|
4154936568 | ||
|
|
9a56b99fa4 | ||
|
|
8dfc81239f | ||
|
|
4327f430f1 | ||
|
|
a8fc5d6edd | ||
|
|
ae6c052ed9 | ||
|
|
a2b6de7e9f | ||
|
|
0ffdd0a683 | ||
|
|
aa0b6babda | ||
|
|
25c2fe1c6b | ||
|
|
33a4cebc22 | ||
|
|
fcc3aa0bbf | ||
|
|
10682ad7e0 | ||
|
|
8490e7746a | ||
|
|
f0c4ac9abd | ||
|
|
bb41683394 | ||
|
|
0829ef97ca | ||
|
|
f0bfb24c61 | ||
|
|
83ba3515ec | ||
|
|
cd542d5aac | ||
|
|
c425a1a857 | ||
|
|
8beca060d1 | ||
|
|
86b55cb6df | ||
|
|
8135f4bfce | ||
|
|
c440ecfafe | ||
|
|
054e39647c | ||
|
|
e25c464c02 | ||
|
|
18fe910957 | ||
|
|
b12ab4d805 | ||
|
|
cc94a6d101 | ||
|
|
afb8af9853 | ||
|
|
e60d4d70bc | ||
|
|
48d9d66dc5 | ||
|
|
444231a83d | ||
|
|
c1accfefde | ||
|
|
27e5efd265 | ||
|
|
f5ec165e8c | ||
|
|
edd668047c | ||
|
|
1ddcd0e6f0 | ||
|
|
a6a73d8892 | ||
|
|
1d98027a0e | ||
|
|
2374471a1e | ||
|
|
e543320129 | ||
|
|
504aafedd2 | ||
|
|
e36e7ae74e | ||
|
|
259e52ccb2 | ||
|
|
889c646ac0 | ||
|
|
d62b0a9e29 | ||
|
|
f09d1dffd1 | ||
|
|
35e1118766 | ||
|
|
dd1aab5446 | ||
|
|
ca482997fe | ||
|
|
e09f1c92fb | ||
|
|
dfd14618f7 | ||
|
|
d0ac624403 | ||
|
|
9f0b247cf6 | ||
|
|
8ff2ce5207 | ||
|
|
e6a2245304 | ||
|
|
e147ed4fc0 | ||
|
|
4ffb487251 | ||
|
|
bd928e70ed | ||
|
|
eb73247433 | ||
|
|
df0f31f44e | ||
|
|
7fefec585c | ||
|
|
2bc6a699dc | ||
|
|
750abcc23d | ||
|
|
8ee3666a3c | ||
|
|
ed956569a4 | ||
|
|
f087639e4a | ||
|
|
cfb20c4c9d | ||
|
|
e78da8dc1f | ||
|
|
be4641c43f | ||
|
|
a3703f5767 | ||
|
|
4039086226 | ||
|
|
a1faed9950 | ||
|
|
57fb15d2a3 | ||
|
|
3435cd8d5e | ||
|
|
b1efceec89 | ||
|
|
98be5a4928 | ||
|
|
145c3ac89e | ||
|
|
c2c5260fd7 | ||
|
|
8b11368671 | ||
|
|
0ed5d728ca | ||
|
|
84412591c9 | ||
|
|
049050856f | ||
|
|
10ab714fe0 | ||
|
|
40ac058557 | ||
|
|
8b800369ea | ||
|
|
c357d8f6f7 | ||
|
|
b34ae2235d | ||
|
|
40cd299f01 | ||
|
|
67c1f31251 | ||
|
|
c82b0afb69 | ||
|
|
ef073ff117 | ||
|
|
7aa7d8f4ff | ||
|
|
f598490b57 | ||
|
|
284536a626 | ||
|
|
7320c667df | ||
|
|
3c02cdcc57 | ||
|
|
72c70d6808 | ||
|
|
b5e685b297 | ||
|
|
bb4549a73d | ||
|
|
a568cd805b | ||
|
|
0306c50339 | ||
|
|
dfa7be5ddf | ||
|
|
0d1d755086 | ||
|
|
42b75e8be8 | ||
|
|
6a6fabea58 | ||
|
|
2bdbb90af7 | ||
|
|
04e68e9738 | ||
|
|
390721a561 | ||
|
|
e01a9de35f | ||
|
|
f61c1ed246 | ||
|
|
9feb68085d | ||
|
|
0be835132c | ||
|
|
eceab45dac | ||
|
|
4076d8fe3e | ||
|
|
2d226ff8ac | ||
|
|
11e7c1244c | ||
|
|
3a6eef27a2 | ||
|
|
26f8a700a7 | ||
|
|
13222f94c0 | ||
|
|
414cc1eb1f | ||
|
|
b0b027d2d0 | ||
|
|
db21ac2627 | ||
|
|
d6f5f5cafa | ||
|
|
3e80b08944 | ||
|
|
911587fb8d | ||
|
|
220df45b7d | ||
|
|
704744bb72 | ||
|
|
3bbaa98c78 | ||
|
|
4b676e7786 | ||
|
|
e597665569 | ||
|
|
a3cdd773c3 | ||
|
|
4596ef5274 | ||
|
|
1a302d4d47 | ||
|
|
35480167b1 | ||
|
|
0b594f3344 | ||
|
|
92b18345a8 | ||
|
|
a897feb21e | ||
|
|
ba14fb42e4 | ||
|
|
1d11def9c4 | ||
|
|
477d616f7d | ||
|
|
07b86dc28c | ||
|
|
44f39bfb68 | ||
|
|
eb1a3c1f4a | ||
|
|
06887c80a5 | ||
|
|
5078106068 | ||
|
|
1710847d08 | ||
|
|
0db8918947 | ||
|
|
c3affdd162 | ||
|
|
09727a6a97 | ||
|
|
c6c1c1b45f | ||
|
|
0c59e6265f | ||
|
|
3c3d9afd61 | ||
|
|
69884d8a8f | ||
|
|
4c818b60e3 | ||
|
|
52c326c301 | ||
|
|
52bb524526 | ||
|
|
9f803299de | ||
|
|
3b84e04af1 | ||
|
|
ba38d91fe2 | ||
|
|
f58a6152fc | ||
|
|
07692230ca | ||
|
|
49a3571e76 | ||
|
|
7e92497402 | ||
|
|
679e63550a | ||
|
|
3d81a8efd5 | ||
|
|
d9f9bb7552 | ||
|
|
f45c25aaba | ||
|
|
2561a79c39 | ||
|
|
ac603cf130 | ||
|
|
8dbb8edcd3 | ||
|
|
c608d3a979 | ||
|
|
932922f67f | ||
|
|
cb2ce9924a | ||
|
|
72c48e01c9 | ||
|
|
5e636d1f6e | ||
|
|
b2b98b2f44 | ||
|
|
62e08c30b7 | ||
|
|
3e1a9a45a6 | ||
|
|
a9268286a4 | ||
|
|
65f3908064 | ||
|
|
6957bac03c | ||
|
|
7842536ddb | ||
|
|
7185d90a53 | ||
|
|
32eb080e02 | ||
|
|
fc6d46c374 | ||
|
|
1f4ca1e841 | ||
|
|
f574d324e7 | ||
|
|
9be3fedcaa | ||
|
|
045b0b70b6 | ||
|
|
a94a828b6c | ||
|
|
d5a4707f65 | ||
|
|
a6f473fd01 | ||
|
|
0fa1ebc349 | ||
|
|
9a82dec74a | ||
|
|
839c4a8a1c | ||
|
|
125599c2d5 | ||
|
|
3049ea45d3 | ||
|
|
372245e0b5 | ||
|
|
3ca0af8bb3 | ||
|
|
3740adb6f5 | ||
|
|
aa427bb2a7 | ||
|
|
636cc2a496 | ||
|
|
ea73ffe202 | ||
|
|
56541b1ae5 | ||
|
|
4bce57b149 | ||
|
|
63783db1b3 | ||
|
|
e8bd6b1fcc | ||
|
|
1c733bf175 | ||
|
|
197d0d9d24 | ||
|
|
cbf8bea9d5 | ||
|
|
eff61c5c42 | ||
|
|
f9e69faf4a | ||
|
|
69da267540 | ||
|
|
08fcf41b0a | ||
|
|
83f8d1aa2e | ||
|
|
966405d64b | ||
|
|
d0e4e43e3c | ||
|
|
96644385ca | ||
|
|
a1b1ea47ed | ||
|
|
faf69a22d4 | ||
|
|
609e98bc63 | ||
|
|
a0f615f232 | ||
|
|
ac754a57d2 | ||
|
|
3629fcf3e6 | ||
|
|
549164d425 | ||
|
|
b8b87ec01f | ||
|
|
5961928543 | ||
|
|
a661025637 | ||
|
|
66978a772d | ||
|
|
e4fa77ef6a | ||
|
|
a63d7c49fc | ||
|
|
b273b19576 | ||
|
|
318bf7c8e3 | ||
|
|
1e40a4b343 | ||
|
|
51de4e00a6 | ||
|
|
1c9c4eefeb | ||
|
|
170c8212bb | ||
|
|
2d6c064e66 | ||
|
|
d911740e5d | ||
|
|
bb12f99b20 | ||
|
|
50195b1704 | ||
|
|
2faad9bf23 | ||
|
|
52223313b1 | ||
|
|
d6b173d5fe | ||
|
|
df74ff3c8b | ||
|
|
af5d7e2c49 | ||
|
|
d9118d324a | ||
|
|
5af82a8ed4 | ||
|
|
c68c13e1aa | ||
|
|
8b5588e776 | ||
|
|
dbed6c6485 | ||
|
|
ea78c78253 | ||
|
|
ab7f52b279 | ||
|
|
55a10d02e5 | ||
|
|
a2e22b4359 | ||
|
|
e1fea1e0c5 | ||
|
|
a1b845b651 | ||
|
|
b11e0b94c7 | ||
|
|
2df55985b6 | ||
|
|
8332482c24 | ||
|
|
3f1136ac6f | ||
|
|
7e13e8bfcb | ||
|
|
dd91650aaf | ||
|
|
d6f76307fe | ||
|
|
027f443e69 | ||
|
|
55e6786254 | ||
|
|
ec98e4d842 | ||
|
|
03e088a4f4 | ||
|
|
2d9136cec6 | ||
|
|
af5c6e4ccb | ||
|
|
4316eaf75c | ||
|
|
fc46ecddb3 | ||
|
|
148a6418ed | ||
|
|
21aff36459 | ||
|
|
59b34b1d76 | ||
|
|
57a900cc45 | ||
|
|
d4fae3a699 | ||
|
|
946c86f0bb | ||
|
|
c9cf899d18 | ||
|
|
4fd06efeb9 | ||
|
|
a40fd07516 | ||
|
|
b184ca9089 | ||
|
|
8d42feb09b | ||
|
|
21f9e9da09 | ||
|
|
68c99d2597 | ||
|
|
cf4a08d950 | ||
|
|
376a837511 | ||
|
|
518a2bd206 | ||
|
|
07944a2345 | ||
|
|
e8235c0215 | ||
|
|
6ca31f544a | ||
|
|
210620ff31 | ||
|
|
b0e7920838 | ||
|
|
becfdb8638 | ||
|
|
8f37531f8e | ||
|
|
8486e7f8c8 | ||
|
|
ccb71bece9 | ||
|
|
faadae5814 | ||
|
|
80183de884 | ||
|
|
078ba28e13 | ||
|
|
acdbbb8885 | ||
|
|
d79c462af0 | ||
|
|
a2819c204f | ||
|
|
39f1c6246a | ||
|
|
501284a81a | ||
|
|
e444a6553f | ||
|
|
3052eae25e | ||
|
|
8abe5ba2c8 | ||
|
|
c849b5b320 | ||
|
|
9118deb990 | ||
|
|
97f33f00cf | ||
|
|
abdbafbc20 | ||
|
|
802c23b8a8 | ||
|
|
2b90637f4b | ||
|
|
878672f371 | ||
|
|
c703f0aee4 | ||
|
|
8215ae942c | ||
|
|
4f95dc950e | ||
|
|
cacb934f21 | ||
|
|
0c049e0a21 | ||
|
|
e3f1233ce1 | ||
|
|
ea358bd4bf | ||
|
|
c9b2a1b051 | ||
|
|
2edee801ce | ||
|
|
06cb910c6d | ||
|
|
5e6a0a08c1 | ||
|
|
964ddeeb90 | ||
|
|
4ccaa1402d | ||
|
|
7c192ec43f | ||
|
|
4c70d5b8eb | ||
|
|
a4f052f6b3 | ||
|
|
0c6fb456e0 | ||
|
|
fbc67a0563 | ||
|
|
a58abbcfc4 | ||
|
|
bf07272695 | ||
|
|
60f044df56 | ||
|
|
e3c55e31d7 | ||
|
|
833d0806f9 | ||
|
|
6b9eea3fe5 | ||
|
|
12a95ff453 | ||
|
|
2f87fd060d | ||
|
|
15f431f0cb | ||
|
|
864e8f55cf | ||
|
|
5e639bfcf6 | ||
|
|
4aaa2192b9 | ||
|
|
15a0e1481d | ||
|
|
294df41b86 | ||
|
|
a0c8c16d07 | ||
|
|
7c6bb8c17f | ||
|
|
215ca770df | ||
|
|
bc879ae880 | ||
|
|
c9ef8b0af1 | ||
|
|
83e8ad2331 | ||
|
|
877a978a22 | ||
|
|
912f2a520a | ||
|
|
3121408a90 | ||
|
|
ac7d8983eb | ||
|
|
05dbb47af5 | ||
|
|
dbb1eb9c29 | ||
|
|
21f1b2889d | ||
|
|
26c9f12271 | ||
|
|
c6016856d8 | ||
|
|
19cf995225 | ||
|
|
23ebd4920e | ||
|
|
b683e41fca | ||
|
|
179ee963db | ||
|
|
17a68e5ebe | ||
|
|
e04d75f44c | ||
|
|
37ac1bb576 | ||
|
|
5933b3ea96 | ||
|
|
35c095898b | ||
|
|
ea4928393f | ||
|
|
2378ecd0e8 | ||
|
|
fc8a8789da | ||
|
|
9e8737b535 | ||
|
|
56e51da1d9 | ||
|
|
40aa1ea9f9 | ||
|
|
54d7b664da | ||
|
|
0f887daa72 | ||
|
|
15501477e7 | ||
|
|
0c5ede492f | ||
|
|
cb930c4b5a | ||
|
|
ef4c4e239d | ||
|
|
45da3be40e | ||
|
|
e00e1fc755 | ||
|
|
32bfa92c71 | ||
|
|
85a3368e6d | ||
|
|
69f622be36 | ||
|
|
4c20a39828 | ||
|
|
621b25b6be | ||
|
|
abbcc8e61e | ||
|
|
94db6e5f3f | ||
|
|
7b9c982d29 | ||
|
|
6d7514ccec | ||
|
|
e0b0947437 |
21
.github/ISSUE_TEMPLATE.md
vendored
21
.github/ISSUE_TEMPLATE.md
vendored
@@ -1,16 +1,27 @@
|
||||
<!--
|
||||
Please keep in mind yuzu is EXPERIMENTAL SOFTWARE.
|
||||
|
||||
Please read the FAQ: https://yuzu-emu.org/wiki/faq/
|
||||
Please read the FAQ:
|
||||
https://yuzu-emu.org/wiki/faq/
|
||||
|
||||
When submitting an issue, please do the following:
|
||||
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
|
||||
https://community.citra-emu.org/
|
||||
|
||||
- Provide the version (commit hash) of yuzu you are using.
|
||||
- Provide sufficient detail for the issue to be reproduced.
|
||||
- Provide:
|
||||
If the FAQ does not answer your question, please go to:
|
||||
https://community.citra-emu.org/
|
||||
|
||||
When submitting an issue, please check the following:
|
||||
|
||||
- You have read the above.
|
||||
- You have provided the version (commit hash) of yuzu you are using.
|
||||
- You have provided sufficient detail for the issue to be reproduced.
|
||||
- You have provided system specs (if relevant).
|
||||
- Please also provide:
|
||||
- For any issues, a log file
|
||||
- For crashes, a backtrace.
|
||||
- For graphical issues, comparison screenshots with real hardware.
|
||||
- For emulation inaccuracies, a test-case (if able).
|
||||
|
||||
-->
|
||||
|
||||
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -37,3 +37,6 @@
|
||||
[submodule "discord-rpc"]
|
||||
path = externals/discord-rpc
|
||||
url = https://github.com/discordapp/discord-rpc.git
|
||||
[submodule "Vulkan-Headers"]
|
||||
path = externals/Vulkan-Headers
|
||||
url = https://github.com/KhronosGroup/Vulkan-Headers.git
|
||||
|
||||
@@ -6,6 +6,8 @@ TRAVIS_BRANCH
|
||||
TRAVIS_BUILD_ID
|
||||
TRAVIS_BUILD_NUMBER
|
||||
TRAVIS_COMMIT
|
||||
TRAVIS_COMMIT_RANGE
|
||||
TRAVIS_EVENT_TYPE
|
||||
TRAVIS_JOB_ID
|
||||
TRAVIS_JOB_NUMBER
|
||||
TRAVIS_REPO_SLUG
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
#!/bin/bash -ex
|
||||
|
||||
apt-get update
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev wget cmake ninja-build ccache
|
||||
apt-get install --no-install-recommends -y build-essential git libqt5opengl5-dev libsdl2-dev libssl-dev python qtbase5-dev qtwebengine5-dev wget cmake ninja-build ccache
|
||||
|
||||
cd /yuzu
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=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 -G Ninja
|
||||
cmake .. -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 -G Ninja
|
||||
ninja
|
||||
|
||||
ccache -s
|
||||
|
||||
@@ -9,7 +9,7 @@ export PATH="/usr/local/opt/ccache/libexec:$PATH"
|
||||
|
||||
mkdir build && cd build
|
||||
cmake --version
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
|
||||
cmake .. -DYUZU_USE_BUNDLED_UNICORN=ON -DYUZU_USE_QT_WEB_ENGINE=ON -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${ENABLE_COMPATIBILITY_REPORTING:-"OFF"} -DUSE_DISCORD_PRESENCE=ON
|
||||
make -j4
|
||||
|
||||
ccache -s
|
||||
|
||||
@@ -19,8 +19,12 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(YUZU_USE_BUNDLED_UNICORN "Build/Download bundled Unicorn" ON)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
|
||||
option(ENABLE_CUBEB "Enables the cubeb audio backend" ON)
|
||||
|
||||
option(ENABLE_VULKAN "Enables Vulkan backend" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
if(NOT EXISTS ${PROJECT_SOURCE_DIR}/.git/hooks/pre-commit)
|
||||
@@ -302,7 +306,7 @@ endif()
|
||||
if (ENABLE_QT)
|
||||
if (YUZU_USE_BUNDLED_QT)
|
||||
if ((MSVC_VERSION GREATER_EQUAL 1910 AND MSVC_VERSION LESS 1920) AND ARCHITECTURE_x86_64)
|
||||
set(QT_VER qt-5.10.0-msvc2015_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()
|
||||
@@ -319,6 +323,10 @@ if (ENABLE_QT)
|
||||
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
|
||||
@@ -413,19 +421,6 @@ function(create_target_directory_groups target_name)
|
||||
endforeach()
|
||||
endfunction()
|
||||
|
||||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# generate git/build information
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
enable_testing()
|
||||
add_subdirectory(externals)
|
||||
add_subdirectory(src)
|
||||
|
||||
@@ -5,6 +5,7 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
set(Qt5_PLATFORMS_DIR "${Qt5_DIR}/../../../plugins/platforms/")
|
||||
set(Qt5_STYLES_DIR "${Qt5_DIR}/../../../plugins/styles/")
|
||||
set(Qt5_IMAGEFORMATS_DIR "${Qt5_DIR}/../../../plugins/imageformats/")
|
||||
set(Qt5_RESOURCES_DIR "${Qt5_DIR}/../../../resources/")
|
||||
set(PLATFORMS ${DLL_DEST}platforms/)
|
||||
set(STYLES ${DLL_DEST}styles/)
|
||||
set(IMAGEFORMATS ${DLL_DEST}imageformats/)
|
||||
@@ -17,7 +18,35 @@ function(copy_yuzu_Qt5_deps target_dir)
|
||||
Qt5OpenGL$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Widgets$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
|
||||
if (YUZU_USE_QT_WEB_ENGINE)
|
||||
windows_copy_files(${target_dir} ${Qt5_DLL_DIR} ${DLL_DEST}
|
||||
Qt5Network$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Positioning$<$<CONFIG:Debug>:d>.*
|
||||
Qt5PrintSupport$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Qml$<$<CONFIG:Debug>:d>.*
|
||||
Qt5Quick$<$<CONFIG:Debug>:d>.*
|
||||
Qt5QuickWidgets$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebChannel$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngine$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngineCore$<$<CONFIG:Debug>:d>.*
|
||||
Qt5WebEngineWidgets$<$<CONFIG:Debug>:d>.*
|
||||
QtWebEngineProcess$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
|
||||
windows_copy_files(${target_dir} ${Qt5_RESOURCES_DIR} ${DLL_DEST}
|
||||
qtwebengine_resources.pak
|
||||
qtwebengine_devtools_resources.pak
|
||||
qtwebengine_resources_100p.pak
|
||||
qtwebengine_resources_200p.pak
|
||||
icudtl.dat
|
||||
)
|
||||
endif ()
|
||||
|
||||
windows_copy_files(yuzu ${Qt5_PLATFORMS_DIR} ${PLATFORMS} qwindows$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_STYLES_DIR} ${STYLES} qwindowsvistastyle$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS} qjpeg$<$<CONFIG:Debug>:d>.*)
|
||||
windows_copy_files(yuzu ${Qt5_IMAGEFORMATS_DIR} ${IMAGEFORMATS}
|
||||
qjpeg$<$<CONFIG:Debug>:d>.*
|
||||
qgif$<$<CONFIG:Debug>:d>.*
|
||||
)
|
||||
endfunction(copy_yuzu_Qt5_deps)
|
||||
|
||||
94
CMakeModules/GenerateSCMRev.cmake
Normal file
94
CMakeModules/GenerateSCMRev.cmake
Normal file
@@ -0,0 +1,94 @@
|
||||
# Gets a UTC timstamp and sets the provided variable to it
|
||||
function(get_timestamp _var)
|
||||
string(TIMESTAMP timestamp UTC)
|
||||
set(${_var} "${timestamp}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${SRC_DIR}/externals/cmake-modules")
|
||||
# generate git/build information
|
||||
include(GetGitRevisionDescription)
|
||||
get_git_head_revision(GIT_REF_SPEC GIT_REV)
|
||||
git_describe(GIT_DESC --always --long --dirty)
|
||||
git_branch_name(GIT_BRANCH)
|
||||
get_timestamp(BUILD_DATE)
|
||||
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
if (BUILD_REPOSITORY)
|
||||
# regex capture the string nightly or canary into CMAKE_MATCH_1
|
||||
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
# capitalize the first letter of each word in the repo name.
|
||||
string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1})
|
||||
foreach(WORD ${REPO_NAME_LIST})
|
||||
string(SUBSTRING ${WORD} 0 1 FIRST_LETTER)
|
||||
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
|
||||
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
|
||||
endforeach()
|
||||
if (BUILD_TAG)
|
||||
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
set(BUILD_VERSION ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
if (BUILD_VERSION)
|
||||
# This leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
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_shader_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_half.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_half_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_integer.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_integer_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/bfe.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/bfi.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/conversion.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/ffma.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/float_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/float_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/hfma2.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/shader_ir.cpp"
|
||||
"${VIDEO_CORE}/shader/shader_ir.h"
|
||||
"${VIDEO_CORE}/shader/track.cpp"
|
||||
)
|
||||
set(COMBINED "")
|
||||
foreach (F IN LISTS HASH_FILES)
|
||||
file(READ ${F} TMP)
|
||||
set(COMBINED "${COMBINED}${TMP}")
|
||||
endforeach()
|
||||
string(MD5 SHADER_CACHE_VERSION "${COMBINED}")
|
||||
configure_file("${SRC_DIR}/src/common/scm_rev.cpp.in" "scm_rev.cpp" @ONLY)
|
||||
@@ -42,7 +42,7 @@ before_build:
|
||||
$COMPAT = if ($env:ENABLE_COMPATIBILITY_REPORTING -eq $null) {0} else {$env:ENABLE_COMPATIBILITY_REPORTING}
|
||||
if ($env:BUILD_TYPE -eq 'msvc') {
|
||||
# redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_BUNDLED_UNICORN=1 -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1 && exit 0'
|
||||
cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -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 .. 2>&1 && exit 0'
|
||||
} else {
|
||||
C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DYUZU_BUILD_UNICORN=1 -DCMAKE_BUILD_TYPE=Release -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DUSE_DISCORD_PRESENCE=ON .. 2>&1"
|
||||
}
|
||||
@@ -94,6 +94,7 @@ after_build:
|
||||
Copy-Item "$BUILD_DIR\*" -Destination $RELEASE_DIST -Recurse
|
||||
rm "$RELEASE_DIST\*.exe"
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "yuzu*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Get-ChildItem "$BUILD_DIR" -Recurse -Filter "QtWebEngineProcess*.exe" | Copy-Item -destination $RELEASE_DIST
|
||||
Copy-Item .\license.txt -Destination $RELEASE_DIST
|
||||
Copy-Item .\README.md -Destination $RELEASE_DIST
|
||||
7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
|
||||
|
||||
1
externals/Vulkan-Headers
vendored
Submodule
1
externals/Vulkan-Headers
vendored
Submodule
Submodule externals/Vulkan-Headers added at 15e5c4db75
2
externals/cubeb
vendored
2
externals/cubeb
vendored
Submodule externals/cubeb updated: 12b78c0edf...6f2420de8f
@@ -26,14 +26,15 @@ static Stream::Format ChannelsToStreamFormat(u32 num_channels) {
|
||||
return {};
|
||||
}
|
||||
|
||||
StreamPtr AudioOut::OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
|
||||
StreamPtr AudioOut::OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate,
|
||||
u32 num_channels, std::string&& name,
|
||||
Stream::ReleaseCallback&& release_callback) {
|
||||
if (!sink) {
|
||||
sink = CreateSinkFromID(Settings::values.sink_id, Settings::values.audio_device_id);
|
||||
}
|
||||
|
||||
return std::make_shared<Stream>(
|
||||
sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
|
||||
core_timing, sample_rate, ChannelsToStreamFormat(num_channels), std::move(release_callback),
|
||||
sink->AcquireSinkStream(sample_rate, num_channels, name), std::move(name));
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,10 @@
|
||||
#include "audio_core/stream.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
/**
|
||||
@@ -21,8 +25,8 @@ namespace AudioCore {
|
||||
class AudioOut {
|
||||
public:
|
||||
/// Opens a new audio stream
|
||||
StreamPtr OpenStream(u32 sample_rate, u32 num_channels, std::string&& name,
|
||||
Stream::ReleaseCallback&& release_callback);
|
||||
StreamPtr OpenStream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, u32 num_channels,
|
||||
std::string&& name, Stream::ReleaseCallback&& release_callback);
|
||||
|
||||
/// Returns a vector of recently released buffers specified by tag for the specified stream
|
||||
std::vector<Buffer::Tag> GetTagsAndReleaseBuffers(StreamPtr stream, std::size_t max_count);
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "audio_core/codec.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
@@ -71,14 +72,14 @@ private:
|
||||
EffectOutStatus out_status{};
|
||||
EffectInStatus info{};
|
||||
};
|
||||
AudioRenderer::AudioRenderer(AudioRendererParameter params,
|
||||
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::WritableEvent> buffer_event)
|
||||
: worker_params{params}, buffer_event{buffer_event}, voices(params.voice_count),
|
||||
effects(params.effect_count) {
|
||||
|
||||
audio_out = std::make_unique<AudioCore::AudioOut>();
|
||||
stream = audio_out->OpenStream(STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS, "AudioRenderer",
|
||||
[=]() { buffer_event->Signal(); });
|
||||
stream = audio_out->OpenStream(core_timing, STREAM_SAMPLE_RATE, STREAM_NUM_CHANNELS,
|
||||
"AudioRenderer", [=]() { buffer_event->Signal(); });
|
||||
audio_out->StartStream(stream);
|
||||
|
||||
QueueMixedBuffer(0);
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
#include "common/swap.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Kernel {
|
||||
class WritableEvent;
|
||||
}
|
||||
@@ -42,16 +46,18 @@ struct AudioRendererParameter {
|
||||
u32_le sample_rate;
|
||||
u32_le sample_count;
|
||||
u32_le mix_buffer_count;
|
||||
u32_le unknown_c;
|
||||
u32_le submix_count;
|
||||
u32_le voice_count;
|
||||
u32_le sink_count;
|
||||
u32_le effect_count;
|
||||
u32_le unknown_1c;
|
||||
u8 unknown_20;
|
||||
INSERT_PADDING_BYTES(3);
|
||||
u32_le performance_frame_count;
|
||||
u8 is_voice_drop_enabled;
|
||||
u8 unknown_21;
|
||||
u8 unknown_22;
|
||||
u8 execution_mode;
|
||||
u32_le splitter_count;
|
||||
u32_le unknown_2c;
|
||||
INSERT_PADDING_WORDS(1);
|
||||
u32_le num_splitter_send_channels;
|
||||
u32_le unknown_30;
|
||||
u32_le revision;
|
||||
};
|
||||
static_assert(sizeof(AudioRendererParameter) == 52, "AudioRendererParameter is an invalid size");
|
||||
@@ -208,7 +214,7 @@ static_assert(sizeof(UpdateDataHeader) == 0x40, "UpdateDataHeader has wrong size
|
||||
|
||||
class AudioRenderer {
|
||||
public:
|
||||
AudioRenderer(AudioRendererParameter params,
|
||||
AudioRenderer(Core::Timing::CoreTiming& core_timing, AudioRendererParameter params,
|
||||
Kernel::SharedPtr<Kernel::WritableEvent> buffer_event);
|
||||
~AudioRenderer();
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ public:
|
||||
Buffer(Tag tag, std::vector<s16>&& samples) : tag{tag}, samples{std::move(samples)} {}
|
||||
|
||||
/// Returns the raw audio data for the buffer
|
||||
std::vector<s16>& Samples() {
|
||||
std::vector<s16>& GetSamples() {
|
||||
return samples;
|
||||
}
|
||||
|
||||
|
||||
@@ -68,8 +68,8 @@ std::vector<s16> DecodeADPCM(const u8* const data, std::size_t size, const ADPCM
|
||||
}
|
||||
}
|
||||
|
||||
state.yn1 = yn1;
|
||||
state.yn2 = yn2;
|
||||
state.yn1 = static_cast<s16>(yn1);
|
||||
state.yn2 = static_cast<s16>(yn2);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "common/ring_buffer.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <objbase.h>
|
||||
#endif
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
class CubebSinkStream final : public SinkStream {
|
||||
@@ -46,7 +50,7 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
~CubebSinkStream() {
|
||||
~CubebSinkStream() override {
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
@@ -75,11 +79,11 @@ public:
|
||||
queue.Push(samples);
|
||||
}
|
||||
|
||||
std::size_t SamplesInQueue(u32 num_channels) const override {
|
||||
std::size_t SamplesInQueue(u32 channel_count) const override {
|
||||
if (!ctx)
|
||||
return 0;
|
||||
|
||||
return queue.Size() / num_channels;
|
||||
return queue.Size() / channel_count;
|
||||
}
|
||||
|
||||
void Flush() override {
|
||||
@@ -98,7 +102,7 @@ private:
|
||||
u32 num_channels{};
|
||||
|
||||
Common::RingBuffer<s16, 0x10000> queue;
|
||||
std::array<s16, 2> last_frame;
|
||||
std::array<s16, 2> last_frame{};
|
||||
std::atomic<bool> should_flush{};
|
||||
TimeStretcher time_stretch;
|
||||
|
||||
@@ -108,6 +112,11 @@ private:
|
||||
};
|
||||
|
||||
CubebSink::CubebSink(std::string_view target_device_name) {
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
#ifdef _MSC_VER
|
||||
com_init_result = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
#endif
|
||||
|
||||
if (cubeb_init(&ctx, "yuzu", nullptr) != CUBEB_OK) {
|
||||
LOG_CRITICAL(Audio_Sink, "cubeb_init failed");
|
||||
return;
|
||||
@@ -142,6 +151,12 @@ CubebSink::~CubebSink() {
|
||||
}
|
||||
|
||||
cubeb_destroy(ctx);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
if (SUCCEEDED(com_init_result)) {
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
SinkStream& CubebSink::AcquireSinkStream(u32 sample_rate, u32 num_channels,
|
||||
|
||||
@@ -25,6 +25,10 @@ private:
|
||||
cubeb* ctx{};
|
||||
cubeb_devid output_device{};
|
||||
std::vector<SinkStreamPtr> sink_streams;
|
||||
|
||||
#ifdef _MSC_VER
|
||||
u32 com_init_result = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
std::vector<std::string> ListCubebSinkDevices();
|
||||
|
||||
@@ -32,12 +32,12 @@ u32 Stream::GetNumChannels() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Stream::Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream, std::string&& name_)
|
||||
Stream::Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format,
|
||||
ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_)
|
||||
: sample_rate{sample_rate}, format{format}, release_callback{std::move(release_callback)},
|
||||
sink_stream{sink_stream}, name{std::move(name_)} {
|
||||
sink_stream{sink_stream}, core_timing{core_timing}, name{std::move(name_)} {
|
||||
|
||||
release_event = CoreTiming::RegisterEvent(
|
||||
release_event = core_timing.RegisterEvent(
|
||||
name, [this](u64 userdata, int cycles_late) { ReleaseActiveBuffer(); });
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ Stream::State Stream::GetState() const {
|
||||
|
||||
s64 Stream::GetBufferReleaseCycles(const Buffer& buffer) const {
|
||||
const std::size_t num_samples{buffer.GetSamples().size() / GetNumChannels()};
|
||||
return CoreTiming::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
return Core::Timing::usToCycles((static_cast<u64>(num_samples) * 1000000) / sample_rate);
|
||||
}
|
||||
|
||||
static void VolumeAdjustSamples(std::vector<s16>& samples) {
|
||||
@@ -68,7 +68,7 @@ static void VolumeAdjustSamples(std::vector<s16>& samples) {
|
||||
}
|
||||
|
||||
// Implementation of a volume slider with a dynamic range of 60 dB
|
||||
const float volume_scale_factor{std::exp(6.90775f * volume) * 0.001f};
|
||||
const float volume_scale_factor = volume == 0 ? 0 : std::exp(6.90775f * volume) * 0.001f;
|
||||
for (auto& sample : samples) {
|
||||
sample = static_cast<s16>(sample * volume_scale_factor);
|
||||
}
|
||||
@@ -95,11 +95,11 @@ void Stream::PlayNextBuffer() {
|
||||
active_buffer = queued_buffers.front();
|
||||
queued_buffers.pop();
|
||||
|
||||
VolumeAdjustSamples(active_buffer->Samples());
|
||||
VolumeAdjustSamples(active_buffer->GetSamples());
|
||||
|
||||
sink_stream.EnqueueSamples(GetNumChannels(), active_buffer->GetSamples());
|
||||
|
||||
CoreTiming::ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
core_timing.ScheduleEventThreadsafe(GetBufferReleaseCycles(*active_buffer), release_event, {});
|
||||
}
|
||||
|
||||
void Stream::ReleaseActiveBuffer() {
|
||||
|
||||
@@ -13,9 +13,10 @@
|
||||
#include "audio_core/buffer.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
}
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
@@ -42,8 +43,8 @@ public:
|
||||
/// Callback function type, used to change guest state on a buffer being released
|
||||
using ReleaseCallback = std::function<void()>;
|
||||
|
||||
Stream(u32 sample_rate, Format format, ReleaseCallback&& release_callback,
|
||||
SinkStream& sink_stream, std::string&& name_);
|
||||
Stream(Core::Timing::CoreTiming& core_timing, u32 sample_rate, Format format,
|
||||
ReleaseCallback&& release_callback, SinkStream& sink_stream, std::string&& name_);
|
||||
|
||||
/// Plays the audio stream
|
||||
void Play();
|
||||
@@ -91,16 +92,17 @@ private:
|
||||
/// Gets the number of core cycles when the specified buffer will be released
|
||||
s64 GetBufferReleaseCycles(const Buffer& buffer) const;
|
||||
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
Format format; ///< Format of the stream
|
||||
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||
State state{State::Stopped}; ///< Playback state of the stream
|
||||
CoreTiming::EventType* release_event{}; ///< Core timing release event for the stream
|
||||
BufferPtr active_buffer; ///< Actively playing buffer in the stream
|
||||
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
|
||||
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
|
||||
SinkStream& sink_stream; ///< Output sink for the stream
|
||||
std::string name; ///< Name of the stream, must be unique
|
||||
u32 sample_rate; ///< Sample rate of the stream
|
||||
Format format; ///< Format of the stream
|
||||
ReleaseCallback release_callback; ///< Buffer release callback for the stream
|
||||
State state{State::Stopped}; ///< Playback state of the stream
|
||||
Core::Timing::EventType* release_event{}; ///< Core timing release event for the stream
|
||||
BufferPtr active_buffer; ///< Actively playing buffer in the stream
|
||||
std::queue<BufferPtr> queued_buffers; ///< Buffers queued to be played in the stream
|
||||
std::queue<BufferPtr> released_buffers; ///< Buffers recently released from the stream
|
||||
SinkStream& sink_stream; ///< Output sink for the stream
|
||||
Core::Timing::CoreTiming& core_timing; ///< Core timing instance.
|
||||
std::string name; ///< Name of the stream, must be unique
|
||||
};
|
||||
|
||||
using StreamPtr = std::shared_ptr<Stream>;
|
||||
|
||||
@@ -53,8 +53,8 @@ std::size_t TimeStretcher::Process(const s16* in, std::size_t num_in, s16* out,
|
||||
const double lpf_gain = 1.0 - std::exp(-time_delta / lpf_time_scale);
|
||||
m_stretch_ratio += lpf_gain * (current_ratio - m_stretch_ratio);
|
||||
|
||||
// Place a lower limit of 5% speed. When a game boots up, there will be
|
||||
// many silence samples. These do not need to be timestretched.
|
||||
// Place a lower limit of 5% speed. When a game boots up, there will be
|
||||
// many silence samples. These do not need to be timestretched.
|
||||
m_stretch_ratio = std::max(m_stretch_ratio, 0.05);
|
||||
m_sound_touch.setTempo(m_stretch_ratio);
|
||||
|
||||
|
||||
@@ -1,42 +1,69 @@
|
||||
# Generate cpp with Git revision from template
|
||||
# Also if this is a CI build, add the build name (ie: Nightly, Canary) to the scm_rev file as well
|
||||
set(REPO_NAME "")
|
||||
set(BUILD_VERSION "0")
|
||||
if ($ENV{CI})
|
||||
if ($ENV{TRAVIS})
|
||||
# Add a custom command to generate a new shader_cache_version hash when any of the following files change
|
||||
# NOTE: This is an approximation of what files affect shader generation, its possible something else
|
||||
# could affect the result, but much more unlikely than the following files. Keeping a list of files
|
||||
# like this allows for much better caching since it doesn't force the user to recompile binary shaders every update
|
||||
set(VIDEO_CORE "${CMAKE_SOURCE_DIR}/src/video_core")
|
||||
if (DEFINED ENV{CI})
|
||||
if (DEFINED ENV{TRAVIS})
|
||||
set(BUILD_REPOSITORY $ENV{TRAVIS_REPO_SLUG})
|
||||
set(BUILD_TAG $ENV{TRAVIS_TAG})
|
||||
elseif($ENV{APPVEYOR})
|
||||
elseif(DEFINED ENV{APPVEYOR})
|
||||
set(BUILD_REPOSITORY $ENV{APPVEYOR_REPO_NAME})
|
||||
set(BUILD_TAG $ENV{APPVEYOR_REPO_TAG_NAME})
|
||||
endif()
|
||||
# regex capture the string nightly or canary into CMAKE_MATCH_1
|
||||
string(REGEX MATCH "yuzu-emu/yuzu-?(.*)" OUTVAR ${BUILD_REPOSITORY})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
# capitalize the first letter of each word in the repo name.
|
||||
string(REPLACE "-" ";" REPO_NAME_LIST ${CMAKE_MATCH_1})
|
||||
foreach(WORD ${REPO_NAME_LIST})
|
||||
string(SUBSTRING ${WORD} 0 1 FIRST_LETTER)
|
||||
string(SUBSTRING ${WORD} 1 -1 REMAINDER)
|
||||
string(TOUPPER ${FIRST_LETTER} FIRST_LETTER)
|
||||
set(REPO_NAME "${REPO_NAME}${FIRST_LETTER}${REMAINDER}")
|
||||
endforeach()
|
||||
if (BUILD_TAG)
|
||||
string(REGEX MATCH "${CMAKE_MATCH_1}-([0-9]+)" OUTVAR ${BUILD_TAG})
|
||||
if (${CMAKE_MATCH_COUNT} GREATER 0)
|
||||
set(BUILD_VERSION ${CMAKE_MATCH_1})
|
||||
endif()
|
||||
if (BUILD_VERSION)
|
||||
# This leaves a trailing space on the last word, but we actually want that
|
||||
# because of how it's styled in the title bar.
|
||||
set(BUILD_FULLNAME "${REPO_NAME} ${BUILD_VERSION} ")
|
||||
else()
|
||||
set(BUILD_FULLNAME "")
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp" @ONLY)
|
||||
add_custom_command(OUTPUT scm_rev.cpp
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
-DSRC_DIR="${CMAKE_SOURCE_DIR}"
|
||||
-DBUILD_REPOSITORY="${BUILD_REPOSITORY}"
|
||||
-DBUILD_TAG="${BUILD_TAG}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
|
||||
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_shader_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_decompiler.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_disk_cache.h"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_half.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_half_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_integer.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/arithmetic_integer_immediate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/bfe.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/bfi.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/conversion.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/ffma.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/float_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/float_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/half_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/hfma2.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/integer_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/memory.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/other.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/predicate_set_register.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/register_set_predicate.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/shift.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/video.cpp"
|
||||
"${VIDEO_CORE}/shader/decode/xmad.cpp"
|
||||
"${VIDEO_CORE}/shader/decode.cpp"
|
||||
"${VIDEO_CORE}/shader/shader_ir.cpp"
|
||||
"${VIDEO_CORE}/shader/shader_ir.h"
|
||||
"${VIDEO_CORE}/shader/track.cpp"
|
||||
# and also check that the scm_rev files haven't changed
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
|
||||
# technically we should regenerate if the git version changed, but its not worth the effort imo
|
||||
"${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
|
||||
)
|
||||
|
||||
add_library(common STATIC
|
||||
alignment.h
|
||||
|
||||
@@ -55,36 +55,36 @@ constexpr u8 Convert8To6(u8 value) {
|
||||
/**
|
||||
* Decode a color stored in RGBA8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGBA8(const u8* bytes) {
|
||||
return {bytes[3], bytes[2], bytes[1], bytes[0]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RGB8 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB8(const u8* bytes) {
|
||||
return {bytes[2], bytes[1], bytes[0], 255};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RG8 (aka HILO8) format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRG8(const u8* bytes) {
|
||||
return {bytes[1], bytes[0], 0, 255};
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode a color stored in RGB565 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert6To8((pixel >> 5) & 0x3F),
|
||||
@@ -94,9 +94,9 @@ inline Math::Vec4<u8> DecodeRGB565(const u8* bytes) {
|
||||
/**
|
||||
* Decode a color stored in RGB5A1 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert5To8((pixel >> 11) & 0x1F), Convert5To8((pixel >> 6) & 0x1F),
|
||||
@@ -106,9 +106,9 @@ inline Math::Vec4<u8> DecodeRGB5A1(const u8* bytes) {
|
||||
/**
|
||||
* Decode a color stored in RGBA4 format
|
||||
* @param bytes Pointer to encoded source color
|
||||
* @return Result color decoded as Math::Vec4<u8>
|
||||
* @return Result color decoded as Common::Vec4<u8>
|
||||
*/
|
||||
inline Math::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
inline Common::Vec4<u8> DecodeRGBA4(const u8* bytes) {
|
||||
u16_le pixel;
|
||||
std::memcpy(&pixel, bytes, sizeof(pixel));
|
||||
return {Convert4To8((pixel >> 12) & 0xF), Convert4To8((pixel >> 8) & 0xF),
|
||||
@@ -138,9 +138,9 @@ inline u32 DecodeD24(const u8* bytes) {
|
||||
/**
|
||||
* Decode a depth value and a stencil value stored in D24S8 format
|
||||
* @param bytes Pointer to encoded source values
|
||||
* @return Resulting values stored as a Math::Vec2
|
||||
* @return Resulting values stored as a Common::Vec2
|
||||
*/
|
||||
inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
inline Common::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
return {static_cast<u32>((bytes[2] << 16) | (bytes[1] << 8) | bytes[0]), bytes[3]};
|
||||
}
|
||||
|
||||
@@ -149,7 +149,7 @@ inline Math::Vec2<u32> DecodeD24S8(const u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGBA8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGBA8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[3] = color.r();
|
||||
bytes[2] = color.g();
|
||||
bytes[1] = color.b();
|
||||
@@ -161,7 +161,7 @@ inline void EncodeRGBA8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[2] = color.r();
|
||||
bytes[1] = color.g();
|
||||
bytes[0] = color.b();
|
||||
@@ -172,7 +172,7 @@ inline void EncodeRGB8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRG8(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
bytes[1] = color.r();
|
||||
bytes[0] = color.g();
|
||||
}
|
||||
@@ -181,7 +181,7 @@ inline void EncodeRG8(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB565(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16_le data =
|
||||
(Convert8To5(color.r()) << 11) | (Convert8To6(color.g()) << 5) | Convert8To5(color.b());
|
||||
|
||||
@@ -193,7 +193,7 @@ inline void EncodeRGB565(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGB5A1(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16_le data = (Convert8To5(color.r()) << 11) | (Convert8To5(color.g()) << 6) |
|
||||
(Convert8To5(color.b()) << 1) | Convert8To1(color.a());
|
||||
|
||||
@@ -205,7 +205,7 @@ inline void EncodeRGB5A1(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
* @param color Source color to encode
|
||||
* @param bytes Destination pointer to store encoded color
|
||||
*/
|
||||
inline void EncodeRGBA4(const Math::Vec4<u8>& color, u8* bytes) {
|
||||
inline void EncodeRGBA4(const Common::Vec4<u8>& color, u8* bytes) {
|
||||
const u16 data = (Convert8To4(color.r()) << 12) | (Convert8To4(color.g()) << 8) |
|
||||
(Convert8To4(color.b()) << 4) | Convert8To4(color.a());
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#define KEYS_DIR "keys"
|
||||
#define LOAD_DIR "load"
|
||||
#define DUMP_DIR "dump"
|
||||
#define SHADER_DIR "shader"
|
||||
#define LOG_DIR "log"
|
||||
|
||||
// Filenames
|
||||
|
||||
@@ -710,6 +710,7 @@ const std::string& GetUserPath(UserPath path, const std::string& new_path) {
|
||||
paths.emplace(UserPath::NANDDir, user_path + NAND_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::LoadDir, user_path + LOAD_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::DumpDir, user_path + DUMP_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::ShaderDir, user_path + SHADER_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::SysDataDir, user_path + SYSDATA_DIR DIR_SEP);
|
||||
paths.emplace(UserPath::KeysDir, user_path + KEYS_DIR DIR_SEP);
|
||||
// TODO: Put the logs in a better location for each OS
|
||||
|
||||
@@ -31,6 +31,7 @@ enum class UserPath {
|
||||
SDMCDir,
|
||||
LoadDir,
|
||||
DumpDir,
|
||||
ShaderDir,
|
||||
SysDataDir,
|
||||
UserDir,
|
||||
};
|
||||
|
||||
@@ -40,9 +40,7 @@ public:
|
||||
const Impl& operator=(Impl const&) = delete;
|
||||
|
||||
void PushEntry(Entry e) {
|
||||
std::lock_guard<std::mutex> lock(message_mutex);
|
||||
message_queue.Push(std::move(e));
|
||||
message_cv.notify_one();
|
||||
}
|
||||
|
||||
void AddBackend(std::unique_ptr<Backend> backend) {
|
||||
@@ -86,15 +84,13 @@ private:
|
||||
}
|
||||
};
|
||||
while (true) {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(message_mutex);
|
||||
message_cv.wait(lock, [&] { return !running || message_queue.Pop(entry); });
|
||||
}
|
||||
if (!running) {
|
||||
entry = message_queue.PopWait();
|
||||
if (entry.final_entry) {
|
||||
break;
|
||||
}
|
||||
write_logs(entry);
|
||||
}
|
||||
|
||||
// Drain the logging queue. Only writes out up to MAX_LOGS_TO_WRITE to prevent a case
|
||||
// where a system is repeatedly spamming logs even on close.
|
||||
const int MAX_LOGS_TO_WRITE = filter.IsDebug() ? INT_MAX : 100;
|
||||
@@ -106,14 +102,13 @@ private:
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
running = false;
|
||||
message_cv.notify_one();
|
||||
Entry entry;
|
||||
entry.final_entry = true;
|
||||
message_queue.Push(entry);
|
||||
backend_thread.join();
|
||||
}
|
||||
|
||||
std::atomic_bool running{true};
|
||||
std::mutex message_mutex, writing_mutex;
|
||||
std::condition_variable message_cv;
|
||||
std::mutex writing_mutex;
|
||||
std::thread backend_thread;
|
||||
std::vector<std::unique_ptr<Backend>> backends;
|
||||
Common::MPSCQueue<Log::Entry> message_queue;
|
||||
@@ -232,6 +227,7 @@ void DebuggerBackend::Write(const Entry& entry) {
|
||||
CLS(Render) \
|
||||
SUB(Render, Software) \
|
||||
SUB(Render, OpenGL) \
|
||||
SUB(Render, Vulkan) \
|
||||
CLS(Audio) \
|
||||
SUB(Audio, DSP) \
|
||||
SUB(Audio, Sink) \
|
||||
|
||||
@@ -27,6 +27,7 @@ struct Entry {
|
||||
unsigned int line_num;
|
||||
std::string function;
|
||||
std::string message;
|
||||
bool final_entry = false;
|
||||
|
||||
Entry() = default;
|
||||
Entry(Entry&& o) = default;
|
||||
|
||||
@@ -112,6 +112,7 @@ enum class Class : ClassType {
|
||||
Render, ///< Emulator video output and hardware acceleration
|
||||
Render_Software, ///< Software renderer backend
|
||||
Render_OpenGL, ///< OpenGL backend
|
||||
Render_Vulkan, ///< Vulkan backend
|
||||
Audio, ///< Audio emulation
|
||||
Audio_DSP, ///< The HLE implementation of the DSP
|
||||
Audio_Sink, ///< Emulator audio output backend
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
|
||||
namespace MathUtil {
|
||||
namespace Common {
|
||||
|
||||
constexpr float PI = 3.14159265f;
|
||||
|
||||
@@ -41,4 +41,4 @@ struct Rectangle {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace MathUtil
|
||||
} // namespace Common
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
|
||||
#include "common/vector_math.h"
|
||||
|
||||
namespace Math {
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
class Quaternion {
|
||||
public:
|
||||
Math::Vec3<T> xyz;
|
||||
Vec3<T> xyz;
|
||||
T w{};
|
||||
|
||||
Quaternion<decltype(-T{})> Inverse() const {
|
||||
@@ -38,12 +38,12 @@ public:
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) {
|
||||
auto QuaternionRotate(const Quaternion<T>& q, const Vec3<T>& v) {
|
||||
return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
|
||||
}
|
||||
|
||||
inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) {
|
||||
inline Quaternion<float> MakeQuaternion(const Vec3<float>& axis, float angle) {
|
||||
return {axis * std::sin(angle / 2), std::cos(angle / 2)};
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
} // namespace Common
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#define BUILD_DATE "@BUILD_DATE@"
|
||||
#define BUILD_FULLNAME "@BUILD_FULLNAME@"
|
||||
#define BUILD_VERSION "@BUILD_VERSION@"
|
||||
#define SHADER_CACHE_VERSION "@SHADER_CACHE_VERSION@"
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -21,6 +22,7 @@ const char g_build_name[] = BUILD_NAME;
|
||||
const char g_build_date[] = BUILD_DATE;
|
||||
const char g_build_fullname[] = BUILD_FULLNAME;
|
||||
const char g_build_version[] = BUILD_VERSION;
|
||||
const char g_shader_cache_version[] = SHADER_CACHE_VERSION;
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -13,5 +13,6 @@ extern const char g_build_name[];
|
||||
extern const char g_build_date[];
|
||||
extern const char g_build_fullname[];
|
||||
extern const char g_build_version[];
|
||||
extern const char g_shader_cache_version[];
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
#include <cstring>
|
||||
#include "common/common_types.h"
|
||||
|
||||
// GCC 4.6+
|
||||
#if __GNUC__ >= 5 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)
|
||||
// GCC
|
||||
#ifdef __GNUC__
|
||||
|
||||
#if __BYTE_ORDER__ && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) && !defined(COMMON_LITTLE_ENDIAN)
|
||||
#define COMMON_LITTLE_ENDIAN 1
|
||||
@@ -38,7 +38,7 @@
|
||||
#endif
|
||||
|
||||
// LLVM/clang
|
||||
#elif __clang__
|
||||
#elif defined(__clang__)
|
||||
|
||||
#if __LITTLE_ENDIAN__ && !defined(COMMON_LITTLE_ENDIAN)
|
||||
#define COMMON_LITTLE_ENDIAN 1
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
// a simple lockless thread-safe,
|
||||
// single reader, single writer queue
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <mutex>
|
||||
#include "common/common_types.h"
|
||||
#include <utility>
|
||||
|
||||
namespace Common {
|
||||
template <typename T, bool NeedSize = true>
|
||||
template <typename T>
|
||||
class SPSCQueue {
|
||||
public:
|
||||
SPSCQueue() : size(0) {
|
||||
SPSCQueue() {
|
||||
write_ptr = read_ptr = new ElementPtr();
|
||||
}
|
||||
~SPSCQueue() {
|
||||
@@ -25,13 +25,12 @@ public:
|
||||
delete read_ptr;
|
||||
}
|
||||
|
||||
u32 Size() const {
|
||||
static_assert(NeedSize, "using Size() on FifoQueue without NeedSize");
|
||||
std::size_t Size() const {
|
||||
return size.load();
|
||||
}
|
||||
|
||||
bool Empty() const {
|
||||
return !read_ptr->next.load();
|
||||
return Size() == 0;
|
||||
}
|
||||
|
||||
T& Front() const {
|
||||
@@ -47,13 +46,14 @@ public:
|
||||
ElementPtr* new_ptr = new ElementPtr();
|
||||
write_ptr->next.store(new_ptr, std::memory_order_release);
|
||||
write_ptr = new_ptr;
|
||||
if (NeedSize)
|
||||
size++;
|
||||
cv.notify_one();
|
||||
|
||||
++size;
|
||||
}
|
||||
|
||||
void Pop() {
|
||||
if (NeedSize)
|
||||
size--;
|
||||
--size;
|
||||
|
||||
ElementPtr* tmpptr = read_ptr;
|
||||
// advance the read pointer
|
||||
read_ptr = tmpptr->next.load();
|
||||
@@ -66,8 +66,7 @@ public:
|
||||
if (Empty())
|
||||
return false;
|
||||
|
||||
if (NeedSize)
|
||||
size--;
|
||||
--size;
|
||||
|
||||
ElementPtr* tmpptr = read_ptr;
|
||||
read_ptr = tmpptr->next.load(std::memory_order_acquire);
|
||||
@@ -77,6 +76,16 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
if (Empty()) {
|
||||
std::unique_lock<std::mutex> lock(cv_mutex);
|
||||
cv.wait(lock, [this]() { return !Empty(); });
|
||||
}
|
||||
T t;
|
||||
Pop(t);
|
||||
return t;
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
size.store(0);
|
||||
@@ -89,7 +98,7 @@ private:
|
||||
// and a pointer to the next ElementPtr
|
||||
class ElementPtr {
|
||||
public:
|
||||
ElementPtr() : next(nullptr) {}
|
||||
ElementPtr() {}
|
||||
~ElementPtr() {
|
||||
ElementPtr* next_ptr = next.load();
|
||||
|
||||
@@ -98,21 +107,23 @@ private:
|
||||
}
|
||||
|
||||
T current;
|
||||
std::atomic<ElementPtr*> next;
|
||||
std::atomic<ElementPtr*> next{nullptr};
|
||||
};
|
||||
|
||||
ElementPtr* write_ptr;
|
||||
ElementPtr* read_ptr;
|
||||
std::atomic<u32> size;
|
||||
std::atomic_size_t size{0};
|
||||
std::mutex cv_mutex;
|
||||
std::condition_variable cv;
|
||||
};
|
||||
|
||||
// a simple thread-safe,
|
||||
// single reader, multiple writer queue
|
||||
|
||||
template <typename T, bool NeedSize = true>
|
||||
template <typename T>
|
||||
class MPSCQueue {
|
||||
public:
|
||||
u32 Size() const {
|
||||
std::size_t Size() const {
|
||||
return spsc_queue.Size();
|
||||
}
|
||||
|
||||
@@ -138,13 +149,17 @@ public:
|
||||
return spsc_queue.Pop(t);
|
||||
}
|
||||
|
||||
T PopWait() {
|
||||
return spsc_queue.PopWait();
|
||||
}
|
||||
|
||||
// not thread-safe
|
||||
void Clear() {
|
||||
spsc_queue.Clear();
|
||||
}
|
||||
|
||||
private:
|
||||
SPSCQueue<T, NeedSize> spsc_queue;
|
||||
SPSCQueue<T> spsc_queue;
|
||||
std::mutex write_lock;
|
||||
};
|
||||
} // namespace Common
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include <cmath>
|
||||
#include <type_traits>
|
||||
|
||||
namespace Math {
|
||||
namespace Common {
|
||||
|
||||
template <typename T>
|
||||
class Vec2;
|
||||
@@ -690,4 +690,4 @@ constexpr Vec4<T> MakeVec(const T& x, const Vec3<T>& yzw) {
|
||||
return MakeVec(x, yzw[0], yzw[1], yzw[2]);
|
||||
}
|
||||
|
||||
} // namespace Math
|
||||
} // namespace Common
|
||||
|
||||
@@ -88,11 +88,15 @@ add_library(core STATIC
|
||||
frontend/applets/profile_select.h
|
||||
frontend/applets/software_keyboard.cpp
|
||||
frontend/applets/software_keyboard.h
|
||||
frontend/applets/web_browser.cpp
|
||||
frontend/applets/web_browser.h
|
||||
frontend/emu_window.cpp
|
||||
frontend/emu_window.h
|
||||
frontend/framebuffer_layout.cpp
|
||||
frontend/framebuffer_layout.h
|
||||
frontend/input.h
|
||||
frontend/scope_acquire_window_context.cpp
|
||||
frontend/scope_acquire_window_context.h
|
||||
gdbstub/gdbstub.cpp
|
||||
gdbstub/gdbstub.h
|
||||
hle/ipc.h
|
||||
@@ -136,8 +140,6 @@ add_library(core STATIC
|
||||
hle/kernel/svc_wrap.h
|
||||
hle/kernel/thread.cpp
|
||||
hle/kernel/thread.h
|
||||
hle/kernel/timer.cpp
|
||||
hle/kernel/timer.h
|
||||
hle/kernel/vm_manager.cpp
|
||||
hle/kernel/vm_manager.h
|
||||
hle/kernel/wait_object.cpp
|
||||
@@ -173,6 +175,8 @@ add_library(core STATIC
|
||||
hle/service/am/applets/software_keyboard.h
|
||||
hle/service/am/applets/stub_applet.cpp
|
||||
hle/service/am/applets/stub_applet.h
|
||||
hle/service/am/applets/web_browser.cpp
|
||||
hle/service/am/applets/web_browser.h
|
||||
hle/service/am/idle.cpp
|
||||
hle/service/am/idle.h
|
||||
hle/service/am/omm.cpp
|
||||
@@ -396,6 +400,10 @@ add_library(core STATIC
|
||||
hle/service/time/time.h
|
||||
hle/service/usb/usb.cpp
|
||||
hle/service/usb/usb.h
|
||||
hle/service/vi/display/vi_display.cpp
|
||||
hle/service/vi/display/vi_display.h
|
||||
hle/service/vi/layer/vi_layer.cpp
|
||||
hle/service/vi/layer/vi_layer.h
|
||||
hle/service/vi/vi.cpp
|
||||
hle/service/vi/vi.h
|
||||
hle/service/vi/vi_m.cpp
|
||||
|
||||
@@ -112,14 +112,14 @@ public:
|
||||
// Always execute at least one tick.
|
||||
amortized_ticks = std::max<u64>(amortized_ticks, 1);
|
||||
|
||||
CoreTiming::AddTicks(amortized_ticks);
|
||||
parent.core_timing.AddTicks(amortized_ticks);
|
||||
num_interpreted_instructions = 0;
|
||||
}
|
||||
u64 GetTicksRemaining() override {
|
||||
return std::max(CoreTiming::GetDowncount(), 0);
|
||||
return std::max(parent.core_timing.GetDowncount(), 0);
|
||||
}
|
||||
u64 GetCNTPCT() override {
|
||||
return CoreTiming::GetTicks();
|
||||
return parent.core_timing.GetTicks();
|
||||
}
|
||||
|
||||
ARM_Dynarmic& parent;
|
||||
@@ -172,8 +172,10 @@ void ARM_Dynarmic::Step() {
|
||||
cb->InterpreterFallback(jit->GetPC(), 1);
|
||||
}
|
||||
|
||||
ARM_Dynarmic::ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), core_index{core_index},
|
||||
ARM_Dynarmic::ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index)
|
||||
: cb(std::make_unique<ARM_Dynarmic_Callbacks>(*this)), inner_unicorn{core_timing},
|
||||
core_index{core_index}, core_timing{core_timing},
|
||||
exclusive_monitor{dynamic_cast<DynarmicExclusiveMonitor&>(exclusive_monitor)} {
|
||||
ThreadContext ctx{};
|
||||
inner_unicorn.SaveContext(ctx);
|
||||
|
||||
@@ -16,6 +16,10 @@ namespace Memory {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Dynarmic_Callbacks;
|
||||
@@ -23,7 +27,8 @@ class DynarmicExclusiveMonitor;
|
||||
|
||||
class ARM_Dynarmic final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Dynarmic(ExclusiveMonitor& exclusive_monitor, std::size_t core_index);
|
||||
ARM_Dynarmic(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
std::size_t core_index);
|
||||
~ARM_Dynarmic();
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
@@ -62,6 +67,7 @@ private:
|
||||
ARM_Unicorn inner_unicorn;
|
||||
|
||||
std::size_t core_index;
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
|
||||
@@ -72,7 +72,7 @@ static bool UnmappedMemoryHook(uc_engine* uc, uc_mem_type type, u64 addr, int si
|
||||
return {};
|
||||
}
|
||||
|
||||
ARM_Unicorn::ARM_Unicorn() {
|
||||
ARM_Unicorn::ARM_Unicorn(Timing::CoreTiming& core_timing) : core_timing{core_timing} {
|
||||
CHECKED(uc_open(UC_ARCH_ARM64, UC_MODE_ARM, &uc));
|
||||
|
||||
auto fpv = 3 << 20;
|
||||
@@ -177,7 +177,7 @@ void ARM_Unicorn::Run() {
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
ExecuteInstructions(std::max(4000000, 0));
|
||||
} else {
|
||||
ExecuteInstructions(std::max(CoreTiming::GetDowncount(), 0));
|
||||
ExecuteInstructions(std::max(core_timing.GetDowncount(), 0));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ MICROPROFILE_DEFINE(ARM_Jit_Unicorn, "ARM JIT", "Unicorn", MP_RGB(255, 64, 64));
|
||||
void ARM_Unicorn::ExecuteInstructions(int num_instructions) {
|
||||
MICROPROFILE_SCOPE(ARM_Jit_Unicorn);
|
||||
CHECKED(uc_emu_start(uc, GetPC(), 1ULL << 63, 0, num_instructions));
|
||||
CoreTiming::AddTicks(num_instructions);
|
||||
core_timing.AddTicks(num_instructions);
|
||||
if (GDBStub::IsServerEnabled()) {
|
||||
if (last_bkpt_hit) {
|
||||
uc_reg_write(uc, UC_ARM64_REG_PC, &last_bkpt.address);
|
||||
|
||||
@@ -9,12 +9,17 @@
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Unicorn final : public ARM_Interface {
|
||||
public:
|
||||
ARM_Unicorn();
|
||||
explicit ARM_Unicorn(Timing::CoreTiming& core_timing);
|
||||
~ARM_Unicorn();
|
||||
|
||||
void MapBackingMemory(VAddr address, std::size_t size, u8* memory,
|
||||
Kernel::VMAPermission perms) override;
|
||||
void UnmapMemory(VAddr address, std::size_t size) override;
|
||||
@@ -43,6 +48,7 @@ public:
|
||||
|
||||
private:
|
||||
uc_engine* uc{};
|
||||
Timing::CoreTiming& core_timing;
|
||||
GDBStub::BreakpointAddress last_bkpt{};
|
||||
bool last_bkpt_hit;
|
||||
};
|
||||
|
||||
@@ -30,8 +30,11 @@
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/perf_stats.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/telemetry_session.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
#include "frontend/applets/software_keyboard.h"
|
||||
#include "frontend/applets/web_browser.h"
|
||||
#include "video_core/debug_utils/debug_utils.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
@@ -91,8 +94,13 @@ struct System::Impl {
|
||||
ResultStatus Init(System& system, Frontend::EmuWindow& emu_window) {
|
||||
LOG_DEBUG(HW_Memory, "initialized OK");
|
||||
|
||||
CoreTiming::Init();
|
||||
kernel.Initialize();
|
||||
core_timing.Initialize();
|
||||
kernel.Initialize(core_timing);
|
||||
|
||||
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;
|
||||
|
||||
// Create a default fs if one doesn't already exist.
|
||||
if (virtual_filesystem == nullptr)
|
||||
@@ -103,6 +111,8 @@ struct System::Impl {
|
||||
profile_selector = std::make_unique<Core::Frontend::DefaultProfileSelectApplet>();
|
||||
if (software_keyboard == nullptr)
|
||||
software_keyboard = std::make_unique<Core::Frontend::DefaultSoftwareKeyboardApplet>();
|
||||
if (web_browser == nullptr)
|
||||
web_browser = std::make_unique<Core::Frontend::DefaultWebBrowserApplet>();
|
||||
|
||||
auto main_process = Kernel::Process::Create(kernel, "main");
|
||||
kernel.MakeCurrentProcess(main_process.get());
|
||||
@@ -110,15 +120,15 @@ struct System::Impl {
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
Service::Init(service_manager, *virtual_filesystem);
|
||||
Service::Init(service_manager, system, *virtual_filesystem);
|
||||
GDBStub::Init();
|
||||
|
||||
renderer = VideoCore::CreateRenderer(emu_window);
|
||||
renderer = VideoCore::CreateRenderer(emu_window, system);
|
||||
if (!renderer->Init()) {
|
||||
return ResultStatus::ErrorVideoCore;
|
||||
}
|
||||
|
||||
gpu_core = std::make_unique<Tegra::GPU>(renderer->Rasterizer());
|
||||
gpu_core = std::make_unique<Tegra::GPU>(system, renderer->Rasterizer());
|
||||
|
||||
cpu_core_manager.Initialize(system);
|
||||
is_powered_on = true;
|
||||
@@ -165,6 +175,7 @@ struct System::Impl {
|
||||
return static_cast<ResultStatus>(static_cast<u32>(ResultStatus::ErrorLoader) +
|
||||
static_cast<u32>(load_result));
|
||||
}
|
||||
|
||||
status = ResultStatus::Success;
|
||||
return status;
|
||||
}
|
||||
@@ -194,11 +205,16 @@ struct System::Impl {
|
||||
|
||||
// Shutdown kernel and core timing
|
||||
kernel.Shutdown();
|
||||
CoreTiming::Shutdown();
|
||||
core_timing.Shutdown();
|
||||
|
||||
// Close app loader
|
||||
app_loader.reset();
|
||||
|
||||
// Clear all applets
|
||||
profile_selector.reset();
|
||||
software_keyboard.reset();
|
||||
web_browser.reset();
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
@@ -216,9 +232,10 @@ struct System::Impl {
|
||||
}
|
||||
|
||||
PerfStatsResults GetAndResetPerfStats() {
|
||||
return perf_stats.GetAndResetStats(CoreTiming::GetGlobalTimeUs());
|
||||
return perf_stats.GetAndResetStats(core_timing.GetGlobalTimeUs());
|
||||
}
|
||||
|
||||
Timing::CoreTiming core_timing;
|
||||
Kernel::KernelCore kernel;
|
||||
/// RealVfsFilesystem instance
|
||||
FileSys::VirtualFilesystem virtual_filesystem;
|
||||
@@ -233,6 +250,7 @@ struct System::Impl {
|
||||
/// Frontend applets
|
||||
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
|
||||
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
|
||||
std::unique_ptr<Core::Frontend::WebBrowserApplet> web_browser;
|
||||
|
||||
/// Service manager
|
||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||
@@ -379,6 +397,14 @@ const Kernel::KernelCore& System::Kernel() const {
|
||||
return impl->kernel;
|
||||
}
|
||||
|
||||
Timing::CoreTiming& System::CoreTiming() {
|
||||
return impl->core_timing;
|
||||
}
|
||||
|
||||
const Timing::CoreTiming& System::CoreTiming() const {
|
||||
return impl->core_timing;
|
||||
}
|
||||
|
||||
Core::PerfStats& System::GetPerfStats() {
|
||||
return impl->perf_stats;
|
||||
}
|
||||
@@ -427,22 +453,34 @@ std::shared_ptr<FileSys::VfsFilesystem> System::GetFilesystem() const {
|
||||
return impl->virtual_filesystem;
|
||||
}
|
||||
|
||||
void System::SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet) {
|
||||
void System::SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet) {
|
||||
impl->profile_selector = std::move(applet);
|
||||
}
|
||||
|
||||
const Core::Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
|
||||
const Frontend::ProfileSelectApplet& System::GetProfileSelector() const {
|
||||
return *impl->profile_selector;
|
||||
}
|
||||
|
||||
void System::SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet) {
|
||||
void System::SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet) {
|
||||
impl->software_keyboard = std::move(applet);
|
||||
}
|
||||
|
||||
const Core::Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
|
||||
const Frontend::SoftwareKeyboardApplet& System::GetSoftwareKeyboard() const {
|
||||
return *impl->software_keyboard;
|
||||
}
|
||||
|
||||
void System::SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet) {
|
||||
impl->web_browser = std::move(applet);
|
||||
}
|
||||
|
||||
Frontend::WebBrowserApplet& System::GetWebBrowser() {
|
||||
return *impl->web_browser;
|
||||
}
|
||||
|
||||
const Frontend::WebBrowserApplet& System::GetWebBrowser() const {
|
||||
return *impl->web_browser;
|
||||
}
|
||||
|
||||
System::ResultStatus System::Init(Frontend::EmuWindow& emu_window) {
|
||||
return impl->Init(*this, emu_window);
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "frontend/applets/profile_select.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
class EmuWindow;
|
||||
class ProfileSelectApplet;
|
||||
class SoftwareKeyboardApplet;
|
||||
class WebBrowserApplet;
|
||||
} // namespace Core::Frontend
|
||||
|
||||
namespace FileSys {
|
||||
@@ -46,6 +47,10 @@ namespace VideoCore {
|
||||
class RendererBase;
|
||||
} // namespace VideoCore
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
@@ -204,6 +209,12 @@ public:
|
||||
/// Provides a constant pointer to the current process.
|
||||
const Kernel::Process* CurrentProcess() const;
|
||||
|
||||
/// Provides a reference to the core timing instance.
|
||||
Timing::CoreTiming& CoreTiming();
|
||||
|
||||
/// Provides a constant reference to the core timing instance.
|
||||
const Timing::CoreTiming& CoreTiming() const;
|
||||
|
||||
/// Provides a reference to the kernel instance.
|
||||
Kernel::KernelCore& Kernel();
|
||||
|
||||
@@ -242,13 +253,18 @@ public:
|
||||
|
||||
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
|
||||
|
||||
void SetProfileSelector(std::unique_ptr<Core::Frontend::ProfileSelectApplet> applet);
|
||||
void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
|
||||
|
||||
const Core::Frontend::ProfileSelectApplet& GetProfileSelector() const;
|
||||
const Frontend::ProfileSelectApplet& GetProfileSelector() const;
|
||||
|
||||
void SetSoftwareKeyboard(std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> applet);
|
||||
void SetSoftwareKeyboard(std::unique_ptr<Frontend::SoftwareKeyboardApplet> applet);
|
||||
|
||||
const Core::Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
|
||||
const Frontend::SoftwareKeyboardApplet& GetSoftwareKeyboard() const;
|
||||
|
||||
void SetWebBrowser(std::unique_ptr<Frontend::WebBrowserApplet> applet);
|
||||
|
||||
Frontend::WebBrowserApplet& GetWebBrowser();
|
||||
const Frontend::WebBrowserApplet& GetWebBrowser() const;
|
||||
|
||||
private:
|
||||
System();
|
||||
|
||||
@@ -49,17 +49,18 @@ bool CpuBarrier::Rendezvous() {
|
||||
return false;
|
||||
}
|
||||
|
||||
Cpu::Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_index{core_index} {
|
||||
Cpu::Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index)
|
||||
: cpu_barrier{cpu_barrier}, core_timing{core_timing}, core_index{core_index} {
|
||||
if (Settings::values.use_cpu_jit) {
|
||||
#ifdef ARCHITECTURE_x86_64
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(exclusive_monitor, core_index);
|
||||
arm_interface = std::make_unique<ARM_Dynarmic>(core_timing, exclusive_monitor, core_index);
|
||||
#else
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
|
||||
#endif
|
||||
} else {
|
||||
arm_interface = std::make_unique<ARM_Unicorn>();
|
||||
arm_interface = std::make_unique<ARM_Unicorn>(core_timing);
|
||||
}
|
||||
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(*arm_interface);
|
||||
@@ -93,14 +94,14 @@ void Cpu::RunLoop(bool tight_loop) {
|
||||
|
||||
if (IsMainCore()) {
|
||||
// TODO(Subv): Only let CoreTiming idle if all 4 cores are idling.
|
||||
CoreTiming::Idle();
|
||||
CoreTiming::Advance();
|
||||
core_timing.Idle();
|
||||
core_timing.Advance();
|
||||
}
|
||||
|
||||
PrepareReschedule();
|
||||
} else {
|
||||
if (IsMainCore()) {
|
||||
CoreTiming::Advance();
|
||||
core_timing.Advance();
|
||||
}
|
||||
|
||||
if (tight_loop) {
|
||||
|
||||
@@ -15,6 +15,10 @@ namespace Kernel {
|
||||
class Scheduler;
|
||||
}
|
||||
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class ARM_Interface;
|
||||
@@ -41,7 +45,8 @@ private:
|
||||
|
||||
class Cpu {
|
||||
public:
|
||||
Cpu(ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
Cpu(Timing::CoreTiming& core_timing, ExclusiveMonitor& exclusive_monitor,
|
||||
CpuBarrier& cpu_barrier, std::size_t core_index);
|
||||
~Cpu();
|
||||
|
||||
void RunLoop(bool tight_loop = true);
|
||||
@@ -82,6 +87,7 @@ private:
|
||||
std::unique_ptr<ARM_Interface> arm_interface;
|
||||
CpuBarrier& cpu_barrier;
|
||||
std::unique_ptr<Kernel::Scheduler> scheduler;
|
||||
Timing::CoreTiming& core_timing;
|
||||
|
||||
std::atomic<bool> reschedule_pending = false;
|
||||
std::size_t core_index;
|
||||
|
||||
@@ -8,71 +8,60 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/thread.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
#include "core/core_timing_util.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
namespace Core::Timing {
|
||||
|
||||
static s64 global_timer;
|
||||
static int slice_length;
|
||||
static int downcount;
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
|
||||
struct EventType {
|
||||
TimedCallback callback;
|
||||
const std::string* name;
|
||||
};
|
||||
|
||||
struct Event {
|
||||
struct CoreTiming::Event {
|
||||
s64 time;
|
||||
u64 fifo_order;
|
||||
u64 userdata;
|
||||
const EventType* type;
|
||||
|
||||
// Sort by time, unless the times are the same, in which case sort by
|
||||
// the order added to the queue
|
||||
friend bool operator>(const Event& left, const Event& right) {
|
||||
return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order);
|
||||
}
|
||||
|
||||
friend bool operator<(const Event& left, const Event& right) {
|
||||
return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order);
|
||||
}
|
||||
};
|
||||
|
||||
// Sort by time, unless the times are the same, in which case sort by the order added to the queue
|
||||
static bool operator>(const Event& left, const Event& right) {
|
||||
return std::tie(left.time, left.fifo_order) > std::tie(right.time, right.fifo_order);
|
||||
CoreTiming::CoreTiming() = default;
|
||||
CoreTiming::~CoreTiming() = default;
|
||||
|
||||
void CoreTiming::Initialize() {
|
||||
downcount = MAX_SLICE_LENGTH;
|
||||
slice_length = MAX_SLICE_LENGTH;
|
||||
global_timer = 0;
|
||||
idled_cycles = 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;
|
||||
|
||||
event_fifo_id = 0;
|
||||
|
||||
const auto empty_timed_callback = [](u64, s64) {};
|
||||
ev_lost = RegisterEvent("_lost_event", empty_timed_callback);
|
||||
}
|
||||
|
||||
static bool operator<(const Event& left, const Event& right) {
|
||||
return std::tie(left.time, left.fifo_order) < std::tie(right.time, right.fifo_order);
|
||||
void CoreTiming::Shutdown() {
|
||||
MoveEvents();
|
||||
ClearPendingEvents();
|
||||
UnregisterAllEvents();
|
||||
}
|
||||
|
||||
// unordered_map stores each element separately as a linked list node so pointers to elements
|
||||
// remain stable regardless of rehashes/resizing.
|
||||
static std::unordered_map<std::string, EventType> event_types;
|
||||
|
||||
// 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
|
||||
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't accomodated
|
||||
// by the standard adaptor class.
|
||||
static std::vector<Event> event_queue;
|
||||
static u64 event_fifo_id;
|
||||
// the queue for storing the events from other threads threadsafe until they will be added
|
||||
// to the event_queue by the emu thread
|
||||
static Common::MPSCQueue<Event, false> ts_queue;
|
||||
|
||||
// the queue for unscheduling the events from other threads threadsafe
|
||||
static Common::MPSCQueue<std::pair<const EventType*, u64>, false> unschedule_queue;
|
||||
|
||||
constexpr int MAX_SLICE_LENGTH = 20000;
|
||||
|
||||
static s64 idled_cycles;
|
||||
|
||||
// Are we in a function that has been called from Advance()
|
||||
// If events are sheduled from a function that gets called from Advance(),
|
||||
// don't change slice_length and downcount.
|
||||
static bool is_global_timer_sane;
|
||||
|
||||
static EventType* ev_lost = nullptr;
|
||||
|
||||
static void EmptyTimedCallback(u64 userdata, s64 cyclesLate) {}
|
||||
|
||||
EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
EventType* CoreTiming::RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
// check for existing type with same name.
|
||||
// we want event type names to remain unique so that we can use them for serialization.
|
||||
ASSERT_MSG(event_types.find(name) == event_types.end(),
|
||||
@@ -86,71 +75,31 @@ EventType* RegisterEvent(const std::string& name, TimedCallback callback) {
|
||||
return event_type;
|
||||
}
|
||||
|
||||
void UnregisterAllEvents() {
|
||||
void CoreTiming::UnregisterAllEvents() {
|
||||
ASSERT_MSG(event_queue.empty(), "Cannot unregister events with events pending");
|
||||
event_types.clear();
|
||||
}
|
||||
|
||||
void Init() {
|
||||
downcount = MAX_SLICE_LENGTH;
|
||||
slice_length = MAX_SLICE_LENGTH;
|
||||
global_timer = 0;
|
||||
idled_cycles = 0;
|
||||
|
||||
// The time between CoreTiming being intialized 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;
|
||||
|
||||
event_fifo_id = 0;
|
||||
ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
MoveEvents();
|
||||
ClearPendingEvents();
|
||||
UnregisterAllEvents();
|
||||
}
|
||||
|
||||
// This should only be called from the CPU thread. If you are calling
|
||||
// it from any other thread, you are doing something evil
|
||||
u64 GetTicks() {
|
||||
u64 ticks = static_cast<u64>(global_timer);
|
||||
if (!is_global_timer_sane) {
|
||||
ticks += slice_length - downcount;
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
void AddTicks(u64 ticks) {
|
||||
downcount -= static_cast<int>(ticks);
|
||||
}
|
||||
|
||||
u64 GetIdleTicks() {
|
||||
return static_cast<u64>(idled_cycles);
|
||||
}
|
||||
|
||||
void ClearPendingEvents() {
|
||||
event_queue.clear();
|
||||
}
|
||||
|
||||
void ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
void CoreTiming::ScheduleEvent(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
ASSERT(event_type != nullptr);
|
||||
s64 timeout = GetTicks() + cycles_into_future;
|
||||
const s64 timeout = GetTicks() + cycles_into_future;
|
||||
|
||||
// If this event needs to be scheduled before the next advance(), force one early
|
||||
if (!is_global_timer_sane)
|
||||
if (!is_global_timer_sane) {
|
||||
ForceExceptionCheck(cycles_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<>());
|
||||
}
|
||||
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata) {
|
||||
void CoreTiming::ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
|
||||
u64 userdata) {
|
||||
ts_queue.Push(Event{global_timer + cycles_into_future, 0, userdata, event_type});
|
||||
}
|
||||
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
void CoreTiming::UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(), [&](const Event& e) {
|
||||
return e.type == event_type && e.userdata == userdata;
|
||||
});
|
||||
|
||||
@@ -161,13 +110,33 @@ void UnscheduleEvent(const EventType* event_type, u64 userdata) {
|
||||
}
|
||||
}
|
||||
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
|
||||
void CoreTiming::UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata) {
|
||||
unschedule_queue.Push(std::make_pair(event_type, userdata));
|
||||
}
|
||||
|
||||
void RemoveEvent(const EventType* event_type) {
|
||||
auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
u64 CoreTiming::GetTicks() const {
|
||||
u64 ticks = static_cast<u64>(global_timer);
|
||||
if (!is_global_timer_sane) {
|
||||
ticks += slice_length - downcount;
|
||||
}
|
||||
return ticks;
|
||||
}
|
||||
|
||||
u64 CoreTiming::GetIdleTicks() const {
|
||||
return static_cast<u64>(idled_cycles);
|
||||
}
|
||||
|
||||
void CoreTiming::AddTicks(u64 ticks) {
|
||||
downcount -= static_cast<int>(ticks);
|
||||
}
|
||||
|
||||
void CoreTiming::ClearPendingEvents() {
|
||||
event_queue.clear();
|
||||
}
|
||||
|
||||
void CoreTiming::RemoveEvent(const EventType* event_type) {
|
||||
const auto itr = std::remove_if(event_queue.begin(), event_queue.end(),
|
||||
[&](const Event& e) { return e.type == event_type; });
|
||||
|
||||
// Removing random items breaks the invariant so we have to re-establish it.
|
||||
if (itr != event_queue.end()) {
|
||||
@@ -176,22 +145,24 @@ void RemoveEvent(const EventType* event_type) {
|
||||
}
|
||||
}
|
||||
|
||||
void RemoveNormalAndThreadsafeEvent(const EventType* event_type) {
|
||||
void CoreTiming::RemoveNormalAndThreadsafeEvent(const EventType* event_type) {
|
||||
MoveEvents();
|
||||
RemoveEvent(event_type);
|
||||
}
|
||||
|
||||
void ForceExceptionCheck(s64 cycles) {
|
||||
void CoreTiming::ForceExceptionCheck(s64 cycles) {
|
||||
cycles = std::max<s64>(0, cycles);
|
||||
if (downcount > cycles) {
|
||||
// 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
|
||||
slice_length -= downcount - static_cast<int>(cycles);
|
||||
downcount = static_cast<int>(cycles);
|
||||
if (downcount <= 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
|
||||
slice_length -= downcount - static_cast<int>(cycles);
|
||||
downcount = static_cast<int>(cycles);
|
||||
}
|
||||
|
||||
void MoveEvents() {
|
||||
void CoreTiming::MoveEvents() {
|
||||
for (Event ev; ts_queue.Pop(ev);) {
|
||||
ev.fifo_order = event_fifo_id++;
|
||||
event_queue.emplace_back(std::move(ev));
|
||||
@@ -199,13 +170,13 @@ void MoveEvents() {
|
||||
}
|
||||
}
|
||||
|
||||
void Advance() {
|
||||
void CoreTiming::Advance() {
|
||||
MoveEvents();
|
||||
for (std::pair<const EventType*, u64> ev; unschedule_queue.Pop(ev);) {
|
||||
UnscheduleEvent(ev.first, ev.second);
|
||||
}
|
||||
|
||||
int cycles_executed = slice_length - downcount;
|
||||
const int cycles_executed = slice_length - downcount;
|
||||
global_timer += cycles_executed;
|
||||
slice_length = MAX_SLICE_LENGTH;
|
||||
|
||||
@@ -229,17 +200,17 @@ void Advance() {
|
||||
downcount = slice_length;
|
||||
}
|
||||
|
||||
void Idle() {
|
||||
void CoreTiming::Idle() {
|
||||
idled_cycles += downcount;
|
||||
downcount = 0;
|
||||
}
|
||||
|
||||
std::chrono::microseconds GetGlobalTimeUs() {
|
||||
std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
|
||||
return std::chrono::microseconds{GetTicks() * 1000000 / BASE_CLOCK_RATE};
|
||||
}
|
||||
|
||||
int GetDowncount() {
|
||||
int CoreTiming::GetDowncount() const {
|
||||
return downcount;
|
||||
}
|
||||
|
||||
} // namespace CoreTiming
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -4,6 +4,27 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
#include "common/threadsafe_queue.h"
|
||||
|
||||
namespace Core::Timing {
|
||||
|
||||
/// A callback that may be scheduled for a particular core timing event.
|
||||
using TimedCallback = std::function<void(u64 userdata, int cycles_late)>;
|
||||
|
||||
/// Contains the characteristics of a particular event.
|
||||
struct EventType {
|
||||
/// The event's callback function.
|
||||
TimedCallback callback;
|
||||
/// A pointer to the name of the event.
|
||||
const std::string* name;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is a system to schedule events into the emulated machine's future. Time is measured
|
||||
* in main CPU clock cycles.
|
||||
@@ -16,80 +37,120 @@
|
||||
* inside callback:
|
||||
* ScheduleEvent(periodInCycles - cyclesLate, callback, "whatever")
|
||||
*/
|
||||
class CoreTiming {
|
||||
public:
|
||||
CoreTiming();
|
||||
~CoreTiming();
|
||||
|
||||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include "common/common_types.h"
|
||||
CoreTiming(const CoreTiming&) = delete;
|
||||
CoreTiming(CoreTiming&&) = delete;
|
||||
|
||||
namespace CoreTiming {
|
||||
CoreTiming& operator=(const CoreTiming&) = delete;
|
||||
CoreTiming& operator=(CoreTiming&&) = delete;
|
||||
|
||||
struct EventType;
|
||||
/// 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();
|
||||
|
||||
using TimedCallback = std::function<void(u64 userdata, int cycles_late)>;
|
||||
/// Tears down all timing related functionality.
|
||||
void Shutdown();
|
||||
|
||||
/**
|
||||
* 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 Init();
|
||||
void Shutdown();
|
||||
/// Registers a core timing event with the given name and callback.
|
||||
///
|
||||
/// @param name The name of the core timing event to register.
|
||||
/// @param callback The callback to execute for the event.
|
||||
///
|
||||
/// @returns An EventType instance representing the registered event.
|
||||
///
|
||||
/// @pre The name of the event being registered must be unique among all
|
||||
/// registered events.
|
||||
///
|
||||
EventType* RegisterEvent(const std::string& name, TimedCallback callback);
|
||||
|
||||
/**
|
||||
* This should only be called from the emu thread, if you are calling it any other thread, you are
|
||||
* doing something evil
|
||||
*/
|
||||
u64 GetTicks();
|
||||
u64 GetIdleTicks();
|
||||
void AddTicks(u64 ticks);
|
||||
/// Unregisters all registered events thus far.
|
||||
void UnregisterAllEvents();
|
||||
|
||||
/**
|
||||
* Returns the event_type identifier. if name is not unique, it will assert.
|
||||
*/
|
||||
EventType* RegisterEvent(const std::string& name, TimedCallback callback);
|
||||
void UnregisterAllEvents();
|
||||
/// 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 EventType* event_type, u64 userdata = 0);
|
||||
|
||||
/**
|
||||
* 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 EventType* event_type, u64 userdata = 0);
|
||||
/// This is to be called when outside of hle threads, such as the graphics thread, wants to
|
||||
/// schedule things to be executed on the main thread.
|
||||
///
|
||||
/// @note This doesn't change slice_length and thus events scheduled by this might be
|
||||
/// called with a delay of up to MAX_SLICE_LENGTH
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type,
|
||||
u64 userdata = 0);
|
||||
|
||||
/**
|
||||
* This is to be called when outside of hle threads, such as the graphics thread, wants to
|
||||
* schedule things to be executed on the main thread.
|
||||
* Not that this doesn't change slice_length and thus events scheduled by this might be called
|
||||
* with a delay of up to MAX_SLICE_LENGTH
|
||||
*/
|
||||
void ScheduleEventThreadsafe(s64 cycles_into_future, const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
|
||||
|
||||
void UnscheduleEvent(const EventType* event_type, u64 userdata);
|
||||
void UnscheduleEventThreadsafe(const EventType* event_type, u64 userdata);
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const EventType* event_type);
|
||||
void RemoveNormalAndThreadsafeEvent(const EventType* event_type);
|
||||
|
||||
/// We only permit one event of each type in the queue at a time.
|
||||
void RemoveEvent(const EventType* event_type);
|
||||
void RemoveNormalAndThreadsafeEvent(const EventType* event_type);
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
|
||||
/** 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 MoveEvents();
|
||||
/// 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;
|
||||
|
||||
/// Pretend that the main CPU has executed enough cycles to reach the next event.
|
||||
void Idle();
|
||||
u64 GetIdleTicks() const;
|
||||
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
void AddTicks(u64 ticks);
|
||||
|
||||
void ForceExceptionCheck(s64 cycles);
|
||||
/// 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();
|
||||
|
||||
std::chrono::microseconds GetGlobalTimeUs();
|
||||
/// Pretend that the main CPU has executed enough cycles to reach the next event.
|
||||
void Idle();
|
||||
|
||||
int GetDowncount();
|
||||
std::chrono::microseconds GetGlobalTimeUs() const;
|
||||
|
||||
} // namespace CoreTiming
|
||||
int GetDowncount() const;
|
||||
|
||||
private:
|
||||
struct Event;
|
||||
|
||||
/// Clear all pending events. This should ONLY be done on exit.
|
||||
void ClearPendingEvents();
|
||||
void MoveEvents();
|
||||
|
||||
s64 global_timer = 0;
|
||||
s64 idled_cycles = 0;
|
||||
int slice_length = 0;
|
||||
int downcount = 0;
|
||||
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
// erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
|
||||
// accomodated by the standard adaptor class.
|
||||
std::vector<Event> event_queue;
|
||||
u64 event_fifo_id = 0;
|
||||
|
||||
// Stores each element separately as a linked list node so pointers to elements
|
||||
// remain stable regardless of rehashes/resizing.
|
||||
std::unordered_map<std::string, EventType> event_types;
|
||||
|
||||
// The queue for storing the events from other threads threadsafe until they will be added
|
||||
// to the event_queue by the emu thread
|
||||
Common::MPSCQueue<Event> ts_queue;
|
||||
|
||||
// The queue for unscheduling the events from other threads threadsafe
|
||||
Common::MPSCQueue<std::pair<const EventType*, u64>> unschedule_queue;
|
||||
|
||||
EventType* ev_lost = nullptr;
|
||||
};
|
||||
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include <limits>
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
namespace Core::Timing {
|
||||
|
||||
constexpr u64 MAX_VALUE_TO_MULTIPLY = std::numeric_limits<s64>::max() / BASE_CLOCK_RATE;
|
||||
|
||||
@@ -60,4 +60,4 @@ s64 nsToCycles(u64 ns) {
|
||||
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
|
||||
}
|
||||
|
||||
} // namespace CoreTiming
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace CoreTiming {
|
||||
namespace Core::Timing {
|
||||
|
||||
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
|
||||
// The exact value used is of course unverified.
|
||||
@@ -61,4 +61,4 @@ inline u64 cyclesToMs(s64 cycles) {
|
||||
return cycles * 1000 / BASE_CLOCK_RATE;
|
||||
}
|
||||
|
||||
} // namespace CoreTiming
|
||||
} // namespace Core::Timing
|
||||
|
||||
@@ -27,7 +27,8 @@ void CpuCoreManager::Initialize(System& system) {
|
||||
exclusive_monitor = Cpu::MakeExclusiveMonitor(cores.size());
|
||||
|
||||
for (std::size_t index = 0; index < cores.size(); ++index) {
|
||||
cores[index] = std::make_unique<Cpu>(*exclusive_monitor, *barrier, index);
|
||||
cores[index] =
|
||||
std::make_unique<Cpu>(system.CoreTiming(), *exclusive_monitor, *barrier, index);
|
||||
}
|
||||
|
||||
// Create threads for CPU cores 1-3, and build thread_to_cpu map
|
||||
|
||||
@@ -398,7 +398,8 @@ static bool ValidCryptoRevisionString(std::string_view base, size_t begin, size_
|
||||
}
|
||||
|
||||
void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
|
||||
std::ifstream file(filename);
|
||||
std::ifstream file;
|
||||
OpenFStream(file, filename, std::ios_base::in);
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
|
||||
@@ -359,6 +359,8 @@ bool NCA::ReadPFS0Section(const NCASectionHeader& section, const NCASectionTable
|
||||
dirs.push_back(std::move(npfs));
|
||||
if (IsDirectoryExeFS(dirs.back()))
|
||||
exefs = dirs.back();
|
||||
else if (IsDirectoryLogoPartition(dirs.back()))
|
||||
logo = dirs.back();
|
||||
} else {
|
||||
if (has_rights_id)
|
||||
status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
|
||||
@@ -546,4 +548,8 @@ u64 NCA::GetBaseIVFCOffset() const {
|
||||
return ivfc_offset;
|
||||
}
|
||||
|
||||
VirtualDir NCA::GetLogoPartition() const {
|
||||
return logo;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -74,6 +74,13 @@ inline bool IsDirectoryExeFS(const std::shared_ptr<VfsDirectory>& pfs) {
|
||||
return pfs->GetFile("main") != nullptr && pfs->GetFile("main.npdm") != nullptr;
|
||||
}
|
||||
|
||||
inline bool IsDirectoryLogoPartition(const VirtualDir& pfs) {
|
||||
// NintendoLogo is the static image in the top left corner while StartupMovie is the animation
|
||||
// in the bottom right corner.
|
||||
return pfs->GetFile("NintendoLogo.png") != nullptr &&
|
||||
pfs->GetFile("StartupMovie.gif") != nullptr;
|
||||
}
|
||||
|
||||
// An implementation of VfsDirectory that represents a Nintendo Content Archive (NCA) conatiner.
|
||||
// After construction, use GetStatus to determine if the file is valid and ready to be used.
|
||||
class NCA : public ReadOnlyVfsDirectory {
|
||||
@@ -102,6 +109,8 @@ public:
|
||||
// Returns the base ivfc offset used in BKTR patching.
|
||||
u64 GetBaseIVFCOffset() const;
|
||||
|
||||
VirtualDir GetLogoPartition() const;
|
||||
|
||||
private:
|
||||
bool CheckSupportedNCA(const NCAHeader& header);
|
||||
bool HandlePotentialHeaderDecryption();
|
||||
@@ -122,6 +131,7 @@ private:
|
||||
|
||||
VirtualFile romfs = nullptr;
|
||||
VirtualDir exefs = nullptr;
|
||||
VirtualDir logo = nullptr;
|
||||
VirtualFile file;
|
||||
VirtualFile bktr_base_romfs;
|
||||
u64 ivfc_offset = 0;
|
||||
|
||||
@@ -39,27 +39,4 @@ static_assert(sizeof(Entry) == 0x310, "Directory Entry struct isn't exactly 0x31
|
||||
static_assert(offsetof(Entry, type) == 0x304, "Wrong offset for type in Entry.");
|
||||
static_assert(offsetof(Entry, file_size) == 0x308, "Wrong offset for file_size in Entry.");
|
||||
|
||||
class DirectoryBackend : NonCopyable {
|
||||
public:
|
||||
DirectoryBackend() {}
|
||||
virtual ~DirectoryBackend() {}
|
||||
|
||||
/**
|
||||
* List files contained in the directory
|
||||
* @param count Number of entries to return at once in entries
|
||||
* @param entries Buffer to read data into
|
||||
* @return Number of entries listed
|
||||
*/
|
||||
virtual u64 Read(const u64 count, Entry* entries) = 0;
|
||||
|
||||
/// Returns the number of entries still left to read.
|
||||
virtual u64 GetEntryCount() const = 0;
|
||||
|
||||
/**
|
||||
* Close the directory
|
||||
* @return true if the directory closed correctly
|
||||
*/
|
||||
virtual bool Close() const = 0;
|
||||
};
|
||||
|
||||
} // namespace FileSys
|
||||
|
||||
@@ -119,6 +119,9 @@ VirtualDir ExtractRomFS(VirtualFile file, RomFSExtractionType type) {
|
||||
|
||||
VirtualDir out = std::move(root);
|
||||
|
||||
if (type == RomFSExtractionType::SingleDiscard)
|
||||
return out->GetSubdirectories().front();
|
||||
|
||||
while (out->GetSubdirectories().size() == 1 && out->GetFiles().empty()) {
|
||||
if (out->GetSubdirectories().front()->GetName() == "data" &&
|
||||
type == RomFSExtractionType::Truncated)
|
||||
|
||||
@@ -33,8 +33,9 @@ struct IVFCHeader {
|
||||
static_assert(sizeof(IVFCHeader) == 0xE0, "IVFCHeader has incorrect size.");
|
||||
|
||||
enum class RomFSExtractionType {
|
||||
Full, // Includes data directory
|
||||
Truncated, // Traverses into data directory
|
||||
Full, // Includes data directory
|
||||
Truncated, // Traverses into data directory
|
||||
SingleDiscard, // Traverses into the first subdirectory of root
|
||||
};
|
||||
|
||||
// Converts a RomFS binary blob to VFS Filesystem
|
||||
|
||||
@@ -47,7 +47,7 @@ std::size_t VectorVfsFile::Write(const u8* data_, std::size_t length, std::size_
|
||||
if (offset + length > data.size())
|
||||
data.resize(offset + length);
|
||||
const auto write = std::min(length, data.size() - offset);
|
||||
std::memcpy(data.data(), data_, write);
|
||||
std::memcpy(data.data() + offset, data_, write);
|
||||
return write;
|
||||
}
|
||||
|
||||
|
||||
24
src/core/frontend/applets/web_browser.cpp
Normal file
24
src/core/frontend/applets/web_browser.cpp
Normal file
@@ -0,0 +1,24 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
WebBrowserApplet::~WebBrowserApplet() = default;
|
||||
|
||||
DefaultWebBrowserApplet::~DefaultWebBrowserApplet() = default;
|
||||
|
||||
void DefaultWebBrowserApplet::OpenPage(std::string_view filename,
|
||||
std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) {
|
||||
LOG_INFO(Service_AM,
|
||||
"(STUBBED) called - No suitable web browser implementation found to open website page "
|
||||
"at '{}'!",
|
||||
filename);
|
||||
finished_callback();
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
28
src/core/frontend/applets/web_browser.h
Normal file
28
src/core/frontend/applets/web_browser.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string_view>
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
class WebBrowserApplet {
|
||||
public:
|
||||
virtual ~WebBrowserApplet();
|
||||
|
||||
virtual void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) = 0;
|
||||
};
|
||||
|
||||
class DefaultWebBrowserApplet final : public WebBrowserApplet {
|
||||
public:
|
||||
~DefaultWebBrowserApplet() override;
|
||||
|
||||
void OpenPage(std::string_view url, std::function<void()> unpack_romfs_callback,
|
||||
std::function<void()> finished_callback) override;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
@@ -67,7 +67,7 @@ static bool IsWithinTouchscreen(const Layout::FramebufferLayout& layout, unsigne
|
||||
framebuffer_x >= layout.screen.left && framebuffer_x < layout.screen.right);
|
||||
}
|
||||
|
||||
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) {
|
||||
std::tuple<unsigned, unsigned> EmuWindow::ClipToTouchScreen(unsigned new_x, unsigned new_y) const {
|
||||
new_x = std::max(new_x, framebuffer_layout.screen.left);
|
||||
new_x = std::min(new_x, framebuffer_layout.screen.right - 1);
|
||||
|
||||
|
||||
@@ -166,7 +166,7 @@ private:
|
||||
/**
|
||||
* Clip the provided coordinates to be inside the touchscreen area.
|
||||
*/
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y);
|
||||
std::tuple<unsigned, unsigned> ClipToTouchScreen(unsigned new_x, unsigned new_y) const;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
|
||||
@@ -12,12 +12,12 @@ namespace Layout {
|
||||
|
||||
// Finds the largest size subrectangle contained in window area that is confined to the aspect ratio
|
||||
template <class T>
|
||||
static MathUtil::Rectangle<T> maxRectangle(MathUtil::Rectangle<T> window_area,
|
||||
float screen_aspect_ratio) {
|
||||
static Common::Rectangle<T> MaxRectangle(Common::Rectangle<T> window_area,
|
||||
float screen_aspect_ratio) {
|
||||
float scale = std::min(static_cast<float>(window_area.GetWidth()),
|
||||
window_area.GetHeight() / screen_aspect_ratio);
|
||||
return MathUtil::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
return Common::Rectangle<T>{0, 0, static_cast<T>(std::round(scale)),
|
||||
static_cast<T>(std::round(scale * screen_aspect_ratio))};
|
||||
}
|
||||
|
||||
FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
@@ -29,8 +29,8 @@ FramebufferLayout DefaultFrameLayout(unsigned width, unsigned height) {
|
||||
|
||||
const float emulation_aspect_ratio{static_cast<float>(ScreenUndocked::Height) /
|
||||
ScreenUndocked::Width};
|
||||
MathUtil::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
MathUtil::Rectangle<unsigned> screen = maxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
Common::Rectangle<unsigned> screen_window_area{0, 0, width, height};
|
||||
Common::Rectangle<unsigned> screen = MaxRectangle(screen_window_area, emulation_aspect_ratio);
|
||||
|
||||
float window_aspect_ratio = static_cast<float>(height) / width;
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ struct FramebufferLayout {
|
||||
unsigned width{ScreenUndocked::Width};
|
||||
unsigned height{ScreenUndocked::Height};
|
||||
|
||||
MathUtil::Rectangle<unsigned> screen;
|
||||
Common::Rectangle<unsigned> screen;
|
||||
|
||||
/**
|
||||
* Returns the ration of pixel size of the screen, compared to the native size of the undocked
|
||||
|
||||
@@ -124,7 +124,7 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
||||
* Orientation is determined by right-hand rule.
|
||||
* Units: deg/sec
|
||||
*/
|
||||
using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
|
||||
using MotionDevice = InputDevice<std::tuple<Common::Vec3<float>, Common::Vec3<float>>>;
|
||||
|
||||
/**
|
||||
* A touch device is an input device that returns a tuple of two floats and a bool. The floats are
|
||||
|
||||
18
src/core/frontend/scope_acquire_window_context.cpp
Normal file
18
src/core/frontend/scope_acquire_window_context.cpp
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/scope_acquire_window_context.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
||||
ScopeAcquireWindowContext::ScopeAcquireWindowContext(Core::Frontend::EmuWindow& emu_window_)
|
||||
: emu_window{emu_window_} {
|
||||
emu_window.MakeCurrent();
|
||||
}
|
||||
ScopeAcquireWindowContext::~ScopeAcquireWindowContext() {
|
||||
emu_window.DoneCurrent();
|
||||
}
|
||||
|
||||
} // namespace Core::Frontend
|
||||
23
src/core/frontend/scope_acquire_window_context.h
Normal file
23
src/core/frontend/scope_acquire_window_context.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 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 Core::Frontend {
|
||||
|
||||
class EmuWindow;
|
||||
|
||||
/// Helper class to acquire/release window context within a given scope
|
||||
class ScopeAcquireWindowContext : NonCopyable {
|
||||
public:
|
||||
explicit ScopeAcquireWindowContext(Core::Frontend::EmuWindow& window);
|
||||
~ScopeAcquireWindowContext();
|
||||
|
||||
private:
|
||||
Core::Frontend::EmuWindow& emu_window;
|
||||
};
|
||||
|
||||
} // namespace Core::Frontend
|
||||
@@ -507,8 +507,11 @@ static void RemoveBreakpoint(BreakpointType type, VAddr addr) {
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: removed a breakpoint: {:016X} bytes at {:016X} of type {}",
|
||||
bp->second.len, bp->second.addr, static_cast<int>(type));
|
||||
Memory::WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
|
||||
if (type == BreakpointType::Execute) {
|
||||
Memory::WriteBlock(bp->second.addr, bp->second.inst.data(), bp->second.inst.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
}
|
||||
p.erase(addr);
|
||||
}
|
||||
|
||||
@@ -1057,9 +1060,12 @@ static bool CommitBreakpoint(BreakpointType type, VAddr addr, u64 len) {
|
||||
breakpoint.addr = addr;
|
||||
breakpoint.len = len;
|
||||
Memory::ReadBlock(addr, breakpoint.inst.data(), breakpoint.inst.size());
|
||||
|
||||
static constexpr std::array<u8, 4> btrap{0x00, 0x7d, 0x20, 0xd4};
|
||||
Memory::WriteBlock(addr, btrap.data(), btrap.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
if (type == BreakpointType::Execute) {
|
||||
Memory::WriteBlock(addr, btrap.data(), btrap.size());
|
||||
Core::System::GetInstance().InvalidateCpuInstructionCaches();
|
||||
}
|
||||
p.insert({addr, breakpoint});
|
||||
|
||||
LOG_DEBUG(Debug_GDBStub, "gdb: added {} breakpoint: {:016X} bytes at {:016X}",
|
||||
|
||||
@@ -216,6 +216,11 @@ private:
|
||||
|
||||
/// Push ///
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(s32 value) {
|
||||
cmdbuf[index++] = static_cast<u32>(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(u32 value) {
|
||||
cmdbuf[index++] = value;
|
||||
@@ -234,6 +239,22 @@ inline void ResponseBuilder::Push(ResultCode value) {
|
||||
Push<u32>(0);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(s8 value) {
|
||||
PushRaw(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(s16 value) {
|
||||
PushRaw(value);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(s64 value) {
|
||||
Push(static_cast<u32>(value));
|
||||
Push(static_cast<u32>(value >> 32));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void ResponseBuilder::Push(u8 value) {
|
||||
PushRaw(value);
|
||||
|
||||
@@ -17,8 +17,7 @@
|
||||
#include "core/hle/result.h"
|
||||
#include "core/memory.h"
|
||||
|
||||
namespace Kernel {
|
||||
namespace AddressArbiter {
|
||||
namespace Kernel::AddressArbiter {
|
||||
|
||||
// Performs actual address waiting logic.
|
||||
static ResultCode WaitForAddress(VAddr address, s64 timeout) {
|
||||
@@ -176,5 +175,4 @@ ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout) {
|
||||
|
||||
return WaitForAddress(address, timeout);
|
||||
}
|
||||
} // namespace AddressArbiter
|
||||
} // namespace Kernel
|
||||
} // namespace Kernel::AddressArbiter
|
||||
|
||||
@@ -8,9 +8,8 @@
|
||||
|
||||
union ResultCode;
|
||||
|
||||
namespace Kernel {
|
||||
namespace Kernel::AddressArbiter {
|
||||
|
||||
namespace AddressArbiter {
|
||||
enum class ArbitrationType {
|
||||
WaitIfLessThan = 0,
|
||||
DecrementAndWaitIfLessThan = 1,
|
||||
@@ -29,6 +28,5 @@ ResultCode ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr address, s32 valu
|
||||
|
||||
ResultCode WaitForAddressIfLessThan(VAddr address, s32 value, s64 timeout, bool should_decrement);
|
||||
ResultCode WaitForAddressIfEqual(VAddr address, s32 value, s64 timeout);
|
||||
} // namespace AddressArbiter
|
||||
|
||||
} // namespace Kernel
|
||||
} // namespace Kernel::AddressArbiter
|
||||
|
||||
@@ -14,6 +14,7 @@ constexpr ResultCode ERR_MAX_CONNECTIONS_REACHED{ErrorModule::Kernel, 7};
|
||||
constexpr ResultCode ERR_INVALID_CAPABILITY_DESCRIPTOR{ErrorModule::Kernel, 14};
|
||||
constexpr ResultCode ERR_INVALID_SIZE{ErrorModule::Kernel, 101};
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS{ErrorModule::Kernel, 102};
|
||||
constexpr ResultCode ERR_OUT_OF_MEMORY{ErrorModule::Kernel, 104};
|
||||
constexpr ResultCode ERR_HANDLE_TABLE_FULL{ErrorModule::Kernel, 105};
|
||||
constexpr ResultCode ERR_INVALID_ADDRESS_STATE{ErrorModule::Kernel, 106};
|
||||
constexpr ResultCode ERR_INVALID_MEMORY_PERMISSIONS{ErrorModule::Kernel, 108};
|
||||
|
||||
@@ -14,32 +14,47 @@
|
||||
namespace Kernel {
|
||||
namespace {
|
||||
constexpr u16 GetSlot(Handle handle) {
|
||||
return handle >> 15;
|
||||
return static_cast<u16>(handle >> 15);
|
||||
}
|
||||
|
||||
constexpr u16 GetGeneration(Handle handle) {
|
||||
return handle & 0x7FFF;
|
||||
return static_cast<u16>(handle & 0x7FFF);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
HandleTable::HandleTable() {
|
||||
next_generation = 1;
|
||||
Clear();
|
||||
}
|
||||
|
||||
HandleTable::~HandleTable() = default;
|
||||
|
||||
ResultCode HandleTable::SetSize(s32 handle_table_size) {
|
||||
if (static_cast<u32>(handle_table_size) > MAX_COUNT) {
|
||||
return ERR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
// Values less than or equal to zero indicate to use the maximum allowable
|
||||
// size for the handle table in the actual kernel, so we ignore the given
|
||||
// value in that case, since we assume this by default unless this function
|
||||
// is called.
|
||||
if (handle_table_size > 0) {
|
||||
table_size = static_cast<u16>(handle_table_size);
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
ResultVal<Handle> HandleTable::Create(SharedPtr<Object> obj) {
|
||||
DEBUG_ASSERT(obj != nullptr);
|
||||
|
||||
u16 slot = next_free_slot;
|
||||
if (slot >= generations.size()) {
|
||||
const u16 slot = next_free_slot;
|
||||
if (slot >= table_size) {
|
||||
LOG_ERROR(Kernel, "Unable to allocate Handle, too many slots in use.");
|
||||
return ERR_HANDLE_TABLE_FULL;
|
||||
}
|
||||
next_free_slot = generations[slot];
|
||||
|
||||
u16 generation = next_generation++;
|
||||
const u16 generation = next_generation++;
|
||||
|
||||
// Overflow count so it fits in the 15 bits dedicated to the generation in the handle.
|
||||
// Horizon OS uses zero to represent an invalid handle, so skip to 1.
|
||||
@@ -64,10 +79,11 @@ ResultVal<Handle> HandleTable::Duplicate(Handle handle) {
|
||||
}
|
||||
|
||||
ResultCode HandleTable::Close(Handle handle) {
|
||||
if (!IsValid(handle))
|
||||
if (!IsValid(handle)) {
|
||||
return ERR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
u16 slot = GetSlot(handle);
|
||||
const u16 slot = GetSlot(handle);
|
||||
|
||||
objects[slot] = nullptr;
|
||||
|
||||
@@ -77,10 +93,10 @@ ResultCode HandleTable::Close(Handle handle) {
|
||||
}
|
||||
|
||||
bool HandleTable::IsValid(Handle handle) const {
|
||||
std::size_t slot = GetSlot(handle);
|
||||
u16 generation = GetGeneration(handle);
|
||||
const std::size_t slot = GetSlot(handle);
|
||||
const u16 generation = GetGeneration(handle);
|
||||
|
||||
return slot < MAX_COUNT && objects[slot] != nullptr && generations[slot] == generation;
|
||||
return slot < table_size && objects[slot] != nullptr && generations[slot] == generation;
|
||||
}
|
||||
|
||||
SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
|
||||
@@ -97,7 +113,7 @@ SharedPtr<Object> HandleTable::GetGeneric(Handle handle) const {
|
||||
}
|
||||
|
||||
void HandleTable::Clear() {
|
||||
for (u16 i = 0; i < MAX_COUNT; ++i) {
|
||||
for (u16 i = 0; i < table_size; ++i) {
|
||||
generations[i] = i + 1;
|
||||
objects[i] = nullptr;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,20 @@ public:
|
||||
HandleTable();
|
||||
~HandleTable();
|
||||
|
||||
/**
|
||||
* Sets the number of handles that may be in use at one time
|
||||
* for this handle table.
|
||||
*
|
||||
* @param handle_table_size The desired size to limit the handle table to.
|
||||
*
|
||||
* @returns an error code indicating if initialization was successful.
|
||||
* If initialization was not successful, then ERR_OUT_OF_MEMORY
|
||||
* will be returned.
|
||||
*
|
||||
* @pre handle_table_size must be within the range [0, 1024]
|
||||
*/
|
||||
ResultCode SetSize(s32 handle_table_size);
|
||||
|
||||
/**
|
||||
* Allocates a handle for the given object.
|
||||
* @return The created Handle or one of the following errors:
|
||||
@@ -103,14 +117,21 @@ private:
|
||||
*/
|
||||
std::array<u16, MAX_COUNT> generations;
|
||||
|
||||
/**
|
||||
* The limited size of the handle table. This can be specified by process
|
||||
* capabilities in order to restrict the overall number of handles that
|
||||
* can be created in a process instance
|
||||
*/
|
||||
u16 table_size = static_cast<u16>(MAX_COUNT);
|
||||
|
||||
/**
|
||||
* Global counter of the number of created handles. Stored in `generations` when a handle is
|
||||
* created, and wraps around to 1 when it hits 0x8000.
|
||||
*/
|
||||
u16 next_generation;
|
||||
u16 next_generation = 1;
|
||||
|
||||
/// Head of the free slots linked list.
|
||||
u16 next_free_slot;
|
||||
u16 next_free_slot = 0;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/resource_limit.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/timer.h"
|
||||
#include "core/hle/lock.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
@@ -86,27 +85,12 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] int cycles_
|
||||
}
|
||||
}
|
||||
|
||||
/// The timer callback event, called when a timer is fired
|
||||
static void TimerCallback(u64 timer_handle, int cycles_late) {
|
||||
const auto proper_handle = static_cast<Handle>(timer_handle);
|
||||
const auto& system = Core::System::GetInstance();
|
||||
SharedPtr<Timer> timer = system.Kernel().RetrieveTimerFromCallbackHandleTable(proper_handle);
|
||||
|
||||
if (timer == nullptr) {
|
||||
LOG_CRITICAL(Kernel, "Callback fired for invalid timer {:016X}", timer_handle);
|
||||
return;
|
||||
}
|
||||
|
||||
timer->Signal(cycles_late);
|
||||
}
|
||||
|
||||
struct KernelCore::Impl {
|
||||
void Initialize(KernelCore& kernel) {
|
||||
void Initialize(KernelCore& kernel, Core::Timing::CoreTiming& core_timing) {
|
||||
Shutdown();
|
||||
|
||||
InitializeSystemResourceLimit(kernel);
|
||||
InitializeThreads();
|
||||
InitializeTimers();
|
||||
InitializeThreads(core_timing);
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
@@ -122,9 +106,6 @@ struct KernelCore::Impl {
|
||||
thread_wakeup_callback_handle_table.Clear();
|
||||
thread_wakeup_event_type = nullptr;
|
||||
|
||||
timer_callback_handle_table.Clear();
|
||||
timer_callback_event_type = nullptr;
|
||||
|
||||
named_ports.clear();
|
||||
}
|
||||
|
||||
@@ -141,14 +122,9 @@ struct KernelCore::Impl {
|
||||
ASSERT(system_resource_limit->SetLimitValue(ResourceType::Sessions, 900).IsSuccess());
|
||||
}
|
||||
|
||||
void InitializeThreads() {
|
||||
void InitializeThreads(Core::Timing::CoreTiming& core_timing) {
|
||||
thread_wakeup_event_type =
|
||||
CoreTiming::RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
}
|
||||
|
||||
void InitializeTimers() {
|
||||
timer_callback_handle_table.Clear();
|
||||
timer_callback_event_type = CoreTiming::RegisterEvent("TimerCallback", TimerCallback);
|
||||
core_timing.RegisterEvent("ThreadWakeupCallback", ThreadWakeupCallback);
|
||||
}
|
||||
|
||||
std::atomic<u32> next_object_id{0};
|
||||
@@ -161,13 +137,7 @@ struct KernelCore::Impl {
|
||||
|
||||
SharedPtr<ResourceLimit> system_resource_limit;
|
||||
|
||||
/// The event type of the generic timer callback event
|
||||
CoreTiming::EventType* timer_callback_event_type = nullptr;
|
||||
// TODO(yuriks): This can be removed if Timer objects are explicitly pooled in the future,
|
||||
// allowing us to simply use a pool index or similar.
|
||||
Kernel::HandleTable timer_callback_handle_table;
|
||||
|
||||
CoreTiming::EventType* thread_wakeup_event_type = nullptr;
|
||||
Core::Timing::EventType* thread_wakeup_event_type = nullptr;
|
||||
// TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future,
|
||||
// allowing us to simply use a pool index or similar.
|
||||
Kernel::HandleTable thread_wakeup_callback_handle_table;
|
||||
@@ -182,8 +152,8 @@ KernelCore::~KernelCore() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
void KernelCore::Initialize() {
|
||||
impl->Initialize(*this);
|
||||
void KernelCore::Initialize(Core::Timing::CoreTiming& core_timing) {
|
||||
impl->Initialize(*this, core_timing);
|
||||
}
|
||||
|
||||
void KernelCore::Shutdown() {
|
||||
@@ -198,10 +168,6 @@ SharedPtr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable(Handle
|
||||
return impl->thread_wakeup_callback_handle_table.Get<Thread>(handle);
|
||||
}
|
||||
|
||||
SharedPtr<Timer> KernelCore::RetrieveTimerFromCallbackHandleTable(Handle handle) const {
|
||||
return impl->timer_callback_handle_table.Get<Timer>(handle);
|
||||
}
|
||||
|
||||
void KernelCore::AppendNewProcess(SharedPtr<Process> process) {
|
||||
impl->process_list.push_back(std::move(process));
|
||||
}
|
||||
@@ -247,18 +213,10 @@ u64 KernelCore::CreateNewProcessID() {
|
||||
return impl->next_process_id++;
|
||||
}
|
||||
|
||||
ResultVal<Handle> KernelCore::CreateTimerCallbackHandle(const SharedPtr<Timer>& timer) {
|
||||
return impl->timer_callback_handle_table.Create(timer);
|
||||
}
|
||||
|
||||
CoreTiming::EventType* KernelCore::ThreadWakeupCallbackEventType() const {
|
||||
Core::Timing::EventType* KernelCore::ThreadWakeupCallbackEventType() const {
|
||||
return impl->thread_wakeup_event_type;
|
||||
}
|
||||
|
||||
CoreTiming::EventType* KernelCore::TimerCallbackEventType() const {
|
||||
return impl->timer_callback_event_type;
|
||||
}
|
||||
|
||||
Kernel::HandleTable& KernelCore::ThreadWakeupCallbackHandleTable() {
|
||||
return impl->thread_wakeup_callback_handle_table;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@
|
||||
template <typename T>
|
||||
class ResultVal;
|
||||
|
||||
namespace CoreTiming {
|
||||
namespace Core::Timing {
|
||||
class CoreTiming;
|
||||
struct EventType;
|
||||
}
|
||||
} // namespace Core::Timing
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
@@ -22,7 +23,6 @@ class HandleTable;
|
||||
class Process;
|
||||
class ResourceLimit;
|
||||
class Thread;
|
||||
class Timer;
|
||||
|
||||
/// Represents a single instance of the kernel.
|
||||
class KernelCore {
|
||||
@@ -40,7 +40,11 @@ public:
|
||||
KernelCore& operator=(KernelCore&&) = delete;
|
||||
|
||||
/// Resets the kernel to a clean slate for use.
|
||||
void Initialize();
|
||||
///
|
||||
/// @param core_timing CoreTiming instance used to create any necessary
|
||||
/// kernel-specific callback events.
|
||||
///
|
||||
void Initialize(Core::Timing::CoreTiming& core_timing);
|
||||
|
||||
/// Clears all resources in use by the kernel instance.
|
||||
void Shutdown();
|
||||
@@ -51,9 +55,6 @@ public:
|
||||
/// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table.
|
||||
SharedPtr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const;
|
||||
|
||||
/// Retrieves a shared pointer to a Timer instance within the timer callback handle table.
|
||||
SharedPtr<Timer> RetrieveTimerFromCallbackHandleTable(Handle handle) const;
|
||||
|
||||
/// Adds the given shared pointer to an internal list of active processes.
|
||||
void AppendNewProcess(SharedPtr<Process> process);
|
||||
|
||||
@@ -82,7 +83,6 @@ private:
|
||||
friend class Object;
|
||||
friend class Process;
|
||||
friend class Thread;
|
||||
friend class Timer;
|
||||
|
||||
/// Creates a new object ID, incrementing the internal object ID counter.
|
||||
u32 CreateNewObjectID();
|
||||
@@ -93,14 +93,8 @@ private:
|
||||
/// Creates a new thread ID, incrementing the internal thread ID counter.
|
||||
u64 CreateNewThreadID();
|
||||
|
||||
/// Creates a timer callback handle for the given timer.
|
||||
ResultVal<Handle> CreateTimerCallbackHandle(const SharedPtr<Timer>& timer);
|
||||
|
||||
/// Retrieves the event type used for thread wakeup callbacks.
|
||||
CoreTiming::EventType* ThreadWakeupCallbackEventType() const;
|
||||
|
||||
/// Retrieves the event type used for timer callbacks.
|
||||
CoreTiming::EventType* TimerCallbackEventType() const;
|
||||
Core::Timing::EventType* ThreadWakeupCallbackEventType() const;
|
||||
|
||||
/// Provides a reference to the thread wakeup callback handle table.
|
||||
Kernel::HandleTable& ThreadWakeupCallbackHandleTable();
|
||||
|
||||
@@ -16,7 +16,6 @@ bool Object::IsWaitable() const {
|
||||
case HandleType::ReadableEvent:
|
||||
case HandleType::Thread:
|
||||
case HandleType::Process:
|
||||
case HandleType::Timer:
|
||||
case HandleType::ServerPort:
|
||||
case HandleType::ServerSession:
|
||||
return true;
|
||||
|
||||
@@ -25,7 +25,6 @@ enum class HandleType : u32 {
|
||||
Thread,
|
||||
Process,
|
||||
AddressArbiter,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
ClientPort,
|
||||
ServerPort,
|
||||
|
||||
@@ -99,7 +99,13 @@ ResultCode Process::LoadFromMetadata(const FileSys::ProgramMetadata& metadata) {
|
||||
vm_manager.Reset(metadata.GetAddressSpaceType());
|
||||
|
||||
const auto& caps = metadata.GetKernelCapabilities();
|
||||
return capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager);
|
||||
const auto capability_init_result =
|
||||
capabilities.InitializeForUserProcess(caps.data(), caps.size(), vm_manager);
|
||||
if (capability_init_result.IsError()) {
|
||||
return capability_init_result;
|
||||
}
|
||||
|
||||
return handle_table.SetSize(capabilities.GetHandleTableSize());
|
||||
}
|
||||
|
||||
void Process::Run(VAddr entry_point, s32 main_thread_priority, u32 stack_size) {
|
||||
|
||||
@@ -96,7 +96,7 @@ void ProcessCapabilities::InitializeForMetadatalessProcess() {
|
||||
interrupt_capabilities.set();
|
||||
|
||||
// Allow using the maximum possible amount of handles
|
||||
handle_table_size = static_cast<u32>(HandleTable::MAX_COUNT);
|
||||
handle_table_size = static_cast<s32>(HandleTable::MAX_COUNT);
|
||||
|
||||
// Allow all debugging capabilities.
|
||||
is_debuggable = true;
|
||||
@@ -337,7 +337,7 @@ ResultCode ProcessCapabilities::HandleHandleTableFlags(u32 flags) {
|
||||
return ERR_RESERVED_VALUE;
|
||||
}
|
||||
|
||||
handle_table_size = (flags >> 16) & 0x3FF;
|
||||
handle_table_size = static_cast<s32>((flags >> 16) & 0x3FF);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public:
|
||||
}
|
||||
|
||||
/// Gets the number of total allowable handles for the process' handle table.
|
||||
u32 GetHandleTableSize() const {
|
||||
s32 GetHandleTableSize() const {
|
||||
return handle_table_size;
|
||||
}
|
||||
|
||||
@@ -252,7 +252,7 @@ private:
|
||||
u64 core_mask = 0;
|
||||
u64 priority_mask = 0;
|
||||
|
||||
u32 handle_table_size = 0;
|
||||
s32 handle_table_size = 0;
|
||||
u32 kernel_version = 0;
|
||||
|
||||
ProgramType program_type = ProgramType::SysModule;
|
||||
|
||||
@@ -44,8 +44,4 @@ ResultCode ReadableEvent::Reset() {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void ReadableEvent::WakeupAllWaitingThreads() {
|
||||
WaitObject::WakeupAllWaitingThreads();
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -39,8 +39,6 @@ public:
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
void WakeupAllWaitingThreads() override;
|
||||
|
||||
/// Unconditionally clears the readable event's state.
|
||||
void Clear();
|
||||
|
||||
|
||||
@@ -111,7 +111,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
|
||||
|
||||
void Scheduler::UpdateLastContextSwitchTime(Thread* thread, Process* process) {
|
||||
const u64 prev_switch_ticks = last_context_switch_time;
|
||||
const u64 most_recent_switch_ticks = CoreTiming::GetTicks();
|
||||
const u64 most_recent_switch_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
const u64 update_ticks = most_recent_switch_ticks - prev_switch_ticks;
|
||||
|
||||
if (thread != nullptr) {
|
||||
|
||||
@@ -597,6 +597,7 @@ enum class BreakType : u32 {
|
||||
PostNROLoad = 4,
|
||||
PreNROUnload = 5,
|
||||
PostNROUnload = 6,
|
||||
CppException = 7,
|
||||
};
|
||||
|
||||
struct BreakReason {
|
||||
@@ -669,6 +670,9 @@ static void Break(u32 reason, u64 info1, u64 info2) {
|
||||
"Signalling debugger, Unloaded an NRO at 0x{:016X} with size 0x{:016X}", info1,
|
||||
info2);
|
||||
break;
|
||||
case BreakType::CppException:
|
||||
LOG_CRITICAL(Debug_Emulated, "Signalling debugger. Uncaught C++ exception encountered.");
|
||||
break;
|
||||
default:
|
||||
LOG_WARNING(
|
||||
Debug_Emulated,
|
||||
@@ -914,6 +918,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
}
|
||||
|
||||
const auto& system = Core::System::GetInstance();
|
||||
const auto& core_timing = system.CoreTiming();
|
||||
const auto& scheduler = system.CurrentScheduler();
|
||||
const auto* const current_thread = scheduler.GetCurrentThread();
|
||||
const bool same_thread = current_thread == thread;
|
||||
@@ -923,9 +928,9 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
|
||||
if (same_thread && info_sub_id == 0xFFFFFFFFFFFFFFFF) {
|
||||
const u64 thread_ticks = current_thread->GetTotalCPUTimeTicks();
|
||||
|
||||
out_ticks = thread_ticks + (CoreTiming::GetTicks() - prev_ctx_ticks);
|
||||
out_ticks = thread_ticks + (core_timing.GetTicks() - prev_ctx_ticks);
|
||||
} else if (same_thread && info_sub_id == system.CurrentCoreIndex()) {
|
||||
out_ticks = CoreTiming::GetTicks() - prev_ctx_ticks;
|
||||
out_ticks = core_timing.GetTicks() - prev_ctx_ticks;
|
||||
}
|
||||
|
||||
*result = out_ticks;
|
||||
@@ -1542,10 +1547,11 @@ static ResultCode SignalToAddress(VAddr address, u32 type, s32 value, s32 num_to
|
||||
static u64 GetSystemTick() {
|
||||
LOG_TRACE(Kernel_SVC, "called");
|
||||
|
||||
const u64 result{CoreTiming::GetTicks()};
|
||||
auto& core_timing = Core::System::GetInstance().CoreTiming();
|
||||
const u64 result{core_timing.GetTicks()};
|
||||
|
||||
// Advance time to defeat dumb games that busy-wait for the frame to end.
|
||||
CoreTiming::AddTicks(400);
|
||||
core_timing.AddTicks(400);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -43,7 +43,8 @@ Thread::~Thread() = default;
|
||||
|
||||
void Thread::Stop() {
|
||||
// Cancel any outstanding wakeup events for this thread
|
||||
CoreTiming::UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(),
|
||||
callback_handle);
|
||||
kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle);
|
||||
callback_handle = 0;
|
||||
|
||||
@@ -85,12 +86,14 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
|
||||
|
||||
// This function might be called from any thread so we have to be cautious and use the
|
||||
// thread-safe version of ScheduleEvent.
|
||||
CoreTiming::ScheduleEventThreadsafe(CoreTiming::nsToCycles(nanoseconds),
|
||||
kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
Core::System::GetInstance().CoreTiming().ScheduleEventThreadsafe(
|
||||
Core::Timing::nsToCycles(nanoseconds), kernel.ThreadWakeupCallbackEventType(),
|
||||
callback_handle);
|
||||
}
|
||||
|
||||
void Thread::CancelWakeupTimer() {
|
||||
CoreTiming::UnscheduleEventThreadsafe(kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
Core::System::GetInstance().CoreTiming().UnscheduleEventThreadsafe(
|
||||
kernel.ThreadWakeupCallbackEventType(), callback_handle);
|
||||
}
|
||||
|
||||
static std::optional<s32> GetNextProcessorId(u64 mask) {
|
||||
@@ -189,6 +192,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
return ResultCode(-1);
|
||||
}
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
SharedPtr<Thread> thread(new Thread(kernel));
|
||||
|
||||
thread->thread_id = kernel.CreateNewThreadID();
|
||||
@@ -197,7 +201,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->stack_top = stack_top;
|
||||
thread->tpidr_el0 = 0;
|
||||
thread->nominal_priority = thread->current_priority = priority;
|
||||
thread->last_running_ticks = CoreTiming::GetTicks();
|
||||
thread->last_running_ticks = system.CoreTiming().GetTicks();
|
||||
thread->processor_id = processor_id;
|
||||
thread->ideal_core = processor_id;
|
||||
thread->affinity_mask = 1ULL << processor_id;
|
||||
@@ -208,7 +212,7 @@ ResultVal<SharedPtr<Thread>> Thread::Create(KernelCore& kernel, std::string name
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap();
|
||||
thread->owner_process = &owner_process;
|
||||
thread->scheduler = &Core::System::GetInstance().Scheduler(processor_id);
|
||||
thread->scheduler = &system.Scheduler(processor_id);
|
||||
thread->scheduler->AddThread(thread, priority);
|
||||
thread->tls_address = thread->owner_process->MarkNextAvailableTLSSlotAsUsed(*thread);
|
||||
|
||||
@@ -257,7 +261,7 @@ void Thread::SetStatus(ThreadStatus new_status) {
|
||||
}
|
||||
|
||||
if (status == ThreadStatus::Running) {
|
||||
last_running_ticks = CoreTiming::GetTicks();
|
||||
last_running_ticks = Core::System::GetInstance().CoreTiming().GetTicks();
|
||||
}
|
||||
|
||||
status = new_status;
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/core_timing_util.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/timer.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
Timer::Timer(KernelCore& kernel) : WaitObject{kernel} {}
|
||||
Timer::~Timer() = default;
|
||||
|
||||
SharedPtr<Timer> Timer::Create(KernelCore& kernel, ResetType reset_type, std::string name) {
|
||||
SharedPtr<Timer> timer(new Timer(kernel));
|
||||
|
||||
timer->reset_type = reset_type;
|
||||
timer->signaled = false;
|
||||
timer->name = std::move(name);
|
||||
timer->initial_delay = 0;
|
||||
timer->interval_delay = 0;
|
||||
timer->callback_handle = kernel.CreateTimerCallbackHandle(timer).Unwrap();
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
bool Timer::ShouldWait(Thread* thread) const {
|
||||
return !signaled;
|
||||
}
|
||||
|
||||
void Timer::Acquire(Thread* thread) {
|
||||
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
|
||||
|
||||
if (reset_type == ResetType::OneShot)
|
||||
signaled = false;
|
||||
}
|
||||
|
||||
void Timer::Set(s64 initial, s64 interval) {
|
||||
// Ensure we get rid of any previous scheduled event
|
||||
Cancel();
|
||||
|
||||
initial_delay = initial;
|
||||
interval_delay = interval;
|
||||
|
||||
if (initial == 0) {
|
||||
// Immediately invoke the callback
|
||||
Signal(0);
|
||||
} else {
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(initial), kernel.TimerCallbackEventType(),
|
||||
callback_handle);
|
||||
}
|
||||
}
|
||||
|
||||
void Timer::Cancel() {
|
||||
CoreTiming::UnscheduleEvent(kernel.TimerCallbackEventType(), callback_handle);
|
||||
}
|
||||
|
||||
void Timer::Clear() {
|
||||
signaled = false;
|
||||
}
|
||||
|
||||
void Timer::WakeupAllWaitingThreads() {
|
||||
WaitObject::WakeupAllWaitingThreads();
|
||||
}
|
||||
|
||||
void Timer::Signal(int cycles_late) {
|
||||
LOG_TRACE(Kernel, "Timer {} fired", GetObjectId());
|
||||
|
||||
signaled = true;
|
||||
|
||||
// Resume all waiting threads
|
||||
WakeupAllWaitingThreads();
|
||||
|
||||
if (interval_delay != 0) {
|
||||
// Reschedule the timer with the interval delay
|
||||
CoreTiming::ScheduleEvent(CoreTiming::nsToCycles(interval_delay) - cycles_late,
|
||||
kernel.TimerCallbackEventType(), callback_handle);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,90 +0,0 @@
|
||||
// Copyright 2015 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class KernelCore;
|
||||
|
||||
class Timer final : public WaitObject {
|
||||
public:
|
||||
/**
|
||||
* Creates a timer
|
||||
* @param kernel The kernel instance to create the timer callback handle for.
|
||||
* @param reset_type ResetType describing how to create the timer
|
||||
* @param name Optional name of timer
|
||||
* @return The created Timer
|
||||
*/
|
||||
static SharedPtr<Timer> Create(KernelCore& kernel, ResetType reset_type,
|
||||
std::string name = "Unknown");
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "Timer";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::Timer;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
ResetType GetResetType() const {
|
||||
return reset_type;
|
||||
}
|
||||
|
||||
u64 GetInitialDelay() const {
|
||||
return initial_delay;
|
||||
}
|
||||
|
||||
u64 GetIntervalDelay() const {
|
||||
return interval_delay;
|
||||
}
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
void WakeupAllWaitingThreads() override;
|
||||
|
||||
/**
|
||||
* Starts the timer, with the specified initial delay and interval.
|
||||
* @param initial Delay until the timer is first fired
|
||||
* @param interval Delay until the timer is fired after the first time
|
||||
*/
|
||||
void Set(s64 initial, s64 interval);
|
||||
|
||||
void Cancel();
|
||||
void Clear();
|
||||
|
||||
/**
|
||||
* Signals the timer, waking up any waiting threads and rescheduling it
|
||||
* for the next interval.
|
||||
* This method should not be called from outside the timer callback handler,
|
||||
* lest multiple callback events get scheduled.
|
||||
*/
|
||||
void Signal(int cycles_late);
|
||||
|
||||
private:
|
||||
explicit Timer(KernelCore& kernel);
|
||||
~Timer() override;
|
||||
|
||||
ResetType reset_type; ///< The ResetType of this timer
|
||||
|
||||
u64 initial_delay; ///< The delay until the timer fires for the first time
|
||||
u64 interval_delay; ///< The delay until the timer fires after the first time
|
||||
|
||||
bool signaled; ///< Whether the timer has been signaled or not
|
||||
std::string name; ///< Name of timer (optional)
|
||||
|
||||
/// Handle used as userdata to reference this object when inserting into the CoreTiming queue.
|
||||
Handle callback_handle;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -33,19 +33,19 @@ public:
|
||||
* Add a thread to wait on this object
|
||||
* @param thread Pointer to thread to add
|
||||
*/
|
||||
virtual void AddWaitingThread(SharedPtr<Thread> thread);
|
||||
void AddWaitingThread(SharedPtr<Thread> thread);
|
||||
|
||||
/**
|
||||
* Removes a thread from waiting on this object (e.g. if it was resumed already)
|
||||
* @param thread Pointer to thread to remove
|
||||
*/
|
||||
virtual void RemoveWaitingThread(Thread* thread);
|
||||
void RemoveWaitingThread(Thread* thread);
|
||||
|
||||
/**
|
||||
* Wake up all threads waiting on this object that can be awoken, in priority order,
|
||||
* and set the synchronization result and output of the thread.
|
||||
*/
|
||||
virtual void WakeupAllWaitingThreads();
|
||||
void WakeupAllWaitingThreads();
|
||||
|
||||
/**
|
||||
* Wakes up a single thread waiting on this object.
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include "core/hle/service/am/applets/profile_select.h"
|
||||
#include "core/hle/service/am/applets/software_keyboard.h"
|
||||
#include "core/hle/service/am/applets/stub_applet.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
#include "core/hle/service/am/idle.h"
|
||||
#include "core/hle/service/am/omm.h"
|
||||
#include "core/hle/service/am/spsm.h"
|
||||
@@ -44,6 +45,7 @@ constexpr ResultCode ERR_SIZE_OUT_OF_BOUNDS{ErrorModule::AM, 0x1F7};
|
||||
enum class AppletId : u32 {
|
||||
ProfileSelect = 0x10,
|
||||
SoftwareKeyboard = 0x11,
|
||||
LibAppletOff = 0x17,
|
||||
};
|
||||
|
||||
constexpr u32 POP_LAUNCH_PARAMETER_MAGIC = 0xC79497CA;
|
||||
@@ -320,14 +322,15 @@ void ISelfController::SetScreenShotImageOrientation(Kernel::HLERequestContext& c
|
||||
|
||||
void ISelfController::CreateManagedDisplayLayer(Kernel::HLERequestContext& ctx) {
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
|
||||
// TODO(Subv): Find out how AM determines the display to use, for now just
|
||||
// create the layer in the Default display.
|
||||
u64 display_id = nvflinger->OpenDisplay("Default");
|
||||
u64 layer_id = nvflinger->CreateLayer(display_id);
|
||||
const auto display_id = nvflinger->OpenDisplay("Default");
|
||||
const auto layer_id = nvflinger->CreateLayer(*display_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(layer_id);
|
||||
rb.Push(*layer_id);
|
||||
}
|
||||
|
||||
void ISelfController::SetHandlesRequestToDisplay(Kernel::HLERequestContext& ctx) {
|
||||
@@ -730,10 +733,10 @@ void IStorageAccessor::Write(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
|
||||
const std::vector<u8> data{ctx.ReadBuffer()};
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, data.size());
|
||||
|
||||
if (data.size() > backing.buffer.size() - offset) {
|
||||
LOG_ERROR(Service_AM,
|
||||
"offset is out of bounds, backing_buffer_sz={}, data_size={}, offset={}",
|
||||
@@ -753,10 +756,10 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
|
||||
const u64 offset{rp.Pop<u64>()};
|
||||
LOG_DEBUG(Service_AM, "called, offset={}", offset);
|
||||
|
||||
const std::size_t size{ctx.GetWriteBufferSize()};
|
||||
|
||||
LOG_DEBUG(Service_AM, "called, offset={}, size={}", offset, size);
|
||||
|
||||
if (size > backing.buffer.size() - offset) {
|
||||
LOG_ERROR(Service_AM, "offset is out of bounds, backing_buffer_sz={}, size={}, offset={}",
|
||||
backing.buffer.size(), size, offset);
|
||||
@@ -791,6 +794,8 @@ static std::shared_ptr<Applets::Applet> GetAppletFromId(AppletId id) {
|
||||
return std::make_shared<Applets::ProfileSelect>();
|
||||
case AppletId::SoftwareKeyboard:
|
||||
return std::make_shared<Applets::SoftwareKeyboard>();
|
||||
case AppletId::LibAppletOff:
|
||||
return std::make_shared<Applets::WebBrowser>();
|
||||
default:
|
||||
LOG_ERROR(Service_AM, "Unimplemented AppletId [{:08X}]! -- Falling back to stub!",
|
||||
static_cast<u32>(id));
|
||||
|
||||
@@ -249,7 +249,8 @@ AppletAE::AppletAE(std::shared_ptr<NVFlinger::NVFlinger> nvflinger,
|
||||
{300, nullptr, "OpenOverlayAppletProxy"},
|
||||
{350, nullptr, "OpenSystemApplicationProxy"},
|
||||
{400, nullptr, "CreateSelfLibraryAppletCreatorForDevelop"},
|
||||
{401, nullptr, "GetSystemAppletControllerForDebug"},
|
||||
{410, nullptr, "GetSystemAppletControllerForDebug"},
|
||||
{1000, nullptr, "GetDebugFunctions"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/applets/software_keyboard.h"
|
||||
#include "core/frontend/applets/profile_select.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/profile_select.h"
|
||||
|
||||
|
||||
190
src/core/hle/service/am/applets/web_browser.cpp
Normal file
190
src/core/hle/service/am/applets/web_browser.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <array>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_paths.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/core.h"
|
||||
#include "core/file_sys/content_archive.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/nca_metadata.h"
|
||||
#include "core/file_sys/registered_cache.h"
|
||||
#include "core/file_sys/romfs.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/frontend/applets/web_browser.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/service/am/applets/web_browser.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
// TODO(DarkLordZach): There are other arguments in the WebBuffer structure that are currently not
|
||||
// parsed, for example footer mode and left stick mode. Some of these are not particularly relevant,
|
||||
// but some may be worth an implementation.
|
||||
constexpr u16 WEB_ARGUMENT_URL_TYPE = 0x6;
|
||||
|
||||
struct WebBufferHeader {
|
||||
u16 count;
|
||||
INSERT_PADDING_BYTES(6);
|
||||
};
|
||||
static_assert(sizeof(WebBufferHeader) == 0x8, "WebBufferHeader has incorrect size.");
|
||||
|
||||
struct WebArgumentHeader {
|
||||
u16 type;
|
||||
u16 size;
|
||||
u32 offset;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentHeader) == 0x8, "WebArgumentHeader has incorrect size.");
|
||||
|
||||
struct WebArgumentResult {
|
||||
u32 result_code;
|
||||
std::array<char, 0x1000> last_url;
|
||||
u64 last_url_size;
|
||||
};
|
||||
static_assert(sizeof(WebArgumentResult) == 0x1010, "WebArgumentResult has incorrect size.");
|
||||
|
||||
static std::vector<u8> GetArgumentDataForTagType(const std::vector<u8>& data, u16 type) {
|
||||
WebBufferHeader header;
|
||||
ASSERT(sizeof(WebBufferHeader) <= data.size());
|
||||
std::memcpy(&header, data.data(), sizeof(WebBufferHeader));
|
||||
|
||||
u64 offset = sizeof(WebBufferHeader);
|
||||
for (u16 i = 0; i < header.count; ++i) {
|
||||
WebArgumentHeader arg;
|
||||
ASSERT(offset + sizeof(WebArgumentHeader) <= data.size());
|
||||
std::memcpy(&arg, data.data() + offset, sizeof(WebArgumentHeader));
|
||||
offset += sizeof(WebArgumentHeader);
|
||||
|
||||
if (arg.type == type) {
|
||||
std::vector<u8> out(arg.size);
|
||||
offset += arg.offset;
|
||||
ASSERT(offset + arg.size <= data.size());
|
||||
std::memcpy(out.data(), data.data() + offset, out.size());
|
||||
return out;
|
||||
}
|
||||
|
||||
offset += arg.offset + arg.size;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
static FileSys::VirtualFile GetManualRomFS() {
|
||||
auto& loader{Core::System::GetInstance().GetAppLoader()};
|
||||
|
||||
FileSys::VirtualFile out;
|
||||
if (loader.ReadManualRomFS(out) == Loader::ResultStatus::Success)
|
||||
return out;
|
||||
|
||||
const auto& installed{FileSystem::GetUnionContents()};
|
||||
const auto res = installed.GetEntry(Core::System::GetInstance().CurrentProcess()->GetTitleID(),
|
||||
FileSys::ContentRecordType::Manual);
|
||||
|
||||
if (res != nullptr)
|
||||
return res->GetRomFS();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
WebBrowser::WebBrowser() = default;
|
||||
|
||||
WebBrowser::~WebBrowser() = default;
|
||||
|
||||
void WebBrowser::Initialize() {
|
||||
Applet::Initialize();
|
||||
|
||||
complete = false;
|
||||
temporary_dir.clear();
|
||||
filename.clear();
|
||||
status = RESULT_SUCCESS;
|
||||
|
||||
const auto web_arg_storage = broker.PopNormalDataToApplet();
|
||||
ASSERT(web_arg_storage != nullptr);
|
||||
const auto& web_arg = web_arg_storage->GetData();
|
||||
|
||||
const auto url_data = GetArgumentDataForTagType(web_arg, WEB_ARGUMENT_URL_TYPE);
|
||||
filename = Common::StringFromFixedZeroTerminatedBuffer(
|
||||
reinterpret_cast<const char*>(url_data.data()), url_data.size());
|
||||
|
||||
temporary_dir = FileUtil::SanitizePath(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
|
||||
"web_applet_manual",
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
|
||||
manual_romfs = GetManualRomFS();
|
||||
if (manual_romfs == nullptr) {
|
||||
status = ResultCode(-1);
|
||||
LOG_ERROR(Service_AM, "Failed to find manual for current process!");
|
||||
}
|
||||
|
||||
filename =
|
||||
FileUtil::SanitizePath(temporary_dir + DIR_SEP + "html-document" + DIR_SEP + filename,
|
||||
FileUtil::DirectorySeparator::PlatformDefault);
|
||||
}
|
||||
|
||||
bool WebBrowser::TransactionComplete() const {
|
||||
return complete;
|
||||
}
|
||||
|
||||
ResultCode WebBrowser::GetStatus() const {
|
||||
return status;
|
||||
}
|
||||
|
||||
void WebBrowser::ExecuteInteractive() {
|
||||
UNIMPLEMENTED_MSG("Unexpected interactive data recieved!");
|
||||
}
|
||||
|
||||
void WebBrowser::Execute() {
|
||||
if (complete)
|
||||
return;
|
||||
|
||||
if (status != RESULT_SUCCESS) {
|
||||
complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
auto& frontend{Core::System::GetInstance().GetWebBrowser()};
|
||||
|
||||
frontend.OpenPage(filename, [this] { UnpackRomFS(); }, [this] { Finalize(); });
|
||||
}
|
||||
|
||||
void WebBrowser::UnpackRomFS() {
|
||||
if (unpacked)
|
||||
return;
|
||||
|
||||
ASSERT(manual_romfs != nullptr);
|
||||
const auto dir =
|
||||
FileSys::ExtractRomFS(manual_romfs, FileSys::RomFSExtractionType::SingleDiscard);
|
||||
const auto& vfs{Core::System::GetInstance().GetFilesystem()};
|
||||
const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
|
||||
FileSys::VfsRawCopyD(dir, temp_dir);
|
||||
|
||||
unpacked = true;
|
||||
}
|
||||
|
||||
void WebBrowser::Finalize() {
|
||||
complete = true;
|
||||
|
||||
WebArgumentResult out{};
|
||||
out.result_code = 0;
|
||||
out.last_url_size = 0;
|
||||
|
||||
std::vector<u8> data(sizeof(WebArgumentResult));
|
||||
std::memcpy(data.data(), &out, sizeof(WebArgumentResult));
|
||||
|
||||
broker.PushNormalDataFromApplet(IStorage{data});
|
||||
broker.SignalStateChanged();
|
||||
|
||||
FileUtil::DeleteDirRecursively(temporary_dir);
|
||||
}
|
||||
|
||||
} // namespace Service::AM::Applets
|
||||
44
src/core/hle/service/am/applets/web_browser.h
Normal file
44
src/core/hle/service/am/applets/web_browser.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/service/am/am.h"
|
||||
#include "core/hle/service/am/applets/applets.h"
|
||||
|
||||
namespace Service::AM::Applets {
|
||||
|
||||
class WebBrowser final : public Applet {
|
||||
public:
|
||||
WebBrowser();
|
||||
~WebBrowser() override;
|
||||
|
||||
void Initialize() override;
|
||||
|
||||
bool TransactionComplete() const override;
|
||||
ResultCode GetStatus() const override;
|
||||
void ExecuteInteractive() override;
|
||||
void Execute() override;
|
||||
|
||||
// Callback to be fired when the frontend needs the manual RomFS unpacked to temporary
|
||||
// directory. This is a blocking call and may take a while as some manuals can be up to 100MB in
|
||||
// size. Attempting to access files at filename before invocation is likely to not work.
|
||||
void UnpackRomFS();
|
||||
|
||||
// Callback to be fired when the frontend is finished browsing. This will delete the temporary
|
||||
// manual RomFS extracted files, so ensure this is only called at actual finalization.
|
||||
void Finalize();
|
||||
|
||||
private:
|
||||
bool complete = false;
|
||||
bool unpacked = false;
|
||||
ResultCode status = RESULT_SUCCESS;
|
||||
|
||||
FileSys::VirtualFile manual_romfs;
|
||||
std::string temporary_dir;
|
||||
std::string filename;
|
||||
};
|
||||
|
||||
} // namespace Service::AM::Applets
|
||||
@@ -12,6 +12,7 @@ namespace Service::Audio {
|
||||
class IAudioIn final : public ServiceFramework<IAudioIn> {
|
||||
public:
|
||||
IAudioIn() : ServiceFramework("IAudioIn") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetAudioInState"},
|
||||
{1, nullptr, "StartAudioIn"},
|
||||
@@ -28,16 +29,24 @@ public:
|
||||
{12, nullptr, "SetAudioInDeviceGain"},
|
||||
{13, nullptr, "GetAudioInDeviceGain"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~IAudioIn() = default;
|
||||
};
|
||||
|
||||
AudInU::AudInU() : ServiceFramework("audin:u") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "ListAudioIns"}, {1, nullptr, "OpenAudioIn"}, {2, nullptr, "Unknown"},
|
||||
{3, nullptr, "OpenAudioInAuto"}, {4, nullptr, "ListAudioInsAuto"},
|
||||
{0, nullptr, "ListAudioIns"},
|
||||
{1, nullptr, "OpenAudioIn"},
|
||||
{2, nullptr, "Unknown"},
|
||||
{3, nullptr, "OpenAudioInAuto"},
|
||||
{4, nullptr, "ListAudioInsAuto"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,12 +68,12 @@ public:
|
||||
RegisterHandlers(functions);
|
||||
|
||||
// This is the event handle used to check if the audio buffer was released
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
|
||||
"IAudioOutBufferReleased");
|
||||
auto& system = Core::System::GetInstance();
|
||||
buffer_event = Kernel::WritableEvent::CreateEventPair(
|
||||
system.Kernel(), Kernel::ResetType::Sticky, "IAudioOutBufferReleased");
|
||||
|
||||
stream = audio_core.OpenStream(audio_params.sample_rate, audio_params.channel_count,
|
||||
std::move(unique_name),
|
||||
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
|
||||
audio_params.channel_count, std::move(unique_name),
|
||||
[=]() { buffer_event.writable->Signal(); });
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Service::Audio {
|
||||
class IFinalOutputRecorder final : public ServiceFramework<IFinalOutputRecorder> {
|
||||
public:
|
||||
IFinalOutputRecorder() : ServiceFramework("IFinalOutputRecorder") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, nullptr, "GetFinalOutputRecorderState"},
|
||||
{1, nullptr, "StartFinalOutputRecorder"},
|
||||
@@ -20,10 +21,13 @@ public:
|
||||
{4, nullptr, "RegisterBufferEvent"},
|
||||
{5, nullptr, "GetReleasedFinalOutputRecorderBuffer"},
|
||||
{6, nullptr, "ContainsFinalOutputRecorderBuffer"},
|
||||
{7, nullptr, "Unknown"},
|
||||
{7, nullptr, "GetFinalOutputRecorderBufferEndTime"},
|
||||
{8, nullptr, "AppendFinalOutputRecorderBufferAuto"},
|
||||
{9, nullptr, "GetReleasedFinalOutputRecorderBufferAuto"},
|
||||
{10, nullptr, "FlushFinalOutputRecorderBuffers"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
~IFinalOutputRecorder() = default;
|
||||
|
||||
@@ -37,15 +37,16 @@ public:
|
||||
{8, &IAudioRenderer::SetRenderingTimeLimit, "SetRenderingTimeLimit"},
|
||||
{9, &IAudioRenderer::GetRenderingTimeLimit, "GetRenderingTimeLimit"},
|
||||
{10, &IAudioRenderer::RequestUpdateImpl, "RequestUpdateAuto"},
|
||||
{11, nullptr, "ExecuteAudioRendererRendering"},
|
||||
{11, &IAudioRenderer::ExecuteAudioRendererRendering, "ExecuteAudioRendererRendering"},
|
||||
};
|
||||
// clang-format on
|
||||
RegisterHandlers(functions);
|
||||
|
||||
auto& kernel = Core::System::GetInstance().Kernel();
|
||||
system_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Sticky,
|
||||
"IAudioRenderer:SystemEvent");
|
||||
renderer = std::make_unique<AudioCore::AudioRenderer>(audren_params, system_event.writable);
|
||||
auto& system = Core::System::GetInstance();
|
||||
system_event = Kernel::WritableEvent::CreateEventPair(
|
||||
system.Kernel(), Kernel::ResetType::Sticky, "IAudioRenderer:SystemEvent");
|
||||
renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), audren_params,
|
||||
system_event.writable);
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -137,6 +138,17 @@ private:
|
||||
rb.Push(rendering_time_limit_percent);
|
||||
}
|
||||
|
||||
void ExecuteAudioRendererRendering(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
// This service command currently only reports an unsupported operation
|
||||
// error code, or aborts. Given that, we just always return an error
|
||||
// code in this case.
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(ResultCode{ErrorModule::Audio, 201});
|
||||
}
|
||||
|
||||
Kernel::EventPair system_event;
|
||||
std::unique_ptr<AudioCore::AudioRenderer> renderer;
|
||||
u32 rendering_time_limit_percent = 100;
|
||||
@@ -229,14 +241,16 @@ private:
|
||||
}; // namespace Audio
|
||||
|
||||
AudRenU::AudRenU() : ServiceFramework("audren:u") {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &AudRenU::OpenAudioRenderer, "OpenAudioRenderer"},
|
||||
{1, &AudRenU::GetAudioRendererWorkBufferSize, "GetAudioRendererWorkBufferSize"},
|
||||
{2, &AudRenU::GetAudioDevice, "GetAudioDevice"},
|
||||
{2, &AudRenU::GetAudioDeviceService, "GetAudioDeviceService"},
|
||||
{3, nullptr, "OpenAudioRendererAuto"},
|
||||
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo,
|
||||
"GetAudioDeviceServiceWithRevisionInfo"},
|
||||
{4, &AudRenU::GetAudioDeviceServiceWithRevisionInfo, "GetAudioDeviceServiceWithRevisionInfo"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
@@ -259,20 +273,20 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
u64 buffer_sz = Common::AlignUp(4 * params.mix_buffer_count, 0x40);
|
||||
buffer_sz += params.unknown_c * 1024;
|
||||
buffer_sz += 0x940 * (params.unknown_c + 1);
|
||||
buffer_sz += params.submix_count * 1024;
|
||||
buffer_sz += 0x940 * (params.submix_count + 1);
|
||||
buffer_sz += 0x3F0 * params.voice_count;
|
||||
buffer_sz += Common::AlignUp(8 * (params.unknown_c + 1), 0x10);
|
||||
buffer_sz += Common::AlignUp(8 * (params.submix_count + 1), 0x10);
|
||||
buffer_sz += Common::AlignUp(8 * params.voice_count, 0x10);
|
||||
buffer_sz +=
|
||||
Common::AlignUp((0x3C0 * (params.sink_count + params.unknown_c) + 4 * params.sample_count) *
|
||||
(params.mix_buffer_count + 6),
|
||||
0x40);
|
||||
buffer_sz += Common::AlignUp(
|
||||
(0x3C0 * (params.sink_count + params.submix_count) + 4 * params.sample_count) *
|
||||
(params.mix_buffer_count + 6),
|
||||
0x40);
|
||||
|
||||
if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
|
||||
u32 count = params.unknown_c + 1;
|
||||
const u32 count = params.submix_count + 1;
|
||||
u64 node_count = Common::AlignUp(count, 0x40);
|
||||
u64 node_state_buffer_sz =
|
||||
const u64 node_state_buffer_sz =
|
||||
4 * (node_count * node_count) + 0xC * node_count + 2 * (node_count / 8);
|
||||
u64 edge_matrix_buffer_sz = 0;
|
||||
node_count = Common::AlignUp(count * count, 0x40);
|
||||
@@ -286,19 +300,19 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
buffer_sz += 0x20 * (params.effect_count + 4 * params.voice_count) + 0x50;
|
||||
if (IsFeatureSupported(AudioFeatures::Splitter, params.revision)) {
|
||||
buffer_sz += 0xE0 * params.unknown_2c;
|
||||
buffer_sz += 0xE0 * params.num_splitter_send_channels;
|
||||
buffer_sz += 0x20 * params.splitter_count;
|
||||
buffer_sz += Common::AlignUp(4 * params.unknown_2c, 0x10);
|
||||
buffer_sz += Common::AlignUp(4 * params.num_splitter_send_channels, 0x10);
|
||||
}
|
||||
buffer_sz = Common::AlignUp(buffer_sz, 0x40) + 0x170 * params.sink_count;
|
||||
u64 output_sz = buffer_sz + 0x280 * params.sink_count + 0x4B0 * params.effect_count +
|
||||
((params.voice_count * 256) | 0x40);
|
||||
|
||||
if (params.unknown_1c >= 1) {
|
||||
if (params.performance_frame_count >= 1) {
|
||||
output_sz = Common::AlignUp(((16 * params.sink_count + 16 * params.effect_count +
|
||||
16 * params.voice_count + 16) +
|
||||
0x658) *
|
||||
(params.unknown_1c + 1) +
|
||||
(params.performance_frame_count + 1) +
|
||||
0xc0,
|
||||
0x40) +
|
||||
output_sz;
|
||||
@@ -313,7 +327,7 @@ void AudRenU::GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "buffer_size=0x{:X}", output_sz);
|
||||
}
|
||||
|
||||
void AudRenU::GetAudioDevice(Kernel::HLERequestContext& ctx) {
|
||||
void AudRenU::GetAudioDeviceService(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
private:
|
||||
void OpenAudioRenderer(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioRendererWorkBufferSize(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDevice(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDeviceService(Kernel::HLERequestContext& ctx);
|
||||
void GetAudioDeviceServiceWithRevisionInfo(Kernel::HLERequestContext& ctx);
|
||||
|
||||
enum class AudioFeatures : u32 {
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#include <chrono>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
@@ -30,48 +29,66 @@ public:
|
||||
u32 channel_count)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder(std::move(decoder)),
|
||||
sample_rate(sample_rate), channel_count(channel_count) {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{0, &IHardwareOpusDecoderManager::DecodeInterleavedOld, "DecodeInterleavedOld"},
|
||||
{1, nullptr, "SetContext"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
{2, nullptr, "DecodeInterleavedForMultiStreamOld"},
|
||||
{3, nullptr, "SetContextForMultiStream"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerformance,
|
||||
"DecodeInterleavedWithPerformance"},
|
||||
{5, nullptr, "Unknown5"},
|
||||
{6, nullptr, "Unknown6"},
|
||||
{7, nullptr, "Unknown7"},
|
||||
{4, &IHardwareOpusDecoderManager::DecodeInterleavedWithPerfOld, "DecodeInterleavedWithPerfOld"},
|
||||
{5, nullptr, "DecodeInterleavedForMultiStreamWithPerfOld"},
|
||||
{6, &IHardwareOpusDecoderManager::DecodeInterleaved, "DecodeInterleaved"},
|
||||
{7, nullptr, "DecodeInterleavedForMultiStream"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
private:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
/// No extra behavior.
|
||||
None,
|
||||
|
||||
/// Resets the decoder context back to a freshly initialized state.
|
||||
ResetContext,
|
||||
};
|
||||
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
DecodeInterleavedHelper(ctx, nullptr, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
|
||||
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 4};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(consumed);
|
||||
rb.Push<u32>(sample_count);
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior =
|
||||
rp.Pop<bool>() ? ExtraBehavior::ResetContext : ExtraBehavior::None;
|
||||
|
||||
u64 performance = 0;
|
||||
DecodeInterleavedHelper(ctx, &performance, extra_behavior);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerformance(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
void DecodeInterleavedHelper(Kernel::HLERequestContext& ctx, u64* performance,
|
||||
ExtraBehavior extra_behavior) {
|
||||
u32 consumed = 0;
|
||||
u32 sample_count = 0;
|
||||
u64 performance = 0;
|
||||
std::vector<opus_int16> samples(ctx.GetWriteBufferSize() / sizeof(opus_int16));
|
||||
|
||||
if (extra_behavior == ExtraBehavior::ResetContext) {
|
||||
ResetDecoderContext();
|
||||
}
|
||||
|
||||
if (!Decoder_DecodeInterleaved(consumed, sample_count, ctx.ReadBuffer(), samples,
|
||||
performance)) {
|
||||
LOG_ERROR(Audio, "Failed to decode opus data");
|
||||
@@ -80,25 +97,28 @@ private:
|
||||
rb.Push(ResultCode(-1));
|
||||
return;
|
||||
}
|
||||
IPC::ResponseBuilder rb{ctx, 6};
|
||||
|
||||
const u32 param_size = performance != nullptr ? 6 : 4;
|
||||
IPC::ResponseBuilder rb{ctx, param_size};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(consumed);
|
||||
rb.Push<u32>(sample_count);
|
||||
rb.Push<u64>(performance);
|
||||
if (performance) {
|
||||
rb.Push<u64>(*performance);
|
||||
}
|
||||
ctx.WriteBuffer(samples.data(), samples.size() * sizeof(s16));
|
||||
}
|
||||
|
||||
bool Decoder_DecodeInterleaved(
|
||||
u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output,
|
||||
std::optional<std::reference_wrapper<u64>> performance_time = std::nullopt) {
|
||||
bool Decoder_DecodeInterleaved(u32& consumed, u32& sample_count, const std::vector<u8>& input,
|
||||
std::vector<opus_int16>& output, u64* out_performance_time) {
|
||||
const auto start_time = std::chrono::high_resolution_clock::now();
|
||||
std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
const std::size_t raw_output_sz = output.size() * sizeof(opus_int16);
|
||||
if (sizeof(OpusHeader) > input.size()) {
|
||||
LOG_ERROR(Audio, "Input is smaller than the header size, header_sz={}, input_sz={}",
|
||||
sizeof(OpusHeader), input.size());
|
||||
return false;
|
||||
}
|
||||
|
||||
OpusHeader hdr{};
|
||||
std::memcpy(&hdr, input.data(), sizeof(OpusHeader));
|
||||
if (sizeof(OpusHeader) + static_cast<u32>(hdr.sz) > input.size()) {
|
||||
@@ -106,8 +126,9 @@ private:
|
||||
sizeof(OpusHeader) + static_cast<u32>(hdr.sz), input.size());
|
||||
return false;
|
||||
}
|
||||
auto frame = input.data() + sizeof(OpusHeader);
|
||||
auto decoded_sample_count = opus_packet_get_nb_samples(
|
||||
|
||||
const auto frame = input.data() + sizeof(OpusHeader);
|
||||
const auto decoded_sample_count = opus_packet_get_nb_samples(
|
||||
frame, static_cast<opus_int32>(input.size() - sizeof(OpusHeader)),
|
||||
static_cast<opus_int32>(sample_rate));
|
||||
if (decoded_sample_count * channel_count * sizeof(u16) > raw_output_sz) {
|
||||
@@ -117,8 +138,9 @@ private:
|
||||
decoded_sample_count * channel_count * sizeof(u16), raw_output_sz);
|
||||
return false;
|
||||
}
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
auto out_sample_count =
|
||||
const auto out_sample_count =
|
||||
opus_decode(decoder.get(), frame, hdr.sz, output.data(), frame_size, 0);
|
||||
if (out_sample_count < 0) {
|
||||
LOG_ERROR(Audio,
|
||||
@@ -127,16 +149,24 @@ private:
|
||||
out_sample_count, frame_size, static_cast<u32>(hdr.sz));
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto end_time = std::chrono::high_resolution_clock::now() - start_time;
|
||||
sample_count = out_sample_count;
|
||||
consumed = static_cast<u32>(sizeof(OpusHeader) + hdr.sz);
|
||||
if (performance_time.has_value()) {
|
||||
performance_time->get() =
|
||||
if (out_performance_time != nullptr) {
|
||||
*out_performance_time =
|
||||
std::chrono::duration_cast<std::chrono::milliseconds>(end_time).count();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
struct OpusHeader {
|
||||
u32_be sz; // Needs to be BE for some odd reason
|
||||
INSERT_PADDING_WORDS(1);
|
||||
@@ -157,6 +187,7 @@ void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Audio, "called with sample_rate={}, channel_count={}", sample_rate, channel_count);
|
||||
|
||||
ASSERT_MSG(sample_rate == 48000 || sample_rate == 24000 || sample_rate == 16000 ||
|
||||
@@ -174,9 +205,10 @@ void HwOpus::GetWorkBufferSize(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
auto sample_rate = rp.Pop<u32>();
|
||||
auto channel_count = rp.Pop<u32>();
|
||||
auto buffer_sz = rp.Pop<u32>();
|
||||
const auto sample_rate = rp.Pop<u32>();
|
||||
const auto channel_count = rp.Pop<u32>();
|
||||
const auto buffer_sz = rp.Pop<u32>();
|
||||
|
||||
LOG_DEBUG(Audio, "called sample_rate={}, channel_count={}, buffer_size={}", sample_rate,
|
||||
channel_count, buffer_sz);
|
||||
|
||||
@@ -185,8 +217,9 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
"Invalid sample rate");
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
|
||||
|
||||
std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
const std::size_t worker_sz = WorkerBufferSize(channel_count);
|
||||
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
|
||||
|
||||
std::unique_ptr<OpusDecoder, OpusDeleter> decoder{
|
||||
static_cast<OpusDecoder*>(operator new(worker_sz))};
|
||||
if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user