Merge branch 'master' into idm_remove

This commit is contained in:
Megamouse 2025-01-16 08:58:18 +01:00 committed by GitHub
commit 778dddd0a6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
151 changed files with 3321 additions and 1052 deletions

2
3rdparty/FAudio vendored

@ -1 +1 @@
Subproject commit b7c2e109ea86b82109244c9c4569ce9ad0c884df
Subproject commit 707114aef2907793644d4067a6e7b09b51502ca9

@ -1 +1 @@
Subproject commit f5e92d76973a7a53f517579bc95d61483bf108c0
Subproject commit 51f5bd68b9b806d2c92b4318164d28b49357da31

@ -1 +1 @@
Subproject commit 9c821dc21ccbd69b2bda421fdb35cb4ae2da8f5e
Subproject commit fa24d868ac2f8fd558e4e914c9863411245db8fd

2
3rdparty/pugixml vendored

@ -1 +1 @@
Subproject commit db78afc2b7d8f043b4bc6b185635d949ea2ed2a8
Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38

View file

@ -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()

@ -1 +1 @@
Subproject commit bdd62314f00fca0e216bf8c963c8eeff6327e0cb
Subproject commit 239b85c80438bf60d9a5b9e0ebe9ff097a760d0d

View file

@ -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

@ -1 +1 @@
Subproject commit bbb27a5efb85b92a0486cf361a8635715a53f6ba
Subproject commit e626a72bc2321cd320e953a0ccf1584cad60f363

View file

@ -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"

View file

@ -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;
}
}

View file

@ -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)

View file

@ -14,6 +14,7 @@ enum class cheat_type : u8
signed_16_cheat,
signed_32_cheat,
signed_64_cheat,
float_32_cheat,
max
};

View file

@ -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)
{

View file

@ -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{};

View file

@ -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()

View file

@ -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

View 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;
}

View file

@ -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;
}

View file

@ -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

View file

@ -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;

View file

@ -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)

View file

@ -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;

View file

@ -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++)
{

View file

@ -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)

View file

@ -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
{

View file

@ -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;

View file

@ -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)

View file

@ -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)

View file

@ -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;
};
}

View file

@ -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--;

View file

@ -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)

View file

@ -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)

View file

@ -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();
}

View file

@ -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;
};
}

View file

@ -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&){}

View file

@ -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);
});

View file

@ -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;
};
}

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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)
{

View file

@ -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();

View file

@ -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];
}

View file

@ -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);
};

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;
}
};

View file

@ -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;

View file

@ -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 };

View file

@ -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;
}

View file

@ -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};

View file

@ -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)
{

View file

@ -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)

View file

@ -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);
}

View file

@ -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.

View file

@ -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;
};
}

View file

@ -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 = &REGS(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;

View file

@ -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;

View file

@ -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);
}

View file

@ -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;
};
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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;

View file

@ -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);
});

View file

@ -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);

View file

@ -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;

View file

@ -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;

View 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 + ")";
}
}

View 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;
}
};
}

View file

@ -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);
}

View file

@ -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;
}
}
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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;

View file

@ -227,6 +227,14 @@ struct ParamArray
return name;
}
void Clear()
{
for (auto& param : params)
{
param.clear();
}
}
};
class ShaderVariable

View file

@ -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;
}
}

View file

@ -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;
};
}

View file

@ -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)
{

View file

@ -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);

View file

@ -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

View file

@ -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 = &REGS(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;

View file

@ -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;

View file

@ -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);
}

View file

@ -190,6 +190,7 @@ namespace vk
case driver_vendor::LAVAPIPE:
case driver_vendor::V3DV:
case driver_vendor::HONEYKRISP:
case driver_vendor::PANVK:
break;
}

View file

@ -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, &region);
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,

View file

@ -54,7 +54,8 @@ namespace vk
LAVAPIPE,
NVK,
V3DV,
HONEYKRISP
HONEYKRISP,
PANVK
};
driver_vendor get_driver_vendor();

View file

@ -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;

View file

@ -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;
}

View file

@ -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>>();

View file

@ -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;

View file

@ -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,

View file

@ -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)
{

View file

@ -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 };

View file

@ -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))

View file

@ -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