diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 434b870b52..3dca06c284 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -44,7 +44,7 @@ add_subdirectory(libpng EXCLUDE_FROM_ALL) # pugixml if (USE_SYSTEM_PUGIXML) - pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml>=1.11) + pkg_check_modules(PUGIXML REQUIRED IMPORTED_TARGET pugixml>=1.15) add_library(pugixml INTERFACE) target_link_libraries(pugixml INTERFACE PkgConfig::PUGIXML) else() diff --git a/3rdparty/libpng/libpng b/3rdparty/libpng/libpng index f5e92d7697..51f5bd68b9 160000 --- a/3rdparty/libpng/libpng +++ b/3rdparty/libpng/libpng @@ -1 +1 @@ -Subproject commit f5e92d76973a7a53f517579bc95d61483bf108c0 +Subproject commit 51f5bd68b9b806d2c92b4318164d28b49357da31 diff --git a/3rdparty/pugixml b/3rdparty/pugixml index db78afc2b7..ee86beb30e 160000 --- a/3rdparty/pugixml +++ b/3rdparty/pugixml @@ -1 +1 @@ -Subproject commit db78afc2b7d8f043b4bc6b185635d949ea2ed2a8 +Subproject commit ee86beb30e4973f5feffe3ce63bfa4fbadf72f38 diff --git a/3rdparty/wolfssl/CMakeLists.txt b/3rdparty/wolfssl/CMakeLists.txt index f74f604269..cf1a66a1f5 100644 --- a/3rdparty/wolfssl/CMakeLists.txt +++ b/3rdparty/wolfssl/CMakeLists.txt @@ -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() diff --git a/Utilities/File.cpp b/Utilities/File.cpp index 3c4a8c769d..ec0f49c89e 100644 --- a/Utilities/File.cpp +++ b/Utilities/File.cpp @@ -21,7 +21,7 @@ using namespace std::literals::string_literals; #include #include -static std::unique_ptr to_wchar(const std::string& source) +static std::unique_ptr 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 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" @@ -2021,7 +2021,7 @@ std::string fs::get_executable_dir() return s_exe_dir; } -const std::string& fs::get_config_dir() +const std::string& fs::get_config_dir([[maybe_unused]] bool get_config_subdirectory) { // Use magic static static const std::string s_dir = [] @@ -2103,6 +2103,14 @@ const std::string& fs::get_config_dir() return dir; }(); +#ifdef _WIN32 + if (get_config_subdirectory) + { + static const std::string subdir = s_dir + "config/"; + return subdir; + } +#endif + return s_dir; } @@ -2144,6 +2152,16 @@ const std::string& fs::get_cache_dir() return s_dir; } +const std::string& fs::get_log_dir() +{ +#ifdef _WIN32 + static const std::string s_dir = fs::get_config_dir() + "log/"; + return s_dir; +#else + return fs::get_cache_dir(); +#endif +} + const std::string& fs::get_temp_dir() { static const std::string s_dir = [] diff --git a/Utilities/File.h b/Utilities/File.h index 4768b24ac0..f3adaa15d4 100644 --- a/Utilities/File.h +++ b/Utilities/File.h @@ -599,12 +599,15 @@ namespace fs // Get executable containing directory std::string get_executable_dir(); - // Get configuration directory - const std::string& get_config_dir(); + // Get configuration directory. Set get_config_subdirectory to true to get the nested config dir on windows. + const std::string& get_config_dir(bool get_config_subdirectory = false); // Get common cache directory const std::string& get_cache_dir(); + // Get common log directory + const std::string& get_log_dir(); + // Temporary directory const std::string& get_temp_dir(); diff --git a/Utilities/Thread.h b/Utilities/Thread.h index 4350915d70..37c4a56f4c 100644 --- a/Utilities/Thread.h +++ b/Utilities/Thread.h @@ -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 @@ -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) { - static_cast(*this) = thread_state::finished; + static_cast(*this) = thread_state::destroying_context; } } diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 49b19f5bda..e726d9dbc8 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -125,19 +125,15 @@ patch_engine::patch_engine() std::string patch_engine::get_patch_config_path() { -#ifdef _WIN32 - const std::string config_dir = fs::get_config_dir() + "config/"; + const std::string config_dir = fs::get_config_dir(true); const std::string patch_path = config_dir + "patch_config.yml"; - +#ifdef _WIN32 if (!fs::create_path(config_dir)) { patch_log.error("Could not create path: %s (%s)", patch_path, fs::g_tls_error); } - - return patch_path; -#else - return fs::get_config_dir() + "patch_config.yml"; #endif + return patch_path; } std::string patch_engine::get_patches_path() @@ -1449,6 +1445,8 @@ static usz apply_modification(std::vector& applied, patch_engine::patch_inf void patch_engine::apply(std::vector& applied_total, const std::string& name, std::function mem_translate, u32 filesz, u32 min_addr) { + // applied_total may be non-empty, do not clear it + if (!m_map.contains(name)) { return; @@ -1597,6 +1595,9 @@ void patch_engine::apply(std::vector& 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) diff --git a/Utilities/cheat_info.h b/Utilities/cheat_info.h index 9c77d8dd7f..3ceb32716b 100644 --- a/Utilities/cheat_info.h +++ b/Utilities/cheat_info.h @@ -14,6 +14,7 @@ enum class cheat_type : u8 signed_16_cheat, signed_32_cheat, signed_64_cheat, + float_32_cheat, max }; diff --git a/Utilities/rXml.cpp b/Utilities/rXml.cpp index db52fb38e3..14ac0659f6 100644 --- a/Utilities/rXml.cpp +++ b/Utilities/rXml.cpp @@ -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) { diff --git a/Utilities/rXml.h b/Utilities/rXml.h index 71ca257902..8b70d06ee4 100644 --- a/Utilities/rXml.h +++ b/Utilities/rXml.h @@ -23,7 +23,7 @@ struct rXmlNode std::shared_ptr GetChildren(); std::shared_ptr 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 GetRoot(); pugi::xml_document handle{}; diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 059d0a4dbf..2b76f3c5e7 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -164,7 +164,7 @@ elseif(WIN32) COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/GuiConfigs $/GuiConfigs COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/bin/git $/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 "$,$/plugins,$/share/qt6/plugins>" --verbose 0 "$") endif() diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 4cd083ab90..f5baa5f068 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -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 @@ -593,6 +595,7 @@ if(TARGET 3rdparty_vulkan) RSX/VK/VKAsyncScheduler.cpp RSX/VK/VKCommandStream.cpp RSX/VK/VKCommonDecompiler.cpp + RSX/VK/VKCommonPipelineLayout.cpp RSX/VK/VKCompute.cpp RSX/VK/VKDMA.cpp RSX/VK/VKDraw.cpp diff --git a/rpcs3/Emu/Cell/ErrorCodes.cpp b/rpcs3/Emu/Cell/ErrorCodes.cpp new file mode 100644 index 0000000000..f81fe16a61 --- /dev/null +++ b/rpcs3/Emu/Cell/ErrorCodes.cpp @@ -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 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()) + { + if (auto current = ppu->current_function) + { + func = current; + } + } + else if (auto spu = get_current_cpu_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; +} diff --git a/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp b/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp index ff26b79473..36228d8a5e 100644 --- a/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellAtracXdec.cpp @@ -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(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(), 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 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().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(type), std::forward(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::make(handle.addr() + utils::align(static_cast(sizeof(AtracXdecContext)), 0x80) + ATXDEC_SPURS_STRUCTS_SIZE))); + const vm::var 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 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 _name = vm::make_str("HLE ATRAC3plus decoder"); const auto entry = g_fxo->get().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::ptrrun_thread_mutex, 0) == CELL_OK); handle->run_thread = false; + ensure(sys_mutex_unlock(ppu, handle->run_thread_mutex) == CELL_OK); + handle->send_command(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 thread_ret; + ensure(sys_ppu_thread_join(ppu, static_cast(handle->thread_id), +thread_ret) == CELL_OK); - if (vm::var ret; sys_ppu_thread_join(ppu, static_cast(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(CELL_ADEC_ERROR_FATAL) : CELL_OK; } error_code _CellAdecCoreOpStartSeq_atracx(ppu_thread& ppu, vm::ptr handle, vm::cptr atracxParam) @@ -808,15 +905,35 @@ error_code _CellAdecCoreOpRealign_atracx(vm::ptr handle, vm::p error_code _CellAdecCoreOpReleasePcm_atracx(ppu_thread& ppu, vm::ptr handle, s32 pcmHandle, vm::cptr 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().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; } diff --git a/rpcs3/Emu/Cell/Modules/cellAtracXdec.h b/rpcs3/Emu/Cell/Modules/cellAtracXdec.h index fc96b6ef86..a670a242c2 100644 --- a/rpcs3/Emu/Cell/Modules/cellAtracXdec.h +++ b/rpcs3/Emu/Cell/Modules/cellAtracXdec.h @@ -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 thread_id; // sys_ppu_thread_t - shared_mutex queue_mutex; // sys_mutex_t - cond_variable queue_not_empty; // sys_cond_t + be_t queue_mutex; // sys_mutex_t + be_t queue_not_empty; // sys_cond_t AdecCmdQueue cmd_queue; - shared_mutex output_mutex; // sys_mutex_t - cond_variable output_consumed; // sys_cond_t + be_t output_mutex; // sys_mutex_t + be_t output_consumed; // sys_cond_t be_t output_locked = false; be_t run_thread_mutex; // sys_mutex_t @@ -239,10 +250,10 @@ struct AtracXdecContext const vm::bptr 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. diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.cpp b/rpcs3/Emu/Cell/Modules/cellCamera.cpp index 64135ca5fa..95cf0a56f9 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.cpp +++ b/rpcs3/Emu/Cell/Modules/cellCamera.cpp @@ -148,9 +148,44 @@ void camera_context::save(utils::serial& ar) return; } - GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera); + const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera); ar(notify_data_map, start_timestamp_us, read_mode, is_streaming, is_attached, is_open, info, attr, frame_num); + + if (ar.is_writing() || version >= 2) + { + ar(is_attached_dirty); + } + + if (!ar.is_writing()) + { + if (is_open) + { + if (!open_camera()) + { + cellCamera.error("Failed to open camera while loading savestate"); + } + else if (is_streaming && !start_camera()) + { + cellCamera.error("Failed to start camera while loading savestate"); + } + } + } +} + +gem_camera_shared::gem_camera_shared(utils::serial& ar) +{ + save(ar); +} + +void gem_camera_shared::save(utils::serial& ar) +{ + const s32 version = GET_OR_USE_SERIALIZATION_VERSION(ar.is_writing(), cellCamera); + + if (ar.is_writing() || version >= 2) + { + ar(frame_timestamp_us, width, height, size, format); + } } static bool check_dev_num(s32 dev_num) @@ -435,7 +470,6 @@ error_code cellCameraInit() g_camera.attr[CELL_CAMERA_USBLOAD] = { 4 }; break; } - case fake_camera_type::eyetoy2: { g_camera.attr[CELL_CAMERA_SATURATION] = { 64 }; @@ -455,7 +489,6 @@ error_code cellCameraInit() g_camera.attr[CELL_CAMERA_AGCHIGH] = { 64 }; break; } - case fake_camera_type::uvc1_1: { g_camera.attr[CELL_CAMERA_DEVICEID] = { 0x5ca, 0x18d0 }; // KBCR-S01MU @@ -463,14 +496,14 @@ error_code cellCameraInit() g_camera.attr[CELL_CAMERA_NUMFRAME] = { 1 }; // Amount of supported resolutions break; } - default: cellCamera.todo("Trying to init cellCamera with un-researched camera type."); + break; } // TODO: Some other default attributes? Need to check the actual behaviour on a real PS3. - g_camera.is_attached = true; + g_camera.is_attached = g_cfg.io.camera != camera_handler::null; g_camera.init = 1; return CELL_OK; } @@ -816,8 +849,8 @@ s32 cellCameraIsAttached(s32 dev_num) // normally should be attached immediately after event queue is registered, but just to be sure if (!is_attached) { - g_camera.send_attach_state(true); - is_attached = g_camera.is_attached; + g_camera.is_attached = is_attached = true; + g_camera.is_attached_dirty = true; } } @@ -1606,9 +1639,15 @@ void camera_context::operator()() { while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) { + // send ATTACH event + if (init && is_attached_dirty && !Emu.IsPaused()) + { + send_attach_state(is_attached); + } + const s32 fps = info.framerate; - if (!fps || Emu.IsPaused() || g_cfg.io.camera == camera_handler::null) + if (!init || !fps || Emu.IsPaused() || g_cfg.io.camera == camera_handler::null) { thread_ctrl::wait_for(1000); // hack continue; @@ -1783,6 +1822,7 @@ void camera_context::reset_state() read_mode = CELL_CAMERA_READ_FUNCCALL; is_streaming = false; is_attached = false; + is_attached_dirty = false; is_open = false; info.framerate = 0; std::memset(&attr, 0, sizeof(attr)); @@ -1828,6 +1868,7 @@ void camera_context::send_attach_state(bool attached) // We're not expected to send any events for attaching/detaching is_attached = attached; + is_attached_dirty = false; } void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2) @@ -1862,15 +1903,13 @@ void camera_context::set_attr(s32 attrib, u32 arg1, u32 arg2) void camera_context::add_queue(u64 key, u64 source, u64 flag) { - std::lock_guard lock(mutex); { std::lock_guard lock_data_map(mutex_notify_data_map); notify_data_map[key] = { source, flag }; } - // send ATTACH event - HACKY - send_attach_state(is_attached); + is_attached_dirty = true; } void camera_context::remove_queue(u64 key) @@ -1897,7 +1936,8 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state { if (is_attached) { - send_attach_state(false); + is_attached = false; + is_attached_dirty = true; } if (handler) { @@ -1938,7 +1978,8 @@ bool camera_context::on_handler_state(camera_handler_base::camera_handler_state if (!is_attached) { cellCamera.warning("Camera handler not attached. Sending attach event...", static_cast(state)); - send_attach_state(true); + is_attached = true; + is_attached_dirty = true; } break; } diff --git a/rpcs3/Emu/Cell/Modules/cellCamera.h b/rpcs3/Emu/Cell/Modules/cellCamera.h index ae4786cdcc..28e8a3152c 100644 --- a/rpcs3/Emu/Cell/Modules/cellCamera.h +++ b/rpcs3/Emu/Cell/Modules/cellCamera.h @@ -427,6 +427,7 @@ public: atomic_t read_mode{CELL_CAMERA_READ_FUNCCALL}; atomic_t is_streaming{false}; atomic_t is_attached{false}; + atomic_t is_attached_dirty{false}; atomic_t is_open{false}; CellCameraInfoEx info{}; @@ -471,6 +472,13 @@ using camera_thread = named_thread; /// Shared data between cellGem and cellCamera struct gem_camera_shared { + gem_camera_shared() {} + gem_camera_shared(utils::serial& ar); + + void save(utils::serial& ar); + + SAVESTATE_INIT_POS(7); + atomic_t frame_timestamp_us{}; // latest read timestamp from cellCamera (cellCameraRead(Ex)) atomic_t width{640}; atomic_t height{480}; diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f7d274c4d0..8475b57b04 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -8,6 +8,7 @@ #include "Emu/Io/MouseHandler.h" #include "Emu/Io/PadHandler.h" #include "Emu/Io/gem_config.h" +#include "Emu/Io/interception.h" #include "Emu/system_config.h" #include "Emu/System.h" #include "Emu/IdManager.h" @@ -40,9 +41,18 @@ void fmt_class_string::format(std::string& out, u64 arg) case gem_btn::square: return "Square"; case gem_btn::move: return "Move"; case gem_btn::t: return "T"; - case gem_btn::count: return "Count"; case gem_btn::x_axis: return "X-Axis"; case gem_btn::y_axis: return "Y-Axis"; + case gem_btn::combo: return "Combo"; + case gem_btn::combo_start: return "Combo Start"; + case gem_btn::combo_select: return "Combo Select"; + case gem_btn::combo_triangle: return "Combo Triangle"; + case gem_btn::combo_circle: return "Combo Circle"; + case gem_btn::combo_cross: return "Combo Cross"; + case gem_btn::combo_square: return "Combo Square"; + case gem_btn::combo_move: return "Combo Move"; + case gem_btn::combo_t: return "Combo T"; + case gem_btn::count: return "Count"; } return unknown; @@ -177,6 +187,7 @@ using gun_thread = named_thread; cfg_gems g_cfg_gem_real; cfg_fake_gems g_cfg_gem_fake; +cfg_mouse_gems g_cfg_gem_mouse; struct gem_config_data { @@ -264,16 +275,119 @@ public: u64 start_timestamp_us = 0; + atomic_t m_wake_up = 0; + atomic_t m_done = 0; + + void wake_up() + { + m_wake_up.release(1); + m_wake_up.notify_one(); + } + + void done() + { + m_done.release(1); + m_done.notify_one(); + } + + bool wait_for_result(ppu_thread& ppu) + { + // Notify gem thread that the initial state after loading a savestate can be updated. + if (m_done.compare_and_swap_test(2, 0)) + { + m_done.notify_one(); + } + + while (!m_done && !ppu.is_stopped()) + { + thread_ctrl::wait_on(m_done, 0); + } + + if (ppu.is_stopped()) + { + ppu.state += cpu_flag::again; + return false; + } + + m_done = 0; + return true; + } + // helper functions bool is_controller_ready(u32 gem_num) const { return controllers[gem_num].status == CELL_GEM_STATUS_READY; } + void update_connections() + { + switch (g_cfg.io.move) + { + case move_handler::real: + case move_handler::fake: + { + connected_controllers = 0; + + std::lock_guard lock(pad::g_pad_mutex); + const auto handler = pad::get_pad_thread(true); + if (!handler) break; + + for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++) + { + const auto& pad = ::at32(handler->GetPads(), pad_num(i)); + const bool connected = (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && i < attribute.max_connect); + const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move; + + if (connected && is_real_move) + { + connected_controllers++; + controllers[i].status = CELL_GEM_STATUS_READY; + controllers[i].port = port_num(i); + } + else + { + controllers[i].status = CELL_GEM_STATUS_DISCONNECTED; + controllers[i].port = 0; + } + } + break; + } + case move_handler::raw_mouse: + { + connected_controllers = 0; + + auto& handler = g_fxo->get(); + std::lock_guard mouse_lock(handler.mutex); + + const MouseInfo& info = handler.GetInfo(); + + for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++) + { + const bool connected = i < attribute.max_connect && info.status[i] == CELL_MOUSE_STATUS_CONNECTED; + + if (connected) + { + connected_controllers++; + controllers[i].status = CELL_GEM_STATUS_READY; + controllers[i].port = port_num(i); + } + else + { + controllers[i].status = CELL_GEM_STATUS_DISCONNECTED; + controllers[i].port = 0; + } + } + break; + } + default: + { + break; + } + } + } + void update_calibration_status() { - std::scoped_lock lock(mtx); - for (u32 gem_num = 0; gem_num < CELL_GEM_MAX_NUM; gem_num++) { gem_controller& controller = controllers[gem_num]; @@ -285,7 +399,7 @@ public: if (g_cfg.io.move == move_handler::real) { std::lock_guard pad_lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad && pad->m_pad_handler == pad_handler::move) { @@ -315,7 +429,7 @@ public: if (g_cfg.io.move == move_handler::real) { std::lock_guard pad_lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad && pad->m_pad_handler == pad_handler::move) { @@ -345,7 +459,7 @@ public: { connected_controllers = 0; std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); for (u32 i = 0; i < std::min(attribute.max_connect, CELL_GEM_MAX_NUM); i++) { const auto& pad = ::at32(handler->GetPads(), pad_num(i)); @@ -366,7 +480,7 @@ public: { connected_controllers = 0; std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); for (u32 i = 0; i < std::min(attribute.max_connect, CELL_GEM_MAX_NUM); i++) { const auto& pad = ::at32(handler->GetPads(), pad_num(i)); @@ -433,7 +547,7 @@ public: load_configs(); }; - SAVESTATE_INIT_POS(15); + SAVESTATE_INIT_POS(16.1); // Depends on cellCamera void save(utils::serial& ar) { @@ -474,6 +588,11 @@ public: } ar(connected_controllers, updating, camera_frame, memory_ptr, start_timestamp_us); + + if (ar.is_writing() || version >= 3) + { + ar(video_conversion_in_progress, video_data_out_size); + } } gem_config_data(utils::serial& ar) @@ -494,8 +613,14 @@ public: cellGem.notice("Could not load fake gem config. Using defaults."); } + if (!g_cfg_gem_mouse.load()) + { + cellGem.notice("Could not load mouse gem config. Using defaults."); + } + cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string()); cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string()); + cellGem.notice("Mouse gem config=\n", g_cfg_gem_mouse.to_string()); } }; @@ -552,9 +677,27 @@ namespace gem std::array positions {}; + struct YUV + { + u8 y = 0; + u8 u = 0; + u8 v = 0; + + YUV(u8 r, u8 g, u8 b) + : y(Y(r, g, b)) + , u(U(r, g, b)) + , v(V(r, g, b)) + { + } + + static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast(0.299f * r + 0.587f * g + 0.114f * b); } + static inline u8 U(u8 r, u8 g, u8 b) { return static_cast(-0.14713f * r - 0.28886f * g + 0.436f * b); } + static inline u8 V(u8 r, u8 g, u8 b) { return static_cast(0.615f * r - 0.51499f * g - 0.10001f * b); } + }; + bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, const std::vector& video_data_in, u32 width, u32 height, - u8* video_data_out, u32 video_data_out_size) + u8* video_data_out, u32 video_data_out_size, std::string_view caller) { if (output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) { @@ -566,13 +709,13 @@ namespace gem if (video_data_in.size() != required_in_size) { - cellGem.error("convert: in_size mismatch: required=%d, actual=%d", required_in_size, video_data_in.size()); + cellGem.error("convert: in_size mismatch: required=%d, actual=%d (called from %s)", required_in_size, video_data_in.size(), caller); return false; } if (required_out_size < 0 || video_data_out_size != static_cast(required_out_size)) { - cellGem.error("convert: out_size unknown: required=%d, format %d", required_out_size, output_format); + cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, output_format, caller); return false; } @@ -594,13 +737,11 @@ namespace gem for (u32 y = 0; y < height - 1; y += 2) { - const u8* src = &video_data_in[y * in_pitch]; - const u8* src0 = src; - const u8* src1 = src + in_pitch; + const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src1 = src0 + in_pitch; - u8* dst_row = video_data_out + y * out_pitch; - u8* dst0 = dst_row; - u8* dst1 = dst_row + out_pitch; + u8* dst0 = video_data_out + y * out_pitch; + u8* dst1 = dst0 + out_pitch; for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) { @@ -634,7 +775,7 @@ namespace gem } default: { - cellGem.error("Unimplemented: Converting %s to %s", input_format, output_format); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); return false; } @@ -649,29 +790,372 @@ namespace gem } else { - cellGem.error("Unimplemented: Converting %s to %s", input_format, output_format); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); return false; } break; } - case CELL_GEM_RGBA_320x240: // RGBA output; 320*240*4-byte output buffer required case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous) + { + const u32 yuv_pitch = width; + + u8* dst_y = video_data_out; + u8* dst_u = dst_y + yuv_pitch * height; + u8* dst_v = dst_u + yuv_pitch * height; + + switch (input_format) + { + case CELL_CAMERA_RAW8: + { + const u32 in_pitch = width; + + for (u32 y = 0; y < height - 1; y += 2) + { + const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst_y0 = dst_y + y * yuv_pitch; + u8* dst_y1 = dst_y0 + yuv_pitch; + + u8* dst_u0 = dst_u + y * yuv_pitch; + u8* dst_u1 = dst_u0 + yuv_pitch; + + u8* dst_v0 = dst_v + y * yuv_pitch; + u8* dst_v1 = dst_v0 + yuv_pitch; + + for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2, dst_u0 += 2, dst_u1 += 2, dst_v0 += 2, dst_v1 += 2) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + // Convert RGBA to YUV + const YUV yuv_top = YUV(r, g0, b); + const YUV yuv_bottom = YUV(r, g1, b); + + dst_y0[0] = dst_y0[1] = yuv_top.y; + dst_y1[0] = dst_y1[1] = yuv_bottom.y; + + dst_u0[0] = dst_u0[1] = yuv_top.u; + dst_u1[0] = dst_u1[1] = yuv_bottom.u; + + dst_v0[0] = dst_v0[1] = yuv_top.v; + dst_v1[0] = dst_v1[1] = yuv_bottom.v; + } + } + break; + } + case CELL_CAMERA_RGBA: + { + const u32 in_pitch = width / 4; + + for (u32 y = 0; y < height; y++) + { + const u8* src = &video_data_in[y * in_pitch]; + + for (u32 x = 0; x < width; x++, src += 4) + { + const u8 r = src[0]; + const u8 g = src[1]; + const u8 b = src[2]; + + // Convert RGBA to YUV + const YUV yuv = YUV(r, g, b); + + *dst_y++ = yuv.y; + *dst_u++ = yuv.u; + *dst_v++ = yuv.v; + } + } + break; + } + default: + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + return false; + } + } + break; + } case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous) + { + const u32 y_pitch = width; + const u32 uv_pitch = width / 2; + + u8* dst_y = video_data_out; + u8* dst_u = dst_y + y_pitch * height; + u8* dst_v = dst_u + uv_pitch * height; + + switch (input_format) + { + case CELL_CAMERA_RAW8: + { + const u32 in_pitch = width; + + for (u32 y = 0; y < height - 1; y += 2) + { + const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst_y0 = dst_y + y * y_pitch; + u8* dst_y1 = dst_y0 + y_pitch; + + u8* dst_u0 = dst_u + y * uv_pitch; + u8* dst_u1 = dst_u0 + uv_pitch; + + u8* dst_v0 = dst_v + y * uv_pitch; + u8* dst_v1 = dst_v0 + uv_pitch; + + for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + // Convert RGBA to YUV + const YUV yuv_top = YUV(r, g0, b); + const YUV yuv_bottom = YUV(r, g1, b); + + dst_y0[0] = dst_y0[1] = yuv_top.y; + dst_y1[0] = dst_y1[1] = yuv_bottom.y; + + *dst_u0++ = yuv_top.u; + *dst_u1++ = yuv_bottom.u; + + *dst_v0++ = yuv_top.v; + *dst_v1++ = yuv_bottom.v; + } + } + break; + } + case CELL_CAMERA_RGBA: + { + const u32 in_pitch = width * 4; + + for (u32 y = 0; y < height; y++) + { + const u8* src = &video_data_in[y * in_pitch]; + + for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2) + { + const u8 r_0 = src[0]; + const u8 g_0 = src[1]; + const u8 b_0 = src[2]; + const u8 r_1 = src[4]; + const u8 g_1 = src[5]; + const u8 b_1 = src[6]; + + // Convert RGBA to YUV + const YUV yuv_0 = YUV(r_0, g_0, b_0); + const u8 y_1 = YUV::Y(r_1, g_1, b_1); + + dst_y[0] = yuv_0.y; + dst_y[1] = y_1; + *dst_u++ = yuv_0.u; + *dst_v++ = yuv_0.v; + } + } + break; + } + default: + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + return false; + } + } + break; + } case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous) + { + const u32 y_pitch = width; + const u32 uv_pitch = width / 4; + + u8* dst_y = video_data_out; + u8* dst_u = dst_y + y_pitch * height; + u8* dst_v = dst_u + uv_pitch * height; + + switch (input_format) + { + case CELL_CAMERA_RAW8: + { + const u32 in_pitch = width; + + for (u32 y = 0; y < height - 1; y += 2) + { + const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst_y0 = dst_y + y * y_pitch; + u8* dst_y1 = dst_y0 + y_pitch; + + u8* dst_u0 = dst_u + y * uv_pitch; + u8* dst_u1 = dst_u0 + uv_pitch; + + u8* dst_v0 = dst_v + y * uv_pitch; + u8* dst_v1 = dst_v0 + uv_pitch; + + for (u32 x = 0; x < width - 3; x += 4, src0 += 4, src1 += 4, dst_y0 += 4, dst_y1 += 4) + { + const u8 b_left = src0[0]; + const u8 g0_left = src0[1]; + const u8 b_right = src0[2]; + const u8 g0_right = src0[3]; + + const u8 g1_left = src1[0]; + const u8 r_left = src1[1]; + const u8 g1_right = src1[2]; + const u8 r_right = src1[3]; + + // Convert RGBA to YUV + const YUV yuv_top_left = YUV(r_left, g0_left, b_left); // Re-used for top-right + const u8 y_top_right = YUV::Y(r_right, g0_right, b_right); + const YUV yuv_bottom_left = YUV(r_left, g1_left, b_left); // Re-used for bottom-right + const u8 y_bottom_right = YUV::Y(r_right, g1_right, b_right); + + dst_y0[0] = dst_y0[1] = yuv_top_left.y; + dst_y0[2] = dst_y0[3] = y_top_right; + + dst_y1[0] = dst_y1[1] = yuv_bottom_left.y; + dst_y1[2] = dst_y1[3] = y_bottom_right; + + *dst_u0++ = yuv_top_left.u; + *dst_u1++ = yuv_bottom_left.u; + + *dst_v0++ = yuv_top_left.v; + *dst_v1++ = yuv_bottom_left.v; + } + } + break; + } + case CELL_CAMERA_RGBA: + { + const u32 in_pitch = width * 4; + + for (u32 y = 0; y < height; y++) + { + const u8* src = &video_data_in[y * in_pitch]; + + for (u32 x = 0; x < width - 3; x += 4, src += 16, dst_y += 4) + { + const u8 r_0 = src[0]; + const u8 g_0 = src[1]; + const u8 b_0 = src[2]; + const u8 r_1 = src[4]; + const u8 g_1 = src[5]; + const u8 b_1 = src[6]; + const u8 r_2 = src[8]; + const u8 g_2 = src[9]; + const u8 b_2 = src[10]; + const u8 r_3 = src[12]; + const u8 g_3 = src[13]; + const u8 b_3 = src[14]; + + // Convert RGBA to YUV + const YUV yuv_0 = YUV(r_0, g_0, b_0); + const u8 y_1 = YUV::Y(r_1, g_1, b_1); + const u8 y_2 = YUV::Y(r_2, g_2, b_2); + const u8 y_3 = YUV::Y(r_3, g_3, b_3); + + dst_y[0] = yuv_0.y; + dst_y[1] = y_1; + dst_y[2] = y_2; + dst_y[3] = y_3; + *dst_u++ = yuv_0.u; + *dst_v++ = yuv_0.v; + } + } + break; + } + default: + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + return false; + } + } + break; + } + case CELL_GEM_RGBA_320x240: // RGBA output; 320*240*4-byte output buffer required + { + switch (input_format) + { + case CELL_CAMERA_RAW8: + { + const u32 in_pitch = width; + const u32 out_pitch = width * 4 / 2; + + for (u32 y = 0; y < height - 1; y += 2) + { + const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst0 = video_data_out + (y / 2) * out_pitch; + u8* dst1 = dst0 + out_pitch; + + for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 4, dst1 += 4) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + const u8 top[4] = { r, g0, b, 255 }; + const u8 bottom[4] = { r, g1, b, 255 }; + + // Top-Left + std::memcpy(dst0, top, 4); + + // Bottom-Left Pixel + std::memcpy(dst1, bottom, 4); + } + } + break; + } + case CELL_CAMERA_RGBA: + { + const u32 in_pitch = width * 4; + const u32 out_pitch = width * 4 / 2; + + for (u32 y = 0; y < height / 2; y++) + { + const u8* src = &video_data_in[y * 2 * in_pitch]; + u8* dst = video_data_out + y * out_pitch; + + for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4) + { + std::memcpy(dst, src, 4); + } + } + break; + } + default: + { + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + return false; + } + } + break; + } case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters { - cellGem.error("Unimplemented: Converting %s to %s", input_format, output_format); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); return false; } case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output { - cellGem.trace("Ignoring frame conversion for CELL_GEM_NO_VIDEO_OUTPUT"); + cellGem.trace("Ignoring frame conversion for CELL_GEM_NO_VIDEO_OUTPUT (called from %s)", caller); break; } default: { - cellGem.error("Trying to convert %s to %s", input_format, output_format); + cellGem.error("Trying to convert %s to %s (called from %s)", input_format, output_format, caller); return false; } } @@ -779,16 +1263,55 @@ void gem_config_data::operator()() { cellGem.notice("Starting thread"); + u64 last_update_us = 0; + + // Handle initial state after loading a savestate + if (state && video_conversion_in_progress) + { + // Wait for cellGemConvertVideoFinish. The initial savestate loading may take a while. + m_done = 2; // Use special value 2 for this case + thread_ctrl::wait_on(m_done, 2, 5'000'000); + + // Just mark this conversion as complete (there's no real downside to this, except for a black image) + video_conversion_in_progress = false; + done(); + } + while (thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) { - while (!video_conversion_in_progress && thread_ctrl::state() != thread_state::aborting && !Emu.IsStopped()) + u64 timeout = umax; + + if (state && !video_conversion_in_progress) { - if (state) + constexpr u64 update_timeout_us = 100'000; // Update controllers at 10Hz + const u64 now_us = get_system_time(); + const u64 elapsed_us = now_us - last_update_us; + + if (elapsed_us < update_timeout_us) { + timeout = update_timeout_us - elapsed_us; + } + else + { + timeout = update_timeout_us; + last_update_us = now_us; + + std::scoped_lock lock(mtx); + update_connections(); update_calibration_status(); } + } - thread_ctrl::wait_for(1000); + if (!m_wake_up) + { + thread_ctrl::wait_on(m_wake_up, 0, timeout); + } + + m_wake_up = 0; + + if (!video_conversion_in_progress) + { + continue; } if (thread_ctrl::state() == thread_state::aborting || Emu.IsStopped()) @@ -805,12 +1328,13 @@ void gem_config_data::operator()() if (g_cfg.io.camera != camera_handler::qt) { video_conversion_in_progress = false; + done(); continue; } const auto& shared_data = g_fxo->get(); - if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size)) + if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size, "cellGem")) { cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get()); @@ -821,6 +1345,7 @@ void gem_config_data::operator()() } video_conversion_in_progress = false; + done(); } } @@ -838,19 +1363,39 @@ public: return m_busy; } - void wake_up() + void wake_up_tracker() { - m_wake_up.release(1); - m_wake_up.notify_one(); + m_wake_up_tracker.release(1); + m_wake_up_tracker.notify_one(); } - void wait_for_result() + void tracker_done() { - if (!m_done) + m_tracker_done.release(1); + m_tracker_done.notify_one(); + } + + bool wait_for_tracker_result(ppu_thread& ppu) + { + if (g_cfg.io.move != move_handler::real) { - m_done.wait(0); - m_done.release(0); + m_tracker_done = 0; + return true; } + + while (!m_tracker_done && !ppu.is_stopped()) + { + thread_ctrl::wait_on(m_tracker_done, 0); + } + + if (ppu.is_stopped()) + { + ppu.state += cpu_flag::again; + return false; + } + + m_tracker_done = 0; + return true; } bool set_image(u32 addr) @@ -905,12 +1450,6 @@ public: return ::at32(m_info, gem_num); } - gem_tracker& operator=(thread_state) - { - wake_up(); - return *this; - } - void operator()() { if (g_cfg.io.move != move_handler::real) @@ -928,10 +1467,10 @@ public: while (thread_ctrl::state() != thread_state::aborting) { // Check if we have a new frame - if (!m_wake_up) + if (!m_wake_up_tracker) { - m_wake_up.wait(0); - m_wake_up.release(0); + thread_ctrl::wait_on(m_wake_up_tracker, 0); + m_wake_up_tracker.release(0); if (thread_ctrl::state() == thread_state::aborting) { @@ -944,7 +1483,7 @@ public: // Update PS Move LED colors { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); auto& handlers = handler->get_handlers(); if (auto it = handlers.find(pad_handler::move); it != handlers.end() && it->second) { @@ -1017,8 +1556,7 @@ public: } // Notify that we are finished with this frame - m_done.release(1); - m_done.notify_one(); + tracker_done(); m_busy.release(false); } @@ -1029,8 +1567,8 @@ public: shared_mutex mutex; private: - atomic_t m_wake_up = 0; - atomic_t m_done = 1; + atomic_t m_wake_up_tracker = 0; + atomic_t m_tracker_done = 0; atomic_t m_busy = false; ps_move_tracker m_tracker{}; CellCameraInfoEx m_camera_info{}; @@ -1142,7 +1680,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con gem_state->handle_pos[3] = 0.f; // Calculate orientation - if (g_cfg.io.move == move_handler::real) + if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled)) { gem_state->quat[0] = move_data.quaternion[0]; // x gem_state->quat[1] = move_data.quaternion[1]; // y @@ -1151,14 +1689,11 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con } else { - static constexpr f32 PI = 3.14159265f; - const auto degree_to_rad = [](f32 degree) -> f32 { return degree * PI / 180.0f; }; - const f32 max_angle_per_side_h = g_cfg.io.fake_move_rotation_cone_h / 2.0f; const f32 max_angle_per_side_v = g_cfg.io.fake_move_rotation_cone_v / 2.0f; - const f32 roll = -degree_to_rad((image_y - half_height) / half_height * max_angle_per_side_v); // This is actually the pitch - const f32 pitch = -degree_to_rad((image_x - half_width) / half_width * max_angle_per_side_h); // This is actually the yaw - const f32 yaw = degree_to_rad(0.0f); + const f32 roll = -PadHandlerBase::degree_to_rad((image_y - half_height) / half_height * max_angle_per_side_v); // This is actually the pitch + const f32 pitch = -PadHandlerBase::degree_to_rad((image_x - half_width) / half_width * max_angle_per_side_h); // This is actually the yaw + const f32 yaw = PadHandlerBase::degree_to_rad(0.0f); const f32 cr = std::cos(roll * 0.5f); const f32 sr = std::sin(roll * 0.5f); const f32 cp = std::cos(pitch * 0.5f); @@ -1203,14 +1738,14 @@ static void ds3_input_to_pad(const u32 gem_num, be_t& digital_buttons, be_t digital_buttons = 0; analog_t = 0; - if (!is_input_allowed()) + if (!is_input_allowed() || input::g_pads_intercepted) // Let's intercept the PS Move just like a pad { return; } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -1218,7 +1753,7 @@ static void ds3_input_to_pad(const u32 gem_num, be_t& digital_buttons, be_t return; } - const auto handle_input = [&](gem_btn btn, u16 value, bool pressed) + const auto handle_input = [&](gem_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) { if (!pressed) return; @@ -1250,9 +1785,7 @@ static void ds3_input_to_pad(const u32 gem_num, be_t& digital_buttons, be_t digital_buttons |= CELL_GEM_CTRL_T; analog_t = std::max(analog_t, value); break; - case gem_btn::x_axis: - case gem_btn::y_axis: - case gem_btn::count: + default: break; } }; @@ -1276,7 +1809,7 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr& y_pos = 0; const auto& cfg = ::at32(g_cfg_gem_fake.players, gem_num); - cfg->handle_input(pad, true, [&](gem_btn btn, u16 value, bool pressed) + cfg->handle_input(pad, true, [&](gem_btn btn, pad_button /*pad_btn*/, u16 value, bool pressed, bool& /*abort*/) { if (!pressed) return; @@ -1298,14 +1831,14 @@ static inline void ds3_get_stick_values(u32 gem_num, const std::shared_ptr& template static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, T& gem_state) { - if (!gem_state || !is_input_allowed()) + if (!gem_state || !is_input_allowed() || input::g_pads_intercepted) // Let's intercept the PS Move just like a pad { return; } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -1318,7 +1851,7 @@ static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& contro if constexpr (std::is_same_v>) { - pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, {}); + pos_to_gem_state(gem_num, controller, gem_state, ds3_pos_x, ds3_pos_y, ds3_max_x, ds3_max_y, pad->move_data); } else if constexpr (std::is_same_v>) { @@ -1329,14 +1862,14 @@ static void ds3_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& contro template static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, T& gem_state) { - if (!gem_state || !is_input_allowed()) + if (!gem_state || !is_input_allowed() || input::g_pads_intercepted) // Let's intercept the PS Move just like a pad { return; } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -1374,14 +1907,14 @@ static void ds3_input_to_ext(u32 gem_num, gem_config::gem_controller& controller { ext = {}; - if (!is_input_allowed()) + if (!is_input_allowed() || input::g_pads_intercepted) // Let's intercept the PS Move just like a pad { return; } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -1455,7 +1988,7 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_t& digital_buttons, be_t pressed_buttons; const Mouse& mouse_data = ::at32(handler.GetMice(), mouse_no); - const auto is_pressed = [&mouse_data, &pressed_buttons](MouseButtonCodes button) -> bool + auto& cfg = ::at32(g_cfg_gem_mouse.players, mouse_no); + + bool combo_active = false; + std::set combos; + + static const std::unordered_map btn_map = { - // Only allow each button to be used for one action unless it's the combo button. - return (mouse_data.buttons & button) && (button == (CELL_MOUSE_BUTTON_3 + 0u/*fix warning*/) || pressed_buttons.insert(button).second); + { gem_btn::start, CELL_GEM_CTRL_START }, + { gem_btn::select, CELL_GEM_CTRL_SELECT }, + { gem_btn::triangle, CELL_GEM_CTRL_TRIANGLE }, + { gem_btn::circle, CELL_GEM_CTRL_CIRCLE }, + { gem_btn::cross, CELL_GEM_CTRL_CROSS }, + { gem_btn::square, CELL_GEM_CTRL_SQUARE }, + { gem_btn::move, CELL_GEM_CTRL_MOVE }, + { gem_btn::t, CELL_GEM_CTRL_T }, + { gem_btn::combo_start, CELL_GEM_CTRL_START }, + { gem_btn::combo_select, CELL_GEM_CTRL_SELECT }, + { gem_btn::combo_triangle, CELL_GEM_CTRL_TRIANGLE }, + { gem_btn::combo_circle, CELL_GEM_CTRL_CIRCLE }, + { gem_btn::combo_cross, CELL_GEM_CTRL_CROSS }, + { gem_btn::combo_square, CELL_GEM_CTRL_SQUARE }, + { gem_btn::combo_move, CELL_GEM_CTRL_MOVE }, + { gem_btn::combo_t, CELL_GEM_CTRL_T }, }; - digital_buttons = 0; + // Check combo button first + cfg->handle_input(mouse_data, [&combo_active](gem_btn btn, pad_button /*pad_btn*/, u16 /*value*/, bool pressed, bool& abort) + { + if (pressed && btn == gem_btn::combo) + { + combo_active = true; + abort = true; + } + }); - if ((is_pressed(CELL_MOUSE_BUTTON_3) && is_pressed(CELL_MOUSE_BUTTON_1)) || is_pressed(CELL_MOUSE_BUTTON_6)) - digital_buttons |= CELL_GEM_CTRL_SELECT; + // Check combos + if (combo_active) + { + cfg->handle_input(mouse_data, [&digital_buttons, &combos](gem_btn btn, pad_button pad_btn, u16 /*value*/, bool pressed, bool& /*abort*/) + { + if (!pressed) + return; - if ((is_pressed(CELL_MOUSE_BUTTON_3) && is_pressed(CELL_MOUSE_BUTTON_2)) || is_pressed(CELL_MOUSE_BUTTON_7)) - digital_buttons |= CELL_GEM_CTRL_START; + switch (btn) + { + case gem_btn::combo_start: + case gem_btn::combo_select: + case gem_btn::combo_triangle: + case gem_btn::combo_circle: + case gem_btn::combo_cross: + case gem_btn::combo_square: + case gem_btn::combo_move: + case gem_btn::combo_t: + digital_buttons |= ::at32(btn_map, btn); + combos.insert(pad_btn); + break; + default: + break; + } + }); + } - if ((is_pressed(CELL_MOUSE_BUTTON_3) && is_pressed(CELL_MOUSE_BUTTON_4)) || is_pressed(CELL_MOUSE_BUTTON_8)) - digital_buttons |= CELL_GEM_CTRL_TRIANGLE; + // Check normal buttons + cfg->handle_input(mouse_data, [&digital_buttons, &combos](gem_btn btn, pad_button pad_btn, u16 /*value*/, bool pressed, bool& /*abort*/) + { + if (!pressed) + return; - if (is_pressed(CELL_MOUSE_BUTTON_3) && is_pressed(CELL_MOUSE_BUTTON_5)) - digital_buttons |= CELL_GEM_CTRL_SQUARE; + switch (btn) + { + case gem_btn::start: + case gem_btn::select: + case gem_btn::square: + case gem_btn::cross: + case gem_btn::circle: + case gem_btn::triangle: + case gem_btn::move: + case gem_btn::t: + // Ignore this gem_btn if the same pad_button was already used in a combo + if (!combos.contains(pad_btn)) + { + digital_buttons |= ::at32(btn_map, btn); + } + break; + default: + break; + } + }); - if (is_pressed(CELL_MOUSE_BUTTON_1)) - digital_buttons |= CELL_GEM_CTRL_T; - - if (is_pressed(CELL_MOUSE_BUTTON_2)) - digital_buttons |= CELL_GEM_CTRL_MOVE; - - if (is_pressed(CELL_MOUSE_BUTTON_4)) - digital_buttons |= CELL_GEM_CTRL_CIRCLE; - - if (is_pressed(CELL_MOUSE_BUTTON_5)) - digital_buttons |= CELL_GEM_CTRL_CROSS; - - analog_t = (mouse_data.buttons & CELL_MOUSE_BUTTON_1) ? 0xFFFF : 0; + analog_t = (digital_buttons & CELL_GEM_CTRL_T) ? 0xFFFF : 0; return true; } @@ -1514,7 +2103,7 @@ static bool mouse_input_to_pad(u32 mouse_no, be_t& digital_buttons, be_t static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& controller, T& gem_state) { - if (!gem_state || !is_input_allowed()) + if (!gem_state || !is_input_allowed() || input::g_pads_intercepted) // Let's intercept the PS Move just like a pad { return; } @@ -1670,7 +2259,7 @@ error_code cellGemClearStatusFlags(u32 gem_num, u64 mask) return CELL_OK; } -error_code cellGemConvertVideoFinish() +error_code cellGemConvertVideoFinish(ppu_thread& ppu) { cellGem.warning("cellGemConvertVideoFinish()"); @@ -1686,9 +2275,9 @@ error_code cellGemConvertVideoFinish() return CELL_GEM_ERROR_CONVERT_NOT_STARTED; } - while (gem.video_conversion_in_progress && !Emu.IsStopped()) + if (!gem.wait_for_result(ppu)) { - thread_ctrl::wait_for(100); + return {}; } return CELL_OK; @@ -1723,7 +2312,9 @@ error_code cellGemConvertVideoStart(vm::cptr video_frame) const auto& shared_data = g_fxo->get(); gem.video_data_in.resize(shared_data.size); std::memcpy(gem.video_data_in.data(), video_frame.get_ptr(), gem.video_data_in.size()); + gem.video_conversion_in_progress = true; + gem.wake_up(); return CELL_OK; } @@ -1783,7 +2374,7 @@ error_code cellGemEnableMagnetometer(u32 gem_num, u32 enable) { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad && pad->m_pad_handler == pad_handler::move) @@ -1831,7 +2422,7 @@ error_code cellGemEnableMagnetometer2(u32 gem_num, u32 enable) { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad && pad->m_pad_handler == pad_handler::move) @@ -1849,7 +2440,7 @@ error_code cellGemEnd(ppu_thread& ppu) auto& gem = g_fxo->get(); - std::scoped_lock lock(gem.mtx); + std::unique_lock lock(gem.mtx); if (gem.state.compare_and_swap_test(1, 0)) { @@ -1861,8 +2452,13 @@ error_code cellGemEnd(ppu_thread& ppu) return CELL_OK; } + lock.unlock(); + auto& tracker = g_fxo->get>(); - tracker.wait_for_result(); + if (!tracker.wait_for_tracker_result(ppu)) + { + return {}; + } gem.updating = false; @@ -1894,7 +2490,7 @@ error_code cellGemFilterState(u32 gem_num, u32 enable) error_code cellGemForceRGB(u32 gem_num, f32 r, f32 g, f32 b) { - cellGem.todo("cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f)", gem_num, r, g, b); + cellGem.warning("cellGemForceRGB(gem_num=%d, r=%f, g=%f, b=%f)", gem_num, r, g, b); auto& gem = g_fxo->get(); @@ -2076,7 +2672,7 @@ error_code cellGemGetImageState(u32 gem_num, vm::ptr gem_imag if (g_cfg.io.move != move_handler::null) { - auto& shared_data = g_fxo->get(); + const auto& shared_data = g_fxo->get(); auto& controller = gem.controllers[gem_num]; gem_image_state->frame_timestamp = shared_data.frame_timestamp_us.load(); @@ -2147,15 +2743,16 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v switch (g_cfg.io.move) { case move_handler::real: + case move_handler::fake: { // Get temperature and sensor data { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); - if (pad && pad->m_pad_handler == pad_handler::move && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) + if (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) { inertial_state->temperature = pad->move_data.temperature; inertial_state->accelerometer[0] = pad->move_data.accelerometer_x; @@ -2170,9 +2767,6 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); break; } - case move_handler::fake: - ds3_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); - break; case move_handler::mouse: case move_handler::raw_mouse: mouse_input_to_pad(gem_num, inertial_state->pad.digitalbuttons, inertial_state->pad.analog_T); @@ -2208,69 +2802,6 @@ error_code cellGemGetInfo(vm::ptr info) return CELL_GEM_ERROR_INVALID_PARAMETER; } - switch (g_cfg.io.move) - { - case move_handler::real: - case move_handler::fake: - { - gem.connected_controllers = 0; - - std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); - - for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++) - { - const auto& pad = ::at32(handler->GetPads(), pad_num(i)); - const bool connected = (pad && (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) && i < gem.attribute.max_connect); - const bool is_real_move = g_cfg.io.move != move_handler::real || pad->m_pad_handler == pad_handler::move; - - if (connected && is_real_move) - { - gem.connected_controllers++; - gem.controllers[i].status = CELL_GEM_STATUS_READY; - gem.controllers[i].port = port_num(i); - } - else - { - gem.controllers[i].status = CELL_GEM_STATUS_DISCONNECTED; - gem.controllers[i].port = 0; - } - } - break; - } - case move_handler::raw_mouse: - { - gem.connected_controllers = 0; - - auto& handler = g_fxo->get(); - std::lock_guard mouse_lock(handler.mutex); - - const MouseInfo& info = handler.GetInfo(); - - for (u32 i = 0; i < CELL_GEM_MAX_NUM; i++) - { - const bool connected = i < gem.attribute.max_connect && info.status[i] == CELL_MOUSE_STATUS_CONNECTED; - - if (connected) - { - gem.connected_controllers++; - gem.controllers[i].status = CELL_GEM_STATUS_READY; - gem.controllers[i].port = port_num(i); - } - else - { - gem.controllers[i].status = CELL_GEM_STATUS_DISCONNECTED; - gem.controllers[i].port = 0; - } - } - break; - } - default: - { - break; - } - } - info->max_connect = gem.attribute.max_connect; info->now_connect = gem.connected_controllers; @@ -2626,6 +3157,8 @@ error_code cellGemInit(ppu_thread& ppu, vm::cptr attribute) // TODO: is this correct? gem.start_timestamp_us = get_guest_system_time(); + gem.wake_up(); + return CELL_OK; } @@ -2803,7 +3336,7 @@ error_code cellGemReadExternalPortDeviceInfo(u32 gem_num, vm::ptr ext_id, v { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), pad_num(gem_num)); if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -2887,7 +3420,7 @@ error_code cellGemSetRumble(u32 gem_num, u8 rumble) if (g_cfg.io.move == move_handler::real) { std::lock_guard pad_lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); auto& handlers = handler->get_handlers(); if (auto it = handlers.find(pad_handler::move); it != handlers.end() && it->second) { @@ -3015,7 +3548,7 @@ error_code cellGemTrackHues(vm::cptr req_hues, vm::ptr res_hues) return CELL_OK; } -error_code cellGemUpdateFinish() +error_code cellGemUpdateFinish(ppu_thread& ppu) { cellGem.warning("cellGemUpdateFinish()"); @@ -3026,15 +3559,18 @@ error_code cellGemUpdateFinish() return CELL_GEM_ERROR_UNINITIALIZED; } - std::scoped_lock lock(gem.mtx); - if (!gem.updating) { return CELL_GEM_ERROR_UPDATE_NOT_STARTED; } auto& tracker = g_fxo->get>(); - tracker.wait_for_result(); + if (!tracker.wait_for_tracker_result(ppu)) + { + return {}; + } + + std::scoped_lock lock(gem.mtx); gem.updating = false; @@ -3084,7 +3620,7 @@ error_code cellGemUpdateStart(vm::cptr camera_frame, u64 timestamp) return not_an_error(CELL_GEM_NO_VIDEO); } - tracker.wake_up(); + tracker.wake_up_tracker(); return CELL_OK; } @@ -3114,7 +3650,7 @@ error_code cellGemWriteExternalPort(u32 gem_num, vm::ptrGetPads(), pad_num(gem_num)); if (pad->m_pad_handler != pad_handler::move || !(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) diff --git a/rpcs3/Emu/Cell/Modules/cellPad.cpp b/rpcs3/Emu/Cell/Modules/cellPad.cpp index 771bb04a6e..74b3c5fc60 100644 --- a/rpcs3/Emu/Cell/Modules/cellPad.cpp +++ b/rpcs3/Emu/Cell/Modules/cellPad.cpp @@ -196,7 +196,7 @@ bool cellPad_NotifyStateChange(usz index, u64 /*state*/, bool locked, bool is_bl return true; } - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[index]; @@ -268,7 +268,7 @@ error_code cellPadInit(ppu_thread& ppu, u32 max_connect) config.port_setting.fill(CELL_PAD_SETTING_PRESS_OFF | CELL_PAD_SETTING_SENSOR_OFF); config.reported_info = {}; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); for (usz i = 0; i < config.get_max_connect(); ++i) @@ -336,7 +336,7 @@ error_code cellPadClearBuf(u32 port_no) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -351,7 +351,7 @@ error_code cellPadClearBuf(u32 port_no) void pad_get_data(u32 port_no, CellPadData* data, bool get_periph_data = false) { auto& config = g_fxo->get(); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = handler->GetPads()[port_no]; const PadInfo& rinfo = handler->GetInfo(); @@ -709,7 +709,7 @@ error_code cellPadGetData(u32 port_no, vm::ptr data) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -740,7 +740,7 @@ error_code cellPadPeriphGetInfo(vm::ptr info) if (!info) return CELL_PAD_ERROR_INVALID_PARAMETER; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const PadInfo& rinfo = handler->GetInfo(); std::memset(info.get_ptr(), 0, sizeof(CellPadPeriphInfo)); @@ -795,7 +795,7 @@ error_code cellPadPeriphGetData(u32 port_no, vm::ptr data) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -827,7 +827,7 @@ error_code cellPadGetRawData(u32 port_no, vm::ptr data) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -891,7 +891,7 @@ error_code cellPadSetActDirect(u32 port_no, vm::ptr param) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -923,7 +923,7 @@ error_code cellPadGetInfo(vm::ptr info) std::memset(info.get_ptr(), 0, sizeof(CellPadInfo)); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const PadInfo& rinfo = handler->GetInfo(); info->max_connect = config.max_connect; info->system_info = rinfo.system_info; @@ -968,7 +968,7 @@ error_code cellPadGetInfo2(vm::ptr info) std::memset(info.get_ptr(), 0, sizeof(CellPadInfo2)); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const PadInfo& rinfo = handler->GetInfo(); info->max_connect = config.get_max_connect(); // Here it is forcibly clamped info->system_info = rinfo.system_info; @@ -1018,7 +1018,7 @@ error_code cellPadGetCapabilityInfo(u32 port_no, vm::ptr if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -1074,7 +1074,7 @@ error_code cellPadInfoPressMode(u32 port_no) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -1101,7 +1101,7 @@ error_code cellPadInfoSensorMode(u32 port_no) if (port_no >= config.get_max_connect()) return CELL_PAD_ERROR_NO_DEVICE; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -1129,7 +1129,7 @@ error_code cellPadSetPressMode(u32 port_no, u32 mode) if (port_no >= CELL_PAD_MAX_PORT_NUM) return CELL_OK; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -1163,7 +1163,7 @@ error_code cellPadSetSensorMode(u32 port_no, u32 mode) if (port_no >= CELL_PAD_MAX_PORT_NUM) return CELL_OK; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = pads[port_no]; @@ -1190,7 +1190,7 @@ error_code cellPadLddRegisterController() if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const s32 handle = handler->AddLddPad(); @@ -1215,7 +1215,7 @@ error_code cellPadLddDataInsert(s32 handle, vm::ptr data) if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); auto& pads = handler->GetPads(); if (handle < 0 || static_cast(handle) >= pads.size() || !data) // data == NULL stalls on decr @@ -1240,7 +1240,7 @@ error_code cellPadLddGetPortNo(s32 handle) if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); auto& pads = handler->GetPads(); if (handle < 0 || static_cast(handle) >= pads.size()) @@ -1264,7 +1264,7 @@ error_code cellPadLddUnregisterController(s32 handle) if (!config.max_connect) return CELL_PAD_ERROR_UNINITIALIZED; - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); if (handle < 0 || static_cast(handle) >= pads.size()) diff --git a/rpcs3/Emu/Cell/Modules/sys_prx_.cpp b/rpcs3/Emu/Cell/Modules/sys_prx_.cpp index c3cb6e8df2..8529f408b0 100644 --- a/rpcs3/Emu/Cell/Modules/sys_prx_.cpp +++ b/rpcs3/Emu/Cell/Modules/sys_prx_.cpp @@ -221,7 +221,7 @@ error_code sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptrinfo = info; // Call the syscall - return _sys_prx_get_module_info(ppu, id, 0, opt); + return _sys_prx_get_module_info(ppu, id, flags, opt); } error_code sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr name, u64 flags, vm::ptr pOpt) diff --git a/rpcs3/Emu/Cell/PPUAnalyser.h b/rpcs3/Emu/Cell/PPUAnalyser.h index 9d6f4ef9ed..0b225bc821 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.h +++ b/rpcs3/Emu/Cell/PPUAnalyser.h @@ -96,6 +96,7 @@ struct ppu_module : public Type std::vector segs{}; std::vector secs{}; std::vector funcs{}; + std::vector applied_patches; std::deque> allocations; std::map addr_to_seg_index; @@ -185,7 +186,6 @@ struct main_ppu_module : public ppu_module { u32 elf_entry{}; u32 seg0_code_end{}; - std::vector applied_patches; // Disable inherited savestate ordering void save(utils::serial&) = delete; diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index d26f060b7d..6a7ce4312b 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -1816,6 +1816,9 @@ shared_ptr ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, c prx->module_info_version[1] = lib_info->version[1]; prx->module_info_attributes = lib_info->attributes; + prx->imports_start = lib_info->imports_start; + prx->imports_end = lib_info->imports_end; + prx->exports_start = lib_info->exports_start; prx->exports_end = lib_info->exports_end; @@ -1947,6 +1950,7 @@ shared_ptr 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) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index 8001b95ac4..8da9cca8f0 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -4898,6 +4898,30 @@ bool ppu_initialize(const ppu_module& info, bool check_only, u64 file_s sha1_update(&ctx, ensure(info.get_ptr(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> 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(addrs.data()), addrs.size() * sizeof(be_t)); + } + if (false) { const be_t forced_upd = 3; diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 8804d93138..54679dee41 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -5481,7 +5481,7 @@ bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t* 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++) { diff --git a/rpcs3/Emu/Cell/lv2/sys_config.cpp b/rpcs3/Emu/Cell/lv2/sys_config.cpp index 6f0617808d..a6ccb74106 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_config.cpp @@ -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()) { @@ -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 { diff --git a/rpcs3/Emu/Cell/lv2/sys_config.h b/rpcs3/Emu/Cell/lv2/sys_config.h index 3915dfc8cb..ea9b9da76d 100644 --- a/rpcs3/Emu/Cell/lv2/sys_config.h +++ b/rpcs3/Emu/Cell/lv2/sys_config.h @@ -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 get_shared_ptr () const { return idm::get_unlocked(idm_id); } + shared_ptr get_shared_ptr () const { return stx::make_shared_from_this(this); } u32 get_id() const { return idm_id; } }; @@ -342,7 +344,7 @@ public: // Utilities u32 get_id() const { return idm_id; } - shared_ptr get_shared_ptr() const { return idm::get_unlocked(idm_id); } + shared_ptr get_shared_ptr() const { return stx::make_shared_from_this(this); } }; /* @@ -360,6 +362,10 @@ class lv2_config_service_event return g_fxo->get().next_id++; } + atomic_t 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; diff --git a/rpcs3/Emu/Cell/lv2/sys_event.cpp b/rpcs3/Emu/Cell/lv2/sys_event.cpp index d5ad126142..db72972b91 100644 --- a/rpcs3/Emu/Cell/lv2/sys_event.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_event.cpp @@ -420,10 +420,8 @@ error_code sys_event_queue_tryreceive(ppu_thread& ppu, u32 equeue_id, vm::ptrevents.empty()) { auto& dest = events[count++]; - const auto event = queue->events.front(); + std::tie(dest.source, dest.data1, dest.data2, dest.data3) = queue->events.front(); queue->events.pop_front(); - - std::tie(dest.source, dest.data1, dest.data2, dest.data3) = event; } lock.unlock(); diff --git a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp index d95c6935fc..7bdd18b3fd 100644 --- a/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_net/lv2_socket.cpp @@ -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(); } diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.h b/rpcs3/Emu/Cell/lv2/sys_overlay.h index ef1c1ffbd7..1c950a4d0c 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.h +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.h @@ -11,7 +11,6 @@ struct lv2_overlay final : ppu_module u32 entry{}; u32 seg0_code_end{}; - std::vector applied_patches; lv2_overlay() = default; lv2_overlay(utils::serial&){} diff --git a/rpcs3/Emu/Cell/lv2/sys_process.cpp b/rpcs3/Emu/Cell/lv2/sys_process.cpp index 8038ffc248..b914408ec9 100644 --- a/rpcs3/Emu/Cell/lv2/sys_process.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_process.cpp @@ -271,7 +271,7 @@ error_code _sys_process_get_paramsfo(vm::ptr buffer) { sys_process.warning("_sys_process_get_paramsfo(buffer=0x%x)", buffer); - if (!Emu.GetTitleID().length()) + if (Emu.GetTitleID().empty()) { return CELL_ENOENT; } @@ -498,6 +498,9 @@ void lv2_exitspawn(ppu_thread& ppu, std::vector& argv, std::vector< }; signal_system_cache_can_stay(); + + // Make sure we keep the game window opened + Emu.SetContinuousMode(true); Emu.Kill(false); }); diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index f918070a4e..21a4f55000 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -1034,7 +1034,7 @@ error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr< return CELL_EFAULT; } - if (pOpt->info->size != pOpt->info.size()) + if (pOpt->info->size != pOpt->info.size() && pOpt->info_v2->size != pOpt->info_v2.size()) { return CELL_EINVAL; } @@ -1072,6 +1072,14 @@ error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr< pOpt->info->segments_num = i; } + if (pOpt->info_v2->size == pOpt->info_v2.size()) + { + pOpt->info_v2->exports_addr = prx->exports_start; + pOpt->info_v2->exports_size = prx->exports_end - prx->exports_start; + pOpt->info_v2->imports_addr = prx->imports_start; + pOpt->info_v2->imports_size = prx->imports_end - prx->imports_start; + } + return CELL_OK; } @@ -1079,11 +1087,30 @@ error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr name, { ppu.state += cpu_flag::wait; - sys_prx.todo("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt); + sys_prx.warning("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt); - //if (realName == "?") ... + std::string module_name; + if (!vm::read_string(name.addr(), 28, module_name)) + { + return CELL_EINVAL; + } - return not_an_error(CELL_PRX_ERROR_UNKNOWN_MODULE); + const auto [prx, id] = idm::select([&](u32 id, lv2_prx& prx) -> u32 + { + if (strncmp(module_name.c_str(), prx.module_info_name, sizeof(prx.module_info_name)) == 0) + { + return id; + } + + return 0; + }); + + if (!id) + { + return CELL_PRX_ERROR_UNKNOWN_MODULE; + } + + return not_an_error(id); } error_code _sys_prx_get_module_id_by_address(ppu_thread& ppu, u32 addr) diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.h b/rpcs3/Emu/Cell/lv2/sys_prx.h index af0539ecc0..610ed68145 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.h +++ b/rpcs3/Emu/Cell/lv2/sys_prx.h @@ -76,10 +76,22 @@ struct sys_prx_module_info_t be_t segments_num; // 0x44 }; +struct sys_prx_module_info_v2_t : sys_prx_module_info_t +{ + be_t exports_addr; // 0x48 + be_t exports_size; // 0x4C + be_t imports_addr; // 0x50 + be_t imports_size; // 0x54 +}; + struct sys_prx_module_info_option_t { be_t size; // 0x10 - vm::bptr info; + union + { + vm::bptr info; + vm::bptr info_v2; + }; }; struct sys_prx_start_module_option_t @@ -192,6 +204,9 @@ struct lv2_prx final : ppu_module u8 module_info_version[2]{}; be_t module_info_attributes{}; + u32 imports_start = umax; + u32 imports_end = 0; + u32 exports_start = umax; u32 exports_end = 0; diff --git a/rpcs3/Emu/IPC_config.cpp b/rpcs3/Emu/IPC_config.cpp index 5aa95f4c34..6a51e80324 100644 --- a/rpcs3/Emu/IPC_config.cpp +++ b/rpcs3/Emu/IPC_config.cpp @@ -25,7 +25,7 @@ void cfg_ipc::load() void cfg_ipc::save() const { #ifdef _WIN32 - const std::string path_to_cfg = fs::get_config_dir() + "config/"; + const std::string path_to_cfg = fs::get_config_dir(true); if (!fs::create_path(path_to_cfg)) { IPC.error("Could not create path: %s", path_to_cfg); @@ -42,11 +42,7 @@ void cfg_ipc::save() const std::string cfg_ipc::get_path() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/ipc.yml"; -#else - return fs::get_config_dir() + "ipc.yml"; -#endif + return fs::get_config_dir(true) + "ipc.yml"; } bool cfg_ipc::get_server_enabled() const diff --git a/rpcs3/Emu/IdManager.h b/rpcs3/Emu/IdManager.h index 26162045b7..7846d6f70f 100644 --- a/rpcs3/Emu/IdManager.h +++ b/rpcs3/Emu/IdManager.h @@ -805,8 +805,8 @@ public: { if (ptr) { - constexpr thread_state finished{3}; - *static_cast(ptr.get()) = finished; + constexpr thread_state destroying_context{7}; + *static_cast(ptr.get()) = destroying_context; } } @@ -837,8 +837,8 @@ public: { if (ptr) { - constexpr thread_state finished{3}; - *static_cast(ptr.get()) = finished; + constexpr thread_state destroying_context{7}; + *static_cast(ptr.get()) = destroying_context; } } diff --git a/rpcs3/Emu/Io/Buzz.cpp b/rpcs3/Emu/Io/Buzz.cpp index 06cdc2f064..575c5585b1 100644 --- a/rpcs3/Emu/Io/Buzz.cpp +++ b/rpcs3/Emu/Io/Buzz.cpp @@ -146,7 +146,7 @@ void usb_device_buzz::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint*/ } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); ensure(pads.size() > m_last_controller); ensure(g_cfg_buzz.players.size() > m_last_controller); @@ -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; diff --git a/rpcs3/Emu/Io/GHLtar.cpp b/rpcs3/Emu/Io/GHLtar.cpp index 140c8e3a98..db60cccd5b 100644 --- a/rpcs3/Emu/Io/GHLtar.cpp +++ b/rpcs3/Emu/Io/GHLtar.cpp @@ -138,7 +138,7 @@ void usb_device_ghltar::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpoint } std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pad = ::at32(handler->GetPads(), m_controller_index); if (!(pad->m_port_status & CELL_PAD_STATUS_CONNECTED)) @@ -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; diff --git a/rpcs3/Emu/Io/GameTablet.cpp b/rpcs3/Emu/Io/GameTablet.cpp index 56286a8bff..31ed81f3af 100644 --- a/rpcs3/Emu/Io/GameTablet.cpp +++ b/rpcs3/Emu/Io/GameTablet.cpp @@ -195,7 +195,7 @@ void usb_device_gametablet::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endp { std::lock_guard lock(pad::g_pad_mutex); - const auto gamepad_handler = pad::get_current_handler(); + const auto gamepad_handler = pad::get_pad_thread(); const auto& pads = gamepad_handler->GetPads(); const auto& pad = ::at32(pads, m_controller_index); if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) diff --git a/rpcs3/Emu/Io/GunCon3.cpp b/rpcs3/Emu/Io/GunCon3.cpp index ee9694447c..d229693907 100644 --- a/rpcs3/Emu/Io/GunCon3.cpp +++ b/rpcs3/Emu/Io/GunCon3.cpp @@ -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; @@ -255,7 +255,7 @@ void usb_device_guncon3::interrupt_transfer(u32 buf_size, u8* buf, u32 endpoint, { std::lock_guard lock(pad::g_pad_mutex); - const auto gamepad_handler = pad::get_current_handler(); + const auto gamepad_handler = pad::get_pad_thread(); const auto& pads = gamepad_handler->GetPads(); const auto& pad = ::at32(pads, m_controller_index); if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) diff --git a/rpcs3/Emu/Io/MouseHandler.cpp b/rpcs3/Emu/Io/MouseHandler.cpp index 0194d8e7e9..c80d7556dc 100644 --- a/rpcs3/Emu/Io/MouseHandler.cpp +++ b/rpcs3/Emu/Io/MouseHandler.cpp @@ -31,12 +31,12 @@ void MouseHandlerBase::save(utils::serial& ar) ar(inited ? m_info.max_connect : 0); } -bool MouseHandlerBase::is_time_for_update(double elapsed_time) +bool MouseHandlerBase::is_time_for_update(double elapsed_time_ms) { steady_clock::time_point now = steady_clock::now(); - double elapsed = (now - last_update).count() / 1000'000.; + const double elapsed_ms = (now - last_update).count() / 1'000'000.; - if (elapsed > elapsed_time) + if (elapsed_ms > elapsed_time_ms) { last_update = now; return true; diff --git a/rpcs3/Emu/Io/MouseHandler.h b/rpcs3/Emu/Io/MouseHandler.h index 007f71ee23..8a88241523 100644 --- a/rpcs3/Emu/Io/MouseHandler.h +++ b/rpcs3/Emu/Io/MouseHandler.h @@ -127,7 +127,7 @@ protected: std::vector m_mice; steady_clock::time_point last_update{}; - bool is_time_for_update(double elapsed_time = 10.0); // 4-10 ms, let's use 10 for now + bool is_time_for_update(double elapsed_time_ms = 10.0); // 4-10 ms, let's use 10 for now public: shared_mutex mutex; diff --git a/rpcs3/Emu/Io/Null/NullPadHandler.h b/rpcs3/Emu/Io/Null/NullPadHandler.h index 63b14a5c34..57e9767ccd 100644 --- a/rpcs3/Emu/Io/Null/NullPadHandler.h +++ b/rpcs3/Emu/Io/Null/NullPadHandler.h @@ -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(); diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index a1aeeb7885..44648f252f 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -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->m_analog_limiter_button_index = static_cast(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(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, PadHandlerBase::button::button_count> PadHandlerBase:: mapping[button::analog_limiter_button] = FindKeyCodes(button_list, cfg->analog_limiter_button); } + if (b_has_orientation) + { + mapping[button::orientation_reset_button] = FindKeyCodes(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(MOTION_ONE_G); + const f32 accel_y = (pad.m_sensors[1].m_value - 512) / static_cast(MOTION_ONE_G); + const f32 accel_z = (pad.m_sensors[2].m_value - 512) / static_cast(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(); + 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]; +} diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index 55bc548620..eb536fde11 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -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 #include #include @@ -38,6 +47,12 @@ public: }; color color_override{}; bool color_override_active{}; + + std::shared_ptr 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 m_pad_configs; std::vector 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& sensors); virtual std::unordered_map 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 get_device(const std::string& /*device*/) { return nullptr; } virtual bool get_is_left_trigger(const std::shared_ptr& /*device*/, u64 /*keyCode*/) { return false; } @@ -336,10 +366,15 @@ private: virtual std::unordered_map get_button_values(const std::shared_ptr& /*device*/) { return {}; } virtual pad_preview_values get_preview_values(const std::unordered_map& /*data*/) { return {}; } + void get_orientation(const pad_ensemble& binding) const; + protected: virtual std::array, PadHandlerBase::button::button_count> get_mapped_key_codes(const std::shared_ptr& device, const cfg_pad* cfg); virtual void get_mapping(const pad_ensemble& binding); void TranslateButtonPress(const std::shared_ptr& 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); }; diff --git a/rpcs3/Emu/Io/TopShotElite.cpp b/rpcs3/Emu/Io/TopShotElite.cpp index 0d85cfd5fa..06cddab9f0 100644 --- a/rpcs3/Emu/Io/TopShotElite.cpp +++ b/rpcs3/Emu/Io/TopShotElite.cpp @@ -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; @@ -315,7 +315,7 @@ void usb_device_topshotelite::interrupt_transfer(u32 buf_size, u8* buf, u32 /*en { std::lock_guard lock(pad::g_pad_mutex); - const auto gamepad_handler = pad::get_current_handler(); + const auto gamepad_handler = pad::get_pad_thread(); const auto& pads = gamepad_handler->GetPads(); const auto& pad = ::at32(pads, m_controller_index); if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) diff --git a/rpcs3/Emu/Io/TopShotFearmaster.cpp b/rpcs3/Emu/Io/TopShotFearmaster.cpp index bfae945365..98b54700d9 100644 --- a/rpcs3/Emu/Io/TopShotFearmaster.cpp +++ b/rpcs3/Emu/Io/TopShotFearmaster.cpp @@ -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; @@ -339,7 +339,7 @@ void usb_device_topshotfearmaster::interrupt_transfer(u32 buf_size, u8* buf, u32 { std::lock_guard lock(pad::g_pad_mutex); - const auto gamepad_handler = pad::get_current_handler(); + const auto gamepad_handler = pad::get_pad_thread(); const auto& pads = gamepad_handler->GetPads(); const auto& pad = ::at32(pads, m_controller_index); if (pad->m_port_status & CELL_PAD_STATUS_CONNECTED) diff --git a/rpcs3/Emu/Io/Turntable.cpp b/rpcs3/Emu/Io/Turntable.cpp index af5648d40b..0ef979725e 100644 --- a/rpcs3/Emu/Io/Turntable.cpp +++ b/rpcs3/Emu/Io/Turntable.cpp @@ -151,7 +151,7 @@ void usb_device_turntable::interrupt_transfer(u32 buf_size, u8* buf, u32 /*endpo // All other bufs are always 0x00 std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const auto& pads = handler->GetPads(); const auto& pad = ::at32(pads, m_controller_index); @@ -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; diff --git a/rpcs3/Emu/Io/camera_config.cpp b/rpcs3/Emu/Io/camera_config.cpp index 9767405646..d7071b05c3 100644 --- a/rpcs3/Emu/Io/camera_config.cpp +++ b/rpcs3/Emu/Io/camera_config.cpp @@ -8,11 +8,7 @@ cfg_camera g_cfg_camera; cfg_camera::cfg_camera() : cfg::node() -#ifdef _WIN32 - , path(fs::get_config_dir() + "config/camera.yml") -#else - , path(fs::get_config_dir() + "camera.yml") -#endif + , path(fs::get_config_dir(true) + "camera.yml") { } diff --git a/rpcs3/Emu/Io/emulated_pad_config.h b/rpcs3/Emu/Io/emulated_pad_config.h index b6703b01dd..ad74e0dfc2 100644 --- a/rpcs3/Emu/Io/emulated_pad_config.h +++ b/rpcs3/Emu/Io/emulated_pad_config.h @@ -88,7 +88,7 @@ public: button_map.clear(); } - void handle_input(std::shared_ptr pad, bool press_only, const std::function& func) const + void handle_input(std::shared_ptr pad, bool press_only, const std::function& 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& func) const + void handle_input(const Mouse& mouse, const std::function& 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(static_cast(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& func, u32 offset, u32 keycode, u16 value, bool pressed, bool check_axis) const + bool handle_input(const std::function& 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(axis_direction::both), value, pressed, false); + abort = handle_input(func, offset, static_cast(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; } }; @@ -221,7 +235,7 @@ struct emulated_pads_config : cfg::node m_mutex.lock(); bool result = false; - const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id); cfg_log.notice("Loading %s config: %s", cfg_id, cfg_name); from_default(); @@ -258,7 +272,7 @@ struct emulated_pads_config : cfg::node { std::lock_guard lock(m_mutex); - const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id); cfg_log.notice("Saving %s config to '%s'", cfg_id, cfg_name); if (!fs::create_path(fs::get_parent_dir(cfg_name))) diff --git a/rpcs3/Emu/Io/gem_config.h b/rpcs3/Emu/Io/gem_config.h index d737928790..2be86ff3c4 100644 --- a/rpcs3/Emu/Io/gem_config.h +++ b/rpcs3/Emu/Io/gem_config.h @@ -4,7 +4,7 @@ #include -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_gems() : emulated_pads_config("gem") {}; }; +struct cfg_mouse_gem final : public emulated_pad_config +{ + cfg_mouse_gem(node* owner, const std::string& name) : emulated_pad_config(owner, name) {} + + cfg_pad_btn start{ this, "Start", gem_btn::start, pad_button::mouse_button_6 }; + cfg_pad_btn select{ this, "Select", gem_btn::select, pad_button::mouse_button_7 }; + cfg_pad_btn triangle{ this, "Triangle", gem_btn::triangle, pad_button::mouse_button_8 }; + cfg_pad_btn circle{ this, "Circle", gem_btn::circle, pad_button::mouse_button_4 }; + cfg_pad_btn cross{ this, "Cross", gem_btn::cross, pad_button::mouse_button_5 }; + cfg_pad_btn square{ this, "Square", gem_btn::square, pad_button::mouse_button_3 }; + cfg_pad_btn move{ this, "Move", gem_btn::move, pad_button::mouse_button_2 }; + cfg_pad_btn t{ this, "T", gem_btn::t, pad_button::mouse_button_1 }; + cfg_pad_btn combo{ this, "Combo", gem_btn::combo, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_start{ this, "Combo Start", gem_btn::combo_start, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_select{ this, "Combo Select", gem_btn::combo_select, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_triangle{ this, "Combo Triangle", gem_btn::combo_triangle, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_circle{ this, "Combo Circle", gem_btn::combo_circle, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_cross{ this, "Combo Cross", gem_btn::combo_cross, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_square{ this, "Combo Square", gem_btn::combo_square, pad_button::pad_button_max_enum }; + cfg_pad_btn combo_move{ this, "Combo Move", gem_btn::combo_move, pad_button::pad_button_max_enum }; + cfg_pad_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_gems() : emulated_pads_config("gem_mouse") {}; +}; + struct cfg_gem final : public emulated_pad_config { 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 extern cfg_gems g_cfg_gem_real; extern cfg_fake_gems g_cfg_gem_fake; +extern cfg_mouse_gems g_cfg_gem_mouse; diff --git a/rpcs3/Emu/Io/mouse_config.cpp b/rpcs3/Emu/Io/mouse_config.cpp index b7dcec7e59..07ec4b525a 100644 --- a/rpcs3/Emu/Io/mouse_config.cpp +++ b/rpcs3/Emu/Io/mouse_config.cpp @@ -4,11 +4,7 @@ #include "Utilities/File.h" mouse_config::mouse_config() -#ifdef _WIN32 - : cfg_name(fs::get_config_dir() + "config/config_mouse.yml") -#else - : cfg_name(fs::get_config_dir() + "config_mouse.yml") -#endif + : cfg_name(fs::get_config_dir(true) + "config_mouse.yml") { } diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index cdca3f6101..fb0b140aeb 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -32,6 +32,20 @@ std::string cfg_pad::get_buttons(std::vector vec) return fmt::merge(vec, ","); } +u8 cfg_pad::get_large_motor_speed(const std::array& motor_speed) const +{ + const u8 idx = switch_vibration_motors ? 1 : 0; + const f32 multiplier = multiplier_vibration_motor_large / 100.0f; + return static_cast(std::clamp(motor_speed[idx].m_value * multiplier, 0.0f, 255.0f)); +} + +u8 cfg_pad::get_small_motor_speed(const std::array& motor_speed) const +{ + const u8 idx = switch_vibration_motors ? 0 : 1; + const f32 multiplier = multiplier_vibration_motor_small / 100.0f; + return static_cast(std::clamp(motor_speed[idx].m_value * multiplier, 0.0f, 255.0f)); +} + bool cfg_input::load(const std::string& title_id, const std::string& config_file, bool strict) { input_log.notice("Loading pad config (title_id='%s', config_file='%s', strict=%d)", title_id, config_file, strict); diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index 662280d7b2..7c39a79411 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -28,6 +28,9 @@ struct cfg_pad final : cfg::node static std::vector get_buttons(const std::string& str); static std::string get_buttons(std::vector vec); + u8 get_large_motor_speed(const std::array& motor_speed) const; + u8 get_small_motor_speed(const std::array& motor_speed) const; + cfg::string ls_left{ this, "Left Stick Left", "" }; cfg::string ls_down{ this, "Left Stick Down", "" }; cfg::string ls_right{ this, "Left Stick Right", "" }; @@ -66,6 +69,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 }; @@ -93,8 +99,8 @@ struct cfg_pad final : cfg::node cfg::uint<0, 100> led_battery_indicator_brightness{ this, "LED battery indicator brightness", 50 }; cfg::_bool player_led_enabled{ this, "Player LED enabled", true }; - cfg::_bool enable_vibration_motor_large{ this, "Enable Large Vibration Motor", true }; - cfg::_bool enable_vibration_motor_small{ this, "Enable Small Vibration Motor", true }; + cfg::uint<0, 200> multiplier_vibration_motor_large{ this, "Large Vibration Motor Multiplier", 100 }; + cfg::uint<0, 200> multiplier_vibration_motor_small{ this, "Small Vibration Motor Multiplier", 100 }; cfg::_bool switch_vibration_motors{ this, "Switch Vibration Motors", false }; cfg::_enum mouse_move_mode{ this, "Mouse Movement Mode", mouse_movement_mode::relative }; diff --git a/rpcs3/Emu/Io/pad_types.cpp b/rpcs3/Emu/Io/pad_types.cpp index 9100e28596..428e0d6cea 100644 --- a/rpcs3/Emu/Io/pad_types.cpp +++ b/rpcs3/Emu/Io/pad_types.cpp @@ -39,7 +39,7 @@ void fmt_class_string::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; +} diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index 1f9552a6f8..f48fbf7850 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -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 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 default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; + std::array 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}; diff --git a/rpcs3/Emu/Io/rb3drums_config.cpp b/rpcs3/Emu/Io/rb3drums_config.cpp index 1e308d8b70..34c289818c 100644 --- a/rpcs3/Emu/Io/rb3drums_config.cpp +++ b/rpcs3/Emu/Io/rb3drums_config.cpp @@ -8,13 +8,7 @@ cfg_rb3drums g_cfg_rb3drums; cfg_rb3drums::cfg_rb3drums() : cfg::node() -#ifdef _WIN32 - , - path(fs::get_config_dir() + "config/rb3drums.yml") -#else - , - path(fs::get_config_dir() + "rb3drums.yml") -#endif + , path(fs::get_config_dir(true) + "rb3drums.yml") { } diff --git a/rpcs3/Emu/Io/recording_config.cpp b/rpcs3/Emu/Io/recording_config.cpp index 0457317d80..357106e074 100644 --- a/rpcs3/Emu/Io/recording_config.cpp +++ b/rpcs3/Emu/Io/recording_config.cpp @@ -8,11 +8,7 @@ cfg_recording g_cfg_recording; cfg_recording::cfg_recording() : cfg::node() -#ifdef _WIN32 - , path(fs::get_config_dir() + "config/recording.yml") -#else - , path(fs::get_config_dir() + "recording.yml") -#endif + , path(fs::get_config_dir(true) + "recording.yml") { } diff --git a/rpcs3/Emu/Io/usio.cpp b/rpcs3/Emu/Io/usio.cpp index 7dea19c8fb..077f6c7d62 100644 --- a/rpcs3/Emu/Io/usio.cpp +++ b/rpcs3/Emu/Io/usio.cpp @@ -189,7 +189,7 @@ void usb_device_usio::save_backup() void usb_device_usio::translate_input_taiko() { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); std::vector input_buf(0x60); constexpr le_t c_hit = 0x1800; @@ -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) { @@ -273,7 +273,7 @@ void usb_device_usio::translate_input_taiko() void usb_device_usio::translate_input_tekken() { std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); std::vector input_buf(0x180); le_t digital_input[2]{}; @@ -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) { diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 3bcff2f960..7c9723a25f 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -59,11 +59,7 @@ namespace np { std::string get_players_history_path() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/players_history.yml"; -#else - return fs::get_config_dir() + "players_history.yml"; -#endif + return fs::get_config_dir(true) + "players_history.yml"; } std::map load_players_history() @@ -1440,7 +1436,7 @@ namespace np void np_handler::save_players_history() { #ifdef _WIN32 - const std::string path_to_cfg = fs::get_config_dir() + "config/"; + const std::string path_to_cfg = fs::get_config_dir(true); if (!fs::create_path(path_to_cfg)) { nph_log.error("Could not create path: %s", path_to_cfg); diff --git a/rpcs3/Emu/NP/rpcn_config.cpp b/rpcs3/Emu/NP/rpcn_config.cpp index ff802e554e..1f52d60a05 100644 --- a/rpcs3/Emu/NP/rpcn_config.cpp +++ b/rpcs3/Emu/NP/rpcn_config.cpp @@ -34,7 +34,7 @@ void cfg_rpcn::load() void cfg_rpcn::save() const { #ifdef _WIN32 - const std::string path_to_cfg = fs::get_config_dir() + "config/"; + const std::string path_to_cfg = fs::get_config_dir(true); if (!fs::create_path(path_to_cfg)) { rpcn_log.error("Could not create path: %s", path_to_cfg); @@ -51,11 +51,7 @@ void cfg_rpcn::save() const std::string cfg_rpcn::get_path() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/rpcn.yml"; -#else - return fs::get_config_dir() + "rpcn.yml"; -#endif + return fs::get_config_dir(true) + "rpcn.yml"; } std::string cfg_rpcn::generate_npid() diff --git a/rpcs3/Emu/NP/upnp_config.cpp b/rpcs3/Emu/NP/upnp_config.cpp index 13a4f72698..bf43b8e55b 100644 --- a/rpcs3/Emu/NP/upnp_config.cpp +++ b/rpcs3/Emu/NP/upnp_config.cpp @@ -24,7 +24,7 @@ void cfg_upnp::load() void cfg_upnp::save() const { #ifdef _WIN32 - const std::string path_to_cfg = fs::get_config_dir() + "config/"; + const std::string path_to_cfg = fs::get_config_dir(true); if (!fs::create_path(path_to_cfg)) { upnp_cfg_log.error("Could not create path: %s", path_to_cfg); @@ -51,9 +51,5 @@ void cfg_upnp::set_device_url(std::string_view url) std::string cfg_upnp::get_path() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/upnp.yml"; -#else - return fs::get_config_dir() + "upnp.yml"; -#endif + return fs::get_config_dir(true) + "upnp.yml"; } diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 7bb00f2683..e791af0274 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -427,5 +427,17 @@ namespace rsx } return result; } + + template + requires std::is_invocable_r_v + U reduce(U initial_value, F&& reducer) const + { + U accumulate = initial_value; + for (auto it = begin(); it != end(); ++it) + { + accumulate = reducer(accumulate, *it); + } + return accumulate; + } }; } diff --git a/rpcs3/Emu/RSX/Common/surface_store.cpp b/rpcs3/Emu/RSX/Common/surface_store.cpp index 53981ac6db..43f4b4161f 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.cpp +++ b/rpcs3/Emu/RSX/Common/surface_store.cpp @@ -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(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(color_target)); } usz get_aligned_pitch(surface_color_format format, u32 width) diff --git a/rpcs3/Emu/RSX/Common/surface_store.h b/rpcs3/Emu/RSX/Common/surface_store.h index e4062707d0..e4d3f2d3fb 100644 --- a/rpcs3/Emu/RSX/Common/surface_store.h +++ b/rpcs3/Emu/RSX/Common/surface_store.h @@ -17,6 +17,7 @@ namespace rsx namespace utility { std::vector 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); } diff --git a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp index 095903832d..389efe0a2e 100644 --- a/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp +++ b/rpcs3/Emu/RSX/Core/RSXDrawCommands.cpp @@ -735,7 +735,7 @@ namespace rsx utils::stream_vector(dst + 4, 0u, fog_mode, std::bit_cast(wpos_scale), std::bit_cast(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 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. diff --git a/rpcs3/Emu/RSX/Core/RSXDrawCommands.h b/rpcs3/Emu/RSX/Core/RSXDrawCommands.h index b69a918401..5bc5991a18 100644 --- a/rpcs3/Emu/RSX/Core/RSXDrawCommands.h +++ b/rpcs3/Emu/RSX/Core/RSXDrawCommands.h @@ -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; }; } diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 3e60af9f68..c37228afe9 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -39,7 +39,7 @@ u64 GLGSRender::get_cycles() GLGSRender::GLGSRender(utils::serial* ar) noexcept : GSRender(ar) { - m_shaders_cache = std::make_unique(m_prog_buffer, "opengl", "v1.94"); + m_shaders_cache = std::make_unique(m_prog_buffer, "opengl", "v1.95"); if (g_cfg.video.disable_vertex_cache) m_vertex_cache = std::make_unique(); @@ -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 data_range {}; void* data_source = nullptr; const auto bound_range = m_transform_constants_buffer->bound_range(); @@ -1046,7 +1071,7 @@ void GLGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou data_range = { bound_range.first + byte_offset, byte_count}; data_source = ®S(ctx)->transform_constants[index]; } - else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0) + else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0) { const auto write_offset = xform_id * 16; const auto byte_count = count * 16; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 866fe288e6..01579a445b 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -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; diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp index 1dfd0f4996..0687798efd 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.cpp @@ -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); } diff --git a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h index 6a46035742..2c15010192 100644 --- a/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h +++ b/rpcs3/Emu/RSX/GL/GLShaderInterpreter.h @@ -84,6 +84,6 @@ namespace gl void update_fragment_textures(const std::array, 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; }; } diff --git a/rpcs3/Emu/RSX/GSFrameBase.h b/rpcs3/Emu/RSX/GSFrameBase.h index f345f6255e..fd91244ffe 100644 --- a/rpcs3/Emu/RSX/GSFrameBase.h +++ b/rpcs3/Emu/RSX/GSFrameBase.h @@ -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; diff --git a/rpcs3/Emu/RSX/GSRender.cpp b/rpcs3/Emu/RSX/GSRender.cpp index 917a772a34..2025842dab 100644 --- a/rpcs3/Emu/RSX/GSRender.cpp +++ b/rpcs3/Emu/RSX/GSRender.cpp @@ -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; } diff --git a/rpcs3/Emu/RSX/GSRender.h b/rpcs3/Emu/RSX/GSRender.h index eea040bf29..d2a6fd9c5f 100644 --- a/rpcs3/Emu/RSX/GSRender.h +++ b/rpcs3/Emu/RSX/GSRender.h @@ -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; diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp index d02b3ffbe2..6706e241c8 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_main_menu.cpp @@ -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; diff --git a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp index 2ff9711d5b..e224bc5727 100644 --- a/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp +++ b/rpcs3/Emu/RSX/Overlays/HomeMenu/overlay_home_menu_savestate.cpp @@ -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); }); diff --git a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp index 8213b29a2d..5d66ae87d0 100644 --- a/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp +++ b/rpcs3/Emu/RSX/Overlays/Trophies/overlay_trophy_list_dialog.cpp @@ -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>(0); diff --git a/rpcs3/Emu/RSX/Overlays/overlays.cpp b/rpcs3/Emu/RSX/Overlays/overlays.cpp index e118b3a68c..a1fe40d8ce 100644 --- a/rpcs3/Emu/RSX/Overlays/overlays.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlays.cpp @@ -193,7 +193,7 @@ namespace rsx // Get gamepad input std::lock_guard lock(pad::g_pad_mutex); - const auto handler = pad::get_current_handler(); + const auto handler = pad::get_pad_thread(); const PadInfo& rinfo = handler->GetInfo(); const bool ignore_gamepad_input = (!rinfo.now_connect || !input::g_pads_intercepted); diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index b3f9b88df2..4a2215d9f9 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -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 output_register_names; std::array 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; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h index df3fbe5e16..dab539f9da 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h @@ -1,116 +1,10 @@ #pragma once #include "ShaderParam.h" +#include "FragmentProgramRegister.h" #include "RSXFragmentProgram.h" + #include -// 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_registers; + std::array temp_registers; std::string GetMask() const; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp new file mode 100644 index 0000000000..a14b142df6 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp @@ -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 + ")"; + } +} diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h new file mode 100644 index 0000000000..6cfc8e76c3 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h @@ -0,0 +1,111 @@ +#pragma once + +#include + +namespace rsx +{ + class MixedPrecisionRegister + { + enum data_type_bits + { + undefined = 0, + f16 = 1, + f32 = 2 + }; + + std::array 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; + } + }; +} + diff --git a/rpcs3/Emu/RSX/Program/GLSLInterpreter/FragmentInterpreter.glsl b/rpcs3/Emu/RSX/Program/GLSLInterpreter/FragmentInterpreter.glsl index ab799b8c80..58510363ca 100644 --- a/rpcs3/Emu/RSX/Program/GLSLInterpreter/FragmentInterpreter.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLInterpreter/FragmentInterpreter.glsl @@ -86,8 +86,11 @@ layout(location=0) in vec4 in_regs[16]; #define CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT 0xe #define CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS 0x40 -#define GET_BITS(word, offset, count) bitfieldExtract(inst.words[word], offset, count) -#define TEST_BIT(word, offset) (GET_BITS(word, offset, 1) > 0) +#define GET_BITS(bitfield, offset, count) bitfieldExtract(bitfield, offset, count) +#define TEST_BIT(bitfield, offset) (GET_BITS(bitfield, offset, 1) > 0) + +#define GET_INST_BITS(word, offset, count) GET_BITS(inst.words[word], offset, count) +#define TEST_INST_BIT(word, offset) (GET_INST_BITS(word, offset, 1) > 0) #define select mix #define reg_mov(d, s, m) d = select(d, s, m) @@ -174,7 +177,7 @@ int counter = 0; vec4 read_src(const in int index) { - ur0 = GET_BITS(index + 1, 0, 2); + ur0 = GET_INST_BITS(index + 1, 0, 2); switch (ur0) { @@ -183,14 +186,14 @@ vec4 read_src(const in int index) switch(index) { case 0: - ur1 = GET_BITS(1, 2, 6); break; + ur1 = GET_INST_BITS(1, 2, 6); break; case 1: - ur1 = GET_BITS(2, 2, 6); break; + ur1 = GET_INST_BITS(2, 2, 6); break; case 2: - ur1 = GET_BITS(3, 2, 6); break; + ur1 = GET_INST_BITS(3, 2, 6); break; } - if (TEST_BIT(index + 1, 8)) + if (TEST_INST_BIT(index + 1, 8)) { vr0 = regs16[ur1]; } @@ -202,7 +205,7 @@ vec4 read_src(const in int index) } case RSX_FP_REGISTER_TYPE_INPUT: { - ur1 = GET_BITS(0, 13, 4); + ur1 = GET_INST_BITS(0, 13, 4); switch (ur1) { case 0: @@ -235,27 +238,27 @@ vec4 read_src(const in int index) } } - ur1 = GET_BITS(index + 1, 9, 8); + ur1 = GET_INST_BITS(index + 1, 9, 8); vr0 = shuffle(vr0, ur1); // abs if (index == 0) { - if (TEST_BIT(1, 29)) vr0 = abs(vr0); + if (TEST_INST_BIT(1, 29)) vr0 = abs(vr0); } else { ur1 = index + 1; - if (TEST_BIT(ur1, 18)) vr0 = abs(vr0); + if (TEST_INST_BIT(ur1, 18)) vr0 = abs(vr0); } // neg - return (TEST_BIT(index + 1, 17))? -vr0 : vr0; + return (TEST_INST_BIT(index + 1, 17))? -vr0 : vr0; } vec4 read_cond() { - return shuffle(cc[GET_BITS(1, 31, 1)], GET_BITS(1, 21, 8)); + return shuffle(cc[GET_INST_BITS(1, 31, 1)], GET_INST_BITS(1, 21, 8)); } bvec4 decode_cond(const in uint mode, const in vec4 cond) @@ -283,7 +286,7 @@ bvec4 decode_cond(const in uint mode, const in vec4 cond) bool check_cond() { - ur0 = GET_BITS(1, 18, 3); + ur0 = GET_INST_BITS(1, 18, 3); if (ur0 == 0x7) { return true; @@ -351,14 +354,14 @@ vec3 _texcoord_xform(const in vec3 coord, const in sampler_info params) vec4 _texture(in vec4 coord, float bias) { - ur0 = GET_BITS(0, 17, 4); + ur0 = GET_INST_BITS(0, 17, 4); if (!IS_TEXTURE_RESIDENT(ur0)) { return vr_zero; } ur1 = ur0 + ur0; - const uint type = bitfieldExtract(texture_control, int(ur1), 2); + const uint type = GET_BITS(texture_control, int(ur1), 2); switch (type) { @@ -380,7 +383,7 @@ vec4 _texture(in vec4 coord, float bias) break; } - if (TEST_BIT(0, 21)) + if (TEST_INST_BIT(0, 21)) { vr0 = vr0 * 2. - 1.; } @@ -390,14 +393,14 @@ vec4 _texture(in vec4 coord, float bias) vec4 _textureLod(in vec4 coord, float lod) { - ur0 = GET_BITS(0, 17, 4); + ur0 = GET_INST_BITS(0, 17, 4); if (!IS_TEXTURE_RESIDENT(ur0)) { return vr_zero; } ur1 = ur0 + ur0; - const uint type = bitfieldExtract(texture_control, int(ur1), 2); + const uint type = GET_BITS(texture_control, int(ur1), 2); switch (type) { @@ -419,7 +422,7 @@ vec4 _textureLod(in vec4 coord, float lod) break; } - if (TEST_BIT(0, 21)) + if (TEST_INST_BIT(0, 21)) { // Normal-expand, v = 2v - 1 vr0 += vr0; @@ -436,27 +439,27 @@ void write_dst(const in vec4 value) uvr0 = uvec4(uint(1 << 9), uint(1 << 10), uint(1 << 11), uint(1 << 12)); bvr0 = bvec4(uvr0 & inst.words.xxxx); - if (TEST_BIT(0, 8)) // SET COND + if (TEST_INST_BIT(0, 8)) // SET COND { - ur0 = GET_BITS(1, 30, 1); + ur0 = GET_INST_BITS(1, 30, 1); reg_mov(cc[ur0], value, bvr0); } - if (TEST_BIT(0, 30)) // NO DEST + if (TEST_INST_BIT(0, 30)) // NO DEST { return; } - ur1 = GET_BITS(2, 28, 3); + ur1 = GET_INST_BITS(2, 28, 3); sr0 = modifier_scale[ur1]; vr0 = value * sr0; - if (TEST_BIT(0, 31)) // SAT + if (TEST_INST_BIT(0, 31)) // SAT { vr0 = clamp(vr0, 0, 1); } - ur0 = GET_BITS(1, 18, 3); + ur0 = GET_INST_BITS(1, 18, 3); if (ur0 != 0x7) { vr1 = read_cond(); @@ -464,8 +467,8 @@ void write_dst(const in vec4 value) bvr0 = bvec4(uvec4(bvr0) & uvec4(bvr1)); } - ur1 = GET_BITS(0, 1, 6); - if (TEST_BIT(0, 7)) + ur1 = GET_INST_BITS(0, 1, 6); + if (TEST_INST_BIT(0, 7)) { reg_mov(regs16[ur1], vr0, bvr0); } @@ -481,7 +484,7 @@ void initialize() // NOTE: Register count is the number of 'full' registers that will be consumed. Hardware seems to do some renaming. // NOTE: Attempting to zero-initialize all the registers will slow things to a crawl! - uint register_count = bitfieldExtract(shader_control, 24, 6); + uint register_count = GET_BITS(shader_control, 24, 6); ur0 = 0, ur1 = 0; while (register_count > 0) { @@ -587,11 +590,11 @@ void main() ((fp_instructions[ip] << 8) & uvec4(0xFF00FF00)) | ((fp_instructions[ip] >> 8) & uvec4(0x00FF00FF)); - inst.opcode = GET_BITS(0, 24, 6); - inst.end = TEST_BIT(0, 0); + inst.opcode = GET_INST_BITS(0, 24, 6); + inst.end = TEST_INST_BIT(0, 0); #ifdef WITH_FLOW_CTRL - if (TEST_BIT(2, 31)) + if (TEST_INST_BIT(2, 31)) { // Flow control switch (inst.opcode | (1 << 6)) @@ -623,8 +626,8 @@ void main() case RSX_FP_OPCODE_REP: if (check_cond()) { - counter = int(GET_BITS(2, 2, 8) - GET_BITS(2, 10, 8)); - counter /= int(GET_BITS(2, 19, 8)); + counter = int(GET_INST_BITS(2, 2, 8) - GET_INST_BITS(2, 10, 8)); + counter /= int(GET_INST_BITS(2, 19, 8)); loop_start_addr = ip + 1; loop_end_addr = int(inst.words.w >> 2); } diff --git a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureMSAAOps.glsl b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureMSAAOps.glsl index d250e2efb4..8575508f54 100644 --- a/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureMSAAOps.glsl +++ b/rpcs3/Emu/RSX/Program/GLSLSnippets/RSXProg/RSXFragmentTextureMSAAOps.glsl @@ -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); } diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp index 2f7330f8fa..6a1bcffc7a 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.cpp @@ -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; + } } } diff --git a/rpcs3/Emu/RSX/Program/ProgramStateCache.h b/rpcs3/Emu/RSX/Program/ProgramStateCache.h index f80f9eeeec..6a2c53ae93 100644 --- a/rpcs3/Emu/RSX/Program/ProgramStateCache.h +++ b/rpcs3/Emu/RSX/Program/ProgramStateCache.h @@ -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 dst_buffer, const fragment_program_type& fragment_program, const RSXFragmentProgram& rsx_prog, bool sanitize = false) const { - ensure((dst_buffer.size_bytes() >= ::narrow(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); } diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index a9e95531aa..f834b7c7f5 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -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; diff --git a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h index 6a25906e35..1277250b56 100644 --- a/rpcs3/Emu/RSX/Program/RSXVertexProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXVertexProgram.h @@ -223,10 +223,10 @@ struct RSXVertexProgram { std::vector 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 instruction_mask; std::set jump_table; diff --git a/rpcs3/Emu/RSX/Program/ShaderParam.h b/rpcs3/Emu/RSX/Program/ShaderParam.h index 3308ba6d9f..01e4931869 100644 --- a/rpcs3/Emu/RSX/Program/ShaderParam.h +++ b/rpcs3/Emu/RSX/Program/ShaderParam.h @@ -227,6 +227,14 @@ struct ParamArray return name; } + + void Clear() + { + for (auto& param : params) + { + param.clear(); + } + } }; class ShaderVariable diff --git a/rpcs3/Emu/RSX/Program/program_util.cpp b/rpcs3/Emu/RSX/Program/program_util.cpp index 9f44f05505..e5daf319fc 100644 --- a/rpcs3/Emu/RSX/Program/program_util.cpp +++ b/rpcs3/Emu/RSX/Program/program_util.cpp @@ -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; + } } diff --git a/rpcs3/Emu/RSX/Program/program_util.h b/rpcs3/Emu/RSX/Program/program_util.h index c8011a3db2..8159b27845 100644 --- a/rpcs3/Emu/RSX/Program/program_util.h +++ b/rpcs3/Emu/RSX/Program/program_util.h @@ -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; }; } diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index 29c2d8e865..57ffc0e11f 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -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) { diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 4bcd08c9f4..257c70e4a5 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -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); diff --git a/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.cpp b/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.cpp new file mode 100644 index 0000000000..6773a4d78f --- /dev/null +++ b/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.cpp @@ -0,0 +1,160 @@ +#include "stdafx.h" +#include "vkutils/device.h" +#include "vkutils/descriptors.h" +#include "VKCommonPipelineLayout.h" +#include "VKHelpers.h" + +#include "Emu/RSX/Common/simple_array.hpp" + +namespace vk +{ + rsx::simple_array get_common_binding_table() + { + const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table(); + rsx::simple_array bindings(binding_table.instancing_constants_buffer_slot + 1); + + u32 idx = 0; + + // Vertex stream, one stream for cacheable data, one stream for transient data + for (int i = 0; i < 3; i++) + { + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i; + bindings[idx].pImmutableSamplers = nullptr; + idx++; + } + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding_table.fragment_state_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding_table.fragment_texture_params_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; + bindings[idx].binding = binding_table.vertex_params_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.conditional_render_predicate_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding_table.rasterizer_env_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.instancing_lookup_table_bind_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.instancing_constants_buffer_slot; + bindings[idx].pImmutableSamplers = nullptr; + + idx++; + + return bindings; + } + + std::tuple get_common_pipeline_layout(VkDevice dev) + { + const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table(); + auto bindings = get_common_binding_table(); + u32 idx = ::size32(bindings); + + bindings.resize(binding_table.total_descriptor_bindings); + + for (auto binding = binding_table.textures_first_bind_slot; + binding < binding_table.vertex_textures_first_bind_slot; + binding++) + { + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; + bindings[idx].binding = binding; + bindings[idx].pImmutableSamplers = nullptr; + idx++; + } + + for (int i = 0; i < rsx::limits::vertex_textures_count; i++) + { + bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bindings[idx].descriptorCount = 1; + bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[idx].binding = binding_table.vertex_textures_first_bind_slot + i; + bindings[idx].pImmutableSamplers = nullptr; + idx++; + } + + ensure(idx == binding_table.total_descriptor_bindings); + + std::array push_constants; + push_constants[0].offset = 0; + push_constants[0].size = 16; + push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + + if (vk::emulate_conditional_rendering()) + { + // Conditional render toggle + push_constants[0].size = 20; + } + + const auto set_layout = vk::descriptors::create_layout(bindings); + + VkPipelineLayoutCreateInfo layout_info = {}; + layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + layout_info.setLayoutCount = 1; + layout_info.pSetLayouts = &set_layout; + layout_info.pushConstantRangeCount = 1; + layout_info.pPushConstantRanges = push_constants.data(); + + VkPipelineLayout result; + CHECK_RESULT(vkCreatePipelineLayout(dev, &layout_info, nullptr, &result)); + return std::make_tuple(result, set_layout); + } +} diff --git a/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.h b/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.h new file mode 100644 index 0000000000..7c64d67982 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/VKCommonPipelineLayout.h @@ -0,0 +1,14 @@ +#pragma once + +#include "vkutils/shared.h" +#include "Emu/RSX/Common/simple_array.hpp" + +namespace vk +{ + // Grab standard layout for decompiled RSX programs. Also used by the interpreter. + // FIXME: This generates a bloated monstrosity that needs to die. + std::tuple get_common_pipeline_layout(VkDevice dev); + + // Returns the standard binding layout without texture slots. Those have special handling depending on the consumer. + rsx::simple_array get_common_binding_table(); +} diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 51d0df3580..4490ff54bf 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -5,6 +5,7 @@ #include "VKAsyncScheduler.h" #include "VKCommandStream.h" #include "VKCommonDecompiler.h" +#include "VKCommonPipelineLayout.h" #include "VKCompute.h" #include "VKGSRender.h" #include "VKHelpers.h" @@ -401,148 +402,6 @@ namespace vk } } -namespace -{ - std::tuple get_shared_pipeline_layout(VkDevice dev) - { - const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table(); - rsx::simple_array bindings(binding_table.total_descriptor_bindings); - - u32 idx = 0; - - // Vertex stream, one stream for cacheable data, one stream for transient data - for (int i = 0; i < 3; i++) - { - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i; - bindings[idx].pImmutableSamplers = nullptr; - idx++; - } - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_state_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_texture_params_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; - bindings[idx].binding = binding_table.vertex_params_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.conditional_render_predicate_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.rasterizer_env_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.instancing_lookup_table_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.instancing_constants_buffer_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - for (auto binding = binding_table.textures_first_bind_slot; - binding < binding_table.vertex_textures_first_bind_slot; - binding++) - { - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding; - bindings[idx].pImmutableSamplers = nullptr; - idx++; - } - - for (int i = 0; i < rsx::limits::vertex_textures_count; i++) - { - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.vertex_textures_first_bind_slot + i; - bindings[idx].pImmutableSamplers = nullptr; - idx++; - } - - ensure(idx == binding_table.total_descriptor_bindings); - - std::array push_constants; - push_constants[0].offset = 0; - push_constants[0].size = 16; - push_constants[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - - if (vk::emulate_conditional_rendering()) - { - // Conditional render toggle - push_constants[0].size = 20; - } - - const auto set_layout = vk::descriptors::create_layout(bindings); - - VkPipelineLayoutCreateInfo layout_info = {}; - layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - layout_info.setLayoutCount = 1; - layout_info.pSetLayouts = &set_layout; - layout_info.pushConstantRangeCount = 1; - layout_info.pPushConstantRanges = push_constants.data(); - - VkPipelineLayout result; - CHECK_RESULT(vkCreatePipelineLayout(dev, &layout_info, nullptr, &result)); - return std::make_tuple(result, set_layout); - } -} - u64 VKGSRender::get_cycles() { return thread_ctrl::get_cycles(static_cast&>(*this)); @@ -633,7 +492,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) m_secondary_cb_list.create(m_secondary_command_buffer_pool, vk::command_buffer::access_type_hint::all); //Precalculated stuff - std::tie(m_pipeline_layout, m_descriptor_layouts) = get_shared_pipeline_layout(*m_device); + std::tie(m_pipeline_layout, m_descriptor_layouts) = vk::get_common_pipeline_layout(*m_device); //Occlusion m_occlusion_query_manager = std::make_unique(*m_device, VK_QUERY_TYPE_OCCLUSION, OCCLUSION_MAX_POOL_SIZE); @@ -688,10 +547,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 +589,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar) else m_vertex_cache = std::make_unique(); - m_shaders_cache = std::make_unique(*m_prog_buffer, "vulkan", "v1.94"); + m_shaders_cache = std::make_unique(*m_prog_buffer, "vulkan", "v1.95"); for (u32 i = 0; i < m_swapchain->get_swap_image_count(); ++i) { @@ -2192,7 +2051,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 +2078,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 +2209,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 +2219,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 +2302,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 data_range; void* data_source = nullptr; @@ -2453,7 +2328,7 @@ void VKGSRender::patch_transform_constants(rsx::context* ctx, u32 index, u32 cou data_range = { m_vertex_constants_buffer_info.offset + byte_offset, byte_count }; data_source = ®S(ctx)->transform_constants[index]; } - else if (auto xform_id = m_vertex_prog->TranslateConstantsRange(index, count); xform_id >= 0) + else if (auto xform_id = m_vertex_prog->translate_constants_range(index, count); xform_id >= 0) { const auto write_offset = xform_id * 16; const auto byte_count = count * 16; diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index 55c4b029bb..c245a2677f 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -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; diff --git a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp index 6b6882f616..930210f19d 100644 --- a/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp +++ b/rpcs3/Emu/RSX/VK/VKPipelineCompiler.cpp @@ -63,7 +63,7 @@ namespace vk const std::vector& vs_inputs, const std::vector& fs_inputs) { VkPipeline pipeline; - CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, nullptr, 1, &create_info, NULL, &pipeline)); + CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, VK_NULL_HANDLE, 1, &create_info, nullptr, &pipeline)); auto result = std::make_unique(*m_device, pipeline, pipe_layout, vs_inputs, fs_inputs); result->link(); return result; diff --git a/rpcs3/Emu/RSX/VK/VKResolveHelper.h b/rpcs3/Emu/RSX/VK/VKResolveHelper.h index ab9e827db9..6b83a5af9c 100644 --- a/rpcs3/Emu/RSX/VK/VKResolveHelper.h +++ b/rpcs3/Emu/RSX/VK/VKResolveHelper.h @@ -387,7 +387,7 @@ namespace vk struct stencilonly_unresolve : depth_resolve_base { - VkClearRect region{}; + VkClearRect clear_region{}; VkClearAttachment clear_info{}; stencilonly_unresolve() @@ -402,8 +402,8 @@ namespace vk renderpass_config.set_depth_mask(false); clear_info.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; - region.baseArrayLayer = 0; - region.layerCount = 1; + clear_region.baseArrayLayer = 0; + clear_region.layerCount = 1; static_parameters_width = 3; @@ -425,7 +425,7 @@ namespace vk void emit_geometry(vk::command_buffer& cmd) override { - vkCmdClearAttachments(cmd, 1, &clear_info, 1, ®ion); + vkCmdClearAttachments(cmd, 1, &clear_info, 1, &clear_region); for (s32 write_mask = 0x1; write_mask <= 0x80; write_mask <<= 1) { @@ -444,8 +444,8 @@ namespace vk auto stencil_view = resolve_image->get_view(rsx::default_remap_vector.with_encoding(VK_REMAP_IDENTITY), VK_IMAGE_ASPECT_STENCIL_BIT); - region.rect.extent.width = resolve_image->width(); - region.rect.extent.height = resolve_image->height(); + clear_region.rect.extent.width = msaa_image->width(); + clear_region.rect.extent.height = msaa_image->height(); overlay_pass::run( cmd, diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp index ecd59dc09d..f32cbfdf3d 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.cpp @@ -1,5 +1,6 @@ #include "stdafx.h" #include "VKShaderInterpreter.h" +#include "VKCommonPipelineLayout.h" #include "VKVertexProgram.h" #include "VKFragmentProgram.h" #include "VKGSRender.h" @@ -233,77 +234,12 @@ namespace vk std::pair shader_interpreter::create_layout(VkDevice dev) { const auto& binding_table = vk::get_current_renderer()->get_pipeline_binding_table(); - rsx::simple_array bindings(binding_table.total_descriptor_bindings); + auto bindings = get_common_binding_table(); + u32 idx = ::size32(bindings); - u32 idx = 0; - - // Vertex stream, one stream for cacheable data, one stream for transient data. Third stream contains vertex layout info - for (int i = 0; i < 3; i++) - { - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.vertex_buffers_first_bind_slot + i; - bindings[idx].pImmutableSamplers = nullptr; - idx++; - } - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_constant_buffers_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_state_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.fragment_texture_params_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.vertex_constant_buffers_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_ALL_GRAPHICS; - bindings[idx].binding = binding_table.vertex_params_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; - bindings[idx].binding = binding_table.conditional_render_predicate_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; - - bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; - bindings[idx].descriptorCount = 1; - bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; - bindings[idx].binding = binding_table.rasterizer_env_bind_slot; - bindings[idx].pImmutableSamplers = nullptr; - - idx++; + bindings.resize(binding_table.total_descriptor_bindings); + // Texture 1D array bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[idx].descriptorCount = 16; bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -313,6 +249,7 @@ namespace vk m_fragment_textures_start = bindings[idx].binding; idx++; + // Texture 2D array bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[idx].descriptorCount = 16; bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -321,6 +258,7 @@ namespace vk idx++; + // Texture 3D array bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[idx].descriptorCount = 16; bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -329,6 +267,7 @@ namespace vk idx++; + // Texture CUBE array bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[idx].descriptorCount = 16; bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -337,6 +276,7 @@ namespace vk idx++; + // Vertex texture array (2D only) bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[idx].descriptorCount = 4; bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; @@ -345,6 +285,7 @@ namespace vk idx++; + // Vertex program ucode block bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[idx].descriptorCount = 1; bindings[idx].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; @@ -354,6 +295,7 @@ namespace vk m_vertex_instruction_start = bindings[idx].binding; idx++; + // Fragment program ucode block bindings[idx].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; bindings[idx].descriptorCount = 1; bindings[idx].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; @@ -364,6 +306,22 @@ namespace vk idx++; bindings.resize(idx); + // Compile descriptor pool sizes + const u32 num_ubo = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER ? y.descriptorCount : 0))); + const u32 num_texel_buffers = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER ? y.descriptorCount : 0))); + const u32 num_combined_image_sampler = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER ? y.descriptorCount : 0))); + const u32 num_ssbo = bindings.reduce(0, FN(x + (y.descriptorType == VK_DESCRIPTOR_TYPE_STORAGE_BUFFER ? y.descriptorCount : 0))); + + ensure(num_ubo > 0 && num_texel_buffers > 0 && num_combined_image_sampler > 0 && num_ssbo > 0); + + m_descriptor_pool_sizes = + { + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , num_ubo }, + { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER , num_texel_buffers }, + { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , num_combined_image_sampler }, + { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, num_ssbo } + }; + std::array push_constants; push_constants[0].offset = 0; push_constants[0].size = 16; @@ -392,16 +350,7 @@ namespace vk void shader_interpreter::create_descriptor_pools(const vk::render_device& dev) { const auto max_draw_calls = dev.get_descriptor_max_draw_calls(); - - rsx::simple_array sizes = - { - { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER , 6 }, - { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER , 3 }, - { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER , 68 }, - { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3 } - }; - - m_descriptor_pool.create(dev, sizes, max_draw_calls); + m_descriptor_pool.create(dev, m_descriptor_pool_sizes, max_draw_calls); } void shader_interpreter::init(const vk::render_device& dev) @@ -410,6 +359,7 @@ namespace vk std::tie(m_shared_descriptor_layout, m_shared_pipeline_layout) = create_layout(dev); create_descriptor_pools(dev); + rsx_log.notice("Building global vertex program interpreter..."); build_vs(); // TODO: Seed the cache } @@ -449,6 +399,7 @@ namespace vk } else { + rsx_log.notice("Compiling FS..."); fs = build_fs(compiler_opt); } @@ -463,15 +414,17 @@ namespace vk shader_stages[1].module = fs->get_handle(); shader_stages[1].pName = "main"; - std::vector dynamic_state_descriptors; - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_VIEWPORT); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_SCISSOR); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_LINE_WIDTH); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_BLEND_CONSTANTS); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE); - dynamic_state_descriptors.push_back(VK_DYNAMIC_STATE_DEPTH_BIAS); + std::vector dynamic_state_descriptors = + { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR, + VK_DYNAMIC_STATE_LINE_WIDTH, + VK_DYNAMIC_STATE_BLEND_CONSTANTS, + VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK, + VK_DYNAMIC_STATE_STENCIL_WRITE_MASK, + VK_DYNAMIC_STATE_STENCIL_REFERENCE, + VK_DYNAMIC_STATE_DEPTH_BIAS + }; if (vk::get_current_renderer()->get_depth_bounds_support()) { @@ -502,6 +455,9 @@ namespace vk VkPipelineColorBlendStateCreateInfo cs = properties.state.cs; cs.pAttachments = properties.state.att_state; + VkPipelineTessellationStateCreateInfo ts = {}; + ts.sType = VK_STRUCTURE_TYPE_PIPELINE_TESSELLATION_STATE_CREATE_INFO; + VkGraphicsPipelineCreateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; info.pVertexInputState = &vi; @@ -511,6 +467,7 @@ namespace vk info.pMultisampleState = &ms; info.pViewportState = &vp; info.pDepthStencilState = &properties.state.ds; + info.pTessellationState = &ts; info.stageCount = 2; info.pStages = shader_stages; info.pDynamicState = &dynamic_state_info; diff --git a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h index 72f3b0ee3b..9dcd109402 100644 --- a/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h +++ b/rpcs3/Emu/RSX/VK/VKShaderInterpreter.h @@ -41,6 +41,7 @@ namespace vk std::unordered_map, key_hasher> m_program_cache; std::unordered_map> m_fs_cache; + rsx::simple_array m_descriptor_pool_sizes; vk::descriptor_pool m_descriptor_pool; u32 m_vertex_instruction_start = 0; diff --git a/rpcs3/Emu/RSX/rsx_cache.h b/rpcs3/Emu/RSX/rsx_cache.h index 843789d442..f5eca2c85b 100644 --- a/rpcs3/Emu/RSX/rsx_cache.h +++ b/rpcs3/Emu/RSX/rsx_cache.h @@ -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(data.vp_ctrl0); - state_hash ^= rpcs3::hash_base(data.vp_ctrl1); - state_hash ^= rpcs3::hash_base(data.fp_ctrl); - state_hash ^= rpcs3::hash_base(data.vp_texture_dimensions); - state_hash ^= rpcs3::hash_base(data.fp_texture_dimensions); - state_hash ^= rpcs3::hash_base(data.fp_texcoord_control); - state_hash ^= rpcs3::hash_base(data.fp_height); - state_hash ^= rpcs3::hash_base(data.fp_pixel_layout); - state_hash ^= rpcs3::hash_base(data.fp_lighting_flags); - state_hash ^= rpcs3::hash_base(data.fp_shadow_textures); - state_hash ^= rpcs3::hash_base(data.fp_redirected_textures); - state_hash ^= rpcs3::hash_base(data.vp_multisampled_textures); - state_hash ^= rpcs3::hash_base(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; } diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 551e7de36d..cb31823026 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -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 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; @@ -347,9 +347,40 @@ void Emulator::Init() { jit_runtime::initialize(); + const std::string emu_dir = rpcs3::utils::get_emu_dir(); + auto make_path_verbose = [&](const std::string& path, bool must_exist_outside_emu_dir) + { + if (fs::is_dir(path)) + { + return true; + } + + if (must_exist_outside_emu_dir) + { + const std::string parent = fs::get_parent_dir(path); + const std::string emu_dir_no_delim = emu_dir.substr(0, emu_dir.find_last_not_of(fs::delim) + 1); + + if (parent != emu_dir_no_delim && GetCallbacks().resolve_path(parent) != GetCallbacks().resolve_path(emu_dir_no_delim)) + { + sys_log.fatal("Cannot use '%s' for Virtual File System because it does not exist.\nPlease specify an existing and writable directory path in Toolbar -> Manage -> Virtual File System.", path); + return false; + } + } + + if (!fs::create_path(path)) + { + sys_log.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error); + return false; + } + + return true; + }; + if (!g_tty) { - const auto tty_path = fs::get_cache_dir() + "TTY.log"; + make_path_verbose(fs::get_log_dir(), true); + + const auto tty_path = fs::get_log_dir() + "TTY.log"; g_tty.open(tty_path, fs::rewrite + fs::append); if (!g_tty) @@ -374,7 +405,22 @@ void Emulator::Init() g_cfg_defaults = g_cfg.to_string(); - const std::string cfg_path = fs::get_config_dir() + "/config.yml"; + const std::string cfg_path = fs::get_config_dir(true) + "config.yml"; + + // Move file from deprecated location to new location +#ifdef _WIN32 + const std::string old_path = fs::get_config_dir(false) + "config.yml"; + + if (fs::is_file(old_path)) + { + sys_log.notice("Found deprecated config.yml file: '%s'", old_path); + + if (!fs::rename(old_path, cfg_path, false)) + { + (fs::g_tls_error == fs::error::exist ? sys_log.warning : sys_log.error)("Failed to move '%s' to '%s' (error='%s')", old_path, cfg_path, fs::g_tls_error); + } + } +#endif // Save new global config if it doesn't exist or is empty if (fs::stat_t info{}; !fs::get_stat(cfg_path, info) || info.size == 0) @@ -387,7 +433,6 @@ void Emulator::Init() sys_log.notice("Using VFS config:\n%s", g_cfg_vfs.to_string()); // Mount all devices - const std::string emu_dir = rpcs3::utils::get_emu_dir(); const std::string elf_dir = fs::get_parent_dir(m_path); const std::string dev_bdvd = g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, emu_dir); // Only used for make_path const std::string dev_hdd0 = g_cfg_vfs.get(g_cfg_vfs.dev_hdd0, emu_dir); @@ -487,34 +532,6 @@ void Emulator::Init() g_backup_cfg.from_string(g_cfg.to_string()); // Create directories (can be disabled if necessary) - auto make_path_verbose = [&](const std::string& path, bool must_exist_outside_emu_dir) - { - if (fs::is_dir(path)) - { - return true; - } - - if (must_exist_outside_emu_dir) - { - const std::string parent = fs::get_parent_dir(path); - const std::string emu_dir_no_delim = emu_dir.substr(0, emu_dir.find_last_not_of(fs::delim) + 1); - - if (parent != emu_dir_no_delim && GetCallbacks().resolve_path(parent) != GetCallbacks().resolve_path(emu_dir_no_delim)) - { - sys_log.fatal("Cannot use '%s' for Virtual File System because it does not exist.\nPlease specify an existing and writable directory path in Toolbar -> Manage -> Virtual File System.", path); - return false; - } - } - - if (!fs::create_path(path)) - { - sys_log.fatal("Failed to create path: %s (%s)", path, fs::g_tls_error); - return false; - } - - return true; - }; - const std::string save_path = dev_hdd0 + "home/" + m_usr + "/savedata/"; const std::string user_path = dev_hdd0 + "home/" + m_usr + "/localusername"; @@ -964,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; @@ -1005,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(g_fxo->try_get())) + { + 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) @@ -1132,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; @@ -2895,8 +2927,14 @@ u64 get_sysutil_cb_manager_read_count(); void qt_events_aware_op(int repeat_duration_ms, std::function 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(); @@ -3040,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>, u32>> paused_spus; if (!try_lock_spu_threads_in_a_state_compatible_with_savestates(false, &paused_spus)) @@ -3110,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{}; @@ -3185,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>() && (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>(); 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 @@ -3265,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) { @@ -3278,7 +3337,6 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s thread_ctrl::wait_for(5'000); } - *closed_sucessfully = true; })); @@ -3580,7 +3638,7 @@ void Emulator::Kill(bool allow_autoexit, bool savestate, savestate_stage* save_s if (usz attempted_read_size = utils::sub_saturate(g_tty.pos(), m_tty_file_init_pos)) { - if (fs::file tty_read_fd{fs::get_cache_dir() + "TTY.log"}) + if (fs::file tty_read_fd{fs::get_log_dir() + "TTY.log"}) { // Enforce an arbitrary limit for now to avoid OOM in case the guest code has bombarded TTY // 3MB, this should be enough @@ -3849,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 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()) - { - if (auto current = ppu->current_function) - { - func = current; - } - } - else if (auto spu = get_current_cpu_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>(); @@ -4508,7 +4483,7 @@ void Emulator::SaveSettings(const std::string& settings, const std::string& titl if (title_id.empty()) { - config_name = fs::get_config_dir() + "/config.yml"; + config_name = fs::get_config_dir(true) + "config.yml"; } else { diff --git a/rpcs3/Emu/System.h b/rpcs3/Emu/System.h index d9d1991b3e..bd127aff4f 100644 --- a/rpcs3/Emu/System.h +++ b/rpcs3/Emu/System.h @@ -86,6 +86,7 @@ struct EmuCallbacks std::function init_pad_handler; std::function update_emu_settings; std::function save_emu_settings; + std::function close_gs_frame; std::function()> get_gs_frame; std::function()> get_camera_handler; std::function()> 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; diff --git a/rpcs3/Emu/games_config.cpp b/rpcs3/Emu/games_config.cpp index 0979a01585..cfa43f6e15 100644 --- a/rpcs3/Emu/games_config.cpp +++ b/rpcs3/Emu/games_config.cpp @@ -123,7 +123,7 @@ bool games_config::save_nl() YAML::Emitter out; out << m_games; - fs::pending_file temp(fs::get_config_dir() + "/games.yml"); + fs::pending_file temp(fs::get_config_dir(true) + "games.yml"); if (temp.file && temp.file.write(out.c_str(), out.size()) >= out.size() && temp.commit()) { @@ -147,7 +147,24 @@ void games_config::load() m_games.clear(); - if (fs::file f{fs::get_config_dir() + "/games.yml", fs::read + fs::create}) + const std::string path = fs::get_config_dir(true) + "games.yml"; + + // Move file from deprecated location to new location +#ifdef _WIN32 + const std::string old_path = fs::get_config_dir(false) + "games.yml"; + + if (fs::is_file(old_path)) + { + cfg_log.notice("Found deprecated games.yml file: '%s'", old_path); + + if (!fs::rename(old_path, path, false)) + { + (fs::g_tls_error == fs::error::exist ? cfg_log.warning : cfg_log.error)("Failed to move '%s' to '%s' (error='%s')", old_path, path, fs::g_tls_error); + } + } +#endif + + if (fs::file f{path, fs::read + fs::create}) { auto [result, error] = yaml_load(f.to_string()); diff --git a/rpcs3/Emu/localized_string_id.h b/rpcs3/Emu/localized_string_id.h index 21437762bb..d1f576051d 100644 --- a/rpcs3/Emu/localized_string_id.h +++ b/rpcs3/Emu/localized_string_id.h @@ -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, diff --git a/rpcs3/Emu/savestate_utils.cpp b/rpcs3/Emu/savestate_utils.cpp index 85a2a82574..bb3e440564 100644 --- a/rpcs3/Emu/savestate_utils.cpp +++ b/rpcs3/Emu/savestate_utils.cpp @@ -71,8 +71,8 @@ SERIALIZATION_VER(sceNp, 11) SERIALIZATION_VER(cellVdec, 12, 1) SERIALIZATION_VER(cellAudio, 13, 1) -SERIALIZATION_VER(cellCamera, 14, 1) -SERIALIZATION_VER(cellGem, 15, 1, 2/*calibration_status_flags*/) +SERIALIZATION_VER(cellCamera, 14, 1, 2/*gem_camera_shared*/) +SERIALIZATION_VER(cellGem, 15, 1, 2/*calibration_status_flags*/, 3/*video_conversion*/) SERIALIZATION_VER(sceNpTrophy, 16, 1) SERIALIZATION_VER(cellMusic, 17, 1) SERIALIZATION_VER(cellVoice, 18, 1) @@ -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) { diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 3cb3e39851..d3dfab4ce8 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -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 }; diff --git a/rpcs3/Emu/system_progress.cpp b/rpcs3/Emu/system_progress.cpp index 66d59af65b..52e49516fb 100644 --- a/rpcs3/Emu/system_progress.cpp +++ b/rpcs3/Emu/system_progress.cpp @@ -26,7 +26,7 @@ atomic_t g_progr_pdone{0}; atomic_t g_system_progress_canceled{false}; // For showing feedback while stopping emulation -atomic_t g_system_progress_stopping{false}; +atomic_t g_system_progress_stopping{system_progress_stop_state::stop_state_disabled}; namespace rsx::overlays { @@ -40,7 +40,7 @@ namespace rsx::overlays void progress_dialog_server::operator()() { std::shared_ptr 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 = []() @@ -63,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(); + + if (show_overlay_message) + { + *show_overlay_message = g_fxo->get().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(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 @@ -113,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 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(); - show_overlay_message = g_fxo->get().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(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())) { @@ -392,6 +405,7 @@ void progress_dialog_server::operator()() else if (native_dlg) { native_dlg->close(false, false); + native_dlg.reset(); } else if (dlg) { @@ -411,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)) diff --git a/rpcs3/Emu/system_progress.hpp b/rpcs3/Emu/system_progress.hpp index 13ae8d13eb..67e4e68df1 100644 --- a/rpcs3/Emu/system_progress.hpp +++ b/rpcs3/Emu/system_progress.hpp @@ -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 g_progr_ftotal; extern atomic_t g_progr_fdone; @@ -44,7 +51,7 @@ extern atomic_t g_progr_fknown_bits; extern atomic_t g_progr_ptotal; extern atomic_t g_progr_pdone; extern atomic_t g_system_progress_canceled; -extern atomic_t g_system_progress_stopping; +extern atomic_t g_system_progress_stopping; // Initialize progress dialog (can be recursive) class scoped_progress_dialog final diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index 050f0839c2..49439dc3fc 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -311,11 +311,7 @@ namespace rpcs3::utils std::string get_custom_config_dir() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/custom_configs/"; -#else - return fs::get_config_dir() + "custom_configs/"; -#endif + return fs::get_config_dir(true) + "custom_configs/"; } std::string get_custom_config_path(const std::string& identifier) @@ -330,11 +326,7 @@ namespace rpcs3::utils std::string get_input_config_root() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/input_configs/"; -#else - return fs::get_config_dir() + "input_configs/"; -#endif + return fs::get_config_dir(true) + "input_configs/"; } std::string get_input_config_dir(const std::string& title_id) diff --git a/rpcs3/Emu/vfs_config.cpp b/rpcs3/Emu/vfs_config.cpp index c650bfaeeb..0d7508a284 100644 --- a/rpcs3/Emu/vfs_config.cpp +++ b/rpcs3/Emu/vfs_config.cpp @@ -115,7 +115,7 @@ void cfg_vfs::load() void cfg_vfs::save() const { #ifdef _WIN32 - const std::string path_to_cfg = fs::get_config_dir() + "config/"; + const std::string path_to_cfg = fs::get_config_dir(true); if (!fs::create_path(path_to_cfg)) { vfs_log.error("Could not create path: %s", path_to_cfg); @@ -140,9 +140,5 @@ void cfg_vfs::save() const std::string cfg_vfs::get_path() { -#ifdef _WIN32 - return fs::get_config_dir() + "config/vfs.yml"; -#else - return fs::get_config_dir() + "vfs.yml"; -#endif + return fs::get_config_dir(true) + "vfs.yml"; } diff --git a/rpcs3/Input/basic_mouse_handler.cpp b/rpcs3/Input/basic_mouse_handler.cpp index d9435ef95e..4ce238441b 100644 --- a/rpcs3/Input/basic_mouse_handler.cpp +++ b/rpcs3/Input/basic_mouse_handler.cpp @@ -108,10 +108,10 @@ bool basic_mouse_handler::eventFilter(QObject* target, QEvent* ev) switch (ev->type()) { case QEvent::MouseButtonPress: - MouseButtonDown(static_cast(ev)); + MouseButton(static_cast(ev), true); break; case QEvent::MouseButtonRelease: - MouseButtonUp(static_cast(ev)); + MouseButton(static_cast(ev), false); break; case QEvent::MouseMove: MouseMove(static_cast(ev)); @@ -119,6 +119,24 @@ bool basic_mouse_handler::eventFilter(QObject* target, QEvent* ev) case QEvent::Wheel: MouseScroll(static_cast(ev)); break; + case QEvent::KeyPress: + Key(static_cast(ev), true); + break; + case QEvent::KeyRelease: + Key(static_cast(ev), false); + break; + case QEvent::Leave: + { + // Issue mouse move on leave. Otherwise we may not get any mouse event at the screen borders. + const QPoint window_pos = m_target->mapToGlobal(m_target->position()) / m_target->devicePixelRatio(); + const QPoint cursor_pos = QCursor::pos() - window_pos; + + if (cursor_pos.x() <= 0 || cursor_pos.x() >= m_target->width() || cursor_pos.y() <= 0 || cursor_pos.y() >= m_target->height()) + { + MouseMove(cursor_pos); + } + break; + } default: return false; } @@ -126,22 +144,22 @@ bool basic_mouse_handler::eventFilter(QObject* target, QEvent* ev) return false; } -void basic_mouse_handler::MouseButtonDown(QMouseEvent* event) +void basic_mouse_handler::Key(QKeyEvent* event, bool pressed) { if (!event) [[unlikely]] { return; } - const int button = event->button(); - if (const auto it = std::find_if(m_buttons.cbegin(), m_buttons.cend(), [button](const auto& entry){ return entry.second == button; }); + const int key = event->key(); + if (const auto it = std::find_if(m_buttons.cbegin(), m_buttons.cend(), [key](const auto& entry){ return entry.second.code == key && entry.second.is_key; }); it != m_buttons.cend()) { - MouseHandlerBase::Button(0, it->first, true); + MouseHandlerBase::Button(0, it->first, pressed); } } -void basic_mouse_handler::MouseButtonUp(QMouseEvent* event) +void basic_mouse_handler::MouseButton(QMouseEvent* event, bool pressed) { if (!event) [[unlikely]] { @@ -149,10 +167,10 @@ void basic_mouse_handler::MouseButtonUp(QMouseEvent* event) } const int button = event->button(); - if (const auto it = std::find_if(m_buttons.cbegin(), m_buttons.cend(), [button](const auto& entry){ return entry.second == button; }); + if (const auto it = std::find_if(m_buttons.cbegin(), m_buttons.cend(), [button](const auto& entry){ return entry.second.code == button && !entry.second.is_key; }); it != m_buttons.cend()) { - MouseHandlerBase::Button(0, it->first, false); + MouseHandlerBase::Button(0, it->first, pressed); } } @@ -176,17 +194,31 @@ bool basic_mouse_handler::get_mouse_lock_state() const return false; } -int basic_mouse_handler::get_mouse_button(const cfg::string& button) +basic_mouse_handler::mouse_button basic_mouse_handler::get_mouse_button(const cfg::string& button) { const std::string name = button.to_string(); const auto it = std::find_if(mouse_list.cbegin(), mouse_list.cend(), [&name](const auto& entry){ return entry.second == name; }); if (it != mouse_list.cend()) { - return it->first; + return mouse_button{ + .code = static_cast(it->first), + .is_key = false + }; } - return Qt::MouseButton::NoButton; + if (const u32 key = keyboard_pad_handler::GetKeyCode(QString::fromStdString(name))) + { + return mouse_button{ + .code = static_cast(key), + .is_key = true + }; + } + + return mouse_button{ + .code = Qt::MouseButton::NoButton, + .is_key = false + }; } void basic_mouse_handler::MouseMove(QMouseEvent* event) @@ -198,38 +230,44 @@ void basic_mouse_handler::MouseMove(QMouseEvent* event) if (is_time_for_update()) { - // get the screen dimensions - const QSize screen = m_target->size(); - const QPoint e_pos = event->pos(); - - if (m_target && m_target->isActive() && get_mouse_lock_state()) - { - // get the center of the screen in global coordinates - QPoint p_center = m_target->geometry().topLeft() + QPoint(screen.width() / 2, screen.height() / 2); - - // reset the mouse to the center for consistent results since edge movement won't be registered - QCursor::setPos(m_target->screen(), p_center); - - // convert the center into screen coordinates - p_center = m_target->mapFromGlobal(p_center); - - // current mouse position, starting at the center - static QPoint p_real(p_center); - - // get the delta of the mouse position to the screen center - const QPoint p_delta = e_pos - p_center; - - // update the current position without leaving the screen borders - p_real.setX(std::clamp(p_real.x() + p_delta.x(), 0, screen.width())); - p_real.setY(std::clamp(p_real.y() + p_delta.y(), 0, screen.height())); - - // pass the 'real' position and the current delta to the screen center - MouseHandlerBase::Move(0, p_real.x(), p_real.y(), screen.width(), screen.height(), true, p_delta.x(), p_delta.y()); - } - else - { - // pass the absolute position - MouseHandlerBase::Move(0, e_pos.x(), e_pos.y(), screen.width(), screen.height()); - } + MouseMove(event->pos()); + } +} + +void basic_mouse_handler::MouseMove(const QPoint& e_pos) +{ + if (!m_target) return; + + // get the screen dimensions + const QSize screen = m_target->size(); + + if (m_target->isActive() && get_mouse_lock_state()) + { + // get the center of the screen in global coordinates + QPoint p_center = m_target->geometry().topLeft() + QPoint(screen.width() / 2, screen.height() / 2); + + // reset the mouse to the center for consistent results since edge movement won't be registered + QCursor::setPos(m_target->screen(), p_center); + + // convert the center into screen coordinates + p_center = m_target->mapFromGlobal(p_center); + + // current mouse position, starting at the center + static QPoint p_real(p_center); + + // get the delta of the mouse position to the screen center + const QPoint p_delta = e_pos - p_center; + + // update the current position without leaving the screen borders + p_real.setX(std::clamp(p_real.x() + p_delta.x(), 0, screen.width())); + p_real.setY(std::clamp(p_real.y() + p_delta.y(), 0, screen.height())); + + // pass the 'real' position and the current delta to the screen center + MouseHandlerBase::Move(0, p_real.x(), p_real.y(), screen.width(), screen.height(), true, p_delta.x(), p_delta.y()); + } + else + { + // pass the absolute position + MouseHandlerBase::Move(0, e_pos.x(), e_pos.y(), screen.width(), screen.height()); } } diff --git a/rpcs3/Input/basic_mouse_handler.h b/rpcs3/Input/basic_mouse_handler.h index ccd4224bd8..e10dafc2f6 100644 --- a/rpcs3/Input/basic_mouse_handler.h +++ b/rpcs3/Input/basic_mouse_handler.h @@ -20,17 +20,24 @@ public: void Init(const u32 max_connect) override; void SetTargetWindow(QWindow* target); - void MouseButtonDown(QMouseEvent* event); - void MouseButtonUp(QMouseEvent* event); + void Key(QKeyEvent* event, bool pressed); + void MouseButton(QMouseEvent* event, bool pressed); void MouseScroll(QWheelEvent* event); void MouseMove(QMouseEvent* event); + void MouseMove(const QPoint& e_pos); bool eventFilter(QObject* obj, QEvent* ev) override; private: void reload_config(); bool get_mouse_lock_state() const; - static int get_mouse_button(const cfg::string& button); + + struct mouse_button + { + int code = Qt::MouseButton::NoButton; + bool is_key = false; + }; + static mouse_button get_mouse_button(const cfg::string& button); QWindow* m_target = nullptr; - std::map m_buttons; + std::map m_buttons; }; diff --git a/rpcs3/Input/ds3_pad_handler.cpp b/rpcs3/Input/ds3_pad_handler.cpp index 41afccfad0..0ea5aad19f 100644 --- a/rpcs3/Input/ds3_pad_handler.cpp +++ b/rpcs3/Input/ds3_pad_handler.cpp @@ -58,6 +58,7 @@ ds3_pad_handler::ds3_pad_handler() b_has_rgb = false; b_has_player_led = true; b_has_pressure_intensity_button = false; // The DS3 obviously already has this feature natively. + b_has_orientation = true; m_name_string = "DS3 Pad #"; m_max_devices = CELL_PAD_MAX_PORT_NUM; @@ -199,6 +200,7 @@ void ds3_pad_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, DS3KeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, DS3KeyCodes::None); + cfg->orientation_reset_button.def = ::at32(button_list, DS3KeyCodes::None); // Set default misc variables cfg->lstick_anti_deadzone.def = 0; @@ -462,6 +464,9 @@ void ds3_pad_handler::get_extended_info(const pad_ensemble& binding) //pad->m_sensors[1].m_value = polish_value(pad->m_sensors[1].m_value, 226, 226, 512, 512, 0, 1023); //pad->m_sensors[2].m_value = polish_value(pad->m_sensors[2].m_value, 113, 113, 512, 512, 0, 1023); //pad->m_sensors[3].m_value = polish_value(pad->m_sensors[3].m_value, 1, 1, 512, 512, 0, 1023); + + // Set raw orientation + set_raw_orientation(*pad); } bool ds3_pad_handler::get_is_left_trigger(const std::shared_ptr& /*device*/, u64 keyCode) @@ -546,11 +551,8 @@ void ds3_pad_handler::apply_pad_data(const pad_ensemble& binding) cfg_pad* config = dev->config; - const int idx_l = config->switch_vibration_motors ? 1 : 0; - const int idx_s = config->switch_vibration_motors ? 0 : 1; - - const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; - const u8 speed_small = config->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + const u8 speed_large = config->get_large_motor_speed(pad->m_vibrateMotors); + const u8 speed_small = config->get_small_motor_speed(pad->m_vibrateMotors); const bool wireless = dev->cable_state == 0; const bool low_battery = dev->battery_level < 25; diff --git a/rpcs3/Input/ds4_pad_handler.cpp b/rpcs3/Input/ds4_pad_handler.cpp index 3cbd0fb61f..a24351fad9 100644 --- a/rpcs3/Input/ds4_pad_handler.cpp +++ b/rpcs3/Input/ds4_pad_handler.cpp @@ -118,6 +118,7 @@ ds4_pad_handler::ds4_pad_handler() b_has_rgb = true; b_has_battery = true; b_has_battery_led = true; + b_has_orientation = true; m_name_string = "DS4 Pad #"; m_max_devices = CELL_PAD_MAX_PORT_NUM; @@ -179,6 +180,7 @@ void ds4_pad_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, DS4KeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, DS4KeyCodes::None); + cfg->orientation_reset_button.def = ::at32(button_list, DS4KeyCodes::None); // Set default misc variables cfg->lstick_anti_deadzone.def = static_cast(0.13 * thumb_max); // 13% @@ -883,29 +885,27 @@ void ds4_pad_handler::get_extended_info(const pad_ensemble& binding) // these values come already calibrated, all we need to do is convert to ds3 range - // accel - f32 accelX = static_cast(input.accel[0]) / static_cast(DS4_ACC_RES_PER_G) * -1; - f32 accelY = static_cast(input.accel[1]) / static_cast(DS4_ACC_RES_PER_G) * -1; - f32 accelZ = static_cast(input.accel[2]) / static_cast(DS4_ACC_RES_PER_G) * -1; + // acceleration (linear velocity in m/s²) + const f32 accel_x = static_cast(input.accel[0]) / static_cast(DS4_ACC_RES_PER_G) * -1; + const f32 accel_y = static_cast(input.accel[1]) / static_cast(DS4_ACC_RES_PER_G) * -1; + const f32 accel_z = static_cast(input.accel[2]) / static_cast(DS4_ACC_RES_PER_G) * -1; + + // gyro (angular velocity in degree/s) + const f32 gyro_x = static_cast(input.gyro[0]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; + const f32 gyro_y = static_cast(input.gyro[1]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; + const f32 gyro_z = static_cast(input.gyro[2]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; // now just use formula from ds3 - accelX = accelX * 113 + 512; - accelY = accelY * 113 + 512; - accelZ = accelZ * 113 + 512; - - pad->m_sensors[0].m_value = Clamp0To1023(accelX); - pad->m_sensors[1].m_value = Clamp0To1023(accelY); - pad->m_sensors[2].m_value = Clamp0To1023(accelZ); - - // gyroY is yaw, which is all that we need - //f32 gyroX = static_cast(input.gyro[0]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; - f32 gyroY = static_cast(input.gyro[1]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; - //f32 gyroZ = static_cast(input.gyro[2]) / static_cast(DS4_GYRO_RES_PER_DEG_S) * -1; + pad->m_sensors[0].m_value = Clamp0To1023(accel_x * MOTION_ONE_G + 512); + pad->m_sensors[1].m_value = Clamp0To1023(accel_y * MOTION_ONE_G + 512); + pad->m_sensors[2].m_value = Clamp0To1023(accel_z * MOTION_ONE_G + 512); + // gyro_y is yaw, which is all that we need. // Convert to ds3. The ds3 resolution is 123/90°/sec. - gyroY = gyroY * (123.f / 90.f) + 512; + pad->m_sensors[3].m_value = Clamp0To1023(gyro_y * (123.f / 90.f) + 512); - pad->m_sensors[3].m_value = Clamp0To1023(gyroY); + // Set raw orientation + set_raw_orientation(pad->move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z); } void ds4_pad_handler::apply_pad_data(const pad_ensemble& binding) @@ -920,11 +920,8 @@ void ds4_pad_handler::apply_pad_data(const pad_ensemble& binding) cfg_pad* config = dev->config; // Attempt to send rumble no matter what - const int idx_l = config->switch_vibration_motors ? 1 : 0; - const int idx_s = config->switch_vibration_motors ? 0 : 1; - - const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; - const u8 speed_small = config->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + const u8 speed_large = config->get_large_motor_speed(pad->m_vibrateMotors); + const u8 speed_small = config->get_small_motor_speed(pad->m_vibrateMotors); const bool wireless = dev->cable_state == 0; const bool low_battery = dev->battery_level < 2; diff --git a/rpcs3/Input/dualsense_pad_handler.cpp b/rpcs3/Input/dualsense_pad_handler.cpp index b9dbcd6d29..4c0253adba 100644 --- a/rpcs3/Input/dualsense_pad_handler.cpp +++ b/rpcs3/Input/dualsense_pad_handler.cpp @@ -90,6 +90,7 @@ dualsense_pad_handler::dualsense_pad_handler() b_has_player_led = true; b_has_battery = true; b_has_battery_led = true; + b_has_orientation = true; m_name_string = "DualSense Pad #"; m_max_devices = CELL_PAD_MAX_PORT_NUM; @@ -252,6 +253,7 @@ void dualsense_pad_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, DualSenseKeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, DualSenseKeyCodes::None); + cfg->orientation_reset_button.def = ::at32(button_list, DualSenseKeyCodes::None); // Set default misc variables cfg->lstick_anti_deadzone.def = static_cast(0.13 * thumb_max); // 13% @@ -614,28 +616,27 @@ void dualsense_pad_handler::get_extended_info(const pad_ensemble& binding) // these values come already calibrated, all we need to do is convert to ds3 range - // gyroY is yaw, which is all that we need - //f32 gyroX = static_cast(input.gyro[0]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; - f32 gyroY = static_cast(input.gyro[1]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; - //f32 gyroZ = static_cast(input.gyro[2]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; + // gyro (angular velocity in degree/s) + const f32 gyro_x = static_cast(input.gyro[0]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; + const f32 gyro_y = static_cast(input.gyro[1]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; + const f32 gyro_z = static_cast(input.gyro[2]) / static_cast(DUALSENSE_GYRO_RES_PER_DEG_S) * -1.f; - // accel - f32 accelX = static_cast(input.accel[0]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; - f32 accelY = static_cast(input.accel[1]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; - f32 accelZ = static_cast(input.accel[2]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; + // acceleration (linear velocity in m/s²) + const f32 accel_x = static_cast(input.accel[0]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; + const f32 accel_y = static_cast(input.accel[1]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; + const f32 accel_z = static_cast(input.accel[2]) / static_cast(DUALSENSE_ACC_RES_PER_G) * -1; // now just use formula from ds3 - accelX = accelX * 113 + 512; - accelY = accelY * 113 + 512; - accelZ = accelZ * 113 + 512; + pad->m_sensors[0].m_value = Clamp0To1023(accel_x * MOTION_ONE_G + 512); + pad->m_sensors[1].m_value = Clamp0To1023(accel_y * MOTION_ONE_G + 512); + pad->m_sensors[2].m_value = Clamp0To1023(accel_z * MOTION_ONE_G + 512); + // gyro_y is yaw, which is all that we need // Convert to ds3. The ds3 resolution is 123/90°/sec. - gyroY = gyroY * (123.f / 90.f) + 512; + pad->m_sensors[3].m_value = Clamp0To1023(gyro_y * (123.f / 90.f) + 512); - pad->m_sensors[0].m_value = Clamp0To1023(accelX); - pad->m_sensors[1].m_value = Clamp0To1023(accelY); - pad->m_sensors[2].m_value = Clamp0To1023(accelZ); - pad->m_sensors[3].m_value = Clamp0To1023(gyroY); + // Set raw orientation + set_raw_orientation(pad->move_data, accel_x, accel_y, accel_z, gyro_x, gyro_y, gyro_z); } std::unordered_map dualsense_pad_handler::get_button_values(const std::shared_ptr& device) @@ -940,11 +941,8 @@ void dualsense_pad_handler::apply_pad_data(const pad_ensemble& binding) cfg_pad* config = dev->config; // Attempt to send rumble no matter what - const int idx_l = config->switch_vibration_motors ? 1 : 0; - const int idx_s = config->switch_vibration_motors ? 0 : 1; - - const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; - const u8 speed_small = config->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + const u8 speed_large = config->get_large_motor_speed(pad->m_vibrateMotors); + const u8 speed_small = config->get_small_motor_speed(pad->m_vibrateMotors); const bool wireless = dev->cable_state == 0; const bool low_battery = dev->battery_level <= 1; diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 55ab002859..d101d74173 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -34,6 +34,7 @@ evdev_joystick_handler::evdev_joystick_handler() b_has_rumble = true; b_has_motion = true; b_has_deadzones = true; + b_has_orientation = true; m_trigger_threshold = trigger_max / 2; m_thumb_threshold = thumb_max / 2; @@ -84,6 +85,7 @@ void evdev_joystick_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, NO_BUTTON); cfg->analog_limiter_button.def = ::at32(button_list, NO_BUTTON); + cfg->orientation_reset_button.def = ::at32(button_list, NO_BUTTON); // Set default misc variables cfg->lstick_anti_deadzone.def = static_cast(0.13 * thumb_max); // 13% @@ -1075,6 +1077,8 @@ void evdev_joystick_handler::get_extended_info(const pad_ensemble& binding) } } + set_raw_orientation(*pad); + if (ret < 0) { // -EAGAIN signifies no available events, not an actual *error*. @@ -1267,10 +1271,8 @@ void evdev_joystick_handler::apply_pad_data(const pad_ensemble& binding) return; // Handle vibration - const int idx_l = cfg->switch_vibration_motors ? 1 : 0; - const int idx_s = cfg->switch_vibration_motors ? 0 : 1; - const u8 force_large = cfg->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value * 257 : 0; - const u8 force_small = cfg->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value * 257 : 0; + const u8 force_large = cfg->get_large_motor_speed(pad->m_vibrateMotors); + const u8 force_small = cfg->get_small_motor_speed(pad->m_vibrateMotors); SetRumble(evdev_device, force_large, force_small); } @@ -1382,6 +1384,12 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad) pad->m_analog_limiter_button_index = static_cast(pad->m_buttons.size()) - 1; } + if (b_has_orientation) + { + pad->m_buttons.emplace_back(special_button_offset, find_buttons(cfg->orientation_reset_button), special_button_value::orientation_reset); + pad->m_orientation_reset_button_index = static_cast(pad->m_buttons.size()) - 1; + } + pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->triangle), CELL_PAD_CTRL_TRIANGLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->circle), CELL_PAD_CTRL_CIRCLE); pad->m_buttons.emplace_back(CELL_PAD_BTN_OFFSET_DIGITAL2,find_buttons(cfg->cross), CELL_PAD_CTRL_CROSS); diff --git a/rpcs3/Input/pad_thread.cpp b/rpcs3/Input/pad_thread.cpp index 0c207d6595..70f0e25618 100644 --- a/rpcs3/Input/pad_thread.cpp +++ b/rpcs3/Input/pad_thread.cpp @@ -37,7 +37,7 @@ extern std::string g_input_config_override; namespace pad { - atomic_t g_current = nullptr; + atomic_t g_pad_thread = nullptr; shared_mutex g_pad_mutex; std::string g_title_id; atomic_t g_started{false}; @@ -62,13 +62,13 @@ struct pad_setting pad_thread::pad_thread(void* curthread, void* curwindow, std::string_view title_id) : m_curthread(curthread), m_curwindow(curwindow) { pad::g_title_id = title_id; - pad::g_current = this; + pad::g_pad_thread = this; pad::g_started = false; } pad_thread::~pad_thread() { - pad::g_current = nullptr; + pad::g_pad_thread = nullptr; } void pad_thread::Init() diff --git a/rpcs3/Input/pad_thread.h b/rpcs3/Input/pad_thread.h index fe9fa015c5..1939ce0104 100644 --- a/rpcs3/Input/pad_thread.h +++ b/rpcs3/Input/pad_thread.h @@ -70,7 +70,7 @@ private: namespace pad { - extern atomic_t g_current; + extern atomic_t g_pad_thread; extern shared_mutex g_pad_mutex; extern std::string g_title_id; extern atomic_t g_enabled; @@ -78,14 +78,14 @@ namespace pad extern atomic_t g_started; extern atomic_t g_home_menu_requested; - static inline class pad_thread* get_current_handler(bool relaxed = false) + static inline class pad_thread* get_pad_thread(bool relaxed = false) { if (relaxed) { - return g_current.observe(); + return g_pad_thread.observe(); } - return ensure(g_current.load()); + return ensure(g_pad_thread.load()); } static inline void set_enabled(bool enabled) @@ -102,7 +102,7 @@ namespace pad static inline void SetIntercepted(bool intercepted) { std::lock_guard lock(g_pad_mutex); - const auto handler = get_current_handler(); + const auto handler = get_pad_thread(); handler->SetIntercepted(intercepted); } } diff --git a/rpcs3/Input/ps_move_config.cpp b/rpcs3/Input/ps_move_config.cpp index 14f9d081fe..bb9daa528a 100644 --- a/rpcs3/Input/ps_move_config.cpp +++ b/rpcs3/Input/ps_move_config.cpp @@ -7,11 +7,7 @@ cfg_ps_moves g_cfg_move; cfg_ps_moves::cfg_ps_moves() : cfg::node() -#ifdef _WIN32 - , path(fs::get_config_dir() + "config/ps_move.yml") -#else - , path(fs::get_config_dir() + "ps_move.yml") -#endif + , path(fs::get_config_dir(true) + "ps_move.yml") { } diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index 83049f5fd0..4acf46cf5f 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -127,6 +127,7 @@ ps_move_handler::ps_move_handler() b_has_battery = true; b_has_battery_led = false; b_has_pressure_intensity_button = false; + b_has_orientation = true; m_name_string = "PS Move #"; m_max_devices = 4; // CELL_GEM_MAX_NUM @@ -170,6 +171,8 @@ void ps_move_handler::init_config(cfg_pad* cfg) cfg->l2.def = ::at32(button_list, ps_move_key_codes::none); cfg->l3.def = ::at32(button_list, ps_move_key_codes::none); + cfg->orientation_reset_button.def = ::at32(button_list, ps_move_key_codes::none); + // Set default misc variables cfg->lstickdeadzone.def = 40; // between 0 and 255 cfg->rstickdeadzone.def = 40; // between 0 and 255 @@ -359,8 +362,6 @@ void ps_move_handler::check_add_device(hid_device* hidDevice, std::string_view p psmove_parse_calibration(calibration, *device); } - device->reset_orientation(); - // Activate if (send_output_report(device) == -1) { @@ -669,60 +670,75 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) // 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. - s16 accel_x = input.accel_x_1; // Increases if the device is rolled to the left - s16 accel_y = input.accel_y_1; // Increases if the device is pitched upwards - s16 accel_z = input.accel_z_1; // Increases if the device is moved upwards - s16 gyro_x = input.gyro_x_1; // Increases if the device is pitched upwards - s16 gyro_y = input.gyro_y_1; // Increases if the device is rolled to the right - s16 gyro_z = input.gyro_z_1; // Increases if the device is yawed to the left + f32 accel_x = input.accel_x_1; // Increases if the device is rolled to the left + f32 accel_y = input.accel_y_1; // Increases if the device is pitched upwards + f32 accel_z = input.accel_z_1; // Increases if the device is moved upwards + f32 gyro_x = input.gyro_x_1; // Increases if the device is pitched upwards + f32 gyro_y = input.gyro_y_1; // Increases if the device is rolled to the right + f32 gyro_z = input.gyro_z_1; // Increases if the device is yawed to the left if (dev->model == ps_move_model::ZCM1) { - accel_x -= zero_shift; - accel_y -= zero_shift; - accel_z -= zero_shift; - gyro_x -= zero_shift; - gyro_y -= zero_shift; - gyro_z -= zero_shift; - - const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1; - - #define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x)) - pad->move_data.magnetometer_x = static_cast(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2)); - pad->move_data.magnetometer_y = static_cast(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4)); - pad->move_data.magnetometer_z = static_cast(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z)); + accel_x -= static_cast(zero_shift); + accel_y -= static_cast(zero_shift); + accel_z -= static_cast(zero_shift); + gyro_x -= static_cast(zero_shift); + gyro_y -= static_cast(zero_shift); + gyro_z -= static_cast(zero_shift); } - // Apply calibration - if (dev->calibration.is_valid) + if (!device->config || !device->config->orientation_enabled) { - pad->move_data.accelerometer_x = accel_x * dev->calibration.accel_x_factor + dev->calibration.accel_x_offset; - pad->move_data.accelerometer_y = accel_y * dev->calibration.accel_y_factor + dev->calibration.accel_y_offset; - pad->move_data.accelerometer_z = accel_z * dev->calibration.accel_z_factor + dev->calibration.accel_z_offset; - pad->move_data.gyro_x = (gyro_x - dev->calibration.gyro_x_offset) * dev->calibration.gyro_x_gain; - pad->move_data.gyro_y = (gyro_y - dev->calibration.gyro_y_offset) * dev->calibration.gyro_y_gain; - pad->move_data.gyro_z = (gyro_z - dev->calibration.gyro_z_offset) * dev->calibration.gyro_z_gain; + pad->move_data.reset_sensors(); } else { - constexpr f32 MOVE_ONE_G = 4096.0f; // This is just a rough estimate and probably depends on the device + // Apply calibration + if (dev->calibration.is_valid) + { + accel_x = accel_x * dev->calibration.accel_x_factor + dev->calibration.accel_x_offset; + accel_y = accel_y * dev->calibration.accel_y_factor + dev->calibration.accel_y_offset; + accel_z = accel_z * dev->calibration.accel_z_factor + dev->calibration.accel_z_offset; + gyro_x = (gyro_x - dev->calibration.gyro_x_offset) * dev->calibration.gyro_x_gain; + gyro_y = (gyro_y - dev->calibration.gyro_y_offset) * dev->calibration.gyro_y_gain; + gyro_z = (gyro_z - dev->calibration.gyro_z_offset) * dev->calibration.gyro_z_gain; + } + else + { + constexpr f32 MOVE_ONE_G = 4096.0f; // This is just a rough estimate and probably depends on the device - pad->move_data.accelerometer_x = accel_x / MOVE_ONE_G; - pad->move_data.accelerometer_y = accel_y / MOVE_ONE_G; - pad->move_data.accelerometer_z = accel_z / MOVE_ONE_G; - pad->move_data.gyro_x = gyro_x / MOVE_ONE_G; - pad->move_data.gyro_y = gyro_y / MOVE_ONE_G; - pad->move_data.gyro_z = gyro_z / MOVE_ONE_G; + accel_x /= MOVE_ONE_G; + accel_y /= MOVE_ONE_G; + accel_z /= MOVE_ONE_G; + gyro_x /= MOVE_ONE_G; + gyro_y /= MOVE_ONE_G; + gyro_z /= MOVE_ONE_G; + } + + pad->move_data.accelerometer_x = accel_x; + pad->move_data.accelerometer_y = accel_y; + pad->move_data.accelerometer_z = accel_z; + pad->move_data.gyro_x = gyro_x; + pad->move_data.gyro_y = gyro_y; + pad->move_data.gyro_z = gyro_z; + + if (dev->model == ps_move_model::ZCM1) + { + const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1; + + #define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x)) + pad->move_data.magnetometer_x = static_cast(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2)); + pad->move_data.magnetometer_y = static_cast(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4)); + pad->move_data.magnetometer_z = static_cast(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z)); + } } pad->move_data.temperature = ((input.temperature << 4) | ((input.magnetometer_x & 0xF0) >> 4)); - pad->m_sensors[0].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_x * -1.0f)); - pad->m_sensors[1].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_y * -1.0f)); - pad->m_sensors[2].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.accelerometer_z)); - pad->m_sensors[3].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * pad->move_data.gyro_z * -1.0f)); - - dev->update_orientation(pad->move_data); + pad->m_sensors[0].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_x * -1.0f)); + pad->m_sensors[1].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_y * -1.0f)); + pad->m_sensors[2].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * accel_z)); + pad->m_sensors[3].m_value = Clamp0To1023(512.0f + (MOTION_ONE_G * gyro_z * -1.0f)); handle_external_device(binding); } @@ -804,9 +820,7 @@ void ps_move_handler::apply_pad_data(const pad_ensemble& binding) cfg_pad* config = dev->config; - const int idx_l = config->switch_vibration_motors ? 1 : 0; - - const u8 speed_large = config->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; + const u8 speed_large = config->get_large_motor_speed(pad->m_vibrateMotors); dev->new_output_data |= dev->large_motor != speed_large; dev->large_motor = speed_large; @@ -863,74 +877,3 @@ u32 ps_move_handler::get_battery_level(const std::string& padId) // 0 to 5 return std::clamp(device->battery_level * 20, 0, 100); } - -void ps_move_device::reset_orientation() -{ - // Initialize Fusion - ahrs = {}; - FusionAhrsInitialise(&ahrs); - 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, &ahrs.settings); - FusionAhrsReset(&ahrs); -} - -void ps_move_device::update_orientation(ps_move_data& move_data) -{ - if (move_data.calibration_requested) - { - reset_orientation(); - - move_data.calibration_succeeded = true; - } - - // 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 - } - }; - - static constexpr f32 PI = 3.14159265f; - const auto rad_to_degree = [](f32 radians) -> f32 { return radians * 180.0f / PI; }; - const FusionVector gyroscope{ - .axis { - .x = +rad_to_degree(move_data.gyro_x), - .y = +rad_to_degree(move_data.gyro_z), - .z = -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, gyroscope, accelerometer, magnetometer, elapsed_sec); - - // Get quaternion - const FusionQuaternion quaternion = FusionAhrsGetQuaternion(&ahrs); - 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]; -} diff --git a/rpcs3/Input/ps_move_handler.h b/rpcs3/Input/ps_move_handler.h index 4c6097fecf..d8efc463c7 100644 --- a/rpcs3/Input/ps_move_handler.h +++ b/rpcs3/Input/ps_move_handler.h @@ -146,12 +146,6 @@ public: u32 external_device_id = 0; ps_move_calibration calibration{}; - 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(); - const reports::ps_move_input_report_common& input_report_common() const; }; diff --git a/rpcs3/Input/ps_move_tracker.cpp b/rpcs3/Input/ps_move_tracker.cpp index b831e87967..d3deead687 100644 --- a/rpcs3/Input/ps_move_tracker.cpp +++ b/rpcs3/Input/ps_move_tracker.cpp @@ -15,7 +15,7 @@ namespace gem { extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, const std::vector& video_data_in, u32 width, u32 height, - u8* video_data_out, u32 video_data_out_size); + u8* video_data_out, u32 video_data_out_size, std::string_view caller); } template @@ -238,7 +238,7 @@ void ps_move_tracker::convert_image(s32 output_format) m_image_binary[index].resize(size); } - if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba))) + if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), "gemTracker")) { ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}); } diff --git a/rpcs3/Input/raw_mouse_config.cpp b/rpcs3/Input/raw_mouse_config.cpp index b02f349dac..c5284c6ef5 100644 --- a/rpcs3/Input/raw_mouse_config.cpp +++ b/rpcs3/Input/raw_mouse_config.cpp @@ -2,21 +2,7 @@ #include "raw_mouse_config.h" #include "Emu/Io/MouseHandler.h" -std::string mouse_button_id(int code) -{ - switch (code) - { - case CELL_MOUSE_BUTTON_1: return "Button 1"; - case CELL_MOUSE_BUTTON_2: return "Button 2"; - case CELL_MOUSE_BUTTON_3: return "Button 3"; - case CELL_MOUSE_BUTTON_4: return "Button 4"; - case CELL_MOUSE_BUTTON_5: return "Button 5"; - case CELL_MOUSE_BUTTON_6: return "Button 6"; - case CELL_MOUSE_BUTTON_7: return "Button 7"; - case CELL_MOUSE_BUTTON_8: return "Button 8"; - } - return ""; -} +LOG_CHANNEL(cfg_log, "CFG"); cfg::string& raw_mouse_config::get_button_by_index(int index) { @@ -50,6 +36,53 @@ cfg::string& raw_mouse_config::get_button(int code) } } +std::string raw_mouse_config::get_button_name(std::string_view value) +{ + if (raw_mouse_button_map.contains(value)) + { + return std::string(value); + } + + if (value.starts_with(key_prefix)) + { + s64 scan_code{}; + if (try_to_int64(&scan_code, value.substr(key_prefix.size()), s32{smin}, s32{smax})) + { + return get_key_name(static_cast(scan_code)); + } + } + + return ""; +} + +std::string raw_mouse_config::get_button_name(s32 button_code) +{ + for (const auto& [name, code] : raw_mouse_button_map) + { + if (code == button_code) + { + return std::string(name); + } + } + return ""; +} + +std::string raw_mouse_config::get_key_name(s32 scan_code) +{ +#ifdef _WIN32 + TCHAR name_buf[MAX_PATH] {}; + if (!GetKeyNameTextW(scan_code, name_buf, MAX_PATH)) + { + cfg_log.error("raw_mouse_config: GetKeyNameText failed: %s", fmt::win_error{GetLastError(), nullptr}); + return {}; + } + return wchar_to_utf8(name_buf); +#else + static_cast(scan_code); + return ""; +#endif +} + raw_mice_config::raw_mice_config() { for (u32 i = 0; i < ::size32(players); i++) @@ -63,7 +96,7 @@ bool raw_mice_config::load() m_mutex.lock(); bool result = false; - const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id); cfg_log.notice("Loading %s config: %s", cfg_id, cfg_name); from_default(); @@ -90,7 +123,7 @@ void raw_mice_config::save() { std::lock_guard lock(m_mutex); - const std::string cfg_name = fmt::format("%sconfig/%s.yml", fs::get_config_dir(), cfg_id); + const std::string cfg_name = fmt::format("%s%s.yml", fs::get_config_dir(true), cfg_id); cfg_log.notice("Saving %s config to '%s'", cfg_id, cfg_name); if (!fs::create_path(fs::get_parent_dir(cfg_name))) diff --git a/rpcs3/Input/raw_mouse_config.h b/rpcs3/Input/raw_mouse_config.h index b1ae83883d..2791725637 100644 --- a/rpcs3/Input/raw_mouse_config.h +++ b/rpcs3/Input/raw_mouse_config.h @@ -5,9 +5,21 @@ #include -LOG_CHANNEL(cfg_log, "CFG"); +#ifdef _WIN32 +#include +#endif -std::string mouse_button_id(int code); +static const std::map raw_mouse_button_map +{ + { "", 0 }, +#ifdef _WIN32 + { "Button 1", RI_MOUSE_BUTTON_1_UP }, + { "Button 2", RI_MOUSE_BUTTON_2_UP }, + { "Button 3", RI_MOUSE_BUTTON_3_UP }, + { "Button 4", RI_MOUSE_BUTTON_4_UP }, + { "Button 5", RI_MOUSE_BUTTON_5_UP }, +#endif +}; struct raw_mouse_config : cfg::node { @@ -29,6 +41,12 @@ public: cfg::string& get_button_by_index(int index); cfg::string& get_button(int code); + + static constexpr std::string_view key_prefix = "Key "; + + static std::string get_button_name(std::string_view value); + static std::string get_button_name(s32 button_code); + static std::string get_key_name(s32 scan_code); }; struct raw_mice_config : cfg::node diff --git a/rpcs3/Input/raw_mouse_handler.cpp b/rpcs3/Input/raw_mouse_handler.cpp index 33e0410c11..d60dfaec0b 100644 --- a/rpcs3/Input/raw_mouse_handler.cpp +++ b/rpcs3/Input/raw_mouse_handler.cpp @@ -27,6 +27,18 @@ static inline void draw_overlay_cursor(u32 index, s32 x_pos, s32 y_pos, s32 x_ma [[maybe_unused]] static inline void draw_overlay_cursor(u32, s32, s32, s32, s32) {} #endif +const std::unordered_map raw_mouse::btn_pairs = +{ + { 0, {}}, +#ifdef _WIN32 + { RI_MOUSE_BUTTON_1_UP, mouse_button{ RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP, 0, false }}, + { RI_MOUSE_BUTTON_2_UP, mouse_button{ RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP, 0, false }}, + { RI_MOUSE_BUTTON_3_UP, mouse_button{ RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP, 0, false }}, + { RI_MOUSE_BUTTON_4_UP, mouse_button{ RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP, 0, false }}, + { RI_MOUSE_BUTTON_5_UP, mouse_button{ RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP, 0, false }}, +#endif +}; + LOG_CHANNEL(input_log, "Input"); raw_mice_config g_cfg_raw_mouse; @@ -70,29 +82,26 @@ void raw_mouse::reload_config() void raw_mouse::set_index(u32 index) { m_index = index; - reload_requested = true; + m_reload_requested = true; } -std::pair raw_mouse::get_mouse_button(const cfg::string& button) +raw_mouse::mouse_button raw_mouse::get_mouse_button(const cfg::string& button) { const std::string value = button.to_string(); -#ifdef _WIN32 - static const std::unordered_map> btn_pairs - { - { 0, {}}, - { RI_MOUSE_BUTTON_1_UP, { RI_MOUSE_BUTTON_1_DOWN, RI_MOUSE_BUTTON_1_UP }}, - { RI_MOUSE_BUTTON_2_UP, { RI_MOUSE_BUTTON_2_DOWN, RI_MOUSE_BUTTON_2_UP }}, - { RI_MOUSE_BUTTON_3_UP, { RI_MOUSE_BUTTON_3_DOWN, RI_MOUSE_BUTTON_3_UP }}, - { RI_MOUSE_BUTTON_4_UP, { RI_MOUSE_BUTTON_4_DOWN, RI_MOUSE_BUTTON_4_UP }}, - { RI_MOUSE_BUTTON_5_UP, { RI_MOUSE_BUTTON_5_DOWN, RI_MOUSE_BUTTON_5_UP }}, - }; - if (const auto it = raw_mouse_button_map.find(value); it != raw_mouse_button_map.cend()) { return ::at32(btn_pairs, it->second); } -#endif + + if (value.starts_with(raw_mouse_config::key_prefix)) + { + s64 scan_code{}; + if (try_to_int64(&scan_code, value.substr(raw_mouse_config::key_prefix.size()), s32{smin}, s32{smax})) + { + return mouse_button{ 0, 0, static_cast(scan_code), true }; + } + } return {}; } @@ -134,40 +143,43 @@ void raw_mouse::update_values(const RAWMOUSE& state) // Update window handle and size update_window_handle(); - if (std::exchange(reload_requested, false)) + if (std::exchange(m_reload_requested, false)) { reload_config(); } - const auto get_button_pressed = [this](u8 button, int button_flags) + if (m_handler->is_for_gui()) { - const auto it = m_buttons.find(button); - if (it == m_buttons.cend()) return; - - const auto& [down, up] = it->second; - - // Only update the value if either down or up flags are present - if ((button_flags & down)) + for (const auto& [up, btn] : btn_pairs) { - m_handler->Button(m_index, button, true); - m_handler->mouse_press_callback(m_device_name, button, true); + // Only update the value if either down or up flags are present + if ((state.usButtonFlags & btn.down)) + { + m_handler->mouse_press_callback(m_device_name, up, true); + } + else if ((state.usButtonFlags & btn.up)) + { + m_handler->mouse_press_callback(m_device_name, up, false); + } } - else if ((button_flags & up)) - { - m_handler->Button(m_index, button, false); - m_handler->mouse_press_callback(m_device_name, button, false); - } - }; + return; + } // Get mouse buttons - get_button_pressed(CELL_MOUSE_BUTTON_1, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_2, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_3, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_4, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_5, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_6, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_7, state.usButtonFlags); - get_button_pressed(CELL_MOUSE_BUTTON_8, state.usButtonFlags); + for (const auto& [button, btn] : m_buttons) + { + if (btn.is_key) continue; + + // Only update the value if either down or up flags are present + if ((state.usButtonFlags & btn.down)) + { + m_handler->Button(m_index, button, true); + } + else if ((state.usButtonFlags & btn.up)) + { + m_handler->Button(m_index, button, false); + } + } // Get mouse wheel if ((state.usButtonFlags & RI_MOUSE_WHEEL)) @@ -237,6 +249,30 @@ void raw_mouse::update_values(const RAWMOUSE& state) } } } + +void raw_mouse::update_values(s32 scan_code, bool pressed) +{ + ensure(m_handler != nullptr); + + if (std::exchange(m_reload_requested, false)) + { + reload_config(); + } + + if (m_handler->is_for_gui()) + { + m_handler->key_press_callback(m_device_name, scan_code, pressed); + return; + } + + // Get mouse buttons + for (const auto& [button, btn] : m_buttons) + { + if (!btn.is_key || btn.scan_code != scan_code) return; + + m_handler->Button(m_index, button, pressed); + } +} #endif raw_mouse_handler::~raw_mouse_handler() @@ -416,6 +452,12 @@ void raw_mouse_handler::register_raw_input_devices() .dwFlags = 0, .hwndTarget = mouse.window_handle() }); + raw_input_devices.push_back(RAWINPUTDEVICE { + .usUsagePage = HID_USAGE_PAGE_GENERIC, + .usUsage = HID_USAGE_GENERIC_KEYBOARD, + .dwFlags = 0, + .hwndTarget = mouse.window_handle() + }); { std::lock_guard lock(g_registered_handlers_mutex); @@ -456,6 +498,13 @@ void raw_mouse_handler::unregister_raw_input_devices() const .dwFlags = RIDEV_REMOVE, .hwndTarget = nullptr }); + raw_input_devices.push_back(RAWINPUTDEVICE { + .usUsagePage = HID_USAGE_PAGE_GENERIC, + .usUsage = HID_USAGE_GENERIC_KEYBOARD, + .dwFlags = 0, + .hwndTarget = nullptr + }); + if (!RegisterRawInputDevices(raw_input_devices.data(), ::size32(raw_input_devices), sizeof(RAWINPUTDEVICE))) { input_log.error("raw_mouse_handler: RegisterRawInputDevices (unregister) failed: %s", fmt::win_error{GetLastError(), nullptr}); @@ -563,7 +612,7 @@ void raw_mouse_handler::handle_native_event(const MSG& msg) return; } - if (msg.message != WM_INPUT) + if (msg.message != WM_INPUT && msg.message != WM_KEYDOWN && msg.message != WM_KEYUP) { return; } @@ -576,32 +625,70 @@ void raw_mouse_handler::handle_native_event(const MSG& msg) RAWINPUT raw_input{}; UINT size = sizeof(RAWINPUT); - u32 res = GetRawInputData(reinterpret_cast(msg.lParam), RID_INPUT, &raw_input, &size, sizeof(RAWINPUTHEADER)); + const u32 res = GetRawInputData(reinterpret_cast(msg.lParam), RID_INPUT, &raw_input, &size, sizeof(RAWINPUTHEADER)); if (res == umax) { return; } + if ((raw_input.header.dwType == RIM_TYPEMOUSE || raw_input.header.dwType == RIM_TYPEKEYBOARD) && + g_cfg_raw_mouse.reload_requested.exchange(false)) + { + std::lock_guard lock(m_raw_mutex); + + for (auto& [handle, mouse] : m_raw_mice) + { + mouse.request_reload(); + } + } + switch (raw_input.header.dwType) { case RIM_TYPEMOUSE: { std::lock_guard lock(m_raw_mutex); - if (g_cfg_raw_mouse.reload_requested.exchange(false)) - { - for (auto& [handle, mouse] : m_raw_mice) - { - mouse.request_reload(); - } - } - if (auto it = m_raw_mice.find(raw_input.header.hDevice); it != m_raw_mice.end()) { it->second.update_values(raw_input.data.mouse); } break; } + case RIM_TYPEKEYBOARD: + { + const RAWKEYBOARD& keyboard = raw_input.data.keyboard; + + // Ignore key overrun state and keys not mapped to any virtual key code + if (keyboard.MakeCode == KEYBOARD_OVERRUN_MAKE_CODE || keyboard.VKey >= UCHAR_MAX) + { + break; + } + + WORD scan_code; + + if (keyboard.MakeCode) + { + // Compose the full scan code value with its extended byte + scan_code = MAKEWORD(keyboard.MakeCode & 0x7f, ((keyboard.Flags & RI_KEY_E0) ? 0xe0 : ((keyboard.Flags & RI_KEY_E1) ? 0xe1 : 0x00))); + } + else + { + // Scan code value may be empty for some buttons (for example multimedia buttons) + // Try to get the scan code from the virtual key code + scan_code = LOWORD(MapVirtualKey(keyboard.VKey, MAPVK_VK_TO_VSC_EX)); + } + + const LONG scan_code_extended = static_cast(MAKELPARAM(0, (HIBYTE(scan_code) ? KF_EXTENDED : 0x00) | LOBYTE(scan_code))); + const bool pressed = !(keyboard.Flags & RI_KEY_BREAK); + + std::lock_guard lock(m_raw_mutex); + + for (auto& [handle, mouse] : m_raw_mice) + { + mouse.update_values(scan_code_extended, pressed); + } + break; + } default: { break; diff --git a/rpcs3/Input/raw_mouse_handler.h b/rpcs3/Input/raw_mouse_handler.h index 155587f11e..9ab7945f20 100644 --- a/rpcs3/Input/raw_mouse_handler.h +++ b/rpcs3/Input/raw_mouse_handler.h @@ -6,22 +6,6 @@ #include "Utilities/mutex.h" #include "Utilities/Thread.h" -#ifdef _WIN32 -#include -#endif - -static const std::map raw_mouse_button_map -{ - { "", 0 }, -#ifdef _WIN32 - { "Button 1", RI_MOUSE_BUTTON_1_UP }, - { "Button 2", RI_MOUSE_BUTTON_2_UP }, - { "Button 3", RI_MOUSE_BUTTON_3_UP }, - { "Button 4", RI_MOUSE_BUTTON_4_UP }, - { "Button 5", RI_MOUSE_BUTTON_5_UP }, -#endif -}; - class raw_mouse_handler; class raw_mouse @@ -38,16 +22,27 @@ public: #ifdef _WIN32 void update_values(const RAWMOUSE& state); + void update_values(s32 scan_code, bool pressed); #endif const std::string& device_name() const { return m_device_name; } u32 index() const { return m_index; } void set_index(u32 index); - void request_reload() { reload_requested = true; } + void request_reload() { m_reload_requested = true; } private: + struct mouse_button + { + int down = 0; + int up = 0; + s32 scan_code = 0; + bool is_key = false; + }; + + static const std::unordered_map btn_pairs; + void reload_config(); - static std::pair get_mouse_button(const cfg::string& button); + static mouse_button get_mouse_button(const cfg::string& button); u32 m_index = 0; std::string m_device_name; @@ -61,8 +56,8 @@ private: int m_pos_y{}; float m_mouse_acceleration = 1.0f; raw_mouse_handler* m_handler{}; - std::map> m_buttons; - bool reload_requested = false; + std::map m_buttons; + bool m_reload_requested = false; }; class raw_mouse_handler final : public MouseHandlerBase @@ -74,10 +69,8 @@ public: void Init(const u32 max_connect) override; - void SetIsForGui(bool value) - { - m_is_for_gui = value; - } + void set_is_for_gui(bool value) { m_is_for_gui = value; } + bool is_for_gui() const { return m_is_for_gui; } const std::map& get_mice() const { return m_raw_mice; }; @@ -86,11 +79,24 @@ public: m_mouse_press_callback = std::move(cb); } - void mouse_press_callback(const std::string& device_name, s32 cell_code, bool pressed) + void mouse_press_callback(const std::string& device_name, s32 button_code, bool pressed) { if (m_mouse_press_callback) { - m_mouse_press_callback(device_name, cell_code, pressed); + m_mouse_press_callback(device_name, button_code, pressed); + } + } + + void set_key_press_callback(std::function cb) + { + m_key_press_callback = std::move(cb); + } + + void key_press_callback(const std::string& device_name, s32 scan_code, bool pressed) + { + if (m_key_press_callback) + { + m_key_press_callback(device_name, scan_code, pressed); } } @@ -115,6 +121,7 @@ private: bool m_is_for_gui = false; std::map m_raw_mice; std::function m_mouse_press_callback; + std::function m_key_press_callback; std::unique_ptr>> m_thread; }; diff --git a/rpcs3/Input/sdl_pad_handler.cpp b/rpcs3/Input/sdl_pad_handler.cpp index aaf95331f1..562085f037 100644 --- a/rpcs3/Input/sdl_pad_handler.cpp +++ b/rpcs3/Input/sdl_pad_handler.cpp @@ -179,6 +179,7 @@ sdl_pad_handler::sdl_pad_handler() : PadHandlerBase(pad_handler::sdl) b_has_rgb = true; b_has_battery = true; b_has_battery_led = true; + b_has_orientation = true; m_trigger_threshold = trigger_max / 2; m_thumb_threshold = thumb_max / 2; @@ -233,6 +234,7 @@ void sdl_pad_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, SDLKeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, SDLKeyCodes::None); + cfg->orientation_reset_button.def = ::at32(button_list, SDLKeyCodes::None); // Set default misc variables cfg->lstick_anti_deadzone.def = static_cast(0.13 * thumb_max); // 13% @@ -732,14 +734,14 @@ void sdl_pad_handler::get_extended_info(const pad_ensemble& binding) } else { - const float& accel_x = dev->values_accel[0]; // Angular speed around the x axis (pitch) - const float& accel_y = dev->values_accel[1]; // Angular speed around the y axis (yaw) - const float& accel_z = dev->values_accel[2]; // Angular speed around the z axis (roll + const f32 accel_x = dev->values_accel[0]; // Angular speed around the x axis (pitch) + const f32 accel_y = dev->values_accel[1]; // Angular speed around the y axis (yaw) + const f32 accel_z = dev->values_accel[2]; // Angular speed around the z axis (roll // Convert to ds3. The ds3 resolution is 113/G. - pad->m_sensors[0].m_value = Clamp0To1023((accel_x / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); - pad->m_sensors[1].m_value = Clamp0To1023((accel_y / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); - pad->m_sensors[2].m_value = Clamp0To1023((accel_z / SDL_STANDARD_GRAVITY) * -1 * 113 + 512); + pad->m_sensors[0].m_value = Clamp0To1023((accel_x / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512); + pad->m_sensors[1].m_value = Clamp0To1023((accel_y / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512); + pad->m_sensors[2].m_value = Clamp0To1023((accel_z / SDL_STANDARD_GRAVITY) * -1 * MOTION_ONE_G + 512); } } @@ -751,16 +753,20 @@ void sdl_pad_handler::get_extended_info(const pad_ensemble& binding) } else { - //const float& gyro_x = dev->values_gyro[0]; // Angular speed around the x axis (pitch) - const float& gyro_y = dev->values_gyro[1]; // Angular speed around the y axis (yaw) - //const float& gyro_z = dev->values_gyro[2]; // Angular speed around the z axis (roll) + //const f32 gyro_x = dev->values_gyro[0]; // Angular speed around the x axis (pitch) + const f32 gyro_y = dev->values_gyro[1]; // Angular speed around the y axis (yaw) + //const f32 gyro_z = dev->values_gyro[2]; // Angular speed around the z axis (roll) // Convert to ds3. The ds3 resolution is 123/90°/sec. The SDL gyro is measured in rad/sec. - static constexpr f32 PI = 3.14159265f; - const float degree = (gyro_y * 180.0f / PI); + const f32 degree = rad_to_degree(gyro_y); pad->m_sensors[3].m_value = Clamp0To1023(degree * (123.f / 90.f) + 512); } } + + if (dev->sdl.has_accel || dev->sdl.has_gyro) + { + set_raw_orientation(*pad); + } } void sdl_pad_handler::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& sensors) @@ -794,13 +800,10 @@ void sdl_pad_handler::apply_pad_data(const pad_ensemble& binding) // The left motor is the low-frequency rumble motor. The right motor is the high-frequency rumble motor. // The two motors are not the same, and they create different vibration effects. Values range between 0 to 65535. - const usz idx_l = cfg->switch_vibration_motors ? 1 : 0; - const usz idx_s = cfg->switch_vibration_motors ? 0 : 1; - if (dev->sdl.has_rumble || dev->sdl.has_rumble_triggers) { - const u8 speed_large = cfg->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; - const u8 speed_small = cfg->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + const u8 speed_large = cfg->get_large_motor_speed(pad->m_vibrateMotors); + const u8 speed_small = cfg->get_small_motor_speed(pad->m_vibrateMotors); dev->new_output_data |= dev->large_motor != speed_large || dev->small_motor != speed_small; diff --git a/rpcs3/Input/sdl_pad_handler.h b/rpcs3/Input/sdl_pad_handler.h index f4fa256d29..a375f8a07f 100644 --- a/rpcs3/Input/sdl_pad_handler.h +++ b/rpcs3/Input/sdl_pad_handler.h @@ -46,8 +46,8 @@ public: bool has_accel = false; bool has_gyro = false; - float data_rate_accel = 0.0f; - float data_rate_gyro = 0.0f; + f32 data_rate_accel = 0.0f; + f32 data_rate_gyro = 0.0f; std::set button_ids; std::set axis_ids; @@ -57,8 +57,8 @@ public: sdl_info sdl{}; - std::array values_accel{}; - std::array values_gyro{}; + std::array values_accel{}; + std::array values_gyro{}; bool led_needs_update = true; bool led_is_on = true; diff --git a/rpcs3/Input/skateboard_pad_handler.cpp b/rpcs3/Input/skateboard_pad_handler.cpp index fbf1937562..86996847d6 100644 --- a/rpcs3/Input/skateboard_pad_handler.cpp +++ b/rpcs3/Input/skateboard_pad_handler.cpp @@ -84,6 +84,7 @@ skateboard_pad_handler::skateboard_pad_handler() b_has_battery = false; b_has_battery_led = false; b_has_pressure_intensity_button = false; + b_has_orientation = true; m_name_string = "Skateboard #"; m_max_devices = CELL_PAD_MAX_PORT_NUM; @@ -134,6 +135,8 @@ void skateboard_pad_handler::init_config(cfg_pad* cfg) cfg->tilt_left.def = ::at32(button_list, skateboard_key_codes::tilt_left); cfg->tilt_right.def = ::at32(button_list, skateboard_key_codes::tilt_right); + cfg->orientation_reset_button.def = ::at32(button_list, skateboard_key_codes::none); + // Set default misc variables cfg->lstick_anti_deadzone.def = 0; cfg->rstick_anti_deadzone.def = 0; @@ -323,6 +326,8 @@ void skateboard_pad_handler::get_extended_info(const pad_ensemble& binding) pad->m_sensors[1].m_value = Clamp0To1023(input.large_axes[1]); pad->m_sensors[2].m_value = Clamp0To1023(input.large_axes[2]); pad->m_sensors[3].m_value = Clamp0To1023(input.large_axes[3]); + + set_raw_orientation(*pad); } pad_preview_values skateboard_pad_handler::get_preview_values(const std::unordered_map& /*data*/) diff --git a/rpcs3/Input/xinput_pad_handler.cpp b/rpcs3/Input/xinput_pad_handler.cpp index 5ee7c4fe23..49c2467faf 100644 --- a/rpcs3/Input/xinput_pad_handler.cpp +++ b/rpcs3/Input/xinput_pad_handler.cpp @@ -64,6 +64,7 @@ xinput_pad_handler::xinput_pad_handler() : PadHandlerBase(pad_handler::xinput) b_has_deadzones = true; b_has_battery = true; b_has_battery_led = false; + b_has_orientation = false; m_name_string = "XInput Pad #"; m_max_devices = XUSER_MAX_COUNT; @@ -119,6 +120,7 @@ void xinput_pad_handler::init_config(cfg_pad* cfg) cfg->pressure_intensity_button.def = ::at32(button_list, XInputKeyCodes::None); cfg->analog_limiter_button.def = ::at32(button_list, XInputKeyCodes::None); + cfg->orientation_reset_button.def = ::at32(button_list, XInputKeyCodes::None); // Set default misc variables cfg->lstick_anti_deadzone.def = static_cast(0.13 * thumb_max); // 13% @@ -413,6 +415,8 @@ bool xinput_pad_handler::Init() } } + b_has_orientation = !!xinputGetCustomData; + if (!m_is_init) return false; @@ -551,6 +555,8 @@ void xinput_pad_handler::get_extended_info(const pad_ensemble& binding) pad->m_sensors[1].m_value = sensors.SCP_ACCEL_Y; pad->m_sensors[2].m_value = sensors.SCP_ACCEL_Z; pad->m_sensors[3].m_value = sensors.SCP_GYRO; + + set_raw_orientation(*pad); } } } @@ -569,11 +575,8 @@ void xinput_pad_handler::apply_pad_data(const pad_ensemble& binding) // The left motor is the low-frequency rumble motor. The right motor is the high-frequency rumble motor. // The two motors are not the same, and they create different vibration effects. Values range between 0 to 65535. - const usz idx_l = cfg->switch_vibration_motors ? 1 : 0; - const usz idx_s = cfg->switch_vibration_motors ? 0 : 1; - - const u8 speed_large = cfg->enable_vibration_motor_large ? pad->m_vibrateMotors[idx_l].m_value : 0; - const u8 speed_small = cfg->enable_vibration_motor_small ? pad->m_vibrateMotors[idx_s].m_value : 0; + const u8 speed_large = cfg->get_large_motor_speed(pad->m_vibrateMotors); + const u8 speed_small = cfg->get_small_motor_speed(pad->m_vibrateMotors); dev->new_output_data |= dev->large_motor != speed_large || dev->small_motor != speed_small; diff --git a/rpcs3/VKGSRender.vcxproj b/rpcs3/VKGSRender.vcxproj index 435e11e423..868ff9ad66 100644 --- a/rpcs3/VKGSRender.vcxproj +++ b/rpcs3/VKGSRender.vcxproj @@ -36,6 +36,7 @@ + @@ -84,6 +85,7 @@ + diff --git a/rpcs3/VKGSRender.vcxproj.filters b/rpcs3/VKGSRender.vcxproj.filters index bc2791f16a..153a21a9b0 100644 --- a/rpcs3/VKGSRender.vcxproj.filters +++ b/rpcs3/VKGSRender.vcxproj.filters @@ -72,6 +72,7 @@ upscalers\fsr1 + @@ -173,6 +174,7 @@ vkutils + diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 0c54d02978..bbb69c30b2 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -66,6 +66,7 @@ true + @@ -139,6 +140,7 @@ + @@ -668,6 +670,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 90df56b77e..23c7c34fb6 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1327,6 +1327,12 @@ Emu\Audio + + Emu\Cell + + + Emu\GPU\RSX\Program + @@ -2683,6 +2689,9 @@ Emu\Audio + + Emu\GPU\RSX\Program + diff --git a/rpcs3/headless_application.cpp b/rpcs3/headless_application.cpp index b12b6d87e2..e82bcc12dc 100644 --- a/rpcs3/headless_application.cpp +++ b/rpcs3/headless_application.cpp @@ -120,6 +120,7 @@ void headless_application::InitializeCallbacks() return nullptr; }; + callbacks.close_gs_frame = [](){}; callbacks.get_gs_frame = []() -> std::unique_ptr { if (g_cfg.video.renderer != video_renderer::null) diff --git a/rpcs3/main.cpp b/rpcs3/main.cpp index 404fa66eb9..894115ecc8 100644 --- a/rpcs3/main.cpp +++ b/rpcs3/main.cpp @@ -111,6 +111,66 @@ extern char **environ; LOG_CHANNEL(sys_log, "SYS"); LOG_CHANNEL(q_debug, "QDEBUG"); +#ifdef _WIN32 +std::set get_one_drive_paths() +{ + std::set paths; + + // NOTE: Disabled. The environment variables can lead to false positives. + //for (const char* key : { "OneDrive", "OneDriveConsumer", "OneDriveCommercial" }) + //{ + // if (const char* env_path = std::getenv(key)) + // { + // sys_log.notice("get_one_drive_paths: Found OneDrive env path: '%s' (key='%s')", env_path, key); + // paths.insert(env_path); + // } + //} + + for (const wchar_t* key : { L"Software\\Microsoft\\OneDrive\\Accounts\\Personal" }) + { + HKEY hkey = NULL; + LSTATUS status = RegOpenKeyW(HKEY_CURRENT_USER, key, &hkey); + if (status != ERROR_SUCCESS) + { + sys_log.trace("get_one_drive_paths: RegOpenKeyW failed: %s (key='%s')", fmt::win_error{static_cast(status), nullptr}, wchar_to_utf8(key)); + continue; + } + + std::wstring path_buffer; + DWORD type = 0U; + + do + { + path_buffer.resize(path_buffer.size() + MAX_PATH); + DWORD buffer_size = static_cast(path_buffer.size() - 1); + status = RegQueryValueExW(hkey, L"UserFolder", NULL, &type, reinterpret_cast(path_buffer.data()), &buffer_size); + } + while (status == ERROR_MORE_DATA); + + const LSTATUS close_status = RegCloseKey(hkey); + if (close_status != ERROR_SUCCESS) + { + sys_log.error("get_one_drive_paths: RegCloseKey failed: %s", fmt::win_error{static_cast(close_status), nullptr}); + } + + if (status != ERROR_SUCCESS) + { + sys_log.trace("get_one_drive_paths: RegQueryValueExW failed: %s", fmt::win_error{static_cast(status), nullptr}); + continue; + } + + if ((type == REG_SZ) || (type == REG_EXPAND_SZ) || (type == REG_MULTI_SZ)) + { + const std::string path = wchar_to_utf8(path_buffer.data()); + sys_log.notice("get_one_drive_paths: Found OneDrive registry path: '%s' (key='%s')", path, wchar_to_utf8(key)); + paths.insert(path); + } + } + + return paths; +} +#endif + [[noreturn]] extern void report_fatal_error(std::string_view _text, bool is_html = false, bool include_help_text = true) { #ifdef __linux__ @@ -510,7 +570,7 @@ int main(int argc, char** argv) } const std::string lock_name = fs::get_cache_dir() + "RPCS3.buf"; - const std::string log_name = fs::get_cache_dir() + "RPCS3.log"; + const std::string log_name = fs::get_log_dir() + "RPCS3.log"; static fs::file instance_lock; @@ -1135,6 +1195,21 @@ int main(int argc, char** argv) return 1; } } + +#ifdef _WIN32 + // Check OneDrive locations + for (const std::string& one_drive_path : get_one_drive_paths()) + { + if (Emu.IsPathInsideDir(emu_dir, one_drive_path)) + { + report_fatal_error(QObject::tr( + "RPCS3 should never be run from a OneDrive path!\n" + "Please move RPCS3 to a location not synced by OneDrive.\n" + "Current location:\n%0").arg(QString::fromStdString(emu_dir)).toStdString()); + return 1; + } + } +#endif } // Set timerslack value for Linux. The default value is 50,000ns. Change this to just 1 since we value precise timers. diff --git a/rpcs3/main_application.cpp b/rpcs3/main_application.cpp index 0040bb235e..d86d6523b2 100644 --- a/rpcs3/main_application.cpp +++ b/rpcs3/main_application.cpp @@ -133,7 +133,7 @@ EmuCallbacks main_application::CreateCallbacks() basic_keyboard_handler* ret = g_fxo->init(Emu.DeserialManager()); ensure(ret); ret->moveToThread(get_thread()); - ret->SetTargetWindow(m_game_window); + ret->SetTargetWindow(reinterpret_cast(m_game_window)); break; } } @@ -170,7 +170,7 @@ EmuCallbacks main_application::CreateCallbacks() basic_mouse_handler* ret = g_fxo->init(Emu.DeserialManager()); ensure(ret); ret->moveToThread(get_thread()); - ret->SetTargetWindow(m_game_window); + ret->SetTargetWindow(reinterpret_cast(m_game_window)); break; } case mouse_handler::raw: diff --git a/rpcs3/main_application.h b/rpcs3/main_application.h index e378eb5c53..dd0806970d 100644 --- a/rpcs3/main_application.h +++ b/rpcs3/main_application.h @@ -1,9 +1,10 @@ #pragma once #include -#include +#include struct EmuCallbacks; +class gs_frame; class main_application { @@ -25,5 +26,5 @@ protected: EmuCallbacks CreateCallbacks(); std::string m_active_user; - QWindow* m_game_window = nullptr; // (Currently) only needed so that pad handlers have a valid target for event filtering. + gs_frame* m_game_window = nullptr; }; diff --git a/rpcs3/rpcs3qt/basic_mouse_settings_dialog.cpp b/rpcs3/rpcs3qt/basic_mouse_settings_dialog.cpp index f56ec6ec5c..e98d2798a4 100644 --- a/rpcs3/rpcs3qt/basic_mouse_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/basic_mouse_settings_dialog.cpp @@ -190,6 +190,26 @@ void basic_mouse_settings_dialog::on_button_click(int id) m_remap_timer.start(1000); } +void basic_mouse_settings_dialog::keyPressEvent(QKeyEvent* event) +{ + if (m_button_id < 0) + { + // We are not remapping a button, so pass the event to the base class. + QDialog::keyPressEvent(event); + return; + } + + const std::string name = keyboard_pad_handler::GetKeyName(event, false); + g_cfg_mouse.get_button(m_button_id).from_string(name); + + if (auto button = m_buttons->button(m_button_id)) + { + button->setText(QString::fromStdString(name)); + } + + reactivate_buttons(); +} + void basic_mouse_settings_dialog::mouseReleaseEvent(QMouseEvent* event) { if (m_button_id < 0) diff --git a/rpcs3/rpcs3qt/basic_mouse_settings_dialog.h b/rpcs3/rpcs3qt/basic_mouse_settings_dialog.h index ab7dd48450..c432143185 100644 --- a/rpcs3/rpcs3qt/basic_mouse_settings_dialog.h +++ b/rpcs3/rpcs3qt/basic_mouse_settings_dialog.h @@ -39,6 +39,7 @@ private: QTimer m_remap_timer; protected: + void keyPressEvent(QKeyEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override; }; diff --git a/rpcs3/rpcs3qt/cheat_manager.cpp b/rpcs3/rpcs3qt/cheat_manager.cpp index 27e0860232..d5af8a1a2f 100644 --- a/rpcs3/rpcs3qt/cheat_manager.cpp +++ b/rpcs3/rpcs3qt/cheat_manager.cpp @@ -43,6 +43,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case cheat_type::signed_16_cheat: return "Signed 16 bits"; case cheat_type::signed_32_cheat: return "Signed 32 bits"; case cheat_type::signed_64_cheat: return "Signed 64 bits"; + case cheat_type::float_32_cheat: return "Float 32 bits"; case cheat_type::max: break; } @@ -612,8 +613,9 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) return; } - bool success; - u64 result_value; + bool success = false; + u64 result_value {}; + f64 result_value_f {}; switch (cheat->type) { @@ -625,6 +627,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) case cheat_type::signed_16_cheat: result_value = cheat_engine::get_value(final_offset, success); break; case cheat_type::signed_32_cheat: result_value = cheat_engine::get_value(final_offset, success); break; case cheat_type::signed_64_cheat: result_value = cheat_engine::get_value(final_offset, success); break; + case cheat_type::float_32_cheat: result_value_f = cheat_engine::get_value(final_offset, success); break; default: log_cheat.fatal("Unsupported cheat type"); return; } @@ -632,6 +635,8 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) { if (cheat->type >= cheat_type::signed_8_cheat && cheat->type <= cheat_type::signed_64_cheat) edt_value_final->setText(tr("%1").arg(static_cast(result_value))); + else if (cheat->type == cheat_type::float_32_cheat) + edt_value_final->setText(tr("%1").arg(result_value_f)); else edt_value_final->setText(tr("%1").arg(result_value)); } @@ -795,6 +800,7 @@ cheat_manager_dialog::cheat_manager_dialog(QWidget* parent) case cheat_type::signed_16_cheat: results = convert_and_set(final_offset); break; case cheat_type::signed_32_cheat: results = convert_and_set(final_offset); break; case cheat_type::signed_64_cheat: results = convert_and_set(final_offset); break; + case cheat_type::float_32_cheat: results = convert_and_set(final_offset); break; default: log_cheat.fatal("Unsupported cheat type"); return; } @@ -888,8 +894,6 @@ cheat_manager_dialog* cheat_manager_dialog::get_dlg(QWidget* parent) template T cheat_manager_dialog::convert_from_QString(const QString& str, bool& success) { - T result; - if constexpr (std::is_same_v) { const u16 result_16 = str.toUShort(&success); @@ -897,17 +901,17 @@ T cheat_manager_dialog::convert_from_QString(const QString& str, bool& success) if (result_16 > 0xFF) success = false; - result = static_cast(result_16); + return static_cast(result_16); } if constexpr (std::is_same_v) - result = str.toUShort(&success); + return str.toUShort(&success); if constexpr (std::is_same_v) - result = str.toUInt(&success); + return str.toUInt(&success); if constexpr (std::is_same_v) - result = str.toULongLong(&success); + return str.toULongLong(&success); if constexpr (std::is_same_v) { @@ -915,28 +919,31 @@ T cheat_manager_dialog::convert_from_QString(const QString& str, bool& success) if (result_16 < -128 || result_16 > 127) success = false; - result = static_cast(result_16); + return static_cast(result_16); } if constexpr (std::is_same_v) - result = str.toShort(&success); + return str.toShort(&success); if constexpr (std::is_same_v) - result = str.toInt(&success); + return str.toInt(&success); if constexpr (std::is_same_v) - result = str.toLongLong(&success); + return str.toLongLong(&success); - return result; + if constexpr (std::is_same_v) + return str.toFloat(&success); + + return {}; } template bool cheat_manager_dialog::convert_and_search() { - bool res_conv; + bool res_conv = false; const QString to_search = edt_cheat_search_value->text(); - T value = convert_from_QString(to_search, res_conv); + const T value = convert_from_QString(to_search, res_conv); if (!res_conv) return false; @@ -948,10 +955,10 @@ bool cheat_manager_dialog::convert_and_search() template std::pair cheat_manager_dialog::convert_and_set(u32 offset) { - bool res_conv; + bool res_conv = false; const QString to_set = edt_value_final->text(); - T value = convert_from_QString(to_set, res_conv); + const T value = convert_from_QString(to_set, res_conv); if (!res_conv) return {false, false}; @@ -974,6 +981,7 @@ void cheat_manager_dialog::do_the_search() case cheat_type::signed_16_cheat: res_conv = convert_and_search(); break; case cheat_type::signed_32_cheat: res_conv = convert_and_search(); break; case cheat_type::signed_64_cheat: res_conv = convert_and_search(); break; + case cheat_type::float_32_cheat: res_conv = convert_and_search(); break; default: log_cheat.fatal("Unsupported cheat type"); break; } @@ -1065,6 +1073,7 @@ QString cheat_manager_dialog::get_localized_cheat_type(cheat_type type) case cheat_type::signed_16_cheat: return tr("Signed 16 bits"); case cheat_type::signed_32_cheat: return tr("Signed 32 bits"); case cheat_type::signed_64_cheat: return tr("Signed 64 bits"); + case cheat_type::float_32_cheat: return tr("Float 32 bits"); case cheat_type::max: break; } std::string type_formatted; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 2b4d4a0b87..eb6439243a 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -93,7 +93,7 @@ void emu_settings::LoadSettings(const std::string& title_id, bool create_config_ m_title_id = title_id; // Create config path if necessary - fs::create_path(title_id.empty() ? fs::get_config_dir() : rpcs3::utils::get_custom_config_dir()); + fs::create_path(title_id.empty() ? fs::get_config_dir(true) : rpcs3::utils::get_custom_config_dir()); // Load default config auto [default_config, default_error] = yaml_load(g_cfg_defaults); @@ -113,7 +113,7 @@ void emu_settings::LoadSettings(const std::string& title_id, bool create_config_ if (create_config_from_global) { // Add global config - const std::string global_config_path = fs::get_config_dir() + "config.yml"; + const std::string global_config_path = fs::get_config_dir(true) + "config.yml"; fs::g_tls_error = fs::error::ok; fs::file config(global_config_path, fs::read + fs::create); auto [global_config, global_error] = yaml_load(config ? config.to_string() : ""); diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index a038fa8c84..1bc86e307f 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -178,10 +178,10 @@ enum class emu_settings_type UseNativeInterface, ShowShaderCompilationHint, ShowPPUCompilationHint, + ShowAutosaveAutoloadHint, ShowPressureIntensityToggleHint, ShowAnalogLimiterToggleHint, ShowMouseAndKeyboardToggleHint, - ShowAutosaveAutoloadHint, WindowTitleFormat, PauseDuringHomeMenu, @@ -377,13 +377,13 @@ inline static const std::map settings_location { emu_settings_type::UseNativeInterface, { "Miscellaneous", "Use native user interface"}}, { emu_settings_type::ShowShaderCompilationHint, { "Miscellaneous", "Show shader compilation hint"}}, { emu_settings_type::ShowPPUCompilationHint, { "Miscellaneous", "Show PPU compilation hint"}}, + { emu_settings_type::ShowAutosaveAutoloadHint, { "Miscellaneous", "Show autosave/autoload hint"}}, { emu_settings_type::ShowPressureIntensityToggleHint, { "Miscellaneous", "Show pressure intensity toggle hint"}}, { emu_settings_type::ShowAnalogLimiterToggleHint, { "Miscellaneous", "Show analog limiter toggle hint"}}, { emu_settings_type::ShowMouseAndKeyboardToggleHint, { "Miscellaneous", "Show mouse and keyboard toggle hint"}}, { emu_settings_type::SilenceAllLogs, { "Miscellaneous", "Silence All Logs" }}, { emu_settings_type::WindowTitleFormat, { "Miscellaneous", "Window Title Format" }}, { emu_settings_type::PauseDuringHomeMenu, { "Miscellaneous", "Pause Emulation During Home Menu" }}, - { emu_settings_type::ShowAutosaveAutoloadHint, { "Miscellaneous", "Show autosave/autoload hint" }}, // Networking { emu_settings_type::InternetStatus, { "Net", "Internet enabled"}}, diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp index 835f940797..aee4c44c77 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp @@ -1,6 +1,8 @@ #include "stdafx.h" #include "emulated_pad_settings_dialog.h" #include "localized_emu.h" +#include "Input/raw_mouse_config.h" +#include "Emu/Io/mouse_config.h" #include "Emu/Io/buzz_config.h" #include "Emu/Io/gem_config.h" #include "Emu/Io/ghltar_config.h" @@ -13,6 +15,7 @@ #include #include +#include #include #include #include @@ -92,6 +95,10 @@ emulated_pad_settings_dialog::emulated_pad_settings_dialog(pad_type type, QWidge setWindowTitle(tr("Configure Emulated PS Move (Fake)")); add_tabs(tabs); break; + case emulated_pad_settings_dialog::pad_type::mousegem: + setWindowTitle(tr("Configure Emulated PS Move (Mouse)")); + add_tabs(tabs); + break; case emulated_pad_settings_dialog::pad_type::guncon3: setWindowTitle(tr("Configure Emulated GunCon 3")); add_tabs(tabs); @@ -116,8 +123,12 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) { ensure(!!tabs); - constexpr u32 max_items_per_column = 6; - int count = static_cast(T::count); + std::set ignored_values; + + const auto remove_value = [&ignored_values](int value) + { + ignored_values.insert(static_cast(value)); + }; usz players = 0; switch (m_type) @@ -137,13 +148,29 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::gem: players = g_cfg_gem_real.players.size(); - // Ignore x and y axis - static_assert(static_cast(gem_btn::y_axis) == static_cast(gem_btn::count) - 1); - static_assert(static_cast(gem_btn::x_axis) == static_cast(gem_btn::count) - 2); - count -= 2; + // Ignore combo, x and y axis + remove_value(static_cast(gem_btn::x_axis)); + remove_value(static_cast(gem_btn::y_axis)); + for (int i = static_cast(gem_btn::combo_begin); i <= static_cast(gem_btn::combo_end); i++) + { + remove_value(i); + } break; case pad_type::ds3gem: players = g_cfg_gem_fake.players.size(); + + // Ignore combo + for (int i = static_cast(gem_btn::combo_begin); i <= static_cast(gem_btn::combo_end); i++) + { + remove_value(i); + } + break; + case pad_type::mousegem: + players = g_cfg_gem_mouse.players.size(); + + // Ignore x and y axis + remove_value(static_cast(gem_btn::x_axis)); + remove_value(static_cast(gem_btn::y_axis)); break; case pad_type::guncon3: players = g_cfg_guncon3.players.size(); @@ -156,6 +183,8 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) break; } + constexpr u32 max_items_per_column = 6; + const int count = static_cast(T::count) - static_cast(ignored_values.size()); int rows = count; for (u32 cols = 1; utils::aligned_div(static_cast(count), cols) > max_items_per_column;) @@ -165,13 +194,30 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) m_combos.resize(players); + const bool show_mouse_legend = m_type == pad_type::mousegem; + + if (show_mouse_legend) + { + if (!g_cfg_mouse.load()) + { + cfg_log.notice("Could not restore mouse config. Using defaults."); + } + + if (!g_cfg_raw_mouse.load()) + { + cfg_log.notice("Could not restore raw mouse config. Using defaults."); + } + } + for (usz player = 0; player < players; player++) { - QWidget* widget = new QWidget(this); + // Create grid with all buttons QGridLayout* grid_layout = new QGridLayout(this); - for (int i = 0, row = 0, col = 0; i < count; i++, row++) + for (int i = 0, row = 0, col = 0; i < static_cast(T::count); i++) { + if (ignored_values.contains(i)) continue; + const T id = static_cast(i); const QString name = QString::fromStdString(fmt::format("%s", id)); @@ -179,16 +225,35 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) QGroupBox* gb = new QGroupBox(name, this); QComboBox* combo = new QComboBox; - for (int p = 0; p < static_cast(pad_button::pad_button_max_enum); p++) + if constexpr (std::is_same_v) { - const QString translated = localized_emu::translated_pad_button(static_cast(p)); - combo->addItem(translated); - const int index = combo->findText(translated); - combo->setItemData(index, p, button_role::button); - combo->setItemData(index, i, button_role::emulated_button); + const gem_btn btn = static_cast(i); + if (btn >= gem_btn::combo_begin && btn <= gem_btn::combo_end) + { + gb->setToolTip(tr("Press the \"Combo\" button in combination with any of the other combo buttons to trigger their related PS Move button.\n" + "This can be useful if your device does not have enough regular buttons.")); + } } - if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) + // Add empty value + combo->addItem(""); + const int index = combo->findText(""); + combo->setItemData(index, static_cast(pad_button::pad_button_max_enum), button_role::button); + combo->setItemData(index, i, button_role::emulated_button); + + if (m_type != pad_type::mousegem) + { + for (int p = 0; p < static_cast(pad_button::pad_button_max_enum); p++) + { + const QString translated = localized_emu::translated_pad_button(static_cast(p)); + combo->addItem(translated); + const int index = combo->findText(translated); + combo->setItemData(index, p, button_role::button); + combo->setItemData(index, i, button_role::emulated_button); + } + } + + if (std::is_same_v || std::is_same_v || std::is_same_v || m_type == pad_type::mousegem) { for (int p = static_cast(pad_button::mouse_button_1); p <= static_cast(pad_button::mouse_button_8); p++) { @@ -221,6 +286,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::ds3gem: saved_btn_id = ::at32(g_cfg_gem_fake.players, player)->get_pad_button(static_cast(id)); break; + case pad_type::mousegem: + saved_btn_id = ::at32(g_cfg_gem_mouse.players, player)->get_pad_button(static_cast(id)); + break; case pad_type::guncon3: saved_btn_id = ::at32(g_cfg_guncon3.players, player)->get_pad_button(static_cast(id)); break; @@ -265,6 +333,9 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) case pad_type::ds3gem: ::at32(g_cfg_gem_fake.players, player)->set_button(static_cast(id), btn_id); break; + case pad_type::mousegem: + ::at32(g_cfg_gem_mouse.players, player)->set_button(static_cast(id), btn_id); + break; case pad_type::guncon3: ::at32(g_cfg_guncon3.players, player)->set_button(static_cast(id), btn_id); break; @@ -287,9 +358,60 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) h_layout->addWidget(combo); gb->setLayout(h_layout); grid_layout->addWidget(gb, row, col); + + row++; } - widget->setLayout(grid_layout); + QVBoxLayout* v_layout = new QVBoxLayout(this); + + // Create a legend of the current mouse settings + if (show_mouse_legend) + { + QHBoxLayout* legend_layout = new QHBoxLayout(this); + if (player == 0) + { + std::string basic_mouse_settings; + fmt::append(basic_mouse_settings, "1: %s\n", g_cfg_mouse.mouse_button_1.to_string()); + fmt::append(basic_mouse_settings, "2: %s\n", g_cfg_mouse.mouse_button_2.to_string()); + fmt::append(basic_mouse_settings, "3: %s\n", g_cfg_mouse.mouse_button_3.to_string()); + fmt::append(basic_mouse_settings, "4: %s\n", g_cfg_mouse.mouse_button_4.to_string()); + fmt::append(basic_mouse_settings, "5: %s\n", g_cfg_mouse.mouse_button_5.to_string()); + fmt::append(basic_mouse_settings, "6: %s\n", g_cfg_mouse.mouse_button_6.to_string()); + fmt::append(basic_mouse_settings, "7: %s\n", g_cfg_mouse.mouse_button_7.to_string()); + fmt::append(basic_mouse_settings, "8: %s", g_cfg_mouse.mouse_button_8.to_string()); + + QGroupBox* gb_legend_basic = new QGroupBox(tr("Current Basic Mouse Config"), this); + QVBoxLayout* gb_legend_basic_layout = new QVBoxLayout(this); + gb_legend_basic_layout->addWidget(new QLabel(QString::fromStdString(basic_mouse_settings), this)); + gb_legend_basic->setLayout(gb_legend_basic_layout); + legend_layout->addWidget(gb_legend_basic); + } + { + std::string raw_mouse_settings; + const auto& raw_cfg = *ensure(::at32(g_cfg_raw_mouse.players, player)); + fmt::append(raw_mouse_settings, "1: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_1.to_string())); + fmt::append(raw_mouse_settings, "2: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_2.to_string())); + fmt::append(raw_mouse_settings, "3: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_3.to_string())); + fmt::append(raw_mouse_settings, "4: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_4.to_string())); + fmt::append(raw_mouse_settings, "5: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_5.to_string())); + fmt::append(raw_mouse_settings, "6: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_6.to_string())); + fmt::append(raw_mouse_settings, "7: %s\n", raw_mouse_config::get_button_name(raw_cfg.mouse_button_7.to_string())); + fmt::append(raw_mouse_settings, "8: %s", raw_mouse_config::get_button_name(raw_cfg.mouse_button_8.to_string())); + + QGroupBox* gb_legend_raw = new QGroupBox(tr("Current Raw Mouse Config"), this); + QVBoxLayout* gb_legend_raw_layout = new QVBoxLayout(this); + gb_legend_raw_layout->addWidget(new QLabel(QString::fromStdString(raw_mouse_settings), this)); + gb_legend_raw->setLayout(gb_legend_raw_layout); + legend_layout->addWidget(gb_legend_raw); + } + v_layout->addLayout(legend_layout); + } + + v_layout->addLayout(grid_layout); + + QWidget* widget = new QWidget(this); + widget->setLayout(v_layout); + tabs->addTab(widget, tr("Player %0").arg(player + 1)); } } @@ -334,6 +456,12 @@ void emulated_pad_settings_dialog::load_config() cfg_log.notice("Could not load fake gem config. Using defaults."); } break; + case emulated_pad_settings_dialog::pad_type::mousegem: + if (!g_cfg_gem_mouse.load()) + { + cfg_log.notice("Could not load mouse gem config. Using defaults."); + } + break; case emulated_pad_settings_dialog::pad_type::guncon3: if (!g_cfg_guncon3.load()) { @@ -377,6 +505,9 @@ void emulated_pad_settings_dialog::save_config() case emulated_pad_settings_dialog::pad_type::ds3gem: g_cfg_gem_fake.save(); break; + case emulated_pad_settings_dialog::pad_type::mousegem: + g_cfg_gem_mouse.save(); + break; case emulated_pad_settings_dialog::pad_type::guncon3: g_cfg_guncon3.save(); break; @@ -411,6 +542,9 @@ void emulated_pad_settings_dialog::reset_config() case emulated_pad_settings_dialog::pad_type::ds3gem: g_cfg_gem_fake.from_default(); break; + case emulated_pad_settings_dialog::pad_type::mousegem: + g_cfg_gem_mouse.from_default(); + break; case emulated_pad_settings_dialog::pad_type::guncon3: g_cfg_guncon3.from_default(); break; @@ -454,6 +588,9 @@ void emulated_pad_settings_dialog::reset_config() case pad_type::ds3gem: def_btn_id = ::at32(g_cfg_gem_fake.players, player)->default_pad_button(static_cast(data.toInt())); break; + case pad_type::mousegem: + def_btn_id = ::at32(g_cfg_gem_mouse.players, player)->default_pad_button(static_cast(data.toInt())); + break; case pad_type::guncon3: def_btn_id = ::at32(g_cfg_guncon3.players, player)->default_pad_button(static_cast(data.toInt())); break; diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h index 772f467182..ad1b863d7e 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.h @@ -21,6 +21,7 @@ public: usio, gem, ds3gem, + mousegem, guncon3, topshotelite, topshotfearmaster, diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 02ca16716e..1699837ae3 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -1315,14 +1315,14 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) { remove_menu->addSeparator(); - QAction* remove_savestate = remove_menu->addAction(tr("&Remove Savestate")); + QAction* remove_savestate = remove_menu->addAction(tr("&Remove Savestates")); remove_savestate->setEnabled(!is_current_running_game); connect(remove_savestate, &QAction::triggered, [this, current_game, savestate_dir]() { if (is_game_running(current_game.serial)) return; - if (QMessageBox::question(this, tr("Confirm Removal"), tr("Remove savestate?")) != QMessageBox::Yes) + if (QMessageBox::question(this, tr("Confirm Removal"), tr("Remove savestates?")) != QMessageBox::Yes) return; RemoveContentPath(savestate_dir, "savestate"); @@ -1768,7 +1768,7 @@ void game_list_frame::ShowContextMenu(const QPoint &pos) QCheckBox* disc = new QCheckBox(tr("Remove title from game list (Disc Game path is not removed!)")); QCheckBox* caches = new QCheckBox(tr("Remove caches and custom configs")); QCheckBox* icons = new QCheckBox(tr("Remove icons and shortcuts")); - QCheckBox* savestate = new QCheckBox(tr("Remove savestate")); + QCheckBox* savestate = new QCheckBox(tr("Remove savestates")); QCheckBox* captures = new QCheckBox(tr("Remove captures")); QCheckBox* recordings = new QCheckBox(tr("Remove recordings")); QCheckBox* screenshots = new QCheckBox(tr("Remove screenshots")); diff --git a/rpcs3/rpcs3qt/gl_gs_frame.cpp b/rpcs3/rpcs3qt/gl_gs_frame.cpp index 1e3f6f8fc4..94b6aa964f 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.cpp +++ b/rpcs3/rpcs3qt/gl_gs_frame.cpp @@ -28,6 +28,11 @@ gl_gs_frame::gl_gs_frame(QScreen* screen, const QRect& geometry, const QIcon& ap show(); } +void gl_gs_frame::reset() +{ + m_primary_context = nullptr; +} + draw_context_t gl_gs_frame::make_context() { auto context = new GLContext(); diff --git a/rpcs3/rpcs3qt/gl_gs_frame.h b/rpcs3/rpcs3qt/gl_gs_frame.h index bc6e9ad65e..d1129f8e3a 100644 --- a/rpcs3/rpcs3qt/gl_gs_frame.h +++ b/rpcs3/rpcs3qt/gl_gs_frame.h @@ -20,6 +20,7 @@ private: public: explicit gl_gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, std::shared_ptr gui_settings, bool force_fullscreen); + void reset() override; draw_context_t make_context() override; void set_current(draw_context_t ctx) override; void delete_context(draw_context_t ctx) override; diff --git a/rpcs3/rpcs3qt/gs_frame.cpp b/rpcs3/rpcs3qt/gs_frame.cpp index 8af0b84757..5a9da7dad6 100644 --- a/rpcs3/rpcs3qt/gs_frame.cpp +++ b/rpcs3/rpcs3qt/gs_frame.cpp @@ -73,6 +73,7 @@ gs_frame::gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, , m_initial_geometry(geometry) , m_gui_settings(std::move(gui_settings)) , m_start_games_fullscreen(force_fullscreen) + , m_renderer(g_cfg.video.renderer) { load_gui_settings(); @@ -328,6 +329,9 @@ void gs_frame::handle_shortcut(gui::shortcuts::shortcut shortcut_key, const QKey { Emu.Restart(); }; + + // Make sure we keep the game window opened + Emu.SetContinuousMode(true); } Emu.Kill(false, true); @@ -602,6 +606,11 @@ void gs_frame::close() gui_log.notice("Closing game window"); + if (m_ignore_stop_events) + { + return; + } + Emu.CallFromMainThread([this]() { // Hide window if necessary @@ -623,6 +632,10 @@ void gs_frame::close() }); } +void gs_frame::reset() +{ +} + bool gs_frame::shown() { return QWindow::isVisible(); @@ -1134,6 +1147,11 @@ bool gs_frame::event(QEvent* ev) gui_log.notice("Game window close event issued"); + if (m_ignore_stop_events) + { + return QWindow::event(ev); + } + if (Emu.IsStopped()) { // This should be unreachable, but never say never. Properly close the window anyway. diff --git a/rpcs3/rpcs3qt/gs_frame.h b/rpcs3/rpcs3qt/gs_frame.h index 97a777360c..8d6316bd88 100644 --- a/rpcs3/rpcs3qt/gs_frame.h +++ b/rpcs3/rpcs3qt/gs_frame.h @@ -15,6 +15,7 @@ #include class gui_settings; +enum class video_renderer; class gs_frame : public QWindow, public GSFrameBase { @@ -45,6 +46,7 @@ private: u32 m_hide_mouse_idletime = 2000; // ms bool m_flip_showed_frame = false; bool m_start_games_fullscreen = false; + bool m_ignore_stop_events = false; std::shared_ptr m_video_encoder{}; @@ -52,6 +54,10 @@ public: explicit gs_frame(QScreen* screen, const QRect& geometry, const QIcon& appIcon, std::shared_ptr gui_settings, bool force_fullscreen); ~gs_frame(); + video_renderer renderer() const { return m_renderer; }; + + void ignore_stop_events() { m_ignore_stop_events = true; } + draw_context_t make_context() override; void set_current(draw_context_t context) override; void delete_context(draw_context_t context) override; @@ -76,10 +82,13 @@ public: void take_screenshot(std::vector data, u32 sshot_width, u32 sshot_height, bool is_bgra) override; protected: + video_renderer m_renderer; + void paintEvent(QPaintEvent *event) override; void showEvent(QShowEvent *event) override; void close() override; + void reset() override; bool shown() override; void hide() override; diff --git a/rpcs3/rpcs3qt/gui_application.cpp b/rpcs3/rpcs3qt/gui_application.cpp index 0a3ff110ce..9f5dc53cb7 100644 --- a/rpcs3/rpcs3qt/gui_application.cpp +++ b/rpcs3/rpcs3qt/gui_application.cpp @@ -348,6 +348,34 @@ void gui_application::InitializeConnects() std::unique_ptr gui_application::get_gs_frame() { + // Load AppIcon + const QIcon app_icon = m_main_window ? m_main_window->GetAppIcon() : gui::utils::get_app_icon_from_path(Emu.GetBoot(), Emu.GetTitleID()); + + if (m_game_window) + { + // Check if the continuous mode is enabled. We reset the mode after each use in order to ensure that it is only used when explicitly needed. + const bool continuous_mode_enabled = Emu.ContinuousModeEnabled(true); + + // Make sure we run the same config + const bool is_same_renderer = m_game_window->renderer() == g_cfg.video.renderer; + + if (is_same_renderer && (Emu.IsChildProcess() || continuous_mode_enabled)) + { + gui_log.notice("gui_application: Re-using old game window (IsChildProcess=%d, ContinuousModeEnabled=%d)", Emu.IsChildProcess(), continuous_mode_enabled); + + if (!app_icon.isNull()) + { + m_game_window->setIcon(app_icon); + } + return std::unique_ptr(m_game_window); + } + + // Clean-up old game window. This should only happen if the renderer changed or there was an unexpected error during boot. + Emu.GetCallbacks().close_gs_frame(); + } + + gui_log.notice("gui_application: Creating new game window"); + extern const std::unordered_map, value_hash> g_video_out_resolution_map; auto [w, h] = ::at32(g_video_out_resolution_map, g_cfg.video.resolution); @@ -424,9 +452,6 @@ std::unique_ptr gui_application::get_gs_frame() frame_geometry.setSize(QSize(w, h)); } - // Load AppIcon - const QIcon app_icon = m_main_window ? m_main_window->GetAppIcon() : gui::utils::get_app_icon_from_path(Emu.GetBoot(), Emu.GetTitleID()); - gs_frame* frame = nullptr; switch (g_cfg.video.renderer.get()) @@ -446,6 +471,12 @@ std::unique_ptr gui_application::get_gs_frame() m_game_window = frame; + connect(m_game_window, &gs_frame::destroyed, this, [this]() + { + gui_log.notice("gui_application: Deleting old game window"); + m_game_window = nullptr; + }); + return std::unique_ptr(frame); } @@ -539,6 +570,16 @@ void gui_application::InitializeCallbacks() return nullptr; }; + callbacks.close_gs_frame = [this]() + { + if (m_game_window) + { + gui_log.warning("gui_application: Closing old game window"); + m_game_window->ignore_stop_events(); + delete m_game_window; + m_game_window = nullptr; + } + }; callbacks.get_gs_frame = [this]() -> std::unique_ptr { return get_gs_frame(); }; callbacks.get_msg_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; callbacks.get_osk_dialog = [this]() -> std::shared_ptr { return m_show_gui ? std::make_shared() : nullptr; }; @@ -582,10 +623,10 @@ void gui_application::InitializeCallbacks() { switch (type) { - case 0: static_cast(m_game_window)->progress_reset(value); break; - case 1: static_cast(m_game_window)->progress_increment(value); break; - case 2: static_cast(m_game_window)->progress_set_limit(value); break; - case 3: static_cast(m_game_window)->progress_set_value(value); break; + case 0: m_game_window->progress_reset(value); break; + case 1: m_game_window->progress_increment(value); break; + case 2: m_game_window->progress_set_limit(value); break; + case 3: m_game_window->progress_set_value(value); break; default: gui_log.fatal("Unknown type in handle_taskbar_progress(type=%d, value=%d)", type, value); break; } } @@ -770,7 +811,7 @@ void gui_application::InitializeCallbacks() verbose_message += ". "; } - verbose_message += "If Stuck, Report To Developers"; + verbose_message += tr("If Stuck, Report To Developers").toStdString(); } else { @@ -1045,7 +1086,7 @@ void gui_application::OnShortcutChange() { if (m_game_window) { - static_cast(m_game_window)->update_shortcuts(); + m_game_window->update_shortcuts(); } } @@ -1074,7 +1115,7 @@ void gui_application::OnAppStateChanged(Qt::ApplicationState state) } const auto emu_state = Emu.GetStatus(); - const bool is_active = state == Qt::ApplicationActive; + const bool is_active = state & Qt::ApplicationActive; if (emu_state != system_state::paused && emu_state != system_state::running) { @@ -1146,7 +1187,7 @@ bool gui_application::native_event_filter::nativeEventFilter([[maybe_unused]] co if (eventType == "windows_generic_MSG") { - if (MSG* msg = static_cast(message); msg && msg->message == WM_INPUT) + if (MSG* msg = static_cast(message); msg && (msg->message == WM_INPUT || msg->message == WM_KEYDOWN || msg->message == WM_KEYUP)) { if (auto* handler = g_fxo->try_get(); handler && handler->type == mouse_handler::raw) { diff --git a/rpcs3/rpcs3qt/localized_emu.cpp b/rpcs3/rpcs3qt/localized_emu.cpp index 4fb2b4b9f9..bfc0b52101 100644 --- a/rpcs3/rpcs3qt/localized_emu.cpp +++ b/rpcs3/rpcs3qt/localized_emu.cpp @@ -36,14 +36,14 @@ QString localized_emu::translated_pad_button(pad_button btn) case pad_button::rs_x: return tr("Right Stick X-Axis"); case pad_button::rs_y: return tr("Right Stick Y-Axis"); case pad_button::pad_button_max_enum: return ""; - case pad_button::mouse_button_1: return tr("Mouse Button 1"); - case pad_button::mouse_button_2: return tr("Mouse Button 2"); - case pad_button::mouse_button_3: return tr("Mouse Button 3"); - case pad_button::mouse_button_4: return tr("Mouse Button 4"); - case pad_button::mouse_button_5: return tr("Mouse Button 5"); - case pad_button::mouse_button_6: return tr("Mouse Button 6"); - case pad_button::mouse_button_7: return tr("Mouse Button 7"); - case pad_button::mouse_button_8: return tr("Mouse Button 8"); + case pad_button::mouse_button_1: return tr("Mouse 1"); + case pad_button::mouse_button_2: return tr("Mouse 2"); + case pad_button::mouse_button_3: return tr("Mouse 3"); + case pad_button::mouse_button_4: return tr("Mouse 4"); + case pad_button::mouse_button_5: return tr("Mouse 5"); + case pad_button::mouse_button_6: return tr("Mouse 6"); + case pad_button::mouse_button_7: return tr("Mouse 7"); + case pad_button::mouse_button_8: return tr("Mouse 8"); } return ""; } diff --git a/rpcs3/rpcs3qt/localized_emu.h b/rpcs3/rpcs3qt/localized_emu.h index 8ea21f6d03..13541ce65a 100644 --- a/rpcs3/rpcs3qt/localized_emu.h +++ b/rpcs3/rpcs3qt/localized_emu.h @@ -165,8 +165,8 @@ private: case localized_string_id::CELL_SAVEDATA_DELETE: return tr("Delete this data?\n\n%0", "Savedata entry info").arg(std::forward(args)...); case localized_string_id::CELL_SAVEDATA_LOAD: return tr("Load this data?\n\n%0", "Savedata entry info").arg(std::forward(args)...); case localized_string_id::CELL_SAVEDATA_OVERWRITE: return tr("Do you want to overwrite the saved data?\n\n%0", "Savedata entry info").arg(std::forward(args)...); - case localized_string_id::CELL_SAVEDATA_AUTOSAVE: return tr("Saving"); - case localized_string_id::CELL_SAVEDATA_AUTOLOAD: return tr("Loading"); + case localized_string_id::CELL_SAVEDATA_AUTOSAVE: return tr("Saving..."); + case localized_string_id::CELL_SAVEDATA_AUTOLOAD: return tr("Loading..."); case localized_string_id::CELL_CROSS_CONTROLLER_MSG: return tr("Start [%0] on the PS Vita system.\nIf you have not installed [%0], go to [Remote Play] on the PS Vita system and start [Cross-Controller] from the LiveArea™ screen.", "Cross-Controller message").arg(std::forward(args)...); case localized_string_id::CELL_CROSS_CONTROLLER_FW_MSG: return tr("If your system software version on the PS Vita system is earlier than 1.80, you must update the system software to the latest version.", "Cross-Controller firmware message"); case localized_string_id::CELL_NP_RECVMESSAGE_DIALOG_TITLE: return tr("Select Message", "RECVMESSAGE_DIALOG"); @@ -314,6 +314,7 @@ private: case localized_string_id::PROGRESS_DIALOG_OF: return tr("of"); case localized_string_id::PROGRESS_DIALOG_PLEASE_WAIT: return tr("Please wait"); case localized_string_id::PROGRESS_DIALOG_STOPPING_PLEASE_WAIT: return tr("Stopping. Please wait..."); + case localized_string_id::PROGRESS_DIALOG_SAVESTATE_PLEASE_WAIT: return tr("Creating savestate. Please wait..."); case localized_string_id::PROGRESS_DIALOG_SCANNING_PPU_EXECUTABLE: return tr("Scanning PPU Executable..."); case localized_string_id::PROGRESS_DIALOG_ANALYZING_PPU_EXECUTABLE: return tr("Analyzing PPU Executable..."); case localized_string_id::PROGRESS_DIALOG_SCANNING_PPU_MODULES: return tr("Scanning PPU Modules..."); diff --git a/rpcs3/rpcs3qt/log_frame.cpp b/rpcs3/rpcs3qt/log_frame.cpp index cde2803a30..a318a9c03d 100644 --- a/rpcs3/rpcs3qt/log_frame.cpp +++ b/rpcs3/rpcs3qt/log_frame.cpp @@ -157,7 +157,7 @@ log_frame::log_frame(std::shared_ptr _gui_settings, QWidget* paren setWidget(m_tabWidget); // Open or create TTY.log - m_tty_file.open(fs::get_cache_dir() + "TTY.log", fs::read + fs::create); + m_tty_file.open(fs::get_log_dir() + "TTY.log", fs::read + fs::create); CreateAndConnectActions(); LoadSettings(); diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index f499cd6474..e23d9b32bc 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -2590,6 +2590,9 @@ void main_window::CreateConnects() { Emu.Restart(); }; + + // Make sure we keep the game window opened + Emu.SetContinuousMode(true); } Emu.Kill(false, true); @@ -2673,8 +2676,8 @@ void main_window::CreateConnects() return; } - const std::string archived_path = fs::get_cache_dir() + "RPCS3.log.gz"; - const std::string raw_file_path = fs::get_cache_dir() + "RPCS3.log"; + const std::string archived_path = fs::get_log_dir() + "RPCS3.log.gz"; + const std::string raw_file_path = fs::get_log_dir() + "RPCS3.log"; fs::stat_t raw_stat{}; fs::stat_t archived_stat{}; @@ -2908,6 +2911,12 @@ void main_window::CreateConnects() dlg->show(); }); + connect(ui->confPSMoveMouseAct, &QAction::triggered, this, [this] + { + emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::mousegem, this); + dlg->show(); + }); + connect(ui->confPSMoveDS3Act, &QAction::triggered, this, [this] { emulated_pad_settings_dialog* dlg = new emulated_pad_settings_dialog(emulated_pad_settings_dialog::pad_type::ds3gem, this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index ba29696974..adb66d8bcf 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -244,6 +244,7 @@ + @@ -1333,6 +1334,11 @@ PS Move (Fake) + + + PS Move (Mouse) + + GunCon 3 diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 1fbd63aad5..2a2194d1b6 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -312,6 +312,7 @@ void pad_settings_dialog::InitButtons() insert_button(button_ids::id_pressure_intensity, ui->b_pressure_intensity); insert_button(button_ids::id_analog_limiter, ui->b_analog_limiter); + insert_button(button_ids::id_orientation_reset, ui->b_orientation_reset); m_pad_buttons->addButton(ui->b_refresh, button_ids::id_refresh); m_pad_buttons->addButton(ui->b_addConfig, button_ids::id_add_config_file); @@ -331,33 +332,25 @@ void pad_settings_dialog::InitButtons() } }); - connect(ui->chb_vibration_large, &QCheckBox::clicked, this, [this](bool checked) + connect(ui->sb_vibration_large, &QSpinBox::valueChanged, this, [this](int value) { - if (!checked) - { - return; - } + const u8 force = static_cast(std::clamp(m_max_force * (value / 100.0f), 0.0f, 255.0f)); + ui->chb_vibration_switch->isChecked() ? SetPadData(m_min_force, force) + : SetPadData(force, m_min_force); - ui->chb_vibration_switch->isChecked() ? SetPadData(m_min_force, m_max_force) - : SetPadData(m_max_force, m_min_force); - - QTimer::singleShot(300, [this]() + QTimer::singleShot(300, this, [this]() { SetPadData(m_min_force, m_min_force); }); }); - connect(ui->chb_vibration_small, &QCheckBox::clicked, this, [this](bool checked) + connect(ui->sb_vibration_small, &QSpinBox::valueChanged, this, [this](int value) { - if (!checked) - { - return; - } + const u8 force = static_cast(std::clamp(m_max_force * (value / 100.0f), 0.0f, 255.0f)); + ui->chb_vibration_switch->isChecked() ? SetPadData(force, m_min_force) + : SetPadData(m_min_force, force); - ui->chb_vibration_switch->isChecked() ? SetPadData(m_max_force, m_min_force) - : SetPadData(m_min_force, m_max_force); - - QTimer::singleShot(300, [this]() + QTimer::singleShot(300, this, [this]() { SetPadData(m_min_force, m_min_force); }); @@ -368,12 +361,12 @@ void pad_settings_dialog::InitButtons() checked ? SetPadData(m_min_force, m_max_force) : SetPadData(m_max_force, m_min_force); - QTimer::singleShot(200, [this, checked]() + QTimer::singleShot(200, this, [this, checked]() { checked ? SetPadData(m_max_force, m_min_force) : SetPadData(m_min_force, m_max_force); - QTimer::singleShot(200, [this]() + QTimer::singleShot(200, this, [this]() { SetPadData(m_min_force, m_min_force); }); @@ -617,12 +610,12 @@ void pad_settings_dialog::RefreshPads() } } -void pad_settings_dialog::SetPadData(u32 large_motor, u32 small_motor, bool led_battery_indicator) +void pad_settings_dialog::SetPadData(u8 large_motor, u8 small_motor, bool led_battery_indicator) { - ensure(m_handler); const cfg_pad& cfg = GetPlayerConfig(); std::lock_guard lock(m_handler_mutex); + ensure(m_handler); m_handler->SetPadData(m_device_name, GetPlayerIndex(), large_motor, small_motor, cfg.colorR, cfg.colorG, cfg.colorB, cfg.player_led_enabled.get(), led_battery_indicator, cfg.led_battery_indicator_brightness); } @@ -720,6 +713,7 @@ void pad_settings_dialog::ReloadButtons() updateButton(button_ids::id_pressure_intensity, ui->b_pressure_intensity, &cfg.pressure_intensity_button); updateButton(button_ids::id_analog_limiter, ui->b_analog_limiter, &cfg.analog_limiter_button); + updateButton(button_ids::id_orientation_reset, ui->b_orientation_reset, &cfg.orientation_reset_button); UpdateLabels(true); } @@ -996,25 +990,26 @@ void pad_settings_dialog::mouseMoveEvent(QMouseEvent* event) } else { + constexpr int delta_threshold = 20; const QPoint mouse_pos = QCursor::pos(); const int delta_x = mouse_pos.x() - m_last_pos.x(); const int delta_y = mouse_pos.y() - m_last_pos.y(); u32 key = 0; - if (delta_x > 100) + if (delta_x > delta_threshold) { key = mouse::move_right; } - else if (delta_x < -100) + else if (delta_x < -delta_threshold) { key = mouse::move_left; } - else if (delta_y > 100) + else if (delta_y > delta_threshold) { key = mouse::move_down; } - else if (delta_y < -100) + else if (delta_y < -delta_threshold) { key = mouse::move_up; } @@ -1113,8 +1108,12 @@ void pad_settings_dialog::UpdateLabels(bool is_reset) } } - ui->chb_vibration_large->setChecked(cfg.enable_vibration_motor_large.get()); - ui->chb_vibration_small->setChecked(cfg.enable_vibration_motor_small.get()); + ui->sb_vibration_large->setRange(cfg.multiplier_vibration_motor_large.min, cfg.multiplier_vibration_motor_large.max); + ui->sb_vibration_large->setValue(cfg.multiplier_vibration_motor_large.get()); + + ui->sb_vibration_small->setRange(cfg.multiplier_vibration_motor_small.min, cfg.multiplier_vibration_motor_small.max); + ui->sb_vibration_small->setValue(cfg.multiplier_vibration_motor_small.get()); + ui->chb_vibration_switch->setChecked(cfg.switch_vibration_motors.get()); // Update Trigger Thresholds @@ -1194,6 +1193,9 @@ void pad_settings_dialog::UpdateLabels(bool is_reset) RepaintPreviewLabel(ui->preview_stick_left, ui->slider_stick_left->value(), ui->anti_deadzone_slider_stick_left->value(), ui->slider_stick_left->size().width(), m_lx, m_ly, cfg.lpadsquircling, cfg.lstickmultiplier / 100.0); RepaintPreviewLabel(ui->preview_stick_right, ui->slider_stick_right->value(), ui->anti_deadzone_slider_stick_right->value(), ui->slider_stick_right->size().width(), m_rx, m_ry, cfg.rpadsquircling, cfg.rstickmultiplier / 100.0); + // Update orientation toggle + ui->cb_orientation_toggle->setChecked(cfg.orientation_enabled.get()); + // Update analog limiter toggle mode ui->cb_analog_limiter_toggle_mode->setChecked(cfg.analog_limiter_toggle_mode.get()); @@ -1248,6 +1250,7 @@ void pad_settings_dialog::SwitchButtons(bool is_enabled) ui->gb_pressure_intensity_deadzone->setEnabled(is_enabled); ui->gb_pressure_intensity->setEnabled(is_enabled && m_enable_pressure_intensity_button); ui->gb_analog_limiter->setEnabled(is_enabled && m_enable_analog_limiter_button); + ui->gb_orientation_reset->setEnabled(is_enabled && m_enable_orientation_reset_button); ui->gb_vibration->setEnabled(is_enabled && m_enable_rumble); ui->gb_motion_controls->setEnabled(is_enabled && m_enable_motion); ui->gb_stick_deadzones->setEnabled(is_enabled && m_enable_deadzones); @@ -1441,10 +1444,6 @@ void pad_settings_dialog::ChangeHandler() } ui->l_description->setText(m_description); - // Update parameters - m_min_force = 0; - m_max_force = 255; - // Reset parameters m_lx = 0; m_ly = 0; @@ -1466,11 +1465,15 @@ void pad_settings_dialog::ChangeHandler() // Enable Analog Limiter Settings m_enable_analog_limiter_button = m_handler->has_analog_limiter_button(); + // Enable Orientation Reset Settings + m_enable_orientation_reset_button = m_handler->has_orientation(); + // Change our contextual widgets ui->left_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0); ui->right_stack->setCurrentIndex((m_handler->m_type == pad_handler::keyboard) ? 1 : 0); ui->gb_pressure_intensity->setVisible(m_handler->has_pressure_intensity_button()); ui->gb_analog_limiter->setVisible(m_handler->has_analog_limiter_button()); + ui->gb_orientation_reset->setVisible(m_handler->has_orientation()); // Update device dropdown and block signals while doing so ui->chooseDevice->blockSignals(true); @@ -1828,8 +1831,11 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) for (const auto& [id, button] : m_cfg_entries) { // Let's ignore special keys, unless we're using a keyboard - if ((id == button_ids::id_pressure_intensity || id == button_ids::id_analog_limiter) && m_handler->m_type != pad_handler::keyboard) + if (m_handler->m_type != pad_handler::keyboard && + (id == button_ids::id_pressure_intensity || id == button_ids::id_analog_limiter || id == button_ids::id_orientation_reset)) + { continue; + } for (const std::string& key : cfg_pad::get_buttons(button.keys)) { @@ -1859,8 +1865,8 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) if (m_handler->has_rumble()) { - cfg.enable_vibration_motor_large.set(ui->chb_vibration_large->isChecked()); - cfg.enable_vibration_motor_small.set(ui->chb_vibration_small->isChecked()); + cfg.multiplier_vibration_motor_large.set(ui->sb_vibration_large->value()); + cfg.multiplier_vibration_motor_small.set(ui->sb_vibration_small->value()); cfg.switch_vibration_motors.set(ui->chb_vibration_switch->isChecked()); } @@ -1885,6 +1891,11 @@ void pad_settings_dialog::ApplyCurrentPlayerConfig(int new_player_id) cfg.pressure_intensity_toggle_mode.set(ui->cb_pressure_intensity_toggle_mode->isChecked()); } + if (m_handler->has_orientation()) + { + cfg.orientation_enabled.set(ui->cb_orientation_toggle->isChecked()); + } + cfg.pressure_intensity_deadzone.set(ui->pressure_intensity_deadzone->value()); if (m_handler->m_type == pad_handler::keyboard) @@ -2006,7 +2017,7 @@ bool pad_settings_dialog::GetIsLddPad(u32 index) const if (!Emu.IsStopped() && (m_title_id.empty() || m_title_id == Emu.GetTitleID())) { std::lock_guard lock(pad::g_pad_mutex); - if (const auto handler = pad::get_current_handler(true)) + if (const auto handler = pad::get_pad_thread(true)) { ensure(index < handler->GetPads().size()); @@ -2076,6 +2087,7 @@ void pad_settings_dialog::SubscribeTooltips() // Localized tooltips const Tooltips tooltips; + SubscribeTooltip(ui->gb_orientation_reset, tooltips.gamepad_settings.orientation_reset); SubscribeTooltip(ui->gb_analog_limiter, tooltips.gamepad_settings.analog_limiter); SubscribeTooltip(ui->gb_pressure_intensity, tooltips.gamepad_settings.pressure_intensity); SubscribeTooltip(ui->gb_pressure_intensity_deadzone, tooltips.gamepad_settings.pressure_deadzone); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.h b/rpcs3/rpcs3qt/pad_settings_dialog.h index 1e80ff65f7..c87bf70f95 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.h +++ b/rpcs3/rpcs3qt/pad_settings_dialog.h @@ -66,6 +66,7 @@ class pad_settings_dialog : public QDialog id_pressure_intensity, // Special button for pressure intensity id_analog_limiter, // Special button for analog limiter + id_orientation_reset, // Special button for orientation reset id_pad_end, // end @@ -123,6 +124,7 @@ private: bool m_enable_motion{ false }; bool m_enable_pressure_intensity_button{ true }; bool m_enable_analog_limiter_button{ true }; + bool m_enable_orientation_reset_button{ true }; // Button Mapping QButtonGroup* m_pad_buttons = nullptr; @@ -137,8 +139,8 @@ private: int m_ry = 0; // Rumble - s32 m_min_force = 0; - s32 m_max_force = 0; + static constexpr u8 m_min_force = 0; + static constexpr u8 m_max_force = 255; // Backup for standard button palette QPalette m_palette; @@ -188,7 +190,7 @@ private: void CancelExit(); // Set vibrate data while keeping the current color - void SetPadData(u32 large_motor, u32 small_motor, bool led_battery_indicator = false); + void SetPadData(u8 large_motor, u8 small_motor, bool led_battery_indicator = false); /** Update all the Button Labels with current button mapping */ void UpdateLabels(bool is_reset = false); diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.ui b/rpcs3/rpcs3qt/pad_settings_dialog.ui index 86add99c28..0ff621c6a2 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.ui +++ b/rpcs3/rpcs3qt/pad_settings_dialog.ui @@ -36,9 +36,9 @@ 0 - -71 - 1290 - 907 + 0 + 1292 + 902 @@ -669,6 +669,57 @@ + + + + Orientation Reset + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + - + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::MinimumExpanding + + + + 0 + 0 + + + + + + + + Orientation + + + + + + @@ -695,12 +746,12 @@ - + - Qt::Horizontal + Qt::Orientation::Horizontal - QSizePolicy::MinimumExpanding + QSizePolicy::Policy::MinimumExpanding @@ -886,7 +937,7 @@ Enable Vibration - + 5 @@ -900,22 +951,22 @@ 5 - - - Large + + + % - - true + + Large - - - Small + + + % - - true + + Small @@ -2286,7 +2337,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2316,7 +2367,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2364,7 +2415,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -2391,7 +2442,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal diff --git a/rpcs3/rpcs3qt/qt_camera_handler.cpp b/rpcs3/rpcs3qt/qt_camera_handler.cpp index 88d8c15963..4703cf12b6 100644 --- a/rpcs3/rpcs3qt/qt_camera_handler.cpp +++ b/rpcs3/rpcs3qt/qt_camera_handler.cpp @@ -113,6 +113,7 @@ void qt_camera_handler::handle_camera_active(bool is_active) void qt_camera_handler::handle_camera_error(QCamera::Error error, const QString& errorString) { camera_log.error("Error event: \"%s\" (error=%d)", errorString, static_cast(error)); + set_state(camera_handler_state::closed); } void qt_camera_handler::open_camera() diff --git a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp index 3cfccb9755..eaacc9c524 100644 --- a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp @@ -11,6 +11,8 @@ #include #include +LOG_CHANNEL(cfg_log, "CFG"); + constexpr u32 button_count = 8; raw_mouse_settings_dialog::raw_mouse_settings_dialog(QWidget* parent) @@ -66,11 +68,15 @@ raw_mouse_settings_dialog::raw_mouse_settings_dialog(QWidget* parent) constexpr u32 max_devices = 16; g_raw_mouse_handler = std::make_unique(); - g_raw_mouse_handler->SetIsForGui(true); + g_raw_mouse_handler->set_is_for_gui(true); g_raw_mouse_handler->Init(std::max(max_devices, ::size32(g_cfg_raw_mouse.players))); - g_raw_mouse_handler->set_mouse_press_callback([this](const std::string& device_name, s32 cell_code, bool pressed) + g_raw_mouse_handler->set_mouse_press_callback([this](const std::string& device_name, s32 button_code, bool pressed) { - mouse_press(device_name, cell_code, pressed); + mouse_press(device_name, button_code, pressed); + }); + g_raw_mouse_handler->set_key_press_callback([this](const std::string& device_name, s32 scan_code, bool pressed) + { + key_press(device_name, scan_code, pressed); }); m_buttons = new QButtonGroup(this); @@ -89,7 +95,7 @@ raw_mouse_settings_dialog::raw_mouse_settings_dialog(QWidget* parent) if (const int button_id = m_buttons->id(button); button_id >= 0) { auto& config = ::at32(g_cfg_raw_mouse.players, m_tab_widget->currentIndex()); - const std::string name = config->get_button_by_index(button_id % button_count).to_string(); + const std::string name = raw_mouse_config::get_button_name(config->get_button_by_index(button_id % button_count).to_string()); button->setText(name.empty() ? QStringLiteral("-") : QString::fromStdString(name)); } } @@ -253,7 +259,7 @@ void raw_mouse_settings_dialog::add_tabs(QTabWidget* tabs) insert_button(static_cast(player * button_count + i), pb); - const std::string saved_btn = config->get_button(cell_code); + const std::string saved_btn = raw_mouse_config::get_button_name(config->get_button(cell_code).to_string()); pb->setText(saved_btn.empty() ? QStringLiteral("-") : QString::fromStdString(saved_btn)); @@ -328,8 +334,8 @@ void raw_mouse_settings_dialog::reset_config() if (!pb) continue; - const QString text = QString::fromStdString(config->get_button(cell_code).def); - pb->setText(text.isEmpty() ? QStringLiteral("-") : text); + const std::string text = raw_mouse_config::get_button_name(config->get_button(cell_code).def); + pb->setText(text.empty() ? QStringLiteral("-") : QString::fromStdString(text)); } if (QComboBox* combo = ::at32(m_device_combos, player)) @@ -344,7 +350,7 @@ void raw_mouse_settings_dialog::reset_config() } } -void raw_mouse_settings_dialog::mouse_press(const std::string& device_name, s32 cell_code, bool pressed) +void raw_mouse_settings_dialog::mouse_press(const std::string& device_name, s32 button_code, bool pressed) { if (m_button_id < 0 || pressed) // Let's only react to mouse releases { @@ -359,12 +365,40 @@ void raw_mouse_settings_dialog::mouse_press(const std::string& device_name, s32 return; } - auto& config = ::at32(g_cfg_raw_mouse.players, m_tab_widget->currentIndex()); - config->get_button_by_index(m_button_id % button_count).from_string(mouse_button_id(cell_code)); + const std::string button_name = raw_mouse_config::get_button_name(button_code); + + auto& config = ::at32(g_cfg_raw_mouse.players, player); + config->get_button_by_index(m_button_id % button_count).from_string(button_name); if (auto button = m_buttons->button(m_button_id)) { - button->setText(localized_emu::translated_mouse_button(cell_code)); + button->setText(QString::fromStdString(button_name)); + } + + reactivate_buttons(); +} + +void raw_mouse_settings_dialog::key_press(const std::string& device_name, s32 scan_code, bool pressed) +{ + if (m_button_id < 0 || !pressed) // Let's only react to key presses + { + return; + } + + const int player = m_tab_widget->currentIndex(); + const std::string current_device_name = get_current_device_name(player); + + if (device_name != current_device_name) + { + return; + } + + auto& config = ::at32(g_cfg_raw_mouse.players, player); + config->get_button_by_index(m_button_id % button_count).from_string(fmt::format("%s%d", raw_mouse_config::key_prefix, scan_code)); + + if (auto button = m_buttons->button(m_button_id)) + { + button->setText(QString::fromStdString(raw_mouse_config::get_key_name(scan_code))); } reactivate_buttons(); diff --git a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.h b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.h index 1ed13efe99..16aced5a77 100644 --- a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.h +++ b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.h @@ -30,7 +30,8 @@ private: void on_enumeration(); void reset_config(); void on_button_click(int id); - void mouse_press(const std::string& device_name, s32 cell_code, bool pressed); + void mouse_press(const std::string& device_name, s32 button_code, bool pressed); + void key_press(const std::string& device_name, s32 scan_code, bool pressed); void handle_device_change(const std::string& device_name); bool is_device_active(const std::string& device_name); std::string get_current_device_name(int player); diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 399e4b72df..12c62c4dbf 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1847,6 +1847,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->showPPUCompilationHint, emu_settings_type::ShowPPUCompilationHint); SubscribeTooltip(ui->showPPUCompilationHint, tooltips.settings.show_ppu_compilation_hint); + m_emu_settings->EnhanceCheckBox(ui->showAutosaveAutoloadHint, emu_settings_type::ShowAutosaveAutoloadHint); + SubscribeTooltip(ui->showAutosaveAutoloadHint, tooltips.settings.show_autosave_autoload_hint); + m_emu_settings->EnhanceCheckBox(ui->showPressureIntensityToggleHint, emu_settings_type::ShowPressureIntensityToggleHint); SubscribeTooltip(ui->showPressureIntensityToggleHint, tooltips.settings.show_pressure_intensity_toggle_hint); @@ -1856,9 +1859,6 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceCheckBox(ui->showMouseAndKeyboardToggleHint, emu_settings_type::ShowMouseAndKeyboardToggleHint); SubscribeTooltip(ui->showMouseAndKeyboardToggleHint, tooltips.settings.show_mouse_and_keyboard_toggle_hint); - m_emu_settings->EnhanceCheckBox(ui->showAutosaveAutoloadHint, emu_settings_type::ShowAutosaveAutoloadHint); - SubscribeTooltip(ui->showAutosaveAutoloadHint, tooltips.settings.show_autosave_autoload_hint); - m_emu_settings->EnhanceCheckBox(ui->pauseDuringHomeMenu, emu_settings_type::PauseDuringHomeMenu); SubscribeTooltip(ui->pauseDuringHomeMenu, tooltips.settings.pause_during_home_menu); diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 7f70c304aa..5eebe763ff 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -3010,6 +3010,13 @@ + + + + Show shader compilation hint + + + @@ -3018,9 +3025,9 @@ - + - Show shader compilation hint + Show autosave/autoload hint @@ -3045,13 +3052,6 @@ - - - - Show autosave/autoload hint - - - diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index d9576fb591..4e5de9d619 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -143,10 +143,10 @@ public: const QString hide_mouse_on_idle = tr("Hides the mouse cursor if no mouse movement is detected for the configured time."); const QString show_shader_compilation_hint = tr("Shows 'Compiling shaders' hint using the native overlay."); const QString show_ppu_compilation_hint = tr("Shows 'Compiling PPU modules' hint using the native overlay."); + const QString show_autosave_autoload_hint = tr("Shows autosave/autoload hint using the native overlay."); const QString show_pressure_intensity_toggle_hint = tr("Shows pressure intensity toggle hint using the native overlay."); const QString show_analog_limiter_toggle_hint = tr("Shows analog limiter toggle hint using the native overlay."); const QString show_mouse_and_keyboard_toggle_hint = tr("Shows mouse and keyboard toggle hint using the native overlay."); - const QString show_autosave_autoload_hint = tr("Shows autosave/autoload hint using the native overlay."); const QString use_native_interface = tr("Enables use of native HUD within the game window that can interact with game controllers.\nWhen disabled, regular Qt dialogs are used instead.\nCurrently, the on-screen keyboard only supports the English key layout."); const QString pause_during_home_menu = tr("When enabled, opening the home menu will also pause emulation.\nWhile most games pause themselves while the home menu is shown, some do not.\nIn that case it can be helpful to pause the emulation whenever the home menu is open."); @@ -287,6 +287,7 @@ public: const QString mmjoy = tr("The MMJoystick handler should work with almost any controller recognized by Windows. However, it is recommended that you use the more specific handlers if you have a controller that supports them."); const QString sdl = tr("The SDL handler supports a variety of controllers across different platforms."); + const QString orientation_reset = tr("Resets the sensor orientation when pressed.
Toggle the checkbox to enable or disable the orientation feature.
Currently only used for PS Move interactions."); const QString analog_limiter = tr("Applies the stick multipliers while this special button is pressed.
Enable \"Toggle\" if you want to toggle the analog limiter on button press instead."); const QString pressure_intensity = tr("Controls the intensity of pressure sensitive buttons while this special button is pressed.
Enable \"Toggle\" if you want to toggle the intensity on button press instead.
Use the percentage to change how hard you want to press a button."); const QString pressure_deadzone = tr("Controls the deadzone of pressure sensitive buttons. It determines how far the button has to be pressed until it is recognized by the game. The resulting range will be projected onto the full button sensitivity range."); diff --git a/rpcs3/rpcs3qt/uuid.cpp b/rpcs3/rpcs3qt/uuid.cpp index 0ade0218d3..d3f3f58b77 100644 --- a/rpcs3/rpcs3qt/uuid.cpp +++ b/rpcs3/rpcs3qt/uuid.cpp @@ -13,19 +13,15 @@ namespace gui { std::string get_uuid_path() { -#ifdef _WIN32 - const std::string config_dir = fs::get_config_dir() + "config/"; + const std::string config_dir = fs::get_config_dir(true); const std::string uuid_path = config_dir + "uuid"; - +#ifdef _WIN32 if (!fs::create_path(config_dir)) { uuid_log.error("Could not create path: %s (%s)", uuid_path, fs::g_tls_error); } - - return uuid_path; -#else - return fs::get_config_dir() + "uuid"; #endif + return uuid_path; } std::string make_uuid() diff --git a/rpcs3/util/fixed_typemap.hpp b/rpcs3/util/fixed_typemap.hpp index c742f8b03b..85d04c57a1 100644 --- a/rpcs3/util/fixed_typemap.hpp +++ b/rpcs3/util/fixed_typemap.hpp @@ -347,6 +347,19 @@ namespace stx } } + // Order semi-destructors before the actual destructors + // This allows to safely access data that may be deallocated or destroyed from other members of FXO regardless of their intialization time + for (u32 i = 0; i < _max; i++) + { + const auto info = (*std::prev(m_info, i + 1)); + + if (auto op = info->thread_op) + { + constexpr thread_state destroying_context{7}; + op(*std::prev(m_order, i + 1), destroying_context); + } + } + // Destroy objects in reverse order for (; _max; _max--) { diff --git a/rpcs3/util/fnv_hash.hpp b/rpcs3/util/fnv_hash.hpp index 42ff84abe2..dea3d8b480 100644 --- a/rpcs3/util/fnv_hash.hpp +++ b/rpcs3/util/fnv_hash.hpp @@ -61,4 +61,29 @@ namespace rpcs3 return hash_struct_base(value); } + + template + requires std::is_integral_v + static inline usz hash_array(const T(&arr)[N]) + { + usz hash = fnv_seed; + for (size_t i = 0; i < N; ++i) + { + hash = hash64(hash, arr[i]); + } + return hash; + } + + template + requires std::is_class_v + static inline usz hash_array(const T(&arr)[N]) + { + usz hash = fnv_seed; + for (size_t i = 0; i < N; ++i) + { + const u64 item_hash = hash_struct(arr[i]); + hash = hash64(hash, item_hash); + } + return hash; + } } diff --git a/rpcs3/util/shared_ptr.hpp b/rpcs3/util/shared_ptr.hpp index fb3c2c4019..e9bdf83b3b 100644 --- a/rpcs3/util/shared_ptr.hpp +++ b/rpcs3/util/shared_ptr.hpp @@ -361,13 +361,8 @@ namespace stx [[deprecated("Use null_ptr")]] shared_ptr(std::nullptr_t) = delete; // Not-so-aliasing constructor: emulates std::enable_shared_from_this without its overhead - explicit shared_ptr(T* _this) noexcept - : m_ptr(_this) - { - // Random checks which may fail on invalid pointer - ensure((reinterpret_cast(d()->destroy) - 0x10000) >> 47 == 0); - ensure((d()->refs++ - 1) >> 58 == 0); - } + template + friend shared_ptr make_shared_from_this(const Type* _this) noexcept; template requires same_ptr_implicit_v shared_ptr(const shared_ptr& r) noexcept @@ -562,11 +557,29 @@ namespace stx template requires (std::is_constructible_v, T&&>) - static shared_ptr> make_shared_value(T&& value) + static shared_ptr> make_shared_value(T&& value) noexcept { return make_single_value(std::forward(value)); } + // Not-so-aliasing constructor: emulates std::enable_shared_from_this without its overhead + template + static shared_ptr make_shared_from_this(const T* _this) noexcept + { + shared_ptr r; + r.m_ptr = const_cast(_this); + + if (!_this) [[unlikely]] + { + return r; + } + + // Random checks which may fail on invalid pointer + ensure((reinterpret_cast(r.d()->destroy.load()) - 0x10000) >> 47 == 0); + ensure((r.d()->refs++ - 1) >> 58 == 0); + return r; + } + // Atomic simplified shared pointer template class atomic_ptr @@ -1059,9 +1072,9 @@ namespace stx do { // Update old head with current value - next.m_ptr = reinterpret_cast(old.m_val.raw() >> c_ref_size); + next.m_ptr = std::launder(ptr_to(old.m_val.raw())); - } while (!m_val.compare_exchange(old.m_val.raw(), reinterpret_cast(exch.m_ptr) << c_ref_size)); + } while (!m_val.compare_exchange(old.m_val.raw(), to_val(exch.m_ptr))); // This argument is consumed (moved from) exch.m_ptr = nullptr; @@ -1076,7 +1089,7 @@ namespace stx // Simple atomic load is much more effective than load(), but it's a non-owning reference T* observe() const noexcept { - return reinterpret_cast(m_val >> c_ref_size); + return std::launder(ptr_to(m_val)); } explicit constexpr operator bool() const noexcept @@ -1138,11 +1151,6 @@ namespace stx return false; } - constexpr std::nullptr_t get() const noexcept - { - return nullptr; - } - } null_ptr; }