mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7093fe962 | ||
|
|
07a24cf71d | ||
|
|
187654eae6 | ||
|
|
276404e19d | ||
|
|
380f18484d | ||
|
|
2844667673 | ||
|
|
da11248a31 | ||
|
|
b3b8e707bc | ||
|
|
32b3e17fd7 | ||
|
|
43dcf6d0ca | ||
|
|
cd829558a8 | ||
|
|
35b374f068 | ||
|
|
3d257cfc0f | ||
|
|
4f310af3e2 | ||
|
|
97d9abd579 | ||
|
|
8c3a017b51 | ||
|
|
7c03c75903 | ||
|
|
ac897144b3 | ||
|
|
d1e1d9975b | ||
|
|
007c4a56a1 | ||
|
|
b27b61f86e | ||
|
|
b13bdd341b | ||
|
|
7c6c26c210 | ||
|
|
6b0d57b32e | ||
|
|
d0f9356199 | ||
|
|
e73a40d9ce | ||
|
|
5a34aeb06f | ||
|
|
99a0fa5dd9 | ||
|
|
65454fef8e | ||
|
|
2e65c8b212 | ||
|
|
91952ae4c1 | ||
|
|
ef63026354 | ||
|
|
da4169148f | ||
|
|
ca3b755fd1 | ||
|
|
5c810c72c2 | ||
|
|
c1781b95cb | ||
|
|
fb5045d086 | ||
|
|
4c080066cf | ||
|
|
044ba9cb03 | ||
|
|
5dee5bc42e | ||
|
|
1b9ab35ac0 | ||
|
|
26c39e1f55 | ||
|
|
8bc7ca307c | ||
|
|
93110e899f | ||
|
|
4b27b585f5 | ||
|
|
bbf44c684c | ||
|
|
f823f6fd66 | ||
|
|
9a12b5e958 | ||
|
|
d094ecd491 | ||
|
|
fda4cc3b50 | ||
|
|
00ce69a315 | ||
|
|
422d831eff | ||
|
|
55a54c924e | ||
|
|
19d23eb691 | ||
|
|
884cb47dde | ||
|
|
3c15df4e4d | ||
|
|
9b33316982 | ||
|
|
719cf8a54a | ||
|
|
1b879360b8 | ||
|
|
ad059d03af | ||
|
|
78ba581137 | ||
|
|
cd044148be | ||
|
|
91cd82a0c2 | ||
|
|
e7c3d6ab26 | ||
|
|
a88331bb22 | ||
|
|
6892ee4c2e | ||
|
|
0059e4e92e | ||
|
|
46b7428fad | ||
|
|
582b5ba29e | ||
|
|
cb175278b6 | ||
|
|
82164a54c1 | ||
|
|
6161ecd7aa | ||
|
|
55a35b5e1d | ||
|
|
8caacc8231 | ||
|
|
0ded153216 | ||
|
|
4baefed106 | ||
|
|
424514fde6 | ||
|
|
de33fda28c | ||
|
|
37a5d118be | ||
|
|
301f45a2cb | ||
|
|
aa25da4ce2 | ||
|
|
e35bd463cf | ||
|
|
f9f88aa9e5 | ||
|
|
968b892e29 | ||
|
|
89c6d08ed3 | ||
|
|
7811cffeed | ||
|
|
20c854aeb3 | ||
|
|
7952244052 | ||
|
|
4179f23e20 | ||
|
|
3be5aa99cc | ||
|
|
3e68a7f385 | ||
|
|
9a4b849260 | ||
|
|
9a24c8d11f | ||
|
|
0617eff348 | ||
|
|
003b368980 | ||
|
|
6f5f198acd | ||
|
|
107b751a43 | ||
|
|
4475671bbf | ||
|
|
7e35f59997 | ||
|
|
970d745818 | ||
|
|
adec3ae7f9 | ||
|
|
5e2d0eb762 | ||
|
|
26782525f4 | ||
|
|
d0e6d4eefc | ||
|
|
f19d398bec | ||
|
|
6b80ac4805 | ||
|
|
13ceef9f46 | ||
|
|
ffc50905a6 | ||
|
|
4b2b8438be | ||
|
|
7b49e1fcea | ||
|
|
d9957c56ae | ||
|
|
62d8208c71 | ||
|
|
cab4f2507c | ||
|
|
cbc7b60ba5 | ||
|
|
47efd770a8 | ||
|
|
f53547b173 | ||
|
|
cb5b866c61 | ||
|
|
2f3c0f04d2 | ||
|
|
3f493fb209 | ||
|
|
f7eb0d8d76 | ||
|
|
27e9d11b80 | ||
|
|
6e731093c4 | ||
|
|
fc93d932c8 | ||
|
|
89b2128679 | ||
|
|
33343cd153 | ||
|
|
6cd7866553 | ||
|
|
6a5ad70ec7 | ||
|
|
c990f34d5f | ||
|
|
bbaebe47a4 | ||
|
|
e480c291da | ||
|
|
7d25a7086e | ||
|
|
dbbb6fbde0 | ||
|
|
d069a55acc | ||
|
|
614bf8b718 | ||
|
|
cce09dbb39 | ||
|
|
b5a715adcf | ||
|
|
eb54f9b75a | ||
|
|
3cbf9b8b6c | ||
|
|
0a9fd15b57 | ||
|
|
0821bbf956 | ||
|
|
b29810d1a5 | ||
|
|
5ef731c9e5 | ||
|
|
002a9b274a | ||
|
|
12b1efc266 | ||
|
|
4c63acfb40 | ||
|
|
3f4364fe74 | ||
|
|
ee43ab7362 | ||
|
|
2dc6cf014c | ||
|
|
c285c2fb41 | ||
|
|
c41595e79f | ||
|
|
bf541b5828 | ||
|
|
41e2d101e7 | ||
|
|
c27b38f300 | ||
|
|
5bd7fd7817 | ||
|
|
7f9b8d23cd | ||
|
|
01e3fed466 | ||
|
|
ea0e10e704 | ||
|
|
c2eb11eae6 | ||
|
|
26e37d8c8c | ||
|
|
9b1eb45a47 | ||
|
|
92870a3d4e | ||
|
|
f7cfdc6570 | ||
|
|
a603fbba8c | ||
|
|
db907a2586 | ||
|
|
b9cee7a3a9 | ||
|
|
d7c15851b4 | ||
|
|
a723152dea | ||
|
|
2f4034590f | ||
|
|
7b6a8cc2d6 | ||
|
|
6df35891ad | ||
|
|
eaef23d43a | ||
|
|
8cde4b2153 | ||
|
|
ed3af84437 | ||
|
|
81b85e55e4 | ||
|
|
9af11f5cf1 | ||
|
|
d6d5c60823 | ||
|
|
0230580a88 | ||
|
|
ae98583ed2 | ||
|
|
628ea5ec15 | ||
|
|
400d9a1c24 | ||
|
|
58ef670992 | ||
|
|
de13ae4753 | ||
|
|
f48ca59235 | ||
|
|
804e06356b | ||
|
|
70e3e15e2f | ||
|
|
852407b8cb | ||
|
|
f945ab62c0 | ||
|
|
ec208289fa | ||
|
|
b93c4733a8 | ||
|
|
6d42df0ffc | ||
|
|
502ea1f436 | ||
|
|
8d034a36e8 | ||
|
|
75fea2216b | ||
|
|
3d587726a2 | ||
|
|
f3f52feddf |
+2
-2
@@ -37,7 +37,7 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then
|
||||
|
||||
# Download translations
|
||||
mkdir -p "./AppDir/usr/translations"
|
||||
ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
| grep "browser_download_url" \
|
||||
| grep "RPCS3-languages.zip" \
|
||||
| cut -d '"' -f 4)
|
||||
@@ -45,7 +45,7 @@ if [ "$DEPLOY_APPIMAGE" = "true" ]; then
|
||||
echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations."
|
||||
else
|
||||
echo "Downloading translations from: $ZIP_URL"
|
||||
curl -L -o translations.zip "$ZIP_URL" || {
|
||||
curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || {
|
||||
echo "Failed to download translations.zip. Continuing without translations."
|
||||
exit 0
|
||||
}
|
||||
|
||||
+1
-1
@@ -29,7 +29,7 @@ rm -rf "rpcs3.app/Contents/Frameworks/QtPdf.framework" \
|
||||
mkdir -p "rpcs3.app/Contents/translations"
|
||||
ZIP_URL="https://github.com/RPCS3/rpcs3_translations/releases/latest/download/RPCS3-languages.zip"
|
||||
echo "Downloading translations from: $ZIP_URL"
|
||||
if curl -fsSL "$ZIP_URL" -o "translations.zip"; then
|
||||
if curl -fsSL --retry 3 --retry-delay 60 "$ZIP_URL" -o "translations.zip"; then
|
||||
echo "Successfully downloaded translations."
|
||||
if unzip -o translations.zip -d "rpcs3.app/Contents/translations" >/dev/null 2>&1; then
|
||||
rm -f translations.zip
|
||||
|
||||
@@ -28,7 +28,7 @@ curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -f ISO-8859-1 -t UTF-8
|
||||
|
||||
# Download translations
|
||||
mkdir -p ./bin/share/qt6/translations
|
||||
ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
| grep "browser_download_url" \
|
||||
| grep "RPCS3-languages.zip" \
|
||||
| cut -d '"' -f 4)
|
||||
@@ -36,7 +36,7 @@ if [ -z "$ZIP_URL" ]; then
|
||||
echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations."
|
||||
else
|
||||
echo "Downloading translations from: $ZIP_URL"
|
||||
curl -L -o translations.zip "$ZIP_URL" || {
|
||||
curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || {
|
||||
echo "Failed to download translations.zip. Continuing without translations."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ curl -fsSL 'https://api.rpcs3.net/config/?api=v1' | iconv -t UTF-8 1> ./bin/GuiC
|
||||
|
||||
# Download translations
|
||||
mkdir -p ./bin/qt6/translations
|
||||
ZIP_URL=$(curl -fsSL "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
ZIP_URL=$(curl -fsSL --retry 3 --retry-delay 60 "https://api.github.com/repos/RPCS3/rpcs3_translations/releases/latest" \
|
||||
| grep "browser_download_url" \
|
||||
| grep "RPCS3-languages.zip" \
|
||||
| cut -d '"' -f 4)
|
||||
@@ -26,7 +26,7 @@ if [ -z "$ZIP_URL" ]; then
|
||||
echo "Failed to find RPCS3-languages.zip in the latest release. Continuing without translations."
|
||||
else
|
||||
echo "Downloading translations from: $ZIP_URL"
|
||||
curl -L -o translations.zip "$ZIP_URL" || {
|
||||
curl -fsSL --retry 3 --retry-delay 60 -o translations.zip "$ZIP_URL" || {
|
||||
echo "Failed to download translations.zip. Continuing without translations."
|
||||
exit 0
|
||||
}
|
||||
|
||||
@@ -30,23 +30,23 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-24.04
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:2.0"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:2.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
compiler: clang
|
||||
UPLOAD_COMMIT_HASH: d812f1254a1157c80fd402f94446310560f54e5f
|
||||
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux"
|
||||
- os: ubuntu-24.04
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:2.0"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy:2.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux.sh"
|
||||
compiler: gcc
|
||||
- os: ubuntu-24.04-arm
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.0"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
|
||||
compiler: clang
|
||||
UPLOAD_COMMIT_HASH: a1d35836e8d45bfc6f63c26f0a3e5d46ef622fe1
|
||||
UPLOAD_REPO_FULL_NAME: "rpcs3/rpcs3-binaries-linux-arm64"
|
||||
- os: ubuntu-24.04-arm
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.0"
|
||||
docker_img: "rpcs3/rpcs3-ci-jammy-aarch64:2.1"
|
||||
build_sh: "/rpcs3/.ci/build-linux-aarch64.sh"
|
||||
compiler: gcc
|
||||
name: RPCS3 Linux ${{ matrix.os }} ${{ matrix.compiler }}
|
||||
|
||||
Vendored
+9
@@ -373,6 +373,15 @@ add_subdirectory(fusion EXCLUDE_FROM_ALL)
|
||||
# FERAL INTERACTIVE
|
||||
add_subdirectory(feralinteractive EXCLUDE_FROM_ALL)
|
||||
|
||||
# LSFG: Lossless Scaling frame generation. Android only, and deliberately NOT EXCLUDE_FROM_ALL --
|
||||
# libarmsx3_lsfg.so has to be built and packaged even though nothing links it, because the core
|
||||
# reaches it by dlopen rather than by linking. Marking it excluded produces a build that succeeds
|
||||
# and an APK with no frame generation in it.
|
||||
#
|
||||
# The subdir returns immediately when the submodule is absent, so a checkout without it still
|
||||
# builds; frame generation simply reports itself unavailable at runtime.
|
||||
add_subdirectory(lsfg)
|
||||
|
||||
# add nice ALIAS targets for ease of use
|
||||
if(USE_SYSTEM_LIBUSB)
|
||||
add_library(3rdparty::libusb ALIAS usb-1.0-shared)
|
||||
|
||||
Vendored
+1
-1
Submodule 3rdparty/discord-rpc/discord-rpc updated: 3dc2c326cb...fb04b1766b
Vendored
+1
-1
Submodule 3rdparty/libsdl-org/SDL updated: f87239e71e...147a8ee32d
@@ -0,0 +1,46 @@
|
||||
diff --git a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
|
||||
index d89a972f5d..f64e551a51 100644
|
||||
--- a/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
|
||||
+++ b/llvm/lib/Target/AArch64/AArch64FrameLowering.cpp
|
||||
@@ -2504,8 +2504,40 @@ void AArch64FrameLowering::determineCalleeSaves(MachineFunction &MF,
|
||||
RegScavenger *RS) const {
|
||||
// All calls are tail calls in GHC calling conv, and functions have no
|
||||
// prologue/epilogue.
|
||||
- if (MF.getFunction().getCallingConv() == CallingConv::GHC)
|
||||
+ if (MF.getFunction().getCallingConv() == CallingConv::GHC) {
|
||||
+ // ...but they can still need an emergency spill slot.
|
||||
+ //
|
||||
+ // Returning here skips every path below that reserves one, so a GHC function never gets
|
||||
+ // a scavenging frame index on AArch64. That is safe only while the premise holds. It
|
||||
+ // stops holding as soon as the allocator spills: the function then has real stack
|
||||
+ // objects, eliminateFrameIndex may need a scratch register to materialise an offset,
|
||||
+ // and GHC has reserved nearly every GPR, so there is no free register to take and no
|
||||
+ // slot to spill one into. The scavenger then aborts the whole module with
|
||||
+ // "Cannot scavenge register without an emergency spill slot".
|
||||
+ //
|
||||
+ // Reproduced with RPCS3's PPU recompiler, which emits ghccc for every guest function.
|
||||
+ // A single function of Saint Seiya: The Sanctuary (BLES01421) fails this way, and losing
|
||||
+ // it costs the entire module, whose functions then fall back to an interpreter loop. The
|
||||
+ // failure needs ghccc AND a scheduling model that pushes pressure over the line (it
|
||||
+ // reproduces on cortex-x1/x2/x3 and cortex-a55, not on cortex-a76/a78/generic) AND -O2;
|
||||
+ // remove any one and the same function compiles.
|
||||
+ //
|
||||
+ // Gated on the function actually having a frame, so a GHC function with no stack objects
|
||||
+ // still gets no prologue and nothing changes for it. The cost where it does apply is one
|
||||
+ // 8-byte slot.
|
||||
+ MachineFrameInfo &GHCMFI = MF.getFrameInfo();
|
||||
+
|
||||
+ if (RS && GHCMFI.estimateStackSize(MF) > 0) {
|
||||
+ const TargetRegisterInfo *TRI = MF.getSubtarget().getRegisterInfo();
|
||||
+ const TargetRegisterClass &RC = AArch64::GPR64RegClass;
|
||||
+ int FI = GHCMFI.CreateSpillStackObject(TRI->getSpillSize(RC), TRI->getSpillAlign(RC));
|
||||
+ RS->addScavengingFrameIndex(FI);
|
||||
+ LLVM_DEBUG(dbgs() << "GHC function with a frame, allocated fi#" << FI
|
||||
+ << " as the emergency spill slot.\n");
|
||||
+ }
|
||||
+
|
||||
return;
|
||||
+ }
|
||||
|
||||
const AArch64Subtarget &Subtarget = MF.getSubtarget<AArch64Subtarget>();
|
||||
|
||||
Vendored
+147
@@ -0,0 +1,147 @@
|
||||
# libarmsx3_lsfg.so -- Lossless Scaling frame generation, sealed away from the emulator core.
|
||||
#
|
||||
# The entire reason this is a separate shared object is symbol collision. volk defines 655 globals
|
||||
# named vkCreateImage, vkQueueSubmit, ... and all 124 that our Vulkan loader declares in
|
||||
# rpcs3/Emu/RSX/VK/vk_android_loader.h are among them. Linked into libarmsx3-core.so this either
|
||||
# fails at link or, worse, merges -- and framegen's volkLoadDevice(itsOwnDevice) then repoints the
|
||||
# whole RSX renderer at framegen's VkDevice. See armsx3_lsfg_shim.h.
|
||||
#
|
||||
# Android only. framegen's non-Android path shares images by FD, which Adreno and Mali refuse for
|
||||
# AHB-imported memory, so there is nothing here worth building for desktop.
|
||||
|
||||
if (NOT ANDROID)
|
||||
return()
|
||||
endif()
|
||||
|
||||
set(LSFG_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/lsfg-vk-android")
|
||||
|
||||
if (NOT EXISTS "${LSFG_ROOT}/framegen/CMakeLists.txt")
|
||||
message(STATUS "LSFG: 3rdparty/lsfg/lsfg-vk-android is missing, frame generation will not be built")
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT EXISTS "${LSFG_ROOT}/thirdparty/volk/volk.c")
|
||||
# Called out explicitly because the failure is otherwise mystifying: framegen links volk
|
||||
# PUBLIC, so without it the error names framegen rather than the submodule that is missing.
|
||||
message(STATUS "LSFG: thirdparty/volk is missing (git submodule update --init), skipping")
|
||||
return()
|
||||
endif()
|
||||
|
||||
# volk, built for Android.
|
||||
#
|
||||
# VK_USE_PLATFORM_ANDROID_KHR has to be set on VOLK ITSELF, not only on framegen. Without it volk
|
||||
# never defines vkGetAndroidHardwareBufferPropertiesANDROID, and the resulting undefined symbol
|
||||
# points at framegen -- sending you to debug the wrong target entirely.
|
||||
add_library(armsx3_lsfg_volk STATIC "${LSFG_ROOT}/thirdparty/volk/volk.c")
|
||||
target_include_directories(armsx3_lsfg_volk PUBLIC "${LSFG_ROOT}/thirdparty/volk")
|
||||
target_compile_definitions(armsx3_lsfg_volk PUBLIC VK_USE_PLATFORM_ANDROID_KHR VK_NO_PROTOTYPES)
|
||||
set_target_properties(armsx3_lsfg_volk PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
C_VISIBILITY_PRESET hidden)
|
||||
|
||||
# framegen.
|
||||
#
|
||||
# Its own CMakeLists expects a target called `volk`, so alias ours rather than patching upstream.
|
||||
if (NOT TARGET volk)
|
||||
add_library(volk ALIAS armsx3_lsfg_volk)
|
||||
endif()
|
||||
|
||||
add_subdirectory("${LSFG_ROOT}/framegen" "${CMAKE_CURRENT_BINARY_DIR}/framegen" EXCLUDE_FROM_ALL)
|
||||
|
||||
# Undo the project-wide -fno-exceptions for framegen and the shim.
|
||||
#
|
||||
# The top-level build sets it with add_compile_options, which every later add_subdirectory
|
||||
# inherits. framegen has dozens of throw sites and they do not warn -- they fail to compile. The
|
||||
# shim needs exceptions for the opposite reason: it exists to CATCH them so none reach the dlopen
|
||||
# boundary.
|
||||
foreach (tgt lsfg-vk-framegen)
|
||||
if (TARGET ${tgt})
|
||||
target_compile_options(${tgt} PRIVATE -fexceptions)
|
||||
set_target_properties(${tgt} PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON)
|
||||
# PRIVATE, not PUBLIC: leaking this onto consumers collides with the valueless #define
|
||||
# our own Vulkan headers use, in hundreds of RSX translation units.
|
||||
target_compile_definitions(${tgt} PRIVATE VK_USE_PLATFORM_ANDROID_KHR)
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Shader extraction: DXBC out of the user's own Lossless.dll, translated to SPIR-V.
|
||||
#
|
||||
# framegen asks for SPIR-V by name and does not read the DLL itself, so this chain is the caller's
|
||||
# responsibility. Building upstream's own libraries rather than writing a DXBC translator: dxbc is
|
||||
# DXVK's, and reimplementing it would be absurd.
|
||||
#
|
||||
# Optional. Without these the library still builds and frame generation still reports itself
|
||||
# available -- it just cannot initialize until shaders exist, which is also what happens when the
|
||||
# user has not supplied a DLL.
|
||||
set(LSFG_HAS_EXTRACT OFF)
|
||||
|
||||
if (EXISTS "${LSFG_ROOT}/thirdparty/dxbc/CMakeLists.txt" AND
|
||||
EXISTS "${LSFG_ROOT}/thirdparty/pe-parse/CMakeLists.txt")
|
||||
|
||||
# pe-parse defaults to a shared library and command-line tools, neither of which belongs in
|
||||
# an APK. Forced here because its options are plain option(), so they take whatever is
|
||||
# already in the cache unless overridden.
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
|
||||
set(BUILD_COMMAND_LINE_TOOLS OFF CACHE BOOL "" FORCE)
|
||||
set(PEPARSE_ENABLE_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(PEPARSE_ENABLE_TESTING OFF CACHE BOOL "" FORCE)
|
||||
|
||||
add_subdirectory("${LSFG_ROOT}/thirdparty/dxbc" "${CMAKE_CURRENT_BINARY_DIR}/dxbc" EXCLUDE_FROM_ALL)
|
||||
add_subdirectory("${LSFG_ROOT}/thirdparty/pe-parse" "${CMAKE_CURRENT_BINARY_DIR}/pe-parse" EXCLUDE_FROM_ALL)
|
||||
|
||||
foreach (tgt dxbc pe-parse)
|
||||
if (TARGET ${tgt})
|
||||
# Same -fno-exceptions problem as framegen: both throw, and inheriting the
|
||||
# project-wide flag turns that into a compile error rather than a warning.
|
||||
target_compile_options(${tgt} PRIVATE -fexceptions)
|
||||
set_target_properties(${tgt} PROPERTIES
|
||||
POSITION_INDEPENDENT_CODE ON
|
||||
CXX_VISIBILITY_PRESET hidden)
|
||||
set(LSFG_HAS_EXTRACT ON)
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
add_library(armsx3_lsfg SHARED armsx3_lsfg_shim.cpp)
|
||||
|
||||
target_include_directories(armsx3_lsfg PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${LSFG_ROOT}/framegen/public")
|
||||
|
||||
target_compile_options(armsx3_lsfg PRIVATE -fexceptions)
|
||||
target_compile_definitions(armsx3_lsfg PRIVATE VK_USE_PLATFORM_ANDROID_KHR)
|
||||
|
||||
set_target_properties(armsx3_lsfg PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_STANDARD_REQUIRED ON
|
||||
CXX_VISIBILITY_PRESET hidden
|
||||
VISIBILITY_INLINES_HIDDEN ON
|
||||
OUTPUT_NAME "armsx3_lsfg")
|
||||
|
||||
target_link_libraries(armsx3_lsfg PRIVATE lsfg-vk-framegen armsx3_lsfg_volk android log)
|
||||
|
||||
if (LSFG_HAS_EXTRACT)
|
||||
target_sources(armsx3_lsfg PRIVATE
|
||||
"${LSFG_ROOT}/src/extract/trans.cpp"
|
||||
"${LSFG_ROOT}/src/extract/extract.cpp")
|
||||
target_include_directories(armsx3_lsfg PRIVATE "${LSFG_ROOT}/include")
|
||||
target_link_libraries(armsx3_lsfg PRIVATE dxbc pe-parse)
|
||||
target_compile_definitions(armsx3_lsfg PRIVATE ARMSX3_LSFG_HAVE_EXTRACT=1)
|
||||
message(STATUS "LSFG: shader extraction enabled (dxbc + pe-parse)")
|
||||
else()
|
||||
message(STATUS "LSFG: shader extraction NOT available, frame generation cannot initialize")
|
||||
endif()
|
||||
|
||||
# Keep the exported surface to the shim alone.
|
||||
#
|
||||
# The version script is what makes the isolation real rather than aspirational: without it,
|
||||
# framegen's and volk's symbols are still dynamic and the loader can bind our renderer's vk* to
|
||||
# them. Verify with:
|
||||
# llvm-nm --defined-only --extern-only libarmsx3_lsfg.so
|
||||
# Only armsx3_lsfg_* may appear. Any vk* or LSFG_3_1 symbol means this stopped working.
|
||||
target_link_options(armsx3_lsfg PRIVATE
|
||||
"-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/armsx3_lsfg.map"
|
||||
"-Wl,--no-undefined")
|
||||
Vendored
+32
@@ -0,0 +1,32 @@
|
||||
/* Exported surface of libarmsx3_lsfg.so.
|
||||
*
|
||||
* This list IS the isolation. framegen and volk are statically linked into this library and
|
||||
* between them define 655 globals named vkCreateImage, vkQueueSubmit, ... -- 124 of which are
|
||||
* exactly the names libarmsx3-core.so's Vulkan loader declares. If any of those stay dynamic,
|
||||
* the loader is free to bind the renderer's entry points to framegen's copies, and framegen's
|
||||
* volkLoadDevice() has already pointed those at a different VkDevice.
|
||||
*
|
||||
* -fvisibility=hidden covers most of it; this covers the rest, including anything upstream marks
|
||||
* __attribute__((visibility("default"))) -- which framegen's public API does.
|
||||
*
|
||||
* Check it, do not assume it:
|
||||
* llvm-nm --defined-only --extern-only libarmsx3_lsfg.so
|
||||
* Nothing but armsx3_lsfg_* should be listed.
|
||||
*/
|
||||
{
|
||||
global:
|
||||
armsx3_lsfg_abi_version;
|
||||
armsx3_lsfg_initialize;
|
||||
armsx3_lsfg_create_context_ahb;
|
||||
armsx3_lsfg_present;
|
||||
armsx3_lsfg_destroy_context;
|
||||
armsx3_lsfg_wait_idle;
|
||||
armsx3_lsfg_finalize;
|
||||
armsx3_lsfg_last_error;
|
||||
armsx3_lsfg_import_shaders;
|
||||
armsx3_lsfg_shader_count;
|
||||
armsx3_lsfg_get_shader;
|
||||
|
||||
local:
|
||||
*;
|
||||
};
|
||||
Vendored
+404
@@ -0,0 +1,404 @@
|
||||
// Implementation of the C ABI in armsx3_lsfg_shim.h.
|
||||
//
|
||||
// This translation unit is the ONLY thing in libarmsx3_lsfg.so that anyone outside it may touch.
|
||||
// Everything else -- framegen, volk, and volk's 655 vk* globals -- stays hidden behind
|
||||
// -fvisibility=hidden so the dynamic linker cannot bind our renderer's vkCmdDraw to framegen's
|
||||
// copy. See the header for why that matters.
|
||||
//
|
||||
// Rules for every entry point here:
|
||||
// * no C++ type crosses the boundary (separate libc++ per .so under c++_static),
|
||||
// * no exception crosses the boundary (framegen throws; dlopen'd code must not),
|
||||
// * a failure returns a code and leaves a message in armsx3_lsfg_last_error().
|
||||
|
||||
#include "armsx3_lsfg_shim.h"
|
||||
|
||||
#include <lsfg_3_1.hpp>
|
||||
#include <lsfg_3_1p.hpp>
|
||||
|
||||
#ifdef ARMSX3_LSFG_HAVE_EXTRACT
|
||||
#include <extract/extract.hpp>
|
||||
#include <extract/trans.hpp>
|
||||
#include <config/config.hpp>
|
||||
#endif
|
||||
|
||||
#include <exception>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
// thread_local because the renderer and whatever calls initialize() are not the same thread,
|
||||
// and a shared buffer would let one overwrite the other's message mid-report.
|
||||
thread_local std::string g_last_error;
|
||||
|
||||
bool g_initialized = false;
|
||||
|
||||
// Which shader family initialize() chose. Fixed until finalize(): LSFG_3_1 and LSFG_3_1P keep
|
||||
// entirely separate device state and context tables, so a context created by one cannot be
|
||||
// presented or destroyed through the other -- every entry point below has to dispatch on this.
|
||||
bool g_performance = false;
|
||||
|
||||
void clear_error()
|
||||
{
|
||||
g_last_error.clear();
|
||||
}
|
||||
|
||||
void set_error(const char* what)
|
||||
{
|
||||
g_last_error = what ? what : "unknown error";
|
||||
}
|
||||
|
||||
void set_error(const std::string& what)
|
||||
{
|
||||
g_last_error = what.empty() ? "unknown error" : what;
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap a call so nothing escapes.
|
||||
//
|
||||
// catch (...) rather than catching LSFG's types: framegen throws several, they are not part of
|
||||
// its public header, and an exception reaching the dlopen boundary is undefined behaviour -- so
|
||||
// the exact type matters less than the guarantee that none of them get out.
|
||||
#define ARMSX3_LSFG_GUARD(expr, failure_result) \
|
||||
try \
|
||||
{ \
|
||||
clear_error(); \
|
||||
expr; \
|
||||
} \
|
||||
catch (const std::exception& e) \
|
||||
{ \
|
||||
set_error(e.what()); \
|
||||
return (failure_result); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ \
|
||||
set_error("unknown exception from framegen"); \
|
||||
return (failure_result); \
|
||||
}
|
||||
|
||||
extern "C" uint32_t armsx3_lsfg_abi_version(void)
|
||||
{
|
||||
return ARMSX3_LSFG_ABI_VERSION;
|
||||
}
|
||||
|
||||
extern "C" const char* armsx3_lsfg_last_error(void)
|
||||
{
|
||||
return g_last_error.c_str();
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_initialize(uint64_t device_uuid, int is_hdr, float flow_scale,
|
||||
uint64_t generation_count, int performance, armsx3_lsfg_shader_loader loader, void* user)
|
||||
{
|
||||
if (!loader)
|
||||
{
|
||||
set_error("no shader loader supplied");
|
||||
return ARMSX3_LSFG_ERR_BAD_ARGUMENT;
|
||||
}
|
||||
|
||||
// The std::function is built HERE, on framegen's side of the boundary, from a plain C
|
||||
// function pointer. That is the whole point of taking a function pointer in the header: an
|
||||
// std::function constructed by the core would be a different type under a different libc++.
|
||||
//
|
||||
// Throwing out of this lambda is how a missing shader is reported to framegen, which is what
|
||||
// it expects -- and the throw stays inside this .so, caught by the guard below.
|
||||
const auto bridge = [loader, user](const std::string& name) -> std::vector<uint8_t>
|
||||
{
|
||||
const uint8_t* data = nullptr;
|
||||
uint32_t size = 0;
|
||||
|
||||
if (loader(name.c_str(), &data, &size, user) != ARMSX3_LSFG_OK || !data || !size)
|
||||
{
|
||||
throw std::runtime_error("shader not available: " + name);
|
||||
}
|
||||
|
||||
return std::vector<uint8_t>(data, data + size);
|
||||
};
|
||||
|
||||
// Recorded BEFORE the call so the guard's failure path cannot leave the two disagreeing.
|
||||
g_performance = performance != 0;
|
||||
|
||||
if (g_performance)
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(
|
||||
LSFG_3_1P::initialize(device_uuid, is_hdr != 0, flow_scale, generation_count, bridge),
|
||||
ARMSX3_LSFG_ERR_SHADERS)
|
||||
}
|
||||
else
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(
|
||||
LSFG_3_1::initialize(device_uuid, is_hdr != 0, flow_scale, generation_count, bridge),
|
||||
ARMSX3_LSFG_ERR_SHADERS)
|
||||
}
|
||||
|
||||
g_initialized = true;
|
||||
return ARMSX3_LSFG_OK;
|
||||
}
|
||||
|
||||
extern "C" int32_t armsx3_lsfg_create_context_ahb(void* in0, void* in1, void* const* out_n,
|
||||
uint32_t out_count, uint32_t width, uint32_t height, int32_t format)
|
||||
{
|
||||
if (!g_initialized)
|
||||
{
|
||||
set_error("not initialized");
|
||||
return ARMSX3_LSFG_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
if (!in0 || !in1 || !out_n || !out_count)
|
||||
{
|
||||
set_error("null image or empty output set");
|
||||
return ARMSX3_LSFG_ERR_BAD_ARGUMENT;
|
||||
}
|
||||
|
||||
int32_t id = ARMSX3_LSFG_ERR_UNKNOWN;
|
||||
|
||||
// AHardwareBuffer* arrives as void* so the header stays free of android/hardware_buffer.h,
|
||||
// which the core has no reason to include.
|
||||
std::vector<AHardwareBuffer*> outs;
|
||||
outs.reserve(out_count);
|
||||
|
||||
for (uint32_t i = 0; i < out_count; ++i)
|
||||
{
|
||||
outs.push_back(static_cast<AHardwareBuffer*>(out_n[i]));
|
||||
}
|
||||
|
||||
if (g_performance)
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(
|
||||
id = LSFG_3_1P::createContextFromAHB(
|
||||
static_cast<AHardwareBuffer*>(in0), static_cast<AHardwareBuffer*>(in1), outs,
|
||||
VkExtent2D{width, height}, static_cast<VkFormat>(format)),
|
||||
ARMSX3_LSFG_ERR_VULKAN)
|
||||
}
|
||||
else
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(
|
||||
id = LSFG_3_1::createContextFromAHB(
|
||||
static_cast<AHardwareBuffer*>(in0), static_cast<AHardwareBuffer*>(in1), outs,
|
||||
VkExtent2D{width, height}, static_cast<VkFormat>(format)),
|
||||
ARMSX3_LSFG_ERR_VULKAN)
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_present(int32_t ctx, int in_sem, const int* out_sems, uint32_t out_count)
|
||||
{
|
||||
if (!g_initialized)
|
||||
{
|
||||
set_error("not initialized");
|
||||
return ARMSX3_LSFG_ERR_NOT_INITIALIZED;
|
||||
}
|
||||
|
||||
std::vector<int> outs;
|
||||
outs.reserve(out_count);
|
||||
|
||||
for (uint32_t i = 0; i < out_count; ++i)
|
||||
{
|
||||
outs.push_back(out_sems ? out_sems[i] : -1);
|
||||
}
|
||||
|
||||
if (g_performance)
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(LSFG_3_1P::presentContext(ctx, in_sem, outs), ARMSX3_LSFG_ERR_VULKAN)
|
||||
}
|
||||
else
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(LSFG_3_1::presentContext(ctx, in_sem, outs), ARMSX3_LSFG_ERR_VULKAN)
|
||||
}
|
||||
|
||||
return ARMSX3_LSFG_OK;
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_destroy_context(int32_t ctx)
|
||||
{
|
||||
if (!g_initialized)
|
||||
{
|
||||
return ARMSX3_LSFG_OK; // nothing to release
|
||||
}
|
||||
|
||||
if (g_performance)
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(LSFG_3_1P::deleteContext(ctx), ARMSX3_LSFG_ERR_UNKNOWN)
|
||||
}
|
||||
else
|
||||
{
|
||||
ARMSX3_LSFG_GUARD(LSFG_3_1::deleteContext(ctx), ARMSX3_LSFG_ERR_UNKNOWN)
|
||||
}
|
||||
|
||||
return ARMSX3_LSFG_OK;
|
||||
}
|
||||
|
||||
extern "C" void armsx3_lsfg_wait_idle(void)
|
||||
{
|
||||
if (!g_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (g_performance) LSFG_3_1P::waitIdle(); else LSFG_3_1::waitIdle();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
// Deliberately swallowed and not recorded: this is called on the present path, and a
|
||||
// failure to wait is reported by whatever uses the images next. Setting the error string
|
||||
// here would overwrite a more useful message from the call that actually failed.
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" void armsx3_lsfg_finalize(void)
|
||||
{
|
||||
if (!g_initialized)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (g_performance) LSFG_3_1P::finalize(); else LSFG_3_1::finalize();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
}
|
||||
|
||||
g_initialized = false;
|
||||
}
|
||||
|
||||
#ifdef ARMSX3_LSFG_HAVE_EXTRACT
|
||||
// Satisfy the one symbol upstream's extract.cpp needs from its config layer.
|
||||
//
|
||||
// It reads exactly one field, Config::activeConf.dll, to find the file. Defining the object here
|
||||
// rather than compiling their config module avoids dragging in toml11 and a config-file format
|
||||
// that has no meaning inside an APK -- the path comes from the user's file picker instead.
|
||||
namespace Config { Configuration activeConf; }
|
||||
|
||||
namespace
|
||||
{
|
||||
// name -> SPIR-V, translated once at import.
|
||||
std::map<std::string, std::vector<uint8_t>> g_shaders;
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_import_shaders(const char* dll_path)
|
||||
{
|
||||
if (!dll_path || !*dll_path)
|
||||
{
|
||||
set_error("no file selected");
|
||||
return ARMSX3_LSFG_ERR_BAD_ARGUMENT;
|
||||
}
|
||||
|
||||
clear_error();
|
||||
g_shaders.clear();
|
||||
|
||||
// Upstream's own shader names, both families.
|
||||
//
|
||||
// Taken verbatim from nameIdxTable in extract.cpp rather than guessed -- a made-up name fails
|
||||
// as "Shader hash not found", which reads like a corrupt DLL and is not.
|
||||
//
|
||||
// Two sets: the plain names are LSFG 3.1 and the p_ prefixed ones are 3.1p. Which family gets
|
||||
// used depends on which framegen entry point runs, so both are extracted and whatever the DLL
|
||||
// actually contains is kept. Missing names are skipped rather than fatal, because a given
|
||||
// Lossless Scaling version legitimately ships only one family.
|
||||
static const char* const k_names[] = {
|
||||
"mipmaps", "alpha[0]", "alpha[1]", "alpha[2]", "alpha[3]",
|
||||
"beta[0]", "beta[1]", "beta[2]", "beta[3]", "beta[4]",
|
||||
"gamma[0]", "gamma[1]", "gamma[2]", "gamma[3]", "gamma[4]",
|
||||
"delta[0]", "delta[1]", "delta[2]", "delta[3]", "delta[4]",
|
||||
"delta[5]", "delta[6]", "delta[7]", "delta[8]", "delta[9]",
|
||||
"generate",
|
||||
"p_mipmaps", "p_alpha[0]", "p_alpha[1]", "p_alpha[2]", "p_alpha[3]",
|
||||
"p_beta[0]", "p_beta[1]", "p_beta[2]", "p_beta[3]", "p_beta[4]",
|
||||
"p_gamma[0]", "p_gamma[1]", "p_gamma[2]", "p_gamma[3]", "p_gamma[4]",
|
||||
"p_delta[0]", "p_delta[1]", "p_delta[2]", "p_delta[3]", "p_delta[4]",
|
||||
"p_delta[5]", "p_delta[6]", "p_delta[7]", "p_delta[8]", "p_delta[9]",
|
||||
"p_generate",
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
Config::activeConf.dll = dll_path;
|
||||
|
||||
Extract::extractShaders();
|
||||
|
||||
for (const char* name : k_names)
|
||||
{
|
||||
// getShader hands back DXBC; framegen wants SPIR-V. Translating at import rather than
|
||||
// on demand keeps the cost off the present path entirely.
|
||||
//
|
||||
// Individually guarded: a DLL that ships only one shader family throws on every name
|
||||
// in the other, and that is normal rather than a failure of the import.
|
||||
try
|
||||
{
|
||||
auto spirv = Extract::translateShader(Extract::getShader(name));
|
||||
|
||||
if (!spirv.empty())
|
||||
{
|
||||
g_shaders[name] = std::move(spirv);
|
||||
}
|
||||
}
|
||||
catch (const std::exception&)
|
||||
{
|
||||
// Not in this DLL. Keep going.
|
||||
}
|
||||
}
|
||||
|
||||
if (g_shaders.empty())
|
||||
{
|
||||
set_error("no usable shaders in that file -- is it Lossless.dll from Lossless Scaling?");
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
}
|
||||
catch (const std::exception& e)
|
||||
{
|
||||
g_shaders.clear();
|
||||
set_error(e.what());
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
g_shaders.clear();
|
||||
set_error("unknown failure reading the file");
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
|
||||
return static_cast<int>(g_shaders.size());
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_shader_count(void)
|
||||
{
|
||||
return static_cast<int>(g_shaders.size());
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_get_shader(const char* name, const uint8_t** out_data, uint32_t* out_size)
|
||||
{
|
||||
if (!name || !out_data || !out_size)
|
||||
{
|
||||
return ARMSX3_LSFG_ERR_BAD_ARGUMENT;
|
||||
}
|
||||
|
||||
const auto it = g_shaders.find(name);
|
||||
|
||||
if (it == g_shaders.end() || it->second.empty())
|
||||
{
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
|
||||
*out_data = it->second.data();
|
||||
*out_size = static_cast<uint32_t>(it->second.size());
|
||||
return ARMSX3_LSFG_OK;
|
||||
}
|
||||
#else
|
||||
extern "C" int armsx3_lsfg_import_shaders(const char*)
|
||||
{
|
||||
set_error("this build has no shader extraction support");
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
|
||||
extern "C" int armsx3_lsfg_shader_count(void) { return 0; }
|
||||
|
||||
extern "C" int armsx3_lsfg_get_shader(const char*, const uint8_t**, uint32_t*)
|
||||
{
|
||||
return ARMSX3_LSFG_ERR_SHADERS;
|
||||
}
|
||||
#endif
|
||||
Vendored
+142
@@ -0,0 +1,142 @@
|
||||
// C ABI for Lossless Scaling frame generation.
|
||||
//
|
||||
// framegen CANNOT be linked into libarmsx3-core.so. It links volk, which defines 655 globals
|
||||
// named vkCreateImage, vkQueueSubmit, ... and 124 of those are byte-for-byte the names our own
|
||||
// Vulkan loader declares in rpcs3/Emu/RSX/VK/vk_android_loader.h -- every single symbol the RSX
|
||||
// renderer uses. Two ways that goes wrong, and the second is the one that costs a week:
|
||||
//
|
||||
// 1. duplicate symbol at link time (clang defaults to -fno-common), or
|
||||
// 2. the linker merges them, and framegen's volkLoadDevice(itsOwnDevice) then repoints every
|
||||
// entry point the renderer uses at framegen's VkDevice. Every later vkCmdDraw goes to the
|
||||
// wrong device, and it presents as a driver crash with nothing pointing at frame generation.
|
||||
//
|
||||
// So framegen and volk live in their own libarmsx3_lsfg.so, reached by dlopen + dlsym through
|
||||
// this header. Nothing here is C++: the CMake project builds ANDROID_STL=c++_static, so each .so
|
||||
// carries its own libc++ and an std::vector or std::function crossing the boundary would be two
|
||||
// unrelated types that happen to share a name. The shim builds those on its own side.
|
||||
//
|
||||
// framegen also throws (LSFG::vulkan_error and friends). Exceptions must not cross a dlopen
|
||||
// boundary either, so every entry point here catches everything and returns a code; the message
|
||||
// is retrievable with armsx3_lsfg_last_error().
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Bump when anything below changes shape. The loader refuses a library whose version it does not
|
||||
// recognise, so a stale libarmsx3_lsfg.so on a user's device fails loudly at load instead of
|
||||
// quietly passing mismatched structs.
|
||||
#define ARMSX3_LSFG_ABI_VERSION 2u
|
||||
|
||||
// Mark the exported surface explicitly.
|
||||
//
|
||||
// The library is built -fvisibility=hidden so framegen's and volk's symbols stay in, and a
|
||||
// version script narrows the dynamic table further. Neither of those can PROMOTE a symbol: a
|
||||
// function hidden at compile time is local in the object, and `global:` in the linker script
|
||||
// cannot bring it back. Without this attribute the .so builds and exports nothing at all, and
|
||||
// the failure only shows up as dlsym returning null at runtime.
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
#define ARMSX3_LSFG_API __attribute__((visibility("default")))
|
||||
#else
|
||||
#define ARMSX3_LSFG_API
|
||||
#endif
|
||||
|
||||
enum armsx3_lsfg_result
|
||||
{
|
||||
ARMSX3_LSFG_OK = 0,
|
||||
ARMSX3_LSFG_ERR_UNKNOWN = -1,
|
||||
ARMSX3_LSFG_ERR_NOT_INITIALIZED = -2,
|
||||
ARMSX3_LSFG_ERR_BAD_ARGUMENT = -3,
|
||||
ARMSX3_LSFG_ERR_SHADERS = -4,
|
||||
ARMSX3_LSFG_ERR_VULKAN = -5,
|
||||
};
|
||||
|
||||
// Hand back the SPIR-V for a named shader.
|
||||
//
|
||||
// framegen does NOT read Lossless.dll -- it asks for shaders by name and expects SPIR-V back.
|
||||
// Extracting them from the user's own copy (PE resource -> DXBC -> SPIR-V) is the caller's job,
|
||||
// which is deliberate: the shaders are THS's property and nothing here ships or downloads them.
|
||||
//
|
||||
// Return ARMSX3_LSFG_OK and set *out_data / *out_size on success. The buffer must stay valid
|
||||
// until the initialize() call that triggered this returns. Any other return means "no such
|
||||
// shader" and fails initialization.
|
||||
typedef int (*armsx3_lsfg_shader_loader)(const char* name, const uint8_t** out_data,
|
||||
uint32_t* out_size, void* user);
|
||||
|
||||
// Version of the loaded library. Call first; anything else on a mismatched library is undefined.
|
||||
ARMSX3_LSFG_API uint32_t armsx3_lsfg_abi_version(void);
|
||||
|
||||
// Bring up framegen on the adapter identified by device_uuid (VkPhysicalDeviceIDProperties
|
||||
// deviceUUID, 16 bytes, passed as the first 8 -- that is what framegen matches on).
|
||||
//
|
||||
// framegen creates its OWN VkDevice on that adapter. It does not share ours, which is why images
|
||||
// have to be handed over as AHardwareBuffer below rather than as VkImage.
|
||||
// performance selects framegen's 3.1p shader family instead of 3.1: a cheaper pipeline at lower
|
||||
// quality, which is the difference between usable and not on a mobile GPU. It is fixed for the
|
||||
// lifetime of the library state -- every context, present and teardown after this call goes to the
|
||||
// family chosen here, because the two keep separate contexts and separate device state.
|
||||
//
|
||||
// flow_scale is the optical-flow resolution as a fraction of full: 1.0 is upstream's default and
|
||||
// lower is cheaper. Note the sense is inverted from upstream's own config file, which stores a
|
||||
// divisor and passes 1.0f/value here.
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_initialize(uint64_t device_uuid, int is_hdr, float flow_scale,
|
||||
uint64_t generation_count, int performance, armsx3_lsfg_shader_loader loader, void* user);
|
||||
|
||||
// Create a context over a set of shared images.
|
||||
//
|
||||
// AHardwareBuffer rather than the FD path framegen also offers, because Adreno and Mali both
|
||||
// refuse vkGetMemoryFdKHR(OPAQUE_FD) on AHB-imported memory -- the FD path simply does not work
|
||||
// on the hardware this port runs on.
|
||||
//
|
||||
// The caller keeps ownership of every AHardwareBuffer and must keep them alive until the context
|
||||
// is destroyed. Returns a context id >= 0, or a negative armsx3_lsfg_result.
|
||||
ARMSX3_LSFG_API int32_t armsx3_lsfg_create_context_ahb(void* in0, void* in1, void* const* out_n,
|
||||
uint32_t out_count, uint32_t width, uint32_t height, int32_t format);
|
||||
|
||||
// Generate frames for one presented pair.
|
||||
//
|
||||
// Semaphores are sync file descriptors, not VkSemaphore: framegen is on a different device and a
|
||||
// VkSemaphore handle would be meaningless to it. in_sem is waited on before generation starts;
|
||||
// each out_sems[i] is signalled when output image i is ready. Pass -1 for an unused slot.
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_present(int32_t ctx, int in_sem, const int* out_sems, uint32_t out_count);
|
||||
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_destroy_context(int32_t ctx);
|
||||
|
||||
// Read the user's own Lossless.dll and keep the shaders it contains.
|
||||
//
|
||||
// Nothing is bundled or downloaded: the shaders are THS's property and the user must supply a
|
||||
// legitimately purchased copy. Only the extracted SPIR-V is kept -- the DLL itself is not needed
|
||||
// afterwards and the caller may delete its copy.
|
||||
//
|
||||
// The work is PE resource walk -> DXBC -> SPIR-V, and it is slow enough to be worth doing once
|
||||
// and caching rather than at every boot. Returns the number of shaders extracted, or a negative
|
||||
// armsx3_lsfg_result; armsx3_lsfg_last_error() explains a failure in terms a user can act on
|
||||
// ("is Lossless Scaling up to date?" rather than a resource id).
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_import_shaders(const char* dll_path);
|
||||
|
||||
// How many shaders are currently held. Zero means frame generation cannot start.
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_shader_count(void);
|
||||
|
||||
// Serve a previously imported shader by name, for initialize()'s loader.
|
||||
//
|
||||
// Pass a null loader to armsx3_lsfg_initialize to use these instead of supplying your own.
|
||||
ARMSX3_LSFG_API int armsx3_lsfg_get_shader(const char* name, const uint8_t** out_data, uint32_t* out_size);
|
||||
|
||||
// Block until framegen's device is idle.
|
||||
//
|
||||
// Needed on Android because framegen's device reads AHBs that OUR device writes, and there is no
|
||||
// semaphore shared between the two. Without this the read races the write. It is also the reason
|
||||
// frame generation cannot be free here: this is a device-level stall, not a queue wait.
|
||||
ARMSX3_LSFG_API void armsx3_lsfg_wait_idle(void);
|
||||
|
||||
ARMSX3_LSFG_API void armsx3_lsfg_finalize(void);
|
||||
|
||||
// Message for the last failing call on this thread, or "" if none. Never null.
|
||||
ARMSX3_LSFG_API const char* armsx3_lsfg_last_error(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -12,6 +12,29 @@ project(rpcs3 LANGUAGES C CXX)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
# Keep the builder's absolute paths out of the shipped binary.
|
||||
#
|
||||
# __FILE__ expands to whatever path the compiler was handed, and RPCS3 prints source locations in
|
||||
# ensure() failures, fmt::throw_exception and assertions -- so every one of those lines carried the
|
||||
# full build directory into EVERY USER'S LOG. On a developer's machine that is a home directory:
|
||||
# the shipped core contained 2500 copies of one username. Someone else's crash report is not the
|
||||
# place to publish where we build.
|
||||
#
|
||||
# -ffile-prefix-map rewrites the prefix at compile time, covering both __FILE__ (macro-prefix-map)
|
||||
# and debug info (debug-prefix-map). Paths become relative-looking (./rpcs3/Emu/...), which is what
|
||||
# a log wants to show anyway. Costs nothing at runtime.
|
||||
#
|
||||
# Applied here, before any add_subdirectory, so third-party targets built in-tree are covered too --
|
||||
# they embed the same root.
|
||||
if(NOT MSVC)
|
||||
include(CheckCXXCompilerFlag)
|
||||
check_cxx_compiler_flag("-ffile-prefix-map=${CMAKE_SOURCE_DIR}=." COMPILER_HAS_FILE_PREFIX_MAP)
|
||||
|
||||
if(COMPILER_HAS_FILE_PREFIX_MAP)
|
||||
add_compile_options("$<$<COMPILE_LANGUAGE:C,CXX>:-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.>")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 13)
|
||||
message(FATAL_ERROR "RPCS3 requires at least gcc-13.")
|
||||
|
||||
@@ -7,7 +7,7 @@ Uses the latest RPCS3 upstream code (the recent ARM64 improvements included).
|
||||
Building
|
||||
--------
|
||||
|
||||
Only arm64-v8a is supported. You need the Android SDK with NDK r27 or newer,
|
||||
arm64-v8a and armv8.2 is supported. You need the Android SDK with NDK r27 or newer,
|
||||
CMake 3.30 or newer, and a JDK 17. Android Studio ships all of these.
|
||||
|
||||
Clone with submodules, then fetch the two third party checkouts that are not
|
||||
|
||||
+14
-2
@@ -332,7 +332,15 @@ struct MemoryManager1 : llvm::RTDyldMemoryManager
|
||||
{
|
||||
const u64 pagea = utils::align(oldp, page_quarter);
|
||||
const u64 psize = utils::align(std::min(newp, c_page_size) - pagea, page_quarter);
|
||||
utils::memory_commit(reinterpret_cast<u8*>(block) + (pagea % c_max_size), psize, prot);
|
||||
|
||||
// try_ rather than memory_commit: a commit failure here is the device being out of
|
||||
// memory, and the caller has a real fallback -- the module does not compile and its
|
||||
// functions are interpreted. The fatal version reported it as "LLVM crash recovery
|
||||
// invoked", which reads like a codegen bug and sent this diagnosis the wrong way.
|
||||
if (!utils::try_memory_commit(reinterpret_cast<u8*>(block) + (pagea % c_max_size), psize, prot))
|
||||
{
|
||||
fmt::throw_exception("Out of memory (commit failed: size=0x%x, align=0x%x)", size, align);
|
||||
}
|
||||
|
||||
// Advance
|
||||
oldp = pagea + psize;
|
||||
@@ -343,7 +351,11 @@ struct MemoryManager1 : llvm::RTDyldMemoryManager
|
||||
// Allocate pages on demand
|
||||
const u64 pagea = utils::align(oldp, c_page_size);
|
||||
const u64 psize = utils::align(newp - pagea, c_page_size);
|
||||
utils::memory_commit(reinterpret_cast<u8*>(block) + (pagea % c_max_size), psize, prot);
|
||||
|
||||
if (!utils::try_memory_commit(reinterpret_cast<u8*>(block) + (pagea % c_max_size), psize, prot))
|
||||
{
|
||||
fmt::throw_exception("Out of memory (commit failed: size=0x%x, align=0x%x)", size, align);
|
||||
}
|
||||
}
|
||||
|
||||
return reinterpret_cast<u8*>(block) + (olda % c_max_size);
|
||||
|
||||
+430
-11
File diff suppressed because it is too large
Load Diff
+29
-9
@@ -1803,13 +1803,6 @@ static void append_patches(patch_engine::patch_map& existing_patches, const patc
|
||||
|
||||
bool patch_engine::save_patches(const patch_map& patches, const std::string& path, std::stringstream* log_messages)
|
||||
{
|
||||
fs::file file(path, fs::rewrite);
|
||||
if (!file)
|
||||
{
|
||||
append_log_message(log_messages, fmt::format("Failed to open patch file %s (%s)", path, fs::g_tls_error), &patch_log.fatal);
|
||||
return false;
|
||||
}
|
||||
|
||||
YAML::Emitter out;
|
||||
out << YAML::BeginMap;
|
||||
out << patch_key::version << patch_engine_version;
|
||||
@@ -1904,7 +1897,24 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat
|
||||
out << YAML::Flow;
|
||||
out << YAML::BeginSeq;
|
||||
out << fmt::format("%s", data.type);
|
||||
out << fmt::format("0x%.8x", data.offset);
|
||||
|
||||
// move_file and hide_file carry a VFS path in the address element instead of a
|
||||
// number. load() keeps that text in original_offset and skips the u32 validation for
|
||||
// them, so formatting it numerically here would write out 0x00000000 and the loader
|
||||
// would accept it back as a patch that silently never matches anything.
|
||||
//
|
||||
// The numeric branch deliberately uses offset rather than original_offset: an
|
||||
// address modifier is folded into offset at load time, and the flat form emitted
|
||||
// here has nowhere to put it.
|
||||
if (patch_type_uses_hex_offset(data.type))
|
||||
{
|
||||
out << fmt::format("0x%.8x", data.offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
out << data.original_offset;
|
||||
}
|
||||
|
||||
out << data.original_value;
|
||||
out << YAML::EndSeq;
|
||||
}
|
||||
@@ -1918,7 +1928,17 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat
|
||||
|
||||
out << YAML::EndMap;
|
||||
|
||||
file.write(out.c_str(), out.size());
|
||||
// Write through a temporary and rename on success, as save_config already does. A truncating
|
||||
// in-place write that fails part way (out of space, process killed) leaves a half-written file,
|
||||
// and load() rejects the whole file on a parse error -- so a failure here costs the user every
|
||||
// patch they had, with no way to rebuild it from inside the app.
|
||||
fs::pending_file file(path);
|
||||
|
||||
if (!file.file || file.file.write(out.c_str(), out.size()) < out.size() || !file.commit())
|
||||
{
|
||||
append_log_message(log_messages, fmt::format("Failed to write patch file %s (%s)", path, fs::g_tls_error), &patch_log.fatal);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
+57
-3
@@ -724,15 +724,24 @@ struct coord3_base
|
||||
struct { T width, height, depth; };
|
||||
};
|
||||
|
||||
constexpr coord3_base() : position{}, size{}
|
||||
constexpr coord3_base()
|
||||
: position{}, size{}
|
||||
{
|
||||
}
|
||||
|
||||
constexpr coord3_base(const position3_base<T>& position, const size3_base<T>& size) : position{ position }, size{ size }
|
||||
constexpr coord3_base(const position3_base<T>& position, const size3_base<T>& size)
|
||||
: position{ position }, size{ size }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr coord3_base(T x, T y, T z, T width, T height, T depth) : x{ x }, y{ y }, z{ z }, width{ width }, height{ height }, depth{ depth }
|
||||
constexpr coord3_base(T x, T y, T z, T width, T height, T depth)
|
||||
: x{ x }, y{ y }, z{ z }, width{ width }, height{ height }, depth{ depth }
|
||||
{
|
||||
}
|
||||
|
||||
constexpr coord3_base(const area_base<T>& area, T z = 0, T depth = 1)
|
||||
: x{ area.x1 }, y{ area.y1 }, z{ z }
|
||||
, width{ area.x2 - area.x1 }, height{ area.y2 - area.y1 }, depth{ depth }
|
||||
{
|
||||
}
|
||||
|
||||
@@ -755,6 +764,51 @@ struct coord3_base
|
||||
{
|
||||
return{ static_cast<NT>(x), static_cast<NT>(y), static_cast<NT>(z), static_cast<NT>(width), static_cast<NT>(height), static_cast<NT>(depth) };
|
||||
}
|
||||
|
||||
void flip_horizontal()
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
auto x2 = x + width;
|
||||
x = x2;
|
||||
width = -width;
|
||||
}
|
||||
|
||||
void flip_vertical()
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
auto y2 = y + height;
|
||||
y = y2;
|
||||
height = -height;
|
||||
}
|
||||
|
||||
bool is_flipped() const
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
return width < 0 || height < 0 || depth < 0;
|
||||
}
|
||||
|
||||
area_base<T> to_area() const
|
||||
{
|
||||
return { x, y, x + width, y + height };
|
||||
}
|
||||
|
||||
T abs_width() const
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
return width < 0 ? -width : width;
|
||||
}
|
||||
|
||||
T abs_height() const
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
return height < 0 ? -height : height;
|
||||
}
|
||||
|
||||
T abs_depth() const
|
||||
requires std::is_signed_v<T>
|
||||
{
|
||||
return depth < 0 ? -depth : depth;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
+259
-172
@@ -1,172 +1,259 @@
|
||||
#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, PCONTEXT ctx)
|
||||
{
|
||||
static struct sym_initer_t
|
||||
{
|
||||
sym_initer_t() noexcept
|
||||
{
|
||||
SymInitialize(GetCurrentProcess(), NULL, TRUE);
|
||||
}
|
||||
~sym_initer_t() noexcept
|
||||
{
|
||||
SymCleanup(GetCurrentProcess());
|
||||
}
|
||||
} s_initer{};
|
||||
|
||||
std::vector<void*> result = {};
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
const auto hThread = ::GetCurrentThread();
|
||||
|
||||
CONTEXT context{};
|
||||
if (ctx)
|
||||
context = *ctx;
|
||||
else
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stack = {};
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Mode = AddrModeFlat;
|
||||
#if defined(ARCH_X64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
|
||||
stack.AddrPC.Offset = context.Rip;
|
||||
stack.AddrStack.Offset = context.Rsp;
|
||||
stack.AddrFrame.Offset = context.Rbp;
|
||||
#elif defined(ARCH_ARM64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_ARM64;
|
||||
stack.AddrPC.Offset = context.Pc;
|
||||
stack.AddrStack.Offset = context.Sp;
|
||||
stack.AddrFrame.Offset = context.Fp;
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
while (max_depth--)
|
||||
{
|
||||
if (!StackWalk64(
|
||||
machineType,
|
||||
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_INFOW) + sizeof(TCHAR) * 256);
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
|
||||
auto sym = reinterpret_cast<SYMBOL_INFOW*>(symbol_buf.data());
|
||||
sym->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
||||
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;
|
||||
SymFromAddrW(hProcess, reinterpret_cast<DWORD64>(pointer), &unused, sym);
|
||||
|
||||
if (sym->NameLen)
|
||||
{
|
||||
std::string function_name = wstr_to_utf8(sym->Name, static_cast<int>(sym->NameLen));
|
||||
|
||||
// Attempt to get file and line information if available
|
||||
DWORD unused2;
|
||||
if (SymGetLineFromAddrW64(hProcess, reinterpret_cast<DWORD64>(pointer), &unused2, &line_info))
|
||||
{
|
||||
std::string full_path = fmt::format("%s:%u %s", wstr_to_utf8(line_info.FileName, -1), line_info.LineNumber, function_name);
|
||||
result.push_back(std::move(full_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(std::move(function_name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(fmt::format("rpcs3@0x%p", pointer));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#else
|
||||
std::vector<void*> get_backtrace(int max_depth)
|
||||
{
|
||||
std::vector<void*> result(max_depth);
|
||||
#ifndef ANDROID
|
||||
int depth = backtrace(result.data(), max_depth);
|
||||
result.resize(depth);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
#ifndef ANDROID
|
||||
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);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
#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>
|
||||
#elif defined(ANDROID)
|
||||
// bionic has no backtrace()/backtrace_symbols(), which is why both were compiled out here and
|
||||
// every native crash on this port had to be read out of a tombstone or symbolized by hand.
|
||||
// _Unwind_Backtrace is always present, and dladdr gives the library-relative offset that
|
||||
// llvm-symbolizer wants.
|
||||
#include <unwind.h>
|
||||
#include <dlfcn.h>
|
||||
#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, PCONTEXT ctx)
|
||||
{
|
||||
static struct sym_initer_t
|
||||
{
|
||||
sym_initer_t() noexcept
|
||||
{
|
||||
SymInitialize(GetCurrentProcess(), NULL, TRUE);
|
||||
}
|
||||
~sym_initer_t() noexcept
|
||||
{
|
||||
SymCleanup(GetCurrentProcess());
|
||||
}
|
||||
} s_initer{};
|
||||
|
||||
std::vector<void*> result = {};
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
const auto hThread = ::GetCurrentThread();
|
||||
|
||||
CONTEXT context{};
|
||||
if (ctx)
|
||||
context = *ctx;
|
||||
else
|
||||
RtlCaptureContext(&context);
|
||||
|
||||
STACKFRAME64 stack = {};
|
||||
stack.AddrPC.Mode = AddrModeFlat;
|
||||
stack.AddrStack.Mode = AddrModeFlat;
|
||||
stack.AddrFrame.Mode = AddrModeFlat;
|
||||
#if defined(ARCH_X64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_AMD64;
|
||||
stack.AddrPC.Offset = context.Rip;
|
||||
stack.AddrStack.Offset = context.Rsp;
|
||||
stack.AddrFrame.Offset = context.Rbp;
|
||||
#elif defined(ARCH_ARM64)
|
||||
const DWORD machineType = IMAGE_FILE_MACHINE_ARM64;
|
||||
stack.AddrPC.Offset = context.Pc;
|
||||
stack.AddrStack.Offset = context.Sp;
|
||||
stack.AddrFrame.Offset = context.Fp;
|
||||
#else
|
||||
#error "Unsupported architecture"
|
||||
#endif
|
||||
|
||||
while (max_depth--)
|
||||
{
|
||||
if (!StackWalk64(
|
||||
machineType,
|
||||
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_INFOW) + sizeof(TCHAR) * 256);
|
||||
|
||||
const auto hProcess = ::GetCurrentProcess();
|
||||
|
||||
auto sym = reinterpret_cast<SYMBOL_INFOW*>(symbol_buf.data());
|
||||
sym->SizeOfStruct = sizeof(SYMBOL_INFOW);
|
||||
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;
|
||||
SymFromAddrW(hProcess, reinterpret_cast<DWORD64>(pointer), &unused, sym);
|
||||
|
||||
if (sym->NameLen)
|
||||
{
|
||||
std::string function_name = wstr_to_utf8(sym->Name, static_cast<int>(sym->NameLen));
|
||||
|
||||
// Attempt to get file and line information if available
|
||||
DWORD unused2;
|
||||
if (SymGetLineFromAddrW64(hProcess, reinterpret_cast<DWORD64>(pointer), &unused2, &line_info))
|
||||
{
|
||||
std::string full_path = fmt::format("%s:%u %s", wstr_to_utf8(line_info.FileName, -1), line_info.LineNumber, function_name);
|
||||
result.push_back(std::move(full_path));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(std::move(function_name));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(fmt::format("rpcs3@0x%p", pointer));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#elif defined(ANDROID)
|
||||
namespace
|
||||
{
|
||||
struct unwind_state
|
||||
{
|
||||
void** current;
|
||||
void** end;
|
||||
};
|
||||
|
||||
_Unwind_Reason_Code unwind_collect(_Unwind_Context* ctx, void* arg)
|
||||
{
|
||||
auto* state = static_cast<unwind_state*>(arg);
|
||||
|
||||
// A frame with no PC is the end of what the unwinder can see; keep the frames
|
||||
// gathered so far rather than discarding a partial stack, which is still the
|
||||
// answer most of the time.
|
||||
const auto pc = _Unwind_GetIP(ctx);
|
||||
|
||||
if (!pc)
|
||||
{
|
||||
return _URC_END_OF_STACK;
|
||||
}
|
||||
|
||||
if (state->current == state->end)
|
||||
{
|
||||
return _URC_END_OF_STACK;
|
||||
}
|
||||
|
||||
*state->current++ = reinterpret_cast<void*>(pc);
|
||||
return _URC_NO_REASON;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<void*> get_backtrace(int max_depth)
|
||||
{
|
||||
std::vector<void*> result(max_depth);
|
||||
unwind_state state{ result.data(), result.data() + max_depth };
|
||||
_Unwind_Backtrace(&unwind_collect, &state);
|
||||
result.resize(state.current - result.data());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::string> get_backtrace_symbols(const std::vector<void*>& stack)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
result.reserve(stack.size());
|
||||
|
||||
for (void* const pointer : stack)
|
||||
{
|
||||
Dl_info info{};
|
||||
|
||||
if (!dladdr(pointer, &info) || !info.dli_fname)
|
||||
{
|
||||
result.push_back(fmt::format("0x%p", pointer));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Library-relative, because that is what symbolizes. The shipped .so is stripped
|
||||
// and loaded at a random base, so an absolute PC is useless on its own; this
|
||||
// offset is what llvm-symbolizer takes against the unstripped build output.
|
||||
const auto base = reinterpret_cast<uptr>(info.dli_fbase);
|
||||
const auto off = reinterpret_cast<uptr>(pointer) - base;
|
||||
|
||||
// Basename only: the full path is the app's private data dir and the same for
|
||||
// every frame.
|
||||
std::string_view lib = info.dli_fname;
|
||||
|
||||
if (const auto slash = lib.find_last_of('/'); slash != umax)
|
||||
{
|
||||
lib.remove_prefix(slash + 1);
|
||||
}
|
||||
|
||||
if (info.dli_sname)
|
||||
{
|
||||
result.push_back(fmt::format("%s+0x%x (%s)", lib, off, info.dli_sname));
|
||||
}
|
||||
else
|
||||
{
|
||||
result.push_back(fmt::format("%s+0x%x", lib, off));
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user