mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-04-20 19:45:20 +00:00
Merge branch 'master' into idm_remove
This commit is contained in:
commit
778dddd0a6
151 changed files with 3321 additions and 1052 deletions
2
3rdparty/FAudio
vendored
2
3rdparty/FAudio
vendored
|
@ -1 +1 @@
|
|||
Subproject commit b7c2e109ea86b82109244c9c4569ce9ad0c884df
|
||||
Subproject commit 707114aef2907793644d4067a6e7b09b51502ca9
|
2
3rdparty/libpng/libpng
vendored
2
3rdparty/libpng/libpng
vendored
|
@ -1 +1 @@
|
|||
Subproject commit f5e92d76973a7a53f517579bc95d61483bf108c0
|
||||
Subproject commit 51f5bd68b9b806d2c92b4318164d28b49357da31
|
2
3rdparty/libsdl-org/SDL
vendored
2
3rdparty/libsdl-org/SDL
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 9c821dc21ccbd69b2bda421fdb35cb4ae2da8f5e
|
||||
Subproject commit fa24d868ac2f8fd558e4e914c9863411245db8fd
|
2
3rdparty/pugixml
vendored
2
3rdparty/pugixml
vendored
|
@ -1 +1 @@
|
|||
Subproject commit db78afc2b7d8f043b4bc6b185635d949ea2ed2a8
|
||||
Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38
|
3
3rdparty/wolfssl/CMakeLists.txt
vendored
3
3rdparty/wolfssl/CMakeLists.txt
vendored
|
@ -22,6 +22,5 @@ else()
|
|||
|
||||
add_subdirectory(wolfssl EXCLUDE_FROM_ALL)
|
||||
|
||||
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP)
|
||||
target_compile_definitions(wolfssl PUBLIC FP_MAX_BITS=8192)
|
||||
target_compile_definitions(wolfssl PUBLIC WOLFSSL_DES_ECB HAVE_WRITE_DUP FP_MAX_BITS=8192 WOLFSSL_NO_OPTIONS_H)
|
||||
endif()
|
||||
|
|
2
3rdparty/wolfssl/wolfssl
vendored
2
3rdparty/wolfssl/wolfssl
vendored
|
@ -1 +1 @@
|
|||
Subproject commit bdd62314f00fca0e216bf8c963c8eeff6327e0cb
|
||||
Subproject commit 239b85c80438bf60d9a5b9e0ebe9ff097a760d0d
|
91
3rdparty/wolfssl/wolfssl.vcxproj
vendored
91
3rdparty/wolfssl/wolfssl.vcxproj
vendored
|
@ -139,6 +139,94 @@
|
|||
<ItemGroup>
|
||||
<ClInclude Include="extra\win32\user_settings.h" />
|
||||
<ClInclude Include="wolfssl\resource.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\aes.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\arc4.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\asn.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\asn_public.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\blake2-impl.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\blake2-int.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\blake2.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\camellia.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\chacha.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\chacha20_poly1305.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\cmac.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\coding.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\compress.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\cpuid.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\cryptocb.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\curve25519.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\curve448.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\des3.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\dh.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\dilithium.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\dsa.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ecc.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\eccsi.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ed25519.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ed448.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\error-crypt.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ext_kyber.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ext_lms.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ext_xmss.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\falcon.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\fe_448.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\fe_operations.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\fips_test.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ge_448.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ge_operations.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\hash.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\hmac.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\hpke.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\integer.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\kdf.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\kyber.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\lms.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\logging.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\md2.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\md4.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\md5.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\memory.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\mem_track.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\misc.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\mpi_class.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\mpi_superclass.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\pkcs11.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\pkcs12.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\pkcs7.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\poly1305.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\pwdbased.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\random.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\rc2.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\ripemd.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\rsa.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sakke.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\selftest.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\settings.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sha.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sha256.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sha3.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sha512.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\signature.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\siphash.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sm2.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sm3.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sm4.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sp.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sphincs.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\sp_int.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\srp.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\tfm.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\types.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\visibility.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_encrypt.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_kyber.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_lms.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_pkcs11.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_port.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wc_xmss.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wolfevent.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\wolfmath.h" />
|
||||
<ClInclude Include="wolfssl\wolfssl\wolfcrypt\xmss.h" />
|
||||
<CustomBuild Include="wolfssl\wolfcrypt\src\aes_asm.asm">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">false</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='DLL Debug|x64'">false</ExcludedFromBuild>
|
||||
|
@ -160,6 +248,9 @@
|
|||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="wolfssl\wolfssl\wolfcrypt\include.am" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
|
|
2
3rdparty/xxHash
vendored
2
3rdparty/xxHash
vendored
|
@ -1 +1 @@
|
|||
Subproject commit bbb27a5efb85b92a0486cf361a8635715a53f6ba
|
||||
Subproject commit e626a72bc2321cd320e953a0ccf1584cad60f363
|
|
@ -21,7 +21,7 @@ using namespace std::literals::string_literals;
|
|||
#include <cwchar>
|
||||
#include <Windows.h>
|
||||
|
||||
static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
|
||||
static std::unique_ptr<wchar_t[]> to_wchar(std::string_view source)
|
||||
{
|
||||
// String size + null terminator
|
||||
const usz buf_size = source.size() + 1;
|
||||
|
@ -44,7 +44,7 @@ static std::unique_ptr<wchar_t[]> to_wchar(const std::string& source)
|
|||
std::memcpy(buffer.get() + 32768 + 4, L"UNC\\", 4 * sizeof(wchar_t));
|
||||
}
|
||||
|
||||
ensure(MultiByteToWideChar(CP_UTF8, 0, source.c_str(), size, buffer.get() + 32768 + (unc ? 8 : 4), size)); // "to_wchar"
|
||||
ensure(MultiByteToWideChar(CP_UTF8, 0, source.data(), size, buffer.get() + 32768 + (unc ? 8 : 4), size)); // "to_wchar"
|
||||
|
||||
// Canonicalize wide path (replace '/', ".", "..", \\ repetitions, etc)
|
||||
ensure(GetFullPathNameW(buffer.get() + 32768, 32768, buffer.get(), nullptr) - 1 < 32768 - 1); // "to_wchar"
|
||||
|
|
|
@ -33,7 +33,8 @@ enum class thread_state : u32
|
|||
aborting = 1, // The thread has been joined in the destructor or explicitly aborted
|
||||
errored = 2, // Set after the emergency_exit call
|
||||
finished = 3, // Final state, always set at the end of thread execution
|
||||
mask = 3
|
||||
mask = 3,
|
||||
destroying_context = 7, // Special value assigned to destroy data explicitly before the destructor
|
||||
};
|
||||
|
||||
template <class Context>
|
||||
|
@ -702,14 +703,17 @@ public:
|
|||
thread::m_sync.notify_all();
|
||||
}
|
||||
|
||||
if (s == thread_state::finished)
|
||||
if (s == thread_state::finished || s == thread_state::destroying_context)
|
||||
{
|
||||
// This participates in emulation stopping, use destruction-alike semantics
|
||||
thread::join(true);
|
||||
}
|
||||
|
||||
if (s == thread_state::destroying_context)
|
||||
{
|
||||
if constexpr (std::is_assignable_v<Context&, thread_state>)
|
||||
{
|
||||
static_cast<Context&>(*this) = thread_state::finished;
|
||||
static_cast<Context&>(*this) = thread_state::destroying_context;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1445,6 +1445,8 @@ static usz apply_modification(std::vector<u32>& applied, patch_engine::patch_inf
|
|||
|
||||
void patch_engine::apply(std::vector<u32>& applied_total, const std::string& name, std::function<u8*(u32, u32)> mem_translate, u32 filesz, u32 min_addr)
|
||||
{
|
||||
// applied_total may be non-empty, do not clear it
|
||||
|
||||
if (!m_map.contains(name))
|
||||
{
|
||||
return;
|
||||
|
@ -1593,6 +1595,9 @@ void patch_engine::apply(std::vector<u32>& applied_total, const std::string& nam
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure consistent order
|
||||
std::sort(applied_total.begin(), applied_total.end());
|
||||
}
|
||||
|
||||
void patch_engine::unload(const std::string& name)
|
||||
|
|
|
@ -14,6 +14,7 @@ enum class cheat_type : u8
|
|||
signed_16_cheat,
|
||||
signed_32_cheat,
|
||||
signed_64_cheat,
|
||||
float_32_cheat,
|
||||
max
|
||||
};
|
||||
|
||||
|
|
|
@ -49,12 +49,11 @@ std::string rXmlNode::GetName()
|
|||
return {};
|
||||
}
|
||||
|
||||
std::string rXmlNode::GetAttribute(const std::string& name)
|
||||
std::string rXmlNode::GetAttribute(std::string_view name)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
const auto pred = [&name](const pugi::xml_attribute& attr) { return (name == attr.name()); };
|
||||
if (const pugi::xml_attribute attr = handle.find_attribute(pred))
|
||||
if (const pugi::xml_attribute attr = handle.attribute(name))
|
||||
{
|
||||
if (const pugi::char_t* value = attr.value())
|
||||
{
|
||||
|
@ -86,7 +85,7 @@ rXmlDocument::rXmlDocument()
|
|||
{
|
||||
}
|
||||
|
||||
pugi::xml_parse_result rXmlDocument::Read(const std::string& data)
|
||||
pugi::xml_parse_result rXmlDocument::Read(std::string_view data)
|
||||
{
|
||||
if (handle)
|
||||
{
|
||||
|
|
|
@ -23,7 +23,7 @@ struct rXmlNode
|
|||
std::shared_ptr<rXmlNode> GetChildren();
|
||||
std::shared_ptr<rXmlNode> GetNext();
|
||||
std::string GetName();
|
||||
std::string GetAttribute(const std::string& name);
|
||||
std::string GetAttribute(std::string_view name);
|
||||
std::string GetNodeContent();
|
||||
|
||||
pugi::xml_node handle{};
|
||||
|
@ -34,7 +34,7 @@ struct rXmlDocument
|
|||
rXmlDocument();
|
||||
rXmlDocument(const rXmlDocument& other) = delete;
|
||||
rXmlDocument &operator=(const rXmlDocument& other) = delete;
|
||||
pugi::xml_parse_result Read(const std::string& data);
|
||||
pugi::xml_parse_result Read(std::string_view data);
|
||||
virtual std::shared_ptr<rXmlNode> GetRoot();
|
||||
|
||||
pugi::xml_document handle{};
|
||||
|
|
|
@ -164,7 +164,7 @@ elseif(WIN32)
|
|||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $<TARGET_FILE_DIR:rpcs3>/GuiConfigs
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $<TARGET_FILE_DIR:rpcs3>/git
|
||||
COMMAND "${WINDEPLOYQT_EXECUTABLE}" --no-compiler-runtime --no-opengl-sw --no-patchqt
|
||||
--no-translations --no-quick --no-system-d3d-compiler --no-quick-import
|
||||
--no-translations --no-system-d3d-compiler --no-quick-import
|
||||
--plugindir "$<IF:$<CXX_COMPILER_ID:MSVC>,$<TARGET_FILE_DIR:rpcs3>/plugins,$<TARGET_FILE_DIR:rpcs3>/share/qt6/plugins>"
|
||||
--verbose 0 "$<TARGET_FILE:rpcs3>")
|
||||
endif()
|
||||
|
|
|
@ -172,6 +172,7 @@ target_link_libraries(rpcs3_emu
|
|||
|
||||
# Cell
|
||||
target_sources(rpcs3_emu PRIVATE
|
||||
Cell/ErrorCodes.cpp
|
||||
Cell/MFC.cpp
|
||||
Cell/PPUAnalyser.cpp
|
||||
Cell/PPUDisAsm.cpp
|
||||
|
@ -557,6 +558,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||
RSX/Program/CgBinaryFragmentProgram.cpp
|
||||
RSX/Program/CgBinaryVertexProgram.cpp
|
||||
RSX/Program/FragmentProgramDecompiler.cpp
|
||||
RSX/Program/FragmentProgramRegister.cpp
|
||||
RSX/Program/GLSLCommon.cpp
|
||||
RSX/Program/ProgramStateCache.cpp
|
||||
RSX/Program/program_util.cpp
|
||||
|
|
91
rpcs3/Emu/Cell/ErrorCodes.cpp
Normal file
91
rpcs3/Emu/Cell/ErrorCodes.cpp
Normal file
|
@ -0,0 +1,91 @@
|
|||
#include "stdafx.h"
|
||||
#include "ErrorCodes.h"
|
||||
#include "PPUThread.h"
|
||||
#include "SPUThread.h"
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
|
||||
bool g_log_all_errors = false;
|
||||
|
||||
s32 error_code::error_report(s32 result, const logs::message* channel, const char* fmt, const fmt_type_info* sup, const u64* args)
|
||||
{
|
||||
static thread_local std::string g_tls_error_str;
|
||||
static thread_local std::unordered_map<std::string, usz> g_tls_error_stats;
|
||||
|
||||
if (!channel)
|
||||
{
|
||||
channel = &sys_log.error;
|
||||
}
|
||||
|
||||
if (!sup && !args)
|
||||
{
|
||||
if (!fmt)
|
||||
{
|
||||
// Report and clean error state
|
||||
for (auto&& pair : g_tls_error_stats)
|
||||
{
|
||||
if (pair.second > 3)
|
||||
{
|
||||
channel->operator()("Stat: %s [x%u]", pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
g_tls_error_stats.clear();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(fmt);
|
||||
|
||||
const char* func = "Unknown function";
|
||||
|
||||
if (auto ppu = get_current_cpu_thread<ppu_thread>())
|
||||
{
|
||||
if (auto current = ppu->current_function)
|
||||
{
|
||||
func = current;
|
||||
}
|
||||
}
|
||||
else if (auto spu = get_current_cpu_thread<spu_thread>())
|
||||
{
|
||||
if (auto current = spu->current_func; current && spu->start_time)
|
||||
{
|
||||
func = current;
|
||||
}
|
||||
}
|
||||
|
||||
// Format log message (use preallocated buffer)
|
||||
g_tls_error_str.clear();
|
||||
|
||||
fmt::append(g_tls_error_str, "'%s' failed with 0x%08x", func, result);
|
||||
|
||||
// Add spacer between error and fmt if necessary
|
||||
if (fmt[0] != ' ')
|
||||
g_tls_error_str += " : ";
|
||||
|
||||
fmt::raw_append(g_tls_error_str, fmt, sup, args);
|
||||
|
||||
// Update stats and check log threshold
|
||||
|
||||
if (g_log_all_errors) [[unlikely]]
|
||||
{
|
||||
if (!g_tls_error_stats.empty())
|
||||
{
|
||||
// Report and clean error state
|
||||
error_report(0, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
channel->operator()("%s", g_tls_error_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto stat = ++g_tls_error_stats[g_tls_error_str];
|
||||
|
||||
if (stat <= 3)
|
||||
{
|
||||
channel->operator()("%s [%u]", g_tls_error_str, stat);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
|
@ -270,35 +270,75 @@ void AtracXdecContext::exec(ppu_thread& ppu)
|
|||
decoder.init_avcodec();
|
||||
}
|
||||
|
||||
switch (savestate)
|
||||
{
|
||||
case atracxdec_state::initial: break;
|
||||
case atracxdec_state::waiting_for_cmd: goto label1_wait_for_cmd_state;
|
||||
case atracxdec_state::checking_run_thread_1: goto label2_check_run_thread_1_state;
|
||||
case atracxdec_state::executing_cmd: goto label3_execute_cmd_state;
|
||||
case atracxdec_state::waiting_for_output: goto label4_wait_for_output_state;
|
||||
case atracxdec_state::checking_run_thread_2: goto label5_check_run_thread_2_state;
|
||||
case atracxdec_state::decoding: goto label6_decode_state;
|
||||
}
|
||||
|
||||
for (;;cmd_counter++)
|
||||
{
|
||||
cellAtracXdec.trace("Command counter: %llu, waiting for next command...", cmd_counter);
|
||||
|
||||
if (!skip_getting_command)
|
||||
for (;;)
|
||||
{
|
||||
lv2_obj::sleep(ppu);
|
||||
std::lock_guard lock{queue_mutex};
|
||||
savestate = atracxdec_state::initial;
|
||||
|
||||
while (cmd_queue.empty() && !ppu.is_stopped())
|
||||
{
|
||||
lv2_obj::sleep(ppu);
|
||||
queue_not_empty.wait(queue_mutex, 20000);
|
||||
}
|
||||
ensure(sys_mutex_lock(ppu, queue_mutex, 0) == CELL_OK);
|
||||
|
||||
if (ppu.is_stopped())
|
||||
{
|
||||
ppu.state += cpu_flag::again;
|
||||
return;
|
||||
}
|
||||
|
||||
cmd_queue.pop(cmd);
|
||||
|
||||
if (!run_thread)
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cmd_queue.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
savestate = atracxdec_state::waiting_for_cmd;
|
||||
label1_wait_for_cmd_state:
|
||||
|
||||
ensure(sys_cond_wait(ppu, queue_not_empty, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
|
||||
}
|
||||
|
||||
cmd_queue.pop(cmd);
|
||||
|
||||
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
|
||||
|
||||
savestate = atracxdec_state::checking_run_thread_1;
|
||||
label2_check_run_thread_1_state:
|
||||
|
||||
ensure(sys_mutex_lock(ppu, run_thread_mutex, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!run_thread)
|
||||
{
|
||||
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
|
||||
|
||||
savestate = atracxdec_state::executing_cmd;
|
||||
label3_execute_cmd_state:
|
||||
|
||||
cellAtracXdec.trace("Command type: %d", static_cast<u32>(cmd.type.get()));
|
||||
|
||||
switch (cmd.type)
|
||||
|
@ -327,8 +367,6 @@ void AtracXdecContext::exec(ppu_thread& ppu)
|
|||
}
|
||||
case AtracXdecCmdType::end_seq:
|
||||
{
|
||||
skip_getting_command = true;
|
||||
|
||||
// Block savestate creation during callbacks
|
||||
std::unique_lock savestate_lock{g_fxo->get<hle_locks_t>(), std::try_to_lock};
|
||||
|
||||
|
@ -338,41 +376,59 @@ void AtracXdecContext::exec(ppu_thread& ppu)
|
|||
return;
|
||||
}
|
||||
|
||||
skip_getting_command = false;
|
||||
|
||||
// Doesn't do anything else
|
||||
notify_seq_done.cbFunc(ppu, notify_seq_done.cbArg);
|
||||
break;
|
||||
}
|
||||
case AtracXdecCmdType::decode_au:
|
||||
{
|
||||
skip_getting_command = true;
|
||||
|
||||
ensure(!!cmd.au_start_addr); // Not checked on LLE
|
||||
|
||||
cellAtracXdec.trace("Waiting for output to be consumed...");
|
||||
|
||||
lv2_obj::sleep(ppu);
|
||||
std::unique_lock output_mutex_lock{output_mutex};
|
||||
ensure(sys_mutex_lock(ppu, output_mutex, 0) == CELL_OK);
|
||||
|
||||
while (output_locked && !ppu.is_stopped())
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
lv2_obj::sleep(ppu);
|
||||
output_consumed.wait(output_mutex, 20000);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ppu.is_stopped())
|
||||
while (output_locked)
|
||||
{
|
||||
savestate = atracxdec_state::waiting_for_output;
|
||||
label4_wait_for_output_state:
|
||||
|
||||
ensure(sys_cond_wait(ppu, output_consumed, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cellAtracXdec.trace("Output consumed");
|
||||
|
||||
savestate = atracxdec_state::checking_run_thread_2;
|
||||
label5_check_run_thread_2_state:
|
||||
|
||||
ensure(sys_mutex_lock(ppu, run_thread_mutex, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
ppu.state += cpu_flag::again;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!run_thread)
|
||||
{
|
||||
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
|
||||
ensure(sys_mutex_unlock(ppu, output_mutex) == CELL_OK);
|
||||
return;
|
||||
}
|
||||
|
||||
cellAtracXdec.trace("Output consumed");
|
||||
ensure(sys_mutex_unlock(ppu, run_thread_mutex) == CELL_OK);
|
||||
|
||||
savestate = atracxdec_state::decoding;
|
||||
label6_decode_state:
|
||||
|
||||
u32 error = CELL_OK;
|
||||
|
||||
|
@ -578,14 +634,12 @@ void AtracXdecContext::exec(ppu_thread& ppu)
|
|||
return;
|
||||
}
|
||||
|
||||
skip_getting_command = false;
|
||||
|
||||
// au_done and pcm_out callbacks are always called after a decode command, even if an error occurred
|
||||
// The output always has to be consumed as well
|
||||
notify_au_done.cbFunc(ppu, cmd.pcm_handle, notify_au_done.cbArg);
|
||||
|
||||
output_locked = true;
|
||||
output_mutex_lock.unlock();
|
||||
ensure(sys_mutex_unlock(ppu, output_mutex) == CELL_OK);
|
||||
|
||||
const u32 output_size = decoded_samples_num * (decoder.bw_pcm & 0x7fu) * decoder.nch_out;
|
||||
|
||||
|
@ -614,29 +668,46 @@ void AtracXdecContext::exec(ppu_thread& ppu)
|
|||
template <AtracXdecCmdType type>
|
||||
error_code AtracXdecContext::send_command(ppu_thread& ppu, auto&&... args)
|
||||
{
|
||||
ppu.state += cpu_flag::wait;
|
||||
auto& savestate = *ppu.optional_savestate_state;
|
||||
const bool signal = savestate.try_read<bool>().second;
|
||||
savestate.clear();
|
||||
|
||||
if (!signal)
|
||||
{
|
||||
std::lock_guard lock{queue_mutex};
|
||||
ensure(sys_mutex_lock(ppu, queue_mutex, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
if constexpr (type == AtracXdecCmdType::close)
|
||||
{
|
||||
// Close command is only sent if the queue is empty on LLE
|
||||
if (!cmd_queue.empty())
|
||||
{
|
||||
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (cmd_queue.full())
|
||||
{
|
||||
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
|
||||
return CELL_ADEC_ERROR_ATX_BUSY;
|
||||
}
|
||||
|
||||
cmd_queue.emplace(std::forward<AtracXdecCmdType>(type), std::forward<decltype(args)>(args)...);
|
||||
|
||||
ensure(sys_mutex_unlock(ppu, queue_mutex) == CELL_OK);
|
||||
}
|
||||
|
||||
queue_not_empty.notify_one();
|
||||
ensure(sys_cond_signal(ppu, queue_not_empty) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
savestate(true);
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
@ -699,6 +770,29 @@ error_code _CellAdecCoreOpOpenExt_atracx(ppu_thread& ppu, vm::ptr<AtracXdecConte
|
|||
write_to_ptr(handle.get_ptr(), AtracXdecContext(notifyAuDone, notifyAuDoneArg, notifyPcmOut, notifyPcmOutArg, notifyError, notifyErrorArg, notifySeqDone, notifySeqDoneArg,
|
||||
vm::bptr<u8>::make(handle.addr() + utils::align(static_cast<u32>(sizeof(AtracXdecContext)), 0x80) + ATXDEC_SPURS_STRUCTS_SIZE)));
|
||||
|
||||
const vm::var<sys_mutex_attribute_t> mutex_attr{{ SYS_SYNC_PRIORITY, SYS_SYNC_NOT_RECURSIVE, SYS_SYNC_NOT_PROCESS_SHARED, SYS_SYNC_NOT_ADAPTIVE, 0, 0, 0, { "_atd001"_u64 } }};
|
||||
const vm::var<sys_cond_attribute_t> cond_attr{{ SYS_SYNC_NOT_PROCESS_SHARED, 0, 0, { "_atd002"_u64 } }};
|
||||
|
||||
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::queue_mutex), mutex_attr) == CELL_OK);
|
||||
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::queue_not_empty), handle->queue_mutex, cond_attr) == CELL_OK);
|
||||
|
||||
mutex_attr->name_u64 = "_atd003"_u64;
|
||||
cond_attr->name_u64 = "_atd004"_u64;
|
||||
|
||||
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::run_thread_mutex), mutex_attr) == CELL_OK);
|
||||
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::run_thread_cond), handle->run_thread_mutex, cond_attr) == CELL_OK);
|
||||
|
||||
mutex_attr->name_u64 = "_atd005"_u64;
|
||||
cond_attr->name_u64 = "_atd006"_u64;
|
||||
|
||||
ensure(sys_mutex_create(ppu, handle.ptr(&AtracXdecContext::output_mutex), mutex_attr) == CELL_OK);
|
||||
ensure(sys_cond_create(ppu, handle.ptr(&AtracXdecContext::output_consumed), handle->output_mutex, cond_attr) == CELL_OK);
|
||||
|
||||
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
|
||||
handle->output_locked = false;
|
||||
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
|
||||
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
|
||||
|
||||
const vm::var<char[]> _name = vm::make_str("HLE ATRAC3plus decoder");
|
||||
const auto entry = g_fxo->get<ppu_function_manager>().func_addr(FIND_FUNC(atracXdecEntry));
|
||||
ppu_execute<&sys_ppu_thread_create>(ppu, handle.ptr(&AtracXdecContext::thread_id), entry, handle.addr(), +res->ppuThreadPriority, +res->ppuThreadStackSize, SYS_PPU_THREAD_CREATE_JOINABLE, +_name);
|
||||
|
@ -725,29 +819,32 @@ error_code _CellAdecCoreOpClose_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext
|
|||
return {};
|
||||
}
|
||||
|
||||
ppu.state += cpu_flag::wait;
|
||||
|
||||
cellAtracXdec.notice("_CellAdecCoreOpClose_atracx(handle=*0x%x)", handle);
|
||||
|
||||
ensure(!!handle); // Not checked on LLE
|
||||
|
||||
ensure(sys_mutex_lock(ppu, handle->run_thread_mutex, 0) == CELL_OK);
|
||||
handle->run_thread = false;
|
||||
ensure(sys_mutex_unlock(ppu, handle->run_thread_mutex) == CELL_OK);
|
||||
|
||||
handle->send_command<AtracXdecCmdType::close>(ppu);
|
||||
|
||||
{
|
||||
std::lock_guard lock{handle->output_mutex};
|
||||
handle->output_locked = false;
|
||||
}
|
||||
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
|
||||
handle->output_locked = false;
|
||||
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
|
||||
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
|
||||
|
||||
handle->output_consumed.notify_one();
|
||||
vm::var<u64> thread_ret;
|
||||
ensure(sys_ppu_thread_join(ppu, static_cast<u32>(handle->thread_id), +thread_ret) == CELL_OK);
|
||||
|
||||
if (vm::var<u64> ret; sys_ppu_thread_join(ppu, static_cast<u32>(handle->thread_id), +ret) != CELL_OK)
|
||||
{
|
||||
// Other thread already closed the decoder
|
||||
return CELL_ADEC_ERROR_FATAL;
|
||||
}
|
||||
error_code ret = sys_cond_destroy(ppu, handle->queue_not_empty);
|
||||
ret = ret ? ret : sys_cond_destroy(ppu, handle->run_thread_cond);
|
||||
ret = ret ? ret : sys_cond_destroy(ppu, handle->output_consumed);
|
||||
ret = ret ? ret : sys_mutex_destroy(ppu, handle->queue_mutex);
|
||||
ret = ret ? ret : sys_mutex_destroy(ppu, handle->run_thread_mutex);
|
||||
ret = ret ? ret : sys_mutex_destroy(ppu, handle->output_mutex);
|
||||
|
||||
return CELL_OK;
|
||||
return ret != CELL_OK ? static_cast<error_code>(CELL_ADEC_ERROR_FATAL) : CELL_OK;
|
||||
}
|
||||
|
||||
error_code _CellAdecCoreOpStartSeq_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext> handle, vm::cptr<CellAdecParamAtracX> atracxParam)
|
||||
|
@ -808,15 +905,35 @@ error_code _CellAdecCoreOpRealign_atracx(vm::ptr<AtracXdecContext> handle, vm::p
|
|||
|
||||
error_code _CellAdecCoreOpReleasePcm_atracx(ppu_thread& ppu, vm::ptr<AtracXdecContext> handle, s32 pcmHandle, vm::cptr<void> outBuffer)
|
||||
{
|
||||
ppu.state += cpu_flag::wait;
|
||||
|
||||
cellAtracXdec.trace("_CellAdecCoreOpReleasePcm_atracx(handle=*0x%x, pcmHandle=%d, outBuffer=*0x%x)", handle, pcmHandle, outBuffer);
|
||||
|
||||
ensure(!!handle); // Not checked on LLE
|
||||
|
||||
std::lock_guard lock{handle->output_mutex};
|
||||
handle->output_locked = false;
|
||||
handle->output_consumed.notify_one();
|
||||
auto& savestate = *ppu.optional_savestate_state;
|
||||
const bool signal = savestate.try_read<bool>().second;
|
||||
savestate.clear();
|
||||
|
||||
if (!signal)
|
||||
{
|
||||
ensure(sys_mutex_lock(ppu, handle->output_mutex, 0) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
handle->output_locked = false;
|
||||
}
|
||||
|
||||
ensure(sys_cond_signal(ppu, handle->output_consumed) == CELL_OK);
|
||||
|
||||
if (ppu.state & cpu_flag::again)
|
||||
{
|
||||
savestate(true);
|
||||
return {};
|
||||
}
|
||||
|
||||
ensure(sys_mutex_unlock(ppu, handle->output_mutex) == CELL_OK);
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ constexpr int averror_eof = AVERROR_EOF; // Workaround for old-style-cast error
|
|||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include "Utilities/cond.h"
|
||||
#include "cellPamf.h"
|
||||
#include "cellAdec.h"
|
||||
|
||||
|
@ -215,16 +214,28 @@ struct AtracXdecDecoder
|
|||
|
||||
CHECK_SIZE(AtracXdecDecoder, 0xa8);
|
||||
|
||||
// HLE exclusive, for savestates
|
||||
enum class atracxdec_state : u8
|
||||
{
|
||||
initial,
|
||||
waiting_for_cmd,
|
||||
checking_run_thread_1,
|
||||
executing_cmd,
|
||||
waiting_for_output,
|
||||
checking_run_thread_2,
|
||||
decoding
|
||||
};
|
||||
|
||||
struct AtracXdecContext
|
||||
{
|
||||
be_t<u64> thread_id; // sys_ppu_thread_t
|
||||
|
||||
shared_mutex queue_mutex; // sys_mutex_t
|
||||
cond_variable queue_not_empty; // sys_cond_t
|
||||
be_t<u32> queue_mutex; // sys_mutex_t
|
||||
be_t<u32> queue_not_empty; // sys_cond_t
|
||||
AdecCmdQueue<AtracXdecCmd> cmd_queue;
|
||||
|
||||
shared_mutex output_mutex; // sys_mutex_t
|
||||
cond_variable output_consumed; // sys_cond_t
|
||||
be_t<u32> output_mutex; // sys_mutex_t
|
||||
be_t<u32> output_consumed; // sys_cond_t
|
||||
be_t<u32> output_locked = false;
|
||||
|
||||
be_t<u32> run_thread_mutex; // sys_mutex_t
|
||||
|
@ -239,10 +250,10 @@ struct AtracXdecContext
|
|||
const vm::bptr<u8> work_mem;
|
||||
|
||||
// HLE exclusive
|
||||
u64 cmd_counter = 0; // For debugging
|
||||
AtracXdecCmd cmd; // For savestates; if savestate was created while processing a decode command, we need to save the current command
|
||||
b8 skip_getting_command = false; // For savestates; skips getting a new command from the queue
|
||||
b8 skip_next_frame; // Needed to emulate behavior of LLE SPU program, it doesn't output the first frame after a sequence reset or error
|
||||
u64 cmd_counter = 0; // For debugging
|
||||
AtracXdecCmd cmd; // For savestates; if savestate was created while processing a decode command, we need to save the current command
|
||||
atracxdec_state savestate{}; // For savestates
|
||||
b8 skip_next_frame; // Needed to emulate behavior of LLE SPU program, it doesn't output the first frame after a sequence reset or error
|
||||
|
||||
u8 spurs_stuff[58]; // 120 bytes on LLE, pointers to CellSpurs, CellSpursTaskset, etc.
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -96,6 +96,7 @@ struct ppu_module : public Type
|
|||
std::vector<ppu_segment> segs{};
|
||||
std::vector<ppu_segment> secs{};
|
||||
std::vector<ppu_function> funcs{};
|
||||
std::vector<u32> applied_patches;
|
||||
std::deque<std::shared_ptr<void>> allocations;
|
||||
std::map<u32, u32> addr_to_seg_index;
|
||||
|
||||
|
@ -185,7 +186,6 @@ struct main_ppu_module : public ppu_module<T>
|
|||
{
|
||||
u32 elf_entry{};
|
||||
u32 seg0_code_end{};
|
||||
std::vector<u32> applied_patches;
|
||||
|
||||
// Disable inherited savestate ordering
|
||||
void save(utils::serial&) = delete;
|
||||
|
|
|
@ -1947,6 +1947,7 @@ shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c
|
|||
ppu_check_patch_spu_images(*prx, seg);
|
||||
}
|
||||
|
||||
prx->applied_patches = applied;
|
||||
prx->analyse(toc, 0, end, applied, exported_funcs);
|
||||
|
||||
if (!ar && !virtual_load)
|
||||
|
|
|
@ -4898,6 +4898,30 @@ bool ppu_initialize(const ppu_module<lv2_obj>& info, bool check_only, u64 file_s
|
|||
sha1_update(&ctx, ensure(info.get_ptr<const u8>(func.addr)), func.size);
|
||||
}
|
||||
|
||||
if (!workload.empty() && fpos >= info.funcs.size())
|
||||
{
|
||||
// Hash the entire function grouped addresses for the integrity of the symbol resolver function
|
||||
// Potentially occuring during patches
|
||||
// Avoid doing it for files with a single module such as most PRX
|
||||
|
||||
std::vector<be_t<u32>> addrs;
|
||||
|
||||
for (const ppu_function& func : info.funcs)
|
||||
{
|
||||
if (func.size == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
addrs.emplace_back(func.addr - reloc);
|
||||
}
|
||||
|
||||
// Hash its size too
|
||||
addrs.emplace_back(::size32(addrs));
|
||||
|
||||
sha1_update(&ctx, reinterpret_cast<const u8*>(addrs.data()), addrs.size() * sizeof(be_t<u32>));
|
||||
}
|
||||
|
||||
if (false)
|
||||
{
|
||||
const be_t<u64> forced_upd = 3;
|
||||
|
|
|
@ -5481,7 +5481,7 @@ bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t<u64, 64>* range_
|
|||
|
||||
usz spu_thread::register_cache_line_waiter(u32 addr)
|
||||
{
|
||||
const u64 value = u64{compute_rdata_hash32(rdata)} << 32 | raddr;
|
||||
const u64 value = u64{compute_rdata_hash32(rdata)} << 32 | addr;
|
||||
|
||||
for (usz i = 0; i < std::size(g_spu_waiters_by_value); i++)
|
||||
{
|
||||
|
|
|
@ -71,7 +71,7 @@ CellError lv2_cond::on_id_create()
|
|||
|
||||
std::function<void(void*)> lv2_cond::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_cond>(ar));
|
||||
return load_func(make_shared<lv2_cond>(stx::exact_t<utils::serial&>(ar)));
|
||||
}
|
||||
|
||||
void lv2_cond::save(utils::serial& ar)
|
||||
|
|
|
@ -122,7 +122,7 @@ void lv2_config::remove_service_event(u32 id)
|
|||
|
||||
lv2_config_service_event& lv2_config_service_event::operator=(thread_state s) noexcept
|
||||
{
|
||||
if (s == thread_state::finished)
|
||||
if (s == thread_state::destroying_context && !m_destroyed.exchange(true))
|
||||
{
|
||||
if (auto global = g_fxo->try_get<lv2_config>())
|
||||
{
|
||||
|
@ -133,6 +133,23 @@ lv2_config_service_event& lv2_config_service_event::operator=(thread_state s) no
|
|||
return *this;
|
||||
}
|
||||
|
||||
lv2_config_service_event::~lv2_config_service_event() noexcept
|
||||
{
|
||||
operator=(thread_state::destroying_context);
|
||||
}
|
||||
|
||||
lv2_config::~lv2_config() noexcept
|
||||
{
|
||||
for (auto& [key, event] : events)
|
||||
{
|
||||
if (event)
|
||||
{
|
||||
// Avoid collision with lv2_config_service_event destructor
|
||||
event->m_destroyed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// LV2 Config Service Listener
|
||||
bool lv2_config_service_listener::check_service(const lv2_config_service& service) const
|
||||
{
|
||||
|
|
|
@ -161,6 +161,8 @@ public:
|
|||
|
||||
return null_ptr;
|
||||
}
|
||||
|
||||
~lv2_config() noexcept;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -276,7 +278,7 @@ public:
|
|||
|
||||
// Utilities
|
||||
usz get_size() const { return sizeof(sys_config_service_event_t)-1 + data.size(); }
|
||||
shared_ptr<lv2_config_service> get_shared_ptr () const { return idm::get_unlocked<lv2_config_service>(idm_id); }
|
||||
shared_ptr<lv2_config_service> get_shared_ptr () const { return stx::make_shared_from_this<lv2_config_service>(this); }
|
||||
u32 get_id() const { return idm_id; }
|
||||
};
|
||||
|
||||
|
@ -342,7 +344,7 @@ public:
|
|||
|
||||
// Utilities
|
||||
u32 get_id() const { return idm_id; }
|
||||
shared_ptr<lv2_config_service_listener> get_shared_ptr() const { return idm::get_unlocked<lv2_config_service_listener>(idm_id); }
|
||||
shared_ptr<lv2_config_service_listener> get_shared_ptr() const { return stx::make_shared_from_this<lv2_config_service_listener>(this); }
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -360,6 +362,10 @@ class lv2_config_service_event
|
|||
return g_fxo->get<service_event_id>().next_id++;
|
||||
}
|
||||
|
||||
atomic_t<bool> m_destroyed = false;
|
||||
|
||||
friend class lv2_config;
|
||||
|
||||
public:
|
||||
const u32 id;
|
||||
|
||||
|
@ -391,8 +397,7 @@ public:
|
|||
|
||||
// Destructor
|
||||
lv2_config_service_event& operator=(thread_state s) noexcept;
|
||||
|
||||
~lv2_config_service_event() noexcept = default;
|
||||
~lv2_config_service_event() noexcept;
|
||||
|
||||
// Notify queue that this event exists
|
||||
bool notify() const;
|
||||
|
|
|
@ -37,8 +37,8 @@ lv2_event_queue::lv2_event_queue(utils::serial& ar) noexcept
|
|||
|
||||
std::function<void(void*)> lv2_event_queue::load(utils::serial& ar)
|
||||
{
|
||||
auto queue = make_shared<lv2_event_queue>(ar);
|
||||
return [ptr = lv2_obj::load(queue->key, queue)](void* storage) { *static_cast<shared_ptr<lv2_obj>*>(storage) = ptr; };
|
||||
auto queue = make_shared<lv2_event_queue>(stx::exact_t<utils::serial&>(ar));
|
||||
return [ptr = lv2_obj::load(queue->key, queue)](void* storage) { *static_cast<atomic_ptr<lv2_obj>*>(storage) = ptr; };
|
||||
}
|
||||
|
||||
void lv2_event_queue::save(utils::serial& ar)
|
||||
|
|
|
@ -24,7 +24,7 @@ lv2_event_flag::lv2_event_flag(utils::serial& ar)
|
|||
|
||||
std::function<void(void*)> lv2_event_flag::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_event_flag>(ar));
|
||||
return load_func(make_shared<lv2_event_flag>(stx::exact_t<utils::serial&>(ar)));
|
||||
}
|
||||
|
||||
void lv2_event_flag::save(utils::serial& ar)
|
||||
|
|
|
@ -33,7 +33,7 @@ std::function<void(void*)> lv2_memory_container::load(utils::serial& ar)
|
|||
// Use idm::last_id() only for the instances at IDM
|
||||
return [ptr = make_shared<lv2_memory_container>(stx::exact_t<utils::serial&>(ar), true)](void* storage)
|
||||
{
|
||||
*static_cast<shared_ptr<lv2_memory_container>*>(storage) = ptr;
|
||||
*static_cast<atomic_ptr<lv2_memory_container>*>(storage) = ptr;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,7 @@ CellError lv2_memory::on_id_create()
|
|||
|
||||
std::function<void(void*)> lv2_memory::load(utils::serial& ar)
|
||||
{
|
||||
auto mem = make_shared<lv2_memory>(ar);
|
||||
auto mem = make_shared<lv2_memory>(stx::exact_t<utils::serial&>(ar));
|
||||
mem->exists++; // Disable on_id_create()
|
||||
auto func = load_func(mem, +mem->pshared);
|
||||
mem->exists--;
|
||||
|
|
|
@ -27,7 +27,7 @@ lv2_mutex::lv2_mutex(utils::serial& ar)
|
|||
|
||||
std::function<void(void*)> lv2_mutex::load(utils::serial& ar)
|
||||
{
|
||||
return load_func(make_shared<lv2_mutex>(ar));
|
||||
return load_func(make_shared<lv2_mutex>(stx::exact_t<utils::serial&>(ar)));
|
||||
}
|
||||
|
||||
void lv2_mutex::save(utils::serial& ar)
|
||||
|
|
|
@ -293,7 +293,7 @@ std::function<void(void*)> lv2_socket::load(utils::serial& ar)
|
|||
sock_lv2->bind(sock_lv2->last_bound_addr);
|
||||
}
|
||||
|
||||
return [ptr = sock_lv2](void* storage) { *static_cast<shared_ptr<lv2_socket>*>(storage) = ptr; };;
|
||||
return [ptr = sock_lv2](void* storage) { *static_cast<atomic_ptr<lv2_socket>*>(storage) = ptr; };;
|
||||
}
|
||||
|
||||
void lv2_socket::save(utils::serial& ar, bool save_only_this_class)
|
||||
|
|
|
@ -178,7 +178,7 @@ void lv2_socket::queue_wake(ppu_thread* ppu)
|
|||
|
||||
lv2_socket& lv2_socket::operator=(thread_state s) noexcept
|
||||
{
|
||||
if (s == thread_state::finished)
|
||||
if (s == thread_state::destroying_context)
|
||||
{
|
||||
close();
|
||||
}
|
||||
|
|
|
@ -112,7 +112,7 @@ std::function<void(void*)> lv2_overlay::load(utils::serial& ar)
|
|||
|
||||
return [ovlm](void* storage)
|
||||
{
|
||||
*static_cast<shared_ptr<lv2_obj>*>(storage) = ovlm;
|
||||
*static_cast<atomic_ptr<lv2_obj>*>(storage) = ovlm;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,6 @@ struct lv2_overlay final : ppu_module<lv2_obj>
|
|||
|
||||
u32 entry{};
|
||||
u32 seg0_code_end{};
|
||||
std::vector<u32> applied_patches;
|
||||
|
||||
lv2_overlay() = default;
|
||||
lv2_overlay(utils::serial&){}
|
||||
|
|
|
@ -498,6 +498,9 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector<std::string>& argv, std::vector<
|
|||
};
|
||||
|
||||
signal_system_cache_can_stay();
|
||||
|
||||
// Make sure we keep the game window opened
|
||||
Emu.SetContinuousMode(true);
|
||||
Emu.Kill(false);
|
||||
});
|
||||
|
||||
|
|
|
@ -372,7 +372,7 @@ std::function<void(void*)> lv2_prx::load(utils::serial& ar)
|
|||
|
||||
return [prx](void* storage)
|
||||
{
|
||||
*static_cast<shared_ptr<lv2_obj>*>(storage) = prx;
|
||||
*static_cast<atomic_ptr<lv2_obj>*>(storage) = prx;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -449,7 +449,7 @@ public:
|
|||
static std::function<void(void*)> load_func(shared_ptr<T> make, u64 pshared = umax)
|
||||
{
|
||||
const u64 key = make->key;
|
||||
return [ptr = load<T>(key, make, pshared)](void* storage) { *static_cast<shared_ptr<Storage>*>(storage) = ptr; };
|
||||
return [ptr = load<T>(key, make, pshared)](void* storage) { *static_cast<atomic_ptr<Storage>*>(storage) = ptr; };
|
||||
}
|
||||
|
||||
static bool wait_timeout(u64 usec, ppu_thread* cpu = {}, bool scale = true, bool is_usleep = false);
|
||||
|
|
|
@ -123,7 +123,7 @@ namespace id_manager
|
|||
ptr = stx::make_shared<T>(stx::exact_t<utils::serial&>(ar));
|
||||
}
|
||||
|
||||
return [ptr](void* storage) { *static_cast<stx::shared_ptr<T>*>(storage) = ptr; };
|
||||
return [ptr](void* storage) { *static_cast<stx::atomic_ptr<T>*>(storage) = ptr; };
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -805,8 +805,8 @@ public:
|
|||
{
|
||||
if (ptr)
|
||||
{
|
||||
constexpr thread_state finished{3};
|
||||
*static_cast<Get*>(ptr.get()) = finished;
|
||||
constexpr thread_state destroying_context{7};
|
||||
*static_cast<Get*>(ptr.get()) = destroying_context;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -837,8 +837,8 @@ public:
|
|||
{
|
||||
if (ptr)
|
||||
{
|
||||
constexpr thread_state finished{3};
|
||||
*static_cast<Get*>(ptr.get()) = finished;
|
||||
constexpr thread_state destroying_context{7};
|
||||
*static_cast<Get*>(ptr.get()) = destroying_context;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -161,7 +161,7 @@ void usb_device_buzz::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/
|
|||
}
|
||||
|
||||
const auto& cfg = g_cfg_buzz.players[i];
|
||||
cfg->handle_input(pad, true, [&buf, &index](buzz_btn btn, u16 /*value*/, bool pressed)
|
||||
cfg->handle_input(pad, true, [&buf, &index](buzz_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -147,7 +147,7 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint
|
|||
}
|
||||
|
||||
const auto& cfg = ::at32(g_cfg_ghltar.players, m_controller_index);
|
||||
cfg->handle_input(pad, true, [&buf](ghltar_btn btn, u16 value, bool pressed)
|
||||
cfg->handle_input(pad, true, [&buf](ghltar_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -227,7 +227,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint,
|
|||
return;
|
||||
}
|
||||
|
||||
const auto input_callback = [&gc](guncon3_btn btn, u16 value, bool pressed)
|
||||
const auto input_callback = [&gc](guncon3_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -34,7 +34,7 @@ void MouseHandlerBase::save(utils::serial& ar)
|
|||
bool MouseHandlerBase::is_time_for_update(double elapsed_time)
|
||||
{
|
||||
steady_clock::time_point now = steady_clock::now();
|
||||
double elapsed = (now - last_update).count() / 1000'000.;
|
||||
const double elapsed = (now - last_update).count() / 1000'000.;
|
||||
|
||||
if (elapsed > elapsed_time)
|
||||
{
|
||||
|
|
|
@ -48,6 +48,7 @@ public:
|
|||
|
||||
cfg->pressure_intensity_button.def = "";
|
||||
cfg->analog_limiter_button.def = "";
|
||||
cfg->orientation_reset_button.def = "";
|
||||
|
||||
// Apply defaults
|
||||
cfg->from_default();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "PadHandler.h"
|
||||
#include "Emu/system_utils.hpp"
|
||||
#include "Emu/system_config.h"
|
||||
#include "Emu/Cell/timers.hpp"
|
||||
#include "Input/pad_thread.h"
|
||||
#include "Input/product_info.h"
|
||||
|
||||
|
@ -494,6 +495,12 @@ bool PadHandlerBase::bindPadToDevice(std::shared_ptr<Pad> pad)
|
|||
pad->m_analog_limiter_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
pad->m_buttons.emplace_back(special_button_offset, mapping[button::orientation_reset_button], special_button_value::orientation_reset);
|
||||
pad->m_orientation_reset_button_index = static_cast<s32>(pad->m_buttons.size()) - 1;
|
||||
}
|
||||
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::up], CELL_PAD_CTRL_UP);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::down], CELL_PAD_CTRL_DOWN);
|
||||
pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL1, mapping[button::left], CELL_PAD_CTRL_LEFT);
|
||||
|
@ -600,6 +607,11 @@ std::array<std::set<u32>, PadHandlerBase::button::button_count> PadHandlerBase::
|
|||
mapping[button::analog_limiter_button] = FindKeyCodes<u32, u32>(button_list, cfg->analog_limiter_button);
|
||||
}
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
mapping[button::orientation_reset_button] = FindKeyCodes<u32, u32>(button_list, cfg->orientation_reset_button);
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
|
@ -739,6 +751,8 @@ void PadHandlerBase::process()
|
|||
if (!device || !pad)
|
||||
continue;
|
||||
|
||||
pad->move_data.orientation_enabled = b_has_orientation && device->config && device->config->orientation_enabled.get();
|
||||
|
||||
const connection status = update_connection(device);
|
||||
|
||||
switch (status)
|
||||
|
@ -754,6 +768,11 @@ void PadHandlerBase::process()
|
|||
|
||||
last_connection_status[i] = true;
|
||||
connected_devices++;
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
device->reset_orientation();
|
||||
}
|
||||
}
|
||||
|
||||
if (status == connection::no_data)
|
||||
|
@ -790,6 +809,11 @@ void PadHandlerBase::process()
|
|||
|
||||
last_connection_status[i] = false;
|
||||
connected_devices--;
|
||||
|
||||
if (b_has_orientation)
|
||||
{
|
||||
device->reset_orientation();
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
@ -797,6 +821,142 @@ void PadHandlerBase::process()
|
|||
|
||||
get_mapping(m_bindings[i]);
|
||||
get_extended_info(m_bindings[i]);
|
||||
get_orientation(m_bindings[i]);
|
||||
apply_pad_data(m_bindings[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z)
|
||||
{
|
||||
if (!move_data.orientation_enabled)
|
||||
{
|
||||
move_data.reset_sensors();
|
||||
return;
|
||||
}
|
||||
|
||||
// This function expects DS3 sensor accel values in linear velocity (m/s²) and gyro values in angular velocity (degree/s)
|
||||
// The default position is flat on the ground, pointing forward.
|
||||
// The accelerometers constantly measure G forces.
|
||||
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
||||
move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left
|
||||
move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards
|
||||
move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards
|
||||
move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
|
||||
move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
|
||||
move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
|
||||
}
|
||||
|
||||
void PadHandlerBase::set_raw_orientation(Pad& pad)
|
||||
{
|
||||
if (!pad.move_data.orientation_enabled)
|
||||
{
|
||||
pad.move_data.reset_sensors();
|
||||
return;
|
||||
}
|
||||
|
||||
// acceleration (linear velocity in m/s²)
|
||||
const f32 accel_x = (pad.m_sensors[0].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
const f32 accel_y = (pad.m_sensors[1].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
const f32 accel_z = (pad.m_sensors[2].m_value - 512) / static_cast<f32>(MOTION_ONE_G);
|
||||
|
||||
// gyro (angular velocity in degree/s)
|
||||
constexpr f32 gyro_x = 0.0f;
|
||||
const f32 gyro_y = (pad.m_sensors[3].m_value - 512) / (123.f / 90.f);
|
||||
constexpr f32 gyro_z = 0.0f;
|
||||
|
||||
set_raw_orientation(pad.move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z);
|
||||
}
|
||||
|
||||
void PadHandlerBase::get_orientation(const pad_ensemble& binding) const
|
||||
{
|
||||
if (!b_has_orientation) return;
|
||||
|
||||
const auto& pad = binding.pad;
|
||||
const auto& device = binding.device;
|
||||
if (!pad || !device) return;
|
||||
|
||||
if (pad->move_data.calibration_requested)
|
||||
{
|
||||
device->reset_orientation();
|
||||
pad->move_data.quaternion = ps_move_data::default_quaternion;
|
||||
pad->move_data.calibration_succeeded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pad->move_data.orientation_enabled || pad->get_orientation_reset_button_active())
|
||||
{
|
||||
// This can be called extensively in quick succession, so let's just reset the pointer instead of creating a new object.
|
||||
device->ahrs.reset();
|
||||
pad->move_data.quaternion = ps_move_data::default_quaternion;
|
||||
return;
|
||||
}
|
||||
|
||||
device->update_orientation(pad->move_data);
|
||||
}
|
||||
|
||||
void PadDevice::reset_orientation()
|
||||
{
|
||||
// Initialize Fusion
|
||||
ahrs = std::make_shared<FusionAhrs>();
|
||||
FusionAhrsInitialise(ahrs.get());
|
||||
ahrs->settings.convention = FusionConvention::FusionConventionEnu;
|
||||
ahrs->settings.gain = 0.0f; // If gain is set, the algorithm tries to adjust the orientation over time.
|
||||
FusionAhrsSetSettings(ahrs.get(), &ahrs->settings);
|
||||
FusionAhrsReset(ahrs.get());
|
||||
}
|
||||
|
||||
void PadDevice::update_orientation(ps_move_data& move_data)
|
||||
{
|
||||
if (!ahrs)
|
||||
{
|
||||
reset_orientation();
|
||||
}
|
||||
|
||||
// Get elapsed time since last update
|
||||
const u64 now_us = get_system_time();
|
||||
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||
last_ahrs_update_time_us = now_us;
|
||||
|
||||
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
||||
// Don't ask how the axis work. It's basically been trial and error.
|
||||
ensure(ahrs->settings.convention == FusionConvention::FusionConventionEnu); // East-North-Up
|
||||
|
||||
const FusionVector accelerometer{
|
||||
.axis {
|
||||
.x = -move_data.accelerometer_x,
|
||||
.y = +move_data.accelerometer_y,
|
||||
.z = +move_data.accelerometer_z
|
||||
}
|
||||
};
|
||||
|
||||
const FusionVector gyroscope{
|
||||
.axis {
|
||||
.x = +PadHandlerBase::rad_to_degree(move_data.gyro_x),
|
||||
.y = +PadHandlerBase::rad_to_degree(move_data.gyro_z),
|
||||
.z = -PadHandlerBase::rad_to_degree(move_data.gyro_y)
|
||||
}
|
||||
};
|
||||
|
||||
FusionVector magnetometer {};
|
||||
|
||||
if (move_data.magnetometer_enabled)
|
||||
{
|
||||
magnetometer = FusionVector{
|
||||
.axis {
|
||||
.x = move_data.magnetometer_x,
|
||||
.y = move_data.magnetometer_y,
|
||||
.z = move_data.magnetometer_z
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Update Fusion
|
||||
FusionAhrsUpdate(ahrs.get(), gyroscope, accelerometer, magnetometer, elapsed_sec);
|
||||
|
||||
// Get quaternion
|
||||
const FusionQuaternion quaternion = FusionAhrsGetQuaternion(ahrs.get());
|
||||
move_data.quaternion[0] = quaternion.array[1];
|
||||
move_data.quaternion[1] = quaternion.array[2];
|
||||
move_data.quaternion[2] = quaternion.array[3];
|
||||
move_data.quaternion[3] = quaternion.array[0];
|
||||
}
|
||||
|
|
|
@ -5,6 +5,15 @@
|
|||
#include "pad_config_types.h"
|
||||
#include "util/types.hpp"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||
#endif
|
||||
#include "3rdparty/fusion/fusion/Fusion/Fusion.h"
|
||||
#ifndef _MSC_VER
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
@ -38,6 +47,12 @@ public:
|
|||
};
|
||||
color color_override{};
|
||||
bool color_override_active{};
|
||||
|
||||
std::shared_ptr<FusionAhrs> ahrs; // Used to calculate quaternions from sensor data
|
||||
u64 last_ahrs_update_time_us = 0; // Last ahrs update
|
||||
|
||||
void update_orientation(ps_move_data& move_data);
|
||||
void reset_orientation();
|
||||
};
|
||||
|
||||
struct pad_ensemble
|
||||
|
@ -125,6 +140,7 @@ protected:
|
|||
|
||||
pressure_intensity_button,
|
||||
analog_limiter_button,
|
||||
orientation_reset_button,
|
||||
|
||||
button_count
|
||||
};
|
||||
|
@ -153,6 +169,7 @@ protected:
|
|||
bool b_has_config = false;
|
||||
bool b_has_pressure_intensity_button = true;
|
||||
bool b_has_analog_limiter_button = true;
|
||||
bool b_has_orientation = false;
|
||||
|
||||
std::array<cfg_pad, MAX_GAMEPADS> m_pad_configs;
|
||||
std::vector<pad_ensemble> m_bindings;
|
||||
|
@ -301,6 +318,7 @@ public:
|
|||
bool has_battery_led() const { return b_has_battery_led; }
|
||||
bool has_pressure_intensity_button() const { return b_has_pressure_intensity_button; }
|
||||
bool has_analog_limiter_button() const { return b_has_analog_limiter_button; }
|
||||
bool has_orientation() const { return b_has_orientation; }
|
||||
|
||||
u16 NormalizeStickInput(u16 raw_value, s32 threshold, s32 multiplier, bool ignore_threshold = false) const;
|
||||
void convert_stick_values(u16& x_out, u16& y_out, s32 x_in, s32 y_in, u32 deadzone, u32 anti_deadzone, u32 padsquircling) const;
|
||||
|
@ -323,6 +341,18 @@ public:
|
|||
virtual void get_motion_sensors(const std::string& pad_id, const motion_callback& callback, const motion_fail_callback& fail_callback, motion_preview_values preview_values, const std::array<AnalogSensor, 4>& sensors);
|
||||
virtual std::unordered_map<u32, std::string> get_motion_axis_list() const { return {}; }
|
||||
|
||||
static constexpr f32 PI = 3.14159265f;
|
||||
|
||||
static f32 degree_to_rad(f32 degree)
|
||||
{
|
||||
return degree * PI / 180.0f;
|
||||
}
|
||||
|
||||
static f32 rad_to_degree(f32 radians)
|
||||
{
|
||||
return radians * 180.0f / PI;
|
||||
};
|
||||
|
||||
private:
|
||||
virtual std::shared_ptr<PadDevice> get_device(const std::string& /*device*/) { return nullptr; }
|
||||
virtual bool get_is_left_trigger(const std::shared_ptr<PadDevice>& /*device*/, u64 /*keyCode*/) { return false; }
|
||||
|
@ -336,10 +366,15 @@ private:
|
|||
virtual std::unordered_map<u64, u16> get_button_values(const std::shared_ptr<PadDevice>& /*device*/) { return {}; }
|
||||
virtual pad_preview_values get_preview_values(const std::unordered_map<u64, u16>& /*data*/) { return {}; }
|
||||
|
||||
void get_orientation(const pad_ensemble& binding) const;
|
||||
|
||||
protected:
|
||||
virtual std::array<std::set<u32>, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr<PadDevice>& device, const cfg_pad* cfg);
|
||||
virtual void get_mapping(const pad_ensemble& binding);
|
||||
void TranslateButtonPress(const std::shared_ptr<PadDevice>& device, u64 keyCode, bool& pressed, u16& val, bool use_stick_multipliers, bool ignore_stick_threshold = false, bool ignore_trigger_threshold = false);
|
||||
void init_configs();
|
||||
cfg_pad* get_config(const std::string& pad_id);
|
||||
|
||||
static void set_raw_orientation(ps_move_data& move_data, f32 accel_x, f32 accel_y, f32 accel_z, f32 gyro_x, f32 gyro_y, f32 gyro_z);
|
||||
static void set_raw_orientation(Pad& pad);
|
||||
};
|
||||
|
|
|
@ -280,7 +280,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en
|
|||
}
|
||||
|
||||
bool up = false, right = false, down = false, left = false;
|
||||
const auto input_callback = [&ts, &up, &down, &left, &right](topshotelite_btn btn, u16 value, bool pressed)
|
||||
const auto input_callback = [&ts, &up, &down, &left, &right](topshotelite_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -308,7 +308,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32
|
|||
}
|
||||
|
||||
bool up = false, right = false, down = false, left = false;
|
||||
const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, u16 value, bool pressed)
|
||||
const auto input_callback = [&ts, &up, &down, &left, &right](topshotfearmaster_btn btn, pad_button /*pad_button*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -159,7 +159,7 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo
|
|||
return;
|
||||
|
||||
const auto& cfg = ::at32(g_cfg_turntable.players, m_controller_index);
|
||||
cfg->handle_input(pad, true, [&buf](turntable_btn btn, u16 value, bool pressed)
|
||||
cfg->handle_input(pad, true, [&buf](turntable_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
if (!pressed)
|
||||
return;
|
||||
|
|
|
@ -88,7 +88,7 @@ public:
|
|||
button_map.clear();
|
||||
}
|
||||
|
||||
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(T, u16, bool)>& func) const
|
||||
void handle_input(std::shared_ptr<Pad> pad, bool press_only, const std::function<void(T, pad_button, u16, bool, bool&)>& func) const
|
||||
{
|
||||
if (!pad)
|
||||
return;
|
||||
|
@ -97,19 +97,25 @@ public:
|
|||
{
|
||||
if (button.m_pressed || !press_only)
|
||||
{
|
||||
handle_input(func, button.m_offset, button.m_outKeyCode, button.m_value, button.m_pressed, true);
|
||||
if (handle_input(func, button.m_offset, button.m_outKeyCode, button.m_value, button.m_pressed, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const AnalogStick& stick : pad->m_sticks)
|
||||
{
|
||||
handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true);
|
||||
if (handle_input(func, stick.m_offset, get_axis_keycode(stick.m_offset, stick.m_value), stick.m_value, true, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void handle_input(const Mouse& mouse, const std::function<void(T, u16, bool)>& func) const
|
||||
void handle_input(const Mouse& mouse, const std::function<void(T, pad_button, u16, bool, bool&)>& func) const
|
||||
{
|
||||
for (int i = 0; i < 7; i++)
|
||||
for (int i = 0; i < 8; i++)
|
||||
{
|
||||
const MouseButtonCodes cell_code = get_mouse_button_code(i);
|
||||
if ((mouse.buttons & cell_code))
|
||||
|
@ -117,7 +123,11 @@ public:
|
|||
const pad_button button = static_cast<pad_button>(static_cast<int>(pad_button::mouse_button_1) + i);
|
||||
const u32 offset = pad_button_offset(button);
|
||||
const u32 keycode = pad_button_keycode(button);
|
||||
handle_input(func, offset, keycode, 255, true, true);
|
||||
|
||||
if (handle_input(func, offset, keycode, 255, true, true))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -163,10 +173,12 @@ protected:
|
|||
return empty_set;
|
||||
}
|
||||
|
||||
void handle_input(const std::function<void(T, u16, bool)>& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const
|
||||
bool handle_input(const std::function<void(T, pad_button, u16, bool, bool&)>& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const
|
||||
{
|
||||
m_mutex.lock();
|
||||
|
||||
bool abort = false;
|
||||
|
||||
const auto& btns = find_button(offset, keycode);
|
||||
if (btns.empty())
|
||||
{
|
||||
|
@ -180,24 +192,26 @@ protected:
|
|||
case CELL_PAD_BTN_OFFSET_ANALOG_LEFT_Y:
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_X:
|
||||
case CELL_PAD_BTN_OFFSET_ANALOG_RIGHT_Y:
|
||||
handle_input(func, offset, static_cast<u32>(axis_direction::both), value, pressed, false);
|
||||
abort = handle_input(func, offset, static_cast<u32>(axis_direction::both), value, pressed, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
return abort;
|
||||
}
|
||||
|
||||
for (const auto& btn : btns)
|
||||
{
|
||||
if (btn && func)
|
||||
{
|
||||
func(btn->btn_id(), value, pressed);
|
||||
func(btn->btn_id(), btn->get(), value, pressed, abort);
|
||||
if (abort) break;
|
||||
}
|
||||
}
|
||||
|
||||
m_mutex.unlock();
|
||||
return abort;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
#include <array>
|
||||
|
||||
enum class gem_btn
|
||||
enum class gem_btn : u32
|
||||
{
|
||||
start,
|
||||
select,
|
||||
|
@ -17,6 +17,18 @@ enum class gem_btn
|
|||
x_axis,
|
||||
y_axis,
|
||||
|
||||
combo_begin,
|
||||
combo = combo_begin,
|
||||
combo_start,
|
||||
combo_select,
|
||||
combo_triangle,
|
||||
combo_circle,
|
||||
combo_cross,
|
||||
combo_square,
|
||||
combo_move,
|
||||
combo_t,
|
||||
combo_end = combo_t,
|
||||
|
||||
count
|
||||
};
|
||||
|
||||
|
@ -41,6 +53,34 @@ struct cfg_fake_gems final : public emulated_pads_config<cfg_fake_gem, 4>
|
|||
cfg_fake_gems() : emulated_pads_config<cfg_fake_gem, 4>("gem") {};
|
||||
};
|
||||
|
||||
struct cfg_mouse_gem final : public emulated_pad_config<gem_btn>
|
||||
{
|
||||
cfg_mouse_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
|
||||
cfg_pad_btn<gem_btn> start{ this, "Start", gem_btn::start, pad_button::mouse_button_6 };
|
||||
cfg_pad_btn<gem_btn> select{ this, "Select", gem_btn::select, pad_button::mouse_button_7 };
|
||||
cfg_pad_btn<gem_btn> triangle{ this, "Triangle", gem_btn::triangle, pad_button::mouse_button_8 };
|
||||
cfg_pad_btn<gem_btn> circle{ this, "Circle", gem_btn::circle, pad_button::mouse_button_4 };
|
||||
cfg_pad_btn<gem_btn> cross{ this, "Cross", gem_btn::cross, pad_button::mouse_button_5 };
|
||||
cfg_pad_btn<gem_btn> square{ this, "Square", gem_btn::square, pad_button::mouse_button_3 };
|
||||
cfg_pad_btn<gem_btn> move{ this, "Move", gem_btn::move, pad_button::mouse_button_2 };
|
||||
cfg_pad_btn<gem_btn> t{ this, "T", gem_btn::t, pad_button::mouse_button_1 };
|
||||
cfg_pad_btn<gem_btn> combo{ this, "Combo", gem_btn::combo, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_start{ this, "Combo Start", gem_btn::combo_start, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_select{ this, "Combo Select", gem_btn::combo_select, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_triangle{ this, "Combo Triangle", gem_btn::combo_triangle, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_circle{ this, "Combo Circle", gem_btn::combo_circle, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_cross{ this, "Combo Cross", gem_btn::combo_cross, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_square{ this, "Combo Square", gem_btn::combo_square, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_move{ this, "Combo Move", gem_btn::combo_move, pad_button::pad_button_max_enum };
|
||||
cfg_pad_btn<gem_btn> combo_t{ this, "Combo T", gem_btn::combo_t, pad_button::pad_button_max_enum };
|
||||
};
|
||||
|
||||
struct cfg_mouse_gems final : public emulated_pads_config<cfg_mouse_gem, 4>
|
||||
{
|
||||
cfg_mouse_gems() : emulated_pads_config<cfg_mouse_gem, 4>("gem_mouse") {};
|
||||
};
|
||||
|
||||
struct cfg_gem final : public emulated_pad_config<gem_btn>
|
||||
{
|
||||
cfg_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {}
|
||||
|
@ -62,3 +102,4 @@ struct cfg_gems final : public emulated_pads_config<cfg_gem, 4>
|
|||
|
||||
extern cfg_gems g_cfg_gem_real;
|
||||
extern cfg_fake_gems g_cfg_gem_fake;
|
||||
extern cfg_mouse_gems g_cfg_gem_mouse;
|
||||
|
|
|
@ -66,6 +66,9 @@ struct cfg_pad final : cfg::node
|
|||
cfg_sensor motion_sensor_z{ this, "Motion Sensor Z" };
|
||||
cfg_sensor motion_sensor_g{ this, "Motion Sensor G" };
|
||||
|
||||
cfg::string orientation_reset_button{ this, "Orientation Reset Button", "" };
|
||||
cfg::_bool orientation_enabled{ this, "Orientation Enabled", false };
|
||||
|
||||
cfg::string pressure_intensity_button{ this, "Pressure Intensity Button", "" };
|
||||
cfg::uint<0, 100> pressure_intensity{ this, "Pressure Intensity Percent", 50 };
|
||||
cfg::_bool pressure_intensity_toggle_mode{ this, "Pressure Intensity Toggle Mode", false };
|
||||
|
|
|
@ -39,7 +39,7 @@ void fmt_class_string<pad_button>::format(std::string& out, u64 arg)
|
|||
case pad_button::rs_right: return "Right Stick Right";
|
||||
case pad_button::rs_x: return "Right Stick X-Axis";
|
||||
case pad_button::rs_y: return "Right Stick Y-Axis";
|
||||
case pad_button::pad_button_max_enum: return "MAX_ENUM";
|
||||
case pad_button::pad_button_max_enum: return "";
|
||||
case pad_button::mouse_button_1: return "Mouse Button 1";
|
||||
case pad_button::mouse_button_2: return "Mouse Button 2";
|
||||
case pad_button::mouse_button_3: return "Mouse Button 3";
|
||||
|
@ -159,6 +159,20 @@ u32 get_axis_keycode(u32 offset, u16 value)
|
|||
}
|
||||
}
|
||||
|
||||
void ps_move_data::reset_sensors()
|
||||
{
|
||||
quaternion = default_quaternion;
|
||||
accelerometer_x = 0.0f;
|
||||
accelerometer_y = 0.0f;
|
||||
accelerometer_z = 0.0f;
|
||||
gyro_x = 0.0f;
|
||||
gyro_y = 0.0f;
|
||||
gyro_z = 0.0f;
|
||||
magnetometer_x = 0.0f;
|
||||
magnetometer_y = 0.0f;
|
||||
magnetometer_z = 0.0f;
|
||||
}
|
||||
|
||||
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
|
||||
{
|
||||
if (m_pressure_intensity_button_index < 0)
|
||||
|
@ -238,3 +252,13 @@ bool Pad::get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id)
|
|||
|
||||
return analog_limiter_button.m_pressed;
|
||||
}
|
||||
|
||||
bool Pad::get_orientation_reset_button_active()
|
||||
{
|
||||
if (m_orientation_reset_button_index < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_buttons[m_orientation_reset_button_index].m_pressed;
|
||||
}
|
||||
|
|
|
@ -365,7 +365,8 @@ constexpr u32 special_button_offset = 666; // Must not conflict with other CELL
|
|||
enum special_button_value
|
||||
{
|
||||
pressure_intensity,
|
||||
analog_limiter
|
||||
analog_limiter,
|
||||
orientation_reset
|
||||
};
|
||||
|
||||
struct Button
|
||||
|
@ -470,8 +471,10 @@ struct ps_move_data
|
|||
bool calibration_succeeded = false;
|
||||
|
||||
bool magnetometer_enabled = false;
|
||||
bool orientation_enabled = false;
|
||||
|
||||
std::array<f32, 4> quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||
static constexpr std::array<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
|
||||
std::array<f32, 4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||
f32 accelerometer_x = 0.0f; // linear velocity in m/s²
|
||||
f32 accelerometer_y = 0.0f; // linear velocity in m/s²
|
||||
f32 accelerometer_z = 0.0f; // linear velocity in m/s²
|
||||
|
@ -482,6 +485,8 @@ struct ps_move_data
|
|||
f32 magnetometer_y = 0.0f;
|
||||
f32 magnetometer_z = 0.0f;
|
||||
s16 temperature = 0;
|
||||
|
||||
void reset_sensors();
|
||||
};
|
||||
|
||||
struct Pad
|
||||
|
@ -512,6 +517,9 @@ struct Pad
|
|||
bool m_analog_limiter_enabled_last{}; // only used in keyboard_pad_handler
|
||||
bool get_analog_limiter_button_active(bool is_toggle_mode, u32 player_id);
|
||||
|
||||
s32 m_orientation_reset_button_index{-1}; // Special button index. -1 if not set.
|
||||
bool get_orientation_reset_button_active();
|
||||
|
||||
// Cable State: 0 - 1 plugged in ?
|
||||
u8 m_cable_state{0};
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ void usb_device_usio::translate_input_taiko()
|
|||
if (const auto& pad = ::at32(handler->GetPads(), pad_number); (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && is_input_allowed())
|
||||
{
|
||||
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
|
||||
cfg->handle_input(pad, false, [&](usio_btn btn, u16 /*value*/, bool pressed)
|
||||
cfg->handle_input(pad, false, [&](usio_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
switch (btn)
|
||||
{
|
||||
|
@ -288,7 +288,7 @@ void usb_device_usio::translate_input_tekken()
|
|||
if (const auto& pad = ::at32(handler->GetPads(), pad_number); (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && is_input_allowed())
|
||||
{
|
||||
const auto& cfg = ::at32(g_cfg_usio.players, pad_number);
|
||||
cfg->handle_input(pad, false, [&](usio_btn btn, u16 /*value*/, bool pressed)
|
||||
cfg->handle_input(pad, false, [&](usio_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& /*abort*/)
|
||||
{
|
||||
switch (btn)
|
||||
{
|
||||
|
|
|
@ -18,7 +18,21 @@ namespace rsx
|
|||
case surface_target::surfaces_a_b_c: return{ 0, 1, 2 };
|
||||
case surface_target::surfaces_a_b_c_d: return{ 0, 1, 2, 3 };
|
||||
}
|
||||
fmt::throw_exception("Wrong color_target");
|
||||
fmt::throw_exception("Invalid color target %d", static_cast<int>(color_target));
|
||||
}
|
||||
|
||||
u8 get_mrt_buffers_count(surface_target color_target)
|
||||
{
|
||||
switch (color_target)
|
||||
{
|
||||
case surface_target::none: return 0;
|
||||
case surface_target::surface_a: return 1;
|
||||
case surface_target::surface_b: return 1;
|
||||
case surface_target::surfaces_a_b: return 2;
|
||||
case surface_target::surfaces_a_b_c: return 3;
|
||||
case surface_target::surfaces_a_b_c_d: return 4;
|
||||
}
|
||||
fmt::throw_exception("Invalid color target %d", static_cast<int>(color_target));
|
||||
}
|
||||
|
||||
usz get_aligned_pitch(surface_color_format format, u32 width)
|
||||
|
|
|
@ -17,6 +17,7 @@ namespace rsx
|
|||
namespace utility
|
||||
{
|
||||
std::vector<u8> get_rtt_indexes(surface_target color_target);
|
||||
u8 get_mrt_buffers_count(surface_target color_target);
|
||||
usz get_aligned_pitch(surface_color_format format, u32 width);
|
||||
usz get_packed_pitch(surface_color_format format, u32 width);
|
||||
}
|
||||
|
|
|
@ -735,7 +735,7 @@ namespace rsx
|
|||
utils::stream_vector(dst + 4, 0u, fog_mode, std::bit_cast<u32>(wpos_scale), std::bit_cast<u32>(wpos_bias));
|
||||
}
|
||||
|
||||
void draw_command_processor::fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase& prog) const
|
||||
void draw_command_processor::fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase* prog) const
|
||||
{
|
||||
auto& draw_call = REGS(m_ctx)->current_draw_clause;
|
||||
|
||||
|
@ -745,8 +745,9 @@ namespace rsx
|
|||
// Temp indirection table. Used to track "running" updates.
|
||||
rsx::simple_array<u32> instancing_indirection_table;
|
||||
// indirection table size
|
||||
const auto reloc_table = prog.has_indexed_constants ? decltype(prog.constant_ids){} : prog.constant_ids;
|
||||
const auto redirection_table_size = prog.has_indexed_constants ? 468u : ::size32(prog.constant_ids);
|
||||
const auto full_reupload = !prog || prog->has_indexed_constants;
|
||||
const auto reloc_table = full_reupload ? decltype(prog->constant_ids){} : prog->constant_ids;
|
||||
const auto redirection_table_size = full_reupload ? 468u : ::size32(prog->constant_ids);
|
||||
instancing_indirection_table.resize(redirection_table_size);
|
||||
|
||||
// Temp constants data
|
||||
|
@ -787,9 +788,9 @@ namespace rsx
|
|||
continue;
|
||||
}
|
||||
|
||||
const int translated_offset = prog.has_indexed_constants
|
||||
const int translated_offset = full_reupload
|
||||
? instance_config.patch_load_offset
|
||||
: prog.TranslateConstantsRange(instance_config.patch_load_offset, instance_config.patch_load_count);
|
||||
: prog->translate_constants_range(instance_config.patch_load_offset, instance_config.patch_load_count);
|
||||
|
||||
if (translated_offset >= 0)
|
||||
{
|
||||
|
@ -809,14 +810,14 @@ namespace rsx
|
|||
continue;
|
||||
}
|
||||
|
||||
ensure(!prog.has_indexed_constants);
|
||||
ensure(!full_reupload);
|
||||
|
||||
// Sparse update. Update records individually instead of bulk
|
||||
// FIXME: Range batching optimization
|
||||
const auto load_end = instance_config.patch_load_offset + instance_config.patch_load_count;
|
||||
for (u32 i = 0; i < redirection_table_size; ++i)
|
||||
{
|
||||
const auto read_index = prog.constant_ids[i];
|
||||
const auto read_index = prog->constant_ids[i];
|
||||
if (read_index < instance_config.patch_load_offset || read_index >= load_end)
|
||||
{
|
||||
// Reading outside "hot" range.
|
||||
|
|
|
@ -105,6 +105,6 @@ namespace rsx
|
|||
|
||||
// Fill instancing buffers. A single iobuf is used for both. 256byte alignment enforced to allow global bind
|
||||
// Returns offsets to the index redirection lookup table and constants field array
|
||||
void fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase& prog) const;
|
||||
void fill_constants_instancing_buffer(rsx::io_buffer& indirection_table_buf, rsx::io_buffer& constants_data_array_buffer, const VertexProgramBase* prog) const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ u64 GLGSRender::get_cycles()
|
|||
|
||||
GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
||||
{
|
||||
m_shaders_cache = std::make_unique<gl::shader_cache>(m_prog_buffer, "opengl", "v1.94");
|
||||
m_shaders_cache = std::make_unique<gl::shader_cache>(m_prog_buffer, "opengl", "v1.95");
|
||||
|
||||
if (g_cfg.video.disable_vertex_cache)
|
||||
m_vertex_cache = std::make_unique<gl::null_vertex_cache>();
|
||||
|
@ -52,6 +52,14 @@ GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
|||
backend_config.supports_normalized_barycentrics = true;
|
||||
}
|
||||
|
||||
GLGSRender::~GLGSRender()
|
||||
{
|
||||
if (m_frame)
|
||||
{
|
||||
m_frame->reset();
|
||||
}
|
||||
}
|
||||
|
||||
extern CellGcmContextData current_context;
|
||||
|
||||
void GLGSRender::set_viewport()
|
||||
|
@ -870,7 +878,7 @@ void GLGSRender::load_program_env()
|
|||
}
|
||||
}
|
||||
|
||||
if (update_fragment_constants && !update_instruction_buffers)
|
||||
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
|
||||
{
|
||||
// Fragment constants
|
||||
auto mapping = m_fragment_constants_buffer->alloc_from_heap(fragment_constants_size, m_uniform_buffer_offset_align);
|
||||
|
@ -970,12 +978,23 @@ void GLGSRender::load_program_env()
|
|||
}
|
||||
}
|
||||
|
||||
m_graphics_state.clear(
|
||||
rsx::flags32_t handled_flags =
|
||||
rsx::pipeline_state::fragment_state_dirty |
|
||||
rsx::pipeline_state::vertex_state_dirty |
|
||||
rsx::pipeline_state::transform_constants_dirty |
|
||||
rsx::pipeline_state::fragment_constants_dirty |
|
||||
rsx::pipeline_state::fragment_texture_state_dirty);
|
||||
rsx::pipeline_state::fragment_texture_state_dirty;
|
||||
|
||||
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
|
||||
{
|
||||
handled_flags |= rsx::pipeline_state::fragment_constants_dirty;
|
||||
}
|
||||
|
||||
m_graphics_state.clear(handled_flags);
|
||||
}
|
||||
|
||||
bool GLGSRender::is_current_program_interpreted() const
|
||||
{
|
||||
return m_program && m_shader_interpreter.is_interpreter(m_program);
|
||||
}
|
||||
|
||||
void GLGSRender::upload_transform_constants(const rsx::io_buffer& buffer)
|
||||
|
@ -1026,13 +1045,19 @@ void GLGSRender::update_vertex_env(const gl::vertex_upload_info& upload_info)
|
|||
|
||||
void GLGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 count)
|
||||
{
|
||||
if (!m_vertex_prog)
|
||||
if (!m_program || !m_vertex_prog)
|
||||
{
|
||||
// Shouldn't be reachable, but handle it correctly anyway
|
||||
m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_vertex_prog->overlaps_constants_range(index, count))
|
||||
{
|
||||
// Nothing meaningful to us
|
||||
return;
|
||||
}
|
||||
|
||||
std::pair<u32, u32> data_range {};
|
||||
void* data_source = nullptr;
|
||||
const auto bound_range = m_transform_constants_buffer->bound_range();
|
||||
|
@ -1046,7 +1071,7 @@ void GLGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou
|
|||
data_range = { bound_range.first + byte_offset, byte_count};
|
||||
data_source = ®S(ctx)->transform_constants[index];
|
||||
}
|
||||
else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0)
|
||||
else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0)
|
||||
{
|
||||
const auto write_offset = xform_id * 16;
|
||||
const auto byte_count = count * 16;
|
||||
|
|
|
@ -159,6 +159,7 @@ public:
|
|||
|
||||
GLGSRender(utils::serial* ar) noexcept;
|
||||
GLGSRender() noexcept : GLGSRender(nullptr) {}
|
||||
virtual ~GLGSRender();
|
||||
|
||||
private:
|
||||
|
||||
|
@ -205,6 +206,9 @@ public:
|
|||
// GRAPH backend
|
||||
void patch_transform_constants(rsx::context* ctx, u32 index, u32 count) override;
|
||||
|
||||
// Misc
|
||||
bool is_current_program_interpreted() const override;
|
||||
|
||||
protected:
|
||||
void clear_surface(u32 arg) override;
|
||||
void begin() override;
|
||||
|
|
|
@ -347,7 +347,7 @@ namespace gl
|
|||
return data;
|
||||
}
|
||||
|
||||
bool shader_interpreter::is_interpreter(const glsl::program* program)
|
||||
bool shader_interpreter::is_interpreter(const glsl::program* program) const
|
||||
{
|
||||
return (program == &m_current_interpreter->prog);
|
||||
}
|
||||
|
|
|
@ -84,6 +84,6 @@ namespace gl
|
|||
void update_fragment_textures(const std::array<std::unique_ptr<rsx::sampled_image_descriptor_base>, 16>& descriptors, u16 reference_mask, u32* out);
|
||||
|
||||
glsl::program* get(const interpreter::program_metadata& fp_metadata);
|
||||
bool is_interpreter(const glsl::program* program);
|
||||
bool is_interpreter(const glsl::program* program) const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ public:
|
|||
virtual ~GSFrameBase() = default;
|
||||
|
||||
virtual void close() = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual bool shown() = 0;
|
||||
virtual void hide() = 0;
|
||||
virtual void show() = 0;
|
||||
|
|
|
@ -18,7 +18,7 @@ GSRender::~GSRender()
|
|||
{
|
||||
m_context = nullptr;
|
||||
|
||||
if (m_frame)
|
||||
if (m_frame && !m_continuous_mode)
|
||||
{
|
||||
m_frame->close();
|
||||
}
|
||||
|
@ -39,7 +39,10 @@ void GSRender::on_exit()
|
|||
|
||||
if (m_frame)
|
||||
{
|
||||
m_frame->hide();
|
||||
if (!m_continuous_mode)
|
||||
{
|
||||
m_frame->hide();
|
||||
}
|
||||
m_frame->delete_context(m_context);
|
||||
m_context = nullptr;
|
||||
}
|
||||
|
|
|
@ -21,12 +21,15 @@ class GSRender : public rsx::thread
|
|||
protected:
|
||||
GSFrameBase* m_frame;
|
||||
draw_context_t m_context = nullptr;
|
||||
bool m_continuous_mode = false;
|
||||
|
||||
public:
|
||||
~GSRender() override;
|
||||
|
||||
GSRender(utils::serial* ar) noexcept;
|
||||
|
||||
void set_continuous_mode(bool continuous_mode) { m_continuous_mode = continuous_mode; }
|
||||
|
||||
void on_init_thread() override;
|
||||
void on_exit() override;
|
||||
|
||||
|
|
|
@ -114,6 +114,8 @@ namespace rsx
|
|||
|
||||
Emu.CallFromMainThread([]()
|
||||
{
|
||||
// Make sure we keep the game window opened
|
||||
Emu.SetContinuousMode(true);
|
||||
Emu.Restart(false);
|
||||
});
|
||||
return page_navigation::exit;
|
||||
|
|
|
@ -26,6 +26,9 @@ namespace rsx
|
|||
if (!suspend_mode)
|
||||
{
|
||||
Emu.after_kill_callback = []() { Emu.Restart(); };
|
||||
|
||||
// Make sure we keep the game window opened
|
||||
Emu.SetContinuousMode(true);
|
||||
}
|
||||
Emu.Kill(false, true);
|
||||
});
|
||||
|
|
|
@ -265,7 +265,6 @@ namespace rsx
|
|||
fade_animation.end = color4f(1.f);
|
||||
fade_animation.active = true;
|
||||
|
||||
this->on_close = std::move(on_close);
|
||||
visible = true;
|
||||
|
||||
const auto notify = std::make_shared<atomic_t<u32>>(0);
|
||||
|
|
|
@ -22,6 +22,15 @@ namespace rsx
|
|||
|
||||
using namespace rsx::fragment_program;
|
||||
|
||||
// SIMD vector lanes
|
||||
enum VectorLane : u8
|
||||
{
|
||||
X = 0,
|
||||
Y = 1,
|
||||
Z = 2,
|
||||
W = 3,
|
||||
};
|
||||
|
||||
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
|
||||
: m_size(size)
|
||||
, m_prog(prog)
|
||||
|
@ -141,8 +150,7 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
|
|||
AddCode(m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "cc" + std::to_string(src0.cond_mod_reg_index)) + "$m = " + dest + ";");
|
||||
}
|
||||
|
||||
u32 reg_index = dst.fp16 ? dst.dest_reg >> 1 : dst.dest_reg;
|
||||
|
||||
const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg;
|
||||
ensure(reg_index < temp_registers.size());
|
||||
|
||||
if (dst.opcode == RSX_FP_OPCODE_MOV &&
|
||||
|
@ -754,14 +762,26 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
const std::string init_value = float4_type + "(0.)";
|
||||
std::array<std::string, 4> output_register_names;
|
||||
std::array<u32, 4> ouput_register_indices = { 0, 2, 3, 4 };
|
||||
bool shader_is_valid = false;
|
||||
|
||||
// Holder for any "cleanup" before exiting main
|
||||
std::stringstream main_epilogue;
|
||||
|
||||
// Check depth export
|
||||
if (m_ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||
{
|
||||
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
|
||||
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
|
||||
shader_is_valid = (!!temp_registers[1].h1_writes);
|
||||
|
||||
auto& r1 = temp_registers[1];
|
||||
if (r1.requires_gather(VectorLane::Z))
|
||||
{
|
||||
// r1.zw was not written to
|
||||
properties.has_gather_op = true;
|
||||
main_epilogue << " r1.z = " << float4_type << r1.gather_r() << ".z;\n";
|
||||
|
||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
||||
rsx_log.warning("ROP reads from shader depth without writing to it. Final value will be gathered.");
|
||||
}
|
||||
}
|
||||
|
||||
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
|
||||
|
@ -775,28 +795,49 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
output_register_names = { "h0", "h4", "h6", "h8" };
|
||||
}
|
||||
|
||||
for (int n = 0; n < 4; ++n)
|
||||
for (u32 n = 0; n < 4; ++n)
|
||||
{
|
||||
if (!m_parr.HasParam(PF_PARAM_NONE, float4_type, output_register_names[n]))
|
||||
const auto& reg_name = output_register_names[n];
|
||||
if (!m_parr.HasParam(PF_PARAM_NONE, float4_type, reg_name))
|
||||
{
|
||||
m_parr.AddParam(PF_PARAM_NONE, float4_type, output_register_names[n], init_value);
|
||||
m_parr.AddParam(PF_PARAM_NONE, float4_type, reg_name, init_value);
|
||||
}
|
||||
|
||||
if (n >= m_prog.mrt_buffers_count)
|
||||
{
|
||||
// Skip gather
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto block_index = ouput_register_indices[n];
|
||||
shader_is_valid |= (!!temp_registers[block_index].h0_writes);
|
||||
}
|
||||
auto& r = temp_registers[block_index];
|
||||
|
||||
if (!shader_is_valid)
|
||||
{
|
||||
properties.has_no_output = true;
|
||||
|
||||
if (!properties.has_discard_op)
|
||||
if (fp16_out)
|
||||
{
|
||||
// NOTE: Discard operation overrides output
|
||||
rsx_log.warning("Shader does not write to any output register and will be NOPed");
|
||||
main = "/*" + main + "*/";
|
||||
// Check if we need a split/extract op
|
||||
if (r.requires_split(0))
|
||||
{
|
||||
main_epilogue << " " << reg_name << " = " << float4_type << r.split_h0() << ";\n";
|
||||
|
||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
||||
rsx_log.warning("ROP reads from %s without writing to it. Final value will be extracted from the 32-bit register.", reg_name);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!r.requires_gather128())
|
||||
{
|
||||
// Nothing to do
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to gather the data from existing registers
|
||||
main_epilogue << " " << reg_name << " = " << float4_type << r.gather_r() << ";\n";
|
||||
properties.has_gather_op = true;
|
||||
|
||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
||||
rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name);
|
||||
}
|
||||
|
||||
if (properties.has_dynamic_register_load)
|
||||
|
@ -822,6 +863,9 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
OS << "#endif\n";
|
||||
OS << " discard;\n";
|
||||
OS << "}\n";
|
||||
|
||||
// Don't consume any args
|
||||
m_parr.Clear();
|
||||
return OS.str();
|
||||
}
|
||||
|
||||
|
@ -1019,6 +1063,12 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||
|
||||
insertMainStart(OS);
|
||||
OS << main << std::endl;
|
||||
|
||||
if (const auto epilogue = main_epilogue.str(); !epilogue.empty())
|
||||
{
|
||||
OS << " // Epilogue\n";
|
||||
OS << epilogue << std::endl;
|
||||
}
|
||||
insertMainEnd(OS);
|
||||
|
||||
return OS.str();
|
||||
|
@ -1360,12 +1410,12 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||
|
||||
switch (opcode)
|
||||
{
|
||||
case RSX_FP_OPCODE_NOP: break;
|
||||
case RSX_FP_OPCODE_NOP:
|
||||
break;
|
||||
case RSX_FP_OPCODE_KIL:
|
||||
properties.has_discard_op = true;
|
||||
AddFlowOp("_kill()");
|
||||
break;
|
||||
|
||||
default:
|
||||
int prev_force_unit = forced_unit;
|
||||
|
||||
|
|
|
@ -1,116 +1,10 @@
|
|||
#pragma once
|
||||
#include "ShaderParam.h"
|
||||
#include "FragmentProgramRegister.h"
|
||||
#include "RSXFragmentProgram.h"
|
||||
|
||||
#include <sstream>
|
||||
|
||||
// Helper for GPR occupancy tracking
|
||||
struct temp_register
|
||||
{
|
||||
bool aliased_r0 = false;
|
||||
bool aliased_h0 = false;
|
||||
bool aliased_h1 = false;
|
||||
bool last_write_half[4] = { false, false, false, false };
|
||||
|
||||
u32 real_index = -1;
|
||||
|
||||
u32 h0_writes = 0u; // Number of writes to the first 64-bits of the register
|
||||
u32 h1_writes = 0u; // Number of writes to the last 64-bits of the register
|
||||
|
||||
void tag(u32 index, bool half_register, bool x, bool y, bool z, bool w)
|
||||
{
|
||||
if (half_register)
|
||||
{
|
||||
if (index & 1)
|
||||
{
|
||||
if (x) last_write_half[2] = true;
|
||||
if (y) last_write_half[2] = true;
|
||||
if (z) last_write_half[3] = true;
|
||||
if (w) last_write_half[3] = true;
|
||||
|
||||
aliased_h1 = true;
|
||||
h1_writes++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x) last_write_half[0] = true;
|
||||
if (y) last_write_half[0] = true;
|
||||
if (z) last_write_half[1] = true;
|
||||
if (w) last_write_half[1] = true;
|
||||
|
||||
aliased_h0 = true;
|
||||
h0_writes++;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (x) last_write_half[0] = false;
|
||||
if (y) last_write_half[1] = false;
|
||||
if (z) last_write_half[2] = false;
|
||||
if (w) last_write_half[3] = false;
|
||||
|
||||
aliased_r0 = true;
|
||||
|
||||
h0_writes++;
|
||||
h1_writes++;
|
||||
}
|
||||
|
||||
if (real_index == umax)
|
||||
{
|
||||
if (half_register)
|
||||
real_index = index >> 1;
|
||||
else
|
||||
real_index = index;
|
||||
}
|
||||
}
|
||||
|
||||
bool requires_gather(u8 channel) const
|
||||
{
|
||||
//Data fetched from the single precision register requires merging of the two half registers
|
||||
ensure(channel < 4);
|
||||
if (aliased_h0 && channel < 2)
|
||||
{
|
||||
return last_write_half[channel];
|
||||
}
|
||||
|
||||
if (aliased_h1 && channel > 1)
|
||||
{
|
||||
return last_write_half[channel];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool requires_split(u32 /*index*/) const
|
||||
{
|
||||
//Data fetched from any of the two half registers requires sync with the full register
|
||||
if (!(last_write_half[0] || last_write_half[1]) && aliased_r0)
|
||||
{
|
||||
//r0 has been written to
|
||||
//TODO: Check for specific elements in real32 register
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string gather_r() const
|
||||
{
|
||||
std::string h0 = "h" + std::to_string(real_index << 1);
|
||||
std::string h1 = "h" + std::to_string(real_index << 1 | 1);
|
||||
std::string reg = "r" + std::to_string(real_index);
|
||||
std::string ret = "//Invalid gather";
|
||||
|
||||
if (aliased_h0 && aliased_h1)
|
||||
ret = "(gather(" + h0 + ", " + h1 + "))";
|
||||
else if (aliased_h0)
|
||||
ret = "(gather(" + h0 + "), " + reg + ".zw)";
|
||||
else if (aliased_h1)
|
||||
ret = "(" + reg + ".xy, gather(" + h1 + "))";
|
||||
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This class is used to translate RSX Fragment program to GLSL/HLSL code
|
||||
* Backend with text based shader can subclass this class and implement :
|
||||
|
@ -157,7 +51,7 @@ class FragmentProgramDecompiler
|
|||
|
||||
bool m_is_valid_ucode = true;
|
||||
|
||||
std::array<temp_register, 64> temp_registers;
|
||||
std::array<rsx::MixedPrecisionRegister, 64> temp_registers;
|
||||
|
||||
std::string GetMask() const;
|
||||
|
||||
|
|
196
rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp
Normal file
196
rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp
Normal file
|
@ -0,0 +1,196 @@
|
|||
#include "stdafx.h"
|
||||
#include "FragmentProgramRegister.h"
|
||||
|
||||
namespace rsx
|
||||
{
|
||||
MixedPrecisionRegister::MixedPrecisionRegister()
|
||||
{
|
||||
std::fill(content_mask.begin(), content_mask.end(), data_type_bits::undefined);
|
||||
}
|
||||
|
||||
void MixedPrecisionRegister::tag_h0(bool x, bool y, bool z, bool w)
|
||||
{
|
||||
if (x) content_mask[0] = data_type_bits::f16;
|
||||
if (y) content_mask[1] = data_type_bits::f16;
|
||||
if (z) content_mask[2] = data_type_bits::f16;
|
||||
if (w) content_mask[3] = data_type_bits::f16;
|
||||
}
|
||||
|
||||
void MixedPrecisionRegister::tag_h1(bool x, bool y, bool z, bool w)
|
||||
{
|
||||
if (x) content_mask[4] = data_type_bits::f16;
|
||||
if (y) content_mask[5] = data_type_bits::f16;
|
||||
if (z) content_mask[6] = data_type_bits::f16;
|
||||
if (w) content_mask[7] = data_type_bits::f16;
|
||||
}
|
||||
|
||||
void MixedPrecisionRegister::tag_r(bool x, bool y, bool z, bool w)
|
||||
{
|
||||
if (x) content_mask[0] = content_mask[1] = data_type_bits::f32;
|
||||
if (y) content_mask[2] = content_mask[3] = data_type_bits::f32;
|
||||
if (z) content_mask[4] = content_mask[5] = data_type_bits::f32;
|
||||
if (w) content_mask[6] = content_mask[7] = data_type_bits::f32;
|
||||
}
|
||||
|
||||
void MixedPrecisionRegister::tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w)
|
||||
{
|
||||
if (file_index == umax)
|
||||
{
|
||||
// First-time use. Initialize...
|
||||
const u32 real_index = is_fp16 ? (index >> 1) : index;
|
||||
file_index = real_index;
|
||||
}
|
||||
|
||||
if (is_fp16)
|
||||
{
|
||||
ensure((index / 2) == file_index);
|
||||
|
||||
if (index & 1)
|
||||
{
|
||||
tag_h1(x, y, z, w);
|
||||
return;
|
||||
}
|
||||
|
||||
tag_h0(x, y, z, w);
|
||||
return;
|
||||
}
|
||||
|
||||
tag_r(x, y, z, w);
|
||||
}
|
||||
|
||||
std::string MixedPrecisionRegister::gather_r() const
|
||||
{
|
||||
const auto half_index = file_index << 1;
|
||||
const std::string reg = "r" + std::to_string(file_index);
|
||||
const std::string gather_half_regs[] = {
|
||||
"gather(h" + std::to_string(half_index) + ")",
|
||||
"gather(h" + std::to_string(half_index + 1) + ")"
|
||||
};
|
||||
|
||||
std::string outputs[4];
|
||||
for (int ch = 0; ch < 4; ++ch)
|
||||
{
|
||||
// FIXME: This approach ignores mixed register bits. Not ideal!!!!
|
||||
const auto channel0 = content_mask[ch * 2];
|
||||
const auto is_fp16_ch = channel0 == content_mask[ch * 2 + 1] && channel0 == data_type_bits::f16;
|
||||
outputs[ch] = is_fp16_ch ? gather_half_regs[ch / 2] : reg;
|
||||
}
|
||||
|
||||
// Grouping. Only replace relevant bits...
|
||||
if (outputs[0] == outputs[1]) outputs[0] = "";
|
||||
if (outputs[2] == outputs[3]) outputs[2] = "";
|
||||
|
||||
// Assemble
|
||||
bool group = false;
|
||||
std::string result = "";
|
||||
constexpr std::string_view swz_mask = "xyzw";
|
||||
|
||||
for (int ch = 0; ch < 4; ++ch)
|
||||
{
|
||||
if (outputs[ch].empty())
|
||||
{
|
||||
group = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.empty())
|
||||
{
|
||||
result += ", ";
|
||||
}
|
||||
|
||||
if (group)
|
||||
{
|
||||
ensure(ch > 0);
|
||||
group = false;
|
||||
|
||||
if (outputs[ch] == reg)
|
||||
{
|
||||
result += reg + "." + swz_mask[ch - 1] + swz_mask[ch];
|
||||
continue;
|
||||
}
|
||||
|
||||
result += outputs[ch];
|
||||
continue;
|
||||
}
|
||||
|
||||
const int subch = outputs[ch] == reg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
|
||||
result += outputs[ch] + "." + swz_mask[subch];
|
||||
}
|
||||
|
||||
// Optimize dual-gather (128-bit gather) to use special function
|
||||
const std::string double_gather = gather_half_regs[0] + ", " + gather_half_regs[1];
|
||||
if (result == double_gather)
|
||||
{
|
||||
result = "gather(h" + std::to_string(half_index) + ", h" + std::to_string(half_index + 1) + ")";
|
||||
}
|
||||
|
||||
return "(" + result + ")";
|
||||
}
|
||||
|
||||
std::string MixedPrecisionRegister::fetch_halfreg(u32 word_index) const
|
||||
{
|
||||
// Reads half-word 0 (H16x4) from a full real (R32x4) register
|
||||
constexpr std::string_view swz_mask = "xyzw";
|
||||
const std::string reg = "r" + std::to_string(file_index);
|
||||
const std::string hreg = "h" + std::to_string(file_index * 2 + word_index);
|
||||
|
||||
const std::string word0_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2] + ")";
|
||||
const std::string word1_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2 + 1] + ")";
|
||||
const std::string words[] = {
|
||||
"unpackHalf2x16(" + word0_bits + ")",
|
||||
"unpackHalf2x16(" + word1_bits + ")"
|
||||
};
|
||||
|
||||
// Assemble
|
||||
std::string outputs[4];
|
||||
|
||||
ensure(word_index <= 1);
|
||||
const int word_offset = word_index * 4;
|
||||
for (int ch = 0; ch < 4; ++ch)
|
||||
{
|
||||
outputs[ch] = content_mask[ch + word_offset] == data_type_bits::f32
|
||||
? words[ch / 2]
|
||||
: hreg;
|
||||
}
|
||||
|
||||
// Grouping. Only replace relevant bits...
|
||||
if (outputs[0] == outputs[1]) outputs[0] = "";
|
||||
if (outputs[2] == outputs[3]) outputs[2] = "";
|
||||
|
||||
// Assemble
|
||||
bool group = false;
|
||||
std::string result = "";
|
||||
|
||||
for (int ch = 0; ch < 4; ++ch)
|
||||
{
|
||||
if (outputs[ch].empty())
|
||||
{
|
||||
group = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!result.empty())
|
||||
{
|
||||
result += ", ";
|
||||
}
|
||||
|
||||
if (group)
|
||||
{
|
||||
ensure(ch > 0);
|
||||
group = false;
|
||||
result += outputs[ch];
|
||||
|
||||
if (outputs[ch] == hreg)
|
||||
{
|
||||
result += std::string(".") + swz_mask[ch - 1] + swz_mask[ch];
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const int subch = outputs[ch] == hreg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
|
||||
result += outputs[ch] + "." + swz_mask[subch];
|
||||
}
|
||||
|
||||
return "(" + result + ")";
|
||||
}
|
||||
}
|
111
rpcs3/Emu/RSX/Program/FragmentProgramRegister.h
Normal file
111
rpcs3/Emu/RSX/Program/FragmentProgramRegister.h
Normal file
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <util/types.hpp>
|
||||
|
||||
namespace rsx
|
||||
{
|
||||
class MixedPrecisionRegister
|
||||
{
|
||||
enum data_type_bits
|
||||
{
|
||||
undefined = 0,
|
||||
f16 = 1,
|
||||
f32 = 2
|
||||
};
|
||||
|
||||
std::array<data_type_bits, 8> content_mask; // Content details for each half-word
|
||||
u32 file_index = umax;
|
||||
|
||||
void tag_h0(bool x, bool y, bool z, bool w);
|
||||
|
||||
void tag_h1(bool x, bool y, bool z, bool w);
|
||||
|
||||
void tag_r(bool x, bool y, bool z, bool w);
|
||||
|
||||
std::string fetch_halfreg(u32 word_index) const;
|
||||
|
||||
public:
|
||||
MixedPrecisionRegister();
|
||||
|
||||
void tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w);
|
||||
|
||||
std::string gather_r() const;
|
||||
|
||||
std::string split_h0() const
|
||||
{
|
||||
return fetch_halfreg(0);
|
||||
}
|
||||
|
||||
std::string split_h1() const
|
||||
{
|
||||
return fetch_halfreg(1);
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
// Return true if all values are unwritten to (undefined)
|
||||
bool floating() const
|
||||
{
|
||||
return file_index == umax;
|
||||
}
|
||||
|
||||
// Return true if the first half register is all undefined
|
||||
bool floating_h0() const
|
||||
{
|
||||
return content_mask[0] == content_mask[1] &&
|
||||
content_mask[1] == content_mask[2] &&
|
||||
content_mask[2] == content_mask[3] &&
|
||||
content_mask[3] == data_type_bits::undefined;
|
||||
}
|
||||
|
||||
// Return true if the second half register is all undefined
|
||||
bool floating_h1() const
|
||||
{
|
||||
return content_mask[4] == content_mask[5] &&
|
||||
content_mask[5] == content_mask[6] &&
|
||||
content_mask[6] == content_mask[7] &&
|
||||
content_mask[7] == data_type_bits::undefined;
|
||||
}
|
||||
|
||||
// Return true if any of the half-words are 16-bit
|
||||
bool requires_gather(u8 channel) const
|
||||
{
|
||||
// Data fetched from the single precision register requires merging of the two half registers
|
||||
const auto channel_offset = channel * 2;
|
||||
ensure(channel_offset <= 6);
|
||||
|
||||
return (content_mask[channel_offset] == data_type_bits::f16 || content_mask[channel_offset + 1] == data_type_bits::f16);
|
||||
}
|
||||
|
||||
// Return true if the entire 128-bit register is filled with 2xfp16x4 data words
|
||||
bool requires_gather128() const
|
||||
{
|
||||
// Full 128-bit check
|
||||
for (const auto& ch : content_mask)
|
||||
{
|
||||
if (ch == data_type_bits::f16)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return true if the half-register is polluted with fp32 data
|
||||
bool requires_split(u32 word_index) const
|
||||
{
|
||||
const u32 content_offset = word_index * 4;
|
||||
for (u32 i = 0; i < 4; ++i)
|
||||
{
|
||||
if (content_mask[content_offset + i] == data_type_bits::f32)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
@ -34,7 +34,8 @@ vec2 texture2DMSCoord(const in vec2 coords, const in uint flags)
|
|||
return coords;
|
||||
}
|
||||
|
||||
const vec2 wrapped_coords = mod(coords, vec2(1.0));
|
||||
const vec2 wrapped_coords_raw = mod(coords, vec2(1.0));
|
||||
const vec2 wrapped_coords = mod(wrapped_coords_raw + vec2(1.0), vec2(1.0));
|
||||
const bvec2 wrap_control_mask = bvec2(uvec2(flags) & uvec2(WRAP_S_MASK, WRAP_T_MASK));
|
||||
return _select(coords, wrapped_coords, wrap_control_mask);
|
||||
}
|
||||
|
|
|
@ -340,12 +340,16 @@ vertex_program_utils::vertex_program_metadata vertex_program_utils::analyse_vert
|
|||
|
||||
usz vertex_program_storage_hash::operator()(const RSXVertexProgram &program) const
|
||||
{
|
||||
usz hash = vertex_program_utils::get_vertex_program_ucode_hash(program);
|
||||
hash ^= program.ctrl;
|
||||
hash ^= program.output_mask;
|
||||
hash ^= program.texture_state.texture_dimensions;
|
||||
hash ^= program.texture_state.multisampled_textures;
|
||||
return hash;
|
||||
const usz ucode_hash = vertex_program_utils::get_vertex_program_ucode_hash(program);
|
||||
const u32 state_params[] =
|
||||
{
|
||||
program.ctrl,
|
||||
program.output_mask,
|
||||
program.texture_state.texture_dimensions,
|
||||
program.texture_state.multisampled_textures,
|
||||
};
|
||||
const usz metadata_hash = rpcs3::hash_array(state_params);
|
||||
return rpcs3::hash64(ucode_hash, metadata_hash);
|
||||
}
|
||||
|
||||
bool vertex_program_compare::operator()(const RSXVertexProgram &binary1, const RSXVertexProgram &binary2) const
|
||||
|
@ -541,24 +545,33 @@ usz fragment_program_utils::get_fragment_program_ucode_hash(const RSXFragmentPro
|
|||
|
||||
usz fragment_program_storage_hash::operator()(const RSXFragmentProgram& program) const
|
||||
{
|
||||
usz hash = fragment_program_utils::get_fragment_program_ucode_hash(program);
|
||||
hash ^= program.ctrl;
|
||||
hash ^= +program.two_sided_lighting;
|
||||
hash ^= program.texture_state.texture_dimensions;
|
||||
hash ^= program.texture_state.shadow_textures;
|
||||
hash ^= program.texture_state.redirected_textures;
|
||||
hash ^= program.texture_state.multisampled_textures;
|
||||
hash ^= program.texcoord_control_mask;
|
||||
|
||||
return hash;
|
||||
const usz ucode_hash = fragment_program_utils::get_fragment_program_ucode_hash(program);
|
||||
const u32 state_params[] =
|
||||
{
|
||||
program.ctrl,
|
||||
program.two_sided_lighting ? 1u : 0u,
|
||||
program.texture_state.texture_dimensions,
|
||||
program.texture_state.shadow_textures,
|
||||
program.texture_state.redirected_textures,
|
||||
program.texture_state.multisampled_textures,
|
||||
program.texcoord_control_mask,
|
||||
program.mrt_buffers_count
|
||||
};
|
||||
const usz metadata_hash = rpcs3::hash_array(state_params);
|
||||
return rpcs3::hash64(ucode_hash, metadata_hash);
|
||||
}
|
||||
|
||||
bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, const RSXFragmentProgram& binary2) const
|
||||
{
|
||||
if (binary1.ctrl != binary2.ctrl || binary1.texture_state != binary2.texture_state ||
|
||||
if (binary1.ucode_length != binary2.ucode_length ||
|
||||
binary1.ctrl != binary2.ctrl ||
|
||||
binary1.texture_state != binary2.texture_state ||
|
||||
binary1.texcoord_control_mask != binary2.texcoord_control_mask ||
|
||||
binary1.two_sided_lighting != binary2.two_sided_lighting)
|
||||
binary1.two_sided_lighting != binary2.two_sided_lighting ||
|
||||
binary1.mrt_buffers_count != binary2.mrt_buffers_count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
const void* instBuffer1 = binary1.get_data();
|
||||
const void* instBuffer2 = binary2.get_data();
|
||||
|
@ -569,7 +582,9 @@ bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, con
|
|||
const auto inst2 = v128::loadu(instBuffer2, instIndex);
|
||||
|
||||
if (inst1._u ^ inst2._u)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
instIndex++;
|
||||
// Skip constants
|
||||
|
@ -578,9 +593,11 @@ bool fragment_program_compare::operator()(const RSXFragmentProgram& binary1, con
|
|||
fragment_program_utils::is_constant(inst1._u32[3]))
|
||||
instIndex++;
|
||||
|
||||
bool end = ((inst1._u32[0] >> 8) & 0x1) && ((inst2._u32[0] >> 8) & 0x1);
|
||||
const bool end = ((inst1._u32[0] >> 8) & 0x1) && ((inst2._u32[0] >> 8) & 0x1);
|
||||
if (end)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -293,10 +293,10 @@ public:
|
|||
bool compile_async,
|
||||
bool allow_notification,
|
||||
Args&& ...args
|
||||
)
|
||||
)
|
||||
{
|
||||
const auto &vp_search = search_vertex_program(vertexShader);
|
||||
const auto &fp_search = search_fragment_program(fragmentShader);
|
||||
const auto& vp_search = search_vertex_program(vertexShader);
|
||||
const auto& fp_search = search_fragment_program(fragmentShader);
|
||||
|
||||
const bool already_existing_fragment_program = std::get<1>(fp_search);
|
||||
const bool already_existing_vertex_program = std::get<1>(vp_search);
|
||||
|
@ -385,7 +385,13 @@ public:
|
|||
|
||||
void fill_fragment_constants_buffer(std::span<f32> dst_buffer, const fragment_program_type& fragment_program, const RSXFragmentProgram& rsx_prog, bool sanitize = false) const
|
||||
{
|
||||
ensure((dst_buffer.size_bytes() >= ::narrow<int>(fragment_program.FragmentConstantOffsetCache.size()) * 16u));
|
||||
if (dst_buffer.size_bytes() < (fragment_program.FragmentConstantOffsetCache.size() * 16))
|
||||
{
|
||||
// This can happen if CELL alters the shader after it has been loaded by RSX.
|
||||
rsx_log.error("Insufficient constants buffer size passed to fragment program! Corrupt shader?");
|
||||
return;
|
||||
}
|
||||
|
||||
rsx::write_fragment_constants_to_buffer(dst_buffer, rsx_prog, fragment_program.FragmentConstantOffsetCache, sanitize);
|
||||
}
|
||||
|
||||
|
|
|
@ -300,8 +300,10 @@ struct RSXFragmentProgram
|
|||
u32 ucode_length = 0;
|
||||
u32 total_length = 0;
|
||||
u32 ctrl = 0;
|
||||
bool two_sided_lighting = false;
|
||||
u32 texcoord_control_mask = 0;
|
||||
u32 mrt_buffers_count = 0;
|
||||
|
||||
bool two_sided_lighting = false;
|
||||
|
||||
rsx::fragment_program_texture_state texture_state;
|
||||
rsx::fragment_program_texture_config texture_params;
|
||||
|
|
|
@ -223,10 +223,10 @@ struct RSXVertexProgram
|
|||
{
|
||||
std::vector<u32> data;
|
||||
rsx::vertex_program_texture_state texture_state;
|
||||
u32 ctrl;
|
||||
u32 output_mask;
|
||||
u32 base_address;
|
||||
u32 entry;
|
||||
u32 ctrl = 0;
|
||||
u32 output_mask = 0;
|
||||
u32 base_address = 0;
|
||||
u32 entry = 0;
|
||||
std::bitset<rsx::max_vertex_program_instructions> instruction_mask;
|
||||
std::set<u32> jump_table;
|
||||
|
||||
|
|
|
@ -227,6 +227,14 @@ struct ParamArray
|
|||
|
||||
return name;
|
||||
}
|
||||
|
||||
void Clear()
|
||||
{
|
||||
for (auto& param : params)
|
||||
{
|
||||
param.clear();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class ShaderVariable
|
||||
|
|
|
@ -110,7 +110,7 @@ namespace rsx
|
|||
multisampled_textures == other.multisampled_textures;
|
||||
}
|
||||
|
||||
int VertexProgramBase::TranslateConstantsRange(int first_index, int count) const
|
||||
int VertexProgramBase::translate_constants_range(int first_index, int count) const
|
||||
{
|
||||
// The constant ids should be sorted, so just find the first one and check for continuity
|
||||
int index = -1;
|
||||
|
@ -157,4 +157,31 @@ namespace rsx
|
|||
// OOB or partial match
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool VertexProgramBase::overlaps_constants_range(int first_index, int count) const
|
||||
{
|
||||
if (has_indexed_constants)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
const int last_index = first_index + count - 1;
|
||||
|
||||
// Early rejection test
|
||||
if (constant_ids.empty() || first_index > constant_ids.back() || last_index < first_index)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for any hits
|
||||
for (auto& idx : constant_ids)
|
||||
{
|
||||
if (idx >= first_index && idx <= last_index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,9 @@ namespace rsx
|
|||
// Translates an incoming range of constants against our mapping.
|
||||
// If there is no linear mapping available, return -1, otherwise returns the translated index of the first slot
|
||||
// TODO: Move this somewhere else during refactor
|
||||
int TranslateConstantsRange(int first_index, int count) const;
|
||||
int translate_constants_range(int first_index, int count) const;
|
||||
|
||||
// Returns true if this program consumes any constants in the range [first, first + count - 1]
|
||||
bool overlaps_constants_range(int first_index, int count) const;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1719,7 +1719,7 @@ namespace rsx
|
|||
|
||||
for (uint i = 0; i < mrt_buffers.size(); ++i)
|
||||
{
|
||||
if (rsx::method_registers.color_write_enabled(i))
|
||||
if (m_ctx->register_state->color_write_enabled(i))
|
||||
{
|
||||
const auto real_index = mrt_buffers[i];
|
||||
m_framebuffer_layout.color_write_enabled[real_index] = true;
|
||||
|
@ -1727,6 +1727,14 @@ namespace rsx
|
|||
}
|
||||
}
|
||||
|
||||
if (::size32(mrt_buffers) != current_fragment_program.mrt_buffers_count &&
|
||||
!m_graphics_state.test(rsx::pipeline_state::fragment_program_dirty) &&
|
||||
!is_current_program_interpreted())
|
||||
{
|
||||
// Notify that we should recompile the FS
|
||||
m_graphics_state |= rsx::pipeline_state::fragment_program_state_dirty;
|
||||
}
|
||||
|
||||
return any_found;
|
||||
};
|
||||
|
||||
|
@ -2038,9 +2046,10 @@ namespace rsx
|
|||
|
||||
m_graphics_state.clear(rsx::pipeline_state::fragment_program_dirty);
|
||||
|
||||
current_fragment_program.ctrl = rsx::method_registers.shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
||||
current_fragment_program.texcoord_control_mask = rsx::method_registers.texcoord_control_mask();
|
||||
current_fragment_program.two_sided_lighting = rsx::method_registers.two_side_light_en();
|
||||
current_fragment_program.ctrl = m_ctx->register_state->shader_control() & (CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS | CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT);
|
||||
current_fragment_program.texcoord_control_mask = m_ctx->register_state->texcoord_control_mask();
|
||||
current_fragment_program.two_sided_lighting = m_ctx->register_state->two_side_light_en();
|
||||
current_fragment_program.mrt_buffers_count = rsx::utility::get_mrt_buffers_count(m_ctx->register_state->surface_color_target());
|
||||
|
||||
if (method_registers.current_draw_clause.classify_mode() == primitive_class::polygon)
|
||||
{
|
||||
|
|
|
@ -436,6 +436,8 @@ namespace rsx
|
|||
|
||||
bool is_current_vertex_program_instanced() const { return !!(current_vertex_program.ctrl & RSX_SHADER_CONTROL_INSTANCED_CONSTANTS); }
|
||||
|
||||
virtual bool is_current_program_interpreted() const { return false; }
|
||||
|
||||
public:
|
||||
void reset();
|
||||
void init(u32 ctrlAddress);
|
||||
|
|
|
@ -78,6 +78,7 @@ namespace vk
|
|||
break;
|
||||
case vk::driver_vendor::LAVAPIPE:
|
||||
case vk::driver_vendor::V3DV:
|
||||
case vk::driver_vendor::PANVK:
|
||||
// TODO: Actually bench this. Using 32 for now to match other common configurations.
|
||||
case vk::driver_vendor::DOZEN:
|
||||
// Actual optimal size depends on the D3D device. Use 32 since it should work well on both AMD and NVIDIA
|
||||
|
|
|
@ -688,10 +688,10 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
|||
}
|
||||
|
||||
// Initialize optional allocation information with placeholders
|
||||
m_vertex_env_buffer_info = { m_vertex_env_ring_info.heap->value, 0, 32 };
|
||||
m_vertex_constants_buffer_info = { m_transform_constants_ring_info.heap->value, 0, 32 };
|
||||
m_fragment_env_buffer_info = { m_fragment_env_ring_info.heap->value, 0, 32 };
|
||||
m_fragment_texture_params_buffer_info = { m_fragment_texture_params_ring_info.heap->value, 0, 32 };
|
||||
m_vertex_env_buffer_info = { m_vertex_env_ring_info.heap->value, 0, 16 };
|
||||
m_vertex_constants_buffer_info = { m_transform_constants_ring_info.heap->value, 0, 16 };
|
||||
m_fragment_env_buffer_info = { m_fragment_env_ring_info.heap->value, 0, 16 };
|
||||
m_fragment_texture_params_buffer_info = { m_fragment_texture_params_ring_info.heap->value, 0, 16 };
|
||||
m_raster_env_buffer_info = { m_raster_env_ring_info.heap->value, 0, 128 };
|
||||
|
||||
const auto limits = m_device->gpu().get_limits();
|
||||
|
@ -730,7 +730,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
|||
else
|
||||
m_vertex_cache = std::make_unique<vk::weak_vertex_cache>();
|
||||
|
||||
m_shaders_cache = std::make_unique<vk::shader_cache>(*m_prog_buffer, "vulkan", "v1.94");
|
||||
m_shaders_cache = std::make_unique<vk::shader_cache>(*m_prog_buffer, "vulkan", "v1.95");
|
||||
|
||||
for (u32 i = 0; i < m_swapchain->get_swap_image_count(); ++i)
|
||||
{
|
||||
|
@ -2192,7 +2192,7 @@ void VKGSRender::load_program_env()
|
|||
return std::make_pair(m_instancing_buffer_ring_info.map(constants_data_table_offset, size), size);
|
||||
});
|
||||
|
||||
m_draw_processor.fill_constants_instancing_buffer(indirection_table_buf, constants_array_buf, *m_vertex_prog);
|
||||
m_draw_processor.fill_constants_instancing_buffer(indirection_table_buf, constants_array_buf, m_vertex_prog);
|
||||
m_instancing_buffer_ring_info.unmap();
|
||||
|
||||
m_instancing_indirection_buffer_info = { m_instancing_buffer_ring_info.heap->value, indirection_table_offset, indirection_table_buf.size() };
|
||||
|
@ -2219,7 +2219,7 @@ void VKGSRender::load_program_env()
|
|||
}
|
||||
}
|
||||
|
||||
if (update_fragment_constants && !update_instruction_buffers)
|
||||
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
|
||||
{
|
||||
check_heap_status(VK_HEAP_CHECK_FRAGMENT_CONSTANTS_STORAGE);
|
||||
|
||||
|
@ -2350,9 +2350,9 @@ void VKGSRender::load_program_env()
|
|||
}
|
||||
|
||||
// Clear flags
|
||||
u32 handled_flags = rsx::pipeline_state::fragment_state_dirty |
|
||||
rsx::flags32_t handled_flags =
|
||||
rsx::pipeline_state::fragment_state_dirty |
|
||||
rsx::pipeline_state::vertex_state_dirty |
|
||||
rsx::pipeline_state::fragment_constants_dirty |
|
||||
rsx::pipeline_state::fragment_texture_state_dirty;
|
||||
|
||||
if (!update_instancing_data)
|
||||
|
@ -2360,9 +2360,19 @@ void VKGSRender::load_program_env()
|
|||
handled_flags |= rsx::pipeline_state::transform_constants_dirty;
|
||||
}
|
||||
|
||||
if (update_fragment_constants && !m_shader_interpreter.is_interpreter(m_program))
|
||||
{
|
||||
handled_flags |= rsx::pipeline_state::fragment_constants_dirty;
|
||||
}
|
||||
|
||||
m_graphics_state.clear(handled_flags);
|
||||
}
|
||||
|
||||
bool VKGSRender::is_current_program_interpreted() const
|
||||
{
|
||||
return m_program && m_shader_interpreter.is_interpreter(m_program);
|
||||
}
|
||||
|
||||
void VKGSRender::upload_transform_constants(const rsx::io_buffer& buffer)
|
||||
{
|
||||
const usz transform_constants_size = (!m_vertex_prog || m_vertex_prog->has_indexed_constants) ? 8192 : m_vertex_prog->constant_ids.size() * 16;
|
||||
|
@ -2433,13 +2443,19 @@ void VKGSRender::update_vertex_env(u32 id, const vk::vertex_upload_info& vertex_
|
|||
|
||||
void VKGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 count)
|
||||
{
|
||||
if (!m_vertex_prog)
|
||||
if (!m_program || !m_vertex_prog)
|
||||
{
|
||||
// Shouldn't be reachable, but handle it correctly anyway
|
||||
m_graphics_state |= rsx::pipeline_state::transform_constants_dirty;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_vertex_prog->overlaps_constants_range(index, count))
|
||||
{
|
||||
// Nothing meaningful to us
|
||||
return;
|
||||
}
|
||||
|
||||
// Hot-patching transform constants mid-draw (instanced draw)
|
||||
std::pair<VkDeviceSize, VkDeviceSize> data_range;
|
||||
void* data_source = nullptr;
|
||||
|
@ -2453,7 +2469,7 @@ void VKGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou
|
|||
data_range = { m_vertex_constants_buffer_info.offset + byte_offset, byte_count };
|
||||
data_source = ®S(ctx)->transform_constants[index];
|
||||
}
|
||||
else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0)
|
||||
else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0)
|
||||
{
|
||||
const auto write_offset = xform_id * 16;
|
||||
const auto byte_count = count * 16;
|
||||
|
|
|
@ -288,6 +288,9 @@ public:
|
|||
// GRAPH backend
|
||||
void patch_transform_constants(rsx::context* ctx, u32 index, u32 count) override;
|
||||
|
||||
// Misc
|
||||
bool is_current_program_interpreted() const override;
|
||||
|
||||
protected:
|
||||
void clear_surface(u32 mask) override;
|
||||
void begin() override;
|
||||
|
|
|
@ -144,6 +144,9 @@ namespace vk
|
|||
case driver_vendor::HONEYKRISP:
|
||||
// Needs more testing
|
||||
break;
|
||||
case driver_vendor::PANVK:
|
||||
// Needs more testing
|
||||
break;
|
||||
default:
|
||||
rsx_log.warning("Unsupported device: %s", gpu_name);
|
||||
}
|
||||
|
|
|
@ -190,6 +190,7 @@ namespace vk
|
|||
case driver_vendor::LAVAPIPE:
|
||||
case driver_vendor::V3DV:
|
||||
case driver_vendor::HONEYKRISP:
|
||||
case driver_vendor::PANVK:
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -387,7 +387,7 @@ namespace vk
|
|||
|
||||
struct stencilonly_unresolve : depth_resolve_base
|
||||
{
|
||||
VkClearRect region{};
|
||||
VkClearRect clear_region{};
|
||||
VkClearAttachment clear_info{};
|
||||
|
||||
stencilonly_unresolve()
|
||||
|
@ -402,8 +402,8 @@ namespace vk
|
|||
renderpass_config.set_depth_mask(false);
|
||||
|
||||
clear_info.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT;
|
||||
region.baseArrayLayer = 0;
|
||||
region.layerCount = 1;
|
||||
clear_region.baseArrayLayer = 0;
|
||||
clear_region.layerCount = 1;
|
||||
|
||||
static_parameters_width = 3;
|
||||
|
||||
|
@ -425,7 +425,7 @@ namespace vk
|
|||
|
||||
void emit_geometry(vk::command_buffer& cmd) override
|
||||
{
|
||||
vkCmdClearAttachments(cmd, 1, &clear_info, 1, ®ion);
|
||||
vkCmdClearAttachments(cmd, 1, &clear_info, 1, &clear_region);
|
||||
|
||||
for (s32 write_mask = 0x1; write_mask <= 0x80; write_mask <<= 1)
|
||||
{
|
||||
|
@ -444,8 +444,8 @@ namespace vk
|
|||
|
||||
auto stencil_view = resolve_image->get_view(rsx::default_remap_vector.with_encoding(VK_REMAP_IDENTITY), VK_IMAGE_ASPECT_STENCIL_BIT);
|
||||
|
||||
region.rect.extent.width = resolve_image->width();
|
||||
region.rect.extent.height = resolve_image->height();
|
||||
clear_region.rect.extent.width = msaa_image->width();
|
||||
clear_region.rect.extent.height = msaa_image->height();
|
||||
|
||||
overlay_pass::run(
|
||||
cmd,
|
||||
|
|
|
@ -54,7 +54,8 @@ namespace vk
|
|||
LAVAPIPE,
|
||||
NVK,
|
||||
V3DV,
|
||||
HONEYKRISP
|
||||
HONEYKRISP,
|
||||
PANVK
|
||||
};
|
||||
|
||||
driver_vendor get_driver_vendor();
|
||||
|
|
|
@ -302,6 +302,11 @@ namespace vk
|
|||
return driver_vendor::HONEYKRISP;
|
||||
}
|
||||
|
||||
if (gpu_name.find("Panfrost") != umax)
|
||||
{
|
||||
return driver_vendor::PANVK;
|
||||
}
|
||||
|
||||
return driver_vendor::unknown;
|
||||
}
|
||||
else
|
||||
|
@ -329,6 +334,8 @@ namespace vk
|
|||
return driver_vendor::V3DV;
|
||||
case VK_DRIVER_ID_MESA_HONEYKRISP:
|
||||
return driver_vendor::HONEYKRISP;
|
||||
case VK_DRIVER_ID_MESA_PANVK:
|
||||
return driver_vendor::PANVK;
|
||||
default:
|
||||
// Mobile?
|
||||
return driver_vendor::unknown;
|
||||
|
@ -659,6 +666,12 @@ namespace vk
|
|||
enabled_features.textureCompressionBC = VK_FALSE;
|
||||
}
|
||||
|
||||
if (!pgpu->features.textureCompressionBC && pgpu->get_driver_vendor() == driver_vendor::PANVK)
|
||||
{
|
||||
rsx_log.error("Your GPU running on the PANVK driver does not support full texture block compression. Graphics may not render correctly.");
|
||||
enabled_features.textureCompressionBC = VK_FALSE;
|
||||
}
|
||||
|
||||
VkDeviceCreateInfo device = {};
|
||||
device.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
||||
device.pNext = nullptr;
|
||||
|
|
|
@ -51,7 +51,10 @@ namespace rsx
|
|||
u16 fp_shadow_textures;
|
||||
u16 fp_redirected_textures;
|
||||
u16 fp_multisampled_textures;
|
||||
u64 fp_reserved_0;
|
||||
u8 fp_mrt_count;
|
||||
u8 fp_reserved0;
|
||||
u16 fp_reserved1;
|
||||
u32 fp_reserved2;
|
||||
|
||||
pipeline_storage_type pipeline_properties;
|
||||
};
|
||||
|
@ -306,20 +309,24 @@ namespace rsx
|
|||
fs::write_file(vp_name, fs::rewrite, vp.data);
|
||||
}
|
||||
|
||||
u64 state_hash = 0;
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.vp_ctrl0);
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.vp_ctrl1);
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.fp_ctrl);
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.vp_texture_dimensions);
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.fp_texture_dimensions);
|
||||
state_hash ^= rpcs3::hash_base<u32>(data.fp_texcoord_control);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_height);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_pixel_layout);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_lighting_flags);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_shadow_textures);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_redirected_textures);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.vp_multisampled_textures);
|
||||
state_hash ^= rpcs3::hash_base<u16>(data.fp_multisampled_textures);
|
||||
const u32 state_params[] =
|
||||
{
|
||||
data.vp_ctrl0,
|
||||
data.vp_ctrl1,
|
||||
data.fp_ctrl,
|
||||
data.vp_texture_dimensions,
|
||||
data.fp_texture_dimensions,
|
||||
data.fp_texcoord_control,
|
||||
data.fp_height,
|
||||
data.fp_pixel_layout,
|
||||
data.fp_lighting_flags,
|
||||
data.fp_shadow_textures,
|
||||
data.fp_redirected_textures,
|
||||
data.vp_multisampled_textures,
|
||||
data.fp_multisampled_textures,
|
||||
data.fp_mrt_count,
|
||||
};
|
||||
const usz state_hash = rpcs3::hash_array(state_params);
|
||||
|
||||
const std::string pipeline_file_name = fmt::format("%llX+%llX+%llX+%llX.bin", data.vertex_program_hash, data.fragment_program_hash, data.pipeline_storage_hash, state_hash);
|
||||
const std::string pipeline_path = root_path + "/pipelines/" + pipeline_class_name + "/" + version_prefix + "/" + pipeline_file_name;
|
||||
|
@ -393,6 +400,7 @@ namespace rsx
|
|||
fp.texture_state.multisampled_textures = data.fp_multisampled_textures;
|
||||
fp.texcoord_control_mask = data.fp_texcoord_control;
|
||||
fp.two_sided_lighting = !!(data.fp_lighting_flags & 0x1);
|
||||
fp.mrt_buffers_count = data.fp_mrt_count;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -439,6 +447,7 @@ namespace rsx
|
|||
data_block.fp_shadow_textures = fp.texture_state.shadow_textures;
|
||||
data_block.fp_redirected_textures = fp.texture_state.redirected_textures;
|
||||
data_block.fp_multisampled_textures = fp.texture_state.multisampled_textures;
|
||||
data_block.fp_mrt_count = fp.mrt_buffers_count;
|
||||
|
||||
return data_block;
|
||||
}
|
||||
|
|
|
@ -60,13 +60,13 @@
|
|||
#include "Emu/RSX/VK/VulkanAPI.h"
|
||||
#endif
|
||||
|
||||
#include "Emu/RSX/GSRender.h"
|
||||
|
||||
LOG_CHANNEL(sys_log, "SYS");
|
||||
|
||||
// Preallocate 32 MiB
|
||||
stx::manual_typemap<void, 0x20'00000, 128> g_fixed_typemap;
|
||||
|
||||
bool g_log_all_errors = false;
|
||||
|
||||
bool g_use_rtm = false;
|
||||
u64 g_rtm_tx_limit1 = 0;
|
||||
u64 g_rtm_tx_limit2 = 0;
|
||||
|
@ -981,6 +981,11 @@ game_boot_result Emulator::BootGame(const std::string& path, const std::string&
|
|||
std::tie(m_path, m_path_original, argv, envp, data, disc, klic, hdd1, m_config_mode, m_config_path) = std::move(save_args);
|
||||
};
|
||||
}
|
||||
|
||||
if (result != game_boot_result::no_errors)
|
||||
{
|
||||
GetCallbacks().close_gs_frame();
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
@ -1022,6 +1027,16 @@ void Emulator::SetForceBoot(bool force_boot)
|
|||
m_force_boot = force_boot;
|
||||
}
|
||||
|
||||
void Emulator::SetContinuousMode(bool continuous_mode)
|
||||
{
|
||||
m_continuous_mode = continuous_mode;
|
||||
|
||||
if (GSRender* render = static_cast<GSRender*>(g_fxo->try_get<rsx::thread>()))
|
||||
{
|
||||
render->set_continuous_mode(continuous_mode);
|
||||
}
|
||||
}
|
||||
|
||||
game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, usz recursion_count)
|
||||
{
|
||||
if (recursion_count == 0 && m_restrict_emu_state_change)
|
||||
|
@ -1149,7 +1164,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||
|
||||
bool resolve_path_as_vfs_path = false;
|
||||
|
||||
const bool from_dev_flash = IsPathInsideDir(m_path, g_cfg_vfs.get_dev_flash());
|
||||
const bool from_dev_flash = IsPathInsideDir(m_path, g_cfg_vfs.get_dev_flash());
|
||||
|
||||
std::string savestate_build_version;
|
||||
std::string savestate_creation_date;
|
||||
|
@ -2912,8 +2927,14 @@ u64 get_sysutil_cb_manager_read_count();
|
|||
|
||||
void qt_events_aware_op(int repeat_duration_ms, std::function<bool()> wrapped_op);
|
||||
|
||||
void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savestate)
|
||||
void Emulator::GracefulShutdown(bool allow_autoexit, bool async_op, bool savestate, bool continuous_mode)
|
||||
{
|
||||
// Make sure we close the game window
|
||||
if (!continuous_mode)
|
||||
{
|
||||
Emu.SetContinuousMode(false);
|
||||
}
|
||||
|
||||
// Ensure no game has booted inbetween
|
||||
const auto guard = Emu.MakeEmulationStateGuard();
|
||||
|
||||
|
@ -3057,6 +3078,22 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
|
||||
*pause_thread = make_ptr(new named_thread("Savestate Prepare Thread"sv, [pause_thread, allow_autoexit, this]() mutable
|
||||
{
|
||||
struct scoped_success_guard
|
||||
{
|
||||
bool save_state_success = false;
|
||||
~scoped_success_guard()
|
||||
{
|
||||
if (!save_state_success)
|
||||
{
|
||||
// Reset continuous mode on savestate error
|
||||
Emu.SetContinuousMode(false);
|
||||
|
||||
// Reset after_kill_callback (which is usually used for Emu.Restart in combination with savestates)
|
||||
Emu.after_kill_callback = nullptr;
|
||||
}
|
||||
}
|
||||
} success_guard {};
|
||||
|
||||
std::vector<std::pair<shared_ptr<named_thread<spu_thread>>, u32>> paused_spus;
|
||||
|
||||
if (!try_lock_spu_threads_in_a_state_compatible_with_savestates(false, &paused_spus))
|
||||
|
@ -3127,6 +3164,8 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
return;
|
||||
}
|
||||
|
||||
success_guard.save_state_success = true;
|
||||
|
||||
CallFromMainThread([allow_autoexit, this, paused_spus]()
|
||||
{
|
||||
savestate_stage stage{};
|
||||
|
@ -3202,15 +3241,15 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
|
||||
sys_log.notice("Stopping emulator...");
|
||||
|
||||
const bool continuous_savestate_mode = savestate && !g_cfg.savestate.suspend_emu;
|
||||
|
||||
// Show visual feedback to the user in case that stopping takes a while.
|
||||
// This needs to be done before actually stopping, because otherwise the necessary threads will be terminated before we can show an image.
|
||||
if (g_fxo->try_get<named_thread<progress_dialog_server>>() && (continuous_savestate_mode || g_progr_text.operator bool()))
|
||||
{
|
||||
// Show visual feedback to the user in case that stopping takes a while.
|
||||
// This needs to be done before actually stopping, because otherwise the necessary threads will be terminated before we can show an image.
|
||||
if (auto progress_dialog = g_fxo->try_get<named_thread<progress_dialog_server>>(); progress_dialog && g_progr_text.operator bool())
|
||||
{
|
||||
// We are currently showing a progress dialog. Notify it that we are going to stop emulation.
|
||||
g_system_progress_stopping = true;
|
||||
std::this_thread::sleep_for(20ms); // Enough for one frame to be rendered
|
||||
}
|
||||
// Notify progress dialog that we are going to stop emulation
|
||||
g_system_progress_stopping = continuous_savestate_mode ? system_progress_stop_state::stop_state_continuous_savestate : system_progress_stop_state::stop_state_stopping;
|
||||
std::this_thread::sleep_for(30ms); // Enough for one frame to be rendered
|
||||
}
|
||||
|
||||
// Signal threads
|
||||
|
@ -3282,7 +3321,10 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
if (auto ar_ptr = to_ar->load())
|
||||
{
|
||||
// Total amount of waiting: about 10s
|
||||
GetCallbacks().on_save_state_progress(closed_sucessfully, ar_ptr, verbose_message.get(), init_mtx);
|
||||
if (g_cfg.savestate.suspend_emu)
|
||||
{
|
||||
GetCallbacks().on_save_state_progress(closed_sucessfully, ar_ptr, verbose_message.get(), init_mtx);
|
||||
}
|
||||
|
||||
while (thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
|
@ -3295,7 +3337,6 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s
|
|||
thread_ctrl::wait_for(5'000);
|
||||
}
|
||||
|
||||
|
||||
*closed_sucessfully = true;
|
||||
}));
|
||||
|
||||
|
@ -3866,89 +3907,6 @@ std::string Emulator::GetFormattedTitle(double fps) const
|
|||
return rpcs3::get_formatted_title(title_data);
|
||||
}
|
||||
|
||||
s32 error_code::error_report(s32 result, const logs::message* channel, const char* fmt, const fmt_type_info* sup, const u64* args)
|
||||
{
|
||||
static thread_local std::string g_tls_error_str;
|
||||
static thread_local std::unordered_map<std::string, usz> g_tls_error_stats;
|
||||
|
||||
if (!channel)
|
||||
{
|
||||
channel = &sys_log.error;
|
||||
}
|
||||
|
||||
if (!sup && !args)
|
||||
{
|
||||
if (!fmt)
|
||||
{
|
||||
// Report and clean error state
|
||||
for (auto&& pair : g_tls_error_stats)
|
||||
{
|
||||
if (pair.second > 3)
|
||||
{
|
||||
channel->operator()("Stat: %s [x%u]", pair.first, pair.second);
|
||||
}
|
||||
}
|
||||
|
||||
g_tls_error_stats.clear();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ensure(fmt);
|
||||
|
||||
const char* func = "Unknown function";
|
||||
|
||||
if (auto ppu = get_current_cpu_thread<ppu_thread>())
|
||||
{
|
||||
if (auto current = ppu->current_function)
|
||||
{
|
||||
func = current;
|
||||
}
|
||||
}
|
||||
else if (auto spu = get_current_cpu_thread<spu_thread>())
|
||||
{
|
||||
if (auto current = spu->current_func; current && spu->start_time)
|
||||
{
|
||||
func = current;
|
||||
}
|
||||
}
|
||||
|
||||
// Format log message (use preallocated buffer)
|
||||
g_tls_error_str.clear();
|
||||
|
||||
fmt::append(g_tls_error_str, "'%s' failed with 0x%08x", func, result);
|
||||
|
||||
// Add spacer between error and fmt if necessary
|
||||
if (fmt[0] != ' ')
|
||||
g_tls_error_str += " : ";
|
||||
|
||||
fmt::raw_append(g_tls_error_str, fmt, sup, args);
|
||||
|
||||
// Update stats and check log threshold
|
||||
|
||||
if (g_log_all_errors) [[unlikely]]
|
||||
{
|
||||
if (!g_tls_error_stats.empty())
|
||||
{
|
||||
// Report and clean error state
|
||||
error_report(0, nullptr, nullptr, nullptr, nullptr);
|
||||
}
|
||||
|
||||
channel->operator()("%s", g_tls_error_str);
|
||||
}
|
||||
else
|
||||
{
|
||||
const auto stat = ++g_tls_error_stats[g_tls_error_str];
|
||||
|
||||
if (stat <= 3)
|
||||
{
|
||||
channel->operator()("%s [%u]", g_tls_error_str, stat);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void Emulator::ConfigurePPUCache() const
|
||||
{
|
||||
auto& _main = g_fxo->get<main_ppu_module<lv2_obj>>();
|
||||
|
|
|
@ -86,6 +86,7 @@ struct EmuCallbacks
|
|||
std::function<void(std::string_view title_id)> init_pad_handler;
|
||||
std::function<void()> update_emu_settings;
|
||||
std::function<void()> save_emu_settings;
|
||||
std::function<void()> close_gs_frame;
|
||||
std::function<std::unique_ptr<class GSFrameBase>()> get_gs_frame;
|
||||
std::function<std::shared_ptr<class camera_handler_base>()> get_camera_handler;
|
||||
std::function<std::shared_ptr<class music_handler_base>()> get_music_handler;
|
||||
|
@ -154,6 +155,7 @@ class Emulator final
|
|||
// 2. It signifies that we don't want to exit on Kill(), for example if we want to transition to another application.
|
||||
bool m_force_boot = false;
|
||||
|
||||
bool m_continuous_mode = false;
|
||||
bool m_has_gui = true;
|
||||
|
||||
bool m_state_inspection_savestate = false;
|
||||
|
@ -346,6 +348,15 @@ public:
|
|||
return m_config_mode == cfg_mode::continuous;
|
||||
}
|
||||
|
||||
bool ContinuousModeEnabled(bool reset)
|
||||
{
|
||||
if (reset)
|
||||
{
|
||||
return std::exchange(m_continuous_mode, false);
|
||||
}
|
||||
return m_continuous_mode;
|
||||
}
|
||||
|
||||
class emulation_state_guard_t
|
||||
{
|
||||
class Emulator* _this = nullptr;
|
||||
|
@ -385,6 +396,7 @@ public:
|
|||
bool BootRsxCapture(const std::string& path);
|
||||
|
||||
void SetForceBoot(bool force_boot);
|
||||
void SetContinuousMode(bool continuous_mode);
|
||||
|
||||
game_boot_result Load(const std::string& title_id = "", bool is_disc_patch = false, usz recursion_count = 0);
|
||||
void Run(bool start_playtime);
|
||||
|
@ -407,7 +419,7 @@ public:
|
|||
|
||||
bool Pause(bool freeze_emulation = false, bool show_resume_message = true);
|
||||
void Resume();
|
||||
void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false);
|
||||
void GracefulShutdown(bool allow_autoexit = true, bool async_op = false, bool savestate = false, bool continuous_mode = false);
|
||||
void Kill(bool allow_autoexit = true, bool savestate = false, savestate_stage* stage = nullptr);
|
||||
game_boot_result Restart(bool graceful = true);
|
||||
bool Quit(bool force_quit);
|
||||
|
@ -456,8 +468,6 @@ public:
|
|||
|
||||
extern Emulator Emu;
|
||||
|
||||
extern bool g_log_all_errors;
|
||||
|
||||
extern bool g_use_rtm;
|
||||
extern u64 g_rtm_tx_limit1;
|
||||
extern u64 g_rtm_tx_limit2;
|
||||
|
|
|
@ -295,6 +295,7 @@ enum class localized_string_id
|
|||
PROGRESS_DIALOG_OF,
|
||||
PROGRESS_DIALOG_PLEASE_WAIT,
|
||||
PROGRESS_DIALOG_STOPPING_PLEASE_WAIT,
|
||||
PROGRESS_DIALOG_SAVESTATE_PLEASE_WAIT,
|
||||
PROGRESS_DIALOG_SCANNING_PPU_EXECUTABLE,
|
||||
PROGRESS_DIALOG_ANALYZING_PPU_EXECUTABLE,
|
||||
PROGRESS_DIALOG_SCANNING_PPU_MODULES,
|
||||
|
|
|
@ -322,7 +322,10 @@ bool boot_last_savestate(bool testing)
|
|||
if (result)
|
||||
{
|
||||
sys_log.success("Booting the most recent savestate \'%s\' using the Reload shortcut.", savestate_path);
|
||||
Emu.GracefulShutdown(false);
|
||||
|
||||
// Make sure we keep the game window opened
|
||||
Emu.SetContinuousMode(true);
|
||||
Emu.GracefulShutdown(false, false, false, true);
|
||||
|
||||
if (game_boot_result error = Emu.BootGame(savestate_path, "", true); error != game_boot_result::no_errors)
|
||||
{
|
||||
|
|
|
@ -347,10 +347,10 @@ struct cfg_root : cfg::node
|
|||
cfg::_bool show_rpcn_popups{ this, "Show RPCN popups", true, true };
|
||||
cfg::_bool show_shader_compilation_hint{ this, "Show shader compilation hint", true, true };
|
||||
cfg::_bool show_ppu_compilation_hint{ this, "Show PPU compilation hint", true, true };
|
||||
cfg::_bool show_autosave_autoload_hint{ this, "Show autosave/autoload hint", false, true };
|
||||
cfg::_bool show_pressure_intensity_toggle_hint{ this, "Show pressure intensity toggle hint", true, true };
|
||||
cfg::_bool show_analog_limiter_toggle_hint{ this, "Show analog limiter toggle hint", true, true };
|
||||
cfg::_bool show_mouse_and_keyboard_toggle_hint{ this, "Show mouse and keyboard toggle hint", true, true };
|
||||
cfg::_bool show_autosave_autoload_hint{ this, "Show autosave/autoload hint", false, true };
|
||||
cfg::_bool use_native_interface{ this, "Use native user interface", true };
|
||||
cfg::string gdb_server{ this, "GDB Server", "127.0.0.1:2345" };
|
||||
cfg::_bool silence_all_logs{ this, "Silence All Logs", false, true };
|
||||
|
|
|
@ -26,7 +26,7 @@ atomic_t<u32> g_progr_pdone{0};
|
|||
atomic_t<bool> g_system_progress_canceled{false};
|
||||
|
||||
// For showing feedback while stopping emulation
|
||||
atomic_t<bool> g_system_progress_stopping{false};
|
||||
atomic_t<system_progress_stop_state> g_system_progress_stopping{system_progress_stop_state::stop_state_disabled};
|
||||
|
||||
namespace rsx::overlays
|
||||
{
|
||||
|
@ -40,7 +40,8 @@ namespace rsx::overlays
|
|||
void progress_dialog_server::operator()()
|
||||
{
|
||||
std::shared_ptr<rsx::overlays::progress_dialog> native_dlg;
|
||||
g_system_progress_stopping = false;
|
||||
g_system_progress_stopping = system_progress_stop_state::stop_state_disabled;
|
||||
g_system_progress_canceled = false;
|
||||
|
||||
const auto get_state = []()
|
||||
{
|
||||
|
@ -62,6 +63,41 @@ void progress_dialog_server::operator()()
|
|||
return whole_state;
|
||||
};
|
||||
|
||||
const auto create_native_dialog = [&native_dlg](const std::string& text, bool* show_overlay_message)
|
||||
{
|
||||
if (const auto renderer = rsx::get_current_renderer())
|
||||
{
|
||||
// Some backends like OpenGL actually initialize a lot of driver objects in the "on_init" method.
|
||||
// Wait for init to complete within reasonable time. Abort just in case we have hardware/driver issues.
|
||||
renderer->is_initialized.wait(0, atomic_wait_timeout(5 * 1000000000ull));
|
||||
|
||||
auto manager = g_fxo->try_get<rsx::overlays::display_manager>();
|
||||
|
||||
if (show_overlay_message)
|
||||
{
|
||||
*show_overlay_message = g_fxo->get<progress_dialog_workaround>().show_overlay_message_only;
|
||||
if (*show_overlay_message)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (manager)
|
||||
{
|
||||
MsgDialogType type{};
|
||||
type.se_mute_on = true;
|
||||
type.se_normal = true;
|
||||
type.bg_invisible = true;
|
||||
type.disable_cancel = true;
|
||||
type.progress_bar_count = 1;
|
||||
|
||||
native_dlg = manager->create<rsx::overlays::progress_dialog>(true);
|
||||
native_dlg->show(false, text, type, msg_dialog_source::sys_progress, nullptr);
|
||||
native_dlg->progress_bar_set_message(0, get_localized_string(localized_string_id::PROGRESS_DIALOG_PLEASE_WAIT));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (!g_system_progress_stopping && thread_ctrl::state() != thread_state::aborting)
|
||||
{
|
||||
// Wait for the start condition
|
||||
|
@ -112,29 +148,7 @@ void progress_dialog_server::operator()()
|
|||
bool show_overlay_message = false; // Only show an overlay message after initial loading is done.
|
||||
std::shared_ptr<MsgDialogBase> dlg;
|
||||
|
||||
if (const auto renderer = rsx::get_current_renderer())
|
||||
{
|
||||
// Some backends like OpenGL actually initialize a lot of driver objects in the "on_init" method.
|
||||
// Wait for init to complete within reasonable time. Abort just in case we have hardware/driver issues.
|
||||
renderer->is_initialized.wait(0, atomic_wait_timeout(5 * 1000000000ull));
|
||||
|
||||
auto manager = g_fxo->try_get<rsx::overlays::display_manager>();
|
||||
show_overlay_message = g_fxo->get<progress_dialog_workaround>().show_overlay_message_only;
|
||||
|
||||
if (manager && !show_overlay_message)
|
||||
{
|
||||
MsgDialogType type{};
|
||||
type.se_mute_on = true;
|
||||
type.se_normal = true;
|
||||
type.bg_invisible = true;
|
||||
type.disable_cancel = true;
|
||||
type.progress_bar_count = 1;
|
||||
|
||||
native_dlg = manager->create<rsx::overlays::progress_dialog>(true);
|
||||
native_dlg->show(false, text0, type, msg_dialog_source::sys_progress, nullptr);
|
||||
native_dlg->progress_bar_set_message(0, get_localized_string(localized_string_id::PROGRESS_DIALOG_PLEASE_WAIT));
|
||||
}
|
||||
}
|
||||
create_native_dialog(text0, &show_overlay_message);
|
||||
|
||||
if (!show_overlay_message && !native_dlg && (dlg = Emu.GetCallbacks().get_msg_dialog()))
|
||||
{
|
||||
|
@ -391,6 +405,7 @@ void progress_dialog_server::operator()()
|
|||
else if (native_dlg)
|
||||
{
|
||||
native_dlg->close(false, false);
|
||||
native_dlg.reset();
|
||||
}
|
||||
else if (dlg)
|
||||
{
|
||||
|
@ -410,10 +425,25 @@ void progress_dialog_server::operator()()
|
|||
g_progr_ptotal.notify_all();
|
||||
}
|
||||
|
||||
if (native_dlg && g_system_progress_stopping)
|
||||
if (g_system_progress_stopping)
|
||||
{
|
||||
native_dlg->set_text(get_localized_string(localized_string_id::PROGRESS_DIALOG_STOPPING_PLEASE_WAIT));
|
||||
native_dlg->refresh();
|
||||
const std::string text = get_localized_string(
|
||||
g_system_progress_stopping == system_progress_stop_state::stop_state_continuous_savestate
|
||||
? localized_string_id::PROGRESS_DIALOG_SAVESTATE_PLEASE_WAIT
|
||||
: localized_string_id::PROGRESS_DIALOG_STOPPING_PLEASE_WAIT
|
||||
);
|
||||
if (native_dlg)
|
||||
{
|
||||
native_dlg->set_text(text);
|
||||
}
|
||||
else
|
||||
{
|
||||
create_native_dialog(text, nullptr);
|
||||
}
|
||||
if (native_dlg)
|
||||
{
|
||||
native_dlg->refresh();
|
||||
}
|
||||
}
|
||||
|
||||
if (g_progr_ptotal.exchange(0))
|
||||
|
|
|
@ -36,6 +36,13 @@ struct alignas(16) progress_dialog_string_t
|
|||
}
|
||||
};
|
||||
|
||||
enum system_progress_stop_state : u32
|
||||
{
|
||||
stop_state_disabled = 0,
|
||||
stop_state_stopping,
|
||||
stop_state_continuous_savestate
|
||||
};
|
||||
|
||||
extern progress_dialog_string_t g_progr_text;
|
||||
extern atomic_t<u32> g_progr_ftotal;
|
||||
extern atomic_t<u32> g_progr_fdone;
|
||||
|
@ -44,7 +51,7 @@ extern atomic_t<u64> g_progr_fknown_bits;
|
|||
extern atomic_t<u32> g_progr_ptotal;
|
||||
extern atomic_t<u32> g_progr_pdone;
|
||||
extern atomic_t<bool> g_system_progress_canceled;
|
||||
extern atomic_t<bool> g_system_progress_stopping;
|
||||
extern atomic_t<system_progress_stop_state> g_system_progress_stopping;
|
||||
|
||||
// Initialize progress dialog (can be recursive)
|
||||
class scoped_progress_dialog final
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue