Compare commits
525 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a00f9e4211 | |||
| 3cb9cd7724 | |||
| 9632d0ab8a | |||
| 68b68e676b | |||
| c7ced46707 | |||
| ecc194cb62 | |||
| a1b4ffcf52 | |||
| ae809ad320 | |||
| 7d1c18a569 | |||
| 71f034c4ea | |||
| b156b40f8f | |||
| 8d5a9dc722 | |||
| 06dc6909c4 | |||
| c639b7d5f4 | |||
| a38e144320 | |||
| 059c45f202 | |||
| eccceea7fb | |||
| ad340c3007 | |||
| c214f45e14 | |||
| c8620070b9 | |||
| 61a371b106 | |||
| 5210df6882 | |||
| edb7991979 | |||
| 90d6069a16 | |||
| 880a4e3ca6 | |||
| 18e30c7e44 | |||
| 0113224cb6 | |||
| d1bd936765 | |||
| 2c41eecdb1 | |||
| 1a9e38a1dd | |||
| 9f3f619774 | |||
| 795e234c6f | |||
| 23fdcbfeb8 | |||
| ee3e36672c | |||
| 119b4e4529 | |||
| cc6112cbc4 | |||
| f16c9cd3c6 | |||
| 9e4c24baa3 | |||
| 801609fb78 | |||
| 4c8630d29f | |||
| 4e426af114 | |||
| 5b66fe47db | |||
| 7ae9b15b5c | |||
| 67323994fc | |||
| fcc7a7452a | |||
| 1bb0caed6f | |||
| 23a445619b | |||
| 837d9ca688 | |||
| 9b121a6414 | |||
| 5f1aafb961 | |||
| 615ff08d1a | |||
| 3320d1e1c7 | |||
| 3deb177ab7 | |||
| 41dad58489 | |||
| cb8a240f1a | |||
| 871ef2cdfe | |||
| 6c0bc43776 | |||
| a16d18d83c | |||
| e499c3c6e3 | |||
| ddd261c943 | |||
| a1f9ff0aaa | |||
| d636ea9338 | |||
| 1747e68d1d | |||
| c4c710d39d | |||
| 9a5dbda063 | |||
| bd9c876e36 | |||
| 903d847058 | |||
| 789e42e26f | |||
| 85e11ca341 | |||
| 0da81d22d3 | |||
| 52b993095d | |||
| 363e0a40e5 | |||
| 7ea0a6d642 | |||
| 035d410a89 | |||
| b0b42071f2 | |||
| 5c24009eb5 | |||
| 00340f33c3 | |||
| 01c285fc4a | |||
| 87797e117e | |||
| a5cc9a5517 | |||
| 806acf8cd0 | |||
| 04f6302ecc | |||
| 1df977fae2 | |||
| 9105c2cf4a | |||
| 6d43fcf8fb | |||
| fafae1cbb5 | |||
| 419e673d5b | |||
| 48f47de95c | |||
| 44a1df4353 | |||
| 13b3c78c7f | |||
| e362c2c078 | |||
| 814adbb8c4 | |||
| cf86b6c107 | |||
| 12a83e5a1c | |||
| 0f769eec81 | |||
| 0737c788fc | |||
| 3fe9aea5b5 | |||
| ab6ba848b8 | |||
| 0a38800e44 | |||
| 6be5618d22 | |||
| 95d9462a71 | |||
| e05cd9f47b | |||
| aa09302ca0 | |||
| 6d6b9fd269 | |||
| 6d265452cf | |||
| 65d20f2d08 | |||
| a229e30b08 | |||
| d246a37b11 | |||
| e839595053 | |||
| a711eb31f4 | |||
| b117a6d4cd | |||
| 36b1004e26 | |||
| c75c47fdd2 | |||
| 0f626c8d30 | |||
| 021783dcc1 | |||
| 1f339862db | |||
| 4446d9ce4b | |||
| 92b08a4faf | |||
| d311b4bd53 | |||
| a6dfc3be2f | |||
| 750e7b73e3 | |||
| 6a36967e6f | |||
| d6d7ade6e3 | |||
| 151d98df51 | |||
| fa744e53d3 | |||
| e0df2c584f | |||
| 3c88477270 | |||
| df46e5137c | |||
| ffe8133865 | |||
| 7140e82189 | |||
| cb743da215 | |||
| 4d24ba1f35 | |||
| 87411da95f | |||
| d25d1ecb3a | |||
| 2a00a88e2a | |||
| b01220d2c5 | |||
| 58dd2bff41 | |||
| f34773ed73 | |||
| 9b5cc7cda7 | |||
| 77db8f04b9 | |||
| 9319bc579c | |||
| 73784b9e12 | |||
| 533f960854 | |||
| 765208a181 | |||
| 4417701ea7 | |||
| f66eaf8f44 | |||
| 0dd9c386ee | |||
| af8c871411 | |||
| 50e1e6596e | |||
| d8897c585d | |||
| 7d32dc312f | |||
| eff1e4bd7e | |||
| ac50b46608 | |||
| a0e2a3db1d | |||
| 0cc981ec18 | |||
| 8170b91f76 | |||
| d1a7bb0136 | |||
| 56caa10e56 | |||
| 926496b59d | |||
| 102d30db2d | |||
| 5281a85b67 | |||
| de28c812e8 | |||
| eea73deab3 | |||
| 67c02e3522 | |||
| 19dd2a693b | |||
| 3df133652a | |||
| 80d2034083 | |||
| db9389d24c | |||
| c996be321c | |||
| 6ff6a4989a | |||
| 2655255d4d | |||
| 8485afa3a5 | |||
| 2759091ede | |||
| ae02b71a85 | |||
| 4445569bb0 | |||
| a604394b20 | |||
| dd8a337b14 | |||
| 0572d44996 | |||
| 38aa116c59 | |||
| 61666bae69 | |||
| 362a26a404 | |||
| 81fa3da101 | |||
| bd1fb86492 | |||
| 238298f695 | |||
| 194f7375da | |||
| 3581c5b078 | |||
| c5572acd38 | |||
| acc2fea7e3 | |||
| 3dc7b64fa1 | |||
| 79f2c21dfb | |||
| df36c44bc2 | |||
| c4db65cc08 | |||
| c86ddb3b7d | |||
| a1aebdc5d6 | |||
| b49a1f27eb | |||
| e7fd84e031 | |||
| 9d9e18f614 | |||
| c8199de188 | |||
| fc331da883 | |||
| b6d3fa8c66 | |||
| 3cebea1801 | |||
| 2e9ee81dcd | |||
| cf4da5c4d1 | |||
| 9d1ec0b319 | |||
| 9c5108c1ca | |||
| 2807be7080 | |||
| ec7b18dab5 | |||
| b9e20dc5c9 | |||
| cfdc852f03 | |||
| 92514e33bb | |||
| 572a2a06d1 | |||
| f076fcd539 | |||
| 4ac59875af | |||
| 780ca38f2f | |||
| 97ea5e9dba | |||
| 9e83bc6544 | |||
| 37aefe5811 | |||
| d686b48f65 | |||
| f319362e35 | |||
| 0d9b1654ec | |||
| 940e726754 | |||
| f43824762a | |||
| cd53bb7eff | |||
| 60fc90bb8e | |||
| a9437d69ab | |||
| 5d91caebe9 | |||
| eb4131e1c7 | |||
| 07f0ba213e | |||
| 48382564d1 | |||
| 274386a078 | |||
| 557d015662 | |||
| 5985f0eefa | |||
| 82258915da | |||
| 11a1f090d3 | |||
| a0d48c588a | |||
| 5e2424da58 | |||
| 72a6696f5c | |||
| 9087116ee5 | |||
| 89e0dcba5e | |||
| 7698064f5a | |||
| ab31e96385 | |||
| a9dcad629b | |||
| e49679484a | |||
| 290226539f | |||
| 75efc79930 | |||
| 905496e926 | |||
| 64579eef7e | |||
| e0a648240f | |||
| 7bad8f3348 | |||
| 11a197a387 | |||
| ee1384341e | |||
| fb01ed55e5 | |||
| 5a0a5d9ff1 | |||
| 58e3232710 | |||
| 2cd7238e76 | |||
| 21f0043b27 | |||
| b7599c0f30 | |||
| d8494ef1ae | |||
| 727b32e886 | |||
| 8124efa505 | |||
| a5cff4d797 | |||
| d1e5828644 | |||
| 18d73d6d76 | |||
| 4a3a24dcc1 | |||
| 591159c4a9 | |||
| 7baf959975 | |||
| 3f02935844 | |||
| f98251a05a | |||
| 80f0741103 | |||
| e28707055b | |||
| 698c3415ea | |||
| daf43989fc | |||
| c7586de280 | |||
| 506b9deec5 | |||
| 1fc0191311 | |||
| 1f9e04f72d | |||
| a71bdc761e | |||
| 9d8f4d5cfa | |||
| ee87fdc869 | |||
| bf63a18c5f | |||
| ddba30988b | |||
| b0e2c959eb | |||
| 28bec8e1bf | |||
| 86702186f2 | |||
| 82b1a2bd7a | |||
| d36cf71a2d | |||
| 1dd1062be1 | |||
| c0e3b86064 | |||
| 6210a8491f | |||
| 33a4f05ffa | |||
| a74d3005f8 | |||
| bca833dad7 | |||
| f981e05908 | |||
| b5abd777b0 | |||
| b2c9add47e | |||
| a401a192b8 | |||
| ad1b007dd1 | |||
| 71e35c8b4d | |||
| 2e504b2dac | |||
| 6e9df9e452 | |||
| 8aa5899196 | |||
| b6608ad31d | |||
| edb39039dd | |||
| bacf518189 | |||
| 1244044647 | |||
| b960ce1426 | |||
| c55a889c23 | |||
| 829af30568 | |||
| a3007e11ca | |||
| 9a981b5292 | |||
| 4464a6c3f6 | |||
| d30ac20fad | |||
| 7bb2d94e53 | |||
| 7ff4509858 | |||
| f2997e4c00 | |||
| 133e9d4705 | |||
| 501ca4bcf5 | |||
| 9212942724 | |||
| 683c1a6512 | |||
| 9b0d33048c | |||
| bc8bf2c329 | |||
| f0002501f7 | |||
| c4cc0154be | |||
| cba4c3cdc4 | |||
| c51d3b5465 | |||
| e179adc4a0 | |||
| 47f3740a70 | |||
| 2880370047 | |||
| 61a055a1c6 | |||
| 64b4cfa59f | |||
| c799ffd223 | |||
| 2ec039365f | |||
| 26e731b487 | |||
| 34bae90820 | |||
| b55a052a22 | |||
| 011aabe9ed | |||
| a17a6527f6 | |||
| 122c6256ca | |||
| 73aaff1b29 | |||
| c7fbc16357 | |||
| bc728db15b | |||
| 2eebbd307d | |||
| dc851a729e | |||
| 6007fd630f | |||
| cb0ecb2afb | |||
| 84a785ea67 | |||
| cc1e4d2194 | |||
| b2d84dcebd | |||
| 958d77ba52 | |||
| c4b38869ab | |||
| fd1968dd22 | |||
| 88b80ebdf5 | |||
| 2445ab8d8e | |||
| 0e1931f178 | |||
| 3e923b4993 | |||
| 8181498d86 | |||
| 57dd611111 | |||
| b3162bd41c | |||
| cd2adbad9a | |||
| 99730ac4f9 | |||
| d2ab3383ad | |||
| 3b15a6b39e | |||
| 651e58f443 | |||
| 769f9e33e9 | |||
| cf77bb7cfc | |||
| c75b76ddf5 | |||
| 4dec8d7925 | |||
| 934e7b614c | |||
| 416b14de72 | |||
| b87099c790 | |||
| 052725fdc7 | |||
| f90b79791f | |||
| 4c32c95b33 | |||
| c00d47cc99 | |||
| ea53c7dc85 | |||
| 2b923c681e | |||
| 7a8e918519 | |||
| d99ae3809d | |||
| 51e6d0a336 | |||
| 228844c017 | |||
| 87e628a9e9 | |||
| e6885e25b5 | |||
| f31ffc4596 | |||
| 4114f00c05 | |||
| 8f7209f886 | |||
| d80146c704 | |||
| 577f379a12 | |||
| 3d73915358 | |||
| a5317d50a5 | |||
| 143bd6cc80 | |||
| 5cc8f970c0 | |||
| c9058280e0 | |||
| 5af50cfd55 | |||
| d846142f0c | |||
| 0ba0f9d2b9 | |||
| f4f9c1865e | |||
| c40439ae6b | |||
| 683fa2a392 | |||
| 246bf1df64 | |||
| 9a868e9239 | |||
| ab3cde1939 | |||
| bec3e156fb | |||
| 086afbbaa5 | |||
| cd4d73114c | |||
| 14ca506774 | |||
| 984bf67d84 | |||
| 2b3ae8b638 | |||
| bb9ae18c71 | |||
| ce01457510 | |||
| d1a46b491e | |||
| 31df99f7d6 | |||
| 5e11b2b056 | |||
| d106934336 | |||
| 68718361a7 | |||
| c887865d54 | |||
| dc48944979 | |||
| d22b8b1d3a | |||
| 56619b20cf | |||
| 12ebf77e22 | |||
| 845af8bd96 | |||
| 680f08c2b8 | |||
| 82bac4173e | |||
| 8a8fda3e02 | |||
| 1c5b685398 | |||
| e95084f138 | |||
| e12d268662 | |||
| 6a3f17cd36 | |||
| db0774a81b | |||
| 34615d3a74 | |||
| 189499c10b | |||
| de613a8a98 | |||
| c0369b2e10 | |||
| 07ebbb6c84 | |||
| 73ed657e00 | |||
| b37217b181 | |||
| b02e6e222f | |||
| c52abed4d3 | |||
| 9e57efe82c | |||
| 77ab872bec | |||
| 27acebc5f5 | |||
| 2755d9941c | |||
| 488982f424 | |||
| 086e12c6ca | |||
| 9b19f16698 | |||
| b13fd68848 | |||
| 3e51426379 | |||
| 105781fa76 | |||
| d2be12bb07 | |||
| fdc15e12c4 | |||
| e548743cbf | |||
| 7499f875a6 | |||
| cdef752a9c | |||
| 1483941bea | |||
| 9cc15be7f2 | |||
| 453e1bfaec | |||
| 82439327fa | |||
| f60002e87d | |||
| 9fc6382909 | |||
| ebad08aa97 | |||
| 599f1dd157 | |||
| bb5ce67d57 | |||
| f948ce399e | |||
| 954c60947d | |||
| 98b6783c05 | |||
| 0894d2886a | |||
| 4995b4abe3 | |||
| 35ef19cfc8 | |||
| 09824a718f | |||
| f4e85af53c | |||
| 74a909cfbc | |||
| 0bfdfd8433 | |||
| 1f5cf776b2 | |||
| cdd6840826 | |||
| ab27ee4cf4 | |||
| 6211295155 | |||
| befd7ceb89 | |||
| 2cead6f328 | |||
| 4e3d58f75a | |||
| 24fddf1ded | |||
| 87cd65ff03 | |||
| 219e3d61f8 | |||
| 4ade06f36f | |||
| 786510a937 | |||
| 0c6df39a45 | |||
| 648ad0cd90 | |||
| cd0d43382f | |||
| 98b730c806 | |||
| 4b787b22c8 | |||
| 5c6f5a1610 | |||
| 0b8d5f7b98 | |||
| 314d481dcd | |||
| 0980c68e6f | |||
| 983bfa2088 | |||
| 3a9928de65 | |||
| 1fd5fb2f7e | |||
| 4ac88fa8d3 | |||
| 5f8f9e33f1 | |||
| f0c71ae2ae | |||
| 0c7ecdc954 | |||
| 73a687df7d | |||
| b683110e72 | |||
| 3840015e7d | |||
| 639b8112b6 | |||
| 2bf17f5d3c | |||
| 4823d4c32a | |||
| c8c035eeef | |||
| 155bd09fd0 | |||
| f546e5a8ef | |||
| 2ccb0c8f42 | |||
| 05303ce6c6 | |||
| d8f3029952 | |||
| 09c28f75b3 | |||
| 57671e267d | |||
| b692108f1e | |||
| 2815aecd0c | |||
| b9f2b1e7c6 | |||
| bd9ba7ef1f | |||
| a015bd1ebd | |||
| d6cf167d52 | |||
| 3fc2194fd5 | |||
| 72c14994f0 | |||
| fddb6a31a7 | |||
| 278ae7763a | |||
| 5cafaef0a9 | |||
| fcd297ffb2 |
+11
-11
@@ -1,9 +1,9 @@
|
||||
#!/bin/sh -ex
|
||||
|
||||
# Setup Qt variables
|
||||
export QT_BASE_DIR=/opt/qt${QTVERMIN}
|
||||
export PATH=$QT_BASE_DIR/bin:$PATH
|
||||
export LD_LIBRARY_PATH=$QT_BASE_DIR/lib/x86_64-linux-gnu:$QT_BASE_DIR/lib
|
||||
export QT_BASE_DIR=/opt/qt"${QTVERMIN}"
|
||||
export PATH="$QT_BASE_DIR"/bin:"$PATH"
|
||||
export LD_LIBRARY_PATH="$QT_BASE_DIR"/lib/x86_64-linux-gnu:"$QT_BASE_DIR"/lib
|
||||
|
||||
if [ -z "$CIRRUS_CI" ]; then
|
||||
cd rpcs3 || exit 1
|
||||
@@ -23,19 +23,19 @@ mkdir build && cd build || exit 1
|
||||
|
||||
if [ "$COMPILER" = "gcc" ]; then
|
||||
# These are set in the dockerfile
|
||||
export CC=${GCC_BINARY}
|
||||
export CXX=${GXX_BINARY}
|
||||
export CC="${GCC_BINARY}"
|
||||
export CXX="${GXX_BINARY}"
|
||||
export LINKER=gold
|
||||
# We need to set the following variables for LTO to link properly
|
||||
export AR=/usr/bin/gcc-ar-$GCCVER
|
||||
export RANLIB=/usr/bin/gcc-ranlib-$GCCVER
|
||||
export AR=/usr/bin/gcc-ar-"$GCCVER"
|
||||
export RANLIB=/usr/bin/gcc-ranlib-"$GCCVER"
|
||||
export CFLAGS="-fuse-linker-plugin"
|
||||
else
|
||||
export CC=${CLANG_BINARY}
|
||||
export CXX=${CLANGXX_BINARY}
|
||||
export CC="${CLANG_BINARY}"
|
||||
export CXX="${CLANGXX_BINARY}"
|
||||
export LINKER=lld
|
||||
export AR=/usr/bin/llvm-ar-$LLVMVER
|
||||
export RANLIB=/usr/bin/llvm-ranlib-$LLVMVER
|
||||
export AR=/usr/bin/llvm-ar-"$LLVMVER"
|
||||
export RANLIB=/usr/bin/llvm-ranlib-"$LLVMVER"
|
||||
fi
|
||||
|
||||
export CFLAGS="$CFLAGS -fuse-ld=${LINKER}"
|
||||
|
||||
+1
-1
@@ -13,7 +13,7 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then
|
||||
ls ./appdir/usr/lib/
|
||||
rm -r ./appdir/usr/share/doc
|
||||
cp "$(readlink -f /lib/x86_64-linux-gnu/libnsl.so.1)" ./appdir/usr/lib/libnsl.so.1
|
||||
export PATH=/rpcs3/build/squashfs-root/usr/bin/:${PATH}
|
||||
export PATH=/rpcs3/build/squashfs-root/usr/bin/:"${PATH}"
|
||||
|
||||
# Embed newer libstdc++ for distros that don't come with it (ubuntu 16.04)
|
||||
mkdir -p appdir/usr/optional/ ; mkdir -p appdir/usr/optional/libstdc++/
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
|
||||
|
||||
cd bin
|
||||
mkdir "rpcs3.app/Contents/lib/"
|
||||
cp "/usr/local/opt/llvm@13/lib/libc++abi.1.0.dylib" "rpcs3.app/Contents/lib/libc++abi.1.dylib"
|
||||
cp "/usr/local/opt/llvm@13/lib/c++/libc++abi.1.0.dylib" "rpcs3.app/Contents/lib/libc++abi.1.dylib"
|
||||
rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \
|
||||
"rpcs3.app/Contents/Frameworks/QtQml.framework" \
|
||||
"rpcs3.app/Contents/Frameworks/QtQmlModels.framework" \
|
||||
|
||||
+2
-2
@@ -61,7 +61,7 @@ windows_task:
|
||||
|
||||
linux_task:
|
||||
container:
|
||||
image: rpcs3/rpcs3-ci-bionic:1.3
|
||||
image: rpcs3/rpcs3-ci-bionic:1.5
|
||||
cpu: 4
|
||||
memory: 16G
|
||||
env:
|
||||
@@ -108,7 +108,7 @@ freebsd_task:
|
||||
matrix:
|
||||
- name: Cirrus FreeBSD
|
||||
freebsd_instance:
|
||||
image_family: freebsd-13-0
|
||||
image_family: freebsd-13-1
|
||||
cpu: 8
|
||||
memory: 8G
|
||||
env:
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@ Cpp11BracedListStyle: true
|
||||
IndentCaseLabels: false
|
||||
SortIncludes: false
|
||||
ReflowComments: true
|
||||
AlignConsecutiveAssignments: true
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignTrailingComments: true
|
||||
AlignAfterOpenBracket: DontAlign
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
---
|
||||
name: Regression report
|
||||
about: If something used to work before, but now it doesn't
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
|
||||
## Quick summary
|
||||
Please briefly describe what has stopped working correctly.
|
||||
|
||||
## Details
|
||||
Please describe the regression as accurately as possible.
|
||||
|
||||
#### 0. Make sure you're running with settings as close to default as possible
|
||||
* **Do NOT enable any emulator game patches when reporting issues**
|
||||
* Only change settings that are required for the game to work
|
||||
|
||||
#### 1. Please provide _exact_ build (or commit) information that introduced the regression you're reporting.
|
||||
* Please see [How to find the build that caused a regression](https://wiki.rpcs3.net/index.php?title=Help:Using_different_versions_of_RPCS3#How_to_find_the_build_that_caused_a_regression.3F) in our wiki.
|
||||
* You can find [History of RPCS3 builds](https://rpcs3.net/compatibility?b) here.
|
||||
|
||||
#### 2. Please attach TWO logs:
|
||||
* One for the build with regression.
|
||||
* One for the build that works as expected.
|
||||
|
||||
##### How to obtain RPCS3's log:
|
||||
* Run the game until you find the regression.
|
||||
* Completely close RPCS3, or move to the next step in case it has crashed.
|
||||
* Locate RPCS3's log file:
|
||||
+ ```RPCS3.log``` (It can show up as just RPCS3 and have a notepad icon)
|
||||
or
|
||||
+ ```RPCS3.log.gz``` (It can show up as RPCS3.log and have a zip or rar icon)
|
||||

|
||||
On Linux it will be in ```~/.cache/rpcs3/```.
|
||||
On Windows it will be near the executable.
|
||||
* Attach the log:
|
||||
+ Drag and drop your log file into the issue.
|
||||
+ Or upload it to the cloud, such as Dropbox, Mega etc.
|
||||
|
||||
#### 3. If you describe graphical regression, please provide an RSX capture and a RenderDoc capture that demonstrate it.
|
||||
* To create an RSX capture, use _Create_ _RSX_ _Capture_ under _Utilities_.
|
||||
Captures will be stored in RPCS3 folder → captures.
|
||||
+ Compress your capture with 7z, Rar etc.
|
||||
And drag and drop it into the issue.
|
||||
+ Or upload it to the cloud, such as Dropbox, Mega etc.
|
||||
* To create a RenderDoc capture, please refer to [RenderDoc's documentation](https://renderdoc.org/docs/how/how_capture_frame.html).
|
||||
|
||||
#### 4. Please attach screenshots of your problem.
|
||||
* Enable performance overlay with at least medium level of detail.
|
||||
You can find it in _Emulator_ tab in _Settings_.
|
||||
|
||||
#### 5. Please provide comparison with real PS3.
|
||||
|
||||
#### 6. Please provide your system configuration:
|
||||
* OS
|
||||
* CPU
|
||||
* GPU
|
||||
* Driver version
|
||||
* etc.
|
||||
|
||||
#### Please include.
|
||||
* Anything else you deem to be important
|
||||
@@ -0,0 +1,93 @@
|
||||
name: Regression report
|
||||
description: If something used to work before, but now it doesn't
|
||||
title: "[Regression] Enter a title here"
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Summary
|
||||
Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
- type: textarea
|
||||
id: quick-summary
|
||||
attributes:
|
||||
label: Quick summary
|
||||
description: Please briefly describe what has stopped working correctly.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Details
|
||||
description: Please describe the regression as accurately as possible. Include screenshots if neccessary.
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Identify the regressed build
|
||||
Please provide the _exact_ build (or commit) information that introduced the regression you're reporting.
|
||||
* Please see [How to find the build that caused a regression](https://wiki.rpcs3.net/index.php?title=Help:Using_different_versions_of_RPCS3#How_to_find_the_build_that_caused_a_regression.3F) in our wiki.
|
||||
* You can find [History of RPCS3 builds](https://rpcs3.net/compatibility?b) here.
|
||||
|
||||
Make sure you're running with settings as close to default as possible
|
||||
* **Do NOT enable any emulator game patches when reporting issues**
|
||||
* Only change settings that are required for the game to work
|
||||
- type: input
|
||||
id: regressed-build
|
||||
attributes:
|
||||
label: Build with regression
|
||||
description: Provide _exact_ build (or commit) information that introduced the regression you're reporting.
|
||||
placeholder: v0.0.23-13948-31df99f7
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Log files
|
||||
Obtaining the log file:
|
||||
* Run the game until you find the regression.
|
||||
* Completely close RPCS3 and locate the log file.
|
||||
|
||||
RPCS3's Log file will be ```RPCS3.log.gz``` (sometimes shows as RPCS3.log with zip icon) or ```RPCS3.log``` (sometimes shows as RPCS3 wtih notepad icon).
|
||||
* On Windows it will be in the RPCS3 directory near the executable
|
||||
* On Linux it will be in ```~/.cache/rpcs3/```
|
||||
* On MacOS it will be in ```~/Library/Caches/rpcs3```. If you're unable to locate it copy paste the path in Spotlight and hit enter.
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Attach two log files
|
||||
description: |
|
||||
Attach one file for the build with regression and another for a build that works.
|
||||
Drag & drop the files into this input field, or upload them to another service (f.ex. Dropbox, Mega) and provide a link.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Other details
|
||||
If you describe a graphical regression, please provide an RSX capture and/or a RenderDoc capture that demonstrate it.
|
||||
* To create an RSX capture, use _Create_ _RSX_ _Capture_ under _Utilities_.
|
||||
* Captures will be stored in RPCS3 folder → captures.
|
||||
* To create a RenderDoc capture, please refer to [RenderDoc's documentation](https://renderdoc.org/docs/how/how_capture_frame.html).
|
||||
- type: textarea
|
||||
id: captures
|
||||
attributes:
|
||||
label: Attach capture files for visual issues
|
||||
description: Compress your capture with 7z, Rar etc. and attach it here, or upload it to the cloud (Dropbox, Mega etc) and add a link to it.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System configuration
|
||||
description: Provide information about your system, such as operating system, CPU and GPU model, GPU driver version and other information that describes your system configuration.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
description: Include anything else you deem to be important.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,59 +0,0 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: If something doesn't work correctly in RPCS3
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
|
||||
## Quick summary
|
||||
Please briefly describe what is not working correctly.
|
||||
|
||||
## Details
|
||||
|
||||
Please describe the problem as accurately as possible.
|
||||
|
||||
#### 0. Make sure you're running with settings as close to default as possible
|
||||
* **Do NOT enable any emulator game patches when reporting issues**
|
||||
* Only change settings that are required for the game to work
|
||||
|
||||
#### 1. Please attach RPCS3's log.
|
||||
* Run the game until you find the issue.
|
||||
* Completely close RPCS3, or move to the next step in case it has crashed.
|
||||
* Locate RPCS3's log file:
|
||||
+ ```RPCS3.log``` (It can show up as just RPCS3 and have a notepad icon)
|
||||
or
|
||||
+ ```RPCS3.log.gz``` (It can show up as RPCS3.log and have a zip or rar icon)
|
||||

|
||||
On Linux it will be in ```~/.cache/rpcs3/```.
|
||||
On Windows it will be near the executable.
|
||||
* Attach the log:
|
||||
+ Drag and drop your log file into the issue.
|
||||
+ Or upload it to the cloud, such as Dropbox, Mega etc.
|
||||
|
||||
#### 2. If you describe graphical issue, please provide an RSX capture and a RenderDoc capture that demonstrate it.
|
||||
* To create an RSX capture, use _Create_ _RSX_ _Capture_ under _Utilities_.
|
||||
Captures will be stored in RPCS3 folder → captures.
|
||||
+ Compress your capture with 7z, Rar etc.
|
||||
And drag and drop it into the issue.
|
||||
+ Or upload it to the cloud, such as Dropbox, Mega etc.
|
||||
* To create a RenderDoc capture, please refer to [RenderDoc's documentation](https://renderdoc.org/docs/how/how_capture_frame.html).
|
||||
|
||||
#### 3. Please attach screenshots of your problem.
|
||||
* Enable performance overlay with at least medium level of detail.
|
||||
You can find it in _Emulator_ tab in _Settings_.
|
||||
|
||||
#### 4. Please provide comparison with real PS3.
|
||||
|
||||
#### 5. Please provide your system configuration:
|
||||
* OS
|
||||
* CPU
|
||||
* GPU
|
||||
* Driver version
|
||||
* etc.
|
||||
|
||||
#### Please include.
|
||||
* Anything else you deem to be important
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Bug report
|
||||
description: If something doesn't work correctly in RPCS3
|
||||
title: "Enter a title here"
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Summary
|
||||
Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
- type: textarea
|
||||
id: quick-summary
|
||||
attributes:
|
||||
label: Quick summary
|
||||
description: Please briefly describe what is not working correctly.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Details
|
||||
description: |
|
||||
Please describe the problem as accurately as possible.
|
||||
Provide a comparison with a real PS3, if possible.
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Log files
|
||||
|
||||
Provide a log file that includes the bug you're reporting.
|
||||
|
||||
Obtaining the log file:
|
||||
* Run the game until you find the regression.
|
||||
* Completely close RPCS3 and locate the log file.
|
||||
|
||||
RPCS3's Log file will be ```RPCS3.log.gz``` (sometimes shows as RPCS3.log with zip icon) or ```RPCS3.log``` (sometimes shows as RPCS3 wtih notepad icon).
|
||||
* On Windows it will be in the RPCS3 directory near the executable
|
||||
* On Linux it will be in ```~/.cache/rpcs3/```
|
||||
* On MacOS it will be in ```~/Library/Caches/rpcs3```. If you're unable to locate it copy paste the path in Spotlight and hit enter.
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Attach a log file
|
||||
description: |
|
||||
Drag & drop the files into this input field, or upload them to another service (f.ex. Dropbox, Mega) and provide a link.
|
||||
validations:
|
||||
required: true
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
# Other details
|
||||
If you describe a graphical issue, please provide an RSX capture and/or a RenderDoc capture that demonstrate it.
|
||||
* To create an RSX capture, use _Create_ _RSX_ _Capture_ under _Utilities_.
|
||||
* Captures will be stored in RPCS3 folder → captures.
|
||||
* To create a RenderDoc capture, please refer to [RenderDoc's documentation](https://renderdoc.org/docs/how/how_capture_frame.html).
|
||||
- type: textarea
|
||||
id: captures
|
||||
attributes:
|
||||
label: Attach capture files for visual issues
|
||||
description: Compress your capture with 7z, Rar etc. and attach it here, or upload it to the cloud (Dropbox, Mega etc) and add a link to it.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: system-info
|
||||
attributes:
|
||||
label: System configuration
|
||||
description: Provide information about your system, such as operating system, CPU and GPU model, GPU driver version and other information that describes your system configuration.
|
||||
validations:
|
||||
required: false
|
||||
- type: textarea
|
||||
id: other-details
|
||||
attributes:
|
||||
label: Other details
|
||||
description: Include anything else you deem to be important.
|
||||
validations:
|
||||
required: false
|
||||
@@ -1,41 +0,0 @@
|
||||
---
|
||||
name: Feature Request
|
||||
about: If RPCS3 lacks a feature you would like to see
|
||||
title: '[Feature Request] Enter Feature Title Here'
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
|
||||
## Quick summary
|
||||
Please briefly describe what feature you would like RPCS3 to have.
|
||||
|
||||
## Details
|
||||
|
||||
Please describe the feature as accurately as possible.
|
||||
|
||||
#### 1. Please describe, what part of RPCS3 would be affected by your feature:
|
||||
* Gameplay
|
||||
* Debugging
|
||||
* UI
|
||||
* Patches
|
||||
* Installation
|
||||
* Github
|
||||
* CI
|
||||
* etc.
|
||||
|
||||
#### 2. Please tell us, why your feature is important to RPCS3.
|
||||
|
||||
#### 3. Please attach screenshots of the feature implemented in other projects.
|
||||
|
||||
#### 4. Please provide your system configuration:
|
||||
* OS
|
||||
* CPU
|
||||
* GPU
|
||||
* Driver version
|
||||
* etc.
|
||||
|
||||
#### Please include.
|
||||
* Anything else you deem to be important
|
||||
@@ -0,0 +1,36 @@
|
||||
name: Feature request
|
||||
description: If RPCS3 lacks a feature you would like to see
|
||||
title: "[Feature request] Enter a title here"
|
||||
labels: []
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
- type: textarea
|
||||
id: quick-summary
|
||||
attributes:
|
||||
label: Quick summary
|
||||
description: Please briefly describe what feature you would like RPCS3 to have.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: details
|
||||
attributes:
|
||||
label: Details
|
||||
description: Please describe the feature as accurately as possible.
|
||||
validations:
|
||||
required: false
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Please include the following information:
|
||||
* What part of RPCS3 would be affected by your feature? (Gameplay, Debugging, UI, Patches, Installation, CI etc.)
|
||||
* Why your feature is important to RPCS3.
|
||||
* If the feature is implemented in other projects, attach screenshots.
|
||||
* If this feature is something that a game is trying to use, upload a log file for it.
|
||||
|
||||
RPCS3's Log file will be ```RPCS3.log.gz``` (sometimes shows as RPCS3.log with zip icon) or ```RPCS3.log``` (sometimes shows as RPCS3 wtih notepad icon).
|
||||
* On Windows it will be in the RPCS3 directory near the executable
|
||||
* On Linux it will be in ```~/.cache/rpcs3/```
|
||||
* On MacOS it will be in ```~/Library/Caches/rpcs3```. If you're unable to locate it copy paste the path in Spotlight and hit enter.
|
||||
@@ -0,0 +1,14 @@
|
||||
---
|
||||
name: Advanced
|
||||
about: For developers and experienced users only
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
## Please do not ask for help or report compatibility regressions here, use [RPCS3 Discord server](https://discord.me/RPCS3) or [forums](https://forums.rpcs3.net/) instead.
|
||||
|
||||
You're using the advanced template. You're expected to know what to write in order to fill in all the required information for proper report.
|
||||
|
||||
If you're unsure on what to do, please return back to the issue type selection and choose different category.
|
||||
Vendored
+9
-5
@@ -1,5 +1,5 @@
|
||||
/* 7zTypes.h -- Basic types
|
||||
2021-12-25 : Igor Pavlov : Public domain */
|
||||
2022-04-01 : Igor Pavlov : Public domain */
|
||||
|
||||
#ifndef __7Z_TYPES_H
|
||||
#define __7Z_TYPES_H
|
||||
@@ -133,10 +133,6 @@ typedef int WRes;
|
||||
#define MY__E_ERROR_NEGATIVE_SEEK MY_HRESULT_FROM_errno_CONST_ERROR(EINVAL)
|
||||
*/
|
||||
|
||||
// gcc / clang : (sizeof(long) == sizeof(void*)) in 32/64 bits
|
||||
typedef long INT_PTR;
|
||||
typedef unsigned long UINT_PTR;
|
||||
|
||||
#define TEXT(quote) quote
|
||||
|
||||
#define FILE_ATTRIBUTE_READONLY 0x0001
|
||||
@@ -520,6 +516,14 @@ struct ISzAlloc
|
||||
|
||||
#endif
|
||||
|
||||
#define k_PropVar_TimePrec_0 0
|
||||
#define k_PropVar_TimePrec_Unix 1
|
||||
#define k_PropVar_TimePrec_DOS 2
|
||||
#define k_PropVar_TimePrec_HighPrec 3
|
||||
#define k_PropVar_TimePrec_Base 16
|
||||
#define k_PropVar_TimePrec_100ns (k_PropVar_TimePrec_Base + 7)
|
||||
#define k_PropVar_TimePrec_1ns (k_PropVar_TimePrec_Base + 9)
|
||||
|
||||
EXTERN_C_END
|
||||
|
||||
#endif
|
||||
|
||||
Vendored
+5
-5
@@ -1,7 +1,7 @@
|
||||
#define MY_VER_MAJOR 21
|
||||
#define MY_VER_MINOR 07
|
||||
#define MY_VER_MAJOR 22
|
||||
#define MY_VER_MINOR 01
|
||||
#define MY_VER_BUILD 0
|
||||
#define MY_VERSION_NUMBERS "21.07"
|
||||
#define MY_VERSION_NUMBERS "22.01"
|
||||
#define MY_VERSION MY_VERSION_NUMBERS
|
||||
|
||||
#ifdef MY_CPU_NAME
|
||||
@@ -10,12 +10,12 @@
|
||||
#define MY_VERSION_CPU MY_VERSION
|
||||
#endif
|
||||
|
||||
#define MY_DATE "2021-12-26"
|
||||
#define MY_DATE "2022-07-15"
|
||||
#undef MY_COPYRIGHT
|
||||
#undef MY_VERSION_COPYRIGHT_DATE
|
||||
#define MY_AUTHOR_NAME "Igor Pavlov"
|
||||
#define MY_COPYRIGHT_PD "Igor Pavlov : Public domain"
|
||||
#define MY_COPYRIGHT_CR "Copyright (c) 1999-2021 Igor Pavlov"
|
||||
#define MY_COPYRIGHT_CR "Copyright (c) 1999-2022 Igor Pavlov"
|
||||
|
||||
#ifdef USE_COPYRIGHT_CR
|
||||
#define MY_COPYRIGHT MY_COPYRIGHT_CR
|
||||
|
||||
Vendored
+10
-7
@@ -1,5 +1,5 @@
|
||||
/* CpuArch.h -- CPU specific code
|
||||
2021-07-13 : Igor Pavlov : Public domain */
|
||||
2022-07-15 : Igor Pavlov : Public domain */
|
||||
|
||||
#ifndef __CPU_ARCH_H
|
||||
#define __CPU_ARCH_H
|
||||
@@ -123,12 +123,15 @@ MY_CPU_64BIT means that processor can work with 64-bit registers.
|
||||
#endif
|
||||
|
||||
|
||||
#if defined(__sparc64__)
|
||||
#define MY_CPU_NAME "sparc64"
|
||||
#define MY_CPU_64BIT
|
||||
#elif defined(__sparc__)
|
||||
#define MY_CPU_NAME "sparc"
|
||||
/* #define MY_CPU_32BIT */
|
||||
#if defined(__riscv) \
|
||||
|| defined(__riscv__)
|
||||
#if __riscv_xlen == 32
|
||||
#define MY_CPU_NAME "riscv32"
|
||||
#elif __riscv_xlen == 64
|
||||
#define MY_CPU_NAME "riscv64"
|
||||
#else
|
||||
#define MY_CPU_NAME "riscv"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
Vendored
+6
-2
@@ -1,5 +1,5 @@
|
||||
/* DllSecur.c -- DLL loading security
|
||||
2021-12-25 : Igor Pavlov : Public domain */
|
||||
2022-07-15 : Igor Pavlov : Public domain */
|
||||
|
||||
#include "Precomp.h"
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
|
||||
#ifndef UNDER_CE
|
||||
|
||||
#if defined(__GNUC__) && (__GNUC__ >= 8)
|
||||
#pragma GCC diagnostic ignored "-Wcast-function-type"
|
||||
#endif
|
||||
|
||||
typedef BOOL (WINAPI *Func_SetDefaultDllDirectories)(DWORD DirectoryFlags);
|
||||
|
||||
#define MY_LOAD_LIBRARY_SEARCH_USER_DIRS 0x400
|
||||
@@ -34,7 +38,7 @@ static const char * const g_Dlls =
|
||||
#endif
|
||||
|
||||
// #define MY_CAST_FUNC (void(*)())
|
||||
#define MY_CAST_FUNC
|
||||
#define MY_CAST_FUNC
|
||||
|
||||
void My_SetDefaultDllDirectories()
|
||||
{
|
||||
|
||||
Vendored
+4
-7
@@ -1,5 +1,5 @@
|
||||
/* LzmaEnc.c -- LZMA Encoder
|
||||
2021-11-18: Igor Pavlov : Public domain */
|
||||
2022-07-15: Igor Pavlov : Public domain */
|
||||
|
||||
#include "Precomp.h"
|
||||
|
||||
@@ -2970,6 +2970,7 @@ const Byte *LzmaEnc_GetCurBuf(CLzmaEncHandle pp)
|
||||
}
|
||||
|
||||
|
||||
// (desiredPackSize == 0) is not allowed
|
||||
SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, BoolInt reInit,
|
||||
Byte *dest, size_t *destLen, UInt32 desiredPackSize, UInt32 *unpackSize)
|
||||
{
|
||||
@@ -2990,14 +2991,10 @@ SRes LzmaEnc_CodeOneMemBlock(CLzmaEncHandle pp, BoolInt reInit,
|
||||
if (reInit)
|
||||
LzmaEnc_Init(p);
|
||||
LzmaEnc_InitPrices(p);
|
||||
|
||||
nowPos64 = p->nowPos64;
|
||||
RangeEnc_Init(&p->rc);
|
||||
p->rc.outStream = &outStream.vt;
|
||||
|
||||
if (desiredPackSize == 0)
|
||||
return SZ_ERROR_OUTPUT_EOF;
|
||||
|
||||
nowPos64 = p->nowPos64;
|
||||
|
||||
res = LzmaEnc_CodeOneBlock(p, desiredPackSize, *unpackSize);
|
||||
|
||||
*unpackSize = (UInt32)(p->nowPos64 - nowPos64);
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/FAudio updated: 2faf0dbdb2...e787ed7655
+24
-3
@@ -153,6 +153,20 @@ typedef int64_t khronos_int64_t;
|
||||
typedef uint64_t khronos_uint64_t;
|
||||
#define KHRONOS_SUPPORT_INT64 1
|
||||
#define KHRONOS_SUPPORT_FLOAT 1
|
||||
/*
|
||||
* To support platform where unsigned long cannot be used interchangeably with
|
||||
* inptr_t (e.g. CHERI-extended ISAs), we can use the stdint.h intptr_t.
|
||||
* Ideally, we could just use (u)intptr_t everywhere, but this could result in
|
||||
* ABI breakage if khronos_uintptr_t is changed from unsigned long to
|
||||
* unsigned long long or similar (this results in different C++ name mangling).
|
||||
* To avoid changes for existing platforms, we restrict usage of intptr_t to
|
||||
* platforms where the size of a pointer is larger than the size of long.
|
||||
*/
|
||||
#if defined(__SIZEOF_LONG__) && defined(__SIZEOF_POINTER__)
|
||||
#if __SIZEOF_POINTER__ > __SIZEOF_LONG__
|
||||
#define KHRONOS_USE_INTPTR_T
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#elif defined(__VMS ) || defined(__sgi)
|
||||
|
||||
@@ -235,14 +249,21 @@ typedef unsigned short int khronos_uint16_t;
|
||||
* pointers are 64 bits, but 'long' is still 32 bits. Win64 appears
|
||||
* to be the only LLP64 architecture in current use.
|
||||
*/
|
||||
#ifdef _WIN64
|
||||
#ifdef KHRONOS_USE_INTPTR_T
|
||||
typedef intptr_t khronos_intptr_t;
|
||||
typedef uintptr_t khronos_uintptr_t;
|
||||
#elif defined(_WIN64)
|
||||
typedef signed long long int khronos_intptr_t;
|
||||
typedef unsigned long long int khronos_uintptr_t;
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_intptr_t;
|
||||
typedef unsigned long int khronos_uintptr_t;
|
||||
#endif
|
||||
|
||||
#if defined(_WIN64)
|
||||
typedef signed long long int khronos_ssize_t;
|
||||
typedef unsigned long long int khronos_usize_t;
|
||||
#else
|
||||
typedef signed long int khronos_ssize_t;
|
||||
typedef unsigned long int khronos_usize_t;
|
||||
#endif
|
||||
Vendored
+36
-2
@@ -32,9 +32,9 @@ extern "C" {
|
||||
#define GLAPI extern
|
||||
#endif
|
||||
|
||||
#define GL_GLEXT_VERSION 20210420
|
||||
#define GL_GLEXT_VERSION 20220530
|
||||
|
||||
#include "khrplatform.h"
|
||||
#include <KHR/khrplatform.h>
|
||||
|
||||
/* Generated C header for:
|
||||
* API: gl
|
||||
@@ -8214,6 +8214,10 @@ GLAPI void APIENTRY glMemoryBarrierEXT (GLbitfield barriers);
|
||||
#define GL_EXT_shader_integer_mix 1
|
||||
#endif /* GL_EXT_shader_integer_mix */
|
||||
|
||||
#ifndef GL_EXT_shader_samples_identical
|
||||
#define GL_EXT_shader_samples_identical 1
|
||||
#endif /* GL_EXT_shader_samples_identical */
|
||||
|
||||
#ifndef GL_EXT_shadow_funcs
|
||||
#define GL_EXT_shadow_funcs 1
|
||||
#endif /* GL_EXT_shadow_funcs */
|
||||
@@ -8625,6 +8629,36 @@ GLAPI void APIENTRY glTextureNormalEXT (GLenum mode);
|
||||
#define GL_RGBA_SNORM 0x8F93
|
||||
#endif /* GL_EXT_texture_snorm */
|
||||
|
||||
#ifndef GL_EXT_texture_storage
|
||||
#define GL_EXT_texture_storage 1
|
||||
#define GL_TEXTURE_IMMUTABLE_FORMAT_EXT 0x912F
|
||||
#define GL_RGBA32F_EXT 0x8814
|
||||
#define GL_RGB32F_EXT 0x8815
|
||||
#define GL_ALPHA32F_EXT 0x8816
|
||||
#define GL_LUMINANCE32F_EXT 0x8818
|
||||
#define GL_LUMINANCE_ALPHA32F_EXT 0x8819
|
||||
#define GL_RGBA16F_EXT 0x881A
|
||||
#define GL_RGB16F_EXT 0x881B
|
||||
#define GL_ALPHA16F_EXT 0x881C
|
||||
#define GL_LUMINANCE16F_EXT 0x881E
|
||||
#define GL_LUMINANCE_ALPHA16F_EXT 0x881F
|
||||
#define GL_BGRA8_EXT 0x93A1
|
||||
#define GL_R8_EXT 0x8229
|
||||
#define GL_RG8_EXT 0x822B
|
||||
#define GL_R32F_EXT 0x822E
|
||||
#define GL_RG32F_EXT 0x8230
|
||||
#define GL_R16F_EXT 0x822D
|
||||
#define GL_RG16F_EXT 0x822F
|
||||
typedef void (APIENTRYP PFNGLTEXSTORAGE1DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
|
||||
typedef void (APIENTRYP PFNGLTEXSTORAGE2DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
typedef void (APIENTRYP PFNGLTEXSTORAGE3DEXTPROC) (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
|
||||
#ifdef GL_GLEXT_PROTOTYPES
|
||||
GLAPI void APIENTRY glTexStorage1DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width);
|
||||
GLAPI void APIENTRY glTexStorage2DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height);
|
||||
GLAPI void APIENTRY glTexStorage3DEXT (GLenum target, GLsizei levels, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth);
|
||||
#endif
|
||||
#endif /* GL_EXT_texture_storage */
|
||||
|
||||
#ifndef GL_EXT_texture_swizzle
|
||||
#define GL_EXT_texture_swizzle 1
|
||||
#define GL_TEXTURE_SWIZZLE_R_EXT 0x8E42
|
||||
|
||||
Vendored
+18
-18
@@ -747,12 +747,12 @@ AF1 sharpness){
|
||||
// Immediate constants for peak range.
|
||||
AF2 peakC=AF2(1.0,-1.0*4.0);
|
||||
// Limiters, these need to be high precision RCPs.
|
||||
AF1 hitMinR=mn4R*ARcpF1(AF1_(4.0)*mx4R);
|
||||
AF1 hitMinG=mn4G*ARcpF1(AF1_(4.0)*mx4G);
|
||||
AF1 hitMinB=mn4B*ARcpF1(AF1_(4.0)*mx4B);
|
||||
AF1 hitMaxR=(peakC.x-mx4R)*ARcpF1(AF1_(4.0)*mn4R+peakC.y);
|
||||
AF1 hitMaxG=(peakC.x-mx4G)*ARcpF1(AF1_(4.0)*mn4G+peakC.y);
|
||||
AF1 hitMaxB=(peakC.x-mx4B)*ARcpF1(AF1_(4.0)*mn4B+peakC.y);
|
||||
AF1 hitMinR=min(mn4R,eR)*ARcpF1(AF1_(4.0)*mx4R);
|
||||
AF1 hitMinG=min(mn4G,eG)*ARcpF1(AF1_(4.0)*mx4G);
|
||||
AF1 hitMinB=min(mn4B,eB)*ARcpF1(AF1_(4.0)*mx4B);
|
||||
AF1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpF1(AF1_(4.0)*mn4R+peakC.y);
|
||||
AF1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpF1(AF1_(4.0)*mn4G+peakC.y);
|
||||
AF1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpF1(AF1_(4.0)*mn4B+peakC.y);
|
||||
AF1 lobeR=max(-hitMinR,hitMaxR);
|
||||
AF1 lobeG=max(-hitMinG,hitMaxG);
|
||||
AF1 lobeB=max(-hitMinB,hitMaxB);
|
||||
@@ -845,12 +845,12 @@ AF1 sharpness){
|
||||
// Immediate constants for peak range.
|
||||
AH2 peakC=AH2(1.0,-1.0*4.0);
|
||||
// Limiters, these need to be high precision RCPs.
|
||||
AH1 hitMinR=mn4R*ARcpH1(AH1_(4.0)*mx4R);
|
||||
AH1 hitMinG=mn4G*ARcpH1(AH1_(4.0)*mx4G);
|
||||
AH1 hitMinB=mn4B*ARcpH1(AH1_(4.0)*mx4B);
|
||||
AH1 hitMaxR=(peakC.x-mx4R)*ARcpH1(AH1_(4.0)*mn4R+peakC.y);
|
||||
AH1 hitMaxG=(peakC.x-mx4G)*ARcpH1(AH1_(4.0)*mn4G+peakC.y);
|
||||
AH1 hitMaxB=(peakC.x-mx4B)*ARcpH1(AH1_(4.0)*mn4B+peakC.y);
|
||||
AH1 hitMinR=min(mn4R,eR)*ARcpH1(AH1_(4.0)*mx4R);
|
||||
AH1 hitMinG=min(mn4G,eG)*ARcpH1(AH1_(4.0)*mx4G);
|
||||
AH1 hitMinB=min(mn4B,eB)*ARcpH1(AH1_(4.0)*mx4B);
|
||||
AH1 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH1(AH1_(4.0)*mn4R+peakC.y);
|
||||
AH1 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH1(AH1_(4.0)*mn4G+peakC.y);
|
||||
AH1 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH1(AH1_(4.0)*mn4B+peakC.y);
|
||||
AH1 lobeR=max(-hitMinR,hitMaxR);
|
||||
AH1 lobeG=max(-hitMinG,hitMaxG);
|
||||
AH1 lobeB=max(-hitMinB,hitMaxB);
|
||||
@@ -963,12 +963,12 @@ AF1 sharpness){
|
||||
// Immediate constants for peak range.
|
||||
AH2 peakC=AH2(1.0,-1.0*4.0);
|
||||
// Limiters, these need to be high precision RCPs.
|
||||
AH2 hitMinR=mn4R*ARcpH2(AH2_(4.0)*mx4R);
|
||||
AH2 hitMinG=mn4G*ARcpH2(AH2_(4.0)*mx4G);
|
||||
AH2 hitMinB=mn4B*ARcpH2(AH2_(4.0)*mx4B);
|
||||
AH2 hitMaxR=(peakC.x-mx4R)*ARcpH2(AH2_(4.0)*mn4R+peakC.y);
|
||||
AH2 hitMaxG=(peakC.x-mx4G)*ARcpH2(AH2_(4.0)*mn4G+peakC.y);
|
||||
AH2 hitMaxB=(peakC.x-mx4B)*ARcpH2(AH2_(4.0)*mn4B+peakC.y);
|
||||
AH2 hitMinR=min(mn4R,eR)*ARcpH2(AH2_(4.0)*mx4R);
|
||||
AH2 hitMinG=min(mn4G,eG)*ARcpH2(AH2_(4.0)*mx4G);
|
||||
AH2 hitMinB=min(mn4B,eB)*ARcpH2(AH2_(4.0)*mx4B);
|
||||
AH2 hitMaxR=(peakC.x-max(mx4R,eR))*ARcpH2(AH2_(4.0)*mn4R+peakC.y);
|
||||
AH2 hitMaxG=(peakC.x-max(mx4G,eG))*ARcpH2(AH2_(4.0)*mn4G+peakC.y);
|
||||
AH2 hitMaxB=(peakC.x-max(mx4B,eB))*ARcpH2(AH2_(4.0)*mn4B+peakC.y);
|
||||
AH2 lobeR=max(-hitMinR,hitMaxR);
|
||||
AH2 lobeG=max(-hitMinG,hitMaxG);
|
||||
AH2 lobeB=max(-hitMinB,hitMaxB);
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@ include(ExternalProject)
|
||||
|
||||
ExternalProject_Add(moltenvk
|
||||
GIT_REPOSITORY https://github.com/KhronosGroup/MoltenVK.git
|
||||
GIT_TAG 1236d2f
|
||||
GIT_TAG 260bad4
|
||||
BUILD_IN_SOURCE 1
|
||||
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK
|
||||
CONFIGURE_COMMAND "${CMAKE_CURRENT_SOURCE_DIR}/MoltenVK/fetchDependencies" --macos
|
||||
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
diff --git a/ExternalRevisions/SPIRV-Cross_repo_revision b/ExternalRevisions/SPIRV-Cross_repo_revision
|
||||
index 777346a0..c1971b45 100644
|
||||
--- a/ExternalRevisions/SPIRV-Cross_repo_revision
|
||||
+++ b/ExternalRevisions/SPIRV-Cross_repo_revision
|
||||
@@ -1 +1 @@
|
||||
-0d4ce028bf8b8a94d325dc1e1c20446153ba19c4
|
||||
+44c3333a1c315ead00c24f7aef5fa8a7ccf49299
|
||||
Vendored
+1
-1
Submodule 3rdparty/SPIRV/SPIRV-Headers updated: 4995a2f272...b2a156e1c0
Vendored
+1
-1
Submodule 3rdparty/SPIRV/SPIRV-Tools updated: 7826e1941e...5e61ea2098
Vendored
+9
-9
@@ -39,28 +39,28 @@
|
||||
<PropertyGroup Label="UserMacros">
|
||||
<CmakeGenerator>"Visual Studio $(VisualStudioVersion.Substring(0,2))"</CmakeGenerator>
|
||||
<CmakeReleaseCLI>call vsdevcmd.bat -arch=amd64
|
||||
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Release" -DSPIRV_WERROR=OFF -DSPIRV-Headers_SOURCE_DIR=$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/SPIRV-Headers').Replace('\', '/')) SPIRV-Tools -B build</CmakeReleaseCLI>
|
||||
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Release" -DSPIRV_WERROR=OFF -DSPIRV-Headers_SOURCE_DIR="$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/SPIRV-Headers').Replace('\', '/'))" SPIRV-Tools -B build</CmakeReleaseCLI>
|
||||
<CmakeDebugCLI>call vsdevcmd.bat -arch=amd64
|
||||
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Debug" -DSPIRV_WERROR=OFF -DSPIRV-Headers_SOURCE_DIR=$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/SPIRV-Headers').Replace('\', '/')) -S SPIRV-Tools -B build</CmakeDebugCLI>
|
||||
cmake -G $(CmakeGenerator) -A x64 -DCMAKE_BUILD_TYPE="Debug" -DSPIRV_WERROR=OFF -DSPIRV-Headers_SOURCE_DIR="$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)/SPIRV-Headers').Replace('\', '/'))" -S SPIRV-Tools -B build</CmakeDebugCLI>
|
||||
<PropsAbsPath>$([System.IO.Path]::GetFullPath('$(MSBuildThisFileDirectory)..\..\buildfiles\msvc\common_default.props'))</PropsAbsPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<NMakeBuildCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeBuildCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeReBuildCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeCleanCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<NMakeBuildCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
@@ -68,4 +68,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Vendored
+1
-1
@@ -27,7 +27,7 @@ target_compile_definitions(soundtouch PUBLIC
|
||||
SOUNDTOUCH_FLOAT_SAMPLES;
|
||||
)
|
||||
|
||||
target_compile_options(soundtouch PUBLIC "-w")
|
||||
target_compile_options(soundtouch PRIVATE "-w")
|
||||
|
||||
if (CMAKE_SYSTEM_PROCESSOR MATCHES "^(x86|X86|amd64|AMD64|em64t|EM64T)")
|
||||
target_compile_definitions(soundtouch PUBLIC
|
||||
|
||||
+1276
-1275
File diff suppressed because it is too large
Load Diff
BIN
Binary file not shown.
Vendored
+1
-1
Submodule 3rdparty/asmjit/asmjit updated: fc2a5d82f7...06d0badec5
Vendored
+5
-3
@@ -62,12 +62,13 @@
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64builder.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64compiler.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64emithelper.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64formatter.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64func.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64instapi.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64instdb.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64operand.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64rapass.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\armformatter.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\armfunc.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="asmjit\src\asmjit\core\api-build_p.h" />
|
||||
@@ -134,11 +135,14 @@
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86opcode_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86operand.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86rapass_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64archtraits_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64assembler.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64builder.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64compiler.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64emithelper_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64emitter.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64formatter_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64func_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64globals.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64instapi_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64instdb.h" />
|
||||
@@ -146,9 +150,7 @@
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64operand.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64rapass_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64utils.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armarchtraits_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armformatter_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armfunc_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armglobals.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armoperand.h" />
|
||||
</ItemGroup>
|
||||
|
||||
+5
-3
@@ -52,12 +52,13 @@
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64builder.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64compiler.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64emithelper.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64formatter.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64func.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64instapi.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64instdb.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64operand.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\a64rapass.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\armformatter.cpp" />
|
||||
<ClCompile Include="asmjit\src\asmjit\arm\armfunc.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="asmjit\src\asmjit\core\api-build_p.h" />
|
||||
@@ -124,11 +125,14 @@
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86opcode_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86operand.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\x86\x86rapass_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64archtraits_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64assembler.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64builder.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64compiler.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64emithelper_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64emitter.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64formatter_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64func_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64globals.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64instapi_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64instdb.h" />
|
||||
@@ -136,9 +140,7 @@
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64operand.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64rapass_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\a64utils.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armarchtraits_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armformatter_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armfunc_p.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armglobals.h" />
|
||||
<ClInclude Include="asmjit\src\asmjit\arm\armoperand.h" />
|
||||
</ItemGroup>
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/cubeb/cubeb updated: 708f52cccf...dc511c6b35
Vendored
+1
@@ -12,6 +12,7 @@ else()
|
||||
option(CURL_USE_WOLFSSL "enable wolfSSL for SSL/TLS" ON)
|
||||
option(HTTP_ONLY "disables all protocols except HTTP (This overrides all CURL_DISABLE_* options)" ON)
|
||||
option(CURL_ZLIB "Set to ON to enable building curl with zlib support." OFF)
|
||||
option(USE_LIBIDN2 "Use libidn2 for IDN support" OFF) # Disabled because MacOs CI doesn't work otherwise
|
||||
set(CURL_CA_PATH "none" CACHE STRING "Location of default CA path. Set 'none' to disable or 'auto' for auto-detection. Defaults to 'auto'.")
|
||||
if(USE_MSVC_STATIC_CRT)
|
||||
set(CURL_STATIC_CRT ON CACHE BOOL "Use static crt to build curl" FORCE)
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/curl/curl updated: 462196e6b4...45ac4d0194
Vendored
+3
@@ -98,6 +98,7 @@
|
||||
<ClCompile Include="curl\lib\escape.c" />
|
||||
<ClCompile Include="curl\lib\file.c" />
|
||||
<ClCompile Include="curl\lib\fileinfo.c" />
|
||||
<ClCompile Include="curl\lib\fopen.c" />
|
||||
<ClCompile Include="curl\lib\formdata.c" />
|
||||
<ClCompile Include="curl\lib\ftp.c" />
|
||||
<ClCompile Include="curl\lib\ftplistparser.c" />
|
||||
@@ -279,9 +280,11 @@
|
||||
<ClInclude Include="curl\lib\dynbuf.h" />
|
||||
<ClInclude Include="curl\lib\easyif.h" />
|
||||
<ClInclude Include="curl\lib\easyoptions.h" />
|
||||
<ClInclude Include="curl\lib\easy_lock.h" />
|
||||
<ClInclude Include="curl\lib\escape.h" />
|
||||
<ClInclude Include="curl\lib\file.h" />
|
||||
<ClInclude Include="curl\lib\fileinfo.h" />
|
||||
<ClInclude Include="curl\lib\fopen.h" />
|
||||
<ClInclude Include="curl\lib\formdata.h" />
|
||||
<ClInclude Include="curl\lib\ftp.h" />
|
||||
<ClInclude Include="curl\lib\ftplistparser.h" />
|
||||
|
||||
+9
@@ -480,6 +480,9 @@
|
||||
<ClCompile Include="curl\lib\timediff.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="curl\lib\fopen.c">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="curl\include\curl\curl.h">
|
||||
@@ -965,6 +968,12 @@
|
||||
<ClInclude Include="curl\lib\timediff.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="curl\lib\easy_lock.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="curl\lib\fopen.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="curl\lib\libcurl.rc">
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/flatbuffers updated: 615616cb55...06c5c7ed0b
Vendored
+1
-1
Submodule 3rdparty/glslang/glslang updated: adbf0d3106...10423ec659
Vendored
+7
-7
@@ -46,21 +46,21 @@
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<NMakeBuildCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeBuildCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeReBuildCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>$(CmakeReleaseCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Release /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m</NMakeCleanCommandLine>
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Release /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<NMakeBuildCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:build /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeBuildCommandLine>
|
||||
<NMakeReBuildCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:rebuild /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeReBuildCommandLine>
|
||||
<NMakeCleanCommandLine>$(CmakeDebugCLI)
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Debug /p:ForceImportBeforeCppTargets=$(PropsAbsPath) /m
|
||||
msbuild.exe build\ALL_BUILD.vcxproj /t:clean /p:Configuration=Debug /p:ForceImportBeforeCppTargets="$(PropsAbsPath)" /m
|
||||
</NMakeCleanCommandLine>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
@@ -68,4 +68,4 @@
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/libpng/libpng updated: a40189cf88...0a158f3506
Vendored
+5
-10
@@ -265,9 +265,8 @@ namespace pine
|
||||
case MsgVersion:
|
||||
{
|
||||
char version[256] = {};
|
||||
sprintf(version, "RPCS3 %s", Impl::get_version_and_branch().c_str());
|
||||
snprintf(version, sizeof(version), "RPCS3 %s", Impl::get_version_and_branch().c_str());
|
||||
const u32 size = strlen(version) + 1;
|
||||
version[size] = 0x00;
|
||||
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size))
|
||||
goto error;
|
||||
ToArray(ret_buffer, size, ret_cnt);
|
||||
@@ -291,8 +290,7 @@ namespace pine
|
||||
const auto title_string = Impl::get_title();
|
||||
const auto size = title_string.size() + 1;
|
||||
char* title = new char[size];
|
||||
sprintf(title, "%s", title_string.c_str());
|
||||
title[size] = 0x00;
|
||||
snprintf(title, size, "%s", title_string.c_str());
|
||||
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size))
|
||||
goto error;
|
||||
ToArray(ret_buffer, size, ret_cnt);
|
||||
@@ -307,8 +305,7 @@ namespace pine
|
||||
const auto title_id_string = Impl::get_title_ID();
|
||||
const auto size = title_id_string.size() + 1;
|
||||
char* title_id = new char[size];
|
||||
sprintf(title_id, "%s", title_id_string.c_str());
|
||||
title_id[size] = 0x00;
|
||||
snprintf(title_id, size, "%s", title_id_string.c_str());
|
||||
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size))
|
||||
goto error;
|
||||
ToArray(ret_buffer, size, ret_cnt);
|
||||
@@ -323,8 +320,7 @@ namespace pine
|
||||
const auto hash_string = Impl::get_executable_hash();
|
||||
const auto size = hash_string.size() + 1;
|
||||
char* hash = new char[size];
|
||||
sprintf(hash, "%s", hash_string.c_str());
|
||||
hash[size] = 0x00;
|
||||
snprintf(hash, size, "%s", hash_string.c_str());
|
||||
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size + 4, buf_size))
|
||||
goto error;
|
||||
ToArray(ret_buffer, size, ret_cnt);
|
||||
@@ -339,8 +335,7 @@ namespace pine
|
||||
const auto game_version_string = Impl::get_app_version();
|
||||
const auto size = game_version_string.size() + 1;
|
||||
char* game_version = new char[size];
|
||||
sprintf(game_version, "%s", game_version_string.c_str());
|
||||
game_version[size] = 0x00;
|
||||
snprintf(game_version, size, "%s", game_version_string.c_str());
|
||||
if (!SafetyChecks(buf_cnt, 0, ret_cnt, size, buf_size))
|
||||
goto error;
|
||||
ToArray(ret_buffer, size, ret_cnt);
|
||||
|
||||
Vendored
+3
-2
@@ -5,9 +5,10 @@ if(USE_SYSTEM_WOLFSSL)
|
||||
target_link_libraries(wolfssl INTERFACE PkgConfig::WolfSSL)
|
||||
else()
|
||||
# TODO(cjj19970505@live.cn)
|
||||
# OPENSSL_EXTRA, WOLFSSL_DES_ECB and HAVE_SNI are unconfigurable from CMake cache.
|
||||
# OPENSSL_EXTRA, and HAVE_SNI are unconfigurable from CMake cache.
|
||||
# but they do have it in a TODO list (wolfssl/CMakeList, 1021)
|
||||
add_compile_definitions(OPENSSL_EXTRA WOLFSSL_DES_ECB HAVE_SNI HAVE_WRITE_DUP)
|
||||
add_compile_definitions(FP_MAX_BITS=8192)
|
||||
|
||||
set(WOLFSSL_TLS13 "no" CACHE INTERNAL "")
|
||||
set(WOLFSSL_SHA224 "yes" CACHE INTERNAL "")
|
||||
@@ -19,7 +20,7 @@ else()
|
||||
set(WOLFSSL_CHACHA "yes" CACHE INTERNAL "")
|
||||
set(WOLFSSL_FILESYSTEM "yes" CACHE INTERNAL "")
|
||||
set(WOLFSSL_PWDBASED "yes" CACHE INTERNAL "")
|
||||
set(WOLFSSL_FAST_MATH "no" CACHE INTERNAL "")
|
||||
set(WOLFSSL_FAST_MATH "yes" CACHE INTERNAL "")
|
||||
set(WOLFSSL_EXAMPLES "no" CACHE INTERNAL "")
|
||||
set(WOLFSSL_CRYPT_TESTS "no" CACHE INTERNAL "")
|
||||
set(WOLFSSL_ASYNC_THREADS "no" CACHE INTERNAL "")
|
||||
|
||||
@@ -43,6 +43,8 @@
|
||||
#define HAVE_ECC
|
||||
#define ECC_SHAMIR
|
||||
#define ECC_TIMING_RESISTANT
|
||||
#define USE_FAST_MATH
|
||||
#define FP_MAX_BITS 8192
|
||||
|
||||
/* UTF-8 aware filesystem functions for Windows */
|
||||
#define WOLFSSL_USER_FILESYSTEM
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/wolfssl/wolfssl updated: e722c15be8...57aac1c50b
Vendored
+1
-1
Submodule 3rdparty/zlib/zlib updated: 21767c654d...04f42ceca4
+4
-3
@@ -102,9 +102,10 @@ git submodule update --init
|
||||
|
||||
#### Configuring the Qt plugin (if used)
|
||||
|
||||
1) Go to the Qt5 menu and edit Qt5 options.
|
||||
2) Add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\5.15.2\msvc2019_64`.
|
||||
3) While selecting the rpcs3qt project, go to Qt5->Project Setting and select the version you added.
|
||||
1) Go to `Extensions->Qt VS Tools->Qt Versions`.
|
||||
2) Add the path to your Qt installation with compiler e.g. `<QtInstallFolder>\5.15.2\msvc2019_64`, version will fill in automatically.
|
||||
3) Go to `Extensions->Qt VS Tools->Options->Legacy Project Format`.
|
||||
4) Set `Build: Run pre-build setup` to `true`.
|
||||
|
||||
#### Building the projects
|
||||
|
||||
|
||||
+3
-3
@@ -104,9 +104,9 @@ if(WIN32)
|
||||
add_definitions(-D_WIN32_WINNT=0x0602)
|
||||
endif()
|
||||
|
||||
if(APPLE)
|
||||
include_directories(/opt/local/include)
|
||||
link_directories(/opt/local/lib)
|
||||
if(APPLE AND CMAKE_OSX_ARCHITECTURES STREQUAL "arm64")
|
||||
include_directories(/opt/homebrew/include)
|
||||
link_directories(/opt/homebrew/lib)
|
||||
endif()
|
||||
|
||||
# Warnings are silenced for 3rdparty code
|
||||
|
||||
@@ -5,10 +5,10 @@ RPCS3
|
||||
[&logo=cirrus-ci)](https://cirrus-ci.com/github/RPCS3/rpcs3)
|
||||
[](https://discord.me/rpcs3)
|
||||
|
||||
The world's first free and open-source PlayStation 3 emulator/debugger, written in C++ for Windows and Linux.
|
||||
The world's first free and open-source PlayStation 3 emulator/debugger, written in C++ for Windows, Linux, macOS and FreeBSD.
|
||||
|
||||
You can find some basic information on our [**website**](https://rpcs3.net/). Game info is being populated on the [**Wiki**](https://wiki.rpcs3.net/).
|
||||
For discussion about this emulator, PS3 emulation, and game compatibility reports, please visit our [**forums**](https://forums.rpcs3.net) and our [**Discord server**](https://discord.me/RPCS3).
|
||||
For discussion about this emulator, PS3 emulation, and game compatibility reports, please visit our [**forums**](https://forums.rpcs3.net) and our [**Discord server**](https://discord.gg/RPCS3).
|
||||
|
||||
[**Support Lead Developers Nekotekina and kd-11 on Patreon**](https://www.patreon.com/Nekotekina)
|
||||
|
||||
|
||||
+56
-8
@@ -60,9 +60,15 @@ std::vector<std::string> cfg::make_int_range(s64 min, s64 max)
|
||||
|
||||
bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
if (out) cfg_log.error("cfg::try_to_uint64(): called with an empty string");
|
||||
return false;
|
||||
}
|
||||
|
||||
s64 result;
|
||||
const char* start = &value.front();
|
||||
const char* end = &value.back() + 1;
|
||||
const char* start = value.data();
|
||||
const char* end = start + value.size();
|
||||
int base = 10;
|
||||
int sign = +1;
|
||||
|
||||
@@ -72,7 +78,7 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max)
|
||||
start += 1;
|
||||
}
|
||||
|
||||
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
||||
if (start[0] == '0' && value.size() >= 2 && (start[1] == 'x' || start[1] == 'X'))
|
||||
{
|
||||
// Limited hex support
|
||||
base = 16;
|
||||
@@ -106,12 +112,18 @@ std::vector<std::string> cfg::make_uint_range(u64 min, u64 max)
|
||||
|
||||
bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
if (out) cfg_log.error("cfg::try_to_uint64(): called with an empty string");
|
||||
return false;
|
||||
}
|
||||
|
||||
u64 result;
|
||||
const char* start = &value.front();
|
||||
const char* end = &value.back() + 1;
|
||||
const char* start = value.data();
|
||||
const char* end = start + value.size();
|
||||
int base = 10;
|
||||
|
||||
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
||||
if (start[0] == '0' && value.size() >= 2 && (start[1] == 'x' || start[1] == 'X'))
|
||||
{
|
||||
// Limited hex support
|
||||
base = 16;
|
||||
@@ -136,6 +148,42 @@ bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max)
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<std::string> cfg::make_float_range(f64 min, f64 max)
|
||||
{
|
||||
return {std::to_string(min), std::to_string(max)};
|
||||
}
|
||||
|
||||
bool try_to_float(f64* out, std::string_view value, f64 min, f64 max)
|
||||
{
|
||||
if (value.empty())
|
||||
{
|
||||
if (out) cfg_log.error("cfg::try_to_float(): called with an empty string");
|
||||
return false;
|
||||
}
|
||||
|
||||
// std::from_chars float is yet to be implemented on Xcode it seems
|
||||
// And strtod doesn't support ranged view so we need to ensure it meets a null terminator
|
||||
const std::string str = std::string{value};
|
||||
|
||||
char* end_check{};
|
||||
const double result = std::strtod(str.data(), &end_check);
|
||||
|
||||
if (end_check != str.data() + str.size())
|
||||
{
|
||||
if (out) cfg_log.error("cfg::try_to_float('%s'): invalid float", value);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (result < min || result > max)
|
||||
{
|
||||
if (out) cfg_log.error("cfg::try_to_float('%s'): out of bounds (%f..%f)", value, min, max);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out) *out = result;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view value)
|
||||
{
|
||||
u64 max = umax;
|
||||
@@ -162,8 +210,8 @@ bool cfg::try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) f
|
||||
}
|
||||
|
||||
u64 result;
|
||||
const char* start = &value.front();
|
||||
const char* end = &value.back() + 1;
|
||||
const char* start = value.data();
|
||||
const char* end = start + value.size();
|
||||
int base = 10;
|
||||
|
||||
if (start[0] == '0' && (start[1] == 'x' || start[1] == 'X'))
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace cfg
|
||||
// Format min and max unsigned values
|
||||
std::vector<std::string> make_uint_range(u64 min, u64 max);
|
||||
|
||||
// Format min and max float values
|
||||
std::vector<std::string> make_float_range(f64 min, f64 max);
|
||||
|
||||
// Internal hack
|
||||
bool try_to_enum_value(u64* out, decltype(&fmt_class_string<int>::format) func, std::string_view);
|
||||
|
||||
@@ -309,6 +312,72 @@ namespace cfg
|
||||
}
|
||||
};
|
||||
|
||||
// Float entry with custom Min/Max range.
|
||||
template <s32 Min, s32 Max>
|
||||
class _float final : public _base
|
||||
{
|
||||
static_assert(Min < Max, "Invalid cfg::_float range");
|
||||
|
||||
using float_type = f64;
|
||||
atomic_t<float_type> m_value;
|
||||
|
||||
public:
|
||||
float_type def;
|
||||
|
||||
// Expose range
|
||||
static constexpr float_type max = Max;
|
||||
static constexpr float_type min = Min;
|
||||
|
||||
_float(node* owner, const std::string& name, float_type def = std::min<float_type>(Max, std::max<float_type>(Min, 0)), bool dynamic = false)
|
||||
: _base(type::_int, owner, name, dynamic)
|
||||
, m_value(def)
|
||||
, def(def)
|
||||
{
|
||||
}
|
||||
|
||||
operator float_type() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
float_type get() const
|
||||
{
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void from_default() override
|
||||
{
|
||||
m_value = def;
|
||||
}
|
||||
|
||||
std::string to_string() const override
|
||||
{
|
||||
return std::to_string(m_value);
|
||||
}
|
||||
|
||||
bool from_string(std::string_view value, bool /*dynamic*/ = false) override
|
||||
{
|
||||
f64 result;
|
||||
if (try_to_float(&result, value, Min, Max))
|
||||
{
|
||||
m_value = static_cast<float_type>(result);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void set(const f64& value)
|
||||
{
|
||||
m_value = static_cast<float_type>(value);
|
||||
}
|
||||
|
||||
std::vector<std::string> to_list() const override
|
||||
{
|
||||
return make_float_range(Min, Max);
|
||||
}
|
||||
};
|
||||
|
||||
// Alias for 32 bit int
|
||||
using int32 = _int<s32{smin}, s32{smax}>;
|
||||
|
||||
|
||||
+34
-3
@@ -71,6 +71,10 @@ namespace fs
|
||||
s64 atime;
|
||||
s64 mtime;
|
||||
s64 ctime;
|
||||
|
||||
using enable_bitcopy = std::true_type;
|
||||
|
||||
constexpr bool operator==(const stat_t&) const = default;
|
||||
};
|
||||
|
||||
// Helper, layout is equal to iovec struct
|
||||
@@ -105,6 +109,8 @@ namespace fs
|
||||
: stat_t{}
|
||||
{
|
||||
}
|
||||
|
||||
using enable_bitcopy = std::false_type;
|
||||
};
|
||||
|
||||
// Directory handle base
|
||||
@@ -681,9 +687,10 @@ namespace fs
|
||||
T obj;
|
||||
u64 pos;
|
||||
|
||||
container_stream(T&& obj)
|
||||
container_stream(T&& obj, const stat_t& init_stat = {})
|
||||
: obj(std::forward<T>(obj))
|
||||
, pos(0)
|
||||
, m_stat(init_stat)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -694,6 +701,7 @@ namespace fs
|
||||
bool trunc(u64 length) override
|
||||
{
|
||||
obj.resize(length);
|
||||
update_time(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -708,6 +716,7 @@ namespace fs
|
||||
{
|
||||
std::copy(obj.cbegin() + pos, obj.cbegin() + pos + max, static_cast<value_type*>(buffer));
|
||||
pos = pos + max;
|
||||
update_time();
|
||||
return max;
|
||||
}
|
||||
}
|
||||
@@ -743,6 +752,7 @@ namespace fs
|
||||
obj.insert(obj.end(), src + overlap, src + size);
|
||||
pos += size;
|
||||
|
||||
if (size) update_time(true);
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -767,13 +777,34 @@ namespace fs
|
||||
{
|
||||
return obj.size();
|
||||
}
|
||||
|
||||
stat_t stat() override
|
||||
{
|
||||
return m_stat;
|
||||
}
|
||||
|
||||
private:
|
||||
stat_t m_stat{};
|
||||
|
||||
void update_time(bool write = false)
|
||||
{
|
||||
// TODO: Accurate timestamps
|
||||
m_stat.atime++;
|
||||
|
||||
if (write)
|
||||
{
|
||||
m_stat.mtime++;
|
||||
m_stat.mtime = std::max(m_stat.atime, m_stat.mtime);
|
||||
m_stat.ctime = m_stat.mtime;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
file make_stream(T&& container = T{})
|
||||
file make_stream(T&& container = T{}, const stat_t& stat = stat_t{})
|
||||
{
|
||||
file result;
|
||||
result.reset(std::make_unique<container_stream<T>>(std::forward<T>(container)));
|
||||
result.reset(std::make_unique<container_stream<T>>(std::forward<T>(container), stat));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
+502
-6
@@ -7,6 +7,8 @@
|
||||
#include "mutex.h"
|
||||
#include "util/vm.hpp"
|
||||
#include "util/asm.hpp"
|
||||
#include "util/v128.hpp"
|
||||
#include "util/simd.hpp"
|
||||
#include <charconv>
|
||||
#include <zlib.h>
|
||||
|
||||
@@ -18,6 +20,34 @@ LOG_CHANNEL(jit_log, "JIT");
|
||||
|
||||
void jit_announce(uptr func, usz size, std::string_view name)
|
||||
{
|
||||
#ifdef __linux__
|
||||
static const struct tmp_perf_map
|
||||
{
|
||||
std::string name{fmt::format("/tmp/perf-%d.map", getpid())};
|
||||
fs::file data{name, fs::rewrite + fs::append};
|
||||
|
||||
tmp_perf_map() = default;
|
||||
tmp_perf_map(const tmp_perf_map&) = delete;
|
||||
tmp_perf_map& operator=(const tmp_perf_map&) = delete;
|
||||
|
||||
~tmp_perf_map()
|
||||
{
|
||||
fs::remove_file(name);
|
||||
}
|
||||
} s_map;
|
||||
|
||||
if (size && name.size())
|
||||
{
|
||||
s_map.data.write(fmt::format("%x %x %s\n", func, size, name));
|
||||
}
|
||||
|
||||
if (!func && !size && !name.size())
|
||||
{
|
||||
fs::remove_file(s_map.name);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!size)
|
||||
{
|
||||
jit_log.error("Empty function announced: %s (%p)", name, func);
|
||||
@@ -81,12 +111,6 @@ void jit_announce(uptr func, usz size, std::string_view name)
|
||||
dump.write(reinterpret_cast<uchar*>(func), size);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef __linux__
|
||||
static const fs::file s_map(fmt::format("/tmp/perf-%d.map", getpid()), fs::rewrite + fs::append);
|
||||
|
||||
s_map.write(fmt::format("%x %x %s\n", func, size, name));
|
||||
#endif
|
||||
}
|
||||
|
||||
static u8* get_jit_memory()
|
||||
@@ -351,6 +375,473 @@ asmjit::inline_runtime::~inline_runtime()
|
||||
utils::memory_protect(m_data, m_size, utils::protection::rx);
|
||||
}
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
asmjit::simd_builder::simd_builder(CodeHolder* ch) noexcept
|
||||
: native_asm(ch)
|
||||
{
|
||||
_init(0);
|
||||
consts[~v128()] = this->newLabel();
|
||||
}
|
||||
|
||||
asmjit::simd_builder::~simd_builder()
|
||||
{
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::_init(uint new_vsize)
|
||||
{
|
||||
if ((!new_vsize && utils::has_avx512_icl()) || new_vsize == 64)
|
||||
{
|
||||
v0 = x86::zmm0;
|
||||
v1 = x86::zmm1;
|
||||
v2 = x86::zmm2;
|
||||
v3 = x86::zmm3;
|
||||
v4 = x86::zmm4;
|
||||
v5 = x86::zmm5;
|
||||
vsize = 64;
|
||||
}
|
||||
else if ((!new_vsize && utils::has_avx2()) || new_vsize == 32)
|
||||
{
|
||||
v0 = x86::ymm0;
|
||||
v1 = x86::ymm1;
|
||||
v2 = x86::ymm2;
|
||||
v3 = x86::ymm3;
|
||||
v4 = x86::ymm4;
|
||||
v5 = x86::ymm5;
|
||||
vsize = 32;
|
||||
}
|
||||
else
|
||||
{
|
||||
v0 = x86::xmm0;
|
||||
v1 = x86::xmm1;
|
||||
v2 = x86::xmm2;
|
||||
v3 = x86::xmm3;
|
||||
v4 = x86::xmm4;
|
||||
v5 = x86::xmm5;
|
||||
vsize = new_vsize ? new_vsize : 16;
|
||||
}
|
||||
|
||||
if (utils::has_avx512())
|
||||
{
|
||||
if (!new_vsize)
|
||||
vmask = -1;
|
||||
}
|
||||
else
|
||||
{
|
||||
vmask = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::operator()() noexcept
|
||||
{
|
||||
for (auto&& [x, y] : consts)
|
||||
{
|
||||
this->align(AlignMode::kData, 16);
|
||||
this->bind(y);
|
||||
this->embed(&x, 16);
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_cleanup_ret()
|
||||
{
|
||||
if (utils::has_avx() && vsize > 16)
|
||||
this->vzeroupper();
|
||||
this->ret();
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_set_all_zeros(const Operand& v)
|
||||
{
|
||||
x86::Xmm reg(v.id());
|
||||
if (utils::has_avx())
|
||||
this->vpxor(reg, reg, reg);
|
||||
else
|
||||
this->xorps(reg, reg);
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_set_all_ones(const Operand& v)
|
||||
{
|
||||
x86::Xmm reg(v.id());
|
||||
if (x86::Zmm zr(v.id()); zr == v)
|
||||
this->vpternlogd(zr, zr, zr, 0xff);
|
||||
else if (x86::Ymm yr(v.id()); yr == v)
|
||||
this->vpcmpeqd(yr, yr, yr);
|
||||
else if (utils::has_avx())
|
||||
this->vpcmpeqd(reg, reg, reg);
|
||||
else
|
||||
this->pcmpeqd(reg, reg);
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_set_const(const Operand& v, const v128& val)
|
||||
{
|
||||
if (!val._u)
|
||||
return vec_set_all_zeros(v);
|
||||
if (!~val._u)
|
||||
return vec_set_all_ones(v);
|
||||
else
|
||||
{
|
||||
Label co = consts[val];
|
||||
if (!co.isValid())
|
||||
co = consts[val] = this->newLabel();
|
||||
if (x86::Zmm zr(v.id()); zr == v)
|
||||
this->vbroadcasti32x4(zr, x86::oword_ptr(co));
|
||||
else if (x86::Ymm yr(v.id()); yr == v)
|
||||
this->vbroadcasti128(yr, x86::oword_ptr(co));
|
||||
else if (utils::has_avx())
|
||||
this->vmovaps(x86::Xmm(v.id()), x86::oword_ptr(co));
|
||||
else
|
||||
this->movaps(x86::Xmm(v.id()), x86::oword_ptr(co));
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_clobbering_test(u32 esize, const Operand& v, const Operand& rhs)
|
||||
{
|
||||
if (esize == 64)
|
||||
{
|
||||
this->emit(x86::Inst::kIdVptestmd, x86::k0, v, rhs);
|
||||
this->ktestw(x86::k0, x86::k0);
|
||||
}
|
||||
else if (esize == 32)
|
||||
{
|
||||
this->emit(x86::Inst::kIdVptest, v, rhs);
|
||||
}
|
||||
else if (esize == 16 && utils::has_avx())
|
||||
{
|
||||
this->emit(x86::Inst::kIdVptest, v, rhs);
|
||||
}
|
||||
else if (esize == 16 && utils::has_sse41())
|
||||
{
|
||||
this->emit(x86::Inst::kIdPtest, v, rhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (v != rhs)
|
||||
this->emit(x86::Inst::kIdPand, v, rhs);
|
||||
if (esize == 16)
|
||||
this->emit(x86::Inst::kIdPacksswb, v, v);
|
||||
this->emit(x86::Inst::kIdMovq, x86::rax, v);
|
||||
if (esize == 16 || esize == 8)
|
||||
this->test(x86::rax, x86::rax);
|
||||
else if (esize == 4)
|
||||
this->test(x86::eax, x86::eax);
|
||||
else if (esize == 2)
|
||||
this->test(x86::ax, x86::ax);
|
||||
else if (esize == 1)
|
||||
this->test(x86::al, x86::al);
|
||||
else
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_broadcast_gpr(u32 esize, const Operand& v, const x86::Gp& r)
|
||||
{
|
||||
if (esize == 2)
|
||||
{
|
||||
if (utils::has_avx512())
|
||||
this->emit(x86::Inst::kIdVpbroadcastw, v, r.r32());
|
||||
else if (utils::has_avx())
|
||||
{
|
||||
this->emit(x86::Inst::kIdVmovd, v, r.r32());
|
||||
if (utils::has_avx2())
|
||||
this->emit(x86::Inst::kIdVpbroadcastw, v, v);
|
||||
else
|
||||
{
|
||||
this->emit(x86::Inst::kIdVpunpcklwd, v, v, v);
|
||||
this->emit(x86::Inst::kIdVpshufd, v, v, Imm(0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emit(x86::Inst::kIdMovd, v, r.r32());
|
||||
this->emit(x86::Inst::kIdPunpcklwd, v, v);
|
||||
this->emit(x86::Inst::kIdPshufd, v, v, Imm(0));
|
||||
}
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
if (utils::has_avx512())
|
||||
this->emit(x86::Inst::kIdVpbroadcastd, v, r.r32());
|
||||
else if (utils::has_avx())
|
||||
{
|
||||
this->emit(x86::Inst::kIdVmovd, v, r.r32());
|
||||
if (utils::has_avx2())
|
||||
this->emit(x86::Inst::kIdVpbroadcastd, v, v);
|
||||
else
|
||||
this->emit(x86::Inst::kIdVpshufd, v, v, Imm(0));
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emit(x86::Inst::kIdMovd, v, r.r32());
|
||||
this->emit(x86::Inst::kIdPshufd, v, v, Imm(0));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
asmjit::x86::Mem asmjit::simd_builder::ptr_scale_for_vec(u32 esize, const x86::Gp& base, const x86::Gp& index)
|
||||
{
|
||||
switch (ensure(esize))
|
||||
{
|
||||
case 1: return x86::ptr(base, index, 0, 0);
|
||||
case 2: return x86::ptr(base, index, 1, 0);
|
||||
case 4: return x86::ptr(base, index, 2, 0);
|
||||
case 8: return x86::ptr(base, index, 3, 0);
|
||||
default: fmt::throw_exception("Bad esize");
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_load_unaligned(u32 esize, const Operand& v, const x86::Mem& src)
|
||||
{
|
||||
ensure(std::has_single_bit(esize));
|
||||
ensure(std::has_single_bit(vsize));
|
||||
|
||||
if (esize == 2)
|
||||
{
|
||||
ensure(vsize >= 2);
|
||||
if (vsize == 2)
|
||||
vec_set_all_zeros(v);
|
||||
if (vsize == 2 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVpinsrw, x86::Xmm(v.id()), x86::Xmm(v.id()), src, Imm(0));
|
||||
else if (vsize == 2)
|
||||
this->emit(x86::Inst::kIdPinsrw, v, src, Imm(0));
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu16, v, src);
|
||||
else
|
||||
return vec_load_unaligned(vsize, v, src);
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
ensure(vsize >= 4);
|
||||
if (vsize == 4 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovd, x86::Xmm(v.id()), src);
|
||||
else if (vsize == 4)
|
||||
this->emit(x86::Inst::kIdMovd, v, src);
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu32, v, src);
|
||||
else
|
||||
return vec_load_unaligned(vsize, v, src);
|
||||
}
|
||||
else if (esize == 8)
|
||||
{
|
||||
ensure(vsize >= 8);
|
||||
if (vsize == 8 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovq, x86::Xmm(v.id()), src);
|
||||
else if (vsize == 8)
|
||||
this->emit(x86::Inst::kIdMovq, v, src);
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu64, v, src);
|
||||
else
|
||||
return vec_load_unaligned(vsize, v, src);
|
||||
}
|
||||
else if (esize >= 16)
|
||||
{
|
||||
ensure(vsize >= 16);
|
||||
if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu64, v, src); // Not really needed
|
||||
else if (utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovdqu, v, src);
|
||||
else
|
||||
this->emit(x86::Inst::kIdMovups, v, src);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_store_unaligned(u32 esize, const Operand& v, const x86::Mem& dst)
|
||||
{
|
||||
ensure(std::has_single_bit(esize));
|
||||
ensure(std::has_single_bit(vsize));
|
||||
|
||||
if (esize == 2)
|
||||
{
|
||||
ensure(vsize >= 2);
|
||||
if (vsize == 2 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVpextrw, dst, x86::Xmm(v.id()), Imm(0));
|
||||
else if (vsize == 2 && utils::has_sse41())
|
||||
this->emit(x86::Inst::kIdPextrw, dst, v, Imm(0));
|
||||
else if (vsize == 2)
|
||||
this->push(x86::rax), this->pextrw(x86::eax, x86::Xmm(v.id()), 0), this->mov(dst, x86::ax), this->pop(x86::rax);
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu16, dst, v);
|
||||
else
|
||||
return vec_store_unaligned(vsize, v, dst);
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
ensure(vsize >= 4);
|
||||
if (vsize == 4 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovd, dst, x86::Xmm(v.id()));
|
||||
else if (vsize == 4)
|
||||
this->emit(x86::Inst::kIdMovd, dst, v);
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu32, dst, v);
|
||||
else
|
||||
return vec_store_unaligned(vsize, v, dst);
|
||||
}
|
||||
else if (esize == 8)
|
||||
{
|
||||
ensure(vsize >= 8);
|
||||
if (vsize == 8 && utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovq, dst, x86::Xmm(v.id()));
|
||||
else if (vsize == 8)
|
||||
this->emit(x86::Inst::kIdMovq, dst, v);
|
||||
else if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu64, dst, v);
|
||||
else
|
||||
return vec_store_unaligned(vsize, v, dst);
|
||||
}
|
||||
else if (esize >= 16)
|
||||
{
|
||||
ensure(vsize >= 16);
|
||||
if ((vmask && vmask < 8) || vsize >= 64)
|
||||
this->emit(x86::Inst::kIdVmovdqu64, dst, v); // Not really needed
|
||||
else if (utils::has_avx())
|
||||
this->emit(x86::Inst::kIdVmovdqu, dst, v);
|
||||
else
|
||||
this->emit(x86::Inst::kIdMovups, dst, v);
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::_vec_binary_op(x86::Inst::Id sse_op, x86::Inst::Id vex_op, x86::Inst::Id evex_op, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
if (utils::has_avx())
|
||||
{
|
||||
if (evex_op != x86::Inst::kIdNone && (vex_op == x86::Inst::kIdNone || this->_extraReg.isReg() || vsize >= 64))
|
||||
{
|
||||
this->evex().emit(evex_op, dst, lhs, rhs);
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emit(vex_op, dst, lhs, rhs);
|
||||
}
|
||||
}
|
||||
else if (dst == lhs)
|
||||
{
|
||||
this->emit(sse_op, dst, rhs);
|
||||
}
|
||||
else if (dst == rhs)
|
||||
{
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
else
|
||||
{
|
||||
this->emit(x86::Inst::kIdMovaps, dst, lhs);
|
||||
this->emit(sse_op, dst, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_umin(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
if (esize == 2)
|
||||
{
|
||||
if (utils::has_sse41())
|
||||
return _vec_binary_op(kIdPminuw, kIdVpminuw, kIdVpminuw, dst, lhs, rhs);
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
if (utils::has_sse41())
|
||||
return _vec_binary_op(kIdPminud, kIdVpminud, kIdVpminud, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_umax(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
if (esize == 2)
|
||||
{
|
||||
if (utils::has_sse41())
|
||||
return _vec_binary_op(kIdPmaxuw, kIdVpmaxuw, kIdVpmaxuw, dst, lhs, rhs);
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
if (utils::has_sse41())
|
||||
return _vec_binary_op(kIdPmaxud, kIdVpmaxud, kIdVpmaxud, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_cmp_eq(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
if (esize == 2)
|
||||
{
|
||||
if (vsize == 64)
|
||||
{
|
||||
this->evex().emit(kIdVpcmpeqw, x86::k0, lhs, rhs);
|
||||
this->evex().emit(kIdVpmovm2w, dst, x86::k0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_vec_binary_op(kIdPcmpeqw, kIdVpcmpeqw, kIdNone, dst, lhs, rhs);
|
||||
}
|
||||
}
|
||||
else if (esize == 4)
|
||||
{
|
||||
if (vsize == 64)
|
||||
{
|
||||
this->evex().emit(kIdVpcmpeqd, x86::k0, lhs, rhs);
|
||||
this->evex().emit(kIdVpmovm2d, dst, x86::k0);
|
||||
}
|
||||
else
|
||||
{
|
||||
_vec_binary_op(kIdPcmpeqw, kIdVpcmpeqw, kIdNone, dst, lhs, rhs);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_extract_high(u32, const Operand& dst, const Operand& src)
|
||||
{
|
||||
if (vsize == 32)
|
||||
this->vextracti32x8(x86::Ymm(dst.id()), x86::Zmm(src.id()), 1);
|
||||
else if (vsize == 16)
|
||||
this->vextracti128(x86::Xmm(dst.id()), x86::Ymm(src.id()), 1);
|
||||
else
|
||||
{
|
||||
if (utils::has_avx())
|
||||
this->vpsrldq(x86::Xmm(dst.id()), x86::Xmm(src.id()), vsize);
|
||||
else
|
||||
{
|
||||
this->movdqa(x86::Xmm(dst.id()), x86::Xmm(src.id()));
|
||||
this->psrldq(x86::Xmm(dst.id()), vsize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void asmjit::simd_builder::vec_extract_gpr(u32 esize, const x86::Gp& dst, const Operand& src)
|
||||
{
|
||||
if (esize == 8 && utils::has_avx())
|
||||
this->vmovq(dst.r64(), x86::Xmm(src.id()));
|
||||
else if (esize == 8)
|
||||
this->movq(dst.r64(), x86::Xmm(src.id()));
|
||||
else if (esize == 4 && utils::has_avx())
|
||||
this->vmovd(dst.r32(), x86::Xmm(src.id()));
|
||||
else if (esize == 4)
|
||||
this->movd(dst.r32(), x86::Xmm(src.id()));
|
||||
else if (esize == 2 && utils::has_avx())
|
||||
this->vpextrw(dst.r32(), x86::Xmm(src.id()), 0);
|
||||
else if (esize == 2)
|
||||
this->pextrw(dst.r32(), x86::Xmm(src.id()), 0);
|
||||
else
|
||||
fmt::throw_exception("Unimplemented");
|
||||
}
|
||||
|
||||
#endif /* X86 */
|
||||
|
||||
#ifdef LLVM_AVAILABLE
|
||||
|
||||
#include <unordered_map>
|
||||
@@ -996,6 +1487,11 @@ bool jit_compiler::check(const std::string& path)
|
||||
return false;
|
||||
}
|
||||
|
||||
void jit_compiler::update_global_mapping(const std::string& name, u64 addr)
|
||||
{
|
||||
m_engine->updateGlobalMapping(name, addr);
|
||||
}
|
||||
|
||||
void jit_compiler::fin()
|
||||
{
|
||||
m_engine->finalizeObject();
|
||||
|
||||
+207
-34
@@ -42,6 +42,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <util/v128.hpp>
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
using native_asm = asmjit::x86::Assembler;
|
||||
@@ -211,40 +212,182 @@ namespace asmjit
|
||||
}
|
||||
|
||||
#if defined(ARCH_X64)
|
||||
template <uint Size>
|
||||
struct native_vec;
|
||||
|
||||
template <>
|
||||
struct native_vec<16> { using type = x86::Xmm; };
|
||||
|
||||
template <>
|
||||
struct native_vec<32> { using type = x86::Ymm; };
|
||||
|
||||
template <>
|
||||
struct native_vec<64> { using type = x86::Zmm; };
|
||||
|
||||
template <uint Size>
|
||||
using native_vec_t = typename native_vec<Size>::type;
|
||||
|
||||
// if (count > step) { for (; ctr < (count - step); ctr += step) {...} count -= ctr; }
|
||||
inline void build_incomplete_loop(native_asm& c, auto ctr, auto count, u32 step, auto&& build)
|
||||
struct simd_builder : native_asm
|
||||
{
|
||||
asmjit::Label body = c.newLabel();
|
||||
asmjit::Label exit = c.newLabel();
|
||||
std::unordered_map<v128, Label> consts;
|
||||
|
||||
ensure((step & (step - 1)) == 0);
|
||||
c.cmp(count, step);
|
||||
c.jbe(exit);
|
||||
c.sub(count, step);
|
||||
c.align(asmjit::AlignMode::kCode, 16);
|
||||
c.bind(body);
|
||||
build();
|
||||
c.add(ctr, step);
|
||||
c.sub(count, step);
|
||||
c.ja(body);
|
||||
c.add(count, step);
|
||||
c.bind(exit);
|
||||
}
|
||||
Operand v0, v1, v2, v3, v4, v5;
|
||||
|
||||
uint vsize = 16;
|
||||
uint vmask = 0;
|
||||
|
||||
simd_builder(CodeHolder* ch) noexcept;
|
||||
~simd_builder();
|
||||
|
||||
void operator()() noexcept;
|
||||
|
||||
void _init(uint new_vsize = 0);
|
||||
void vec_cleanup_ret();
|
||||
void vec_set_all_zeros(const Operand& v);
|
||||
void vec_set_all_ones(const Operand& v);
|
||||
void vec_set_const(const Operand& v, const v128& value);
|
||||
void vec_clobbering_test(u32 esize, const Operand& v, const Operand& rhs);
|
||||
void vec_broadcast_gpr(u32 esize, const Operand& v, const x86::Gp& r);
|
||||
|
||||
// return x86::ptr(base, ctr, X, 0) where X is set for esize accordingly
|
||||
x86::Mem ptr_scale_for_vec(u32 esize, const x86::Gp& base, const x86::Gp& index);
|
||||
|
||||
void vec_load_unaligned(u32 esize, const Operand& v, const x86::Mem& src);
|
||||
void vec_store_unaligned(u32 esize, const Operand& v, const x86::Mem& dst);
|
||||
void vec_partial_move(u32 esize, const Operand& dst, const Operand& src);
|
||||
|
||||
void _vec_binary_op(x86::Inst::Id sse_op, x86::Inst::Id vex_op, x86::Inst::Id evex_op, const Operand& dst, const Operand& lhs, const Operand& rhs);
|
||||
|
||||
void vec_shuffle_xi8(const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
_vec_binary_op(kIdPshufb, kIdVpshufb, kIdVpshufb, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
void vec_xor(u32, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
_vec_binary_op(kIdPxor, kIdVpxor, kIdVpxord, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
void vec_or(u32, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
_vec_binary_op(kIdPor, kIdVpor, kIdVpord, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
void vec_andn(u32, const Operand& dst, const Operand& lhs, const Operand& rhs)
|
||||
{
|
||||
using enum x86::Inst::Id;
|
||||
_vec_binary_op(kIdPandn, kIdVpandn, kIdVpandnd, dst, lhs, rhs);
|
||||
}
|
||||
|
||||
void vec_umin(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs);
|
||||
void vec_umax(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs);
|
||||
void vec_cmp_eq(u32 esize, const Operand& dst, const Operand& lhs, const Operand& rhs);
|
||||
|
||||
void vec_extract_high(u32 esize, const Operand& dst, const Operand& src);
|
||||
void vec_extract_gpr(u32 esize, const x86::Gp& dst, const Operand& src);
|
||||
|
||||
simd_builder& keep_if_not_masked()
|
||||
{
|
||||
if (vmask && vmask < 8)
|
||||
{
|
||||
this->k(x86::KReg(vmask));
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
simd_builder& zero_if_not_masked()
|
||||
{
|
||||
if (vmask && vmask < 8)
|
||||
{
|
||||
this->k(x86::KReg(vmask));
|
||||
this->z();
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
void build_loop(u32 esize, const x86::Gp& reg_ctr, const x86::Gp& reg_cnt, auto&& build, auto&& reduce)
|
||||
{
|
||||
ensure((esize & (esize - 1)) == 0);
|
||||
ensure(esize <= vsize);
|
||||
|
||||
Label body = this->newLabel();
|
||||
Label next = this->newLabel();
|
||||
Label exit = this->newLabel();
|
||||
|
||||
const u32 step = vsize / esize;
|
||||
|
||||
this->xor_(reg_ctr.r32(), reg_ctr.r32()); // Reset counter reg
|
||||
this->cmp(reg_cnt, step);
|
||||
this->jb(next); // If count < step, skip main loop body
|
||||
this->align(AlignMode::kCode, 16);
|
||||
this->bind(body);
|
||||
this->sub(reg_cnt, step);
|
||||
build();
|
||||
this->add(reg_ctr, step);
|
||||
this->cmp(reg_cnt, step);
|
||||
this->jae(body);
|
||||
this->bind(next);
|
||||
|
||||
if (vmask)
|
||||
{
|
||||
// Build single last iteration (masked)
|
||||
this->test(reg_cnt, reg_cnt);
|
||||
this->jz(exit);
|
||||
|
||||
if (esize == 1 && vsize == 64)
|
||||
{
|
||||
this->bzhi(reg_cnt.r64(), x86::Mem(consts[~u128()], 0), reg_cnt.r64());
|
||||
this->kmovq(x86::k7, reg_cnt.r64());
|
||||
}
|
||||
else
|
||||
{
|
||||
this->bzhi(reg_cnt.r32(), x86::Mem(consts[~u128()], 0), reg_cnt.r32());
|
||||
this->kmovd(x86::k7, reg_cnt.r32());
|
||||
}
|
||||
|
||||
vmask = 7;
|
||||
build();
|
||||
|
||||
// Rollout reduction step
|
||||
this->bind(exit);
|
||||
while (true)
|
||||
{
|
||||
vsize /= 2;
|
||||
if (vsize < esize)
|
||||
break;
|
||||
this->_init(vsize);
|
||||
reduce();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build unrolled loop tail (reduced vector width)
|
||||
while (true)
|
||||
{
|
||||
vsize /= 2;
|
||||
if (vsize < esize)
|
||||
break;
|
||||
|
||||
// Shall not clobber flags
|
||||
this->_init(vsize);
|
||||
reduce();
|
||||
|
||||
if (vsize == esize)
|
||||
{
|
||||
// Last "iteration"
|
||||
this->test(reg_cnt, reg_cnt);
|
||||
this->jz(exit);
|
||||
build();
|
||||
}
|
||||
else
|
||||
{
|
||||
const u32 step = vsize / esize;
|
||||
Label next = this->newLabel();
|
||||
this->cmp(reg_cnt, step);
|
||||
this->jb(next);
|
||||
build();
|
||||
this->add(reg_ctr, step);
|
||||
this->sub(reg_cnt, step);
|
||||
this->bind(next);
|
||||
}
|
||||
}
|
||||
|
||||
this->bind(exit);
|
||||
}
|
||||
|
||||
this->_init(0);
|
||||
}
|
||||
};
|
||||
|
||||
// for (; count > 0; ctr++, count--)
|
||||
inline void build_loop(native_asm& c, auto ctr, auto count, auto&& build)
|
||||
@@ -262,19 +405,40 @@ namespace asmjit
|
||||
c.ja(body);
|
||||
c.bind(exit);
|
||||
}
|
||||
|
||||
inline void maybe_flush_lbr(native_asm& c, uint count = 2)
|
||||
{
|
||||
// Workaround for bad LBR callstacks which happen in some situations (mainly TSX) - execute additional RETs
|
||||
Label next = c.newLabel();
|
||||
c.lea(x86::rcx, x86::qword_ptr(next));
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
c.push(x86::rcx);
|
||||
c.sub(x86::rcx, 16);
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < count; i++)
|
||||
{
|
||||
c.ret();
|
||||
c.align(asmjit::AlignMode::kCode, 16);
|
||||
}
|
||||
|
||||
c.bind(next);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Build runtime function with asmjit::X86Assembler
|
||||
template <typename FT, typename Asm = native_asm, typename F>
|
||||
inline FT build_function_asm(std::string_view name, F&& builder)
|
||||
inline FT build_function_asm(std::string_view name, F&& builder, ::jit_runtime* custom_runtime = nullptr)
|
||||
{
|
||||
#ifdef __APPLE__
|
||||
pthread_jit_write_protect_np(false);
|
||||
#endif
|
||||
using namespace asmjit;
|
||||
|
||||
auto& rt = get_global_runtime();
|
||||
auto& rt = custom_runtime ? *custom_runtime : get_global_runtime();
|
||||
|
||||
CodeHolder code;
|
||||
code.init(rt.environment());
|
||||
@@ -312,6 +476,12 @@ inline FT build_function_asm(std::string_view name, F&& builder)
|
||||
builder(compiler, args);
|
||||
}
|
||||
|
||||
if constexpr (std::is_invocable_r_v<void, Asm>)
|
||||
{
|
||||
// Finalization
|
||||
compiler();
|
||||
}
|
||||
|
||||
const auto result = rt._add(&code);
|
||||
jit_announce(result, code.codeSize(), name);
|
||||
return reinterpret_cast<FT>(uptr(result));
|
||||
@@ -362,6 +532,9 @@ public:
|
||||
// Add object (path to obj file)
|
||||
void add(const std::string& path);
|
||||
|
||||
// Update global mapping for a single value
|
||||
void update_global_mapping(const std::string& name, u64 addr);
|
||||
|
||||
// Check object file
|
||||
static bool check(const std::string& path);
|
||||
|
||||
|
||||
+19
-3
@@ -5,6 +5,8 @@
|
||||
#include "util/logs.hpp"
|
||||
#include "util/v128.hpp"
|
||||
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <algorithm>
|
||||
#include <string_view>
|
||||
#include "Thread.h"
|
||||
@@ -13,8 +15,6 @@
|
||||
#include <Windows.h>
|
||||
#else
|
||||
#include <errno.h>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#endif
|
||||
|
||||
std::string wchar_to_utf8(std::wstring_view src)
|
||||
@@ -26,11 +26,17 @@ std::string wchar_to_utf8(std::wstring_view src)
|
||||
WideCharToMultiByte(CP_UTF8, 0, src.data(), src.size(), utf8_string.data(), tmp_size, nullptr, nullptr);
|
||||
return utf8_string;
|
||||
#else
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> converter{};
|
||||
std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t> converter{};
|
||||
return converter.to_bytes(src.data());
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string utf16_to_utf8(std::u16string_view src)
|
||||
{
|
||||
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> converter{};
|
||||
return converter.to_bytes(src.data());
|
||||
}
|
||||
|
||||
std::wstring utf8_to_wchar(std::string_view src)
|
||||
{
|
||||
#ifdef _WIN32
|
||||
@@ -609,3 +615,13 @@ bool fmt::match(const std::string& source, const std::string& mask)
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string get_file_extension(const std::string& file_path)
|
||||
{
|
||||
if (usz dotpos = file_path.find_last_of('.'); dotpos != std::string::npos && dotpos + 1 < file_path.size())
|
||||
{
|
||||
return file_path.substr(dotpos + 1);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
|
||||
std::wstring utf8_to_wchar(std::string_view src);
|
||||
std::string wchar_to_utf8(std::wstring_view src);
|
||||
std::string utf16_to_utf8(std::u16string_view src);
|
||||
|
||||
// Copy null-terminated string from a std::string or a char array to a char array with truncation
|
||||
template <typename D, typename T>
|
||||
@@ -26,6 +27,12 @@ bool try_to_int64(s64* out, std::string_view value, s64 min, s64 max);
|
||||
// Convert string to unsigned integer
|
||||
bool try_to_uint64(u64* out, std::string_view value, u64 min, u64 max);
|
||||
|
||||
// Convert string to float
|
||||
bool try_to_float(f64* out, std::string_view value, f64 min, f64 max);
|
||||
|
||||
// Get the file extension of a file path ("png", "jpg", etc.)
|
||||
std::string get_file_extension(const std::string& file_path);
|
||||
|
||||
namespace fmt
|
||||
{
|
||||
std::string replace_all(std::string_view src, std::string_view from, std::string_view to, usz count = -1);
|
||||
|
||||
+14
-16
@@ -8,7 +8,6 @@
|
||||
#include "Thread.h"
|
||||
#include "Utilities/JIT.h"
|
||||
#include <thread>
|
||||
#include <sstream>
|
||||
#include <cfenv>
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -17,6 +16,9 @@
|
||||
#include <process.h>
|
||||
#include <sysinfoapi.h>
|
||||
#else
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
#define _XOPEN_SOURCE
|
||||
#define __USE_GNU
|
||||
@@ -96,14 +98,6 @@ extern thread_local std::string(*g_tls_log_prefix)();
|
||||
// Report error and call std::abort(), defined in main.cpp
|
||||
[[noreturn]] void report_fatal_error(std::string_view);
|
||||
|
||||
template <>
|
||||
void fmt_class_string<std::thread::id>::format(std::string& out, u64 arg)
|
||||
{
|
||||
std::ostringstream ss;
|
||||
ss << get_object(arg);
|
||||
out += ss.str();
|
||||
}
|
||||
|
||||
std::string dump_useful_thread_info()
|
||||
{
|
||||
thread_local volatile bool guard = false;
|
||||
@@ -1248,16 +1242,18 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
|
||||
const auto cpu = get_current_cpu_thread();
|
||||
|
||||
if (rsx::g_access_violation_handler)
|
||||
if (addr < RAW_SPU_BASE_ADDR && vm::check_addr(addr) && rsx::g_access_violation_handler)
|
||||
{
|
||||
bool state_changed = false;
|
||||
|
||||
if (cpu)
|
||||
{
|
||||
vm::temporary_unlock(*cpu);
|
||||
state_changed = vm::temporary_unlock(*cpu);
|
||||
}
|
||||
|
||||
bool handled = rsx::g_access_violation_handler(addr, is_writing);
|
||||
|
||||
if (cpu && (cpu->state += cpu_flag::temp, cpu->test_stopped()))
|
||||
if (state_changed && (cpu->state += cpu_flag::temp, cpu->test_stopped()))
|
||||
{
|
||||
//
|
||||
}
|
||||
@@ -1536,14 +1532,14 @@ bool handle_access_violation(u32 addr, bool is_writing, ucontext_t* context) noe
|
||||
// If we fail due to being busy, wait a bit and try again.
|
||||
while (static_cast<u32>(sending_error) == CELL_EBUSY)
|
||||
{
|
||||
thread_ctrl::wait_for(1000);
|
||||
sending_error = sys_event_port_send(pf_port_id, data1, data2, data3);
|
||||
|
||||
if (cpu->is_stopped())
|
||||
{
|
||||
sending_error = {};
|
||||
break;
|
||||
}
|
||||
|
||||
thread_ctrl::wait_for(1000);
|
||||
sending_error = sys_event_port_send(pf_port_id, data1, data2, data3);
|
||||
}
|
||||
|
||||
if (sending_error)
|
||||
@@ -1644,7 +1640,7 @@ static void append_thread_name(std::string& msg)
|
||||
}
|
||||
else
|
||||
{
|
||||
fmt::append(msg, "Thread id = %s.\n", std::this_thread::get_id());
|
||||
fmt::append(msg, "Thread id = %u.\n", thread_ctrl::get_tid());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3139,6 +3135,8 @@ u64 thread_ctrl::get_tid()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return GetCurrentThreadId();
|
||||
#elif defined(__linux__)
|
||||
return syscall(SYS_gettid);
|
||||
#else
|
||||
return reinterpret_cast<u64>(pthread_self());
|
||||
#endif
|
||||
|
||||
+9
-1
@@ -635,13 +635,21 @@ public:
|
||||
// Join thread by thread_state::finished
|
||||
named_thread& operator=(thread_state s)
|
||||
{
|
||||
bool notify_sync = false;
|
||||
|
||||
if (s >= thread_state::aborting && thread::m_sync.fetch_op([](u64& v){ return !(v & 3) && (v |= 1); }).second)
|
||||
{
|
||||
notify_sync = true;
|
||||
}
|
||||
|
||||
if constexpr (std::is_assignable_v<Context&, thread_state>)
|
||||
{
|
||||
static_cast<Context&>(*this) = s;
|
||||
}
|
||||
|
||||
if (s >= thread_state::aborting && thread::m_sync.fetch_op([](u64& v){ return !(v & 3) && (v |= 1); }).second)
|
||||
if (notify_sync)
|
||||
{
|
||||
// Notify after context abortion has been made so all conditions for wake-up be satisfied by the time of notification
|
||||
thread::m_sync.notify_one(1);
|
||||
}
|
||||
|
||||
|
||||
+10
-11
@@ -1,37 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/vm.hpp"
|
||||
#include "StrFmt.h"
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace utils
|
||||
{
|
||||
class address_range_vector;
|
||||
|
||||
|
||||
/**
|
||||
* Constexprs
|
||||
* Helpers
|
||||
*/
|
||||
constexpr inline u32 page_start(u32 addr)
|
||||
static inline u32 page_start(u32 addr)
|
||||
{
|
||||
return addr & ~4095u;
|
||||
return addr & ~(c_page_size - 1);
|
||||
}
|
||||
|
||||
constexpr inline u32 next_page(u32 addr)
|
||||
static inline u32 next_page(u32 addr)
|
||||
{
|
||||
return page_start(addr + 4096u);
|
||||
return page_start(addr) + c_page_size;
|
||||
}
|
||||
|
||||
constexpr inline u32 page_end(u32 addr)
|
||||
static inline u32 page_end(u32 addr)
|
||||
{
|
||||
return next_page(addr) - 1;
|
||||
}
|
||||
|
||||
constexpr inline u32 is_page_aligned(u32 addr)
|
||||
static inline u32 is_page_aligned(u32 val)
|
||||
{
|
||||
return page_start(addr) == addr;
|
||||
return (val & (c_page_size - 1)) == 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +185,7 @@ namespace utils
|
||||
|
||||
bool is_page_range() const
|
||||
{
|
||||
return (valid() && start % 4096u == 0 && length() % 4096u == 0);
|
||||
return (valid() && is_page_aligned(start) && is_page_aligned(length()));
|
||||
}
|
||||
|
||||
address_range to_page_range() const
|
||||
|
||||
@@ -939,7 +939,7 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
||||
}
|
||||
|
||||
std::basic_string<u32> applied_total;
|
||||
const auto& container = m_map.at(name);
|
||||
const auto& container = ::at32(m_map, name);
|
||||
const auto& serial = Emu.GetTitleID();
|
||||
const auto& app_version = Emu.GetAppVersion();
|
||||
|
||||
@@ -975,7 +975,7 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto& app_versions = serials.at(found_serial);
|
||||
const auto& app_versions = ::at32(serials, found_serial);
|
||||
std::string found_app_version;
|
||||
|
||||
if (app_versions.find(app_version) != app_versions.end())
|
||||
@@ -988,7 +988,7 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
||||
is_all_versions = true;
|
||||
}
|
||||
|
||||
if (!found_app_version.empty() && app_versions.at(found_app_version))
|
||||
if (!found_app_version.empty() && ::at32(app_versions, found_app_version))
|
||||
{
|
||||
// This patch is enabled
|
||||
if (is_all_serials)
|
||||
@@ -1073,7 +1073,7 @@ void patch_engine::unload(const std::string& name)
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& container = m_map.at(name);
|
||||
const auto& container = ::at32(m_map, name);
|
||||
|
||||
for (const auto& [description, patch] : container.patch_info_map)
|
||||
{
|
||||
|
||||
+2
-2
@@ -371,12 +371,12 @@ public:
|
||||
bool bit_test_reset(uint bit) = delete;
|
||||
bool bit_test_invert(uint bit) = delete;
|
||||
|
||||
bool all_of(bs_t arg)
|
||||
bool all_of(bs_t arg) const
|
||||
{
|
||||
return base::load().all_of(arg);
|
||||
}
|
||||
|
||||
bool none_of(bs_t arg)
|
||||
bool none_of(bs_t arg) const
|
||||
{
|
||||
return base::load().none_of(arg);
|
||||
}
|
||||
|
||||
@@ -700,6 +700,12 @@ template<typename T>
|
||||
struct size3_base
|
||||
{
|
||||
T width, height, depth;
|
||||
|
||||
template<typename NT>
|
||||
explicit constexpr operator size3_base<NT>() const
|
||||
{
|
||||
return{ static_cast<NT>(width), static_cast<NT>(height), static_cast<NT>(depth) };
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
|
||||
+29
-3
@@ -6,14 +6,27 @@ void shared_mutex::imp_lock_shared(u32 val)
|
||||
{
|
||||
ensure(val < c_err); // "shared_mutex underflow"
|
||||
|
||||
// Try to steal the notification bit
|
||||
if (val & c_sig && m_value.compare_exchange(val, val - c_sig + 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
busy_wait();
|
||||
|
||||
if (try_lock_shared())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const u32 old = m_value;
|
||||
|
||||
if (old & c_sig && m_value.compare_and_swap_test(old, old - c_sig + 1))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
busy_wait();
|
||||
}
|
||||
|
||||
// Acquire writer lock and downgrade
|
||||
@@ -75,11 +88,24 @@ void shared_mutex::imp_lock(u32 val)
|
||||
{
|
||||
ensure(val < c_err); // "shared_mutex underflow"
|
||||
|
||||
// Try to steal the notification bit
|
||||
if (val & c_sig && m_value.compare_exchange(val, val - c_sig + c_one))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
busy_wait();
|
||||
|
||||
if (!m_value && try_lock())
|
||||
const u32 old = m_value;
|
||||
|
||||
if (!old && try_lock())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (old & c_sig && m_value.compare_and_swap_test(old, old - c_sig + c_one))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -171,6 +171,11 @@ public:
|
||||
{
|
||||
return m_value.load() < c_one - 1;
|
||||
}
|
||||
|
||||
bool has_waiters() const
|
||||
{
|
||||
return m_value.load() > c_one;
|
||||
}
|
||||
};
|
||||
|
||||
// Simplified shared (reader) lock implementation.
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
#include "stdafx.h"
|
||||
#include "stack_trace.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <Windows.h>
|
||||
#define DBGHELP_TRANSLATE_TCHAR
|
||||
#include <DbgHelp.h>
|
||||
#include <codecvt>
|
||||
#else
|
||||
#include <execinfo.h>
|
||||
#endif
|
||||
|
||||
namespace utils
|
||||
{
|
||||
#ifdef _WIN32
|
||||
std::string wstr_to_utf8(LPWSTR data, int str_len)
|
||||
{
|
||||
if (!str_len)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Calculate size
|
||||
const auto length = WideCharToMultiByte(CP_UTF8, 0, data, str_len, NULL, 0, NULL, NULL);
|
||||
|
||||
// Convert
|
||||
std::vector<char> out(length + 1, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, data, str_len, out.data(), length, NULL, NULL);
|
||||
return out.data();
|
||||
}
|
||||
|
||||
std::vector<void*> get_backtrace(int max_depth)
|
||||
{
|
||||
std::vector<void*> result = {};
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
const auto hThread = ::GetCurrentThread();
|
||||
|
||||
CONTEXT context{};
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stack = {};
|
||||
stack.AddrPC.Offset = context.Rip;
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Offset = context.Rsp;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Offset = context.Rbp;
|
||||
stack.AddrFrame.Mode = AddrModeFlat;
|
||||
|
||||
while (max_depth--)
|
||||
{
|
||||
if (!StackWalk64(
|
||||
IMAGE_FILE_MACHINE_AMD64,
|
||||
hProcess,
|
||||
hThread,
|
||||
&stack,
|
||||
&context,
|
||||
NULL,
|
||||
SymFunctionTableAccess64,
|
||||
SymGetModuleBase64,
|
||||
NULL))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
result.push_back(reinterpret_cast<void*>(stack.AddrPC.Offset));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack)
|
||||
{
|
||||
std::vector<std::string> result = {};
|
||||
std::vector<u8> symbol_buf(sizeof(SYMBOL_INFO) + sizeof(TCHAR) * 256);
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
|
||||
auto sym = reinterpret_cast<SYMBOL_INFO*>(symbol_buf.data());
|
||||
sym->SizeOfStruct = sizeof(SYMBOL_INFO);
|
||||
sym->MaxNameLen = 256;
|
||||
|
||||
IMAGEHLP_LINEW64 line_info{};
|
||||
line_info.SizeOfStruct = sizeof(IMAGEHLP_LINEW64);
|
||||
|
||||
SymInitialize(hProcess, NULL, TRUE);
|
||||
SymSetOptions(SYMOPT_LOAD_LINES);
|
||||
|
||||
for (const auto& pointer : stack)
|
||||
{
|
||||
DWORD64 unused;
|
||||
SymFromAddr(hProcess, reinterpret_cast<DWORD64>(pointer), &unused, sym);
|
||||
|
||||
if (sym->NameLen)
|
||||
{
|
||||
const auto function_name = wstr_to_utf8(sym->Name, static_cast<int>(sym->NameLen));
|
||||
|
||||
// Attempt to get file and line information if available
|
||||
DWORD unused2;
|
||||
if (SymGetLineFromAddrW(hProcess, reinterpret_cast<DWORD64>(pointer), &unused2, &line_info))
|
||||
{
|
||||
const auto full_path = fmt::format("%s:%u %s", wstr_to_utf8(line_info.FileName, -1), line_info.LineNumber, function_name);
|
||||
result.push_back(full_path);
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(function_name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(fmt::format("rpcs3@0xp", pointer));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
std::vector<void*> get_backtrace(int max_depth)
|
||||
{
|
||||
std::vector<void*> result(max_depth);
|
||||
int depth = backtrace(result.data(), max_depth);
|
||||
|
||||
result.resize(depth);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(stack.size());
|
||||
|
||||
const auto symbols = backtrace_symbols(stack.data(), static_cast<int>(stack.size()));
|
||||
for (usz i = 0; i < stack.size(); ++i)
|
||||
{
|
||||
result.push_back(symbols[i]);
|
||||
}
|
||||
|
||||
free(symbols);
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <util/types.hpp>
|
||||
#include <util/logs.hpp>
|
||||
|
||||
namespace utils
|
||||
{
|
||||
std::vector<void*> get_backtrace(int max_depth = 255);
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack);
|
||||
|
||||
FORCE_INLINE void print_trace(logs::channel& logger, int max_depth = 255)
|
||||
{
|
||||
const auto trace = get_backtrace(max_depth);
|
||||
const auto lines = get_backtrace_symbols(trace);
|
||||
|
||||
for (const auto& line : lines)
|
||||
{
|
||||
logger.error("%s", line);
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -99,13 +99,13 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t
|
||||
|
||||
if (!timeout)
|
||||
{
|
||||
rec.cv.wait(lock, [&] { return !rec.mask; });
|
||||
rec.cv.wait(lock, FN(!rec.mask));
|
||||
}
|
||||
else if (futex_op == FUTEX_WAIT)
|
||||
{
|
||||
const auto nsec = std::chrono::nanoseconds(timeout->tv_nsec + timeout->tv_sec * 1000000000ull);
|
||||
|
||||
if (!rec.cv.wait_for(lock, nsec, [&] { return !rec.mask; }))
|
||||
if (!rec.cv.wait_for(lock, nsec, FN(!rec.mask)))
|
||||
{
|
||||
res = -1;
|
||||
errno = ETIMEDOUT;
|
||||
|
||||
@@ -30,7 +30,8 @@ namespace utils
|
||||
|
||||
if (lo())
|
||||
{
|
||||
version += "." + std::to_string(lo());
|
||||
version += '.';
|
||||
version += std::to_string(lo());
|
||||
}
|
||||
|
||||
if (type() != version_type::release)
|
||||
@@ -40,7 +41,8 @@ namespace utils
|
||||
version += "-" + postfix();
|
||||
}
|
||||
|
||||
version += " " + utils::to_string(type());
|
||||
version += ' ';
|
||||
version += utils::to_string(type());
|
||||
|
||||
if (type_index() > 1)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ YoRHa Theme for RPCS3
|
||||
by Ani @ https://github.com/AniLeo
|
||||
r1 (2018.02.27)
|
||||
r2 (2021.08.28)
|
||||
r3 (2022.08.20)
|
||||
*/
|
||||
|
||||
/*
|
||||
@@ -26,7 +27,7 @@ aea993
|
||||
|
||||
/* Every widget */
|
||||
QWidget {
|
||||
font-family: SCE-PS3 Rodin LATIN;
|
||||
font-family: "SCE-PS3 Rodin LATIN";
|
||||
font-size: 9.00pt;
|
||||
|
||||
color: #292929;
|
||||
@@ -98,6 +99,9 @@ QToolTip {
|
||||
}
|
||||
|
||||
/* Table headers */
|
||||
QHeaderView {
|
||||
color: #b3ac98;
|
||||
}
|
||||
QHeaderView::section {
|
||||
text-transform: uppercase;
|
||||
background: #4d4940;
|
||||
@@ -109,22 +113,32 @@ QHeaderView::section {
|
||||
border: none;
|
||||
}
|
||||
|
||||
/* All other Tabs */
|
||||
/* All Tabs */
|
||||
QTabBar {
|
||||
border-bottom: 0.05em solid #4d4940;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
QTabBar::tab {
|
||||
background: transparent;
|
||||
width: 4.80em;
|
||||
padding-left: 0.50em;
|
||||
padding-right: 0.50em;
|
||||
padding-top: 0.25em;
|
||||
padding-bottom: 0.25em;
|
||||
padding-top: 0.65em;
|
||||
padding-bottom: 0.65em;
|
||||
margin-right: 0.25em;
|
||||
font-size: 10.5pt;
|
||||
font-weight: 550;
|
||||
}
|
||||
QTabBar::tab:last {
|
||||
margin-right: 0em;
|
||||
}
|
||||
QTabBar::tab:!selected:hover {
|
||||
background: transparent;
|
||||
color: #4d4940;
|
||||
}
|
||||
QTabBar::tab::selected {
|
||||
background: #4d4940;
|
||||
color: #aea993;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
/* Checkboxes */
|
||||
@@ -153,29 +167,35 @@ QCheckBox::indicator::disabled, QRadioButton::indicator::disabled {
|
||||
background-color: #828790; /* Gray */
|
||||
}
|
||||
|
||||
/* Combo Boxes */
|
||||
QComboBox {
|
||||
/* Combo Boxes, Datetime dropdown */
|
||||
QComboBox, QDateTimeEdit, QLineEdit {
|
||||
background: transparent;
|
||||
color: #4d4940;
|
||||
border: 0.05em solid #1e1e1e;
|
||||
border-radius: 0.15em;
|
||||
padding-bottom: 0.2em;
|
||||
padding-left: 0.4em;
|
||||
padding-top: 0.1em;
|
||||
padding-bottom: 0.1em;
|
||||
}
|
||||
QComboBox QAbstractItemView {
|
||||
background: #b3ac98;
|
||||
}
|
||||
QComboBox::disabled {
|
||||
QComboBox::disabled, QDateTimeEdit::disabled, QLineEdit::disabled {
|
||||
background: #828790;
|
||||
color: #4d4940;
|
||||
}
|
||||
/* Text boxes need less left padding than combo boxes */
|
||||
QComboBox {
|
||||
padding-left: 0.4em;
|
||||
}
|
||||
QLineEdit {
|
||||
padding-left: 0.2em;
|
||||
}
|
||||
|
||||
/* Group Boxes (Settings Dialog) */
|
||||
QGroupBox {
|
||||
margin-top: 1em;
|
||||
border: 0.05em solid #4d4940;
|
||||
text-transform: uppercase;
|
||||
font-size: 9.00pt;
|
||||
}
|
||||
QGroupBox::title {
|
||||
subcontrol-origin: margin;
|
||||
@@ -236,6 +256,10 @@ QDockWidget::close-button, QDockWidget::float-button {
|
||||
QTabWidget::pane {
|
||||
border: 0em solid #4d4940;
|
||||
}
|
||||
/* Center tabs */
|
||||
QTabWidget::tab-bar {
|
||||
alignment: center;
|
||||
}
|
||||
|
||||
/* Top menu bar */
|
||||
QMenuBar {
|
||||
@@ -284,6 +308,12 @@ QToolButton::hover {
|
||||
background-color: #b3ac98;
|
||||
}
|
||||
|
||||
/* Calendar widget */
|
||||
QCalendarWidget QWidget{
|
||||
background: #b3ac98;
|
||||
color: #4d4940;
|
||||
}
|
||||
|
||||
|
||||
/*** RPCS3 Specifics ***/
|
||||
|
||||
@@ -303,40 +333,24 @@ QWidget#trophy_manager, QWidget#cg_disasm, QWidget#log_viewer, QMainWindow#main_
|
||||
border: 0.05em solid #4d4940;
|
||||
color: #292929;
|
||||
font-family: none;
|
||||
font-size: 9.50pt;
|
||||
font-size: 9.5pt;
|
||||
}
|
||||
|
||||
/* Pad Settings, Emulator Settings Dialogs: Tabs */
|
||||
#pad_settings_dialog QTabWidget::tab-bar {
|
||||
alignment: center;
|
||||
/* Align the log widget tabs to the left */
|
||||
QTabWidget#tab_widget_log::tab-bar {
|
||||
alignment: left;
|
||||
}
|
||||
QTabBar#tab_bar_settings, #pad_settings_dialog QTabBar {
|
||||
border-bottom: 0.05em solid #4d4940;
|
||||
text-transform: uppercase;
|
||||
|
||||
/* Restore log section QTabBar to small sizes */
|
||||
QTabBar#tab_bar_log {
|
||||
border-bottom: 0;
|
||||
}
|
||||
QTabBar::tab#tab_bar_settings, #pad_settings_dialog QTabBar::tab {
|
||||
background: transparent;
|
||||
width: 5.20em;
|
||||
padding-left: 0.50em;
|
||||
padding-right: 0.50em;
|
||||
padding-top: 0.65em;
|
||||
padding-bottom: 0.65em;
|
||||
margin-right: 0.25em;
|
||||
font-size: 10.5pt;
|
||||
font-weight: 550;
|
||||
}
|
||||
QTabBar::tab:last#tab_bar_settings, #pad_settings_dialog QTabBar::tab:last {
|
||||
margin-right: 0em;
|
||||
}
|
||||
QTabBar::tab:!selected:hover#tab_bar_settings, #pad_settings_dialog QTabBar::tab:!selected:hover {
|
||||
background: transparent;
|
||||
color: #4d4940;
|
||||
}
|
||||
QTabBar::tab::selected#tab_bar_settings, #pad_settings_dialog QTabBar::tab::selected {
|
||||
background: #4d4940;
|
||||
color: #aea993;
|
||||
border-bottom-style: solid;
|
||||
margin-top: 0.15em;
|
||||
QTabBar#tab_bar_log::tab {
|
||||
width: 2.00em;
|
||||
padding: 0.25em;
|
||||
font-size: 9.0pt;
|
||||
font-weight: 400;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
/* Pad Settings: Controller Image */
|
||||
@@ -362,6 +376,7 @@ QLineEdit#mw_searchbar {
|
||||
margin-left: 0.7em;
|
||||
color: #4d4940;
|
||||
font-size: 10.25pt;
|
||||
border: 0em;
|
||||
}
|
||||
|
||||
/* Set Theme UI colors */
|
||||
@@ -440,3 +455,11 @@ QWidget#trophy_notification_frame {
|
||||
QSlider#sizeSlider::groove:horizontal {
|
||||
height: 1.5em;
|
||||
}
|
||||
|
||||
/*
|
||||
On Patch Creator, it's the patch validation label
|
||||
since we can't avoid repainting color, let's bold it instead
|
||||
*/
|
||||
QLabel#validLabel {
|
||||
font-weight: 600;
|
||||
}
|
||||
Binary file not shown.
+1
-1
Submodule llvm updated: 5521155be5...9b52b6c39a
@@ -26,11 +26,17 @@ struct loaded_npdrm_keys
|
||||
}
|
||||
|
||||
// TODO: Check if correct for ELF files usage
|
||||
u128 last_key() const
|
||||
u128 last_key(usz backwards = 0) const
|
||||
{
|
||||
backwards++;
|
||||
const usz pos = dec_keys_pos;
|
||||
return pos ? dec_keys[(pos - 1) % std::size(dec_keys)].load() : u128{};
|
||||
return pos >= backwards ? dec_keys[(pos - backwards) % std::size(dec_keys)].load() : u128{};
|
||||
}
|
||||
|
||||
SAVESTATE_INIT_POS(2);
|
||||
loaded_npdrm_keys() = default;
|
||||
loaded_npdrm_keys(utils::serial& ar);
|
||||
void save(utils::serial& ar);
|
||||
};
|
||||
|
||||
struct NPD_HEADER
|
||||
|
||||
+3
-1
@@ -53,11 +53,13 @@
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Emu\Audio\Cubeb\CubebBackend.h" />
|
||||
<ClInclude Include="Emu\Audio\Cubeb\cubeb_enumerator.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Emu\Audio\Cubeb\CubebBackend.cpp" />
|
||||
<ClCompile Include="Emu\Audio\Cubeb\cubeb_enumerator.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
||||
@@ -10,10 +10,16 @@
|
||||
<ClCompile Include="Emu\Audio\Cubeb\CubebBackend.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="Emu\Audio\Cubeb\cubeb_enumerator.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Emu\Audio\Cubeb\CubebBackend.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="Emu\Audio\Cubeb\cubeb_enumerator.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -6,10 +6,16 @@
|
||||
|
||||
AudioBackend::AudioBackend() {}
|
||||
|
||||
void AudioBackend::SetErrorCallback(std::function<void()> cb)
|
||||
void AudioBackend::SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void* /* buffer */)> cb)
|
||||
{
|
||||
std::lock_guard lock(m_error_cb_mutex);
|
||||
m_error_callback = cb;
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
m_write_callback = cb;
|
||||
}
|
||||
|
||||
void AudioBackend::SetStateCallback(std::function<void(AudioStateEvent)> cb)
|
||||
{
|
||||
std::lock_guard lock(m_state_cb_mutex);
|
||||
m_state_callback = cb;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -105,7 +111,7 @@ std::pair<AudioChannelCnt, AudioChannelCnt> AudioBackend::get_channel_count_and_
|
||||
audio_out_configuration& audio_out_cfg = g_fxo->get<audio_out_configuration>();
|
||||
std::lock_guard lock(audio_out_cfg.mtx);
|
||||
ensure(device_index < audio_out_cfg.out.size());
|
||||
const audio_out_configuration::audio_out& out = audio_out_cfg.out.at(device_index);
|
||||
const audio_out_configuration::audio_out& out = ::at32(audio_out_cfg.out, device_index);
|
||||
return out.get_channel_count_and_downmixer();
|
||||
}
|
||||
|
||||
@@ -114,7 +120,7 @@ AudioChannelCnt AudioBackend::get_max_channel_count(u32 device_index)
|
||||
audio_out_configuration& audio_out_cfg = g_fxo->get<audio_out_configuration>();
|
||||
std::lock_guard lock(audio_out_cfg.mtx);
|
||||
ensure(device_index < audio_out_cfg.out.size());
|
||||
const audio_out_configuration::audio_out& out = audio_out_cfg.out.at(device_index);
|
||||
const audio_out_configuration::audio_out& out = ::at32(audio_out_cfg.out, device_index);
|
||||
|
||||
AudioChannelCnt count = AudioChannelCnt::STEREO;
|
||||
|
||||
@@ -134,3 +140,24 @@ AudioChannelCnt AudioBackend::get_max_channel_count(u32 device_index)
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
AudioChannelCnt AudioBackend::convert_channel_count(u64 raw)
|
||||
{
|
||||
switch (raw)
|
||||
{
|
||||
default:
|
||||
case 8:
|
||||
return AudioChannelCnt::SURROUND_7_1;
|
||||
case 7:
|
||||
case 6:
|
||||
return AudioChannelCnt::SURROUND_5_1;
|
||||
case 5:
|
||||
case 4:
|
||||
case 3:
|
||||
case 2:
|
||||
case 1:
|
||||
return AudioChannelCnt::STEREO;
|
||||
case 0:
|
||||
fmt::throw_exception("Usupported channel count");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,12 @@ enum class AudioChannelCnt : u32
|
||||
SURROUND_7_1 = 8,
|
||||
};
|
||||
|
||||
enum class AudioStateEvent : u32
|
||||
{
|
||||
UNSPECIFIED_ERROR,
|
||||
DEFAULT_DEVICE_MAYBE_CHANGED,
|
||||
};
|
||||
|
||||
class AudioBackend
|
||||
{
|
||||
public:
|
||||
@@ -60,19 +66,21 @@ public:
|
||||
virtual std::string_view GetName() const = 0;
|
||||
|
||||
// (Re)create output stream with new parameters. Blocks until data callback returns.
|
||||
// If dev_id is empty, then default device will be selected.
|
||||
// May override channel count if device has smaller number of channels.
|
||||
// Should return 'true' on success.
|
||||
virtual bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0;
|
||||
virtual bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) = 0;
|
||||
|
||||
// Reset backend state. Blocks until data callback returns.
|
||||
virtual void Close() = 0;
|
||||
|
||||
// Sets write callback. It's called when backend requests new data to be sent.
|
||||
// Callback should return number of submitted bytes. Calling other backend functions from callback is unsafe.
|
||||
virtual void SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void* /* buffer */)> cb) = 0;
|
||||
virtual void SetWriteCallback(std::function<u32(u32 /* byte_cnt */, void* /* buffer */)> cb);
|
||||
|
||||
// Sets error callback. It's called when backend detects uncorrectable error condition in audio chain.
|
||||
// Sets error callback. It's called when backend detects event in audio chain that needs immediate attention.
|
||||
// Calling other backend functions from callback is unsafe.
|
||||
virtual void SetErrorCallback(std::function<void()> cb);
|
||||
virtual void SetStateCallback(std::function<void(AudioStateEvent)> cb);
|
||||
|
||||
/*
|
||||
* All functions below require that Open() was called prior.
|
||||
@@ -101,6 +109,11 @@ public:
|
||||
*/
|
||||
virtual bool Operational() { return true; }
|
||||
|
||||
/*
|
||||
* This virtual method should be reimplemented if backend can report device changes
|
||||
*/
|
||||
virtual bool DefaultDeviceChanged() { return false; }
|
||||
|
||||
/*
|
||||
* Helper methods
|
||||
*/
|
||||
@@ -145,6 +158,11 @@ public:
|
||||
*/
|
||||
static AudioChannelCnt get_max_channel_count(u32 device_index);
|
||||
|
||||
/*
|
||||
* Converts raw channel count to value usable by backends
|
||||
*/
|
||||
static AudioChannelCnt convert_channel_count(u64 raw);
|
||||
|
||||
/*
|
||||
* Downmix audio stream.
|
||||
*/
|
||||
@@ -208,8 +226,11 @@ protected:
|
||||
AudioFreq m_sampling_rate = AudioFreq::FREQ_48K;
|
||||
AudioChannelCnt m_channels = AudioChannelCnt::STEREO;
|
||||
|
||||
shared_mutex m_error_cb_mutex{};
|
||||
std::function<void()> m_error_callback{};
|
||||
std::timed_mutex m_cb_mutex{};
|
||||
std::function<u32(u32, void *)> m_write_callback{};
|
||||
|
||||
shared_mutex m_state_cb_mutex{};
|
||||
std::function<void(AudioStateEvent)> m_state_callback{};
|
||||
|
||||
bool m_playing = false;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include "util/logs.hpp"
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
@@ -15,27 +16,46 @@ CubebBackend::CubebBackend()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (SUCCEEDED(hr))
|
||||
if (HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); SUCCEEDED(hr))
|
||||
{
|
||||
m_com_init_success = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (int err = cubeb_init(&m_ctx, "RPCS3", nullptr))
|
||||
cubeb *ctx{};
|
||||
if (int err = cubeb_init(&ctx, "RPCS3", nullptr))
|
||||
{
|
||||
Cubeb.error("cubeb_init() failed: 0x%08x", err);
|
||||
m_ctx = nullptr;
|
||||
Cubeb.error("cubeb_init() failed: %i", err);
|
||||
return;
|
||||
}
|
||||
|
||||
Cubeb.notice("Using backend %s", cubeb_get_backend_id(m_ctx));
|
||||
if (int err = cubeb_register_device_collection_changed(ctx, CUBEB_DEVICE_TYPE_OUTPUT, device_collection_changed_cb, this))
|
||||
{
|
||||
Cubeb.error("cubeb_register_device_collection_changed() failed: %i", err);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_dev_collection_cb_enabled = true;
|
||||
}
|
||||
|
||||
Cubeb.notice("Using backend %s", cubeb_get_backend_id(ctx));
|
||||
|
||||
std::lock_guard cb_lock{m_state_cb_mutex};
|
||||
m_ctx = ctx;
|
||||
}
|
||||
|
||||
CubebBackend::~CubebBackend()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (m_dev_collection_cb_enabled)
|
||||
{
|
||||
if (int err = cubeb_register_device_collection_changed(m_ctx, CUBEB_DEVICE_TYPE_OUTPUT, nullptr, nullptr))
|
||||
{
|
||||
Cubeb.error("cubeb_register_device_collection_changed() failed: %i", err);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_ctx)
|
||||
{
|
||||
cubeb_destroy(m_ctx);
|
||||
@@ -56,11 +76,27 @@ bool CubebBackend::Initialized()
|
||||
|
||||
bool CubebBackend::Operational()
|
||||
{
|
||||
std::lock_guard lock(m_error_cb_mutex);
|
||||
return m_stream != nullptr && !m_reset_req;
|
||||
return m_stream != nullptr && !m_reset_req.observe();
|
||||
}
|
||||
|
||||
bool CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
bool CubebBackend::DefaultDeviceChanged()
|
||||
{
|
||||
if (m_default_device.empty() || m_reset_req.observe())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
device_handle device = GetDevice();
|
||||
if (!device.handle)
|
||||
{
|
||||
Cubeb.error("Selected device not found. Trying alternative approach...");
|
||||
device = GetDefaultDeviceAlt(m_sampling_rate, m_sample_size, m_channels);
|
||||
}
|
||||
|
||||
return !device.handle || device.id != m_default_device;
|
||||
}
|
||||
|
||||
bool CubebBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
{
|
||||
if (!Initialized())
|
||||
{
|
||||
@@ -68,12 +104,49 @@ bool CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
|
||||
return false;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
CloseUnlocked();
|
||||
Close();
|
||||
std::lock_guard lock{m_cb_mutex};
|
||||
|
||||
const bool use_default_device = dev_id.empty() || dev_id == audio_device_enumerator::DEFAULT_DEV_ID;
|
||||
|
||||
if (use_default_device) Cubeb.notice("Trying to open default device");
|
||||
else Cubeb.notice("Trying to open device with dev_id='%s'", dev_id);
|
||||
|
||||
device_handle device = GetDevice(use_default_device ? "" : dev_id);
|
||||
|
||||
if (!device.handle)
|
||||
{
|
||||
if (use_default_device)
|
||||
{
|
||||
device = GetDefaultDeviceAlt(freq, sample_size, ch_cnt);
|
||||
|
||||
if (!device.handle)
|
||||
{
|
||||
Cubeb.error("Cannot detect default device. Channel count detection unavailable.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Cubeb.error("Device with id=%s not found", dev_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (device.ch_cnt == 0)
|
||||
{
|
||||
Cubeb.error("Device reported invalid channel count, using stereo instead");
|
||||
device.ch_cnt = 2;
|
||||
}
|
||||
|
||||
if (use_default_device)
|
||||
{
|
||||
std::lock_guard lock{m_state_cb_mutex};
|
||||
m_default_device = device.id;
|
||||
}
|
||||
|
||||
m_sampling_rate = freq;
|
||||
m_sample_size = sample_size;
|
||||
m_channels = ch_cnt;
|
||||
m_channels = static_cast<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(device.ch_cnt)), static_cast<u32>(ch_cnt)));
|
||||
full_sample_size = get_channels() * get_sample_size();
|
||||
|
||||
cubeb_stream_params stream_param{};
|
||||
@@ -82,70 +155,69 @@ bool CubebBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChanne
|
||||
stream_param.channels = get_channels();
|
||||
stream_param.layout = [&]()
|
||||
{
|
||||
switch (ch_cnt)
|
||||
switch (m_channels)
|
||||
{
|
||||
case AudioChannelCnt::STEREO: return CUBEB_LAYOUT_STEREO;
|
||||
case AudioChannelCnt::SURROUND_5_1: return CUBEB_LAYOUT_3F2_LFE;
|
||||
case AudioChannelCnt::SURROUND_7_1: return CUBEB_LAYOUT_3F4_LFE;
|
||||
default:
|
||||
ensure(false);
|
||||
return CUBEB_LAYOUT_UNDEFINED;
|
||||
fmt::throw_exception("Invalid audio channel count");
|
||||
}
|
||||
}();
|
||||
stream_param.prefs = m_dev_collection_cb_enabled && device.handle ? CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING : CUBEB_STREAM_PREF_NONE;
|
||||
|
||||
u32 min_latency{};
|
||||
if (int err = cubeb_get_min_latency(m_ctx, &stream_param, &min_latency))
|
||||
{
|
||||
Cubeb.error("cubeb_get_min_latency() failed: 0x%08x", err);
|
||||
Cubeb.error("cubeb_get_min_latency() failed: %i", err);
|
||||
min_latency = 0;
|
||||
}
|
||||
|
||||
const u32 stream_latency = std::max(static_cast<u32>(AUDIO_MIN_LATENCY * get_sampling_rate()), min_latency);
|
||||
if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, nullptr, &stream_param, stream_latency, data_cb, state_cb, this))
|
||||
|
||||
if (int err = cubeb_stream_init(m_ctx, &m_stream, "Main stream", nullptr, nullptr, device.handle, &stream_param, stream_latency, data_cb, state_cb, this))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_init() failed: 0x%08x", err);
|
||||
Cubeb.error("cubeb_stream_init() failed: %i", err);
|
||||
m_stream = nullptr;
|
||||
}
|
||||
else if (int err = cubeb_stream_start(m_stream))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_start() failed: 0x%08x", err);
|
||||
CloseUnlocked();
|
||||
}
|
||||
else if (int err = cubeb_stream_set_volume(m_stream, 1.0))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_set_volume() failed: 0x%08x", err);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_stream == nullptr)
|
||||
if (int err = cubeb_stream_start(m_stream))
|
||||
{
|
||||
Cubeb.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
|
||||
Cubeb.error("cubeb_stream_start() failed: %i", err);
|
||||
Close();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (int err = cubeb_stream_set_volume(m_stream, 1.0))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_set_volume() failed: %i", err);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CubebBackend::CloseUnlocked()
|
||||
void CubebBackend::Close()
|
||||
{
|
||||
if (m_stream != nullptr)
|
||||
{
|
||||
if (int err = cubeb_stream_stop(m_stream))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_stop() failed: 0x%08x", err);
|
||||
Cubeb.error("cubeb_stream_stop() failed: %i", err);
|
||||
}
|
||||
|
||||
cubeb_stream_destroy(m_stream);
|
||||
m_stream = nullptr;
|
||||
}
|
||||
|
||||
m_playing = false;
|
||||
m_last_sample.fill(0);
|
||||
}
|
||||
{
|
||||
std::lock_guard lock{m_cb_mutex};
|
||||
m_stream = nullptr;
|
||||
m_playing = false;
|
||||
m_last_sample.fill(0);
|
||||
}
|
||||
|
||||
void CubebBackend::Close()
|
||||
{
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
CloseUnlocked();
|
||||
std::lock_guard lock{m_state_cb_mutex};
|
||||
m_default_device.clear();
|
||||
}
|
||||
|
||||
void CubebBackend::Play()
|
||||
@@ -177,12 +249,6 @@ void CubebBackend::Pause()
|
||||
m_last_sample.fill(0);
|
||||
}
|
||||
|
||||
void CubebBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
|
||||
{
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
m_write_callback = cb;
|
||||
}
|
||||
|
||||
f64 CubebBackend::GetCallbackFrameLen()
|
||||
{
|
||||
if (m_stream == nullptr)
|
||||
@@ -194,20 +260,187 @@ f64 CubebBackend::GetCallbackFrameLen()
|
||||
u32 stream_latency{};
|
||||
if (int err = cubeb_stream_get_latency(m_stream, &stream_latency))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_get_latency() failed: 0x%08x", err);
|
||||
Cubeb.error("cubeb_stream_get_latency() failed: %i", err);
|
||||
stream_latency = 0;
|
||||
}
|
||||
|
||||
return std::max<f64>(AUDIO_MIN_LATENCY, static_cast<f64>(stream_latency) / get_sampling_rate());
|
||||
}
|
||||
|
||||
long CubebBackend::data_cb(cubeb_stream* /* stream */, void* user_ptr, void const* /* input_buffer */, void* output_buffer, long nframes)
|
||||
CubebBackend::device_handle CubebBackend::GetDevice(std::string_view dev_id)
|
||||
{
|
||||
const bool default_dev = dev_id.empty();
|
||||
|
||||
if (default_dev) Cubeb.notice("Searching for default device");
|
||||
else Cubeb.notice("Searching for device with dev_id='%s'", dev_id);
|
||||
|
||||
cubeb_device_collection dev_collection{};
|
||||
if (int err = cubeb_enumerate_devices(m_ctx, CUBEB_DEVICE_TYPE_OUTPUT, &dev_collection))
|
||||
{
|
||||
Cubeb.error("cubeb_enumerate_devices() failed: %i", err);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (dev_collection.count == 0)
|
||||
{
|
||||
Cubeb.error("No output devices available");
|
||||
if (int err = cubeb_device_collection_destroy(m_ctx, &dev_collection))
|
||||
{
|
||||
Cubeb.error("cubeb_device_collection_destroy() failed: %i", err);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Cubeb.notice("Found %d possible output devices", dev_collection.count);
|
||||
|
||||
device_handle result{};
|
||||
|
||||
for (u64 dev_idx = 0; dev_idx < dev_collection.count; dev_idx++)
|
||||
{
|
||||
const cubeb_device_info& dev_info = dev_collection.device[dev_idx];
|
||||
|
||||
device_handle device{};
|
||||
device.handle = dev_info.devid;
|
||||
device.ch_cnt = dev_info.max_channels;
|
||||
|
||||
if (dev_info.device_id)
|
||||
{
|
||||
device.id = dev_info.device_id;
|
||||
}
|
||||
|
||||
if (device.id.empty())
|
||||
{
|
||||
Cubeb.error("device_id is missing from device");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (default_dev)
|
||||
{
|
||||
if (dev_info.preferred & CUBEB_DEVICE_PREF_MULTIMEDIA)
|
||||
{
|
||||
result = std::move(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (device.id == dev_id)
|
||||
{
|
||||
result = std::move(device);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.handle)
|
||||
{
|
||||
Cubeb.notice("Found device '%s' with %d channels", result.id, result.ch_cnt);
|
||||
}
|
||||
else
|
||||
{
|
||||
Cubeb.notice("No device found for dev_id='%s'", dev_id);
|
||||
}
|
||||
|
||||
if (int err = cubeb_device_collection_destroy(m_ctx, &dev_collection))
|
||||
{
|
||||
Cubeb.error("cubeb_device_collection_destroy() failed: %i", err);
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
CubebBackend::device_handle CubebBackend::GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
{
|
||||
Cubeb.notice("Starting alternative search for default device with freq=%d, sample_size=%d and ch_cnt=%d", static_cast<u32>(freq), static_cast<u32>(sample_size), static_cast<u32>(ch_cnt));
|
||||
|
||||
cubeb_stream_params param =
|
||||
{
|
||||
.format = sample_size == AudioSampleSize::S16 ? CUBEB_SAMPLE_S16NE : CUBEB_SAMPLE_FLOAT32NE,
|
||||
.rate = static_cast<u32>(freq),
|
||||
.channels = static_cast<u32>(ch_cnt),
|
||||
.layout = CUBEB_LAYOUT_UNDEFINED,
|
||||
.prefs = CUBEB_STREAM_PREF_DISABLE_DEVICE_SWITCHING
|
||||
};
|
||||
|
||||
u32 min_latency{};
|
||||
if (int err = cubeb_get_min_latency(m_ctx, ¶m, &min_latency))
|
||||
{
|
||||
Cubeb.error("cubeb_get_min_latency() failed: %i", err);
|
||||
min_latency = 100;
|
||||
}
|
||||
|
||||
cubeb_stream* tmp_stream{};
|
||||
static auto dummy_data_cb = [](cubeb_stream*, void*, void const*, void*, long) -> long { return 0; };
|
||||
static auto dummy_state_cb = [](cubeb_stream*, void*, cubeb_state) {};
|
||||
|
||||
if (int err = cubeb_stream_init(m_ctx, &tmp_stream, "Default device detector", nullptr, nullptr, nullptr, ¶m, min_latency, dummy_data_cb, dummy_state_cb, nullptr))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_init() failed: %i", err);
|
||||
return {};
|
||||
}
|
||||
|
||||
cubeb_device* crnt_dev{};
|
||||
|
||||
if (int err = cubeb_stream_get_current_device(tmp_stream, &crnt_dev); err != CUBEB_OK || !crnt_dev)
|
||||
{
|
||||
Cubeb.error("cubeb_stream_get_current_device() failed: err=%i, crnt_dev=%d", err, !!crnt_dev);
|
||||
cubeb_stream_destroy(tmp_stream);
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string out_dev_name;
|
||||
|
||||
if (crnt_dev->output_name)
|
||||
{
|
||||
out_dev_name = crnt_dev->output_name;
|
||||
}
|
||||
|
||||
if (int err = cubeb_stream_device_destroy(tmp_stream, crnt_dev))
|
||||
{
|
||||
Cubeb.error("cubeb_stream_device_destroy() failed: %i", err);
|
||||
}
|
||||
|
||||
cubeb_stream_destroy(tmp_stream);
|
||||
|
||||
if (out_dev_name.empty())
|
||||
{
|
||||
Cubeb.notice("No default device available");
|
||||
return {};
|
||||
}
|
||||
|
||||
return GetDevice(out_dev_name);
|
||||
}
|
||||
|
||||
long CubebBackend::data_cb(cubeb_stream* stream, void* user_ptr, void const* /* input_buffer */, void* output_buffer, long nframes)
|
||||
{
|
||||
if (nframes <= 0)
|
||||
{
|
||||
Cubeb.error("data_cb called with nframes=%d", nframes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!output_buffer)
|
||||
{
|
||||
Cubeb.error("data_cb called with invalid output_buffer");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
if (!stream)
|
||||
{
|
||||
Cubeb.error("data_cb called with invalid stream");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
CubebBackend* const cubeb = static_cast<CubebBackend*>(user_ptr);
|
||||
ensure(cubeb);
|
||||
|
||||
std::unique_lock lock(cubeb->m_cb_mutex, std::defer_lock);
|
||||
|
||||
if (nframes && lock.try_lock() && cubeb->m_write_callback && cubeb->m_playing)
|
||||
if (!cubeb->m_reset_req.observe() && lock.try_lock_for(std::chrono::microseconds{50}) && cubeb->m_write_callback && cubeb->m_playing)
|
||||
{
|
||||
if (stream != cubeb->m_stream)
|
||||
{
|
||||
// Cubeb.error("data_cb called with unknown stream");
|
||||
return CUBEB_ERROR;
|
||||
}
|
||||
|
||||
const u32 sample_size = cubeb->full_sample_size.observe();
|
||||
const u32 bytes_req = nframes * sample_size;
|
||||
u32 written = std::min(cubeb->m_write_callback(bytes_req, output_buffer), bytes_req);
|
||||
@@ -227,26 +460,102 @@ long CubebBackend::data_cb(cubeb_stream* /* stream */, void* user_ptr, void cons
|
||||
{
|
||||
// Stream parameters are modified only after stream_destroy. stream_destroy will return
|
||||
// only after this callback returns, so it's safe to access full_sample_size here.
|
||||
memset(output_buffer, 0, nframes * cubeb->full_sample_size.observe());
|
||||
memset(output_buffer, 0, nframes * cubeb->full_sample_size);
|
||||
}
|
||||
|
||||
return nframes;
|
||||
}
|
||||
|
||||
void CubebBackend::state_cb(cubeb_stream* /* stream */, void* user_ptr, cubeb_state state)
|
||||
void CubebBackend::state_cb(cubeb_stream* stream, void* user_ptr, cubeb_state state)
|
||||
{
|
||||
CubebBackend* const cubeb = static_cast<CubebBackend*>(user_ptr);
|
||||
if (!stream)
|
||||
{
|
||||
Cubeb.error("state_cb called with invalid stream");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state == CUBEB_STATE_ERROR)
|
||||
CubebBackend* const cubeb = static_cast<CubebBackend*>(user_ptr);
|
||||
ensure(cubeb);
|
||||
|
||||
std::lock_guard lock(cubeb->m_state_cb_mutex);
|
||||
|
||||
if (stream != cubeb->m_stream)
|
||||
{
|
||||
Cubeb.error("state_cb called with unknown stream");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case CUBEB_STATE_ERROR:
|
||||
{
|
||||
Cubeb.error("Stream entered error state");
|
||||
|
||||
std::lock_guard lock(cubeb->m_error_cb_mutex);
|
||||
cubeb->m_reset_req = true;
|
||||
|
||||
if (cubeb->m_error_callback)
|
||||
if (!cubeb->m_reset_req.test_and_set() && cubeb->m_state_callback)
|
||||
{
|
||||
cubeb->m_error_callback();
|
||||
cubeb->m_state_callback(AudioStateEvent::UNSPECIFIED_ERROR);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case CUBEB_STATE_STARTED:
|
||||
{
|
||||
Cubeb.notice("Stream started");
|
||||
break;
|
||||
}
|
||||
case CUBEB_STATE_STOPPED:
|
||||
{
|
||||
Cubeb.notice("Stream stopped");
|
||||
break;
|
||||
}
|
||||
case CUBEB_STATE_DRAINED:
|
||||
{
|
||||
Cubeb.notice("Stream drained");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
Cubeb.notice("Stream entered unknown state %d", static_cast<u32>(state));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CubebBackend::device_collection_changed_cb(cubeb* context, void* user_ptr)
|
||||
{
|
||||
Cubeb.notice("Device collection changed");
|
||||
|
||||
if (!context)
|
||||
{
|
||||
Cubeb.error("device_collection_changed_cb called with invalid stream");
|
||||
return;
|
||||
}
|
||||
|
||||
CubebBackend* const cubeb = static_cast<CubebBackend*>(user_ptr);
|
||||
ensure(cubeb);
|
||||
|
||||
if (cubeb->m_reset_req.observe())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard cb_lock{cubeb->m_state_cb_mutex};
|
||||
|
||||
if (context != cubeb->m_ctx)
|
||||
{
|
||||
Cubeb.error("device_collection_changed_cb called with unkown context");
|
||||
return;
|
||||
}
|
||||
|
||||
// Non default device is used (or default device cannot be detected)
|
||||
if (cubeb->m_default_device.empty())
|
||||
{
|
||||
Cubeb.notice("Skipping default device notification");
|
||||
return;
|
||||
}
|
||||
|
||||
if (cubeb->m_state_callback)
|
||||
{
|
||||
cubeb->m_state_callback(AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,11 @@ public:
|
||||
|
||||
bool Initialized() override;
|
||||
bool Operational() override;
|
||||
bool DefaultDeviceChanged() override;
|
||||
|
||||
bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
void Close() override;
|
||||
|
||||
void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
|
||||
f64 GetCallbackFrameLen() override;
|
||||
|
||||
void Play() override;
|
||||
@@ -39,16 +39,28 @@ private:
|
||||
bool m_com_init_success = false;
|
||||
#endif
|
||||
|
||||
shared_mutex m_cb_mutex{};
|
||||
std::function<u32(u32, void *)> m_write_callback{};
|
||||
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
|
||||
atomic_t<u8> full_sample_size = 0;
|
||||
|
||||
bool m_reset_req = false;
|
||||
atomic_t<bool> m_reset_req = false;
|
||||
|
||||
// Protected by callback mutex
|
||||
std::string m_default_device{};
|
||||
|
||||
bool m_dev_collection_cb_enabled = false;
|
||||
|
||||
// Cubeb callbacks
|
||||
static long data_cb(cubeb_stream* stream, void* user_ptr, void const* input_buffer, void* output_buffer, long nframes);
|
||||
static void state_cb(cubeb_stream* stream, void* user_ptr, cubeb_state state);
|
||||
static void device_collection_changed_cb(cubeb* context, void* user_ptr);
|
||||
|
||||
void CloseUnlocked();
|
||||
struct device_handle
|
||||
{
|
||||
cubeb_devid handle{};
|
||||
std::string id;
|
||||
u32 ch_cnt{};
|
||||
};
|
||||
|
||||
device_handle GetDevice(std::string_view dev_id = "");
|
||||
device_handle GetDefaultDeviceAlt(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,113 @@
|
||||
#include "Emu/Audio/Cubeb/cubeb_enumerator.h"
|
||||
#include "util/logs.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <Windows.h>
|
||||
#include <system_error>
|
||||
#endif
|
||||
|
||||
LOG_CHANNEL(cubeb_dev_enum);
|
||||
|
||||
cubeb_enumerator::cubeb_enumerator() : audio_device_enumerator()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
// Cubeb requires COM to be initialized on the thread calling cubeb_init on Windows
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
com_init_success = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (int err = cubeb_init(&ctx, "RPCS3 device enumeration", nullptr))
|
||||
{
|
||||
cubeb_dev_enum.error("cubeb_init() failed: %i", err);
|
||||
ctx = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
cubeb_enumerator::~cubeb_enumerator()
|
||||
{
|
||||
if (ctx)
|
||||
{
|
||||
cubeb_destroy(ctx);
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (com_init_success)
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::vector<audio_device_enumerator::audio_device> cubeb_enumerator::get_output_devices()
|
||||
{
|
||||
if (ctx == nullptr)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
cubeb_device_collection dev_collection{};
|
||||
if (int err = cubeb_enumerate_devices(ctx, CUBEB_DEVICE_TYPE_OUTPUT, &dev_collection))
|
||||
{
|
||||
cubeb_dev_enum.error("cubeb_enumerate_devices() failed: %i", err);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (dev_collection.count == 0)
|
||||
{
|
||||
cubeb_dev_enum.error("No output devices available");
|
||||
if (int err = cubeb_device_collection_destroy(ctx, &dev_collection))
|
||||
{
|
||||
cubeb_dev_enum.error("cubeb_device_collection_destroy() failed: %i", err);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<audio_device> device_list{};
|
||||
|
||||
for (u64 dev_idx = 0; dev_idx < dev_collection.count; dev_idx++)
|
||||
{
|
||||
const cubeb_device_info& dev_info = dev_collection.device[dev_idx];
|
||||
|
||||
if (dev_info.state == CUBEB_DEVICE_STATE_UNPLUGGED)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
audio_device dev =
|
||||
{
|
||||
.id = std::string{dev_info.device_id},
|
||||
.name = std::string{dev_info.friendly_name},
|
||||
.max_ch = dev_info.max_channels
|
||||
};
|
||||
|
||||
if (dev.id.empty())
|
||||
{
|
||||
cubeb_dev_enum.warning("Empty device id - skipping");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (dev.name.empty())
|
||||
{
|
||||
dev.name = dev.id;
|
||||
}
|
||||
|
||||
cubeb_dev_enum.notice("Found device: id=%s, name=%s, max_ch=%d", dev.id, dev.name, dev.max_ch);
|
||||
device_list.emplace_back(dev);
|
||||
}
|
||||
|
||||
if (int err = cubeb_device_collection_destroy(ctx, &dev_collection))
|
||||
{
|
||||
cubeb_dev_enum.error("cubeb_device_collection_destroy() failed: %i", err);
|
||||
}
|
||||
|
||||
std::sort(device_list.begin(), device_list.end(), [](audio_device_enumerator::audio_device a, audio_device_enumerator::audio_device b)
|
||||
{
|
||||
return a.name < b.name;
|
||||
});
|
||||
|
||||
return device_list;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
|
||||
#include "cubeb/cubeb.h"
|
||||
|
||||
class cubeb_enumerator final : public audio_device_enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
cubeb_enumerator();
|
||||
~cubeb_enumerator() override;
|
||||
|
||||
std::vector<audio_device> get_output_devices() override;
|
||||
|
||||
private:
|
||||
|
||||
cubeb* ctx{};
|
||||
#ifdef _WIN32
|
||||
bool com_init_success = false;
|
||||
#endif
|
||||
};
|
||||
@@ -6,6 +6,8 @@
|
||||
#include "FAudioBackend.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
||||
LOG_CHANNEL(FAudio_, "FAudio");
|
||||
|
||||
@@ -117,11 +119,10 @@ bool FAudioBackend::Initialized()
|
||||
|
||||
bool FAudioBackend::Operational()
|
||||
{
|
||||
std::lock_guard lock(m_error_cb_mutex);
|
||||
return m_source_voice != nullptr && !m_reset_req;
|
||||
return m_source_voice != nullptr && !m_reset_req.observe();
|
||||
}
|
||||
|
||||
bool FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
bool FAudioBackend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
{
|
||||
if (!Initialized())
|
||||
{
|
||||
@@ -132,9 +133,37 @@ bool FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChann
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
CloseUnlocked();
|
||||
|
||||
const bool use_default_dev = dev_id.empty() || dev_id == audio_device_enumerator::DEFAULT_DEV_ID;
|
||||
u64 devid{};
|
||||
if (!use_default_dev)
|
||||
{
|
||||
if (!try_to_uint64(&devid, dev_id, 0, UINT32_MAX))
|
||||
{
|
||||
FAudio_.error("Invalid device id - %s", dev_id);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (u32 res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, static_cast<u32>(devid), nullptr))
|
||||
{
|
||||
FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res);
|
||||
m_master_voice = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
FAudioVoiceDetails vd{};
|
||||
FAudioVoice_GetVoiceDetails(m_master_voice, &vd);
|
||||
|
||||
if (vd.InputChannels == 0)
|
||||
{
|
||||
FAudio_.error("Channel count of 0 is invalid");
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sampling_rate = freq;
|
||||
m_sample_size = sample_size;
|
||||
m_channels = ch_cnt;
|
||||
m_channels = static_cast<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(vd.InputChannels)), static_cast<u32>(ch_cnt)));;
|
||||
|
||||
FAudioWaveFormatEx waveformatex;
|
||||
waveformatex.wFormatTag = get_convert_to_s16() ? FAUDIO_FORMAT_PCM : FAUDIO_FORMAT_IEEE_FLOAT;
|
||||
@@ -153,43 +182,30 @@ bool FAudioBackend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChann
|
||||
OnLoopEnd = nullptr;
|
||||
OnVoiceError = nullptr;
|
||||
|
||||
if (u32 res = FAudio_CreateMasteringVoice(m_instance, &m_master_voice, FAUDIO_DEFAULT_CHANNELS, FAUDIO_DEFAULT_SAMPLERATE, 0, 0, nullptr))
|
||||
{
|
||||
FAudio_.error("FAudio_CreateMasteringVoice() failed(0x%08x)", res);
|
||||
m_master_voice = nullptr;
|
||||
}
|
||||
else if (u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr))
|
||||
if (u32 res = FAudio_CreateSourceVoice(m_instance, &m_source_voice, &waveformatex, 0, FAUDIO_DEFAULT_FREQ_RATIO, this, nullptr, nullptr))
|
||||
{
|
||||
FAudio_.error("FAudio_CreateSourceVoice() failed(0x%08x)", res);
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
else if (u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW))
|
||||
|
||||
if (u32 res = FAudioSourceVoice_Start(m_source_voice, 0, FAUDIO_COMMIT_NOW))
|
||||
{
|
||||
FAudio_.error("FAudioSourceVoice_Start() failed(0x%08x)", res);
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
else if (u32 res = FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW))
|
||||
|
||||
if (u32 res = FAudioVoice_SetVolume(m_source_voice, 1.0f, FAUDIO_COMMIT_NOW))
|
||||
{
|
||||
FAudio_.error("FAudioVoice_SetVolume() failed(0x%08x)", res);
|
||||
}
|
||||
|
||||
if (m_source_voice == nullptr)
|
||||
{
|
||||
FAudio_.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<u32>(FAUDIO_DEFAULT_FREQ_RATIO) / 1000);
|
||||
m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS / 1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FAudioBackend::SetWriteCallback(std::function<u32(u32, void *)> cb)
|
||||
{
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
m_write_callback = cb;
|
||||
}
|
||||
|
||||
f64 FAudioBackend::GetCallbackFrameLen()
|
||||
{
|
||||
constexpr f64 _10ms = 0.01;
|
||||
@@ -218,7 +234,7 @@ void FAudioBackend::OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj,
|
||||
FAudioBackend *faudio = static_cast<FAudioBackend *>(cb_obj);
|
||||
|
||||
std::unique_lock lock(faudio->m_cb_mutex, std::defer_lock);
|
||||
if (BytesRequired && lock.try_lock() && faudio->m_write_callback && faudio->m_playing)
|
||||
if (BytesRequired && !faudio->m_reset_req.observe() && lock.try_lock_for(std::chrono::microseconds{50}) && faudio->m_write_callback && faudio->m_playing)
|
||||
{
|
||||
ensure(BytesRequired <= faudio->m_data_buf.size(), "FAudio internal buffer is too small. Report to developers!");
|
||||
|
||||
@@ -250,11 +266,10 @@ void FAudioBackend::OnCriticalError_func(FAudioEngineCallback *cb_obj, u32 Error
|
||||
|
||||
FAudioBackend *faudio = static_cast<FAudioBackend *>(cb_obj);
|
||||
|
||||
std::lock_guard lock(faudio->m_error_cb_mutex);
|
||||
faudio->m_reset_req = true;
|
||||
std::lock_guard lock(faudio->m_state_cb_mutex);
|
||||
|
||||
if (faudio->m_error_callback)
|
||||
if (!faudio->m_reset_req.test_and_set() && faudio->m_state_callback)
|
||||
{
|
||||
faudio->m_error_callback();
|
||||
faudio->m_state_callback(AudioStateEvent::UNSPECIFIED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,9 @@ public:
|
||||
bool Initialized() override;
|
||||
bool Operational() override;
|
||||
|
||||
bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
void Close() override;
|
||||
|
||||
void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
|
||||
f64 GetCallbackFrameLen() override;
|
||||
|
||||
void Play() override;
|
||||
@@ -40,12 +39,10 @@ private:
|
||||
FAudioMasteringVoice* m_master_voice{};
|
||||
FAudioSourceVoice* m_source_voice{};
|
||||
|
||||
shared_mutex m_cb_mutex{};
|
||||
std::function<u32(u32, void *)> m_write_callback{};
|
||||
std::vector<u8> m_data_buf{};
|
||||
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
|
||||
|
||||
bool m_reset_req = false;
|
||||
atomic_t<bool> m_reset_req = false;
|
||||
|
||||
// FAudio voice callbacks
|
||||
static void OnVoiceProcessingPassStart_func(FAudioVoiceCallback *cb_obj, u32 BytesRequired);
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
#ifndef HAVE_FAUDIO
|
||||
#error "FAudio support disabled but still being built."
|
||||
#endif
|
||||
|
||||
#include "Emu/Audio/FAudio/faudio_enumerator.h"
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include "util/logs.hpp"
|
||||
|
||||
LOG_CHANNEL(faudio_dev_enum);
|
||||
|
||||
faudio_enumerator::faudio_enumerator() : audio_device_enumerator()
|
||||
{
|
||||
FAudio *tmp{};
|
||||
|
||||
if (u32 res = FAudioCreate(&tmp, 0, FAUDIO_DEFAULT_PROCESSOR))
|
||||
{
|
||||
faudio_dev_enum.error("FAudioCreate() failed(0x%08x)", res);
|
||||
return;
|
||||
}
|
||||
|
||||
// All succeeded, "commit"
|
||||
instance = tmp;
|
||||
}
|
||||
|
||||
faudio_enumerator::~faudio_enumerator()
|
||||
{
|
||||
if (instance != nullptr)
|
||||
{
|
||||
FAudio_StopEngine(instance);
|
||||
FAudio_Release(instance);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<audio_device_enumerator::audio_device> faudio_enumerator::get_output_devices()
|
||||
{
|
||||
if (!instance)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
u32 dev_cnt{};
|
||||
if (u32 res = FAudio_GetDeviceCount(instance, &dev_cnt))
|
||||
{
|
||||
faudio_dev_enum.error("FAudio_GetDeviceCount() failed(0x%08x)", res);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (dev_cnt == 0)
|
||||
{
|
||||
faudio_dev_enum.warning("No devices available");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<audio_device> device_list{};
|
||||
|
||||
for (u32 dev_idx = 0; dev_idx < dev_cnt; dev_idx++)
|
||||
{
|
||||
FAudioDeviceDetails dev_info{};
|
||||
|
||||
if (u32 res = FAudio_GetDeviceDetails(instance, dev_idx, &dev_info))
|
||||
{
|
||||
faudio_dev_enum.error("FAudio_GetDeviceDetails() failed(0x%08x)", res);
|
||||
continue;
|
||||
}
|
||||
|
||||
audio_device dev =
|
||||
{
|
||||
.id = std::to_string(dev_idx),
|
||||
.name = utf16_to_utf8(std::bit_cast<char16_t*>(&dev_info.DisplayName[0])),
|
||||
.max_ch = dev_info.OutputFormat.Format.nChannels
|
||||
};
|
||||
|
||||
if (dev.name.empty())
|
||||
{
|
||||
dev.name = "Device " + dev.id;
|
||||
}
|
||||
|
||||
faudio_dev_enum.notice("Found device: id=%s, name=%s, max_ch=%d", dev.id, dev.name, dev.max_ch);
|
||||
device_list.emplace_back(dev);
|
||||
}
|
||||
|
||||
std::sort(device_list.begin(), device_list.end(), [](audio_device_enumerator::audio_device a, audio_device_enumerator::audio_device b)
|
||||
{
|
||||
return a.name < b.name;
|
||||
});
|
||||
|
||||
return device_list;
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef HAVE_FAUDIO
|
||||
#error "FAudio support disabled but still being built."
|
||||
#endif
|
||||
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
#include "FAudio.h"
|
||||
|
||||
class faudio_enumerator final : public audio_device_enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
faudio_enumerator();
|
||||
~faudio_enumerator() override;
|
||||
|
||||
std::vector<audio_device> get_output_devices() override;
|
||||
|
||||
private:
|
||||
|
||||
FAudio* instance{};
|
||||
};
|
||||
@@ -10,18 +10,15 @@ public:
|
||||
|
||||
std::string_view GetName() const override { return "Null"sv; }
|
||||
|
||||
bool Open(AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override
|
||||
bool Open(std::string_view /* dev_id */, AudioFreq /* freq */, AudioSampleSize /* sample_size */, AudioChannelCnt /* ch_cnt */) override
|
||||
{
|
||||
Close();
|
||||
return true;
|
||||
}
|
||||
void Close() override { m_playing = false; }
|
||||
|
||||
void SetWriteCallback(std::function<u32(u32, void *)> /* cb */) override {};
|
||||
f64 GetCallbackFrameLen() override { return 0.01; };
|
||||
|
||||
void SetErrorCallback(std::function<void()> /* cb */) override {};
|
||||
|
||||
void Play() override { m_playing = true; }
|
||||
void Pause() override { m_playing = false; }
|
||||
bool IsPlaying() override { return m_playing; }
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
|
||||
class null_enumerator final : public audio_device_enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
null_enumerator() {};
|
||||
~null_enumerator() override {};
|
||||
|
||||
std::vector<audio_device> get_output_devices() override { return {}; }
|
||||
};
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <algorithm>
|
||||
#include "util/logs.hpp"
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
#include "Utilities/StrUtil.h"
|
||||
|
||||
#include "XAudio2Backend.h"
|
||||
#include <Windows.h>
|
||||
@@ -14,42 +16,85 @@
|
||||
|
||||
LOG_CHANNEL(XAudio);
|
||||
|
||||
template <>
|
||||
void fmt_class_string<ERole>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case eConsole: return "eConsole";
|
||||
case eMultimedia: return "eMultimedia";
|
||||
case eCommunications: return "eCommunications";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<EDataFlow>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case eRender: return "eRender";
|
||||
case eCapture: return "eCapture";
|
||||
case eAll: return "eAll";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
XAudio2Backend::XAudio2Backend()
|
||||
: AudioBackend()
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IXAudio2> instance;
|
||||
Microsoft::WRL::ComPtr<IXAudio2> instance{};
|
||||
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> enumerator{};
|
||||
|
||||
// In order to prevent errors on CreateMasteringVoice, apparently we need CoInitializeEx according to:
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/xaudio2fx/nf-xaudio2fx-xaudio2createvolumemeter
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (SUCCEEDED(hr))
|
||||
if (HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED); SUCCEEDED(hr))
|
||||
{
|
||||
m_com_init_success = true;
|
||||
}
|
||||
|
||||
hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_USE_DEFAULT_PROCESSOR);
|
||||
if (FAILED(hr))
|
||||
if (HRESULT hr = XAudio2Create(instance.GetAddressOf(), 0, XAUDIO2_USE_DEFAULT_PROCESSOR); FAILED(hr))
|
||||
{
|
||||
XAudio.error("XAudio2Create() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
hr = instance->RegisterForCallbacks(this);
|
||||
if (FAILED(hr))
|
||||
if (HRESULT hr = instance->RegisterForCallbacks(this); FAILED(hr))
|
||||
{
|
||||
// Some error recovery functionality will be lost, but otherwise backend is operational
|
||||
XAudio.error("RegisterForCallbacks() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
}
|
||||
|
||||
// Try to register a listener for device changes
|
||||
if (HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(enumerator.GetAddressOf())); FAILED(hr))
|
||||
{
|
||||
XAudio.error("CoCreateInstance() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return;
|
||||
}
|
||||
|
||||
// All succeeded, "commit"
|
||||
m_xaudio2_instance = std::move(instance);
|
||||
m_device_enumerator = std::move(enumerator);
|
||||
}
|
||||
|
||||
XAudio2Backend::~XAudio2Backend()
|
||||
{
|
||||
Close();
|
||||
|
||||
if (m_device_enumerator != nullptr)
|
||||
{
|
||||
m_device_enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
m_device_enumerator = nullptr;
|
||||
}
|
||||
|
||||
if (m_xaudio2_instance != nullptr)
|
||||
{
|
||||
m_xaudio2_instance->StopEngine();
|
||||
@@ -69,14 +114,13 @@ bool XAudio2Backend::Initialized()
|
||||
|
||||
bool XAudio2Backend::Operational()
|
||||
{
|
||||
std::lock_guard lock(m_error_cb_mutex);
|
||||
return m_source_voice != nullptr && !m_reset_req.observe();
|
||||
}
|
||||
|
||||
if (m_dev_listener.output_device_changed())
|
||||
{
|
||||
m_reset_req = true;
|
||||
}
|
||||
|
||||
return m_source_voice != nullptr && !m_reset_req;
|
||||
bool XAudio2Backend::DefaultDeviceChanged()
|
||||
{
|
||||
std::lock_guard lock{m_state_cb_mutex};
|
||||
return !m_reset_req.observe() && m_default_dev_changed;
|
||||
}
|
||||
|
||||
void XAudio2Backend::Play()
|
||||
@@ -112,8 +156,14 @@ void XAudio2Backend::CloseUnlocked()
|
||||
m_master_voice = nullptr;
|
||||
}
|
||||
|
||||
m_device_enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
|
||||
m_playing = false;
|
||||
m_last_sample.fill(0);
|
||||
|
||||
std::lock_guard lock(m_state_cb_mutex);
|
||||
m_default_dev_changed = false;
|
||||
m_current_device.clear();
|
||||
}
|
||||
|
||||
void XAudio2Backend::Close()
|
||||
@@ -144,7 +194,7 @@ void XAudio2Backend::Pause()
|
||||
}
|
||||
}
|
||||
|
||||
bool XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
bool XAudio2Backend::Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt)
|
||||
{
|
||||
if (!Initialized())
|
||||
{
|
||||
@@ -155,9 +205,54 @@ bool XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChan
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
CloseUnlocked();
|
||||
|
||||
const bool use_default_device = dev_id.empty() || dev_id == audio_device_enumerator::DEFAULT_DEV_ID;
|
||||
std::string selected_dev_id{};
|
||||
|
||||
if (use_default_device)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IMMDevice> default_dev{};
|
||||
if (HRESULT hr = m_device_enumerator->GetDefaultAudioEndpoint(eRender, eConsole, default_dev.GetAddressOf()); FAILED(hr))
|
||||
{
|
||||
XAudio.error("GetDefaultAudioEndpoint() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
LPWSTR default_id{};
|
||||
if (HRESULT hr = default_dev->GetId(&default_id); FAILED(hr))
|
||||
{
|
||||
XAudio.error("GetId() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return false;
|
||||
}
|
||||
|
||||
selected_dev_id = wchar_to_utf8(std::wstring_view{default_id});
|
||||
CoTaskMemFree(default_id);
|
||||
if (selected_dev_id.empty())
|
||||
{
|
||||
XAudio.error("Default device id is empty");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice, 0, 0, 0, utf8_to_wchar(use_default_device ? selected_dev_id : dev_id).c_str()); FAILED(hr))
|
||||
{
|
||||
XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
m_master_voice = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
XAUDIO2_VOICE_DETAILS vd{};
|
||||
m_master_voice->GetVoiceDetails(&vd);
|
||||
|
||||
if (vd.InputChannels == 0)
|
||||
{
|
||||
XAudio.error("Channel count 0 is invalid");
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
m_sampling_rate = freq;
|
||||
m_sample_size = sample_size;
|
||||
m_channels = ch_cnt;
|
||||
m_channels = static_cast<AudioChannelCnt>(std::min(static_cast<u32>(convert_channel_count(vd.InputChannels)), static_cast<u32>(ch_cnt)));
|
||||
|
||||
WAVEFORMATEX waveformatex{};
|
||||
waveformatex.wFormatTag = get_convert_to_s16() ? WAVE_FORMAT_PCM : WAVE_FORMAT_IEEE_FLOAT;
|
||||
@@ -168,65 +263,63 @@ bool XAudio2Backend::Open(AudioFreq freq, AudioSampleSize sample_size, AudioChan
|
||||
waveformatex.wBitsPerSample = get_sample_size() * 8;
|
||||
waveformatex.cbSize = 0;
|
||||
|
||||
if (HRESULT hr = m_xaudio2_instance->CreateMasteringVoice(&m_master_voice); FAILED(hr))
|
||||
{
|
||||
XAudio.error("CreateMasteringVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
m_master_voice = nullptr;
|
||||
}
|
||||
else if (HRESULT hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); FAILED(hr))
|
||||
if (HRESULT hr = m_xaudio2_instance->CreateSourceVoice(&m_source_voice, &waveformatex, 0, XAUDIO2_DEFAULT_FREQ_RATIO, this); FAILED(hr))
|
||||
{
|
||||
XAudio.error("CreateSourceVoice() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
else if (HRESULT hr = m_source_voice->Start(); FAILED(hr))
|
||||
|
||||
if (HRESULT hr = m_source_voice->Start(); FAILED(hr))
|
||||
{
|
||||
XAudio.error("Start() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
else if (HRESULT hr = m_source_voice->SetVolume(1.0f); FAILED(hr))
|
||||
|
||||
if (HRESULT hr = m_device_enumerator->RegisterEndpointNotificationCallback(this); FAILED(hr))
|
||||
{
|
||||
XAudio.error("RegisterEndpointNotificationCallback() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
CloseUnlocked();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (HRESULT hr = m_source_voice->SetVolume(1.0f); FAILED(hr))
|
||||
{
|
||||
XAudio.error("SetVolume() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
}
|
||||
|
||||
if (m_source_voice == nullptr)
|
||||
m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS / 1000);
|
||||
|
||||
if (use_default_device)
|
||||
{
|
||||
XAudio.error("Failed to open audio backend. Make sure that no other application is running that might block audio access (e.g. Netflix).");
|
||||
return false;
|
||||
m_current_device = selected_dev_id;
|
||||
}
|
||||
|
||||
m_data_buf.resize(get_sampling_rate() * get_sample_size() * get_channels() * INTERNAL_BUF_SIZE_MS * static_cast<u32>(XAUDIO2_DEFAULT_FREQ_RATIO) / 1000);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void XAudio2Backend::SetWriteCallback(std::function<u32(u32, void *)> cb)
|
||||
{
|
||||
std::lock_guard lock(m_cb_mutex);
|
||||
m_write_callback = cb;
|
||||
}
|
||||
|
||||
f64 XAudio2Backend::GetCallbackFrameLen()
|
||||
{
|
||||
constexpr f64 _10ms = 0.01;
|
||||
|
||||
if (m_source_voice == nullptr)
|
||||
if (m_xaudio2_instance == nullptr)
|
||||
{
|
||||
XAudio.error("GetCallbackFrameLen() called uninitialized");
|
||||
return _10ms;
|
||||
}
|
||||
|
||||
void *ext;
|
||||
Microsoft::WRL::ComPtr<IXAudio2Extension> xaudio_ext{};
|
||||
f64 min_latency{};
|
||||
|
||||
const HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, &ext);
|
||||
if (FAILED(hr))
|
||||
if (HRESULT hr = m_xaudio2_instance->QueryInterface(IID_IXAudio2Extension, std::bit_cast<void**>(xaudio_ext.GetAddressOf())); FAILED(hr))
|
||||
{
|
||||
XAudio.error("QueryInterface() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
}
|
||||
else
|
||||
{
|
||||
u32 samples_per_q = 0, freq = 0;
|
||||
static_cast<IXAudio2Extension *>(ext)->GetProcessingQuantum(&samples_per_q, &freq);
|
||||
xaudio_ext->GetProcessingQuantum(&samples_per_q, &freq);
|
||||
|
||||
if (freq)
|
||||
{
|
||||
@@ -240,7 +333,7 @@ f64 XAudio2Backend::GetCallbackFrameLen()
|
||||
void XAudio2Backend::OnVoiceProcessingPassStart(UINT32 BytesRequired)
|
||||
{
|
||||
std::unique_lock lock(m_cb_mutex, std::defer_lock);
|
||||
if (BytesRequired && lock.try_lock() && m_write_callback && m_playing)
|
||||
if (BytesRequired && !m_reset_req.observe() && lock.try_lock_for(std::chrono::microseconds{50}) && m_write_callback && m_playing)
|
||||
{
|
||||
ensure(BytesRequired <= m_data_buf.size(), "XAudio internal buffer is too small. Report to developers!");
|
||||
|
||||
@@ -270,11 +363,53 @@ void XAudio2Backend::OnCriticalError(HRESULT Error)
|
||||
{
|
||||
XAudio.error("OnCriticalError() called: %s (0x%08x)", std::system_category().message(Error), static_cast<u32>(Error));
|
||||
|
||||
std::lock_guard lock(m_error_cb_mutex);
|
||||
m_reset_req = true;
|
||||
std::lock_guard lock(m_state_cb_mutex);
|
||||
|
||||
if (m_error_callback)
|
||||
if (!m_reset_req.test_and_set() && m_state_callback)
|
||||
{
|
||||
m_error_callback();
|
||||
m_state_callback(AudioStateEvent::UNSPECIFIED_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
HRESULT XAudio2Backend::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id)
|
||||
{
|
||||
XAudio.notice("OnDefaultDeviceChanged(flow=%s, role=%s, new_default_device_id=0x%x)", flow, role, new_default_device_id);
|
||||
|
||||
if (!new_default_device_id)
|
||||
{
|
||||
XAudio.notice("OnDefaultDeviceChanged(): new_default_device_id empty");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Listen only for one device role, otherwise we're going to receive more than one notification for flow type
|
||||
if (role != eConsole)
|
||||
{
|
||||
XAudio.notice("OnDefaultDeviceChanged(): we don't care about this device");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
std::lock_guard lock(m_state_cb_mutex);
|
||||
|
||||
// Non default device is used
|
||||
if (m_current_device.empty())
|
||||
{
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const std::string new_device_id = wchar_to_utf8(std::wstring_view{new_default_device_id});
|
||||
|
||||
if (flow == eRender || flow == eAll)
|
||||
{
|
||||
if (!m_reset_req.observe() && new_device_id != m_current_device)
|
||||
{
|
||||
m_default_dev_changed = true;
|
||||
|
||||
if (m_state_callback)
|
||||
{
|
||||
m_state_callback(AudioStateEvent::DEFAULT_DEVICE_MAYBE_CHANGED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
#include <memory>
|
||||
#include "Utilities/mutex.h"
|
||||
#include "Emu/Audio/AudioBackend.h"
|
||||
#include "Emu/Audio/audio_device_listener.h"
|
||||
|
||||
#include <xaudio2redist.h>
|
||||
#include <wrl/client.h>
|
||||
#include <MMDeviceAPI.h>
|
||||
|
||||
class XAudio2Backend final : public AudioBackend, public IXAudio2VoiceCallback, public IXAudio2EngineCallback
|
||||
class XAudio2Backend final : public AudioBackend, public IXAudio2VoiceCallback, public IXAudio2EngineCallback, public IMMNotificationClient
|
||||
{
|
||||
public:
|
||||
XAudio2Backend();
|
||||
@@ -25,11 +25,11 @@ public:
|
||||
|
||||
bool Initialized() override;
|
||||
bool Operational() override;
|
||||
bool DefaultDeviceChanged() override;
|
||||
|
||||
bool Open(AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
bool Open(std::string_view dev_id, AudioFreq freq, AudioSampleSize sample_size, AudioChannelCnt ch_cnt) override;
|
||||
void Close() override;
|
||||
|
||||
void SetWriteCallback(std::function<u32(u32, void *)> cb) override;
|
||||
f64 GetCallbackFrameLen() override;
|
||||
|
||||
void Play() override;
|
||||
@@ -43,14 +43,16 @@ private:
|
||||
IXAudio2SourceVoice* m_source_voice{};
|
||||
bool m_com_init_success = false;
|
||||
|
||||
shared_mutex m_cb_mutex{};
|
||||
std::function<u32(u32, void *)> m_write_callback{};
|
||||
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> m_device_enumerator{};
|
||||
|
||||
// Protected by state callback mutex
|
||||
std::string m_current_device{};
|
||||
bool m_default_dev_changed = false;
|
||||
|
||||
std::vector<u8> m_data_buf{};
|
||||
std::array<u8, sizeof(float) * static_cast<u32>(AudioChannelCnt::SURROUND_7_1)> m_last_sample{};
|
||||
|
||||
bool m_reset_req = false;
|
||||
|
||||
audio_device_listener m_dev_listener{};
|
||||
atomic_t<bool> m_reset_req = false;
|
||||
|
||||
// XAudio voice callbacks
|
||||
void OnVoiceProcessingPassStart(UINT32 BytesRequired) override;
|
||||
@@ -66,5 +68,15 @@ private:
|
||||
void OnProcessingPassEnd() override {};
|
||||
void OnCriticalError(HRESULT Error) override;
|
||||
|
||||
// IMMNotificationClient callbacks
|
||||
IFACEMETHODIMP_(ULONG) AddRef() override { return 1; };
|
||||
IFACEMETHODIMP_(ULONG) Release() override { return 1; };
|
||||
IFACEMETHODIMP QueryInterface(REFIID /*iid*/, void** /*object*/) override { return E_NOINTERFACE; };
|
||||
IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*device_id*/, const PROPERTYKEY /*key*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceAdded(LPCWSTR /*device_id*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceRemoved(LPCWSTR /*device_id*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR /*device_id*/, DWORD /*new_state*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override;
|
||||
|
||||
void CloseUnlocked();
|
||||
};
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
#ifndef _WIN32
|
||||
#error "XAudio2 can only be built on Windows."
|
||||
#endif
|
||||
|
||||
#include "Emu/Audio/XAudio2/xaudio2_enumerator.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include <algorithm>
|
||||
|
||||
#include <wrl/client.h>
|
||||
#include <Windows.h>
|
||||
#include <system_error>
|
||||
#include <mmdeviceapi.h>
|
||||
#include <Functiondiscoverykeys_devpkey.h>
|
||||
|
||||
LOG_CHANNEL(xaudio_dev_enum);
|
||||
|
||||
xaudio2_enumerator::xaudio2_enumerator() : audio_device_enumerator()
|
||||
{
|
||||
}
|
||||
|
||||
xaudio2_enumerator::~xaudio2_enumerator()
|
||||
{
|
||||
}
|
||||
|
||||
std::vector<audio_device_enumerator::audio_device> xaudio2_enumerator::get_output_devices()
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IMMDeviceEnumerator> devEnum{};
|
||||
if (HRESULT hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(devEnum.GetAddressOf())); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("CoCreateInstance() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return {};
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IMMDeviceCollection> devices{};
|
||||
if (HRESULT hr = devEnum->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE | DEVICE_STATE_DISABLED, &devices); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("EnumAudioEndpoints() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return {};
|
||||
}
|
||||
|
||||
UINT count = 0;
|
||||
if (HRESULT hr = devices->GetCount(&count); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("devices->GetCount() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
return {};
|
||||
}
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
xaudio_dev_enum.warning("No devices available");
|
||||
return {};
|
||||
}
|
||||
|
||||
std::vector<audio_device> device_list{};
|
||||
|
||||
for (UINT dev_idx = 0; dev_idx < count; dev_idx++)
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IMMDevice> endpoint{};
|
||||
if (HRESULT hr = devices->Item(dev_idx, endpoint.GetAddressOf()); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("devices->Item() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
continue;
|
||||
}
|
||||
|
||||
LPWSTR id = nullptr;
|
||||
if (HRESULT hr = endpoint->GetId(&id); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("endpoint->GetId() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (std::wstring_view{id}.empty())
|
||||
{
|
||||
xaudio_dev_enum.error("Empty device id - skipping");
|
||||
CoTaskMemFree(id);
|
||||
continue;
|
||||
}
|
||||
|
||||
audio_device dev{};
|
||||
dev.id = wchar_to_utf8(id);
|
||||
|
||||
CoTaskMemFree(id);
|
||||
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> props{};
|
||||
if (HRESULT hr = endpoint->OpenPropertyStore(STGM_READ, props.GetAddressOf()); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("endpoint->OpenPropertyStore() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
continue;
|
||||
}
|
||||
|
||||
PROPVARIANT var;
|
||||
PropVariantInit(&var);
|
||||
|
||||
if (HRESULT hr = props->GetValue(PKEY_Device_FriendlyName, &var); FAILED(hr))
|
||||
{
|
||||
xaudio_dev_enum.error("props->GetValue() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
PropVariantClear(&var);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (var.vt != VT_LPWSTR)
|
||||
{
|
||||
PropVariantClear(&var);
|
||||
continue;
|
||||
}
|
||||
|
||||
dev.name = wchar_to_utf8(var.pwszVal);
|
||||
if (dev.name.empty())
|
||||
{
|
||||
dev.name = dev.id;
|
||||
}
|
||||
|
||||
PropVariantClear(&var);
|
||||
dev.max_ch = 2;
|
||||
|
||||
if (HRESULT hr = props->GetValue(PKEY_AudioEngine_DeviceFormat, &var); SUCCEEDED(hr))
|
||||
{
|
||||
if (var.vt == VT_BLOB)
|
||||
{
|
||||
if (var.blob.cbSize == sizeof(PCMWAVEFORMAT))
|
||||
{
|
||||
const PCMWAVEFORMAT* pcm = std::bit_cast<const PCMWAVEFORMAT*>(var.blob.pBlobData);
|
||||
dev.max_ch = pcm->wf.nChannels;
|
||||
}
|
||||
else if (var.blob.cbSize >= sizeof(WAVEFORMATEX))
|
||||
{
|
||||
const WAVEFORMATEX* wfx = std::bit_cast<const WAVEFORMATEX*>(var.blob.pBlobData);
|
||||
if (var.blob.cbSize >= sizeof(WAVEFORMATEX) + wfx->cbSize || wfx->wFormatTag == WAVE_FORMAT_PCM)
|
||||
{
|
||||
dev.max_ch = wfx->nChannels;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
xaudio_dev_enum.error("props->GetValue() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
}
|
||||
|
||||
PropVariantClear(&var);
|
||||
|
||||
xaudio_dev_enum.notice("Found device: id=%s, name=%s, max_ch=%d", dev.id, dev.name, dev.max_ch);
|
||||
device_list.emplace_back(dev);
|
||||
}
|
||||
|
||||
std::sort(device_list.begin(), device_list.end(), [](audio_device_enumerator::audio_device a, audio_device_enumerator::audio_device b)
|
||||
{
|
||||
return a.name < b.name;
|
||||
});
|
||||
|
||||
return device_list;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#ifndef _WIN32
|
||||
#error "XAudio2 can only be built on Windows."
|
||||
#endif
|
||||
|
||||
#include "Emu/Audio/audio_device_enumerator.h"
|
||||
|
||||
class xaudio2_enumerator final : public audio_device_enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
xaudio2_enumerator();
|
||||
~xaudio2_enumerator() override;
|
||||
|
||||
std::vector<audio_device> get_output_devices() override;
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
|
||||
class audio_device_enumerator
|
||||
{
|
||||
public:
|
||||
|
||||
static constexpr std::string_view DEFAULT_DEV_ID = "@@@default@@@";
|
||||
|
||||
struct audio_device
|
||||
{
|
||||
std::string id{};
|
||||
std::string name{};
|
||||
usz max_ch{};
|
||||
};
|
||||
|
||||
audio_device_enumerator() {};
|
||||
|
||||
virtual ~audio_device_enumerator() = default;
|
||||
|
||||
// Enumerate available output devices.
|
||||
virtual std::vector<audio_device> get_output_devices() = 0;
|
||||
};
|
||||
@@ -1,147 +0,0 @@
|
||||
#include "stdafx.h"
|
||||
#include "audio_device_listener.h"
|
||||
#include "util/logs.hpp"
|
||||
#include "Utilities/StrUtil.h"
|
||||
#include "Emu/Cell/Modules/cellAudio.h"
|
||||
#include "Emu/IdManager.h"
|
||||
|
||||
LOG_CHANNEL(IO);
|
||||
|
||||
audio_device_listener::audio_device_listener()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
HRESULT hr = CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
m_com_init_success = true;
|
||||
}
|
||||
|
||||
// Try to register a listener for device changes
|
||||
hr = CoCreateInstance(__uuidof(MMDeviceEnumerator), nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_device_enumerator));
|
||||
if (hr != S_OK)
|
||||
{
|
||||
IO.error("CoCreateInstance() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
}
|
||||
else if (m_device_enumerator)
|
||||
{
|
||||
hr = m_device_enumerator->RegisterEndpointNotificationCallback(this);
|
||||
if (FAILED(hr))
|
||||
{
|
||||
IO.error("RegisterEndpointNotificationCallback() failed: %s (0x%08x)", std::system_category().message(hr), static_cast<u32>(hr));
|
||||
m_device_enumerator->Release();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
IO.error("Device enumerator invalid");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
audio_device_listener::~audio_device_listener()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if (m_device_enumerator != nullptr)
|
||||
{
|
||||
m_device_enumerator->UnregisterEndpointNotificationCallback(this);
|
||||
m_device_enumerator->Release();
|
||||
}
|
||||
|
||||
if (m_com_init_success)
|
||||
{
|
||||
CoUninitialize();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool audio_device_listener::input_device_changed()
|
||||
{
|
||||
return m_input_device_changed.test_and_reset();
|
||||
}
|
||||
|
||||
bool audio_device_listener::output_device_changed()
|
||||
{
|
||||
return m_output_device_changed.test_and_reset();
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
template <>
|
||||
void fmt_class_string<ERole>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case eConsole: return "eConsole";
|
||||
case eMultimedia: return "eMultimedia";
|
||||
case eCommunications: return "eCommunications";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<EDataFlow>::format(std::string& out, u64 arg)
|
||||
{
|
||||
format_enum(out, arg, [](auto value)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case eRender: return "eRender";
|
||||
case eCapture: return "eCapture";
|
||||
case eAll: return "eAll";
|
||||
}
|
||||
|
||||
return unknown;
|
||||
});
|
||||
}
|
||||
|
||||
HRESULT audio_device_listener::OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id)
|
||||
{
|
||||
IO.notice("OnDefaultDeviceChanged(flow=%s, role=%s, new_default_device_id=0x%x)", flow, role, new_default_device_id);
|
||||
|
||||
if (!new_default_device_id)
|
||||
{
|
||||
IO.notice("OnDefaultDeviceChanged(): new_default_device_id empty");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Listen only for one device role, otherwise we're going to receive more than one
|
||||
// notification for flow type
|
||||
if (role != eConsole)
|
||||
{
|
||||
IO.notice("OnDefaultDeviceChanged(): we don't care about this device");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
const std::wstring tmp(new_default_device_id);
|
||||
const std::string new_device_id = wchar_to_utf8(tmp.c_str());
|
||||
|
||||
if (flow == eRender || flow == eAll)
|
||||
{
|
||||
if (output_device_id != new_device_id)
|
||||
{
|
||||
output_device_id = new_device_id;
|
||||
|
||||
IO.warning("Default output device changed: new device = '%s'", output_device_id);
|
||||
|
||||
m_output_device_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (flow == eCapture || flow == eAll)
|
||||
{
|
||||
if (input_device_id != new_device_id)
|
||||
{
|
||||
input_device_id = new_device_id;
|
||||
|
||||
IO.warning("Default input device changed: new device = '%s'", input_device_id);
|
||||
|
||||
m_input_device_changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
#endif
|
||||
@@ -1,42 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <MMDeviceAPI.h>
|
||||
#endif
|
||||
|
||||
#include "util/atomic.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
class audio_device_listener : public IMMNotificationClient
|
||||
#else
|
||||
class audio_device_listener
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
audio_device_listener();
|
||||
~audio_device_listener();
|
||||
|
||||
bool input_device_changed();
|
||||
bool output_device_changed();
|
||||
|
||||
private:
|
||||
#ifdef _WIN32
|
||||
std::string input_device_id;
|
||||
std::string output_device_id;
|
||||
|
||||
IFACEMETHODIMP_(ULONG) AddRef() override { return 1; };
|
||||
IFACEMETHODIMP_(ULONG) Release() override { return 1; };
|
||||
IFACEMETHODIMP QueryInterface(REFIID /*iid*/, void** /*object*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnPropertyValueChanged(LPCWSTR /*device_id*/, const PROPERTYKEY /*key*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceAdded(LPCWSTR /*device_id*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceRemoved(LPCWSTR /*device_id*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDeviceStateChanged(LPCWSTR /*device_id*/, DWORD /*new_state*/) override { return S_OK; };
|
||||
IFACEMETHODIMP OnDefaultDeviceChanged(EDataFlow flow, ERole role, LPCWSTR new_default_device_id) override;
|
||||
|
||||
IMMDeviceEnumerator* m_device_enumerator = nullptr;
|
||||
bool m_com_init_success = false;
|
||||
#endif
|
||||
|
||||
atomic_t<bool> m_input_device_changed = false;
|
||||
atomic_t<bool> m_output_device_changed = false;
|
||||
};
|
||||
@@ -2,6 +2,7 @@ add_library(rpcs3_emu
|
||||
cache_utils.cpp
|
||||
IdManager.cpp
|
||||
localized_string.cpp
|
||||
savestate_utils.cpp
|
||||
System.cpp
|
||||
system_config.cpp
|
||||
system_config_types.cpp
|
||||
@@ -36,7 +37,6 @@ target_include_directories(rpcs3_emu
|
||||
PUBLIC
|
||||
${RPCS3_SRC_DIR})
|
||||
|
||||
|
||||
# Utilities
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
../util/atomic.cpp
|
||||
@@ -58,6 +58,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
../../Utilities/rXml.cpp
|
||||
../../Utilities/sema.cpp
|
||||
../../Utilities/simple_ringbuf.cpp
|
||||
../../Utilities/stack_trace.cpp
|
||||
../../Utilities/StrFmt.cpp
|
||||
../../Utilities/Thread.cpp
|
||||
../../Utilities/version.cpp
|
||||
@@ -115,20 +116,22 @@ target_sources(rpcs3_emu PRIVATE
|
||||
../Loader/TRP.cpp
|
||||
)
|
||||
|
||||
|
||||
# Audio
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
Audio/audio_device_listener.cpp
|
||||
Audio/audio_resampler.cpp
|
||||
Audio/AudioDumper.cpp
|
||||
Audio/AudioBackend.cpp
|
||||
Audio/Cubeb/CubebBackend.cpp
|
||||
Audio/Cubeb/cubeb_enumerator.cpp
|
||||
)
|
||||
|
||||
if(USE_FAUDIO)
|
||||
find_package(SDL2)
|
||||
if(SDL2_FOUND AND NOT SDL2_VERSION VERSION_LESS 2.0.9)
|
||||
target_sources(rpcs3_emu PRIVATE Audio/FAudio/FAudioBackend.cpp)
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
Audio/FAudio/FAudioBackend.cpp
|
||||
Audio/FAudio/faudio_enumerator.cpp
|
||||
)
|
||||
target_link_libraries(rpcs3_emu PUBLIC 3rdparty::faudio)
|
||||
endif()
|
||||
endif()
|
||||
@@ -139,6 +142,7 @@ if(WIN32)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DELAYLOAD:xaudio2_9redist.dll")
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
Audio/XAudio2/XAudio2Backend.cpp
|
||||
Audio/XAudio2/xaudio2_enumerator.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -182,6 +186,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Cell/lv2/sys_event.cpp
|
||||
Cell/lv2/sys_event_flag.cpp
|
||||
Cell/lv2/sys_fs.cpp
|
||||
Cell/lv2/sys_game.cpp
|
||||
Cell/lv2/sys_gamepad.cpp
|
||||
Cell/lv2/sys_gpio.cpp
|
||||
Cell/lv2/sys_hid.cpp
|
||||
@@ -259,6 +264,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Cell/Modules/cellMsgDialog.cpp
|
||||
Cell/Modules/cellMusic.cpp
|
||||
Cell/Modules/cellMusicDecode.cpp
|
||||
Cell/Modules/cellMusicSelectionContext.cpp
|
||||
Cell/Modules/cellMusicExport.cpp
|
||||
Cell/Modules/cellNetAoi.cpp
|
||||
Cell/Modules/cellNetCtl.cpp
|
||||
@@ -332,7 +338,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Cell/Modules/sceNpUtil.cpp
|
||||
Cell/Modules/StaticHLE.cpp
|
||||
Cell/Modules/sys_crashdump.cpp
|
||||
Cell/Modules/sys_game.cpp
|
||||
Cell/Modules/sys_game_.cpp
|
||||
Cell/Modules/sys_heap.cpp
|
||||
Cell/Modules/sys_io_.cpp
|
||||
Cell/Modules/sys_libc_.cpp
|
||||
@@ -408,7 +414,6 @@ target_sources(rpcs3_emu PRIVATE
|
||||
Memory/vm.cpp
|
||||
)
|
||||
|
||||
|
||||
# RSX
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
RSX/gcm_enums.cpp
|
||||
@@ -430,6 +435,7 @@ target_sources(rpcs3_emu PRIVATE
|
||||
RSX/Null/NullGSRender.cpp
|
||||
RSX/Overlays/overlay_animation.cpp
|
||||
RSX/Overlays/overlay_controls.cpp
|
||||
RSX/Overlays/overlay_cursor.cpp
|
||||
RSX/Overlays/overlay_edit_text.cpp
|
||||
RSX/Overlays/overlay_fonts.cpp
|
||||
RSX/Overlays/overlay_list_view.cpp
|
||||
@@ -457,10 +463,15 @@ target_sources(rpcs3_emu PRIVATE
|
||||
RSX/Program/VertexProgramDecompiler.cpp
|
||||
RSX/Capture/rsx_capture.cpp
|
||||
RSX/Capture/rsx_replay.cpp
|
||||
RSX/GL/glutils/blitter.cpp
|
||||
RSX/GL/glutils/buffer_object.cpp
|
||||
RSX/GL/glutils/capabilities.cpp
|
||||
RSX/GL/glutils/common.cpp
|
||||
RSX/GL/glutils/fbo.cpp
|
||||
RSX/GL/glutils/image.cpp
|
||||
RSX/GL/glutils/program.cpp
|
||||
RSX/GL/glutils/ring_buffer.cpp
|
||||
RSX/GL/glutils/sampler.cpp
|
||||
RSX/GL/GLCommonDecompiler.cpp
|
||||
RSX/GL/GLCompute.cpp
|
||||
RSX/GL/GLDraw.cpp
|
||||
@@ -538,7 +549,7 @@ target_link_libraries(rpcs3_emu
|
||||
if(APPLE)
|
||||
check_function_exists(clock_gettime HAVE_CLOCK_GETTIME)
|
||||
if (${HAVE_CLOCK_GETTIME})
|
||||
target_compile_definitions(rpcs3_emu PUBLIC -DHAVE_CLOCK_GETTIME)
|
||||
target_compile_definitions(rpcs3_emu PUBLIC -DHAVE_CLOCK_GETTIME)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
+253
-41
@@ -5,6 +5,7 @@
|
||||
#include "Emu/System.h"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/Memory/vm_locking.h"
|
||||
#include "Emu/Memory/vm_reservation.h"
|
||||
#include "Emu/IdManager.h"
|
||||
#include "Emu/GDB.h"
|
||||
#include "Emu/Cell/PPUThread.h"
|
||||
@@ -49,10 +50,14 @@ void fmt_class_string<cpu_flag>::format(std::string& out, u64 arg)
|
||||
case cpu_flag::pause: return "p";
|
||||
case cpu_flag::suspend: return "s";
|
||||
case cpu_flag::ret: return "ret";
|
||||
case cpu_flag::again: return "a";
|
||||
case cpu_flag::signal: return "sig";
|
||||
case cpu_flag::memory: return "mem";
|
||||
case cpu_flag::pending: return "pend";
|
||||
case cpu_flag::pending_recheck: return "pend-re";
|
||||
case cpu_flag::notify: return "ntf";
|
||||
case cpu_flag::yield: return "y";
|
||||
case cpu_flag::preempt: return "PREEMPT";
|
||||
case cpu_flag::dbg_global_pause: return "G-PAUSE";
|
||||
case cpu_flag::dbg_pause: return "PAUSE";
|
||||
case cpu_flag::dbg_step: return "STEP";
|
||||
@@ -83,15 +88,18 @@ struct cpu_prof
|
||||
// Total number of samples
|
||||
u64 samples = 0, idle = 0;
|
||||
|
||||
// Avoid printing replicas
|
||||
bool printed = false;
|
||||
// Avoid printing replicas or when not much changed
|
||||
u64 new_samples = 0;
|
||||
|
||||
static constexpr u64 min_print_samples = 500;
|
||||
static constexpr u64 min_print_all_samples = min_print_samples * 20;
|
||||
|
||||
void reset()
|
||||
{
|
||||
freq.clear();
|
||||
samples = 0;
|
||||
idle = 0;
|
||||
printed = false;
|
||||
new_samples = 0;
|
||||
}
|
||||
|
||||
static std::string format(const std::multimap<u64, u64, std::greater<u64>>& chart, u64 samples, u64 idle, bool extended_print = false)
|
||||
@@ -127,8 +135,13 @@ struct cpu_prof
|
||||
// Print info
|
||||
void print(const std::shared_ptr<cpu_thread>& ptr)
|
||||
{
|
||||
if (printed || samples == idle)
|
||||
if (new_samples < min_print_samples || samples == idle)
|
||||
{
|
||||
if (cpu_flag::exit - ptr->state)
|
||||
{
|
||||
profiler.notice("Thread \"%s\" [0x%08x]: %u samples, %u new (%.4f%% idle): Not enough new samples have been collected since the last print.", ptr->get_name(), ptr->id, samples, new_samples, 100. * idle / samples);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -142,13 +155,22 @@ struct cpu_prof
|
||||
|
||||
// Print results
|
||||
const std::string results = format(chart, samples, idle);
|
||||
profiler.notice("Thread \"%s\" [0x%08x]: %u samples (%.4f%% idle):%s", ptr->get_name(), ptr->id, samples, 100. * idle / samples, results);
|
||||
profiler.notice("Thread \"%s\" [0x%08x]: %u samples, %u new (%.4f%% idle):%s", ptr->get_name(), ptr->id, samples, new_samples, 100. * idle / samples, results);
|
||||
|
||||
printed = true;
|
||||
new_samples = 0;
|
||||
}
|
||||
|
||||
static void print_all(const std::unordered_map<std::shared_ptr<cpu_thread>, sample_info>& threads)
|
||||
static void print_all(std::unordered_map<std::shared_ptr<cpu_thread>, sample_info>& threads)
|
||||
{
|
||||
u64 new_samples = 0;
|
||||
|
||||
// Print all results and cleanup
|
||||
for (auto& [ptr, info] : threads)
|
||||
{
|
||||
new_samples += info.new_samples;
|
||||
info.print(ptr);
|
||||
}
|
||||
|
||||
std::multimap<u64, u64, std::greater<u64>> chart;
|
||||
|
||||
std::unordered_map<u64, u64, value_hash<u64>> freq;
|
||||
@@ -157,7 +179,7 @@ struct cpu_prof
|
||||
|
||||
for (auto& [_, info] : threads)
|
||||
{
|
||||
// This function collects thread information regardless of 'printed' member state
|
||||
// This function collects thread information regardless of 'new_samples' member state
|
||||
for (auto& [name, count] : info.freq)
|
||||
{
|
||||
freq[name] += count;
|
||||
@@ -167,18 +189,24 @@ struct cpu_prof
|
||||
idle += info.idle;
|
||||
}
|
||||
|
||||
for (auto& [name, count] : freq)
|
||||
{
|
||||
chart.emplace(count, name);
|
||||
}
|
||||
|
||||
if (samples == idle)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (new_samples < min_print_all_samples && thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
profiler.notice("All Threads: %u samples, %u new (%.4f%% idle): Not enough new samples have been collected since the last print.", samples, new_samples, 100. * idle / samples);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto& [name, count] : freq)
|
||||
{
|
||||
chart.emplace(count, name);
|
||||
}
|
||||
|
||||
const std::string results = format(chart, samples, idle, true);
|
||||
profiler.notice("All Threads: %u samples (%.4f%% idle):%s", samples, 100. * idle / samples, results);
|
||||
profiler.notice("All Threads: %u samples, %u new (%.4f%% idle):%s", samples, new_samples, 100. * idle / samples, results);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -239,7 +267,7 @@ struct cpu_prof
|
||||
// Sample active threads
|
||||
for (auto& [ptr, info] : threads)
|
||||
{
|
||||
if (cpu_flag::exit - ptr->state)
|
||||
if (auto state = +ptr->state; cpu_flag::exit - state)
|
||||
{
|
||||
// Get short function hash
|
||||
const u64 name = atomic_storage<u64>::load(ptr->block_hash);
|
||||
@@ -247,9 +275,10 @@ struct cpu_prof
|
||||
// Append occurrence
|
||||
info.samples++;
|
||||
|
||||
if (auto state = +ptr->state; !::is_paused(state) && !::is_stopped(state) && cpu_flag::wait - state)
|
||||
if (cpu_flag::wait - state)
|
||||
{
|
||||
info.freq[name]++;
|
||||
info.new_samples++;
|
||||
|
||||
// Append verification time to fixed common name 0000000...chunk-0x3fffc
|
||||
if (name >> 16 && (name & 0xffff) == 0)
|
||||
@@ -257,6 +286,12 @@ struct cpu_prof
|
||||
}
|
||||
else
|
||||
{
|
||||
if (state & (cpu_flag::dbg_pause + cpu_flag::dbg_global_pause))
|
||||
{
|
||||
// Idle state caused by emulation pause is not accounted for
|
||||
continue;
|
||||
}
|
||||
|
||||
info.idle++;
|
||||
}
|
||||
}
|
||||
@@ -270,11 +305,13 @@ struct cpu_prof
|
||||
{
|
||||
profiler.success("Flushing profiling results...");
|
||||
|
||||
// Print all results and cleanup
|
||||
for (auto& [ptr, info] : threads)
|
||||
{
|
||||
info.print(ptr);
|
||||
}
|
||||
sample_info::print_all(threads);
|
||||
}
|
||||
|
||||
if (Emu.IsPaused())
|
||||
{
|
||||
thread_ctrl::wait_for(5000);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Wait, roughly for 20µs
|
||||
@@ -282,11 +319,6 @@ struct cpu_prof
|
||||
}
|
||||
|
||||
// Print all remaining results
|
||||
for (auto& [ptr, info] : threads)
|
||||
{
|
||||
info.print(ptr);
|
||||
}
|
||||
|
||||
sample_info::print_all(threads);
|
||||
}
|
||||
|
||||
@@ -573,6 +605,7 @@ void cpu_thread::operator()()
|
||||
if (!(state0 & cpu_flag::stop))
|
||||
{
|
||||
cpu_task();
|
||||
state += cpu_flag::wait;
|
||||
|
||||
if (state & cpu_flag::ret && state.test_and_reset(cpu_flag::ret))
|
||||
{
|
||||
@@ -582,7 +615,7 @@ void cpu_thread::operator()()
|
||||
continue;
|
||||
}
|
||||
|
||||
thread_ctrl::wait_on(state, state0);
|
||||
state.wait(state0);
|
||||
|
||||
if (state & cpu_flag::ret && state.test_and_reset(cpu_flag::ret))
|
||||
{
|
||||
@@ -634,20 +667,22 @@ cpu_thread::cpu_thread(u32 id)
|
||||
|
||||
void cpu_thread::cpu_wait(bs_t<cpu_flag> old)
|
||||
{
|
||||
thread_ctrl::wait_on(state, old);
|
||||
state.wait(old);
|
||||
}
|
||||
|
||||
static atomic_t<u32> s_dummy_atomic = 0;
|
||||
|
||||
bool cpu_thread::check_state() noexcept
|
||||
{
|
||||
bool cpu_sleep_called = false;
|
||||
bool cpu_can_stop = true;
|
||||
bool escape, retval;
|
||||
bool escape{}, retval{};
|
||||
|
||||
while (true)
|
||||
{
|
||||
// Process all flags in a single atomic op
|
||||
bs_t<cpu_flag> state1;
|
||||
const auto state0 = state.fetch_op([&](bs_t<cpu_flag>& flags)
|
||||
auto state0 = state.fetch_op([&](bs_t<cpu_flag>& flags)
|
||||
{
|
||||
bool store = false;
|
||||
|
||||
@@ -687,7 +722,6 @@ bool cpu_thread::check_state() noexcept
|
||||
{
|
||||
// Sticky flag, indicates check_state() is not allowed to return true
|
||||
flags -= cpu_flag::temp;
|
||||
flags -= cpu_flag::wait;
|
||||
cpu_can_stop = false;
|
||||
store = true;
|
||||
}
|
||||
@@ -699,6 +733,12 @@ bool cpu_thread::check_state() noexcept
|
||||
store = true;
|
||||
}
|
||||
|
||||
if (flags & cpu_flag::notify)
|
||||
{
|
||||
flags -= cpu_flag::notify;
|
||||
store = true;
|
||||
}
|
||||
|
||||
// Can't process dbg_step if we only paused temporarily
|
||||
if (cpu_can_stop && flags & cpu_flag::dbg_step)
|
||||
{
|
||||
@@ -720,10 +760,10 @@ bool cpu_thread::check_state() noexcept
|
||||
}
|
||||
|
||||
// Atomically clean wait flag and escape
|
||||
if (!(flags & (cpu_flag::exit + cpu_flag::ret + cpu_flag::stop)))
|
||||
if (!is_stopped(flags) && flags.none_of(cpu_flag::ret))
|
||||
{
|
||||
// Check pause flags which hold thread inside check_state (ignore suspend/debug flags on cpu_flag::temp)
|
||||
if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend)))
|
||||
if (flags & (cpu_flag::pause + cpu_flag::memory) || (cpu_can_stop && flags & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause + cpu_flag::suspend + cpu_flag::yield + cpu_flag::preempt)))
|
||||
{
|
||||
if (!(flags & cpu_flag::wait))
|
||||
{
|
||||
@@ -731,6 +771,12 @@ bool cpu_thread::check_state() noexcept
|
||||
store = true;
|
||||
}
|
||||
|
||||
if (flags & (cpu_flag::yield + cpu_flag::preempt) && cpu_can_stop)
|
||||
{
|
||||
flags -= (cpu_flag::yield + cpu_flag::preempt);
|
||||
store = true;
|
||||
}
|
||||
|
||||
escape = false;
|
||||
state1 = flags;
|
||||
return store;
|
||||
@@ -746,9 +792,9 @@ bool cpu_thread::check_state() noexcept
|
||||
}
|
||||
else
|
||||
{
|
||||
if (cpu_can_stop && !(flags & cpu_flag::wait))
|
||||
if (flags & cpu_flag::wait)
|
||||
{
|
||||
flags += cpu_flag::wait;
|
||||
flags -= cpu_flag::wait;
|
||||
store = true;
|
||||
}
|
||||
|
||||
@@ -760,6 +806,34 @@ bool cpu_thread::check_state() noexcept
|
||||
return store;
|
||||
}).first;
|
||||
|
||||
if (state0 & cpu_flag::preempt && cpu_can_stop)
|
||||
{
|
||||
if (cpu_flag::wait - state0)
|
||||
{
|
||||
if (!escape || !retval)
|
||||
{
|
||||
// Yield itself
|
||||
state0 += cpu_flag::yield;
|
||||
escape = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (const u128 bits = s_cpu_bits)
|
||||
{
|
||||
reader_lock lock(s_cpu_lock);
|
||||
|
||||
cpu_counter::for_all_cpu(bits & s_cpu_bits, [](cpu_thread* cpu)
|
||||
{
|
||||
if (cpu->state.none_of(cpu_flag::wait + cpu_flag::yield))
|
||||
{
|
||||
cpu->state += cpu_flag::yield;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (escape)
|
||||
{
|
||||
if (s_tls_thread_slot == umax && !retval)
|
||||
@@ -768,16 +842,16 @@ bool cpu_thread::check_state() noexcept
|
||||
cpu_counter::add(this);
|
||||
}
|
||||
|
||||
constexpr auto pending_and_temp = (cpu_flag::pending + cpu_flag::temp);
|
||||
|
||||
if ((state0 & pending_and_temp) == cpu_flag::pending)
|
||||
if (cpu_can_stop && state0 & cpu_flag::pending)
|
||||
{
|
||||
// Execute pending work
|
||||
cpu_work();
|
||||
|
||||
if ((state1 ^ state) - pending_and_temp)
|
||||
if ((state1 ^ state) - cpu_flag::pending)
|
||||
{
|
||||
// Work could have changed flags
|
||||
// Reset internal flags as if check_state() has just been called
|
||||
cpu_sleep_called = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -848,6 +922,32 @@ bool cpu_thread::check_state() noexcept
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (state0 & cpu_flag::yield && cpu_flag::wait - state0 && cpu_can_stop)
|
||||
{
|
||||
if (auto spu = try_get<spu_thread>())
|
||||
{
|
||||
if (spu->raddr && spu->rtime == vm::reservation_acquire(spu->raddr) && spu->getllar_spin_count < 10)
|
||||
{
|
||||
// Reservation operation is a critical section (but this may result in false positives)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (auto ppu = try_get<ppu_thread>())
|
||||
{
|
||||
if (ppu->raddr && ppu->rtime == vm::reservation_acquire(ppu->raddr))
|
||||
{
|
||||
// Same
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Short sleep when yield flag is present alone (makes no sense when other methods which can stop thread execution have been done)
|
||||
// Pass a mask of a single bit which is often unused to avoid notifications
|
||||
s_dummy_atomic.wait(0, 1u << 30, atomic_wait_timeout{80'000});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -874,8 +974,27 @@ void cpu_thread::notify()
|
||||
|
||||
cpu_thread& cpu_thread::operator=(thread_state)
|
||||
{
|
||||
state += cpu_flag::exit;
|
||||
state.notify_one(cpu_flag::exit);
|
||||
if (state & cpu_flag::exit)
|
||||
{
|
||||
// Must be notified elsewhere or self-raised
|
||||
return *this;
|
||||
}
|
||||
|
||||
const auto old = state.fetch_add(cpu_flag::exit);
|
||||
|
||||
if (old & cpu_flag::wait && old.none_of(cpu_flag::again + cpu_flag::exit))
|
||||
{
|
||||
state.notify_one(cpu_flag::exit);
|
||||
|
||||
if (auto thread = try_get<spu_thread>())
|
||||
{
|
||||
if (u32 resv = atomic_storage<u32>::load(thread->raddr))
|
||||
{
|
||||
vm::reservation_notifier(resv).notify_one();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -945,6 +1064,24 @@ u32* cpu_thread::get_pc2()
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
cpu_thread* cpu_thread::get_next_cpu()
|
||||
{
|
||||
switch (id_type())
|
||||
{
|
||||
case 1:
|
||||
{
|
||||
return static_cast<ppu_thread*>(this)->next_cpu;
|
||||
}
|
||||
case 2:
|
||||
{
|
||||
return static_cast<spu_thread*>(this)->next_cpu;
|
||||
}
|
||||
default: break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<CPUDisAsm> make_disasm(const cpu_thread* cpu);
|
||||
|
||||
void cpu_thread::dump_all(std::string& ret) const
|
||||
@@ -1195,3 +1332,78 @@ u32 CPUDisAsm::DisAsmBranchTarget(s32 /*imm*/)
|
||||
// Unused
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern bool try_lock_spu_threads_in_a_state_compatible_with_savestates(bool revert_lock)
|
||||
{
|
||||
const u64 start = get_system_time();
|
||||
|
||||
// Attempt to lock for half a second, if somehow takes longer abort it
|
||||
do
|
||||
{
|
||||
if (revert_lock)
|
||||
{
|
||||
// Revert the operation of this function
|
||||
break;
|
||||
}
|
||||
|
||||
if (cpu_thread::suspend_all(nullptr, {}, []()
|
||||
{
|
||||
return idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
||||
{
|
||||
if (!spu.unsavable)
|
||||
{
|
||||
if (Emu.IsPaused())
|
||||
{
|
||||
// If emulation is paused, we can only hope it's already in a state compatible with savestates
|
||||
return !!(spu.state & (cpu_flag::dbg_global_pause + cpu_flag::dbg_pause));
|
||||
}
|
||||
else
|
||||
{
|
||||
ensure(!spu.state.test_and_set(cpu_flag::dbg_global_pause));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).ret;
|
||||
}))
|
||||
{
|
||||
if (Emu.IsPaused())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// It's faster to lock once
|
||||
reader_lock lock(id_manager::g_mutex);
|
||||
|
||||
idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
||||
{
|
||||
spu.state -= cpu_flag::dbg_global_pause;
|
||||
}, idm::unlocked);
|
||||
|
||||
// For faster signalling, first remove state flags then batch notifications
|
||||
idm::select<named_thread<spu_thread>>([](u32, spu_thread& spu)
|
||||
{
|
||||
if (spu.state & cpu_flag::wait)
|
||||
{
|
||||
spu.state.notify_one();
|
||||
}
|
||||
}, idm::unlocked);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
return true;
|
||||
} while (std::this_thread::yield(), get_system_time() - start <= 500'000);
|
||||
|
||||
idm::select<named_thread<spu_thread>>([&](u32, named_thread<spu_thread>& spu)
|
||||
{
|
||||
if (spu.state.test_and_reset(cpu_flag::dbg_global_pause))
|
||||
{
|
||||
spu.state.notify_one();
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,14 @@ enum class cpu_flag : u32
|
||||
pause, // Thread suspended by suspend_all technique
|
||||
suspend, // Thread suspended
|
||||
ret, // Callback return requested
|
||||
again, // Thread must complete the syscall after deserialization
|
||||
signal, // Thread received a signal (HLE)
|
||||
memory, // Thread must unlock memory mutex
|
||||
pending, // Thread has postponed work
|
||||
pending_recheck, // Thread needs to recheck if there is pending work before ::pending removal
|
||||
notify, // Flag meant solely to allow atomic notification on state without changing other flags
|
||||
yield, // Thread is being requested to yield its execution time if it's running
|
||||
preempt, // Thread is being requested to preempt the execution of all CPU threads
|
||||
|
||||
dbg_global_pause, // Emulation paused
|
||||
dbg_pause, // Thread paused
|
||||
@@ -34,7 +38,7 @@ enum class cpu_flag : u32
|
||||
// Test stopped state
|
||||
constexpr bool is_stopped(bs_t<cpu_flag> state)
|
||||
{
|
||||
return !!(state & (cpu_flag::stop + cpu_flag::exit));
|
||||
return !!(state & (cpu_flag::stop + cpu_flag::exit + cpu_flag::again));
|
||||
}
|
||||
|
||||
// Test paused state
|
||||
@@ -139,6 +143,7 @@ public:
|
||||
|
||||
u32 get_pc() const;
|
||||
u32* get_pc2(); // Last PC before stepping for the debugger (may be null)
|
||||
cpu_thread* get_next_cpu(); // Access next_cpu member if the is one
|
||||
|
||||
void notify();
|
||||
cpu_thread& operator=(thread_state);
|
||||
@@ -172,7 +177,7 @@ public:
|
||||
virtual void cpu_sleep() {}
|
||||
|
||||
// Callback for cpu_flag::pending
|
||||
virtual void cpu_work() {}
|
||||
virtual void cpu_work() { state -= cpu_flag::pending + cpu_flag::pending_recheck; }
|
||||
|
||||
// Callback for cpu_flag::ret
|
||||
virtual void cpu_return() {}
|
||||
|
||||
@@ -32,7 +32,21 @@ cpu_translator::cpu_translator(llvm::Module* _module, bool is_be)
|
||||
|
||||
if (m_use_ssse3)
|
||||
{
|
||||
#if defined(ARCH_X64)
|
||||
return m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::x86_ssse3_pshuf_b_128), {data0, index});
|
||||
#elif defined(ARCH_ARM64)
|
||||
// Modified from sse2neon
|
||||
// movi v2.16b, #143
|
||||
// and v1.16b, v1.16b, v2.16b
|
||||
// tbl v0.16b, { v0.16b }, v1.16b
|
||||
auto mask = llvm::ConstantInt::get(get_type<u8[16]>(), 0x8F);
|
||||
auto and_mask = llvm::ConstantInt::get(get_type<bool[16]>(), true);
|
||||
auto vec_len = llvm::ConstantInt::get(get_type<u32>(), 16);
|
||||
auto index_masked = m_ir->CreateCall(get_intrinsic<u8[16]>(llvm::Intrinsic::vp_and), {index, mask, and_mask, vec_len});
|
||||
return m_ir->CreateCall(get_intrinsic<u8[16]>(llvm::Intrinsic::aarch64_neon_tbl1), {data0, index_masked});
|
||||
#else
|
||||
#error "Unimplemented"
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -140,6 +154,14 @@ void cpu_translator::initialize(llvm::LLVMContext& context, llvm::ExecutionEngin
|
||||
m_use_avx512_icl = true;
|
||||
m_use_vnni = true;
|
||||
}
|
||||
|
||||
// Aarch64 CPUs
|
||||
if (cpu == "cyclone" || cpu.contains("cortex"))
|
||||
{
|
||||
m_use_fma = true;
|
||||
// AVX does not use intrinsics so far
|
||||
m_use_avx = true;
|
||||
}
|
||||
}
|
||||
|
||||
llvm::Value* cpu_translator::bitcast(llvm::Value* val, llvm::Type* type) const
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#endif
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/sysinfo.hpp"
|
||||
#include "Utilities/StrFmt.h"
|
||||
#include "Utilities/BitField.h"
|
||||
#include "Utilities/JIT.h"
|
||||
@@ -2896,12 +2897,8 @@ protected:
|
||||
bool m_is_be;
|
||||
|
||||
// Allow PSHUFB intrinsic
|
||||
#ifdef ARCH_X64
|
||||
bool m_use_ssse3 = true;
|
||||
#else
|
||||
// TODO: fix the pshufb arm64 native impl using TBL instruction
|
||||
bool m_use_ssse3 = false;
|
||||
#endif
|
||||
|
||||
// Allow FMA
|
||||
bool m_use_fma = false;
|
||||
|
||||
@@ -2979,6 +2976,7 @@ public:
|
||||
m_engine->updateGlobalMapping({lame.data(), lame.size()}, reinterpret_cast<uptr>(_func));
|
||||
|
||||
const auto inst = m_ir->CreateCall(func, {args...});
|
||||
inst->setTailCallKind(llvm::CallInst::TCK_NoTail);
|
||||
#ifdef _WIN32
|
||||
inst->setCallingConv(llvm::CallingConv::Win64);
|
||||
#endif
|
||||
@@ -3445,6 +3443,11 @@ public:
|
||||
template <typename T1, typename T2, typename T3>
|
||||
value_t<u8[16]> vperm2b(T1 a, T2 b, T3 c)
|
||||
{
|
||||
if (!utils::has_fast_vperm2b())
|
||||
{
|
||||
return vperm2b256to128(a, b, c);
|
||||
}
|
||||
|
||||
value_t<u8[16]> result;
|
||||
|
||||
const auto data0 = a.eval(m_ir);
|
||||
|
||||
@@ -39,6 +39,8 @@ public:
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
ENABLE_BITWISE_SERIALIZATION;
|
||||
};
|
||||
|
||||
enum CellNotAnError : s32
|
||||
|
||||
@@ -88,6 +88,8 @@ enum : u32
|
||||
|
||||
struct alignas(16) spu_mfc_cmd
|
||||
{
|
||||
ENABLE_BITWISE_SERIALIZATION;
|
||||
|
||||
MFC cmd;
|
||||
u8 tag;
|
||||
u16 size;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user