mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
9b33316982 | ||
|
|
55a35b5e1d | ||
|
|
8caacc8231 | ||
|
|
0ded153216 | ||
|
|
4baefed106 | ||
|
|
424514fde6 | ||
|
|
37a5d118be | ||
|
|
301f45a2cb | ||
|
|
aa25da4ce2 | ||
|
|
e35bd463cf | ||
|
|
968b892e29 | ||
|
|
89c6d08ed3 | ||
|
|
7811cffeed | ||
|
|
20c854aeb3 | ||
|
|
7952244052 | ||
|
|
4179f23e20 | ||
|
|
4b2b8438be | ||
|
|
7b49e1fcea | ||
|
|
d9957c56ae | ||
|
|
62d8208c71 | ||
|
|
cab4f2507c | ||
|
|
27e9d11b80 | ||
|
|
6e731093c4 | ||
|
|
89b2128679 | ||
|
|
33343cd153 | ||
|
|
6cd7866553 | ||
|
|
6a5ad70ec7 | ||
|
|
c990f34d5f | ||
|
|
bbaebe47a4 | ||
|
|
e480c291da | ||
|
|
7d25a7086e | ||
|
|
dbbb6fbde0 | ||
|
|
d069a55acc | ||
|
|
614bf8b718 | ||
|
|
cce09dbb39 | ||
|
|
b5a715adcf | ||
|
|
eb54f9b75a | ||
|
|
0a9fd15b57 | ||
|
|
0821bbf956 | ||
|
|
b29810d1a5 | ||
|
|
5ef731c9e5 | ||
|
|
002a9b274a | ||
|
|
39ca5cdab6 | ||
|
|
b82432c793 | ||
|
|
7f54855b7d | ||
|
|
0819f1ef15 | ||
|
|
8ee20d91d5 | ||
|
|
4797ad8a9a | ||
|
|
c4b45eee27 | ||
|
|
87ccdb8515 | ||
|
|
e10f846924 | ||
|
|
db6ee86806 | ||
|
|
708582e523 | ||
|
|
8a6deab362 | ||
|
|
5b740f8921 | ||
|
|
11f043b529 | ||
|
|
b8987b8c92 | ||
|
|
403df1c651 | ||
|
|
614cdd74a3 | ||
|
|
76af990eed | ||
|
|
441c3f1bde | ||
|
|
fcdd7cd6af | ||
|
|
ad15f81d6e | ||
|
|
2df75aa604 | ||
|
|
b0c7a0260e | ||
|
|
7745a3c92d | ||
|
|
b09815e595 | ||
|
|
3791865e2f | ||
|
|
6831c87a0e | ||
|
|
b4378d8977 | ||
|
|
8fed09d40e | ||
|
|
8772786f9a | ||
|
|
5405d71e2e | ||
|
|
4101367b2d | ||
|
|
466d85d5b9 |
@@ -112,3 +112,6 @@
|
||||
path = 3rdparty/protobuf/protobuf
|
||||
url = ../../protocolbuffers/protobuf.git
|
||||
ignore = dirty
|
||||
[submodule "3rdparty/oboe/oboe"]
|
||||
path = 3rdparty/oboe/oboe
|
||||
url = https://github.com/google/oboe.git
|
||||
|
||||
Vendored
+15
@@ -141,6 +141,12 @@ else()
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# Oboe (Android only)
|
||||
if(ANDROID)
|
||||
message(STATUS "Using static oboe from 3rdparty")
|
||||
add_subdirectory(oboe EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# SoundTouch
|
||||
add_subdirectory(SoundTouch EXCLUDE_FROM_ALL)
|
||||
|
||||
@@ -367,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)
|
||||
|
||||
@@ -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
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
# Oboe
|
||||
#
|
||||
# Android-only. Oboe wraps AAudio (and OpenSL ES on older devices) and carries a
|
||||
# per-device quirks database plus stream-restart handling, which is the part that
|
||||
# matters on the low-end parts where plain AAudio glitches.
|
||||
|
||||
add_subdirectory(oboe EXCLUDE_FROM_ALL)
|
||||
add_library(3rdparty::oboe ALIAS oboe)
|
||||
+1
Submodule 3rdparty/oboe/oboe added at 0da326e4ef
@@ -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.")
|
||||
|
||||
@@ -1,44 +1,13 @@
|
||||
ARMSX3
|
||||
======
|
||||
|
||||
Proof of concept Android port of RPCS3.
|
||||
|
||||
Uses the latest RPCS3 upstream code (the recent ARM64 improvements included).
|
||||
|
||||
Status
|
||||
------
|
||||
|
||||
From my testing, I only tried Skate 3. It boots, loads and reaches gameplay at roughly 20 to 30 fps on a
|
||||
Snapdragon 8 Gen 2. Rendering, audio, touch controls and physical controllers
|
||||
work. Almost nothing else has been tested. So the main stop gap at the moment is performance/speed.
|
||||
|
||||
Differences from upstream RPCS3
|
||||
-------------------------------
|
||||
|
||||
Some of the fixes here are not in upstream and affect any ARM64 build, not only
|
||||
Android:
|
||||
|
||||
* Shaders declared runtime sized arrays inside uniform blocks, which requires
|
||||
VK_EXT_shader_uniform_buffer_unsized_array. Adreno does not support that
|
||||
extension, so every game pipeline failed to compile and nothing rendered.
|
||||
Concrete array bounds are emitted when the extension is missing.
|
||||
|
||||
* The ARM64 SPU block verification checksum folded two thirds of every block
|
||||
through an absolute difference. That collides on the near identical job
|
||||
binaries an SPU job manager streams through the same local store address, so
|
||||
a cached block could end up running against another job's code. It sums now.
|
||||
|
||||
* Thread affinity was compiled out on Android, and the core had no ARM
|
||||
big.LITTLE topology, so SPU and RSX threads were never placed on the fast
|
||||
cores.
|
||||
|
||||
* The LLVM JIT target was pinned to cortex-a34, an in order core from 2016. It
|
||||
detects the host now.
|
||||
|
||||
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
|
||||
|
||||
+34
-3
@@ -1,3 +1,4 @@
|
||||
#include <cerrno>
|
||||
#include "File.h"
|
||||
#include "mutex.h"
|
||||
#include "StrFmt.h"
|
||||
@@ -684,8 +685,20 @@ namespace fs
|
||||
u64 result = 0;
|
||||
|
||||
// Loop because (huge?) read can be processed partially
|
||||
while (auto r = ::read(m_fd, buffer, count))
|
||||
for (;;)
|
||||
{
|
||||
const auto r = ::read(m_fd, buffer, count);
|
||||
|
||||
// EINTR is benign -- a signal landed mid-syscall -- and must be retried, not treated
|
||||
// as failure. Android app storage is FUSE-backed, where this genuinely happens, and
|
||||
// the ensure() below turns it into a process abort. Ported in spirit from
|
||||
// ouroboros420/rpcsx (92144f094).
|
||||
if (r < 0 && errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r) break; // EOF
|
||||
ensure(r > 0); // "file::read"
|
||||
count -= r;
|
||||
result += r;
|
||||
@@ -702,8 +715,17 @@ namespace fs
|
||||
u64 result = 0;
|
||||
|
||||
// For safety; see read()
|
||||
while (auto r = ::pread(m_fd, buffer, count, offset))
|
||||
for (;;)
|
||||
{
|
||||
const auto r = ::pread(m_fd, buffer, count, offset);
|
||||
|
||||
// See read(): retry EINTR rather than aborting.
|
||||
if (r < 0 && errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r) break; // EOF
|
||||
ensure(r > 0); // "file::read_at"
|
||||
count -= r;
|
||||
offset += r;
|
||||
@@ -721,8 +743,17 @@ namespace fs
|
||||
u64 result = 0;
|
||||
|
||||
// For safety; see read()
|
||||
while (auto r = ::write(m_fd, buffer, count))
|
||||
for (;;)
|
||||
{
|
||||
const auto r = ::write(m_fd, buffer, count);
|
||||
|
||||
// See read(): retry EINTR rather than aborting.
|
||||
if (r < 0 && errno == EINTR)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r) break;
|
||||
ensure(r > 0); // "file::write"
|
||||
count -= r;
|
||||
result += r;
|
||||
|
||||
@@ -618,6 +618,22 @@ std::string jit_compiler::cpu(std::string_view _cpu)
|
||||
m_cpu = fallback_cpu_detection();
|
||||
}
|
||||
|
||||
#ifdef ARCH_ARM64
|
||||
// Detection reads the MIDR of whichever core happens to be running, so on big.LITTLE it
|
||||
// can name a small in-order core. JIT'd code runs on every core, so scheduling for the
|
||||
// smallest one is the wrong default -- fall back to the same wide out-of-order baseline
|
||||
// used when detection fails outright. Only the schedule/cost model is affected: the
|
||||
// instruction set still comes from setMAttrs (HWCAP-gated), so this can never emit an
|
||||
// illegal instruction. Ported from ouroboros420/rpcsx (cc3a18e29), widened to the
|
||||
// A5xx little cores that modern SoCs actually ship.
|
||||
if (m_cpu == "cortex-a34" || m_cpu == "cortex-a35" || m_cpu == "cortex-a53" ||
|
||||
m_cpu == "cortex-a55" || m_cpu == "cortex-a510" || m_cpu == "cortex-a520")
|
||||
{
|
||||
jit_log.notice("CPU detection named a little core ('%s'); using cortex-a78 as the schedule baseline.", m_cpu);
|
||||
m_cpu = "cortex-a78";
|
||||
}
|
||||
#endif
|
||||
|
||||
if (m_cpu == "sandybridge" ||
|
||||
m_cpu == "ivybridge" ||
|
||||
m_cpu == "haswell" ||
|
||||
@@ -754,6 +770,29 @@ jit_compiler::jit_compiler(const std::unordered_map<std::string, u64>& _link, st
|
||||
fmt::throw_exception("LLVM Emergency Exit Invoked: '%s'", out);
|
||||
}, nullptr);
|
||||
|
||||
// A separate handler from the fatal one -- LLVM installs and dispatches the two
|
||||
// independently. Without this, an allocation failure inside LLVM (SmallVector growth
|
||||
// while codegenning the enormous PPU symbol-resolver module, say) writes "LLVM ERROR:
|
||||
// out of memory" to fd 2 -- which goes nowhere in an Android app -- and calls abort():
|
||||
// a signal-6 death with nothing whatsoever in the log. Route it through the same
|
||||
// recoverable path as the fatal handler, so a guarded compile survives and anything
|
||||
// else at least says why it died. Allocating inside a bad-alloc handler is
|
||||
// best-effort, but the failures here are huge single allocations, so a short log
|
||||
// string still succeeds. Ported from ouroboros420/rpcsx (39a6a4c36).
|
||||
llvm::remove_bad_alloc_error_handler();
|
||||
llvm::install_bad_alloc_error_handler([](void*, const char* msg, bool)
|
||||
{
|
||||
const std::string_view out = msg ? msg : "";
|
||||
|
||||
if (g_llvm_fatal_message)
|
||||
{
|
||||
*g_llvm_fatal_message = out;
|
||||
thread_ctrl::silent_exit();
|
||||
}
|
||||
|
||||
fmt::throw_exception("LLVM Out Of Memory: '%s'", out);
|
||||
}, nullptr);
|
||||
|
||||
return true;
|
||||
}();
|
||||
|
||||
|
||||
+416
-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;
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ set(ARMSX3_INPUT_SOURCES
|
||||
${CMAKE_SOURCE_DIR}/rpcs3/Input/mouse_gyro_handler.cpp
|
||||
# Ours: on-screen touch controls.
|
||||
${CMAKE_SOURCE_DIR}/rpcs3/Input/virtual_pad_handler.cpp
|
||||
# Ours: cellKb fed from the Android IME / a physical keyboard. The desktop
|
||||
# handler is a QObject and cannot be built here.
|
||||
${CMAKE_SOURCE_DIR}/rpcs3/Input/virtual_keyboard_handler.cpp
|
||||
)
|
||||
|
||||
add_library(rpcsx-android SHARED
|
||||
|
||||
@@ -87,42 +87,13 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// ARMSX3: fail the build if the bundled ANGLE libraries are not there.
|
||||
//
|
||||
// This check exists because of a specific, expensive bug in ARMSX2: the repo's
|
||||
// blanket `*.so` gitignore rule swallowed the ANGLE prebuilts, they never made it
|
||||
// into release staging, the APK shipped without them, and the core fell back to
|
||||
// the system GLES driver in complete silence. Users reported "ANGLE is broken"
|
||||
// and there was nothing in any log to contradict them.
|
||||
//
|
||||
// jniLibs/.gitignore now un-ignores the two files by name. This task is the
|
||||
// second lock: packaging an APK that claims to support ANGLE without shipping
|
||||
// ANGLE is a build error, not a runtime surprise. The core-side counterpart is
|
||||
// the loud error in gl::es::egl_initialize() when the override library is
|
||||
// selected but cannot be dlopen'd.
|
||||
val verifyAngleLibs by tasks.registering {
|
||||
val angleLibs = listOf("libEGL_angle.so", "libGLESv2_angle.so")
|
||||
val jniLibDir = file("src/main/jniLibs/arm64-v8a")
|
||||
|
||||
doLast {
|
||||
val missing = angleLibs.filter { !File(jniLibDir, it).isFile }
|
||||
if (missing.isNotEmpty()) {
|
||||
throw GradleException(
|
||||
"ANGLE libraries missing from ${'$'}jniLibDir: ${'$'}{missing.joinToString(", ")}.\n" +
|
||||
"The OpenGL renderer's ANGLE option cannot work without them and would " +
|
||||
"silently fall back to the system GLES driver.\n" +
|
||||
"They are tracked in git - check them out, or remove the ANGLE option."
|
||||
)
|
||||
}
|
||||
|
||||
angleLibs.forEach {
|
||||
logger.lifecycle("ANGLE: packaging ${'$'}it (${'$'}{File(jniLibDir, it).length()} bytes)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.matching { it.name.startsWith("merge") && it.name.endsWith("JniLibFolders") }
|
||||
.configureEach { dependsOn(verifyAngleLibs) }
|
||||
// ARMSX3: the ANGLE prebuilts and the verifyAngleLibs task that guarded them used
|
||||
// to live here. They now live in android/armsx3-ui, which is the module that
|
||||
// actually ships (applicationId com.armsx3) and the module whose UI exposes the
|
||||
// OpenGL renderer's ANGLE option. This module builds nothing that ships, so the
|
||||
// guard here could never protect the APK it was written for -- and it never ran at
|
||||
// all: its message was written with `${'$'}`-style template escaping, and the `", "`
|
||||
// inside it closed the Kotlin string early, so this file did not compile.
|
||||
|
||||
base.archivesName = "rpcsx"
|
||||
|
||||
|
||||
@@ -27,10 +27,13 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.armsx3"
|
||||
minSdk = 26
|
||||
// Set per variant by android/build-variants.sh: 33 for the A13 build (NDK 28), 35 for
|
||||
// the A15 build (NDK 29). The core is compiled against the matching API, so these must
|
||||
// agree -- an APK that installs below its core's target is a dlopen failure at boot.
|
||||
minSdk = (project.findProperty("armsx3.minSdk") as String?)?.toInt() ?: 33
|
||||
targetSdk = 37
|
||||
versionCode = 9
|
||||
versionName = "0.5"
|
||||
versionCode = 17
|
||||
versionName = "0.9.2"
|
||||
|
||||
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
|
||||
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
|
||||
@@ -110,6 +113,51 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
// ARMSX3: fail the build if the bundled ANGLE libraries are not there.
|
||||
//
|
||||
// This check exists because of a specific, expensive bug in ARMSX2: the repo's
|
||||
// blanket `*.so` gitignore rule swallowed the ANGLE prebuilts, they never made it
|
||||
// into release staging, the APK shipped without them, and the core fell back to
|
||||
// the system GLES driver in complete silence. Users reported "ANGLE is broken"
|
||||
// and there was nothing in any log to contradict them.
|
||||
//
|
||||
// jniLibs/.gitignore un-ignores the two files by name. This task is the second
|
||||
// lock: packaging an APK that claims to support ANGLE without shipping ANGLE is a
|
||||
// build error, not a runtime surprise. The core-side counterpart is the loud error
|
||||
// in gl::es::egl_initialize() when the override library is selected but cannot be
|
||||
// dlopen'd; the UI-side counterpart is the MISSING_LIBS line that
|
||||
// MainActivityRuntime.applyAngleEnv logs when the option is on and the .so is not
|
||||
// in nativeLibraryDir.
|
||||
//
|
||||
// The claim being guarded is live in THIS module: RendererBackendSection ->
|
||||
// AngleDriverSection writes Settings.useAngleOpenGL, and applyAngleEnv turns it
|
||||
// into ARMSX2_ANGLE_EGL_LIBRARY. (Both the libraries and this task used to sit in
|
||||
// the stale android/armsx3-app module, which builds nothing that ships -- so the
|
||||
// guard could not fire for the APK it was meant to protect.)
|
||||
val verifyAngleLibs by tasks.registering {
|
||||
val angleLibs = listOf("libEGL_angle.so", "libGLESv2_angle.so")
|
||||
val jniLibDir = file("src/main/jniLibs/arm64-v8a")
|
||||
|
||||
doLast {
|
||||
val missing = angleLibs.filter { !jniLibDir.resolve(it).isFile }
|
||||
if (missing.isNotEmpty()) {
|
||||
throw GradleException(
|
||||
"ANGLE libraries missing from $jniLibDir: ${missing.joinToString(", ")}.\n" +
|
||||
"The OpenGL renderer's ANGLE option cannot work without them and would " +
|
||||
"silently fall back to the system GLES driver.\n" +
|
||||
"They are tracked in git - check them out, or remove the ANGLE option."
|
||||
)
|
||||
}
|
||||
|
||||
angleLibs.forEach {
|
||||
logger.lifecycle("ANGLE: packaging $it (${jniLibDir.resolve(it).length()} bytes)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tasks.matching { it.name.startsWith("merge") && it.name.endsWith("JniLibFolders") }
|
||||
.configureEach { dependsOn(verifyAngleLibs) }
|
||||
|
||||
dependencies {
|
||||
// Discord Social SDK, staged locally rather than pulled from a repo: it is
|
||||
// proprietary and distributed per-application from the developer portal.
|
||||
|
||||
@@ -50,3 +50,44 @@ PPU-4b46d0161ca657ab16b0a779d9062810ea5ea2dd:
|
||||
- [ jumpf, 0x00000000, "RPCS3_HLE_LIBRARY:WaitForSPUsToEmptySNRs" ] # Args: (SPU ID, 3)
|
||||
- [ be32, 0x00000000, 0x38800000 ] # li r4, 0
|
||||
- [ be32, 0x00000000, 0x44000002 ] # sc
|
||||
|
||||
# Tom Clancy's H.A.W.X. 2 (BLES00928) -- boot hang at the first intro video.
|
||||
#
|
||||
# The SPU dies with "Access violation reading location 0x20" in CellSpursKernel0 and
|
||||
# is parked forever (dbg_pause, which nothing in the Android build can clear), so the
|
||||
# emulator looks healthy at a locked 30 fps while the guest is dead. Upstream RPCS3
|
||||
# lists the title as Loadable with no fix but "delete data/movies".
|
||||
#
|
||||
# The title looks up a section named '.reload' in this SPU module. The module is
|
||||
# stripped -- e_shnum = 0 -- so the lookup can never succeed, on hardware either, and
|
||||
# the game is built to cope: the failure path writes 0 to the work descriptor's +0x10
|
||||
# field, and this very module tests that field to skip the overlay load:
|
||||
#
|
||||
# 03224 lqr r8,0x1b810 ; r8 = desc[+0x10]
|
||||
# 0322c brz r8,0x32cc ; == 0 -> skip
|
||||
#
|
||||
# A bump allocator on the PPU side then runs over the field unconditionally --
|
||||
# (0 - 0x10) & ~0xF = 0xfffffff0 -- destroying the sentinel. The guard no longer
|
||||
# fires, so the SPU issues GET lsa=0 ea=0 size=0x4000, a transfer that would have
|
||||
# overwritten the running SPURS kernel had it succeeded.
|
||||
#
|
||||
# This makes the overlay routine at LS 0x3208 return immediately, which is what the
|
||||
# surviving guard would have caused anyway. Safe because the section it needs cannot
|
||||
# exist in a stripped module. Suppressing the DMA instead does NOT work: the guest
|
||||
# loop waits on data that never arrives and runs away.
|
||||
#
|
||||
# Ps3PatchRepo.BUNDLED must list this, or it is imported but never enabled.
|
||||
|
||||
SPU-42bae8e5d6a9304068ba1c6bbfdc18d656e287a1:
|
||||
Bink overlay skip:
|
||||
Games:
|
||||
"Tom Clancy's H.A.W.X. 2":
|
||||
BLES00928:
|
||||
- "All"
|
||||
Author: Zulux91
|
||||
Patch Version: 1.0
|
||||
Notes: Fixes the boot hang at the first intro video.
|
||||
Patch:
|
||||
# LS 0x3208 is the first instruction of the overlay routine (il r5,0).
|
||||
# Offsets are LS addresses: apply_modification subtracts p_vaddr (0x3000).
|
||||
- [ be32, 0x3208, 0x35000000 ] # bi lr -- return immediately
|
||||
|
||||
@@ -98,7 +98,6 @@
|
||||
"app.bgColor.rgb": "Ciclo RGB",
|
||||
"app.bgColor.rgb.desc": "Desvie continuamente o fundo através do espectro de cores, como periféricos RGB. Substitui a cor fixa abaixo. Mesma limitação acima: nenhum efeito onde o plano de fundo substituto está em uso.",
|
||||
"app.blockHome": "Bloquear botão Home durante o jogo",
|
||||
"app.blockHome.desc": "Fixa a tela enquanto o jogo é executado, para que o botão Home ou Guide do controle não possa ser minimizado ",
|
||||
"app.bootLogo": "Animação de inicialização",
|
||||
"app.bootLogo.desc": "Reproduza o vídeo de introdução do ARMSX3 quando o aplicativo for iniciado.",
|
||||
"app.clearCache": "Limpar dados em cache",
|
||||
@@ -414,13 +413,19 @@
|
||||
"overlay.uiSize.description": "Dimensiona o preenchimento do menu/biblioteca e os tamanhos de controle. 100% = padrão.",
|
||||
"overlay.uiSize.label": "Tamanho da IU (bordas)",
|
||||
"packages.description": "Instale um jogo, atualização ou DLC .pkg, ou um arquivo de licença .rap. Alguns jogos precisam de ambos: o .pkg contém o conteúdo e o .rap o desbloqueia. Os títulos instalados são adicionados à sua biblioteca automaticamente, e as atualizações e DLC precisam do jogo base instalado primeiro.",
|
||||
"packages.install.copyFailed": "Não foi possível copiar %s desse armazenamento. Se a unidade foi desconectada ou não havia espaço suficiente para a cópia, tente novamente com o arquivo no armazenamento interno.",
|
||||
"packages.install.done": "Instalado. Ele aparecerá na sua biblioteca na próxima digitalização.",
|
||||
"packages.install.failed": "Falha na instalação. O arquivo pode estar criptografado, incompleto ou não ser um pacote PS3.",
|
||||
"packages.install.noRoom": "Não há espaço livre suficiente para instalar %s. Esse armazenamento não pode ser lido diretamente, então o arquivo precisa ser copiado primeiro — libere espaço ou mova o arquivo para o armazenamento interno ou para um cartão SD.",
|
||||
"packages.install.unreadable": "Não foi possível abrir %s. O aplicativo que fornece esse armazenamento pode ter perdido o acesso a ele — reabra a unidade e selecione o arquivo novamente.",
|
||||
"packages.installed.header": "Títulos instalados",
|
||||
"packages.installing": "Instalando. Pacotes grandes podem demorar alguns minutos.",
|
||||
"packages.installingFile": "Instalando %s",
|
||||
"packages.licences.header": "Licenças instaladas",
|
||||
"packages.multiHint": "Toque em vários arquivos para selecionar todos e confirme: as partes de um jogo dividido ou um jogo junto com sua licença .rap.",
|
||||
"packages.reading": "Lendo %s",
|
||||
"packages.select.action": "Escolha o arquivo",
|
||||
"packages.select.external": "Escolher no USB ou cartão SD",
|
||||
"packages.select.title": "Selecione um arquivo .pkg ou .rap",
|
||||
"packages.title": "Instalar pacote",
|
||||
"packages.uninstall": "Desinstalar",
|
||||
@@ -511,7 +516,6 @@
|
||||
"pad.players.help": "O PS3 possui sete portas de controle e nenhum multitap, portanto, até sete pads funcionam sem configuração. Conecte-os antes do lançamento – a ordem em que eles pressionam um botão pela primeira vez é a ordem em que são atribuídos.",
|
||||
"pad.pressButton": "Aperte um botão...",
|
||||
"pad.pressControllerButton": "Pressione um botão do controlador…",
|
||||
"pad.pressureAmount.description": "Quão forte o modificador de pressão pressiona, para jogos sensíveis à pressão DualShock 2 ",
|
||||
"pad.pressureAmount.label": "Quantidade do modificador de pressão",
|
||||
"pad.rightStick.description": "O que o botão analógico direito envia: Analógico (padrão), Face ou Personalizado (vincule cada direção abaixo).",
|
||||
"pad.rightStick.invertX.description": "Espelhe o controle direito horizontalmente - corrige \"esquerda é direita\".",
|
||||
@@ -617,7 +621,7 @@
|
||||
"perf.llvmThreads.description": "Quantos módulos PS3 são compilados ao mesmo tempo quando um jogo é inicializado pela primeira vez. Auto usa todos os núcleos da CPU, que é mais rápido, mas precisa de muita memória – o suficiente para que grandes jogos possam executar o dispositivo e fechá-lo. Reduza este valor se um jogo fechar no meio de \"Compilando Módulos PPU\".",
|
||||
"perf.llvmThreads.label": "Máximo de threads de compilação LLVM",
|
||||
"perf.maxSpursThreads.description": "Limita quantos encadeamentos SPURS são executados por grupo de encadeamentos. 6 é preciso em termos de hardware. Reduzi-lo é um hack que pode ajudar jogos mal encadeados em dispositivos com poucos núcleos, correndo o risco de quebrar outros.",
|
||||
"perf.maxSpursThreads.label": "Max SPURS Threads",
|
||||
"perf.maxSpursThreads.label": "Máximo de threads SPURS",
|
||||
"perf.ppuDecoder.description": "Como é executada a CPU principal (PPU) do PS3. O LLVM recompila o PowerPC para o ARM64 nativo e é enormemente mais rápido - mantenha-o, a menos que você esteja depurando. O intérprete serve apenas para diagnosticar um jogo que o LLVM está errado.",
|
||||
"perf.ppuDecoder.label": "Decodificador PPU",
|
||||
"perf.preferredSpuThreads.description": "Quantos threads de CPU estão reservados para trabalho pesado simultâneo de SPU. Auto permite que o RPCS3 decida a partir de sua contagem de núcleos, o que geralmente ocorre em um dispositivo portátil. Definir um valor muito alto deixa o PPU sem energia.",
|
||||
@@ -723,7 +727,6 @@
|
||||
"renderer.clearShaderCache.alreadyEmpty": "O cache do shader já está vazio.",
|
||||
"renderer.clearShaderCache.description": "Limpa os caches de shader/pipeline Vulkan + GL compilados. Use se um jogo for corrompido após uma troca ou atualização de driver – a próxima inicialização os reconstruirá de forma limpa.",
|
||||
"renderer.clearShaderCache.label": "Limpar cache do sombreador",
|
||||
"renderer.coalesceRenderPasses.description": "Agrupa empates consecutivos para o mesmo alvo em uma passagem de renderização. Ajuda no agrupamento de GPUs ",
|
||||
"renderer.consoleAspect.description": "O aspecto que o PS3 emulado reporta ao jogo. O console sempre sinalizou 4:3 ou 16:9, então essas são as únicas opções reais - Auto deixa isso para o jogo. É para isso que o jogo serve; como ele é ajustado à SUA tela é a configuração abaixo.",
|
||||
"renderer.consoleAspect.label": "Proporção do console",
|
||||
"renderer.disableZcull.description": "Ignora totalmente as consultas de oclusão. Mais rápido, mas objetos que deveriam estar ocultos podem aparecer e sair - um hack de velocidade, não uma solução.",
|
||||
@@ -810,8 +813,8 @@
|
||||
"renderer.shaderChain.params.resetAll.confirmBody": "Cada parâmetro retorna aos padrões do próprio preset. Suas alterações aqui não podem ser desfeitas – se você quiser mantê-las, cancele e use “Salvar como nova predefinição” primeiro.",
|
||||
"renderer.shaderChain.params.resetAll.confirmTitle": "Redefinir todos os parâmetros?",
|
||||
"renderer.shaderChain.params.saveAs": "Salvar como nova predefinição…",
|
||||
"renderer.shaderChain.pass": "passar",
|
||||
"renderer.shaderChain.passes": "passes",
|
||||
"renderer.shaderChain.pass": "etapa",
|
||||
"renderer.shaderChain.passes": "etapas",
|
||||
"renderer.shaderChain.passesUnknown": "custo desconhecido",
|
||||
"renderer.shaderChain.preset.label": "Predefinição de sombreador",
|
||||
"renderer.shaderChain.preset.none": "Nenhum",
|
||||
@@ -877,9 +880,7 @@
|
||||
"savestate.delete.title": "Excluir estado salvo",
|
||||
"savestate.empty.description": "Crie um estado de salvamento enquanto o jogo está em execução e gerencie-o ou faça backup aqui.",
|
||||
"savestate.empty.title": "Ainda não há estados salvos",
|
||||
"savestate.error.hardcore": "Os estados de salvamento são desativados enquanto o modo Hardcore RetroAchievements está ativado. Desligue o Hardcore ",
|
||||
"savestate.error.load": "Não foi possível carregar esse slot.",
|
||||
"savestate.error.memcardBusy": "O jogo ainda está gravando dados salvos, então o estado não foi salvo.",
|
||||
"savestate.error.save": "Não foi possível salvar nesse slot. Verifique o log de @@ANDROID_SAVESTATE@@.",
|
||||
"savestate.hint": "Escolha um slot. Segure um slot ou use o botão de lixeira para excluí-lo.",
|
||||
"savestate.import": "Importar",
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user