From b76ca8807ff8e6a4f2c9eb37af5c3f30ca61bbe6 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Sat, 6 Dec 2025 16:21:54 +0000 Subject: [PATCH 01/94] (macOS) Support loading VK ICDs (in preparation for KosmicKrisp) --- .ci/build-mac-arm64.sh | 8 +++---- .ci/build-mac.sh | 5 ++--- .ci/deploy-mac-arm64.sh | 4 ++++ .ci/deploy-mac.sh | 6 +++++- rpcs3/Emu/CMakeLists.txt | 5 +++++ rpcs3/Emu/RSX/VK/VulkanAPI.h | 4 ++-- rpcs3/Emu/RSX/VK/vkutils/device.cpp | 7 +++++++ rpcs3/Emu/RSX/VK/vkutils/instance.cpp | 22 ++++++++++++++++++-- rpcs3/Emu/RSX/VK/vkutils/metal_layer.h | 2 ++ rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm | 10 +++++++++ rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp | 9 ++++---- 11 files changed, 66 insertions(+), 16 deletions(-) create mode 100644 rpcs3/Emu/RSX/VK/vkutils/metal_layer.h create mode 100644 rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index b4e255c083..f4e194ca0e 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,8 +6,9 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative + brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 # moltenvk based on commit for 1.4.0 release @@ -57,15 +58,14 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib" -export CPPFLAGS="-I$BREW_PATH/include -I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" +export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" export LIBRARY_PATH="$BREW_PATH/lib" export LD_LIBRARY_PATH="$BREW_PATH/lib" export VULKAN_SDK VULKAN_SDK="$BREW_PATH/opt/molten-vk" -ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true -export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json" +ln -s "$BREW_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true export LLVM_DIR LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 98034c458e..6dc6977f82 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -12,7 +12,7 @@ brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \ rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config} arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers +arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 @@ -64,8 +64,7 @@ export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64 export VULKAN_SDK VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk" -ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" -export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json" +ln -s "$BREW_X64_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" export LLVM_DIR LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER" diff --git a/.ci/deploy-mac-arm64.sh b/.ci/deploy-mac-arm64.sh index 57cd72dafb..41f27a2082 100755 --- a/.ci/deploy-mac-arm64.sh +++ b/.ci/deploy-mac-arm64.sh @@ -15,6 +15,10 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin mkdir "rpcs3.app/Contents/lib/" || true +mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true +cp "$(realpath /opt/homebrew/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "$(realpath /opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib" diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 92acb70003..02d5945727 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -14,7 +14,11 @@ AVVER="${COMM_TAG}-${COMM_COUNT}" echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin -mkdir "rpcs3.app/Contents/lib/" +mkdir "rpcs3.app/Contents/lib/" || true +mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true +cp "$(realpath /usr/local/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "$(realpath /usr/local/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 1a902b46df..39e145900a 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -623,6 +623,11 @@ if(TARGET 3rdparty_vulkan) RSX/VK/VKTextureCache.cpp RSX/VK/VulkanAPI.cpp ) + if(APPLE) + target_sources(rpcs3_emu PRIVATE + RSX/VK/vkutils/metal_layer.mm + ) + endif() endif() find_package(Threads REQUIRED) diff --git a/rpcs3/Emu/RSX/VK/VulkanAPI.h b/rpcs3/Emu/RSX/VK/VulkanAPI.h index c91e9cb5a5..411d653807 100644 --- a/rpcs3/Emu/RSX/VK/VulkanAPI.h +++ b/rpcs3/Emu/RSX/VK/VulkanAPI.h @@ -4,7 +4,7 @@ #ifdef _WIN32 #define VK_USE_PLATFORM_WIN32_KHR #elif defined(__APPLE__) -#define VK_USE_PLATFORM_MACOS_MVK +#define VK_USE_PLATFORM_METAL_EXT #elif defined(ANDROID) #define VK_USE_PLATFORM_ANDROID_KHR #else @@ -29,7 +29,7 @@ // Undefine header configuration variables #undef VK_USE_PLATFORM_WIN32_KHR -#undef VK_USE_PLATFORM_MACOS_MVK +#undef VK_USE_PLATFORM_METAL_EXT #undef VK_USE_PLATFORM_ANDROID_KHR #undef VK_USE_PLATFORM_XLIB_KHR #undef VK_USE_PLATFORM_WAYLAND_KHR diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.cpp b/rpcs3/Emu/RSX/VK/vkutils/device.cpp index e29a9b66c2..34b7758b50 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/device.cpp @@ -3,6 +3,9 @@ #include "util/logs.hpp" #include "Emu/system_config.h" #include +#ifdef __APPLE__ +#include +#endif namespace vk { @@ -555,6 +558,10 @@ namespace vk { requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME); } + +#ifdef __APPLE__ + requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif enabled_features.robustBufferAccess = VK_TRUE; enabled_features.fullDrawIndexUint32 = VK_TRUE; diff --git a/rpcs3/Emu/RSX/VK/vkutils/instance.cpp b/rpcs3/Emu/RSX/VK/vkutils/instance.cpp index fff6691aea..49224fcc27 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/instance.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/instance.cpp @@ -124,10 +124,11 @@ namespace vk } #ifdef __APPLE__ + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME)) { extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME); - layers.push_back(kMVKMoltenVKDriverLayerName); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true }); mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math }); @@ -154,7 +155,7 @@ namespace vk #ifdef _WIN32 extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); #elif defined(__APPLE__) - extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME); + extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME); #else bool found_surface_ext = false; #ifdef HAVE_X11 @@ -187,15 +188,32 @@ namespace vk if (g_cfg.video.debug_output) layers.push_back("VK_LAYER_KHRONOS_validation"); } +#ifdef __APPLE__ + // MoltenVK's ICD will not be detected without these extensions enabled. + else + { + extensions_loaded = true; + extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); + extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + } +#endif VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pApplicationInfo = &app; instance_info.enabledLayerCount = static_cast(layers.size()); instance_info.ppEnabledLayerNames = layers.data(); +#ifdef __APPLE__ + instance_info.enabledExtensionCount = static_cast(extensions.size()); + instance_info.ppEnabledExtensionNames = extensions.data(); +#else instance_info.enabledExtensionCount = fast ? 0 : static_cast(extensions.size()); instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data(); +#endif instance_info.pNext = next_info; +#ifdef __APPLE__ + instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; +#endif if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS) { diff --git a/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h new file mode 100644 index 0000000000..d587b55bf7 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.h @@ -0,0 +1,2 @@ +#pragma once +void* GetCAMetalLayerFromMetalView(void* view); diff --git a/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm new file mode 100644 index 0000000000..5c593bc5b6 --- /dev/null +++ b/rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm @@ -0,0 +1,10 @@ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wold-style-cast" +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wmissing-declarations" +#import +#import +#import + +void* GetCAMetalLayerFromMetalView(void* view) { return ((NSView*)view).layer; } +#pragma GCC diagnostic pop diff --git a/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp b/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp index 9e4217692d..e4485324d5 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp +++ b/rpcs3/Emu/RSX/VK/vkutils/swapchain_macos.hpp @@ -1,6 +1,7 @@ #pragma once #include "swapchain_core.h" +#include "metal_layer.h" namespace vk { @@ -12,11 +13,11 @@ namespace vk VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/) { VkSurfaceKHR result = VK_NULL_HANDLE; - VkMacOSSurfaceCreateInfoMVK createInfo = {}; - createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK; - createInfo.pView = window_handle; + VkMetalSurfaceCreateInfoEXT createInfo = {}; + createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT; + createInfo.pLayer = GetCAMetalLayerFromMetalView(window_handle); - CHECK_RESULT(vkCreateMacOSSurfaceMVK(vk_instance, &createInfo, NULL, &result)); + CHECK_RESULT(vkCreateMetalSurfaceEXT(vk_instance, &createInfo, NULL, &result)); return result; } #endif From 3f6529fecbacdf90aa556ae0fd39b478ae28c27f Mon Sep 17 00:00:00 2001 From: oltolm Date: Mon, 1 Dec 2025 22:22:29 +0100 Subject: [PATCH 02/94] UI: remove unnecessary QOverloads --- rpcs3/rpcs3qt/camera_settings_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/debugger_frame.cpp | 6 ++--- rpcs3/rpcs3qt/dimensions_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/emu_settings.cpp | 2 +- .../rpcs3qt/emulated_pad_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/game_list_frame.cpp | 8 +++---- rpcs3/rpcs3qt/infinity_dialog.cpp | 8 +++---- rpcs3/rpcs3qt/kamen_rider_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/memory_viewer_panel.cpp | 6 ++--- rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/pad_settings_dialog.cpp | 6 ++--- rpcs3/rpcs3qt/patch_creator_dialog.cpp | 2 +- rpcs3/rpcs3qt/patch_manager_dialog.cpp | 4 ++-- rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/rpcn_settings_dialog.cpp | 2 +- rpcs3/rpcs3qt/settings_dialog.cpp | 22 +++++++++---------- rpcs3/rpcs3qt/skylander_dialog.cpp | 4 ++-- 17 files changed, 45 insertions(+), 45 deletions(-) diff --git a/rpcs3/rpcs3qt/camera_settings_dialog.cpp b/rpcs3/rpcs3qt/camera_settings_dialog.cpp index 0cc5843595..da3576183a 100644 --- a/rpcs3/rpcs3qt/camera_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/camera_settings_dialog.cpp @@ -70,8 +70,8 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent) camera_log.notice("Found camera: '%s'", camera_info.description()); } - connect(ui->combo_camera, QOverload::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_camera_change); - connect(ui->combo_settings, QOverload::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_settings_change); + connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change); + connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change); connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) diff --git a/rpcs3/rpcs3qt/debugger_frame.cpp b/rpcs3/rpcs3qt/debugger_frame.cpp index 8eb4bce3fa..7ffe1b32a2 100644 --- a/rpcs3/rpcs3qt/debugger_frame.cpp +++ b/rpcs3/rpcs3qt/debugger_frame.cpp @@ -126,7 +126,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg m_regs->setLineWrapMode(QPlainTextEdit::NoWrap); m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard); m_regs->setContextMenuPolicy(Qt::CustomContextMenu); - + m_debugger_list->setFont(m_mono); m_misc_state->setFont(m_mono); m_regs->setFont(m_mono); @@ -180,7 +180,7 @@ debugger_frame::debugger_frame(std::shared_ptr gui_settings, QWidg m_choice_units->clearFocus(); }); - connect(m_choice_units, QOverload::of(&QComboBox::currentIndexChanged), this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); }); + connect(m_choice_units, &QComboBox::currentIndexChanged, this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); }); connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer); connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest); @@ -1082,7 +1082,7 @@ void debugger_frame::UpdateUnitList() if (reselected_index != umax) { - // Include no-thread at index 0 + // Include no-thread at index 0 m_choice_units->setCurrentIndex(::narrow(reselected_index + 1)); } } diff --git a/rpcs3/rpcs3qt/dimensions_dialog.cpp b/rpcs3/rpcs3qt/dimensions_dialog.cpp index a6eb07018e..4ea4c7dc19 100644 --- a/rpcs3/rpcs3qt/dimensions_dialog.cpp +++ b/rpcs3/rpcs3qt/dimensions_dialog.cpp @@ -433,7 +433,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u16 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFF) @@ -486,7 +486,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentIndex(combo_figlist->findText(text)); }); diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 37692423a3..769c843a66 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -375,7 +375,7 @@ void emu_settings::EnhanceComboBox(QComboBox* combobox, emu_settings_type type, combobox->setCurrentIndex(index); - connect(combobox, QOverload::of(&QComboBox::currentIndexChanged), combobox, [this, is_ranged, combobox, type](int index) + connect(combobox, &QComboBox::currentIndexChanged, combobox, [this, is_ranged, combobox, type](int index) { if (index < 0) return; diff --git a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp index aee4c44c77..c0abb6e5e1 100644 --- a/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/emulated_pad_settings_dialog.cpp @@ -302,7 +302,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs) combo->setCurrentIndex(combo->findData(static_cast(saved_btn_id))); - connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, [this, player, id, combo](int index) + connect(combo, &QComboBox::currentIndexChanged, this, [this, player, id, combo](int index) { if (index < 0 || !combo) return; diff --git a/rpcs3/rpcs3qt/game_list_frame.cpp b/rpcs3/rpcs3qt/game_list_frame.cpp index 7bf27c4695..f68bc44896 100644 --- a/rpcs3/rpcs3qt/game_list_frame.cpp +++ b/rpcs3/rpcs3qt/game_list_frame.cpp @@ -185,13 +185,13 @@ game_list_frame::game_list_frame(std::shared_ptr gui_settings, std connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot); - connect(m_game_list, &QTableWidget::itemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); + connect(m_game_list, &QTableWidget::itemDoubleClicked, this, qOverload(&game_list_frame::doubleClickedSlot)); connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked); connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu); connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection); - connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, QOverload::of(&game_list_frame::doubleClickedSlot)); + connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, qOverload(&game_list_frame::doubleClickedSlot)); connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]() { @@ -1383,7 +1383,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos) const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/"; const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty(); - + if (has_hdd1_cache_dir) { QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache")); @@ -1856,7 +1856,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos) { text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free)); } - + if (has_data_dir) { text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category); diff --git a/rpcs3/rpcs3qt/infinity_dialog.cpp b/rpcs3/rpcs3qt/infinity_dialog.cpp index 223a753c15..7aa00e5819 100644 --- a/rpcs3/rpcs3qt/infinity_dialog.cpp +++ b/rpcs3/rpcs3qt/infinity_dialog.cpp @@ -448,7 +448,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) for (const auto& [figure, entry] : list_figures) { const auto& [num, figure_name] = entry; - + // Apply series filter (0 = all, 1-3 = specific series) if (series_filter != 0 && num != series_filter) continue; @@ -486,7 +486,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) co_compl->setFilterMode(Qt::MatchContains); combo_figlist->setCompleter(co_compl); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentIndex(combo_figlist->findText(text)); }); @@ -536,12 +536,12 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot) filter_group->addButton(btn_series2, 2); // ID 2 for series 2 filter_group->addButton(btn_series3, 3); // ID 3 for series 3 - connect(filter_group, QOverload::of(&QButtonGroup::idClicked), [=](int id) + connect(filter_group, &QButtonGroup::idClicked, [=](int id) { populate_combo(id); }); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u32 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFFFFFF) diff --git a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp index 71a8bd3f88..61932f4cd1 100644 --- a/rpcs3/rpcs3qt/kamen_rider_dialog.cpp +++ b/rpcs3/rpcs3qt/kamen_rider_dialog.cpp @@ -160,7 +160,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_figlist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index) { const u16 fig_info = combo_figlist->itemData(index).toUInt(); if (fig_info != 0xFFFF) @@ -243,7 +243,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated), [=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_figlist->setCurrentText(text); combo_figlist->setCurrentIndex(combo_figlist->findText(text)); diff --git a/rpcs3/rpcs3qt/memory_viewer_panel.cpp b/rpcs3/rpcs3qt/memory_viewer_panel.cpp index 2a8b6c29b1..bcaa6537ef 100644 --- a/rpcs3/rpcs3qt/memory_viewer_panel.cpp +++ b/rpcs3/rpcs3qt/memory_viewer_panel.cpp @@ -290,7 +290,7 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr(u8"Ʌ"), group_search); button_collapse_viewer->setFixedWidth(QLabel(button_collapse_viewer->text()).sizeHint().width() * 3); button_collapse_viewer->setAutoDefault(false); - + m_search_line = new QLineEdit(group_search); m_search_line->setFixedWidth(QLabel(QString("This is the very length of the lineedit due to hidpi reasons.").chopped(4)).sizeHint().width()); m_search_line->setPlaceholderText(tr("Search...")); @@ -328,7 +328,7 @@ memory_viewer_panel::memory_viewer_panel(QWidget* parent, std::shared_ptr::of(&QComboBox::currentIndexChanged), group_search, [this, button_search](int index) + connect(m_cbox_input_mode, &QComboBox::currentIndexChanged, group_search, [this, button_search](int index) { if (index < 1 || m_rsx) { @@ -1305,7 +1305,7 @@ void memory_viewer_panel::ShowAtPC(u32 pc, std::function func) if (!panel->isVisible()) panel->show(); - + panel->raise(); return; diff --git a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp index 0899096c95..13ee4bc092 100644 --- a/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_motion_settings_dialog.cpp @@ -70,7 +70,7 @@ pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::sha } } - connect(ui->cb_choose_device, QOverload::of(&QComboBox::currentIndexChanged), this, &pad_motion_settings_dialog::change_device); + connect(ui->cb_choose_device, &QComboBox::currentIndexChanged, this, &pad_motion_settings_dialog::change_device); // Combobox: Configure Axis m_motion_axis_list = m_handler->get_motion_axis_list(); @@ -111,7 +111,7 @@ pad_motion_settings_dialog::pad_motion_settings_dialog(QDialog* parent, std::sha m_config_entries[i]->shift.set(value); }); - connect(m_axis_names[i], QOverload::of(&QComboBox::currentIndexChanged), this, [this, i](int index) + connect(m_axis_names[i], &QComboBox::currentIndexChanged, this, [this, i](int index) { std::lock_guard lock(m_config_mutex); if (!m_config_entries[i]->axis.from_string(m_axis_names[i]->itemText(index).toStdString())) diff --git a/rpcs3/rpcs3qt/pad_settings_dialog.cpp b/rpcs3/rpcs3qt/pad_settings_dialog.cpp index 8075577b95..36ece3b0ec 100644 --- a/rpcs3/rpcs3qt/pad_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/pad_settings_dialog.cpp @@ -124,7 +124,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti connect(ui->chooseHandler, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeHandler); // Combobox: Devices - connect(ui->chooseDevice, QOverload::of(&QComboBox::currentIndexChanged), this, &pad_settings_dialog::ChangeDevice); + connect(ui->chooseDevice, &QComboBox::currentIndexChanged, this, &pad_settings_dialog::ChangeDevice); // Combobox: Configs connect(ui->chooseConfig, &QComboBox::currentTextChanged, this, &pad_settings_dialog::ChangeConfig); @@ -179,7 +179,7 @@ pad_settings_dialog::pad_settings_dialog(std::shared_ptr gui_setti ui->chooseClass->addItem(tr("Copilot for Player 6"), u32{CELL_PAD_FAKE_TYPE_COPILOT_6}); ui->chooseClass->addItem(tr("Copilot for Player 7"), u32{CELL_PAD_FAKE_TYPE_COPILOT_7}); - connect(ui->chooseClass, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->chooseClass, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; HandleDeviceClassChange(ui->chooseClass->currentData().toUInt()); @@ -2196,7 +2196,7 @@ void pad_settings_dialog::SubscribeTooltips() SubscribeTooltip(ui->gb_mouse_accel, tooltips.gamepad_settings.mouse_acceleration); SubscribeTooltip(ui->gb_mouse_dz, tooltips.gamepad_settings.mouse_deadzones); SubscribeTooltip(ui->gb_mouse_movement, tooltips.gamepad_settings.mouse_movement); - + for (int i = button_ids::id_pad_begin + 1; i < button_ids::id_pad_end; i++) { SubscribeTooltip(m_pad_buttons->button(i), tooltips.gamepad_settings.button_assignment); diff --git a/rpcs3/rpcs3qt/patch_creator_dialog.cpp b/rpcs3/rpcs3qt/patch_creator_dialog.cpp index b94fd65421..539a2e0f82 100644 --- a/rpcs3/rpcs3qt/patch_creator_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_creator_dialog.cpp @@ -72,7 +72,7 @@ patch_creator_dialog::patch_creator_dialog(QWidget* parent) connect(ui->addPatchButton, &QAbstractButton::clicked, this, [this]() { add_instruction(ui->instructionTable->rowCount()); }); init_patch_type_bombo_box(ui->addPatchTypeComboBox, patch_type::be32, false); - connect(ui->addPatchTypeComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index){ update_validator(index, ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); }); + connect(ui->addPatchTypeComboBox, &QComboBox::currentIndexChanged, this, [this](int index){ update_validator(index, ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); }); update_validator(ui->addPatchTypeComboBox->currentIndex(), ui->addPatchTypeComboBox, ui->addPatchOffsetEdit); generate_yml(); diff --git a/rpcs3/rpcs3qt/patch_manager_dialog.cpp b/rpcs3/rpcs3qt/patch_manager_dialog.cpp index 0df58fb35e..04c8906e9b 100644 --- a/rpcs3/rpcs3qt/patch_manager_dialog.cpp +++ b/rpcs3/rpcs3qt/patch_manager_dialog.cpp @@ -107,7 +107,7 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set connect(ui->patch_tree, &QTreeWidget::itemChanged, this, &patch_manager_dialog::handle_item_changed); connect(ui->patch_tree, &QTreeWidget::customContextMenuRequested, this, &patch_manager_dialog::handle_custom_context_menu_requested); connect(ui->cb_owned_games_only, &QCheckBox::checkStateChanged, this, &patch_manager_dialog::handle_show_owned_games_only); - connect(ui->configurable_selector, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->configurable_selector, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) { @@ -116,7 +116,7 @@ patch_manager_dialog::patch_manager_dialog(std::shared_ptr gui_set handle_item_selected(item, item); } }); - connect(ui->configurable_combo_box, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->configurable_combo_box, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) { diff --git a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp index 57b4f70d02..c64391c021 100644 --- a/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/raw_mouse_settings_dialog.cpp @@ -225,7 +225,7 @@ void raw_mouse_settings_dialog::add_tabs(QTabWidget* tabs) update_combo_box(player); - connect(combo, QOverload::of(&QComboBox::currentIndexChanged), this, [this, player, combo](int index) + connect(combo, &QComboBox::currentIndexChanged, this, [this, player, combo](int index) { if (index < 0 || !combo) return; diff --git a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp index 7cc819521a..74d5b14e6a 100644 --- a/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp +++ b/rpcs3/rpcs3qt/rpcn_settings_dialog.cpp @@ -196,7 +196,7 @@ rpcn_account_dialog::rpcn_account_dialog(QWidget* parent) setLayout(vbox_global); - connect(cbx_servers, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(cbx_servers, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 2e3964bbe7..902da72cff 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -553,7 +553,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std reset_zcull_options(); connect(this, &settings_dialog::signal_restore_dependant_defaults, this, reset_zcull_options); - connect(ui->zcullPrecisionMode, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->zcullPrecisionMode, &QComboBox::currentIndexChanged, [this](int index) { if (index < 0) return; @@ -589,7 +589,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->stereoRenderMode->setEnabled(stereo_allowed && stereo_enabled); ui->stereoRenderEnabled->setEnabled(stereo_allowed); }; - connect(ui->resBox, QOverload::of(&QComboBox::currentIndexChanged), this, [enable_3D_modes](int){ enable_3D_modes(); }); + connect(ui->resBox, &QComboBox::currentIndexChanged, this, [enable_3D_modes](int){ enable_3D_modes(); }); connect(ui->stereoRenderEnabled, &QCheckBox::checkStateChanged, this, [enable_3D_modes](Qt::CheckState){ enable_3D_modes(); }); enable_3D_modes(); } @@ -872,7 +872,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std apply_fsr_specific_options(); connect(ui->renderBox, &QComboBox::currentTextChanged, apply_renderer_specific_options); connect(ui->renderBox, &QComboBox::currentTextChanged, this, apply_fsr_specific_options); - connect(ui->outputScalingMode, QOverload::of(&QComboBox::currentIndexChanged), this, apply_fsr_specific_options); + connect(ui->outputScalingMode, &QComboBox::currentIndexChanged, this, apply_fsr_specific_options); // _ _ _______ _ // /\ | (_) |__ __| | | @@ -997,7 +997,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std #else SubscribeTooltip(ui->gb_audio_out, tooltips.settings.audio_out_linux); #endif - connect(ui->audioOutBox, QOverload::of(&QComboBox::currentIndexChanged), this, [change_audio_output_device, get_audio_output_devices](int) + connect(ui->audioOutBox, &QComboBox::currentIndexChanged, this, [change_audio_output_device, get_audio_output_devices](int) { get_audio_output_devices(false); change_audio_output_device(0); // Set device to 'Default' @@ -1006,7 +1006,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->combo_audio_channel_layout, emu_settings_type::AudioChannelLayout); SubscribeTooltip(ui->gb_audio_channel_layout, tooltips.settings.audio_channel_layout); - connect(ui->combo_audio_format, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->combo_audio_format, &QComboBox::currentIndexChanged, this, [this](int index) { const auto [text, value] = get_data(ui->combo_audio_format, index); ui->list_audio_formats->setEnabled(static_cast(value) == audio_format::manual); @@ -1071,7 +1071,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std SubscribeTooltip(ui->gb_audio_avport, tooltips.settings.audio_avport); SubscribeTooltip(ui->gb_audio_device, tooltips.settings.audio_device); - connect(ui->audioDeviceBox, QOverload::of(&QComboBox::currentIndexChanged), this, change_audio_output_device); + connect(ui->audioDeviceBox, &QComboBox::currentIndexChanged, this, change_audio_output_device); connect(this, &settings_dialog::signal_restore_dependant_defaults, this, [change_audio_output_device]() { change_audio_output_device(0); }); // Set device to 'Default' get_audio_output_devices(); @@ -1111,7 +1111,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->microphoneBox, emu_settings_type::MicrophoneType); SubscribeTooltip(ui->microphoneBox, tooltips.settings.microphone); - connect(ui->microphoneBox, QOverload::of(&QComboBox::currentIndexChanged), change_microphone_type); + connect(ui->microphoneBox, &QComboBox::currentIndexChanged, change_microphone_type); propagate_used_devices(); // Enables/Disables comboboxes and checks values from config for sanity // Checkboxes @@ -1184,7 +1184,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std cfg_log.error("The selected camera was not found. Selecting default camera as fallback."); ui->cameraIdBox->setCurrentIndex(ui->cameraIdBox->findData(QString::fromStdString(default_camera))); } - connect(ui->cameraIdBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->cameraIdBox, &QComboBox::currentIndexChanged, this, [this](int index) { if (index >= 0) m_emu_settings->SetSetting(emu_settings_type::CameraID, ui->cameraIdBox->itemData(index).toString().toStdString()); }); @@ -1424,7 +1424,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std // Comboboxes - connect(ui->netStatusBox, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->netStatusBox, &QComboBox::currentIndexChanged, [this](int index) { if (index < 0) return; const auto [text, value] = get_data(ui->netStatusBox, index); @@ -1448,7 +1448,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std SubscribeTooltip(ui->gb_psnStatusBox, tooltips.settings.psn_status); settings_dialog::refresh_countrybox(); - connect(ui->psnCountryBox, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) + connect(ui->psnCountryBox, &QComboBox::currentIndexChanged, this, [this](int index) { if (index < 0) return; @@ -2277,7 +2277,7 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std ui->combo_updates->addItem(tr("Automatic", "Updates"), gui::update_auto); ui->combo_updates->addItem(tr("No", "Updates"), gui::update_off); ui->combo_updates->setCurrentIndex(ui->combo_updates->findData(m_gui_settings->GetValue(gui::m_check_upd_start).toString())); - connect(ui->combo_updates, QOverload::of(&QComboBox::currentIndexChanged), [this](int index) + connect(ui->combo_updates, &QComboBox::currentIndexChanged, [this](int index) { if (index >= 0) m_gui_settings->SetValue(gui::m_check_upd_start, ui->combo_updates->itemData(index)); }); diff --git a/rpcs3/rpcs3qt/skylander_dialog.cpp b/rpcs3/rpcs3qt/skylander_dialog.cpp index 86dfb61464..6623d27f03 100644 --- a/rpcs3/rpcs3qt/skylander_dialog.cpp +++ b/rpcs3/rpcs3qt/skylander_dialog.cpp @@ -586,7 +586,7 @@ skylander_creator_dialog::skylander_creator_dialog(QWidget* parent) setLayout(vbox_panel); - connect(combo_skylist, QOverload::of(&QComboBox::currentIndexChanged), [=](int index) + connect(combo_skylist, &QComboBox::currentIndexChanged, [=](int index) { const u32 sky_info = combo_skylist->itemData(index).toUInt(); if (sky_info != 0xFFFFFFFF) @@ -663,7 +663,7 @@ skylander_creator_dialog::skylander_creator_dialog(QWidget* parent) connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject); - connect(co_compl, QOverload::of(&QCompleter::activated),[=](const QString& text) + connect(co_compl, qOverload(&QCompleter::activated), [=](const QString& text) { combo_skylist->setCurrentText(text); combo_skylist->setCurrentIndex(combo_skylist->findText(text)); From 0f1d516d9ae6a1725e4db5553c99463b1aa6d821 Mon Sep 17 00:00:00 2001 From: Antonino Di Guardo <64427768+digant73@users.noreply.github.com> Date: Sun, 7 Dec 2025 14:38:13 +0100 Subject: [PATCH 03/94] Check available and required size when installing a package (#17829) Show and check available and required size when installing a package. Installation is not allowed, by disabling `Yes` and/or `Install` button, if not enough disk space is detected. On multi installation dialog box, installation size for each package is reported in the list. fixes #14440

### Single PKG installation - Not enough space available ![pr_single_no_space](https://github.com/user-attachments/assets/3fc83d3e-8219-4dca-a4ae-a351f3c4c662)

### Single PKG installation - Required space available ![pr_single_space](https://github.com/user-attachments/assets/84b6b4a0-ee4a-430e-b84f-60a754e0a4fa)

### Multi PKG installation - Not enough space available ![pr_no_space](https://github.com/user-attachments/assets/56af51a1-cd78-4ad5-818d-7707c0c45de4)

### Multi PKG installation - Required space available ![pr_space](https://github.com/user-attachments/assets/43cc66dd-7e98-46de-8dd3-859f8ebb2b05) --------- Co-authored-by: Megamouse --- rpcs3/Crypto/unpkg.h | 1 + rpcs3/Emu/system_utils.cpp | 5 +++ rpcs3/Emu/system_utils.hpp | 2 + rpcs3/rpcs3qt/game_compatibility.cpp | 4 ++ rpcs3/rpcs3qt/game_compatibility.h | 3 ++ rpcs3/rpcs3qt/main_window.cpp | 23 +++++++++-- rpcs3/rpcs3qt/pkg_install_dialog.cpp | 57 +++++++++++++++++++++------- rpcs3/rpcs3qt/pkg_install_dialog.h | 3 ++ 8 files changed, 82 insertions(+), 16 deletions(-) diff --git a/rpcs3/Crypto/unpkg.h b/rpcs3/Crypto/unpkg.h index d253561509..0b7971c07e 100644 --- a/rpcs3/Crypto/unpkg.h +++ b/rpcs3/Crypto/unpkg.h @@ -347,6 +347,7 @@ public: }; bool is_valid() const { return m_is_valid; } + const PKGHeader& get_header() const { return m_header; } package_install_result check_target_app_version() const; static package_install_result extract_data(std::deque& readers, std::deque& bootable_paths); const psf::registry& get_psf() const { return m_psf; } diff --git a/rpcs3/Emu/system_utils.cpp b/rpcs3/Emu/system_utils.cpp index e840887bac..3cf886d06d 100644 --- a/rpcs3/Emu/system_utils.cpp +++ b/rpcs3/Emu/system_utils.cpp @@ -184,6 +184,11 @@ namespace rpcs3::utils return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir()); } + std::string get_hdd0_game_dir() + { + return get_hdd0_dir() + "game/"; + } + u64 get_cache_disk_usage() { if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax) diff --git a/rpcs3/Emu/system_utils.hpp b/rpcs3/Emu/system_utils.hpp index b4142dacb9..c2825abb24 100644 --- a/rpcs3/Emu/system_utils.hpp +++ b/rpcs3/Emu/system_utils.hpp @@ -34,6 +34,8 @@ namespace rpcs3::utils std::string get_flash3_dir(); std::string get_bdvd_dir(); + std::string get_hdd0_game_dir(); + // Cache directories and disk usage u64 get_cache_disk_usage(); std::string get_cache_dir(); diff --git a/rpcs3/rpcs3qt/game_compatibility.cpp b/rpcs3/rpcs3qt/game_compatibility.cpp index 2d901b6642..2845659716 100644 --- a/rpcs3/rpcs3qt/game_compatibility.cpp +++ b/rpcs3/rpcs3qt/game_compatibility.cpp @@ -272,6 +272,10 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, gam info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY"))); info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER"))); + // Technically, there is no specific package's header info providing its installation size on disk. + // We use "data_size" header as an approximation (a bit larger) for this purpose + info.data_size = reader.get_header().data_size.value(); + if (!info.category.isEmpty()) { const Localized localized; diff --git a/rpcs3/rpcs3qt/game_compatibility.h b/rpcs3/rpcs3qt/game_compatibility.h index f24ebfbe1f..664bafea34 100644 --- a/rpcs3/rpcs3qt/game_compatibility.h +++ b/rpcs3/rpcs3qt/game_compatibility.h @@ -1,5 +1,7 @@ #pragma once +#include "util/types.hpp" + #include #include @@ -108,6 +110,7 @@ namespace compat QString version; // May be empty QString category; // HG, DG, GD etc. QString local_cat; // Localized category + u64 data_size = 0; // Installation size package_type type = package_type::other; // The type of package (Update, DLC or other) }; diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 5509a6f69c..80d71a44c2 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -860,11 +860,28 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog); } - const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); - QString message = tr("Do you want to install this package?\n\n%0").arg(info_string); + u64 free_space = 0; - QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), message, QMessageBox::Yes | QMessageBox::No, this); + // Retrieve disk space info on data path's drive + if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_dir(), stat)) + { + free_space = stat.avail_free; + } + + const QString installation_info = + tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") + .arg(rpcs3::utils::get_hdd0_game_dir()) + .arg(gui::utils::format_byte_size(free_space)) + .arg(info.data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) + .arg(gui::utils::format_byte_size(info.data_size)); + + const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version); + QString message = tr("Do you want to install this package?\n\n%0\n\n%1").arg(info_string).arg(installation_info); + + QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), gui::utils::make_paragraph(message), QMessageBox::Yes | QMessageBox::No, this); mb.setDefaultButton(QMessageBox::No); + mb.setTextFormat(Qt::RichText); // Support HTML tags + mb.button(QMessageBox::Yes)->setEnabled(info.data_size <= free_space); if (!info.changelog.isEmpty()) { diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index 11431b2ad4..26fb7515c4 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -2,6 +2,10 @@ #include "game_compatibility.h" #include "numbered_widget_item.h" #include "richtext_item_delegate.h" +#include "qt_utils.h" + +#include "Emu/system_utils.hpp" +#include "Utilities/File.h" #include #include @@ -17,6 +21,7 @@ enum Roles TitleRole = Qt::UserRole + 2, TitleIdRole = Qt::UserRole + 3, VersionRole = Qt::UserRole + 4, + DataSizeRole = Qt::UserRole + 5, }; pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibility* compat, QWidget* parent) @@ -89,7 +94,8 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil append_comma(); accumulated_info += file_info.fileName(); - const QString text = tr("%0 (%2)", "Package text").arg(info.title.simplified()).arg(accumulated_info); + const QString text = tr("%0 (%1) - %2", "Package text").arg(info.title.simplified()) + .arg(accumulated_info).arg(gui::utils::format_byte_size(info.data_size)); QListWidgetItem* item = new numbered_widget_item(text, m_dir_list); item->setData(Roles::FullPathRole, info.path); @@ -97,6 +103,7 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil item->setData(Roles::TitleRole, info.title); item->setData(Roles::TitleIdRole, info.title_id); item->setData(Roles::VersionRole, info.version); + item->setData(Roles::DataSizeRole, static_cast(info.data_size)); item->setToolTip(tooltip); item->setFlags(item->flags() | Qt::ItemIsUserCheckable); item->setCheckState(Qt::Checked); @@ -106,6 +113,10 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil m_dir_list->setCurrentRow(0); m_dir_list->setMinimumWidth((m_dir_list->sizeHintForColumn(0) * 125) / 100); + // Create contextual label (updated in connect(m_dir_list, &QListWidget::itemChanged ...)) + QLabel* installation_info = new QLabel(); + installation_info->setTextFormat(Qt::RichText); // Support HTML tags + // Create buttons QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); buttons->button(QDialogButtonBox::Ok)->setText(tr("Install")); @@ -123,19 +134,9 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil } }); - connect(m_dir_list, &QListWidget::itemChanged, this, [this, buttons](QListWidgetItem*) + connect(m_dir_list, &QListWidget::itemChanged, this, [this, installation_info, buttons](QListWidgetItem*) { - bool any_checked = false; - for (int i = 0; i < m_dir_list->count(); i++) - { - if (m_dir_list->item(i)->checkState() == Qt::Checked) - { - any_checked = true; - break; - } - } - - buttons->button(QDialogButtonBox::Ok)->setEnabled(any_checked); + UpdateInfo(installation_info, buttons); }); QToolButton* move_up = new QToolButton; @@ -159,11 +160,41 @@ pkg_install_dialog::pkg_install_dialog(const QStringList& paths, game_compatibil vbox->addWidget(description); vbox->addLayout(hbox); vbox->addWidget(m_dir_list); + vbox->addWidget(installation_info); vbox->addWidget(buttons); setLayout(vbox); setWindowTitle(tr("Batch PKG Installation")); setObjectName("pkg_install_dialog"); + UpdateInfo(installation_info, buttons); // Just to show and check available and required size +} + +void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const +{ + u64 data_size = 0; + u64 free_space = 0; + + // Retrieve disk space info on data path's drive + if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_game_dir(), stat)) + { + free_space = stat.avail_free; + } + + for (int i = 0; i < m_dir_list->count(); i++) + { + if (m_dir_list->item(i)->checkState() == Qt::Checked) + { + data_size += m_dir_list->item(i)->data(Roles::DataSizeRole).toULongLong(); + } + } + + installation_info->setText(gui::utils::make_paragraph( + tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") + .arg(rpcs3::utils::get_hdd0_game_dir()) + .arg(gui::utils::format_byte_size(free_space)) + .arg(data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) + .arg(gui::utils::format_byte_size(data_size)))); + buttons->button(QDialogButtonBox::Ok)->setEnabled(data_size && (data_size <= free_space)); } void pkg_install_dialog::MoveItem(int offset) const diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.h b/rpcs3/rpcs3qt/pkg_install_dialog.h index bb566b1b67..2122259a1d 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.h +++ b/rpcs3/rpcs3qt/pkg_install_dialog.h @@ -2,6 +2,8 @@ #include #include +#include +#include namespace compat { @@ -19,6 +21,7 @@ public: std::vector GetPathsToInstall() const; private: + void UpdateInfo(QLabel* installation_info, QDialogButtonBox* buttons) const; void MoveItem(int offset) const; QListWidget* m_dir_list; From 65458effa6dff24a871db30675813ea2afee2706 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Sat, 6 Dec 2025 04:54:10 +0100 Subject: [PATCH 04/94] RPCN v1.5.0 --- 3rdparty/flatbuffers | 2 +- rpcs3/Emu/NP/generated/np2_structs.fbs | 2 +- .../Emu/NP/generated/np2_structs_generated.h | 26 ++++++++++--------- rpcs3/Emu/NP/rpcn_client.cpp | 10 ++++--- 4 files changed, 23 insertions(+), 17 deletions(-) diff --git a/3rdparty/flatbuffers b/3rdparty/flatbuffers index 595bf0007a..1872409707 160000 --- a/3rdparty/flatbuffers +++ b/3rdparty/flatbuffers @@ -1 +1 @@ -Subproject commit 595bf0007ab1929570c7671f091313c8fc20644e +Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3 diff --git a/rpcs3/Emu/NP/generated/np2_structs.fbs b/rpcs3/Emu/NP/generated/np2_structs.fbs index bf360bdb86..9ca6e4a437 100644 --- a/rpcs3/Emu/NP/generated/np2_structs.fbs +++ b/rpcs3/Emu/NP/generated/np2_structs.fbs @@ -206,7 +206,7 @@ table SetRoomDataInternalRequest { flagAttr:uint32; roomBinAttrInternal:[BinAttr]; passwordConfig:[RoomGroupPasswordConfig]; - passwordSlotMask:uint64; + passwordSlotMask:[uint64]; ownerPrivilegeRank:[uint16]; } diff --git a/rpcs3/Emu/NP/generated/np2_structs_generated.h b/rpcs3/Emu/NP/generated/np2_structs_generated.h index f1d24ae568..5dabacbbc9 100644 --- a/rpcs3/Emu/NP/generated/np2_structs_generated.h +++ b/rpcs3/Emu/NP/generated/np2_structs_generated.h @@ -8,9 +8,9 @@ // Ensure the included flatbuffers.h is the same version as when this file was // generated, otherwise it may not be compatible. -static_assert(FLATBUFFERS_VERSION_MAJOR == 24 && - FLATBUFFERS_VERSION_MINOR == 3 && - FLATBUFFERS_VERSION_REVISION == 25, +static_assert(FLATBUFFERS_VERSION_MAJOR == 25 && + FLATBUFFERS_VERSION_MINOR == 9 && + FLATBUFFERS_VERSION_REVISION == 23, "Non-compatible flatbuffers version included"); struct SignalingAddr; @@ -2762,8 +2762,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer const ::flatbuffers::Vector<::flatbuffers::Offset> *passwordConfig() const { return GetPointer> *>(VT_PASSWORDCONFIG); } - uint64_t passwordSlotMask() const { - return GetField(VT_PASSWORDSLOTMASK, 0); + const ::flatbuffers::Vector *passwordSlotMask() const { + return GetPointer *>(VT_PASSWORDSLOTMASK); } const ::flatbuffers::Vector *ownerPrivilegeRank() const { return GetPointer *>(VT_OWNERPRIVILEGERANK); @@ -2779,7 +2779,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer VerifyOffset(verifier, VT_PASSWORDCONFIG) && verifier.VerifyVector(passwordConfig()) && verifier.VerifyVectorOfTables(passwordConfig()) && - VerifyField(verifier, VT_PASSWORDSLOTMASK, 8) && + VerifyOffset(verifier, VT_PASSWORDSLOTMASK) && + verifier.VerifyVector(passwordSlotMask()) && VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) && verifier.VerifyVector(ownerPrivilegeRank()) && verifier.EndTable(); @@ -2805,8 +2806,8 @@ struct SetRoomDataInternalRequestBuilder { void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> passwordConfig) { fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig); } - void add_passwordSlotMask(uint64_t passwordSlotMask) { - fbb_.AddElement(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask, 0); + void add_passwordSlotMask(::flatbuffers::Offset<::flatbuffers::Vector> passwordSlotMask) { + fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask); } void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector> ownerPrivilegeRank) { fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank); @@ -2829,12 +2830,12 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern uint32_t flagAttr = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> roomBinAttrInternal = 0, ::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset>> passwordConfig = 0, - uint64_t passwordSlotMask = 0, + ::flatbuffers::Offset<::flatbuffers::Vector> passwordSlotMask = 0, ::flatbuffers::Offset<::flatbuffers::Vector> ownerPrivilegeRank = 0) { SetRoomDataInternalRequestBuilder builder_(_fbb); - builder_.add_passwordSlotMask(passwordSlotMask); builder_.add_roomId(roomId); builder_.add_ownerPrivilegeRank(ownerPrivilegeRank); + builder_.add_passwordSlotMask(passwordSlotMask); builder_.add_passwordConfig(passwordConfig); builder_.add_roomBinAttrInternal(roomBinAttrInternal); builder_.add_flagAttr(flagAttr); @@ -2849,10 +2850,11 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern uint32_t flagAttr = 0, const std::vector<::flatbuffers::Offset> *roomBinAttrInternal = nullptr, const std::vector<::flatbuffers::Offset> *passwordConfig = nullptr, - uint64_t passwordSlotMask = 0, + const std::vector *passwordSlotMask = nullptr, const std::vector *ownerPrivilegeRank = nullptr) { auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset>(*roomBinAttrInternal) : 0; auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset>(*passwordConfig) : 0; + auto passwordSlotMask__ = passwordSlotMask ? _fbb.CreateVector(*passwordSlotMask) : 0; auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector(*ownerPrivilegeRank) : 0; return CreateSetRoomDataInternalRequest( _fbb, @@ -2861,7 +2863,7 @@ inline ::flatbuffers::Offset CreateSetRoomDataIntern flagAttr, roomBinAttrInternal__, passwordConfig__, - passwordSlotMask, + passwordSlotMask__, ownerPrivilegeRank__); } diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index c1dce01cdb..e02078f512 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -255,7 +255,7 @@ namespace rpcn rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size())); } - constexpr u32 RPCN_PROTOCOL_VERSION = 26; + constexpr u32 RPCN_PROTOCOL_VERSION = 27; constexpr usz RPCN_HEADER_SIZE = 15; const char* error_to_explanation(rpcn::ErrorType error) @@ -2019,9 +2019,13 @@ namespace rpcn } final_grouppasswordconfig_vec = builder.CreateVector(davec); } - u64 final_passwordSlotMask = 0; + + flatbuffers::Offset> final_passwordSlotMask; if (req->passwordSlotMask) - final_passwordSlotMask = *req->passwordSlotMask; + { + const u64 value = *req->passwordSlotMask; + final_passwordSlotMask = builder.CreateVector(&value, 1); + } flatbuffers::Offset> final_ownerprivilege_vec; if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank) From adcacc11195f23978363af8824d132d51a220804 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Mon, 8 Dec 2025 00:03:49 +0100 Subject: [PATCH 05/94] Remove RPCN test server --- rpcs3/Emu/NP/rpcn_config.cpp | 5 ++--- rpcs3/Emu/NP/rpcn_config.h | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/NP/rpcn_config.cpp b/rpcs3/Emu/NP/rpcn_config.cpp index b489d7d924..204091d295 100644 --- a/rpcs3/Emu/NP/rpcn_config.cpp +++ b/rpcs3/Emu/NP/rpcn_config.cpp @@ -190,9 +190,8 @@ bool cfg_rpcn::add_host(std::string_view new_description, std::string_view new_h bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host) { - // Do not delete default servers - if ((del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") || - (del_description == "RPCN Test Server" && del_host == "test-np.rpcs3.net")) + // Do not delete default server + if (del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") { return true; } diff --git a/rpcs3/Emu/NP/rpcn_config.h b/rpcs3/Emu/NP/rpcn_config.h index a94eb053b7..0a8c7c9cc5 100644 --- a/rpcs3/Emu/NP/rpcn_config.h +++ b/rpcs3/Emu/NP/rpcn_config.h @@ -9,7 +9,7 @@ struct cfg_rpcn : cfg::node cfg::string npid{this, "NPID", ""}; cfg::string password{this, "Password", ""}; cfg::string token{this, "Token", ""}; - cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net|||RPCN Test Server|test-np.rpcs3.net"}; + cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net"}; cfg::_bool ipv6_support{this, "IPv6 support", true}; void load(); From c840c98e9e19342f430b25723d78be9cdd04441e Mon Sep 17 00:00:00 2001 From: oltolm Date: Mon, 8 Dec 2025 11:43:15 +0100 Subject: [PATCH 06/94] bit_set.h: forward declare fmt_unveil This change is necessary to fix the following clangd error in `StrFmt.h` Redefinition of 'fmt_unveil'clang(redefinition) bit_set.h(388, 8): Previous definition is here --- Utilities/bit_set.h | 4 +++- rpcs3/Crypto/utils.cpp | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Utilities/bit_set.h b/Utilities/bit_set.h index 8b98ae0fd4..9fdbf15de1 100644 --- a/Utilities/bit_set.h +++ b/Utilities/bit_set.h @@ -23,7 +23,6 @@ Intersection (&) and symmetric difference (^) is also available. #include "util/types.hpp" #include "util/atomic.hpp" -#include "Utilities/StrFmt.h" template concept BitSetEnum = std::is_enum_v && requires(T x) @@ -384,6 +383,9 @@ public: } }; +template +struct fmt_unveil; + template struct fmt_unveil> { diff --git a/rpcs3/Crypto/utils.cpp b/rpcs3/Crypto/utils.cpp index 8d2fd4e9aa..71f687bf83 100644 --- a/rpcs3/Crypto/utils.cpp +++ b/rpcs3/Crypto/utils.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "Utilities/StrFmt.h" #include "Utilities/StrUtil.h" #include "Utilities/File.h" From 27f39d2ac08e40fc705135cba6898bc3166e0e71 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 8 Dec 2025 03:07:49 +0100 Subject: [PATCH 07/94] fmt: add more string_view versions of string functions --- Utilities/StrFmt.cpp | 82 ++++++++- Utilities/StrUtil.h | 49 +++++- Utilities/cheat_info.cpp | 4 +- Utilities/cheat_info.h | 2 +- rpcs3/Crypto/unpkg.cpp | 12 +- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 8 +- rpcs3/Emu/Cell/SPURecompiler.h | 2 +- rpcs3/Emu/Cell/lv2/sys_usbd.cpp | 5 +- rpcs3/Emu/Io/PadHandler.h | 2 +- rpcs3/Emu/Io/RB3MidiDrums.cpp | 9 +- rpcs3/Emu/Io/camera_config.cpp | 4 +- rpcs3/Emu/Io/camera_config.h | 4 +- rpcs3/Emu/Io/midi_config_types.cpp | 4 +- rpcs3/Emu/Io/midi_config_types.h | 3 +- rpcs3/Emu/Io/pad_config.cpp | 2 +- rpcs3/Emu/Io/pad_config.h | 2 +- rpcs3/Emu/NP/np_dnshook.cpp | 3 +- rpcs3/Emu/NP/rpcn_config.cpp | 3 +- rpcs3/Emu/RSX/Program/ShaderParam.h | 8 +- rpcs3/Emu/System.cpp | 18 +- rpcs3/Emu/VFS.cpp | 10 +- rpcs3/Input/evdev_joystick_handler.cpp | 2 +- rpcs3/Input/keyboard_pad_handler.cpp | 2 +- rpcs3/Input/mm_joystick_handler.cpp | 2 +- rpcs3/Loader/disc.cpp | 8 +- rpcs3/rpcs3_version.cpp | 4 +- rpcs3/rpcs3qt/cheat_manager.cpp | 6 +- rpcs3/rpcs3qt/cheat_manager.h | 2 +- rpcs3/rpcs3qt/memory_string_searcher.cpp | 6 +- rpcs3/rpcs3qt/microphone_creator.cpp | 12 +- rpcs3/rpcs3qt/microphone_creator.h | 2 +- rpcs3/rpcs3qt/midi_creator.cpp | 4 +- rpcs3/rpcs3qt/midi_creator.h | 2 +- rpcs3/tests/test_fmt.cpp | 209 +++++++++++++++++++++++ rpcs3/util/media_utils.cpp | 4 +- rpcs3/util/sysinfo.cpp | 2 +- 36 files changed, 413 insertions(+), 90 deletions(-) diff --git a/Utilities/StrFmt.cpp b/Utilities/StrFmt.cpp index c628a90a8a..4431769f3a 100644 --- a/Utilities/StrFmt.cpp +++ b/Utilities/StrFmt.cpp @@ -83,7 +83,8 @@ std::string fmt::win_error_to_string(unsigned long error, void* module_handle) if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS, module_handle, error, 0, reinterpret_cast(&message_buffer), 0, nullptr)) { - message = fmt::format("%s (0x%x)", fmt::trim(wchar_to_utf8(message_buffer), " \t\n\r\f\v"), error); + const std::string utf8 = wchar_to_utf8(message_buffer); + message = fmt::format("%s (0x%x)", fmt::trim_sv(utf8, " \t\n\r\f\v"), error); } else { @@ -823,6 +824,50 @@ std::vector fmt::split(std::string_view source, std::initializer_li return result; } +std::vector fmt::split_sv(std::string_view source, std::initializer_list separators, bool is_skip_empty) +{ + std::vector result; + + for (usz index = 0; index < source.size();) + { + usz pos = -1; + usz sep_size = 0; + + for (auto& separator : separators) + { + if (usz pos0 = source.find(separator, index); pos0 < pos) + { + pos = pos0; + sep_size = separator.size(); + } + } + + if (!sep_size) + { + result.emplace_back(&source[index], source.size() - index); + return result; + } + + std::string_view piece = {&source[index], pos - index}; + + index = pos + sep_size; + + if (piece.empty() && is_skip_empty) + { + continue; + } + + result.emplace_back(std::move(piece)); + } + + if (result.empty() && !is_skip_empty) + { + result.emplace_back(); + } + + return result; +} + std::string fmt::trim(const std::string& source, std::string_view values) { const usz begin = source.find_first_not_of(values); @@ -838,6 +883,21 @@ std::string fmt::trim(const std::string& source, std::string_view values) return source.substr(begin, end + 1 - begin); } +std::string_view fmt::trim_sv(std::string_view source, std::string_view values) +{ + const usz begin = source.find_first_not_of(values); + + if (begin == source.npos) + return {}; + + const usz end = source.find_last_not_of(values); + + if (end == source.npos) + return source.substr(begin); + + return source.substr(begin, end + 1 - begin); +} + std::string fmt::trim_front(const std::string& source, std::string_view values) { const usz begin = source.find_first_not_of(values); @@ -848,12 +908,32 @@ std::string fmt::trim_front(const std::string& source, std::string_view values) return source.substr(begin); } +std::string_view fmt::trim_front_sv(std::string_view source, std::string_view values) +{ + const usz begin = source.find_first_not_of(values); + + if (begin == source.npos) + return {}; + + return source.substr(begin); +} + void fmt::trim_back(std::string& source, std::string_view values) { const usz index = source.find_last_not_of(values); source.resize(index + 1); } +std::string_view fmt::trim_back_sv(std::string_view source, std::string_view values) +{ + const usz index = source.find_last_not_of(values); + if (index == std::string_view::npos) + return {}; + + source.remove_suffix(source.size() - (index + 1)); + return source; +} + std::string fmt::to_upper(std::string_view string) { std::string result; diff --git a/Utilities/StrUtil.h b/Utilities/StrUtil.h index 285718ac21..b5df886164 100644 --- a/Utilities/StrUtil.h +++ b/Utilities/StrUtil.h @@ -139,57 +139,90 @@ namespace fmt // Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true. std::vector split(std::string_view source, std::initializer_list separators, bool is_skip_empty = true); + // Splits the string_view into a vector of string_views using the separators. The vector may contain empty string_views unless is_skip_empty is true. + std::vector split_sv(std::string_view source, std::initializer_list separators, bool is_skip_empty = true); + // Removes all preceding and trailing characters specified by 'values' from 'source'. std::string trim(const std::string& source, std::string_view values = " \t"); + // Removes all preceding and trailing characters specified by 'values' from 'source' and returns the result. + std::string_view trim_sv(std::string_view source, std::string_view values = " \t"); + // Removes all preceding characters specified by 'values' from 'source'. std::string trim_front(const std::string& source, std::string_view values = " \t"); + // Removes all preceding characters specified by 'values' from 'source' and returns the result. + std::string_view trim_front_sv(std::string_view source, std::string_view values = " \t"); + // Removes all trailing characters specified by 'values' from 'source'. void trim_back(std::string& source, std::string_view values = " \t"); + // Removes all trailing characters specified by 'values' from 'source' and returns the result. + std::string_view trim_back_sv(std::string_view source, std::string_view values = " \t"); + template - std::string merge(const T& source, const std::string& separator) + std::string merge(const T& source, std::string_view separator) { if (source.empty()) { return {}; } + usz total = (source.size() - 1) * separator.size(); + for (const auto& s : source) + { + total += s.size(); + } + std::string result; + result.reserve(total); auto it = source.begin(); auto end = source.end(); + for (--end; it != end; ++it) { - result += std::string{*it} + separator; + result.append(*it); + + if (!separator.empty()) + result.append(separator); } - return result + std::string{source.back()}; + return result.append(source.back()); } template - std::string merge(std::initializer_list sources, const std::string& separator) + std::string merge(std::initializer_list sources, std::string_view separator) { if (!sources.size()) { return {}; } + usz total = (sources.size() - 1) * separator.size(); + for (const auto& s : sources) + { + if (s.empty()) continue; + total += s.size() + (s.size() - 1) * separator.size(); + } + std::string result; + result.reserve(total); + bool first = true; for (const auto& v : sources) { if (first) { - result = fmt::merge(v, separator); - first = false; + first = false; } - else + else if (!separator.empty()) { - result += separator + fmt::merge(v, separator); + result.append(separator); } + + result.append(fmt::merge(v, separator)); } return result; diff --git a/Utilities/cheat_info.cpp b/Utilities/cheat_info.cpp index a16be2767f..cc8934f15a 100644 --- a/Utilities/cheat_info.cpp +++ b/Utilities/cheat_info.cpp @@ -27,9 +27,9 @@ void fmt_class_string::format(std::string& out, u64 arg) }); } -bool cheat_info::from_str(const std::string& cheat_line) +bool cheat_info::from_str(std::string_view cheat_line) { - auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); + const auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false); s64 val64 = 0; if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1)) diff --git a/Utilities/cheat_info.h b/Utilities/cheat_info.h index 3ceb32716b..a109d86e56 100644 --- a/Utilities/cheat_info.h +++ b/Utilities/cheat_info.h @@ -28,6 +28,6 @@ struct cheat_info u32 offset{}; std::string red_script{}; - bool from_str(const std::string& cheat_line); + bool from_str(std::string_view cheat_line); std::string to_str() const; }; diff --git a/rpcs3/Crypto/unpkg.cpp b/rpcs3/Crypto/unpkg.cpp index bc7481fcd6..de23f5f1ba 100644 --- a/rpcs3/Crypto/unpkg.cpp +++ b/rpcs3/Crypto/unpkg.cpp @@ -598,15 +598,15 @@ bool package_reader::read_param_sfo() const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; - std::string name(entry.name_size + BUF_PADDING, '\0'); + std::string name_buf(entry.name_size + BUF_PADDING, '\0'); - if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) + if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) { pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); continue; } - fmt::trim_back(name, "\0"sv); + std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); // We're looking for the PARAM.SFO file, if there is any if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO") @@ -854,18 +854,18 @@ bool package_reader::fill_data(std::map& all_instal break; } - std::string name(entry.name_size + BUF_PADDING, '\0'); + std::string name_buf(entry.name_size + BUF_PADDING, '\0'); const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u; - if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size) + if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size) { num_failures++; pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset); break; } - fmt::trim_back(name, "\0"sv); + std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv); std::string path = m_install_path + vfs::escape(name); diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index d23903997f..2fbb08a97d 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -7320,7 +7320,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s } // spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all); - // add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data); + // add_pattern(inst_attr::putllc0, pattern.put_pc - lsa, value.data); continue; } @@ -7411,7 +7411,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (allow_pattern) { - add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); + add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data); } spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)" @@ -7433,7 +7433,7 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none) { - add_pattern(false, inst_attr::rchcnt_loop, read_pc - result.entry_point, 0); + add_pattern(inst_attr::rchcnt_loop, read_pc - result.entry_point, 0); spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash); } @@ -8519,7 +8519,7 @@ std::array& block_reg_info::evaluate_start_state(const s return walkby_state; } -void spu_recompiler_base::add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info) +void spu_recompiler_base::add_pattern(inst_attr attr, u32 start, u64 info) { m_patterns[start] = pattern_info{info}; m_inst_attrs[start / 4] = attr; diff --git a/rpcs3/Emu/Cell/SPURecompiler.h b/rpcs3/Emu/Cell/SPURecompiler.h index 6bddb5a035..57d842e69d 100644 --- a/rpcs3/Emu/Cell/SPURecompiler.h +++ b/rpcs3/Emu/Cell/SPURecompiler.h @@ -402,7 +402,7 @@ protected: std::unordered_map m_patterns; - void add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info); + void add_pattern(inst_attr attr, u32 start, u64 info); private: // For private use diff --git a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp index fd5257b03a..78183293e7 100644 --- a/rpcs3/Emu/Cell/lv2/sys_usbd.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_usbd.cpp @@ -321,7 +321,7 @@ static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level le if (!str) return; - const std::string msg = fmt::trim(str, " \t\n"); + const std::string_view msg = fmt::trim_sv(str, " \t\n"); switch (level) { @@ -555,7 +555,8 @@ usb_handler_thread::usb_handler_thread() usb_devices.push_back(std::make_shared(usb_info, get_new_location())); } - const std::vector devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" }); + const std::string midi_devices = g_cfg.io.midi_devices.to_string(); + const std::vector devices_list = fmt::split_sv(midi_devices, { "@@@" }); for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++) { const midi_device device = midi_device::from_string(::at32(devices_list, index)); diff --git a/rpcs3/Emu/Io/PadHandler.h b/rpcs3/Emu/Io/PadHandler.h index e9fc166d93..a27e0bcc50 100644 --- a/rpcs3/Emu/Io/PadHandler.h +++ b/rpcs3/Emu/Io/PadHandler.h @@ -205,7 +205,7 @@ protected: std::set key_codes; const std::string& def = cfg_string.def; - const std::vector names = cfg_pad::get_buttons(cfg_string); + const std::vector names = cfg_pad::get_buttons(cfg_string.to_string()); T def_code = umax; for (const std::string& nam : names) diff --git a/rpcs3/Emu/Io/RB3MidiDrums.cpp b/rpcs3/Emu/Io/RB3MidiDrums.cpp index 67ff121d77..61b172bbdd 100644 --- a/rpcs3/Emu/Io/RB3MidiDrums.cpp +++ b/rpcs3/Emu/Io/RB3MidiDrums.cpp @@ -177,7 +177,7 @@ Note str_to_note(const std::string_view name) std::optional> parse_midi_override(const std::string_view config) { - auto split = fmt::split(config, {"="}); + const auto split = fmt::split_sv(config, {"="}); if (split.size() != 2) { return {}; @@ -236,8 +236,9 @@ std::unordered_map create_id_to_note_mapping() }; // Apply configured overrides. - const std::vector segments = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","}); - for (const std::string& segment : segments) + const std::string midi_overrides = g_cfg_rb3drums.midi_overrides.to_string(); + const std::vector segments = fmt::split_sv(midi_overrides, {","}); + for (const std::string_view& segment : segments) { if (const auto midi_override = parse_midi_override(segment)) { @@ -259,7 +260,7 @@ std::vector parse_combo(const std::string_view name, const std::string_view return {}; } std::vector notes; - const auto& note_names = fmt::split(csv, {","}); + const auto note_names = fmt::split_sv(csv, {","}); for (const auto& note_name : note_names) { const auto note = str_to_note(note_name); diff --git a/rpcs3/Emu/Io/camera_config.cpp b/rpcs3/Emu/Io/camera_config.cpp index d7071b05c3..f498de72b1 100644 --- a/rpcs3/Emu/Io/camera_config.cpp +++ b/rpcs3/Emu/Io/camera_config.cpp @@ -36,7 +36,7 @@ void cfg_camera::save() const } } -cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success) +cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success) { camera_setting setting; const std::string value = cameras.get_value(camera); @@ -64,7 +64,7 @@ std::string cfg_camera::camera_setting::to_string() const return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format); } -void cfg_camera::camera_setting::from_string(const std::string& text) +void cfg_camera::camera_setting::from_string(std::string_view text) { if (text.empty()) { diff --git a/rpcs3/Emu/Io/camera_config.h b/rpcs3/Emu/Io/camera_config.h index a918dea458..1c576e32c1 100644 --- a/rpcs3/Emu/Io/camera_config.h +++ b/rpcs3/Emu/Io/camera_config.h @@ -19,9 +19,9 @@ struct cfg_camera final : cfg::node static constexpr u32 member_count = 5; std::string to_string() const; - void from_string(const std::string& text); + void from_string(std::string_view text); }; - camera_setting get_camera_setting(const std::string& camera, bool& success); + camera_setting get_camera_setting(std::string_view camera, bool& success); void set_camera_setting(const std::string& camera, const camera_setting& setting); const std::string path; diff --git a/rpcs3/Emu/Io/midi_config_types.cpp b/rpcs3/Emu/Io/midi_config_types.cpp index abe976ee93..0105da3d51 100644 --- a/rpcs3/Emu/Io/midi_config_types.cpp +++ b/rpcs3/Emu/Io/midi_config_types.cpp @@ -27,11 +27,11 @@ void fmt_class_string::format(std::string& out, u64 arg) fmt::append(out, "%sßßß%s", obj.type, obj.name); } -midi_device midi_device::from_string(const std::string& str) +midi_device midi_device::from_string(std::string_view str) { midi_device res{}; - if (const std::vector parts = fmt::split(str, {"ßßß"}); !parts.empty()) + if (const std::vector parts = fmt::split_sv(str, {"ßßß"}); !parts.empty()) { u64 result; diff --git a/rpcs3/Emu/Io/midi_config_types.h b/rpcs3/Emu/Io/midi_config_types.h index 9d2f40adf1..d21cbfbfce 100644 --- a/rpcs3/Emu/Io/midi_config_types.h +++ b/rpcs3/Emu/Io/midi_config_types.h @@ -1,6 +1,7 @@ #pragma once #include +#include static constexpr usz max_midi_devices = 3; @@ -17,5 +18,5 @@ struct midi_device midi_device_type type{}; std::string name; - static midi_device from_string(const std::string& str); + static midi_device from_string(std::string_view str); }; diff --git a/rpcs3/Emu/Io/pad_config.cpp b/rpcs3/Emu/Io/pad_config.cpp index 614d972716..937626076e 100644 --- a/rpcs3/Emu/Io/pad_config.cpp +++ b/rpcs3/Emu/Io/pad_config.cpp @@ -5,7 +5,7 @@ extern std::string g_input_config_override; -std::vector cfg_pad::get_buttons(const std::string& str) +std::vector cfg_pad::get_buttons(std::string_view str) { std::vector vec = fmt::split(str, {","}); diff --git a/rpcs3/Emu/Io/pad_config.h b/rpcs3/Emu/Io/pad_config.h index fa695c4941..7dd4bd6323 100644 --- a/rpcs3/Emu/Io/pad_config.h +++ b/rpcs3/Emu/Io/pad_config.h @@ -25,7 +25,7 @@ struct cfg_pad final : cfg::node cfg_pad() {}; cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {} - static std::vector get_buttons(const std::string& str); + static std::vector get_buttons(std::string_view str); static std::string get_buttons(std::vector vec); u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const; diff --git a/rpcs3/Emu/NP/np_dnshook.cpp b/rpcs3/Emu/NP/np_dnshook.cpp index a035947e1f..515cba5fc6 100644 --- a/rpcs3/Emu/NP/np_dnshook.cpp +++ b/rpcs3/Emu/NP/np_dnshook.cpp @@ -28,7 +28,8 @@ namespace np dnshook::dnshook() { // Init switch map for dns - auto swaps = fmt::split(g_cfg.net.swap_list.to_string(), {"&&"}); + const std::string swap_list = g_cfg.net.swap_list.to_string(); + const auto swaps = fmt::split_sv(swap_list, {"&&"}); for (usz i = 0; i < swaps.size(); i++) { auto host_and_ip = fmt::split(swaps[i], {"="}); diff --git a/rpcs3/Emu/NP/rpcn_config.cpp b/rpcs3/Emu/NP/rpcn_config.cpp index 204091d295..0b5cabe768 100644 --- a/rpcs3/Emu/NP/rpcn_config.cpp +++ b/rpcs3/Emu/NP/rpcn_config.cpp @@ -79,7 +79,8 @@ std::string cfg_rpcn::get_host() const std::vector> cfg_rpcn::get_hosts() { std::vector> vec_hosts; - auto hosts_list = fmt::split(hosts.to_string(), {"|||"}); + const std::string host_str = hosts.to_string(); + const auto hosts_list = fmt::split_sv(host_str, {"|||"}); for (const auto& cur_host : hosts_list) { diff --git a/rpcs3/Emu/RSX/Program/ShaderParam.h b/rpcs3/Emu/RSX/Program/ShaderParam.h index 266ab51cbd..6131f36ae9 100644 --- a/rpcs3/Emu/RSX/Program/ShaderParam.h +++ b/rpcs3/Emu/RSX/Program/ShaderParam.h @@ -244,10 +244,10 @@ public: std::vector swizzles; ShaderVariable() = default; - ShaderVariable(const std::string& var) + ShaderVariable(std::string_view var) { // Separate 'double destination' variables 'X=Y=SRC' - std::string simple_var; + std::string_view simple_var; const auto eq_pos = var.find('='); if (eq_pos != umax) @@ -267,11 +267,11 @@ public: simple_var = simple_var.substr(brace_pos); } - auto var_blocks = fmt::split(simple_var, { "." }); + const auto var_blocks = fmt::split_sv(simple_var, { "." }); ensure((!var_blocks.empty())); - name = prefix + var_blocks[0]; + name = prefix + std::string(var_blocks[0]); if (var_blocks.size() == 1) { diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index dd6c3506b2..651c220b60 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -1866,8 +1866,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // PS1 Classic located in dev_hdd0/game sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title); - const std::string tail = m_path.substr(hdd0_game.size()); - const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); + const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size()); + const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim))); const std::string game_path = "/dev_hdd0/game/" + dirname; argv.resize(9); @@ -1894,8 +1894,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // PSP Remaster located in dev_hdd0/game sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title); - const std::string tail = m_path.substr(hdd0_game.size()); - const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)); + const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size()); + const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim))); const std::string game_path = "/dev_hdd0/game/" + dirname; argv.resize(2); @@ -1913,7 +1913,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, // Add HG games not in HDD0 to games.yml [[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir); - const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); + const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim)); vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/'); } } @@ -2265,10 +2265,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, auto unescape = [](std::string_view path) { // Unescape from host FS - std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + const std::vector escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); std::vector result; - for (auto& sv : escaped) - result.emplace_back(vfs::unescape(sv)); + for (const auto& sv : escaped) + result.push_back(vfs::unescape(sv)); return fmt::merge(result, "/"); }; @@ -2315,7 +2315,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, game_dir = game_dir.substr(0, game_dir.size() - 4); } - const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim); + const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim)); m_dir = "/dev_hdd0/game/" + dir + '/'; argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size())); diff --git a/rpcs3/Emu/VFS.cpp b/rpcs3/Emu/VFS.cpp index 8484c57246..f5963ca7c1 100644 --- a/rpcs3/Emu/VFS.cpp +++ b/rpcs3/Emu/VFS.cpp @@ -137,7 +137,7 @@ bool vfs::unmount(std::string_view vpath) return false; } - const std::vector entry_list = fmt::split(vpath, {"/"}); + const std::vector entry_list = fmt::split_sv(vpath, {"/"}); if (entry_list.empty()) { @@ -166,7 +166,7 @@ bool vfs::unmount(std::string_view vpath) } // Get the current name based on the depth - const std::string& name = ::at32(entry_list, depth); + const std::string_view name = ::at32(entry_list, depth); // Go through all children of this node for (auto it = dir.dirs.begin(); it != dir.dirs.end();) @@ -456,10 +456,10 @@ std::string vfs::retrieve(std::string_view path, const vfs_directory* node, std: auto unescape_path = [](std::string_view path) { // Unescape from host FS - std::vector escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); + const std::vector escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}}); std::vector result; - for (auto& sv : escaped) - result.emplace_back(vfs::unescape(sv)); + for (const auto& sv : escaped) + result.push_back(vfs::unescape(sv)); return fmt::merge(result, "/"); }; diff --git a/rpcs3/Input/evdev_joystick_handler.cpp b/rpcs3/Input/evdev_joystick_handler.cpp index 6ddb0ff989..03f2016c27 100644 --- a/rpcs3/Input/evdev_joystick_handler.cpp +++ b/rpcs3/Input/evdev_joystick_handler.cpp @@ -1346,7 +1346,7 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr pad) const auto find_buttons = [&](const cfg::string& name) -> std::set { - const std::vector names = cfg_pad::get_buttons(name); + const std::vector names = cfg_pad::get_buttons(name.to_string()); // In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes. std::set indices; diff --git a/rpcs3/Input/keyboard_pad_handler.cpp b/rpcs3/Input/keyboard_pad_handler.cpp index c9da8dcb29..e70cf8d45b 100644 --- a/rpcs3/Input/keyboard_pad_handler.cpp +++ b/rpcs3/Input/keyboard_pad_handler.cpp @@ -835,7 +835,7 @@ std::string keyboard_pad_handler::GetKeyName(const u32& keyCode) std::set keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string) { std::set key_codes; - for (const std::string& key_name : cfg_pad::get_buttons(cfg_string)) + for (const std::string& key_name : cfg_pad::get_buttons(cfg_string.to_string())) { if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton) { diff --git a/rpcs3/Input/mm_joystick_handler.cpp b/rpcs3/Input/mm_joystick_handler.cpp index cbe9b60223..fed1f428df 100644 --- a/rpcs3/Input/mm_joystick_handler.cpp +++ b/rpcs3/Input/mm_joystick_handler.cpp @@ -148,7 +148,7 @@ std::vector mm_joystick_handler::list_devices() template std::set mm_joystick_handler::find_keys(const cfg::string& cfg_string) const { - return find_keys(cfg_pad::get_buttons(cfg_string)); + return find_keys(cfg_pad::get_buttons(cfg_string.to_string())); } template diff --git a/rpcs3/Loader/disc.cpp b/rpcs3/Loader/disc.cpp index b2ea4dc855..9ae33e42e0 100644 --- a/rpcs3/Loader/disc.cpp +++ b/rpcs3/Loader/disc.cpp @@ -96,7 +96,7 @@ namespace disc for (usz i = 0; i < lines.size(); i++) { - const std::string& line = lines[i]; + const std::string_view line = lines[i]; const usz pos = line.find('='); if (pos == umax) @@ -104,12 +104,12 @@ namespace disc continue; } - const std::string key = fmt::trim(line.substr(0, pos)); - std::string value; + const std::string_view key = fmt::trim_sv(line.substr(0, pos)); + std::string_view value; if (pos != (line.size() - 1)) { - value = fmt::trim(line.substr(pos + 1)); + value = fmt::trim_sv(line.substr(pos + 1)); } if (value.empty() && i != (lines.size() - 1) && line.size() != 1) diff --git a/rpcs3/rpcs3_version.cpp b/rpcs3/rpcs3_version.cpp index 8d789d2ba9..ab18b56caf 100644 --- a/rpcs3/rpcs3_version.cpp +++ b/rpcs3/rpcs3_version.cpp @@ -17,11 +17,11 @@ namespace rpcs3 std::pair get_commit_and_hash() { - const auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"}); + auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"}); if (commit_and_hash.size() != 2) return std::make_pair("0", "00000000"); - return std::make_pair(commit_and_hash[0], commit_and_hash[1]); + return std::make_pair(std::move(commit_and_hash[0]), std::move(commit_and_hash[1])); } // TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION. diff --git a/rpcs3/rpcs3qt/cheat_manager.cpp b/rpcs3/rpcs3qt/cheat_manager.cpp index e6b760a0a2..a24a2cd5fc 100644 --- a/rpcs3/rpcs3qt/cheat_manager.cpp +++ b/rpcs3/rpcs3qt/cheat_manager.cpp @@ -129,11 +129,11 @@ void cheat_engine::save() const cheat_file.write(out.c_str(), out.size()); } -void cheat_engine::import_cheats_from_str(const std::string& str_cheats) +void cheat_engine::import_cheats_from_str(std::string_view str_cheats) { - auto cheats_vec = fmt::split(str_cheats, {"^^^"}); + const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"}); - for (auto& cheat_line : cheats_vec) + for (const auto& cheat_line : cheats_vec) { cheat_info new_cheat; if (new_cheat.from_str(cheat_line)) diff --git a/rpcs3/rpcs3qt/cheat_manager.h b/rpcs3/rpcs3qt/cheat_manager.h index 458c7400b9..b915faa8a2 100644 --- a/rpcs3/rpcs3qt/cheat_manager.h +++ b/rpcs3/rpcs3qt/cheat_manager.h @@ -25,7 +25,7 @@ public: cheat_info* get(const std::string& game, const u32 offset); bool erase(const std::string& game, const u32 offset); - void import_cheats_from_str(const std::string& str_cheats); + void import_cheats_from_str(std::string_view str_cheats); std::string export_cheats_to_str() const; void save() const; diff --git a/rpcs3/rpcs3qt/memory_string_searcher.cpp b/rpcs3/rpcs3qt/memory_string_searcher.cpp index c55cb8144d..de8ee1814f 100644 --- a/rpcs3/rpcs3qt/memory_string_searcher.cpp +++ b/rpcs3/rpcs3qt/memory_string_searcher.cpp @@ -115,11 +115,7 @@ u64 memory_viewer_panel::OnSearch(std::string wstr, u32 mode) } // Concat strings - wstr.clear(); - for (const std::string& part : parts) - { - wstr += part; - } + wstr = fmt::merge(parts, {}); if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax) { diff --git a/rpcs3/rpcs3qt/microphone_creator.cpp b/rpcs3/rpcs3qt/microphone_creator.cpp index 2821fd82e4..c44449c544 100644 --- a/rpcs3/rpcs3qt/microphone_creator.cpp +++ b/rpcs3/rpcs3qt/microphone_creator.cpp @@ -61,23 +61,23 @@ std::array microphone_creator::get_selection_list() const std::string microphone_creator::set_device(u32 num, const QString& text) { - ensure(num < m_sel_list.size()); + std::string& device = ::at32(m_sel_list, num); if (text == get_none()) - m_sel_list[num].clear(); + device.clear(); else - m_sel_list[num] = text.toStdString(); + device = text.toStdString(); return m_sel_list[0] + "@@@" + m_sel_list[1] + "@@@" + m_sel_list[2] + "@@@" + m_sel_list[3] + "@@@"; } -void microphone_creator::parse_devices(const std::string& list) +void microphone_creator::parse_devices(std::string_view list) { m_sel_list = {}; - const std::vector devices_list = fmt::split(list, { "@@@" }); + std::vector devices_list = fmt::split(list, { "@@@" }); for (usz index = 0; index < std::min(m_sel_list.size(), devices_list.size()); index++) { - m_sel_list[index] = devices_list[index]; + m_sel_list[index] = std::move(devices_list[index]); } } diff --git a/rpcs3/rpcs3qt/microphone_creator.h b/rpcs3/rpcs3qt/microphone_creator.h index 95a3827c93..37b2672083 100644 --- a/rpcs3/rpcs3qt/microphone_creator.h +++ b/rpcs3/rpcs3qt/microphone_creator.h @@ -17,7 +17,7 @@ public: microphone_creator(); QString get_none(); std::string set_device(u32 num, const QString& text); - void parse_devices(const std::string& list); + void parse_devices(std::string_view list); void refresh_list(); QStringList get_microphone_list() const; std::array get_selection_list() const; diff --git a/rpcs3/rpcs3qt/midi_creator.cpp b/rpcs3/rpcs3qt/midi_creator.cpp index 57476af3d6..d4ad70d5e9 100644 --- a/rpcs3/rpcs3qt/midi_creator.cpp +++ b/rpcs3/rpcs3qt/midi_creator.cpp @@ -104,11 +104,11 @@ std::string midi_creator::set_device(u32 num, const midi_device& device) return result; } -void midi_creator::parse_devices(const std::string& list) +void midi_creator::parse_devices(std::string_view list) { m_sel_list = {}; - const std::vector devices_list = fmt::split(list, { "@@@" }); + const std::vector devices_list = fmt::split_sv(list, { "@@@" }); for (usz index = 0; index < std::min(m_sel_list.size(), devices_list.size()); index++) { m_sel_list[index] = midi_device::from_string(devices_list[index]); diff --git a/rpcs3/rpcs3qt/midi_creator.h b/rpcs3/rpcs3qt/midi_creator.h index cfcbae3ebc..5a8cb05fd6 100644 --- a/rpcs3/rpcs3qt/midi_creator.h +++ b/rpcs3/rpcs3qt/midi_creator.h @@ -14,7 +14,7 @@ public: midi_creator(); QString get_none(); std::string set_device(u32 num, const midi_device& device); - void parse_devices(const std::string& list); + void parse_devices(std::string_view list); void refresh_list(); QStringList get_midi_list() const; std::array get_selection_list() const; diff --git a/rpcs3/tests/test_fmt.cpp b/rpcs3/tests/test_fmt.cpp index e44b4adab0..95c31d17c6 100644 --- a/rpcs3/tests/test_fmt.cpp +++ b/rpcs3/tests/test_fmt.cpp @@ -25,6 +25,26 @@ namespace fmt EXPECT_EQ("b"s, fmt::trim(" aba ", " a")); } + TEST(StrUtil, TrimSv) + { + EXPECT_EQ(""sv, fmt::trim_sv("", "")); + EXPECT_EQ(""sv, fmt::trim_sv("", " ")); + EXPECT_EQ(""sv, fmt::trim_sv("", "a ")); + EXPECT_EQ(" "sv, fmt::trim_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_sv(" ", " ")); + EXPECT_EQ("a"sv, fmt::trim_sv("a ", " ")); + EXPECT_EQ("a"sv, fmt::trim_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_sv(" a a ", " a")); + EXPECT_EQ("b"sv, fmt::trim_sv(" aba ", " a")); + } + TEST(StrUtil, TrimFront) { EXPECT_EQ(""s, fmt::trim_front("", "")); @@ -45,6 +65,26 @@ namespace fmt EXPECT_EQ("ba "s, fmt::trim_front(" aba ", " a")); } + TEST(StrUtil, TrimFrontSv) + { + EXPECT_EQ(""sv, fmt::trim_front_sv("", "")); + EXPECT_EQ(""sv, fmt::trim_front_sv("", " ")); + EXPECT_EQ(""sv, fmt::trim_front_sv("", "a ")); + EXPECT_EQ(" "sv, fmt::trim_front_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_front_sv(" ", " ")); + EXPECT_EQ("a "sv, fmt::trim_front_sv("a ", " ")); + EXPECT_EQ("a"sv, fmt::trim_front_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv("a a", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv("a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv(" a a", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv(" a a ", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv("a a ", " ")); + EXPECT_EQ("a a "sv, fmt::trim_front_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_front_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_front_sv(" a a ", " a")); + EXPECT_EQ("ba "sv, fmt::trim_front_sv(" aba ", " a")); + } + TEST(StrUtil, TrimBack) { std::string str; @@ -112,6 +152,26 @@ namespace fmt EXPECT_EQ(" ab"s, str); } + TEST(StrUtil, TrimBackSv) + { + EXPECT_EQ(""sv, fmt::trim_back_sv({}, "")); + EXPECT_EQ(""sv, fmt::trim_back_sv({}, " ")); + EXPECT_EQ(""sv, fmt::trim_back_sv({}, "a ")); + EXPECT_EQ(" "sv, fmt::trim_back_sv(" ", "")); + EXPECT_EQ(""sv, fmt::trim_back_sv(" ", " ")); + EXPECT_EQ("a"sv, fmt::trim_back_sv("a ", " ")); + EXPECT_EQ(" a"sv, fmt::trim_back_sv(" a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a ", " ")); + EXPECT_EQ("a a"sv, fmt::trim_back_sv("a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a ", " ")); + EXPECT_EQ(" a a"sv, fmt::trim_back_sv(" a a", " ")); + EXPECT_EQ(""sv, fmt::trim_back_sv(" a a ", " a")); + EXPECT_EQ(" ab"sv, fmt::trim_back_sv(" aba ", " a")); + } + TEST(StrUtil, ToUpperToLower) { const std::string lowercase = "abcdefghijklmnopqrstuvwxyzäüöß0123456789 .,-<#+"; @@ -340,6 +400,155 @@ namespace fmt EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split(" This is a test! ", {"a", " ", "b"}, true)); } + TEST(StrUtil, SplitSv) + { + using vec = std::vector; + + EXPECT_EQ(vec{""}, fmt::split_sv("", {}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {""}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {" "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {" "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv(" ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {" "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, false)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {"a", " "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv(" ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {""}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {" "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a"}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a "}, false)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a b"}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a", " "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {""}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {" "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a"}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a "}, false)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a b"}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a", " "}, false)); + EXPECT_EQ(vec({"", ""}), fmt::split_sv("aa", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {}, false)); + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {""}, false)); + EXPECT_EQ(vec({"a", "b"}), fmt::split_sv("a b", {" "}, false)); + EXPECT_EQ(vec({"", " b"}), fmt::split_sv("a b", {"a"}, false)); + EXPECT_EQ(vec({"", "b"}), fmt::split_sv("a b", {"a "}, false)); + EXPECT_EQ(vec{""}, fmt::split_sv("a b", {"a b"}, false)); + EXPECT_EQ(vec({"", "", "b"}), fmt::split_sv("a b", {"a", " "}, false)); + EXPECT_EQ(vec({"", "", ""}), fmt::split_sv("a b", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {}, false)); + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {""}, false)); + EXPECT_EQ(vec({"a", "b", "c", "c", "b", "a"}), fmt::split_sv("a b c c b a", {" "}, false)); + EXPECT_EQ(vec({"", " b c c b "}), fmt::split_sv("a b c c b a", {"a"}, false)); + EXPECT_EQ(vec({"", "b c c b a"}), fmt::split_sv("a b c c b a", {"a "}, false)); + EXPECT_EQ(vec({"", " c c b a"}), fmt::split_sv("a b c c b a", {"a b"}, false)); + EXPECT_EQ(vec({"", "", "b", "c", "c", "b", ""}), fmt::split_sv("a b c c b a", {"a", " "}, false)); + EXPECT_EQ(vec({"", "", "", "", "c", "c", "", "", ""}), fmt::split_sv("a b c c b a", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {}, false)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {""}, false)); + EXPECT_EQ(vec({"", "This", "is", "a", "test!"}), fmt::split_sv(" This is a test! ", {" "}, false)); + EXPECT_EQ(vec({" This is ", " test! "}), fmt::split_sv(" This is a test! ", {"a"}, false)); + EXPECT_EQ(vec({" This is ", "test! "}), fmt::split_sv(" This is a test! ", {"a "}, false)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {"a b"}, false)); + EXPECT_EQ(vec({"", "This", "is", "", "", "test!"}), fmt::split_sv(" This is a test! ", {"a", " "}, false)); + EXPECT_EQ(vec({"", "This", "is", "", "", "test!"}), fmt::split_sv(" This is a test! ", {"a", " ", "b"}, false)); + + EXPECT_EQ(vec{}, fmt::split_sv("", {}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {" "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {""}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {" "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a"}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a "}, true)); + EXPECT_EQ(vec{" "}, fmt::split_sv(" ", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv(" ", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {""}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a"}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a "}, true)); + EXPECT_EQ(vec{"a"}, fmt::split_sv("a", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {""}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {" "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a"}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a "}, true)); + EXPECT_EQ(vec{"aa"}, fmt::split_sv("aa", {"a b"}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("aa", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {}, true)); + EXPECT_EQ(vec{"a b"}, fmt::split_sv("a b", {""}, true)); + EXPECT_EQ(vec({"a", "b"}), fmt::split_sv("a b", {" "}, true)); + EXPECT_EQ(vec{" b"}, fmt::split_sv("a b", {"a"}, true)); + EXPECT_EQ(vec{"b"}, fmt::split_sv("a b", {"a "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a b", {"a b"}, true)); + EXPECT_EQ(vec{"b"}, fmt::split_sv("a b", {"a", " "}, true)); + EXPECT_EQ(vec{}, fmt::split_sv("a b", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {}, true)); + EXPECT_EQ(vec{"a b c c b a"}, fmt::split_sv("a b c c b a", {""}, true)); + EXPECT_EQ(vec({"a", "b", "c", "c", "b", "a"}), fmt::split_sv("a b c c b a", {" "}, true)); + EXPECT_EQ(vec{" b c c b "}, fmt::split_sv("a b c c b a", {"a"}, true)); + EXPECT_EQ(vec{"b c c b a"}, fmt::split_sv("a b c c b a", {"a "}, true)); + EXPECT_EQ(vec{" c c b a"}, fmt::split_sv("a b c c b a", {"a b"}, true)); + EXPECT_EQ(vec({"b", "c", "c", "b"}), fmt::split_sv("a b c c b a", {"a", " "}, true)); + EXPECT_EQ(vec({"c", "c"}), fmt::split_sv("a b c c b a", {"a", " ", "b"}, true)); + + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {}, true)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {""}, true)); + EXPECT_EQ(vec({"This", "is", "a", "test!"}), fmt::split_sv(" This is a test! ", {" "}, true)); + EXPECT_EQ(vec({" This is ", " test! "}), fmt::split_sv(" This is a test! ", {"a"}, true)); + EXPECT_EQ(vec({" This is ", "test! "}), fmt::split_sv(" This is a test! ", {"a "}, true)); + EXPECT_EQ(vec{" This is a test! "}, fmt::split_sv(" This is a test! ", {"a b"}, true)); + EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split_sv(" This is a test! ", {"a", " "}, true)); + EXPECT_EQ(vec({"This", "is", "test!"}), fmt::split_sv(" This is a test! ", {"a", " ", "b"}, true)); + } + TEST(StrUtil, Merge) { using vec = std::vector; diff --git a/rpcs3/util/media_utils.cpp b/rpcs3/util/media_utils.cpp index d4f450ab69..60a337e750 100644 --- a/rpcs3/util/media_utils.cpp +++ b/rpcs3/util/media_utils.cpp @@ -82,8 +82,8 @@ namespace utils return; } - std::string msg = line; - fmt::trim_back(msg, "\n\r\t "); + std::string msg_buf = line; + std::string_view msg = fmt::trim_back_sv(msg_buf, "\n\r\t "); if (level <= AV_LOG_ERROR) media_log.error("av_log: %s", msg); diff --git a/rpcs3/util/sysinfo.cpp b/rpcs3/util/sysinfo.cpp index bf9376239c..94563e8d10 100755 --- a/rpcs3/util/sysinfo.cpp +++ b/rpcs3/util/sysinfo.cpp @@ -850,7 +850,7 @@ static const bool s_tsc_freq_evaluated = []() -> bool printf("[TSC calibration] Available clock sources: '%s'\n", clock_sources.c_str()); // Check if the Kernel has blacklisted the TSC - const auto available_clocks = fmt::split(clock_sources, { " " }); + const auto available_clocks = fmt::split_sv(clock_sources, { " " }); const bool tsc_reliable = std::find(available_clocks.begin(), available_clocks.end(), "tsc") != available_clocks.end(); if (!tsc_reliable) From aff645272fb51440276b07fac8d8c219eaee1772 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Tue, 9 Dec 2025 02:46:59 +0100 Subject: [PATCH 08/94] overlays/macOs: add some fallback fonts for cyrillic --- rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp index b1dc3fee5a..78229d3845 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp @@ -120,9 +120,15 @@ namespace rsx { result.font_names.emplace_back("Arial.ttf"); result.font_names.emplace_back("arial.ttf"); -#ifndef _WIN32 - result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu - result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch +#ifdef __APPLE__ + result.font_names.emplace_back("DejaVuSans.ttf"); + result.font_names.emplace_back("NotoSans-Regular.ttf"); + result.font_names.emplace_back("Roboto-Regular.ttf"); + result.font_names.emplace_back("OpenSans-Regular.ttf"); + result.font_names.emplace_back("FreeSans.ttf"); +#elifndef _WIN32 + result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu + result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch #endif // Attempt to load a font from dev_flash as a last resort result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF"); From e20bae3cd77ffa03db535b8bd245e9eb57170d73 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 29 Nov 2025 16:31:43 +0300 Subject: [PATCH 09/94] rsx/asm: Stub out register annotation and dependency passes --- rpcs3/Emu/CMakeLists.txt | 2 ++ rpcs3/Emu/RSX/Program/Assembler/CFG.h | 5 +++++ .../Assembler/Passes/RegisterAnnotationPass.cpp | 10 ++++++++++ .../Assembler/Passes/RegisterAnnotationPass.h | 16 ++++++++++++++++ .../Assembler/Passes/RegisterDependencyPass.cpp | 10 ++++++++++ .../Assembler/Passes/RegisterDependencyPass.h | 15 +++++++++++++++ rpcs3/emucore.vcxproj | 4 ++++ rpcs3/emucore.vcxproj.filters | 15 +++++++++++++++ 8 files changed, 77 insertions(+) create mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp create mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h create mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp create mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 39e145900a..d556b3ebdc 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -517,6 +517,8 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/Shaders/shader_loading_dialog.cpp RSX/Overlays/Shaders/shader_loading_dialog_native.cpp RSX/Program/Assembler/FPToCFG.cpp + RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp + RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp RSX/Program/CgBinaryProgram.cpp RSX/Program/CgBinaryFragmentProgram.cpp RSX/Program/CgBinaryVertexProgram.cpp diff --git a/rpcs3/Emu/RSX/Program/Assembler/CFG.h b/rpcs3/Emu/RSX/Program/Assembler/CFG.h index 9bc44a22d1..818bc2a018 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/CFG.h +++ b/rpcs3/Emu/RSX/Program/Assembler/CFG.h @@ -34,6 +34,11 @@ namespace rsx::assembler } }; + struct CFGPass + { + virtual void run(FlowGraph& graph) = 0; + }; + FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog); } diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp new file mode 100644 index 0000000000..8d23080daa --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp @@ -0,0 +1,10 @@ +#include "stdafx.h" +#include "RegisterAnnotationPass.h" + +namespace rsx::assembler +{ + void RegisterAnnotationPass::run(FlowGraph& graph) + { + // TODO + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h new file mode 100644 index 0000000000..f08f0d6e8e --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h @@ -0,0 +1,16 @@ +#pragma once + +#include "../CFG.h" + +namespace rsx::assembler +{ + // The annotation pass annotates each basic block with 2 pieces of information: + // 1. The "input" register list for a block. + // 2. The "output" register list for a block (clobber list). + // The information can be used by other passes to set up prologue/epilogue on each block. + class RegisterAnnotationPass : public CFGPass + { + public: + void run(FlowGraph& graph) override; + }; +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp new file mode 100644 index 0000000000..290f1a4022 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp @@ -0,0 +1,10 @@ +#include "stdafx.h" +#include "RegisterDependencyPass.h" + +namespace rsx::assembler +{ + void RegisterDependencyPass::run(FlowGraph& graph) + { + // TODO + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h new file mode 100644 index 0000000000..b7af86761b --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h @@ -0,0 +1,15 @@ +#pragma once + +#include "../CFG.h" + +namespace rsx::assembler +{ + // The register dependency pass identifies data hazards for each basic block and injects barrier instructions. + // Real PS3 does not have explicit barriers, but does instead often use delay slots or fence instructions to stall until a specific hardware unit clears the fence to advance. + // For decompiled shaders, we have the problem that aliasing is not real and is instead simulated. We do not have access to unions on the GPU without really nasty tricks. + class RegisterDependencyPass : public CFGPass + { + public: + void run(FlowGraph& graph) override; + }; +} diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 1b5716f01b..5e8971fab2 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -157,6 +157,8 @@ + + @@ -702,6 +704,8 @@ + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 23b7ef174d..d0b2a25233 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -136,6 +136,9 @@ {d99df916-8a99-428b-869a-9f14ac0ab411} + + {d13db076-47e4-45b9-bb8a-6b711ea40622} + @@ -1378,6 +1381,12 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program\Assembler\Passes + + + Emu\GPU\RSX\Program\Assembler\Passes + @@ -2776,6 +2785,12 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program\Assembler\Passes + + + Emu\GPU\RSX\Program\Assembler\Passes + From 9d30716aa8633cea4153c29bb7dbcfbcf6444e35 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 29 Nov 2025 23:56:01 +0300 Subject: [PATCH 10/94] rsx: Implement register annotation pass --- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 427 ++++++++++++++++++ rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h | 106 +++++ rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 1 + rpcs3/Emu/RSX/Program/Assembler/IR.h | 10 +- .../Passes/FP/RegisterAnnotationPass.cpp | 181 ++++++++ .../Passes/{ => FP}/RegisterAnnotationPass.h | 14 +- .../{ => FP}/RegisterDependencyPass.cpp | 2 +- .../Passes/{ => FP}/RegisterDependencyPass.h | 4 +- .../Passes/RegisterAnnotationPass.cpp | 10 - rpcs3/Emu/RSX/Program/RSXFragmentProgram.h | 95 +--- rpcs3/emucore.vcxproj | 10 +- rpcs3/emucore.vcxproj.filters | 25 +- 12 files changed, 787 insertions(+), 98 deletions(-) create mode 100644 rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp create mode 100644 rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h create mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp rename rpcs3/Emu/RSX/Program/Assembler/Passes/{ => FP}/RegisterAnnotationPass.h (54%) rename rpcs3/Emu/RSX/Program/Assembler/Passes/{ => FP}/RegisterDependencyPass.cpp (76%) rename rpcs3/Emu/RSX/Program/Assembler/Passes/{ => FP}/RegisterDependencyPass.h (89%) delete mode 100644 rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp new file mode 100644 index 0000000000..401bfd5492 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -0,0 +1,427 @@ +#include "stdafx.h" +#include "FPOpcodes.h" + +#include "Emu/RSX/Common/simple_array.hpp" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include + +namespace rsx::assembler::FP +{ + u8 get_operand_count(FP_opcode opcode) + { + switch (opcode) + { + case RSX_FP_OPCODE_NOP: + return 0; + case RSX_FP_OPCODE_MOV: + case RSX_FP_OPCODE_MUL: + case RSX_FP_OPCODE_ADD: + return 2; + case RSX_FP_OPCODE_MAD: + return 3; + case RSX_FP_OPCODE_DP3: + case RSX_FP_OPCODE_DP4: + return 2; + case RSX_FP_OPCODE_DST: + return 2; + case RSX_FP_OPCODE_MIN: + case RSX_FP_OPCODE_MAX: + return 2; + case RSX_FP_OPCODE_SLT: + case RSX_FP_OPCODE_SGE: + case RSX_FP_OPCODE_SLE: + case RSX_FP_OPCODE_SGT: + case RSX_FP_OPCODE_SNE: + case RSX_FP_OPCODE_SEQ: + return 2; + case RSX_FP_OPCODE_FRC: + case RSX_FP_OPCODE_FLR: + return 1; + case RSX_FP_OPCODE_KIL: + return 0; + case RSX_FP_OPCODE_PK4: + case RSX_FP_OPCODE_UP4: + return 1; + case RSX_FP_OPCODE_DDX: + case RSX_FP_OPCODE_DDY: + return 1; + case RSX_FP_OPCODE_TEX: + case RSX_FP_OPCODE_TXD: + case RSX_FP_OPCODE_TXP: + return 1; + case RSX_FP_OPCODE_RCP: + case RSX_FP_OPCODE_RSQ: + case RSX_FP_OPCODE_EX2: + case RSX_FP_OPCODE_LG2: + return 1; + case RSX_FP_OPCODE_LIT: + return 1; + case RSX_FP_OPCODE_LRP: + return 3; + case RSX_FP_OPCODE_STR: + case RSX_FP_OPCODE_SFL: + return 0; + case RSX_FP_OPCODE_COS: + case RSX_FP_OPCODE_SIN: + return 1; + case RSX_FP_OPCODE_PK2: + case RSX_FP_OPCODE_UP2: + return 1; + case RSX_FP_OPCODE_PKB: + case RSX_FP_OPCODE_UPB: + case RSX_FP_OPCODE_PK16: + case RSX_FP_OPCODE_UP16: + case RSX_FP_OPCODE_PKG: + case RSX_FP_OPCODE_UPG: + return 1; + case RSX_FP_OPCODE_DP2A: + return 3; + case RSX_FP_OPCODE_TXL: + case RSX_FP_OPCODE_TXB: + return 2; + case RSX_FP_OPCODE_DP2: + return 2; + case RSX_FP_OPCODE_NRM: + return 1; + case RSX_FP_OPCODE_DIV: + case RSX_FP_OPCODE_DIVSQ: + return 2; + case RSX_FP_OPCODE_LIF: + return 1; + case RSX_FP_OPCODE_FENCT: + case RSX_FP_OPCODE_FENCB: + case RSX_FP_OPCODE_BRK: + case RSX_FP_OPCODE_CAL: + case RSX_FP_OPCODE_IFE: + case RSX_FP_OPCODE_LOOP: + case RSX_FP_OPCODE_REP: + case RSX_FP_OPCODE_RET: + // Flow control. Special registers are provided for these outside the common file + return 0; + + // The rest are unimplemented and not encountered in real software. + // TODO: Probe these on real PS3 and figure out what they actually do. + case RSX_FP_OPCODE_POW: + fmt::throw_exception("Unimplemented POW instruction."); // Unused + case RSX_FP_OPCODE_BEM: + case RSX_FP_OPCODE_TEXBEM: + case RSX_FP_OPCODE_TXPBEM: + case RSX_FP_OPCODE_BEMLUM: + fmt::throw_exception("Unimplemented BEM class instruction"); // Unused + case RSX_FP_OPCODE_REFL: + return 2; + case RSX_FP_OPCODE_TIMESWTEX: + fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused + default: + break; + } + + return 0; + } + + // Returns a lane mask for the given operand. + // The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel. + u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + constexpr u32 x = 0b0001; + constexpr u32 y = 0b0010; + constexpr u32 z = 0b0100; + constexpr u32 w = 0b1000; + constexpr u32 xy = 0b0011; + constexpr u32 xyz = 0b0111; + constexpr u32 xyzw = 0b1111; + + const auto decode = [&](const rsx::simple_array& masks) -> u32 + { + return operand < masks.size() + ? masks[operand] + : 0u; + }; + + auto opcode = static_cast(instruction->opcode); + if (operand >= get_operand_count(opcode)) + { + return 0; + } + + OPDEST d0 { .HEX = instruction->bytecode[0] }; + const u32 dst_write_mask = d0.no_dest ? 0 : d0.write_mask; + + switch (opcode) + { + case RSX_FP_OPCODE_NOP: + return 0; + case RSX_FP_OPCODE_MOV: + case RSX_FP_OPCODE_MUL: + case RSX_FP_OPCODE_ADD: + case RSX_FP_OPCODE_MAD: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_DP3: + return xyz; + case RSX_FP_OPCODE_DP4: + return xyzw; + case RSX_FP_OPCODE_DST: + return decode({ y | z, y | w }); + case RSX_FP_OPCODE_MIN: + case RSX_FP_OPCODE_MAX: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_SLT: + case RSX_FP_OPCODE_SGE: + case RSX_FP_OPCODE_SLE: + case RSX_FP_OPCODE_SGT: + case RSX_FP_OPCODE_SNE: + case RSX_FP_OPCODE_SEQ: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_FRC: + case RSX_FP_OPCODE_FLR: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_KIL: + return 0; + case RSX_FP_OPCODE_PK4: + return xyzw; + case RSX_FP_OPCODE_UP4: + return x; + case RSX_FP_OPCODE_DDX: + case RSX_FP_OPCODE_DDY: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_TEX: + case RSX_FP_OPCODE_TXD: + switch (prog.get_texture_dimension(d0.tex_num)) + { + case rsx::texture_dimension_extended::texture_dimension_1d: + return x; + case rsx::texture_dimension_extended::texture_dimension_2d: + return xy; + case rsx::texture_dimension_extended::texture_dimension_3d: + case rsx::texture_dimension_extended::texture_dimension_cubemap: + return xyz; + default: + return 0; + } + case RSX_FP_OPCODE_TXP: + switch (prog.get_texture_dimension(d0.tex_num)) + { + case rsx::texture_dimension_extended::texture_dimension_1d: + return xy; + case rsx::texture_dimension_extended::texture_dimension_2d: + return xyz; + case rsx::texture_dimension_extended::texture_dimension_3d: + case rsx::texture_dimension_extended::texture_dimension_cubemap: + return xyzw; + default: + return 0; + } + case RSX_FP_OPCODE_RCP: + case RSX_FP_OPCODE_RSQ: + case RSX_FP_OPCODE_EX2: + case RSX_FP_OPCODE_LG2: + return x; + case RSX_FP_OPCODE_LIT: + return xyzw; + case RSX_FP_OPCODE_LRP: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_STR: + case RSX_FP_OPCODE_SFL: + return xyzw & dst_write_mask; + case RSX_FP_OPCODE_COS: + case RSX_FP_OPCODE_SIN: + return x; + case RSX_FP_OPCODE_PK2: + return xy; + case RSX_FP_OPCODE_UP2: + return x; + case RSX_FP_OPCODE_PKB: + return xyzw; + case RSX_FP_OPCODE_UPB: + return x; + case RSX_FP_OPCODE_PK16: + return xy; + case RSX_FP_OPCODE_UP16: + return x; + case RSX_FP_OPCODE_PKG: + return xyzw; + case RSX_FP_OPCODE_UPG: + return x; + case RSX_FP_OPCODE_DP2A: + return decode({ xy, xy, x }); + case RSX_FP_OPCODE_TXL: + case RSX_FP_OPCODE_TXB: + return decode({ xy, x }); + case RSX_FP_OPCODE_REFL: + return xyzw; + case RSX_FP_OPCODE_DP2: + return xy; + case RSX_FP_OPCODE_NRM: + return xyz; + case RSX_FP_OPCODE_DIV: + case RSX_FP_OPCODE_DIVSQ: + return decode({ xyzw, x }); + case RSX_FP_OPCODE_LIF: + return decode({ y | w }); + case RSX_FP_OPCODE_FENCT: + case RSX_FP_OPCODE_FENCB: + case RSX_FP_OPCODE_BRK: + case RSX_FP_OPCODE_CAL: + case RSX_FP_OPCODE_IFE: + case RSX_FP_OPCODE_LOOP: + case RSX_FP_OPCODE_REP: + case RSX_FP_OPCODE_RET: + // Flow control. Special registers are provided for these outside the common file + return 0; + + case RSX_FP_OPCODE_POW: + fmt::throw_exception("Unimplemented POW instruction."); // Unused ?? + case RSX_FP_OPCODE_BEM: + case RSX_FP_OPCODE_TEXBEM: + case RSX_FP_OPCODE_TXPBEM: + case RSX_FP_OPCODE_BEMLUM: + fmt::throw_exception("Unimplemented BEM class instruction"); // Unused + case RSX_FP_OPCODE_TIMESWTEX: + fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused + default: + break; + } + + return 0; + } + + // Resolved vector lane mask with swizzles applied. + u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + // Brute-force this. There's only 16 permutations. + constexpr u32 x = 0b0000; + constexpr u32 y = 0b0000; + constexpr u32 z = 0b0100; + constexpr u32 w = 0b1000; + + const u32 lane_mask = get_src_vector_lane_mask(prog, instruction, operand); + if (!lane_mask) + { + return lane_mask; + } + + // Now we resolve matching lanes. + // This sequence can be drastically sped up using lookup tables but that will come later. + std::unordered_set inputs; + SRC_Common src { .HEX = instruction->bytecode[operand + 1] }; + + if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP) + { + return 0; + } + + if (lane_mask & x) inputs.insert(src.swizzle_x); + if (lane_mask & y) inputs.insert(src.swizzle_y); + if (lane_mask & z) inputs.insert(src.swizzle_z); + if (lane_mask & w) inputs.insert(src.swizzle_w); + + u32 result = 0; + if (inputs.contains(0)) result |= x; + if (inputs.contains(1)) result |= y; + if (inputs.contains(2)) result |= z; + if (inputs.contains(3)) result |= w; + + return result; + } + + bool is_delay_slot(const Instruction* instruction) + { + OPDEST dst { .HEX = instruction->bytecode[0] }; + SRC0 src0 { .HEX = instruction->bytecode[1] }; + SRC1 src1{ .HEX = instruction->bytecode[2] }; + + if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV + dst.no_dest || // Must have a sink + src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg + dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self + dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter + dst.saturate || // Precision modifier + (dst.prec != RSX_FP_PRECISION_REAL && + dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers + { + return false; + } + + // Check if we have precision modifiers on the source + if (src0.abs || src0.neg || src1.scale) + { + return false; + } + + if (dst.mask_x && src0.swizzle_x != 0) return false; + if (dst.mask_y && src0.swizzle_y != 1) return false; + if (dst.mask_z && src0.swizzle_z != 2) return false; + if (dst.mask_w && src0.swizzle_w != 3) return false; + + return true; + } + + RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) + { + SRC_Common src{ .HEX = instruction->bytecode[operand + 1] }; + if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP) + { + return {}; + } + + const u32 read_lanes = get_src_vector_lane_mask_shuffled(prog, instruction, operand); + if (!read_lanes) + { + return {}; + } + + RegisterRef ref{ .mask = read_lanes }; + Register& reg = ref.reg; + + reg.f16 = !!src.fp16; + reg.id = src.tmp_reg_index; + return ref; + } + + RegisterRef get_dst_register(const Instruction* instruction) + { + OPDEST dst { .HEX = instruction->bytecode[0] }; + if (dst.no_dest) + { + return {}; + } + + RegisterRef ref{ .mask = dst.write_mask }; + ref.reg.f16 = dst.fp16; + ref.reg.id = dst.dest_reg; + return ref; + } + + // Convert vector mask to file range + rsx::simple_array get_register_file_range(const RegisterRef& reg) + { + if (!reg.mask) + { + return {}; + } + + constexpr u32 register_file_max_len = 48 * 8; // H0 - H47, R0 - R23 + + const u32 lane_width = reg.reg.f16 ? 2 : 4; + const u32 file_offset = reg.reg.id * lane_width * 4; + + ensure(file_offset < register_file_max_len, "Invalid register index"); + + rsx::simple_array result{}; + auto insert_lane = [&](u32 word_offset) + { + for (u32 i = 0; i < lane_width; ++i) + { + result.push_back(file_offset + (word_offset * lane_width) + i); + } + }; + + if (reg.x) insert_lane(0); + if (reg.y) insert_lane(1); + if (reg.z) insert_lane(2); + if (reg.w) insert_lane(3); + + return result; + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h new file mode 100644 index 0000000000..b2297a24ab --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h @@ -0,0 +1,106 @@ +#pragma once + +#include "IR.h" +#include "Emu/RSX/Common/simple_array.hpp" + +struct RSXFragmentProgram; + +namespace rsx::assembler +{ + enum FP_opcode + { + RSX_FP_OPCODE_NOP = 0x00, // No-Operation + RSX_FP_OPCODE_MOV = 0x01, // Move + RSX_FP_OPCODE_MUL = 0x02, // Multiply + RSX_FP_OPCODE_ADD = 0x03, // Add + RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add + RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product + RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product + RSX_FP_OPCODE_DST = 0x07, // Distance + RSX_FP_OPCODE_MIN = 0x08, // Minimum + RSX_FP_OPCODE_MAX = 0x09, // Maximum + RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan + RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual + RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual + RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan + RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual + RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal + RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract) + RSX_FP_OPCODE_FLR = 0x11, // Floor + RSX_FP_OPCODE_KIL = 0x12, // Kill fragment + RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values + RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values + RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x) + RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y) + RSX_FP_OPCODE_TEX = 0x17, // Texture lookup + RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup) + RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives) + RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal + RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root + RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2 + RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2 + RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients + RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation + RSX_FP_OPCODE_STR = 0x20, // Set-If-True + RSX_FP_OPCODE_SFL = 0x21, // Set-If-False + RSX_FP_OPCODE_COS = 0x22, // Cosine + RSX_FP_OPCODE_SIN = 0x23, // Sine + RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats + RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats + RSX_FP_OPCODE_POW = 0x26, // Power + RSX_FP_OPCODE_PKB = 0x27, // Pack bytes + RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes + RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits + RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16 + RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform) + RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation + RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma + RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition + RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD + RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias + RSX_FP_OPCODE_TEXBEM = 0x33, + RSX_FP_OPCODE_TXPBEM = 0x34, + RSX_FP_OPCODE_BEMLUM = 0x35, + RSX_FP_OPCODE_REFL = 0x36, // Reflection vector + RSX_FP_OPCODE_TIMESWTEX = 0x37, + RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product + RSX_FP_OPCODE_NRM = 0x39, // Normalize + RSX_FP_OPCODE_DIV = 0x3A, // Division + RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root + RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT + RSX_FP_OPCODE_FENCT = 0x3D, // Fence T? + RSX_FP_OPCODE_FENCB = 0x3E, // Fence B? + RSX_FP_OPCODE_BRK = 0x40, // Break + RSX_FP_OPCODE_CAL = 0x41, // Subroutine call + RSX_FP_OPCODE_IFE = 0x42, // If + RSX_FP_OPCODE_LOOP = 0x43, // Loop + RSX_FP_OPCODE_REP = 0x44, // Repeat + RSX_FP_OPCODE_RET = 0x45 // Return + }; + + namespace FP + { + // Returns number of operands consumed by an instruction + u8 get_operand_count(FP_opcode opcode); + + // Returns a lane mask for the given operand. + // The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel. + u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + + // Resolved vector lane mask with swizzles applied. + u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + + // Returns true on delay slot instructions. + bool is_delay_slot(const Instruction* instruction); + + // Generate register references + RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand); + RegisterRef get_dst_register(const Instruction* instruction); + + // Convert vector mask to file ranges + rsx::simple_array get_register_file_range(const RegisterRef& reg); + + // Compile a register file annotated blob to register references + std::vector compile_register_file(const std::array& file); + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index d8de4eda0b..055d74a88c 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -126,6 +126,7 @@ namespace rsx::assembler std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16); ir_inst.length = 4; ir_inst.addr = pc * 16; + ir_inst.opcode = opcode; switch (opcode) { diff --git a/rpcs3/Emu/RSX/Program/Assembler/IR.h b/rpcs3/Emu/RSX/Program/Assembler/IR.h index 65960f3d99..0fbd1f98ae 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/IR.h +++ b/rpcs3/Emu/RSX/Program/Assembler/IR.h @@ -19,7 +19,7 @@ namespace rsx::assembler // Vector information union { - u32 mask; + u32 mask = 0; struct { @@ -29,6 +29,11 @@ namespace rsx::assembler bool w : 1; }; }; + + operator bool() const + { + return !!mask; + } }; struct Instruction @@ -78,6 +83,9 @@ namespace rsx::assembler std::vector prologue; // Prologue, created by passes std::vector epilogue; // Epilogue, created by passes + std::vector input_list; // Register inputs. + std::vector clobber_list; // Clobbered outputs + FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE) { FlowEdge e{ .type = type, .from = this, .to = b }; diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp new file mode 100644 index 0000000000..196bc19361 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -0,0 +1,181 @@ +#include "stdafx.h" +#include "RegisterAnnotationPass.h" +#include "Emu/RSX/Program/Assembler/FPOpcodes.h" + +#include +#include + +namespace rsx::assembler::FP +{ + static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers + static constexpr char content_unknown = 0; + static constexpr char content_float32 = 'F'; + static constexpr char content_float16 = 'H'; + static constexpr char content_dual = 'D'; + + std::vector compile_register_file(const std::array& file) + { + std::vector results; + + // F16 register processing + for (int reg16 = 0; reg16 < 48; ++reg16) + { + const u32 offset = reg16 * 8; + auto word = *reinterpret_cast(&file[offset]); + + if (!word) [[ likely ]] + { + // Trivial rejection, very commonly hit. + continue; + } + + RegisterRef ref{ .reg {.id = reg16, .f16 = true } }; + ref.x = (file[offset] == content_dual || file[offset] == content_float16); + ref.y = (file[offset + 2] == content_dual || file[offset + 2] == content_float16); + ref.z = (file[offset + 4] == content_dual || file[offset + 4] == content_float16); + ref.w = (file[offset + 6] == content_dual || file[offset + 6] == content_float16); + + if (ref) + { + results.push_back(ref); + } + } + + // Helper to check a span for 32-bit access + auto match_any_32 = [](const std::span lanes) + { + for (const auto& c : lanes) + { + if (c == content_dual || c == content_float32) + { + return true; + } + } + return false; + }; + + // F32 register processing + for (int reg32 = 0; reg32 < 24; ++reg32) + { + const u32 offset = reg32 * 16; + auto word0 = *reinterpret_cast(&file[offset]); + auto word1 = *reinterpret_cast(&file[offset + 8]); + + if (!word0 && !word1) [[ likely ]] + { + // Trivial rejection, very commonly hit. + continue; + } + + RegisterRef ref{ .reg {.id = reg32, .f16 = false } }; + if (word0) + { + ref.x = match_any_32({ &file[offset], 4 }); + ref.y = match_any_32({ &file[offset + 4], 4 }); + } + + if (word1) + { + ref.z = match_any_32({ &file[offset + 8], 4 }); + ref.w = match_any_32({ &file[offset + 12], 4 }); + } + + if (ref) + { + results.push_back(ref); + } + } + + return results; + } + + // Decay instructions into register references + void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog) + { + for (auto& instruction : block->instructions) + { + const u32 operand_count = get_operand_count(static_cast(instruction.opcode)); + for (u32 i = 0; i < operand_count; i++) + { + RegisterRef reg = get_src_register(prog, &instruction, i); + ensure(reg.mask, "Invalid register read"); + + instruction.srcs.push_back(reg); + } + + RegisterRef dst = get_dst_register(&instruction); + if (dst) + { + instruction.dsts.push_back(dst); + } + } + } + + // Annotate each block with input and output lanes (read and clobber list) + void annotate_block_io(BasicBlock* block) + { + alignas(16) std::array output_register_file; + alignas(16) std::array input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now + + std::memset(output_register_file.data(), content_unknown, register_file_length); + std::memset(input_register_file.data(), content_unknown, register_file_length); + + for (const auto& instruction : block->instructions) + { + for (const auto& src : instruction.srcs) + { + const auto read_bytes = get_register_file_range(src); + const char expected_type = src.reg.f16 ? content_float16 : content_float16; + for (const auto& index : read_bytes) + { + if (output_register_file[index] != content_unknown) + { + // Something already wrote to this lane + continue; + } + + if (input_register_file[index] == expected_type) + { + // We already know about this input + continue; + } + + if (input_register_file[index] == 0) + { + // Not known, tag as input + input_register_file[index] = expected_type; + continue; + } + + // Collision on the lane + input_register_file[index] = content_dual; + } + } + + if (!instruction.dsts.empty()) + { + const auto& dst = instruction.dsts.front(); + const auto write_bytes = get_register_file_range(dst); + const char expected_type = dst.reg.f16 ? content_float16 : content_float16; + + for (const auto& index : write_bytes) + { + output_register_file[index] = expected_type; + } + } + } + + // Compile the input and output refs into register references + block->clobber_list = compile_register_file(output_register_file); + block->input_list = compile_register_file(input_register_file); + } + + void RegisterAnnotationPass::run(FlowGraph& graph) + { + for (auto& block : graph.blocks) + { + annotate_instructions(&block, m_prog); + annotate_block_io(&block); + } + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h similarity index 54% rename from rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h rename to rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h index f08f0d6e8e..8856ecb44e 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.h +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h @@ -1,16 +1,26 @@ #pragma once -#include "../CFG.h" +#include "../../CFG.h" -namespace rsx::assembler +struct RSXFragmentProgram; + +namespace rsx::assembler::FP { // The annotation pass annotates each basic block with 2 pieces of information: // 1. The "input" register list for a block. // 2. The "output" register list for a block (clobber list). // The information can be used by other passes to set up prologue/epilogue on each block. + // The pass also populates register reference members of each instruction, such as the input and output lanes. class RegisterAnnotationPass : public CFGPass { public: + RegisterAnnotationPass(RSXFragmentProgram& prog) + : m_prog(prog) + {} + void run(FlowGraph& graph) override; + + private: + const RSXFragmentProgram& m_prog; }; } diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp similarity index 76% rename from rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp rename to rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 290f1a4022..1e60c85519 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -1,7 +1,7 @@ #include "stdafx.h" #include "RegisterDependencyPass.h" -namespace rsx::assembler +namespace rsx::assembler::FP { void RegisterDependencyPass::run(FlowGraph& graph) { diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h similarity index 89% rename from rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h rename to rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h index b7af86761b..48068691e1 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterDependencyPass.h +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h @@ -1,8 +1,8 @@ #pragma once -#include "../CFG.h" +#include "../../CFG.h" -namespace rsx::assembler +namespace rsx::assembler::FP { // The register dependency pass identifies data hazards for each basic block and injects barrier instructions. // Real PS3 does not have explicit barriers, but does instead often use delay slots or fence instructions to stall until a specific hardware unit clears the fence to advance. diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp deleted file mode 100644 index 8d23080daa..0000000000 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include "stdafx.h" -#include "RegisterAnnotationPass.h" - -namespace rsx::assembler -{ - void RegisterAnnotationPass::run(FlowGraph& graph) - { - // TODO - } -} diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index f834b7c7f5..e20098ff57 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -1,6 +1,7 @@ #pragma once #include "program_util.h" +#include "Assembler/FPOpcodes.h" #include #include @@ -23,76 +24,7 @@ enum register_precision RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it? }; -enum fp_opcode -{ - RSX_FP_OPCODE_NOP = 0x00, // No-Operation - RSX_FP_OPCODE_MOV = 0x01, // Move - RSX_FP_OPCODE_MUL = 0x02, // Multiply - RSX_FP_OPCODE_ADD = 0x03, // Add - RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add - RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product - RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product - RSX_FP_OPCODE_DST = 0x07, // Distance - RSX_FP_OPCODE_MIN = 0x08, // Minimum - RSX_FP_OPCODE_MAX = 0x09, // Maximum - RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan - RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual - RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual - RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan - RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual - RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal - RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract) - RSX_FP_OPCODE_FLR = 0x11, // Floor - RSX_FP_OPCODE_KIL = 0x12, // Kill fragment - RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values - RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values - RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x) - RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y) - RSX_FP_OPCODE_TEX = 0x17, // Texture lookup - RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup) - RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives) - RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal - RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root - RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2 - RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2 - RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients - RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation - RSX_FP_OPCODE_STR = 0x20, // Set-If-True - RSX_FP_OPCODE_SFL = 0x21, // Set-If-False - RSX_FP_OPCODE_COS = 0x22, // Cosine - RSX_FP_OPCODE_SIN = 0x23, // Sine - RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats - RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats - RSX_FP_OPCODE_POW = 0x26, // Power - RSX_FP_OPCODE_PKB = 0x27, // Pack bytes - RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes - RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits - RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16 - RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform) - RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation - RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma - RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition - RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD - RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias - RSX_FP_OPCODE_TEXBEM = 0x33, - RSX_FP_OPCODE_TXPBEM = 0x34, - RSX_FP_OPCODE_BEMLUM = 0x35, - RSX_FP_OPCODE_REFL = 0x36, // Reflection vector - RSX_FP_OPCODE_TIMESWTEX = 0x37, - RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product - RSX_FP_OPCODE_NRM = 0x39, // Normalize - RSX_FP_OPCODE_DIV = 0x3A, // Division - RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root - RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT - RSX_FP_OPCODE_FENCT = 0x3D, // Fence T? - RSX_FP_OPCODE_FENCB = 0x3E, // Fence B? - RSX_FP_OPCODE_BRK = 0x40, // Break - RSX_FP_OPCODE_CAL = 0x41, // Subroutine call - RSX_FP_OPCODE_IFE = 0x42, // If - RSX_FP_OPCODE_LOOP = 0x43, // Loop - RSX_FP_OPCODE_REP = 0x44, // Repeat - RSX_FP_OPCODE_RET = 0x45 // Return -}; +using enum rsx::assembler::FP_opcode; union OPDEST { @@ -116,6 +48,12 @@ union OPDEST u32 no_dest : 1; u32 saturate : 1; // _sat }; + + struct + { + u32 : 9; + u32 write_mask : 4; + }; }; union SRC0 @@ -207,6 +145,23 @@ union SRC2 }; }; +union SRC_Common +{ + u32 HEX; + + struct + { + u32 reg_type : 2; + u32 tmp_reg_index : 6; + u32 fp16 : 1; + u32 swizzle_x : 2; + u32 swizzle_y : 2; + u32 swizzle_z : 2; + u32 swizzle_w : 2; + u32 neg : 1; + }; +}; + constexpr const char* rsx_fp_input_attr_regs[] = { "WPOS", "COL0", "COL1", "FOGC", "TEX0", diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 5e8971fab2..1b46634019 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -156,9 +156,10 @@ + - - + + @@ -703,9 +704,10 @@ + - - + + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index d0b2a25233..94cc8c5aed 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -139,6 +139,9 @@ {d13db076-47e4-45b9-bb8a-6b711ea40622} + + {7fb59544-9761-4b4a-bb04-07deb43cf3c2} + @@ -1381,11 +1384,14 @@ Emu\GPU\RSX\Program\Assembler - - Emu\GPU\RSX\Program\Assembler\Passes + + Emu\GPU\RSX\Program\Assembler\Passes\FP - - Emu\GPU\RSX\Program\Assembler\Passes + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler @@ -2785,11 +2791,14 @@ Emu\GPU\RSX\Program\Assembler - - Emu\GPU\RSX\Program\Assembler\Passes + + Emu\GPU\RSX\Program\Assembler - - Emu\GPU\RSX\Program\Assembler\Passes + + Emu\GPU\RSX\Program\Assembler\Passes\FP + + + Emu\GPU\RSX\Program\Assembler\Passes\FP From 88a54e2d989e5956dd344a532605424bdb890f07 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 30 Nov 2025 02:29:41 +0300 Subject: [PATCH 11/94] rsx/fp: Add a basic assembler to aid in test authoring --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 301 ++++++++++++++++++++++ rpcs3/Emu/RSX/Program/Assembler/FPASM.h | 28 ++ rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 + rpcs3/tests/rpcs3_test.vcxproj | 1 + rpcs3/tests/test_rsx_fp_asm.cpp | 33 +++ 6 files changed, 371 insertions(+) create mode 100644 rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp create mode 100644 rpcs3/Emu/RSX/Program/Assembler/FPASM.h create mode 100644 rpcs3/tests/test_rsx_fp_asm.cpp diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp new file mode 100644 index 0000000000..f8fcb3c571 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -0,0 +1,301 @@ +#include "stdafx.h" +#include "FPASM.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#ifndef _WIN32 +#define sscanf_s sscanf +#endif + +namespace rsx::assembler +{ + Instruction* FPIR::load(const RegisterRef& ref, int operand, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + SRC_Common src{ .HEX = target->bytecode[operand + 1] }; + src.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src.fp16 = ref.reg.f16 ? 1 : 0; + src.tmp_reg_index = static_cast(ref.reg.id); + + src.swizzle_x = 0; + src.swizzle_y = 1; + src.swizzle_z = 2; + src.swizzle_w = 3; + + target->bytecode[operand + 1] = src.HEX; + return target; + } + + Instruction* FPIR::load(const std::array& constants, int operand, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + // Unsupported for now + ensure(target->length == 4, "FPIR cannot encode more than one constant load per instruction"); + + SRC_Common src{ .HEX = target->bytecode[operand + 1] }; + src.reg_type = RSX_FP_REGISTER_TYPE_CONSTANT; + target->bytecode[operand + 1] = src.HEX; + + src.swizzle_x = 0; + src.swizzle_y = 1; + src.swizzle_z = 2; + src.swizzle_w = 3; + + // Embed literal constant + std::memcpy(&target->bytecode[4], constants.data(), 4 * sizeof(u32)); + target->length = 8; + return target; + } + + Instruction* FPIR::store(const RegisterRef& ref, Instruction* prev) + { + Instruction* target = prev; + if (!target) + { + m_instructions.push_back({}); + target = &m_instructions.back(); + } + + OPDEST dst{ .HEX = target->bytecode[0] }; + dst.dest_reg = static_cast(ref.reg.id); + dst.fp16 = ref.reg.f16 ? 1 : 0; + dst.write_mask = ref.mask; + dst.prec = ref.reg.f16 ? RSX_FP_PRECISION_HALF : RSX_FP_PRECISION_REAL; + + target->bytecode[0] = dst.HEX; + return target; + } + + void FPIR::mov(const RegisterRef& dst, f32 constant) + { + Instruction* inst = store(dst); + inst = load(std::array{ constant, constant, constant, constant }, 0); + inst->opcode = RSX_FP_OPCODE_MOV; + } + + void FPIR::mov(const RegisterRef& dst, const RegisterRef& src) + { + Instruction* inst = store(dst); + inst = load(src, 0); + inst->opcode = RSX_FP_OPCODE_MOV; + } + + void FPIR::add(const RegisterRef& dst, const std::array& constants) + { + Instruction* inst = store(dst); + inst = load(constants, 0); + inst->opcode = RSX_FP_OPCODE_ADD; + } + + void FPIR::add(const RegisterRef& dst, const RegisterRef& src) + { + Instruction* inst = store(dst); + inst = load(src, 0); + inst->opcode = RSX_FP_OPCODE_ADD; + } + + std::vector FPIR::build() + { + return m_instructions; + } + + FPIR FPIR::from_source(const std::string& asm_) + { + std::vector instructions = fmt::split(asm_, { "\n", ";" }); + if (instructions.empty()) + { + return {}; + } + + auto transform_inst = [](const std::string& s) + { + std::string result; + result.reserve(s.size()); + + bool literal = false; + for (auto& c : s) + { + if (c == ' ') + { + if (!literal && !result.empty() && result.back() != ',') + { + result += ','; // Replace token separator space with comma + } + continue; + } + + if (std::isspace(c)) + { + continue; + } + + if (!literal && c == '{') + { + literal = true; + } + + if (literal && c == '}') + { + literal = false; + } + + if (c == ',') + { + result += (literal ? '|' : ','); + continue; + } + + result += c; + } + return result; + }; + + auto decode_instruction = [&](const std::string& inst, std::string& op, std::string& dst, std::vector& sources) + { + const auto i = transform_inst(inst); + if (i.empty()) + { + return; + } + + const auto tokens = fmt::split(i, { "," }); + ensure(!tokens.empty(), "Invalid input"); + + op = tokens.front(); + + if (tokens.size() > 1) + { + dst = tokens[1]; + } + + for (size_t n = 2; n < tokens.size(); ++n) + { + sources.push_back(tokens[n]); + } + }; + + auto get_ref = [](const std::string& reg) + { + ensure(reg.length() > 1, "Invalid register specifier"); + + const auto index = std::stoi(reg.substr(1)); + RegisterRef ref + { + .reg { .id = index, .f16 = false }, + .mask = 0x0F + }; + + if (reg[0] == 'H' || reg[0] == 'h') + { + ref.reg.f16 = true; + } + + return ref; + }; + + auto get_constants = [](const std::string& reg) -> std::array + { + float x, y, z, w; + if (sscanf_s(reg.c_str(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4) + { + return { x, y, z, w }; + } + + if (sscanf_s(reg.c_str(), "#{%f}", &x) == 1) + { + return { x, x, x, x }; + } + + fmt::throw_exception("Invalid constant literal"); + }; + + auto encode_opcode = [](const std::string& op, Instruction* inst) + { + OPDEST d0 { .HEX = inst->bytecode[0] }; + + if (op == "MOV") + { + inst->opcode = d0.opcode = RSX_FP_OPCODE_MOV; + inst->bytecode[0] = d0.HEX; + return; + } + + if (op == "ADD") + { + inst->opcode = d0.opcode = RSX_FP_OPCODE_ADD; + inst->bytecode[0] = d0.HEX; + return; + } + + fmt::throw_exception("Unhandled instruction '%s'", op); + }; + + std::string op, dst; + std::vector sources; + + FPIR ir{}; + + for (const auto& instruction : instructions) + { + op.clear(); + dst.clear(); + sources.clear(); + decode_instruction(instruction, op, dst, sources); + + if (op.empty()) + { + continue; + } + + ir.m_instructions.push_back({}); + Instruction* target = &ir.m_instructions.back(); + + encode_opcode(op, target); + + if (dst.empty()) + { + OPDEST dst{}; + dst.no_dest = 1; + target->bytecode[0] = dst.HEX; + } + else + { + ir.store(get_ref(dst), target); + } + + int operand = 0; + for (const auto& source : sources) + { + if (source.front() == '#') + { + const auto literal = get_constants(source); + ir.load(literal, operand++, target); + continue; + } + + ir.load(get_ref(source), operand++, target); + } + } + + if (!ir.m_instructions.empty()) + { + OPDEST d0{ .HEX = ir.m_instructions.back().bytecode[0] }; + d0.end = 1; + + ir.m_instructions.back().bytecode[0] = d0.HEX; + } + + return ir; + } +} diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h new file mode 100644 index 0000000000..c30b1560b6 --- /dev/null +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h @@ -0,0 +1,28 @@ +#pragma once + +#include "IR.h" + +namespace rsx::assembler +{ + class FPIR + { + public: + void mov(const RegisterRef& dst, f32 constant); + void mov(const RegisterRef& dst, const RegisterRef& src); + + void add(const RegisterRef& dst, const std::array& constants); + void add(const RegisterRef& dst, const RegisterRef& src); + + std::vector build(); + + static FPIR from_source(const std::string& asm_); + + private: + Instruction* load(const RegisterRef& reg, int operand, Instruction* target = nullptr); + Instruction* load(const std::array& constants, int operand, Instruction* target = nullptr); + Instruction* store(const RegisterRef& reg, Instruction* target = nullptr); + + std::vector m_instructions; + }; +} + diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 1b46634019..761f42dd07 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -156,6 +156,7 @@ + @@ -704,6 +705,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 94cc8c5aed..937b22f5cc 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1393,6 +1393,9 @@ Emu\GPU\RSX\Program\Assembler + + Emu\GPU\RSX\Program\Assembler + @@ -2800,6 +2803,9 @@ Emu\GPU\RSX\Program\Assembler\Passes\FP + + Emu\GPU\RSX\Program\Assembler + diff --git a/rpcs3/tests/rpcs3_test.vcxproj b/rpcs3/tests/rpcs3_test.vcxproj index 22992e6a07..2851f2faa6 100644 --- a/rpcs3/tests/rpcs3_test.vcxproj +++ b/rpcs3/tests/rpcs3_test.vcxproj @@ -89,6 +89,7 @@ + diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp new file mode 100644 index 0000000000..1fe899e925 --- /dev/null +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -0,0 +1,33 @@ +#include + +#include "Emu/RSX/Common/simple_array.hpp" +#include "Emu/RSX/Program/Assembler/FPASM.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +namespace rsx::assembler +{ + TEST(TestFPIR, FromSource) + { + auto ir = FPIR::from_source(R"( + MOV R0, #{ 0.125 }; + ADD R1, R0; + )"); + + const auto instructions = ir.build(); + + ASSERT_EQ(instructions.size(), 2); + + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.end, 0); + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.opcode, RSX_FP_OPCODE_MOV); + EXPECT_EQ(SRC0{ .HEX = instructions[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_CONSTANT); + EXPECT_EQ(OPDEST{ .HEX = instructions[0].bytecode[0] }.opcode, RSX_FP_OPCODE_MOV); + EXPECT_EQ(instructions[0].length, 8); + + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.end, 1); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.opcode, RSX_FP_OPCODE_ADD); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.dest_reg, 1); + EXPECT_EQ(OPDEST{ .HEX = instructions[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = instructions[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(instructions[1].length, 4); + } +} From aaaa6feb5a6f4e69b0ba436274dea0600fecef9f Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 30 Nov 2025 21:46:46 +0300 Subject: [PATCH 12/94] rsx: Add UTs for register annotation pass and fix uncovered bugs --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 36 ++++++++-- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 5 +- rpcs3/Emu/RSX/Program/Assembler/IR.h | 15 ++++ .../Passes/FP/RegisterAnnotationPass.cpp | 6 +- rpcs3/tests/test_rsx_fp_asm.cpp | 71 ++++++++++++++++++- 5 files changed, 123 insertions(+), 10 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index f8fcb3c571..d1ef30b695 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -223,21 +223,48 @@ namespace rsx::assembler auto encode_opcode = [](const std::string& op, Instruction* inst) { OPDEST d0 { .HEX = inst->bytecode[0] }; + SRC0 s0 { .HEX = inst->bytecode[1] }; + +#define SET_OPCODE(code) \ + do { \ + inst->opcode = d0.opcode = code; \ + s0.exec_if_eq = s0.exec_if_gr = s0.exec_if_lt = 1; \ + inst->bytecode[0] = d0.HEX; \ + inst->bytecode[1] = s0.HEX; \ + } while (0) if (op == "MOV") { - inst->opcode = d0.opcode = RSX_FP_OPCODE_MOV; - inst->bytecode[0] = d0.HEX; + SET_OPCODE(RSX_FP_OPCODE_MOV); return; } if (op == "ADD") { - inst->opcode = d0.opcode = RSX_FP_OPCODE_ADD; - inst->bytecode[0] = d0.HEX; + SET_OPCODE(RSX_FP_OPCODE_ADD); return; } + if (op == "MAD" || op == "FMA") + { + SET_OPCODE(RSX_FP_OPCODE_MAD); + return; + } + + if (op == "UP4S") + { + SET_OPCODE(RSX_FP_OPCODE_UP4); + return; + } + + if (op == "PK4S") + { + SET_OPCODE(RSX_FP_OPCODE_PK4); + return; + } + +#undef SET_OPCODE + fmt::throw_exception("Unhandled instruction '%s'", op); }; @@ -262,6 +289,7 @@ namespace rsx::assembler Instruction* target = &ir.m_instructions.back(); encode_opcode(op, target); + ensure(sources.size() == FP::get_operand_count(static_cast(target->opcode)), "Invalid operand count for opcode"); if (dst.empty()) { diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp index 401bfd5492..890653f569 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -15,6 +15,7 @@ namespace rsx::assembler::FP case RSX_FP_OPCODE_NOP: return 0; case RSX_FP_OPCODE_MOV: + return 1; case RSX_FP_OPCODE_MUL: case RSX_FP_OPCODE_ADD: return 2; @@ -290,8 +291,8 @@ namespace rsx::assembler::FP u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand) { // Brute-force this. There's only 16 permutations. - constexpr u32 x = 0b0000; - constexpr u32 y = 0b0000; + constexpr u32 x = 0b0001; + constexpr u32 y = 0b0010; constexpr u32 z = 0b0100; constexpr u32 w = 0b1000; diff --git a/rpcs3/Emu/RSX/Program/Assembler/IR.h b/rpcs3/Emu/RSX/Program/Assembler/IR.h index 0fbd1f98ae..fff3d5aa83 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/IR.h +++ b/rpcs3/Emu/RSX/Program/Assembler/IR.h @@ -10,6 +10,16 @@ namespace rsx::assembler { int id = 0; bool f16 = false; + + bool operator == (const Register& other) const + { + return id == other.id && f16 == other.f16; + } + + std::string to_string() const + { + return std::string(f16 ? "H" : "R") + std::to_string(id); + } }; struct RegisterRef @@ -34,6 +44,11 @@ namespace rsx::assembler { return !!mask; } + + bool operator == (const RegisterRef& other) const + { + return reg == other.reg && mask == other.mask; + } }; struct Instruction diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index 196bc19361..ea0e9216fb 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -9,7 +9,7 @@ namespace rsx::assembler::FP { static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers static constexpr char content_unknown = 0; - static constexpr char content_float32 = 'F'; + static constexpr char content_float32 = 'R'; static constexpr char content_float16 = 'H'; static constexpr char content_dual = 'D'; @@ -125,7 +125,7 @@ namespace rsx::assembler::FP for (const auto& src : instruction.srcs) { const auto read_bytes = get_register_file_range(src); - const char expected_type = src.reg.f16 ? content_float16 : content_float16; + const char expected_type = src.reg.f16 ? content_float16 : content_float32; for (const auto& index : read_bytes) { if (output_register_file[index] != content_unknown) @@ -156,7 +156,7 @@ namespace rsx::assembler::FP { const auto& dst = instruction.dsts.front(); const auto write_bytes = get_register_file_range(dst); - const char expected_type = dst.reg.f16 ? content_float16 : content_float16; + const char expected_type = dst.reg.f16 ? content_float16 : content_float32; for (const auto& index : write_bytes) { diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 1fe899e925..9ae23fa81c 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -2,15 +2,56 @@ #include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Program/Assembler/FPASM.h" +#include "Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" namespace rsx::assembler { +#define DECLARE_REG32(num)\ + Register R##num{ .id = num, .f16 = false } + +#define DECLARE_REG16(num)\ + Register H##num{ .id = num, .f16 = true } + + DECLARE_REG32(0); + DECLARE_REG32(1); + DECLARE_REG32(2); + DECLARE_REG32(3); + DECLARE_REG32(4); + DECLARE_REG32(5); + DECLARE_REG32(6); + DECLARE_REG32(7); + DECLARE_REG32(8); + + DECLARE_REG16(0); + DECLARE_REG16(1); + DECLARE_REG16(2); + DECLARE_REG16(3); + DECLARE_REG16(4); + DECLARE_REG16(5); + DECLARE_REG16(6); + DECLARE_REG16(7); + DECLARE_REG16(8); + +#undef DECLARE_REG32 +#undef DECLARE_REG16 + + static FlowGraph CFG_from_source(const std::string& asm_) + { + auto ir = FPIR::from_source(asm_); + + FlowGraph graph{}; + graph.blocks.push_back({}); + + auto& bb = graph.blocks.back(); + bb.instructions = ir.build(); + return graph; + } TEST(TestFPIR, FromSource) { auto ir = FPIR::from_source(R"( MOV R0, #{ 0.125 }; - ADD R1, R0; + ADD R1, R0, R0; )"); const auto instructions = ir.build(); @@ -30,4 +71,32 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = instructions[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); EXPECT_EQ(instructions[1].length, 4); } + + TEST(TestFPIR, RegisterAnnotationPass) + { + // Code snippet reads from R0 and R2, clobbers R0, R1, H0 + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H0, H4; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 2); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + FP::RegisterAnnotationPass annotation_pass(prog); + + annotation_pass.run(graph); + + ASSERT_EQ(block.clobber_list.size(), 2); + ASSERT_EQ(block.input_list.size(), 3); + + EXPECT_EQ(block.clobber_list[0].reg, H0); + EXPECT_EQ(block.clobber_list[1].reg, R1); + + EXPECT_EQ(block.input_list[0].reg, H4); + EXPECT_EQ(block.input_list[1].reg, R0); + EXPECT_EQ(block.input_list[2].reg, R1); + } } From 13d66f324bfb83cd3c1c1d98f1db852bfec56a24 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 30 Nov 2025 21:48:48 +0300 Subject: [PATCH 13/94] rsx/shaders: Fix nix builds --- rpcs3/Emu/CMakeLists.txt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index d556b3ebdc..395babec48 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -516,9 +516,11 @@ target_sources(rpcs3_emu PRIVATE RSX/Overlays/overlay_video.cpp RSX/Overlays/Shaders/shader_loading_dialog.cpp RSX/Overlays/Shaders/shader_loading_dialog_native.cpp + RSX/Program/Assembler/FPASM.cpp + RSX/Program/Assembler/FPOpcodes.cpp RSX/Program/Assembler/FPToCFG.cpp - RSX/Program/Assembler/Passes/RegisterAnnotationPass.cpp - RSX/Program/Assembler/Passes/RegisterDependencyPass.cpp + RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp + RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp RSX/Program/CgBinaryProgram.cpp RSX/Program/CgBinaryFragmentProgram.cpp RSX/Program/CgBinaryVertexProgram.cpp From ed397c5d67ceac1435b3d713382e75627bf6ff10 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 30 Nov 2025 23:14:40 +0300 Subject: [PATCH 14/94] rsx/asm: Add support for more opcodes and add tests for mixed block IO --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 58 ++++++++++++----------- rpcs3/tests/test_rsx_fp_asm.cpp | 32 ++++++++++++- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index d1ef30b695..da3f47e940 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -8,6 +8,32 @@ namespace rsx::assembler { + static std::unordered_map s_opcode_lookup + { + // Arithmetic + { "NOP", RSX_FP_OPCODE_NOP }, + { "MOV", RSX_FP_OPCODE_MOV }, + { "ADD", RSX_FP_OPCODE_ADD }, + { "MAD", RSX_FP_OPCODE_MAD }, + { "FMA", RSX_FP_OPCODE_MAD }, + { "DP3", RSX_FP_OPCODE_DP3 }, + { "DP4", RSX_FP_OPCODE_DP4 }, + + // Pack-unpack operations are great for testing dependencies + { "PKH", RSX_FP_OPCODE_PK2 }, + { "UPH", RSX_FP_OPCODE_UP2 }, + { "PK16U", RSX_FP_OPCODE_PK16 }, + { "UP16U", RSX_FP_OPCODE_UP16 }, + { "PK8U", RSX_FP_OPCODE_PKB }, + { "UP8U", RSX_FP_OPCODE_UPB }, + { "PK8G", RSX_FP_OPCODE_PKG }, + { "UP8G", RSX_FP_OPCODE_UPG }, + { "PK8S", RSX_FP_OPCODE_PK4 }, + { "UP8S", RSX_FP_OPCODE_UP4 }, + // TODO: Add more + + }; + Instruction* FPIR::load(const RegisterRef& ref, int operand, Instruction* prev) { Instruction* target = prev; @@ -233,39 +259,15 @@ namespace rsx::assembler inst->bytecode[1] = s0.HEX; \ } while (0) - if (op == "MOV") + const auto found = s_opcode_lookup.find(op); + if (found == s_opcode_lookup.end()) { - SET_OPCODE(RSX_FP_OPCODE_MOV); - return; + fmt::throw_exception("Unhandled instruction '%s'", op); } - if (op == "ADD") - { - SET_OPCODE(RSX_FP_OPCODE_ADD); - return; - } - - if (op == "MAD" || op == "FMA") - { - SET_OPCODE(RSX_FP_OPCODE_MAD); - return; - } - - if (op == "UP4S") - { - SET_OPCODE(RSX_FP_OPCODE_UP4); - return; - } - - if (op == "PK4S") - { - SET_OPCODE(RSX_FP_OPCODE_PK4); - return; - } + SET_OPCODE(found->second); #undef SET_OPCODE - - fmt::throw_exception("Unhandled instruction '%s'", op); }; std::string op, dst; diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 9ae23fa81c..b09088c80c 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -74,7 +74,7 @@ namespace rsx::assembler TEST(TestFPIR, RegisterAnnotationPass) { - // Code snippet reads from R0 and R2, clobbers R0, R1, H0 + // Code snippet reads from R0, R1 and H4, clobbers R1, H0 auto graph = CFG_from_source(R"( ADD R1, R0, R1; MOV H0, H4; @@ -99,4 +99,34 @@ namespace rsx::assembler EXPECT_EQ(block.input_list[1].reg, R0); EXPECT_EQ(block.input_list[2].reg, R1); } + + TEST(TestFPIR, RegisterAnnotationPass_MixedIO) + { + // Code snippet reads from R0, R1, clobbers R0, R1, H0. + // The H2 read does not count because R1 is clobbered. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + PK8U R0, R1; + MOV H0, H2; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + FP::RegisterAnnotationPass annotation_pass(prog); + + annotation_pass.run(graph); + + ASSERT_EQ(block.clobber_list.size(), 3); + ASSERT_EQ(block.input_list.size(), 2); + + EXPECT_EQ(block.clobber_list[0].reg, H0); + EXPECT_EQ(block.clobber_list[1].reg, R0); + EXPECT_EQ(block.clobber_list[2].reg, R1); + + EXPECT_EQ(block.input_list[0].reg, R0); + EXPECT_EQ(block.input_list[1].reg, R1); + } } From 5a8ad219ba373c83635f67c9e48c853a1e6dc130 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 1 Dec 2025 03:19:46 +0300 Subject: [PATCH 15/94] rsx/asm: Implement dependency barrier injection pass --- .../Passes/FP/RegisterDependencyPass.cpp | 280 +++++++++++++++++- rpcs3/tests/test_rsx_fp_asm.cpp | 111 ++++++- 2 files changed, 388 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 1e60c85519..33bad149ea 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -1,10 +1,288 @@ #include "stdafx.h" #include "RegisterDependencyPass.h" +#include "Emu/RSX/Program/Assembler/FPOpcodes.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" + +#include namespace rsx::assembler::FP { + static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers + static constexpr char content_unknown = 0; + static constexpr char content_float32 = 'R'; + static constexpr char content_float16 = 'H'; + static constexpr char content_dual = 'D'; + + std::vector decode_lanes16(const std::unordered_set& lanes) + { + std::vector result; + + for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 8) + { + // Each register has 4 16-bit lanes + u32 mask = 0; + if (lanes.contains(file_offset + 0)) mask |= (1u << 0); + if (lanes.contains(file_offset + 2)) mask |= (1u << 1); + if (lanes.contains(file_offset + 4)) mask |= (1u << 2); + if (lanes.contains(file_offset + 6)) mask |= (1u << 3); + + if (mask == 0) + { + continue; + } + + RegisterRef ref{ .reg{.id = static_cast(index), .f16 = true } }; + ref.mask = mask; + result.push_back(ref); + } + return result; + } + + std::vector decode_lanes32(const std::unordered_set& lanes) + { + std::vector result; + + for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 16) + { + // Each register has 8 16-bit lanes + + u32 mask = 0; + if (lanes.contains(file_offset + 0) || lanes.contains(file_offset + 2)) mask |= (1u << 0); + if (lanes.contains(file_offset + 4) || lanes.contains(file_offset + 6)) mask |= (1u << 1); + if (lanes.contains(file_offset + 8) || lanes.contains(file_offset + 10)) mask |= (1u << 2); + if (lanes.contains(file_offset + 12) || lanes.contains(file_offset + 14)) mask |= (1u << 3); + + if (mask == 0) + { + continue; + } + + RegisterRef ref{ .reg{.id = static_cast(index), .f16 = false } }; + ref.mask = mask; + result.push_back(ref); + } + + return result; + } + + std::vector build_barrier32(const RegisterRef& reg) + { + // Upto 4 instructions are needed per 32-bit register + // R0.x = packHalf2x16(H0.xy) + // R0.y = packHalf2x16(H0.zw); + // R0.z = packHalf2x16(H1.xy); + // R0.w = packHalf2x16(H1.zw); + + std::vector result; + + for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch) + { + if (!(mask & 1)) + { + continue; + } + + Instruction instruction{}; + OPDEST dst{}; + dst.opcode = RSX_FP_OPCODE_PK2; + dst.prec = RSX_FP_PRECISION_REAL; + dst.fp16 = 0; + dst.dest_reg = reg.reg.id; + dst.write_mask = (1u << ch); + + const u32 src_reg_id = (ch / 2) + (reg.reg.id * 2); + const bool is_word0 = !(ch & 1); // Only even + + SRC0 src0{}; + src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; + src0.fp16 = 1; + + if (is_word0) + { + src0.swizzle_x = 0; + src0.swizzle_y = 1; + } + else + { + src0.swizzle_x = 2; + src0.swizzle_y = 3; + } + + src0.swizzle_z = 2; + src0.swizzle_w = 3; + src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src0.tmp_reg_index = src_reg_id; + + instruction.opcode = dst.opcode; + instruction.bytecode[0] = dst.HEX; + instruction.bytecode[1] = src0.HEX; + + Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; + instruction.srcs.push_back({ .reg=src_reg, .mask=0xF }); + instruction.dsts.push_back({ .reg{ .id = reg.reg.id, .f16 = false }, .mask = (1u << ch) }); + result.push_back(instruction); + } + + return result; + } + + std::vector build_barrier16(const RegisterRef& reg) + { + // H0.xy = unpackHalf2x16(R0.x) + // H0.zw = unpackHalf2x16(R0.y) + // H1.xy = unpackHalf2x16(R0.z) + // H1.zw = unpackHalf2x16(R0.w) + + std::vector result; + + for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch) + { + if (!(mask & 1)) + { + continue; + } + + Instruction instruction{}; + OPDEST dst{}; + dst.opcode = RSX_FP_OPCODE_UP2; + dst.prec = RSX_FP_PRECISION_HALF; + dst.fp16 = 1; + dst.dest_reg = reg.reg.id; + dst.write_mask = 1u << ch; + + const u32 src_reg_id = reg.reg.id / 2; + const bool is_odd_reg = !!(reg.reg.id & 1); + const bool is_word0 = ch < 2; + + // If we're a non-odd register, we should also write the next channel (y/w) + if (!is_odd_reg && (mask & 2)) + { + mask >>= 1; + ++ch; + dst.write_mask |= (1u << ch); + } + + SRC0 src0{}; + src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; + + if (is_word0) + { + src0.swizzle_x = is_odd_reg ? 2 : 0; + } + else + { + src0.swizzle_x = is_odd_reg ? 3 : 1; + } + + src0.swizzle_y = 1; + src0.swizzle_z = 2; + src0.swizzle_w = 3; + src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src0.tmp_reg_index = src_reg_id; + + instruction.opcode = dst.opcode; + instruction.bytecode[0] = dst.HEX; + instruction.bytecode[1] = src0.HEX; + + Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; + instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); + instruction.dsts.push_back({ .reg{.id = reg.reg.id, .f16 = false }, .mask = dst.write_mask }); + result.push_back(instruction); + } + + return result; + } + + void insert_dependency_barriers(BasicBlock* block) + { + std::array register_file; + std::memset(register_file.data(), content_unknown, register_file_length); + + std::unordered_set barrier16; + std::unordered_set barrier32; + + // This subpass does not care about the prologue and epilogue and assumes each block is unique. + for (auto it = block->instructions.begin(); it != block->instructions.end();) + { + auto& inst = *it; + + barrier16.clear(); + barrier32.clear(); + + for (const auto& src : inst.srcs) + { + const auto read_bytes = get_register_file_range(src); + const char expected_type = src.reg.f16 ? content_float16 : content_float32; + for (const auto& index : read_bytes) + { + if (register_file[index] == content_unknown) + { + // Skip input + continue; + } + + if (register_file[index] == expected_type || register_file[index] == content_dual) + { + // Match - nothing to do + continue; + } + + // Collision on the lane + register_file[index] = content_dual; + (src.reg.f16 ? barrier16 : barrier32).insert(index); + } + } + + for (const auto& dst : inst.dsts) + { + const auto write_bytes = get_register_file_range(dst); + const char expected_type = dst.reg.f16 ? content_float16 : content_float32; + + for (const auto& index : write_bytes) + { + register_file[index] = expected_type; + } + } + + if (barrier16.empty() && barrier32.empty()) + { + ++it; + continue; + } + + // We need to inject some barrier instructions + if (!barrier16.empty()) + { + auto barrier16_in = decode_lanes16(barrier16); + for (const auto& reg : barrier16_in) + { + auto instructions = build_barrier16(reg); + it = block->instructions.insert(it, instructions.begin(), instructions.end()); + std::advance(it, instructions.size() + 1); + } + } + + if (!barrier32.empty()) + { + auto barrier32_in = decode_lanes32(barrier32); + for (const auto& reg : barrier32_in) + { + auto instructions = build_barrier32(reg); + it = block->instructions.insert(it, instructions.begin(), instructions.end()); + std::advance(it, instructions.size() + 1); + } + } + } + } + void RegisterDependencyPass::run(FlowGraph& graph) { - // TODO + // First, run intra-block dependency + for (auto& block : graph.blocks) + { + insert_dependency_barriers(&block); + } + + // TODO: Create prologue/epilogue instructions } } diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index b09088c80c..61202aa3e1 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -3,6 +3,7 @@ #include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Program/Assembler/FPASM.h" #include "Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h" +#include "Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" namespace rsx::assembler @@ -85,7 +86,7 @@ namespace rsx::assembler auto& block = graph.blocks.front(); RSXFragmentProgram prog{}; - FP::RegisterAnnotationPass annotation_pass(prog); + FP::RegisterAnnotationPass annotation_pass{ prog }; annotation_pass.run(graph); @@ -115,7 +116,7 @@ namespace rsx::assembler auto& block = graph.blocks.front(); RSXFragmentProgram prog{}; - FP::RegisterAnnotationPass annotation_pass(prog); + FP::RegisterAnnotationPass annotation_pass{ prog }; annotation_pass.run(graph); @@ -129,4 +130,110 @@ namespace rsx::assembler EXPECT_EQ(block.input_list[0].reg, R0); EXPECT_EQ(block.input_list[1].reg, R1); } + + TEST(TestFPIR, RegisterDependencyPass_Simple16) + { + // Instruction 2 clobers R0 which in turn clobbers H0. + // Instruction 3 reads from H0 so a barrier16 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + PK8U R0, R1; + MOV H2, H0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 5); + + // H0.xy = unpackHalf2(r0.x); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.opcode, RSX_FP_OPCODE_UP2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.fp16, 1); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.tmp_reg_index, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_x, 0); + + // H0.zw = unpackHalf2(r0.y); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.opcode, RSX_FP_OPCODE_UP2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_z, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_w, true); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.tmp_reg_index, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.fp16, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_x, 1); + } + + TEST(TestFPIR, RegisterDependencyPass_Simple32) + { + // Instruction 2 clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1, R1 + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 5); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.dest_reg, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_z, true); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[2].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.tmp_reg_index, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = block.instructions[2].bytecode[1] }.swizzle_y, 1); + + // R0.w = packHalf2(H1.zw); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.dest_reg, 0); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = block.instructions[3].bytecode[0] }.mask_w, true); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.tmp_reg_index, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_y, 3); + } + + TEST(TestFPIR, RegisterDependencyPass_Complex) + { + // TODO: Multi-level block structure with nested IFs/LOOPs + } } From 65b4bcb027225e60ca0ba8af4182a82b487c0abf Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 3 Dec 2025 14:14:30 +0300 Subject: [PATCH 16/94] rsx/fp/asm: Add support for more instructions --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 74 ++++++++++++++++------- 1 file changed, 53 insertions(+), 21 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index da3f47e940..d83acf4d45 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -8,28 +8,53 @@ namespace rsx::assembler { - static std::unordered_map s_opcode_lookup + struct FP_opcode_encoding_t + { + FP_opcode op; + bool exec_if_lt; + bool exec_if_eq; + bool exec_if_gt; + bool set_cond; + }; + + static std::unordered_map s_opcode_lookup { // Arithmetic - { "NOP", RSX_FP_OPCODE_NOP }, - { "MOV", RSX_FP_OPCODE_MOV }, - { "ADD", RSX_FP_OPCODE_ADD }, - { "MAD", RSX_FP_OPCODE_MAD }, - { "FMA", RSX_FP_OPCODE_MAD }, - { "DP3", RSX_FP_OPCODE_DP3 }, - { "DP4", RSX_FP_OPCODE_DP4 }, + { "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MOV", { .op = RSX_FP_OPCODE_MOV, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "ADD", { .op = RSX_FP_OPCODE_ADD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MAD", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "FMA", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "DP3", { .op = RSX_FP_OPCODE_DP3, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "DP4", { .op = RSX_FP_OPCODE_DP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + + // Constant load + { "SFL", {.op = RSX_FP_OPCODE_SFL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "STR", {.op = RSX_FP_OPCODE_STR, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, // Pack-unpack operations are great for testing dependencies - { "PKH", RSX_FP_OPCODE_PK2 }, - { "UPH", RSX_FP_OPCODE_UP2 }, - { "PK16U", RSX_FP_OPCODE_PK16 }, - { "UP16U", RSX_FP_OPCODE_UP16 }, - { "PK8U", RSX_FP_OPCODE_PKB }, - { "UP8U", RSX_FP_OPCODE_UPB }, - { "PK8G", RSX_FP_OPCODE_PKG }, - { "UP8G", RSX_FP_OPCODE_UPG }, - { "PK8S", RSX_FP_OPCODE_PK4 }, - { "UP8S", RSX_FP_OPCODE_UP4 }, + { "PKH", { .op = RSX_FP_OPCODE_PK2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UPH", { .op = RSX_FP_OPCODE_UP2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK16U", { .op = RSX_FP_OPCODE_PK16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP16U", { .op = RSX_FP_OPCODE_UP16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8U", { .op = RSX_FP_OPCODE_PKB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8U", { .op = RSX_FP_OPCODE_UPB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8G", { .op = RSX_FP_OPCODE_PKG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8G", { .op = RSX_FP_OPCODE_UPG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "PK8S", { .op = RSX_FP_OPCODE_PK4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "UP8S", { .op = RSX_FP_OPCODE_UP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + + // Basic conditionals + { "IF.LT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = false, .exec_if_gt = false, .set_cond = false } }, + { "IF.LE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } }, + { "IF.EQ", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } }, + { "IF.GE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "IF.GT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = true, .set_cond = false } }, + + { "SLT", { .op = RSX_FP_OPCODE_SLT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + { "SEQ", { .op = RSX_FP_OPCODE_SEQ, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + { "SGT", { .op = RSX_FP_OPCODE_SGT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } }, + // TODO: Add more }; @@ -250,13 +275,20 @@ namespace rsx::assembler { OPDEST d0 { .HEX = inst->bytecode[0] }; SRC0 s0 { .HEX = inst->bytecode[1] }; + SRC1 s1 { .HEX = inst->bytecode[2] }; -#define SET_OPCODE(code) \ +#define SET_OPCODE(encoding) \ do { \ - inst->opcode = d0.opcode = code; \ - s0.exec_if_eq = s0.exec_if_gr = s0.exec_if_lt = 1; \ + inst->opcode = encoding.op; \ + d0.opcode = encoding.op; \ + s1.opcode_is_branch = (encoding.op > 0x3F)? 1 : 0; \ + s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; \ + s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; \ + s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0; \ + d0.set_cond = encoding.set_cond ? 1 : 0; \ inst->bytecode[0] = d0.HEX; \ inst->bytecode[1] = s0.HEX; \ + inst->bytecode[2] = s1.HEX; \ } while (0) const auto found = s_opcode_lookup.find(op); From fa40cef0b12a6b7e527284967c7be42000341489 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 3 Dec 2025 14:15:01 +0300 Subject: [PATCH 17/94] rsx/fp/cfg: Insert bi-directional edges correctly during traversal --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 055d74a88c..7887a8a9f4 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -101,6 +101,28 @@ namespace rsx::assembler if (found) { + if (!bb->pred.empty()) + { + switch (bb->pred.back().type) + { + case EdgeType::IF: + case EdgeType::ELSE: + bb->insert_succ(*found, EdgeType::ENDIF); + break; + case EdgeType::LOOP: + bb->insert_succ(*found, EdgeType::ENDLOOP); + break; + default: + // Missing an edge type? + rsx_log.error("CFG: Unexpected block exit. Report to developers."); + } + } + else + { + // Impossible situation. + rsx_log.error("CFG: Child block has no parent but has successor! Report to developers."); + } + bb = *found; } From 8ff3dda5e8c740bf2e08aceb5d2834215cfe7105 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 3 Dec 2025 14:15:55 +0300 Subject: [PATCH 18/94] rsx/cfg: Skip literal constants when annotating instructions --- .../Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index ea0e9216fb..b96856b7a6 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -98,7 +98,11 @@ namespace rsx::assembler::FP for (u32 i = 0; i < operand_count; i++) { RegisterRef reg = get_src_register(prog, &instruction, i); - ensure(reg.mask, "Invalid register read"); + if (!reg.mask) + { + // Likely a literal constant + continue; + } instruction.srcs.push_back(reg); } From 856eaac1b66c192405e186bf9c91cd6f595e98eb Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 3 Dec 2025 14:16:37 +0300 Subject: [PATCH 19/94] rsx/cfg: Implement dependency injection pass with branches --- .../Passes/FP/RegisterDependencyPass.cpp | 126 +++++++++++++++++- rpcs3/tests/test_rsx_fp_asm.cpp | 88 +++++++++++- 2 files changed, 208 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 33bad149ea..0f528f66f3 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -3,6 +3,7 @@ #include "Emu/RSX/Program/Assembler/FPOpcodes.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" +#include #include namespace rsx::assembler::FP @@ -13,6 +14,14 @@ namespace rsx::assembler::FP static constexpr char content_float16 = 'H'; static constexpr char content_dual = 'D'; + using register_file_t = std::array; + + struct DependencyPassContext + { + std::unordered_map exec_register_map; + std::unordered_map sync_register_map; + }; + std::vector decode_lanes16(const std::unordered_set& lanes) { std::vector result; @@ -193,9 +202,23 @@ namespace rsx::assembler::FP return result; } - void insert_dependency_barriers(BasicBlock* block) + std::vector resolve_dependencies(const std::unordered_set& lanes, bool f16) { - std::array register_file; + std::vector result; + + const auto regs = (f16 ? decode_lanes16 : decode_lanes32)(lanes); + for (const auto& ref : regs) + { + auto instructions = (f16 ? build_barrier16 : build_barrier32)(ref); + result.insert(result.end(), instructions.begin(), instructions.end()); + } + + return result; + } + + void insert_dependency_barriers(DependencyPassContext& ctx, BasicBlock* block) + { + register_file_t& register_file = ctx.exec_register_map[block]; std::memset(register_file.data(), content_unknown, register_file_length); std::unordered_set barrier16; @@ -275,14 +298,109 @@ namespace rsx::assembler::FP } } + void insert_block_register_dependency(DependencyPassContext& ctx, BasicBlock* block, const std::unordered_set& lanes, bool f16) + { + if (block->pred.empty()) + { + return; + } + + std::unordered_set clobbered_lanes; + std::unordered_set lanes_to_search; + + for (auto& back_edge : block->pred) + { + auto target = back_edge.from; + + // Did this target even clobber our register? + ensure(ctx.exec_register_map.find(target) != ctx.exec_register_map.end(), "Block has not been pre-processed"); + + if (ctx.sync_register_map.find(target) == ctx.sync_register_map.end()) + { + auto& blob = ctx.sync_register_map[target]; + std::memset(blob.data(), content_unknown, register_file_length); + } + + auto& sync_register_file = ctx.sync_register_map[target]; + const auto& exec_register_file = ctx.exec_register_map[target]; + const auto clobber_type = f16 ? content_float32 : content_float16; + + lanes_to_search.clear(); + clobbered_lanes.clear(); + + for (auto& lane : lanes) + { + if (exec_register_file[lane] == clobber_type && + sync_register_file[lane] == content_unknown) + { + clobbered_lanes.insert(lane); + sync_register_file[lane] = content_dual; + continue; + } + + if (exec_register_file[lane] == content_unknown) + { + lanes_to_search.insert(lane); + } + } + + if (!clobbered_lanes.empty()) + { + const auto instructions = resolve_dependencies(clobbered_lanes, f16); + target->epilogue.insert(target->epilogue.end(), instructions.begin(), instructions.end()); + } + + if (lanes_to_search.empty()) + { + break; + } + + // We have some missing lanes. Search upwards + if (!target->pred.empty()) + { + // We only need to search the last predecessor which is the true "root" of the branch + auto parent = target->pred.back().from; + insert_block_register_dependency(ctx, parent, lanes_to_search, f16); + } + } + } + + void insert_block_dependencies(DependencyPassContext& ctx, BasicBlock* block) + { + auto range_from_ref = [](const RegisterRef& ref) + { + const auto range = get_register_file_range(ref); + + std::unordered_set result; + for (const auto& value : range) + { + result.insert(value); + } + return result; + }; + + for (auto& ref : block->input_list) + { + const auto range = range_from_ref(ref); + insert_block_register_dependency(ctx, block, range, ref.reg.f16); + } + } + void RegisterDependencyPass::run(FlowGraph& graph) { + DependencyPassContext ctx{}; + // First, run intra-block dependency for (auto& block : graph.blocks) { - insert_dependency_barriers(&block); + insert_dependency_barriers(ctx, &block); } - // TODO: Create prologue/epilogue instructions + // Then, create prologue/epilogue instructions + // Traverse the list in reverse order to bubble up dependencies correctly. + for (auto it = graph.blocks.rbegin(); it != graph.blocks.rend(); ++it) + { + insert_block_dependencies(ctx, &(*it)); + } } } diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 61202aa3e1..fc28525f94 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -48,6 +48,15 @@ namespace rsx::assembler bb.instructions = ir.build(); return graph; } + + static BasicBlock* BB_from_source(FlowGraph* graph, const std::string& asm_) + { + auto ir = FPIR::from_source(asm_); + graph->blocks.push_back({}); + BasicBlock& bb = graph->blocks.back(); + bb.instructions = ir.build(); + return &bb; + } TEST(TestFPIR, FromSource) { auto ir = FPIR::from_source(R"( @@ -232,8 +241,83 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = block.instructions[3].bytecode[1] }.swizzle_y, 3); } - TEST(TestFPIR, RegisterDependencyPass_Complex) + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_BothPredecessorsClobber) { - // TODO: Multi-level block structure with nested IFs/LOOPs + // Multi-level but only single IF + // Mockup of a simple lighting function, R0 = Light vector, R1 = Decompressed normal. DP4 used for simplicity. + // Data hazards sprinkled in for testing. R3 is clobbered in the ancestor and the IF branch. + // Barrier should go in the IF branch here. + FlowGraph graph; + BasicBlock* bb0 = BB_from_source(&graph, R"( + DP4 R2, R0, R1 + SFL R3 + SGT R3, R2, R0 + IF.GE + )"); + + BasicBlock* bb1 = BB_from_source(&graph, R"( + ADD R0, R0, R2 + MOV H6, #{ 0.25 } + )"); + + BasicBlock* bb2 = BB_from_source(&graph, R"( + ADD R0, R0, R3 + MOV R1, R0 + )"); + + // Front edges + bb0->insert_succ(bb1, EdgeType::IF); + bb0->insert_succ(bb2, EdgeType::ENDIF); + bb1->insert_succ(bb2, EdgeType::ENDIF); + + // Back edges + bb2->insert_pred(bb1, EdgeType::ENDIF); + bb2->insert_pred(bb0, EdgeType::ENDIF); + bb1->insert_pred(bb0, EdgeType::IF); + + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(bb0->instructions.size(), 4); + ASSERT_EQ(bb1->instructions.size(), 2); + ASSERT_EQ(bb2->instructions.size(), 2); + + // bb1 has a epilogue + ASSERT_EQ(bb1->epilogue.size(), 2); + + // bb1 epilogue updates R3.xy + + // R3.x = packHalf2(H6.xy) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_y, 1); + + // R3.y = packHalf2(H6.zw) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); } } From f4ba548748b1fe46a89881e42dcdb4ff4b3e0e52 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 5 Dec 2025 03:48:13 +0300 Subject: [PATCH 20/94] rsx/fp/asm: Implement asm support for branches - Allows creating more complex command sequences from text --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 84 ++++++++++++++++++++++- rpcs3/Emu/RSX/Program/Assembler/FPASM.h | 3 +- 2 files changed, 83 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index d83acf4d45..7b32ff56d5 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -2,6 +2,8 @@ #include "FPASM.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" +#include + #ifndef _WIN32 #define sscanf_s sscanf #endif @@ -156,11 +158,32 @@ namespace rsx::assembler inst->opcode = RSX_FP_OPCODE_ADD; } - std::vector FPIR::build() + const std::vector& FPIR::instructions() const { return m_instructions; } + std::vector FPIR::compile() const + { + std::vector result; + result.reserve(m_instructions.size() * 4); + + for (u32 i = 0; i < m_instructions.size(); ++i) + { + const auto& inst = m_instructions[i]; + auto src = reinterpret_cast*>(inst.bytecode); + for (u32 j = 0; j < inst.length; ++j) + { + const u16 low = src[j * 2]; + const u16 hi = src[j * 2 + 1]; + const u32 word = static_cast(low) | (static_cast(hi) << 16u); + result.push_back(word); + } + } + + return result; + } + FPIR FPIR::from_source(const std::string& asm_) { std::vector instructions = fmt::split(asm_, { "\n", ";" }); @@ -271,6 +294,20 @@ namespace rsx::assembler fmt::throw_exception("Invalid constant literal"); }; + auto encode_branch_end = [](Instruction *inst, u32 end) + { + SRC2 src2 { .HEX = inst->bytecode[3] }; + src2.end_offset = static_cast(end); + inst->bytecode[3] = src2.HEX; + }; + + auto encode_branch_else = [](Instruction* inst, u32 end) + { + SRC1 src1{ .HEX = inst->bytecode[2] }; + src1.else_offset = static_cast(end); + inst->bytecode[2] = src1.HEX; + }; + auto encode_opcode = [](const std::string& op, Instruction* inst) { OPDEST d0 { .HEX = inst->bytecode[0] }; @@ -280,7 +317,7 @@ namespace rsx::assembler #define SET_OPCODE(encoding) \ do { \ inst->opcode = encoding.op; \ - d0.opcode = encoding.op; \ + d0.opcode = encoding.op & 0x3F; \ s1.opcode_is_branch = (encoding.op > 0x3F)? 1 : 0; \ s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; \ s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; \ @@ -305,6 +342,10 @@ namespace rsx::assembler std::string op, dst; std::vector sources; + std::stack if_ops; + std::stack loop_ops; + u32 pc = 0; + FPIR ir{}; for (const auto& instruction : instructions) @@ -319,15 +360,45 @@ namespace rsx::assembler continue; } + if (op.starts_with("IF.")) + { + if_ops.push(ir.m_instructions.size()); + } + else if (op == "LOOP") + { + loop_ops.push(ir.m_instructions.size()); + } + else if (op == "ELSE") + { + ensure(!if_ops.empty()); + encode_branch_else(&ir.m_instructions[if_ops.top()], pc); + continue; + } + else if (op == "ENDIF") + { + ensure(!if_ops.empty()); + encode_branch_end(&ir.m_instructions[if_ops.top()], pc); + if_ops.pop(); + continue; + } + else if (op == "ENDLOOP") + { + ensure(!loop_ops.empty()); + encode_branch_end(&ir.m_instructions[loop_ops.top()], pc); + loop_ops.pop(); + continue; + } + ir.m_instructions.push_back({}); Instruction* target = &ir.m_instructions.back(); + pc += 4; encode_opcode(op, target); ensure(sources.size() == FP::get_operand_count(static_cast(target->opcode)), "Invalid operand count for opcode"); if (dst.empty()) { - OPDEST dst{}; + OPDEST dst{ .HEX = target->bytecode[0] }; dst.no_dest = 1; target->bytecode[0] = dst.HEX; } @@ -337,17 +408,24 @@ namespace rsx::assembler } int operand = 0; + bool has_literal = false; for (const auto& source : sources) { if (source.front() == '#') { const auto literal = get_constants(source); ir.load(literal, operand++, target); + has_literal = true; continue; } ir.load(get_ref(source), operand++, target); } + + if (has_literal) + { + pc += 4; + } } if (!ir.m_instructions.empty()) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h index c30b1560b6..4f413c21c3 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h @@ -13,7 +13,8 @@ namespace rsx::assembler void add(const RegisterRef& dst, const std::array& constants); void add(const RegisterRef& dst, const RegisterRef& src); - std::vector build(); + const std::vector& instructions() const; + std::vector compile() const; static FPIR from_source(const std::string& asm_); From b244c0fa0fb142cc1e0d432cb7b92da379e1964a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Fri, 5 Dec 2025 03:49:22 +0300 Subject: [PATCH 21/94] rsx/cfg/gtest: Update unit tests --- rpcs3/tests/test_rsx_fp_asm.cpp | 98 ++++++++++++++++++++++++++++++++- 1 file changed, 95 insertions(+), 3 deletions(-) diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index fc28525f94..609a984014 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -45,7 +45,7 @@ namespace rsx::assembler graph.blocks.push_back({}); auto& bb = graph.blocks.back(); - bb.instructions = ir.build(); + bb.instructions = ir.instructions(); return graph; } @@ -54,7 +54,7 @@ namespace rsx::assembler auto ir = FPIR::from_source(asm_); graph->blocks.push_back({}); BasicBlock& bb = graph->blocks.back(); - bb.instructions = ir.build(); + bb.instructions = ir.instructions(); return &bb; } TEST(TestFPIR, FromSource) @@ -64,7 +64,7 @@ namespace rsx::assembler ADD R1, R0, R0; )"); - const auto instructions = ir.build(); + const auto instructions = ir.instructions(); ASSERT_EQ(instructions.size(), 2); @@ -320,4 +320,96 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); } + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_OneBranchClobbers) + { + // Single IF-ELSE, if clobbers, ELSE does not + auto ir = FPIR::from_source(R"( + DP4 R2, R0, R1 + SFL R3 + SGT R3, R2, R0 + IF.GE + ADD R0, R0, R2 + MOV H6, #{ 0.25 } + ELSE + ADD R0, R0, R1 + ENDIF + ADD R0, R0, R3 + MOV R1, R0 + )"); + + auto bytecode = ir.compile(); + + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + ASSERT_EQ(graph.blocks.size(), 4); + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + auto get_block = [&](u32 index) -> BasicBlock* + { + ensure(index < graph.blocks.size()); + for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it) + { + if (!index) + { + return &(*it); + } + index--; + } + return nullptr; + }; + + BasicBlock + *bb0 = get_block(0), + *bb1 = get_block(1), + *bb2 = get_block(2), + *bb3 = get_block(3); + + ASSERT_EQ(bb0->instructions.size(), 4); + ASSERT_EQ(bb1->instructions.size(), 2); + ASSERT_EQ(bb2->instructions.size(), 1); + ASSERT_EQ(bb3->instructions.size(), 2); + + // bb1 has a epilogue + ASSERT_EQ(bb0->epilogue.size(), 0); + ASSERT_EQ(bb1->epilogue.size(), 2); + ASSERT_EQ(bb2->epilogue.size(), 0); + + // bb1 epilogue updates R3.xy + + // R3.x = packHalf2(H6.xy) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_x, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_y, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[0].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_x, 0); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[0].bytecode[1] }.swizzle_y, 1); + + // R3.y = packHalf2(H6.zw) + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.opcode, RSX_FP_OPCODE_PK2); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.fp16, 0); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.dest_reg, 3); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_x, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_y, true); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_z, false); + EXPECT_EQ(OPDEST{ .HEX = bb1->epilogue[1].bytecode[0] }.mask_w, false); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.tmp_reg_index, 6); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.fp16, 1); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); + EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); + } } From 91e19652def534c2f7e20a8a34e2aff40b8a23a0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 03:34:46 +0300 Subject: [PATCH 22/94] rsx/cfg/gtest: Rewrite CFG tests using the assembler - Also extend IF-ELSE test to catch a broken succession chain --- rpcs3/tests/test_rsx_cfg.cpp | 216 +++++++++++++++-------------------- 1 file changed, 95 insertions(+), 121 deletions(-) diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index 1708774d76..5e22311ac3 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -2,89 +2,28 @@ #include "Emu/RSX/Common/simple_array.hpp" #include "Emu/RSX/Program/Assembler/CFG.h" +#include "Emu/RSX/Program/Assembler/FPASM.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" #include namespace rsx::assembler { - auto swap_bytes16 = [](u32 dword) -> u32 + static const BasicBlock* get_graph_block_by_id(const FlowGraph& graph, u32 id) { - // Lazy encode, but good enough for what we need here. - union v32 - { - u32 HEX; - u8 _v[4]; - }; - - u8* src_bytes = reinterpret_cast(&dword); - v32 dst_bytes; - - dst_bytes._v[0] = src_bytes[1]; - dst_bytes._v[1] = src_bytes[0]; - dst_bytes._v[2] = src_bytes[3]; - dst_bytes._v[3] = src_bytes[2]; - - return dst_bytes.HEX; - }; - - // Instruction mocks because we don't have a working assember (yet) - auto encode_instruction = [](u32 opcode, bool end = false) -> v128 - { - OPDEST dst{}; - dst.opcode = opcode; - - if (end) - { - dst.end = 1; - } - - return v128::from32(swap_bytes16(dst.HEX), 0, 0, 0); - }; - - auto create_if(u32 end, u32 _else = 0) - { - OPDEST dst{}; - dst.opcode = RSX_FP_OPCODE_IFE & 0x3Fu; - - SRC1 src1{}; - src1.else_offset = (_else ? _else : end) << 2; - src1.opcode_is_branch = 1; - - SRC2 src2{}; - src2.end_offset = end << 2; - - return v128::from32(swap_bytes16(dst.HEX), 0, swap_bytes16(src1.HEX), swap_bytes16(src2.HEX)); - }; - - TEST(CFG, FpToCFG_Basic) - { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), - encode_instruction(RSX_FP_OPCODE_MOV, true) - }; - - RSXFragmentProgram program{}; - program.data = buffer.data(); - - FlowGraph graph = deconstruct_fragment_program(program); - - EXPECT_EQ(graph.blocks.size(), 1); - EXPECT_EQ(graph.blocks.front().instructions.size(), 2); - EXPECT_EQ(graph.blocks.front().instructions.front().length, 4); - EXPECT_EQ(graph.blocks.front().instructions[0].addr, 0); - EXPECT_EQ(graph.blocks.front().instructions[1].addr, 16); + auto found = std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == id)); + return &(*found); } - TEST(CFG, FpToCFG_IF) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(4), // 2 (BR, 4) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - encode_instruction(RSX_FP_OPCODE_MOV, true), // 4 (Merge block) - }; + auto ir = FPIR::from_source(R"( + ADD R0, R0, R0; + MOV R1, R0; + IF.LT; + ADD R1, R1, R0; + ENDIF; + MOV R0, R1; + )"); const std::pair expected_block_data[3] = { { 0, 3 }, // Head @@ -93,7 +32,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -108,24 +48,26 @@ namespace rsx::assembler } // Check edges - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 3))->pred[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 4))->pred[0].type, EdgeType::ENDIF); + EXPECT_EQ(get_graph_block_by_id(graph, 3)->pred[0].type, EdgeType::IF); + EXPECT_EQ(get_graph_block_by_id(graph, 0)->succ[0].type, EdgeType::IF); + EXPECT_EQ(get_graph_block_by_id(graph, 4)->pred[0].type, EdgeType::ENDIF); } TEST(CFG, FpToCFG_NestedIF) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(8), // 2 (BR, 8) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - create_if(6), // 4 (BR, 6) - encode_instruction(RSX_FP_OPCODE_MOV), // 5 - encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block 1) - encode_instruction(RSX_FP_OPCODE_ADD), // 7 - encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 (merge block 2 - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 8) + " ADD R1, R1, R0;" // 3 + " IF.GT;" // 4 (BR, 6) + " MOV R3, R0;" // 5 + " ENDIF;" + " MOV R2, R3;" // 6 (merge block 1) + " ADD R1, R2, R1;" // 7 + "ENDIF;" + "MOV R0, R1;" // 8 (merge block 2 + ); const std::pair expected_block_data[5] = { { 0, 3 }, // Head @@ -136,7 +78,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -153,17 +96,19 @@ namespace rsx::assembler TEST(CFG, FpToCFG_NestedIF_MultiplePred) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(6), // 2 (BR, 6) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - create_if(6), // 4 (BR, 6) - encode_instruction(RSX_FP_OPCODE_MOV), // 5 - encode_instruction(RSX_FP_OPCODE_MOV), // 6 (merge block) - encode_instruction(RSX_FP_OPCODE_ADD), // 7 - encode_instruction(RSX_FP_OPCODE_MOV, true) // 8 - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 6) + " ADD R1, R1, R0;" // 3 + " IF.GT;" // 4 (BR, 6) + " MOV R3, R0;" // 5 + " ENDIF;" // ENDIF (4) + "ENDIF;" // ENDIF (2) + "MOV R2, R3;" // 6 (merge block, unified) + "ADD R1, R2, R1;" // 7 + "MOV R0, R1;" // 8 + ); const std::pair expected_block_data[4] = { { 0, 3 }, // Head @@ -173,7 +118,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -187,32 +133,40 @@ namespace rsx::assembler EXPECT_EQ(it->instructions.size(), expected.second); } + const BasicBlock + *bb0 = get_graph_block_by_id(graph, 0), + *bb6 = get_graph_block_by_id(graph, 6); + // Predecessors must be ordered, closest first - ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred.size(), 2); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[0].from->id, 3); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 6))->pred[1].from->id, 0); + ASSERT_EQ(bb6->pred.size(), 3); + EXPECT_EQ(bb6->pred[0].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[0].from->id, 5); + EXPECT_EQ(bb6->pred[1].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[1].from->id, 3); + EXPECT_EQ(bb6->pred[2].type, EdgeType::ENDIF); + EXPECT_EQ(bb6->pred[2].from->id, 0); // Successors must also be ordered, closest first - ASSERT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ.size(), 2); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].type, EdgeType::IF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[0].to->id, 3); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].type, EdgeType::ENDIF); - EXPECT_EQ(std::find_if(graph.blocks.begin(), graph.blocks.end(), FN(x.id == 0))->succ[1].to->id, 6); + ASSERT_EQ(bb0->succ.size(), 2); + EXPECT_EQ(bb0->succ[0].type, EdgeType::IF); + EXPECT_EQ(bb0->succ[0].to->id, 3); + EXPECT_EQ(bb0->succ[1].type, EdgeType::ENDIF); + EXPECT_EQ(bb0->succ[1].to->id, 6); } TEST(CFG, FpToCFG_IF_ELSE) { - rsx::simple_array buffer = { - encode_instruction(RSX_FP_OPCODE_ADD), // 0 - encode_instruction(RSX_FP_OPCODE_MOV), // 1 - create_if(6, 4), // 2 (BR, 6) - encode_instruction(RSX_FP_OPCODE_ADD), // 3 - encode_instruction(RSX_FP_OPCODE_MOV), // 4 (Else) - encode_instruction(RSX_FP_OPCODE_ADD), // 5 - encode_instruction(RSX_FP_OPCODE_MOV, true), // 6 (Merge) - }; + auto ir = FPIR::from_source( + "ADD R0, R0, R0;" // 0 + "MOV R1, R0;" // 1 + "IF.LT;" // 2 (BR, 6) + " ADD R1, R1, R0;" // 3 + "ELSE;" // ELSE (2) + " MOV R2, R3;" // 4 + " ADD R1, R2, R1;" // 5 + "ENDIF;" // ENDIF (2) + "MOV R0, R1;" // 6 (merge) + ); const std::pair expected_block_data[4] = { { 0, 3 }, // Head @@ -222,7 +176,8 @@ namespace rsx::assembler }; RSXFragmentProgram program{}; - program.data = buffer.data(); + auto bytecode = ir.compile(); + program.data = bytecode.data(); FlowGraph graph = deconstruct_fragment_program(program); @@ -235,5 +190,24 @@ namespace rsx::assembler EXPECT_EQ(it->id, expected.first); EXPECT_EQ(it->instructions.size(), expected.second); } + + // The IF and ELSE branches don't link to each other directly. Their predecessor should point to both and they both point to the merge. + const BasicBlock + *bb0 = get_graph_block_by_id(graph, 0), + *bb3 = get_graph_block_by_id(graph, 3), + *bb4 = get_graph_block_by_id(graph, 4), + *bb6 = get_graph_block_by_id(graph, 6); + + EXPECT_EQ(bb0->succ.size(), 3); + EXPECT_EQ(bb3->succ.size(), 1); + EXPECT_EQ(bb4->succ.size(), 1); + + EXPECT_EQ(bb3->succ.front().to, bb6); + EXPECT_EQ(bb4->succ.front().to, bb6); + + EXPECT_EQ(bb6->pred.size(), 3); + EXPECT_EQ(bb6->pred[0].from, bb4); + EXPECT_EQ(bb6->pred[1].from, bb3); + EXPECT_EQ(bb6->pred[2].from, bb0); } } From e1ec2f58bb3515e931566b1e2ddab8e225152bae Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 03:35:20 +0300 Subject: [PATCH 23/94] rsx/fp/asm: Fix ELSE encoding --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index 7b32ff56d5..cddc562fa5 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -294,13 +294,6 @@ namespace rsx::assembler fmt::throw_exception("Invalid constant literal"); }; - auto encode_branch_end = [](Instruction *inst, u32 end) - { - SRC2 src2 { .HEX = inst->bytecode[3] }; - src2.end_offset = static_cast(end); - inst->bytecode[3] = src2.HEX; - }; - auto encode_branch_else = [](Instruction* inst, u32 end) { SRC1 src1{ .HEX = inst->bytecode[2] }; @@ -308,6 +301,20 @@ namespace rsx::assembler inst->bytecode[2] = src1.HEX; }; + auto encode_branch_end = [](Instruction *inst, u32 end) + { + SRC2 src2 { .HEX = inst->bytecode[3] }; + src2.end_offset = static_cast(end); + inst->bytecode[3] = src2.HEX; + + SRC1 src1{ .HEX = inst->bytecode[2] }; + if (!src1.else_offset) + { + src1.else_offset = static_cast(end); + inst->bytecode[2] = src1.HEX; + } + }; + auto encode_opcode = [](const std::string& op, Instruction* inst) { OPDEST d0 { .HEX = inst->bytecode[0] }; From 3bed46b844726474e083e0cba22a13b57ac7b9f0 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 03:55:33 +0300 Subject: [PATCH 24/94] rsx/fp/cfg: Fix IF/ELSE and LOOP node linkage --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 7887a8a9f4..fbdf830cf7 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -1,5 +1,4 @@ #include "stdafx.h" - #include "CFG.h" #include "Emu/RSX/Common/simple_array.hpp" @@ -107,11 +106,25 @@ namespace rsx::assembler { case EdgeType::IF: case EdgeType::ELSE: - bb->insert_succ(*found, EdgeType::ENDIF); + { + // Find the merge node from the parent + auto parent = bb->pred.back().from; + auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF)); + ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers."); + bb->insert_succ(succ->to, EdgeType::ENDIF); + succ->to->insert_pred(bb, EdgeType::ENDIF); break; + } case EdgeType::LOOP: - bb->insert_succ(*found, EdgeType::ENDLOOP); + { + // Find the merge node from the parent + auto parent = bb->pred.back().from; + auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDLOOP)); + ensure(succ != parent->succ.end(), "CFG: Broken LOOP linkage. Please report to developers."); + bb->insert_succ(succ->to, EdgeType::ENDLOOP); + succ->to->insert_pred(bb, EdgeType::ENDLOOP); break; + } default: // Missing an edge type? rsx_log.error("CFG: Unexpected block exit. Report to developers."); From a7f5514913af0b57c22c788035affcdae9ea9117 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 03:56:48 +0300 Subject: [PATCH 25/94] rsx/cfg: Fix dependency injection tests for FP --- rpcs3/tests/test_rsx_fp_asm.cpp | 68 +++++++++++++++------------------ 1 file changed, 30 insertions(+), 38 deletions(-) diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 609a984014..55cbb2fad7 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -37,6 +37,20 @@ namespace rsx::assembler #undef DECLARE_REG32 #undef DECLARE_REG16 + static const BasicBlock* get_graph_block(const FlowGraph& graph, u32 index) + { + ensure(index < graph.blocks.size()); + for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it) + { + if (!index) + { + return &(*it); + } + index--; + } + return nullptr; + }; + static FlowGraph CFG_from_source(const std::string& asm_) { auto ir = FPIR::from_source(asm_); @@ -247,35 +261,27 @@ namespace rsx::assembler // Mockup of a simple lighting function, R0 = Light vector, R1 = Decompressed normal. DP4 used for simplicity. // Data hazards sprinkled in for testing. R3 is clobbered in the ancestor and the IF branch. // Barrier should go in the IF branch here. - FlowGraph graph; - BasicBlock* bb0 = BB_from_source(&graph, R"( + auto ir = FPIR::from_source(R"( DP4 R2, R0, R1 SFL R3 SGT R3, R2, R0 IF.GE - )"); - - BasicBlock* bb1 = BB_from_source(&graph, R"( - ADD R0, R0, R2 - MOV H6, #{ 0.25 } - )"); - - BasicBlock* bb2 = BB_from_source(&graph, R"( + ADD R0, R0, R2 + MOV H6, #{ 0.25 } + ENDIF ADD R0, R0, R3 MOV R1, R0 )"); - // Front edges - bb0->insert_succ(bb1, EdgeType::IF); - bb0->insert_succ(bb2, EdgeType::ENDIF); - bb1->insert_succ(bb2, EdgeType::ENDIF); - - // Back edges - bb2->insert_pred(bb1, EdgeType::ENDIF); - bb2->insert_pred(bb0, EdgeType::ENDIF); - bb1->insert_pred(bb0, EdgeType::IF); + auto bytecode = ir.compile(); RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + + auto graph = deconstruct_fragment_program(prog); + auto bb0 = get_graph_block(graph, 0); + auto bb1 = get_graph_block(graph, 1); + auto bb2 = get_graph_block(graph, 2); FP::RegisterAnnotationPass annotation_pass{ prog }; FP::RegisterDependencyPass deps_pass{}; @@ -352,25 +358,11 @@ namespace rsx::assembler annotation_pass.run(graph); deps_pass.run(graph); - auto get_block = [&](u32 index) -> BasicBlock* - { - ensure(index < graph.blocks.size()); - for (auto it = graph.blocks.begin(); it != graph.blocks.end(); ++it) - { - if (!index) - { - return &(*it); - } - index--; - } - return nullptr; - }; - - BasicBlock - *bb0 = get_block(0), - *bb1 = get_block(1), - *bb2 = get_block(2), - *bb3 = get_block(3); + const BasicBlock + *bb0 = get_graph_block(graph, 0), + *bb1 = get_graph_block(graph, 1), + *bb2 = get_graph_block(graph, 2), + *bb3 = get_graph_block(graph, 3); ASSERT_EQ(bb0->instructions.size(), 4); ASSERT_EQ(bb1->instructions.size(), 2); From e8e2d4b93df3652aea03bd682471be8c4a1fcd0a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 14:01:54 +0300 Subject: [PATCH 26/94] rsx/fp: Fix issues with FP decompiler using new CFG system --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 10 ++--- rpcs3/Emu/RSX/Program/Assembler/IR.h | 20 +++++++++ .../RSX/Program/FragmentProgramDecompiler.cpp | 44 ++++++++++++++----- 3 files changed, 57 insertions(+), 17 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index fbdf830cf7..5d8afeba58 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -100,15 +100,16 @@ namespace rsx::assembler if (found) { - if (!bb->pred.empty()) + auto front_edge = std::find_if(bb->pred.begin(), bb->pred.end(), FN(x.type != EdgeType::ENDIF && x.type != EdgeType::ENDLOOP)); + if (front_edge != bb->pred.end()) { - switch (bb->pred.back().type) + auto parent = ensure(front_edge->from); + switch (front_edge->type) { case EdgeType::IF: case EdgeType::ELSE: { // Find the merge node from the parent - auto parent = bb->pred.back().from; auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF)); ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers."); bb->insert_succ(succ->to, EdgeType::ENDIF); @@ -118,7 +119,6 @@ namespace rsx::assembler case EdgeType::LOOP: { // Find the merge node from the parent - auto parent = bb->pred.back().from; auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDLOOP)); ensure(succ != parent->succ.end(), "CFG: Broken LOOP linkage. Please report to developers."); bb->insert_succ(succ->to, EdgeType::ENDLOOP); @@ -130,7 +130,7 @@ namespace rsx::assembler rsx_log.error("CFG: Unexpected block exit. Report to developers."); } } - else + else if (bb->pred.empty()) { // Impossible situation. rsx_log.error("CFG: Child block has no parent but has successor! Report to developers."); diff --git a/rpcs3/Emu/RSX/Program/Assembler/IR.h b/rpcs3/Emu/RSX/Program/Assembler/IR.h index fff3d5aa83..ef6079a85d 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/IR.h +++ b/rpcs3/Emu/RSX/Program/Assembler/IR.h @@ -114,5 +114,25 @@ namespace rsx::assembler pred.push_back(e); return &pred.back(); } + + bool is_of_type(EdgeType type) const + { + return pred.size() == 1 && + pred.front().type == type; + } + + bool has_sibling_of_type(EdgeType type) const + { + if (pred.size() != 1) + { + return false; + } + + auto source_node = pred.front().from; + return std::find_if( + source_node->succ.begin(), + source_node->succ.end(), + FN(x.type == type)) != source_node->succ.end(); + } }; } diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 2ebfd7d8d7..a5a6cd5876 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -21,6 +21,7 @@ namespace rsx } using namespace rsx::fragment_program; +using namespace rsx::assembler; // SIMD vector lanes enum VectorLane : u8 @@ -1295,7 +1296,7 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode) std::string FragmentProgramDecompiler::Decompile() { - const auto graph = rsx::assembler::deconstruct_fragment_program(m_prog); + const auto graph = deconstruct_fragment_program(m_prog); m_size = 0; m_location = 0; m_loop_count = 0; @@ -1322,22 +1323,41 @@ std::string FragmentProgramDecompiler::Decompile() { switch (pred.type) { - case rsx::assembler::EdgeType::ENDLOOP: - m_loop_count--; - [[ fallthrough ]]; - case rsx::assembler::EdgeType::ENDIF: - m_code_level--; - AddCode("}"); + case EdgeType::ENDLOOP: + // Because of succession rules, endloop is seen twice. + // Once from the the for statement at the end of the parent + // and again at the end of the child block. + if (pred.from->is_of_type(EdgeType::LOOP)) + { + m_loop_count--; + m_code_level--; + AddCode("}"); + } break; - case rsx::assembler::EdgeType::LOOP: + case EdgeType::ENDIF: + { + // Same thing happens with ENDIF + // Once for the IF statement itself + // And again for the child blocks with code for the IF and ELSE paths. + const bool is_else_end = pred.from->is_of_type(EdgeType::ELSE); + const bool is_if_end = pred.from->is_of_type(EdgeType::IF) && + !pred.from->has_sibling_of_type(EdgeType::ELSE); // Avoid double-counting if the IF has an ELSE sibling + if (is_else_end || is_if_end) + { + m_code_level--; + AddCode("}"); + } + break; + } + case EdgeType::LOOP: m_loop_count++; [[ fallthrough ]]; - case rsx::assembler::EdgeType::IF: + case EdgeType::IF: // Instruction will be inserted by the SIP decoder AddCode("{"); m_code_level++; break; - case rsx::assembler::EdgeType::ELSE: + case EdgeType::ELSE: // This one needs more testing m_code_level--; AddCode("}"); @@ -1440,8 +1460,8 @@ std::string FragmentProgramDecompiler::Decompile() while (m_code_level > 1) { - rsx_log.error("Hanging block found at end of shader. Malformed shader?"); - + // Happens if the last block was hanging (no merge) + // FIXME: We must always have a merge block on exit to resolve dependencies on outputs m_code_level--; AddCode("}"); } From 26fd0510abe11d797d8a16d6ad858e879da9044d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 6 Dec 2025 14:17:00 +0300 Subject: [PATCH 27/94] rsx/fp: Enable CFG passes and emit block epilogues --- .../Passes/FP/RegisterAnnotationPass.h | 2 +- .../RSX/Program/FragmentProgramDecompiler.cpp | 35 +++++++++++++++++-- 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h index 8856ecb44e..c719a80381 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h @@ -14,7 +14,7 @@ namespace rsx::assembler::FP class RegisterAnnotationPass : public CFGPass { public: - RegisterAnnotationPass(RSXFragmentProgram& prog) + RegisterAnnotationPass(const RSXFragmentProgram& prog) : m_prog(prog) {} diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index a5a6cd5876..68874272a1 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -3,6 +3,11 @@ #include "FragmentProgramDecompiler.h" #include "ProgramStateCache.h" +#include "Assembler/Passes/FP/RegisterAnnotationPass.h" +#include "Assembler/Passes/FP/RegisterDependencyPass.h" + +#include "Emu/system_config.h" + #include namespace rsx @@ -1296,7 +1301,17 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode) std::string FragmentProgramDecompiler::Decompile() { - const auto graph = deconstruct_fragment_program(m_prog); + auto graph = deconstruct_fragment_program(m_prog); + + if (g_cfg.video.shader_precision != gpu_preset_level::low) + { + FP::RegisterAnnotationPass annotation_pass{ m_prog }; + FP::RegisterDependencyPass dependency_pass{}; + + annotation_pass.run(graph); + dependency_pass.run(graph); + } + m_size = 0; m_location = 0; m_loop_count = 0; @@ -1455,7 +1470,23 @@ std::string FragmentProgramDecompiler::Decompile() if (dst.end) break; } - // TODO: Handle block epilogue if needed + if (block.epilogue.empty()) + { + continue; + } + + AddCode("// Epilogue"); + + for (auto& inst : block.epilogue) + { + m_instruction = &inst; + dst.HEX = inst.bytecode[0]; + src0.HEX = inst.bytecode[1]; + src1.HEX = inst.bytecode[2]; + src2.HEX = inst.bytecode[3]; + + ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); + } } while (m_code_level > 1) From d23ea4760bacb8970113e4aaeeeb7cf644e137d5 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 18:16:04 +0300 Subject: [PATCH 28/94] rsx/cfg: Handle nested IF/LOOP blocks falling out to unsuitable nodes (ELSE). - In that case, find the node's END node and link to that instead. ELSE nodes are not reachable from children of the preceding IF. - ELSE nodes are special this way, all other types of nodes are reachable by adjacency. --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 17 +++++- rpcs3/Emu/RSX/Program/Assembler/IR.h | 1 + rpcs3/tests/test_rsx_fp_asm.cpp | 68 +++++++++++++++++++++ 3 files changed, 83 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 5d8afeba58..82f5464a0a 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -74,8 +74,19 @@ namespace rsx::assembler { if (auto found = find_block_for_pc(id)) { - parent->insert_succ(found, edge_type); - found->insert_pred(parent, edge_type); + auto succ = found; + if (found->is_of_type(EdgeType::ELSE) && + (edge_type == EdgeType::ENDIF || edge_type == EdgeType::ENDLOOP)) + { + // If we landed on an "ELSE" node, link to its "ENDIF" counterpart + auto if_parent = found->pred.front().from; + auto endif_edge = std::find_if(if_parent->succ.begin(), if_parent->succ.end(), FN(x.type == EdgeType::ENDIF)); + ensure(endif_edge != if_parent->succ.end(), "CFG: Invalid ELSE node"); + succ = endif_edge->to; + } + + parent->insert_succ(succ, edge_type); + succ->insert_pred(parent, edge_type); return found; } @@ -109,7 +120,7 @@ namespace rsx::assembler case EdgeType::IF: case EdgeType::ELSE: { - // Find the merge node from the parent + // Find the merge node from the parent. auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF)); ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers."); bb->insert_succ(succ->to, EdgeType::ENDIF); diff --git a/rpcs3/Emu/RSX/Program/Assembler/IR.h b/rpcs3/Emu/RSX/Program/Assembler/IR.h index ef6079a85d..635aec7209 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/IR.h +++ b/rpcs3/Emu/RSX/Program/Assembler/IR.h @@ -91,6 +91,7 @@ namespace rsx::assembler struct BasicBlock { u32 id = 0; + std::vector instructions; // Program instructions for the RSX processor std::vector succ; // Forward edges. Sorted closest first. std::vector pred; // Back edges. Sorted closest first. diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 55cbb2fad7..752baf0776 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -404,4 +404,72 @@ namespace rsx::assembler EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_x, 2); EXPECT_EQ(SRC0{ .HEX = bb1->epilogue[1].bytecode[1] }.swizzle_y, 3); } + + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_Simpsons) + { + // Complex IF-ELSE nest observed in Simpson's game. Rewritten for simplicity. + // There is no tail block. No epilogues should be injected in this scenario since H4 (the trigger) is defined on all branches. + // R2 is indeed clobbered but the outer ELSE branch should not be able to see the inner IF-ELSE blocks as predecessors. + auto ir = FPIR::from_source(R"( + MOV R2, #{ 0.25 }; + IF.GT; + SLT R4, H2, #{ 0.125 }; + IF.GT; + ADD H2, H0, H3; + FMA H4, R2, H2, H3; + ELSE; + MOV H2, #{ 0.125 }; + ADD H0, H0, H2; + FMA H4, R2, H2, H3; + ENDIF; + ELSE; + FMA H4, R2, H2, H3; + MOV H0, H4; + ENDIF; + )"); + + auto bytecode = ir.compile(); + + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + ASSERT_EQ(graph.blocks.size(), 6); + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + const BasicBlock + *bb0 = get_graph_block(graph, 0), + *bb1 = get_graph_block(graph, 1), + *bb2 = get_graph_block(graph, 2), + *bb3 = get_graph_block(graph, 3), + *bb4 = get_graph_block(graph, 4), + *bb5 = get_graph_block(graph, 5); + + // Sanity + EXPECT_EQ(bb0->instructions.size(), 2); + EXPECT_EQ(bb1->instructions.size(), 2); + EXPECT_EQ(bb2->instructions.size(), 2); + EXPECT_EQ(bb3->instructions.size(), 3); + EXPECT_EQ(bb4->instructions.size(), 2); + EXPECT_EQ(bb5->instructions.size(), 0); // Phi/Merge only. + + // Nested children must recursively fall out to the closest ENDIF + ASSERT_EQ(bb4->pred.size(), 1); + EXPECT_EQ(bb4->pred.front().type, EdgeType::ELSE); + EXPECT_EQ(bb5->pred.size(), 4); // 2 IF and 2 ELSE paths exist + + // Check that we get no epilogues + EXPECT_EQ(bb0->epilogue.size(), 0); + EXPECT_EQ(bb1->epilogue.size(), 0); + EXPECT_EQ(bb2->epilogue.size(), 0); + EXPECT_EQ(bb3->epilogue.size(), 0); + EXPECT_EQ(bb4->epilogue.size(), 0); + EXPECT_EQ(bb5->epilogue.size(), 0); + } } From f2913e4692521825eb214b162de707f1224c5466 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 18:16:27 +0300 Subject: [PATCH 29/94] rsx/fp: Reimplement GLSL code generation from CFG --- .../RSX/Program/FragmentProgramDecompiler.cpp | 198 +++++++++--------- 1 file changed, 101 insertions(+), 97 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 68874272a1..b03f534fa9 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1319,71 +1319,82 @@ std::string FragmentProgramDecompiler::Decompile() m_is_valid_ucode = true; m_constant_offsets.clear(); - enum - { - FORCE_NONE, - FORCE_SCT, - FORCE_SCB, - }; + // For GLSL scope wind/unwind. We store the min scope depth and loop count for each block and "unwind" to it. + // This should recover information lost when multiple nodes converge on a single merge node or even skip a merge node as is the case with "ELSE" nodes. + std::unordered_map> block_data; - int forced_unit = FORCE_NONE; + auto push_block_info = [&](const BasicBlock* block) + { + u32 loop = m_loop_count; + int level = m_code_level; + + auto found = block_data.find(block); + if (found != block_data.end()) + { + level = std::min(level, found->second.first); + loop = std::min(loop, found->second.second); + } + + block_data[block] = { level, loop }; + }; for (const auto &block : graph.blocks) { - // TODO: Handle block prologue if any + auto found = block_data.find(&block); + if (found != block_data.end()) + { + const auto [level, loop] = found->second; + for (int i = m_code_level; i > level; i--) + { + m_code_level--; + AddCode("}"); + } + + m_loop_count = loop; + } + if (!block.pred.empty()) { - // CFG guarantees predecessors are sorted, closest one first - for (const auto& pred : block.pred) + // Predecessors are always sorted closest last. + // This gives some adjacency info and tells us how the previous block connects to this one. + const auto& pred = block.pred.back(); + switch (pred.type) { - switch (pred.type) - { - case EdgeType::ENDLOOP: - // Because of succession rules, endloop is seen twice. - // Once from the the for statement at the end of the parent - // and again at the end of the child block. - if (pred.from->is_of_type(EdgeType::LOOP)) - { - m_loop_count--; - m_code_level--; - AddCode("}"); - } - break; - case EdgeType::ENDIF: - { - // Same thing happens with ENDIF - // Once for the IF statement itself - // And again for the child blocks with code for the IF and ELSE paths. - const bool is_else_end = pred.from->is_of_type(EdgeType::ELSE); - const bool is_if_end = pred.from->is_of_type(EdgeType::IF) && - !pred.from->has_sibling_of_type(EdgeType::ELSE); // Avoid double-counting if the IF has an ELSE sibling - if (is_else_end || is_if_end) - { - m_code_level--; - AddCode("}"); - } - break; - } - case EdgeType::LOOP: - m_loop_count++; - [[ fallthrough ]]; - case EdgeType::IF: - // Instruction will be inserted by the SIP decoder - AddCode("{"); - m_code_level++; - break; - case EdgeType::ELSE: - // This one needs more testing - m_code_level--; - AddCode("}"); - AddCode("else"); - AddCode("{"); - m_code_level++; - break; - default: - // Start a new block anyway - fmt::throw_exception("Unexpected block found"); - } + case EdgeType::LOOP: + m_loop_count++; + [[ fallthrough ]]; + case EdgeType::IF: + AddCode("{"); + m_code_level++; + break; + case EdgeType::ELSE: + AddCode("else"); + AddCode("{"); + m_code_level++; + break; + case EdgeType::ENDIF: + case EdgeType::ENDLOOP: + // Pure merge block? + break; + default: + fmt::throw_exception("Unhandled edge type %d", static_cast(pred.type)); + break; + } + } + + if (!block.prologue.empty()) + { + AddCode("// Prologue"); + + for (auto& inst : block.prologue) + { + m_instruction = &inst; + dst.HEX = inst.bytecode[0]; + src0.HEX = inst.bytecode[1]; + src1.HEX = inst.bytecode[2]; + src2.HEX = inst.bytecode[3]; + + ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); } } @@ -1398,11 +1409,9 @@ std::string FragmentProgramDecompiler::Decompile() opflags = 0; - const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6); - auto SIP = [&]() { - switch (opcode) + switch (m_instruction->opcode) { case RSX_FP_OPCODE_BRK: if (m_loop_count) AddFlowOp("break"); @@ -1412,12 +1421,10 @@ std::string FragmentProgramDecompiler::Decompile() rsx_log.error("Unimplemented SIP instruction: CAL"); break; case RSX_FP_OPCODE_FENCT: - AddCode("//FENCT"); - forced_unit = FORCE_SCT; + AddCode("// FENCT"); break; case RSX_FP_OPCODE_FENCB: - AddCode("//FENCB"); - forced_unit = FORCE_SCB; + AddCode("// FENCB"); break; case RSX_FP_OPCODE_IFE: AddCode("if($cond)"); @@ -1441,7 +1448,7 @@ std::string FragmentProgramDecompiler::Decompile() return true; }; - switch (opcode) + switch (m_instruction->opcode) { case RSX_FP_OPCODE_NOP: break; @@ -1450,19 +1457,10 @@ std::string FragmentProgramDecompiler::Decompile() AddFlowOp("_kill()"); break; default: - int prev_force_unit = forced_unit; - - // Some instructions do not respect forced unit - // Tested with Tales of Vesperia if (SIP()) break; - if (handle_tex_srb(opcode)) break; - - // FENCT/FENCB do not actually reject instructions if they dont match the forced unit - // Looks like they are optimization hints and not hard-coded forced paths - if (handle_sct_scb(opcode)) break; - forced_unit = FORCE_NONE; - - rsx_log.error("Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit); + if (handle_tex_srb(m_instruction->opcode)) break; + if (handle_sct_scb(m_instruction->opcode)) break; + rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode); break; } @@ -1470,32 +1468,38 @@ std::string FragmentProgramDecompiler::Decompile() if (dst.end) break; } - if (block.epilogue.empty()) + if (!block.epilogue.empty()) { - continue; + AddCode("// Epilogue"); + + for (auto& inst : block.epilogue) + { + m_instruction = &inst; + dst.HEX = inst.bytecode[0]; + src0.HEX = inst.bytecode[1]; + src1.HEX = inst.bytecode[2]; + src2.HEX = inst.bytecode[3]; + + ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); + } } - AddCode("// Epilogue"); - - for (auto& inst : block.epilogue) + for (auto& succ : block.succ) { - m_instruction = &inst; - dst.HEX = inst.bytecode[0]; - src0.HEX = inst.bytecode[1]; - src1.HEX = inst.bytecode[2]; - src2.HEX = inst.bytecode[3]; - - ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); + switch (succ.type) + { + case EdgeType::ENDIF: + case EdgeType::ENDLOOP: + case EdgeType::ELSE: + push_block_info(succ.to); + break; + default: + break; + } } } - while (m_code_level > 1) - { - // Happens if the last block was hanging (no merge) - // FIXME: We must always have a merge block on exit to resolve dependencies on outputs - m_code_level--; - AddCode("}"); - } + ensure(m_code_level == 1); // flush m_code_level m_code_level = 1; From 316e01995b5543287f47e5df445083f76e339130 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 19:16:24 +0300 Subject: [PATCH 30/94] rsx/fp: Re-implement ROP output resolve --- rpcs3/Emu/CMakeLists.txt | 1 - .../RSX/Program/FragmentProgramDecompiler.cpp | 154 ++++++-------- .../RSX/Program/FragmentProgramDecompiler.h | 4 - .../RSX/Program/FragmentProgramRegister.cpp | 196 ------------------ .../Emu/RSX/Program/FragmentProgramRegister.h | 111 ---------- rpcs3/emucore.vcxproj | 2 - rpcs3/emucore.vcxproj.filters | 6 - 7 files changed, 69 insertions(+), 405 deletions(-) delete mode 100644 rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp delete mode 100644 rpcs3/Emu/RSX/Program/FragmentProgramRegister.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 395babec48..48674612c7 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -525,7 +525,6 @@ 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 diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index b03f534fa9..c8b6156065 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -14,6 +14,8 @@ namespace rsx { namespace fragment_program { + using namespace rsx::assembler; + static const std::string reg_table[] = { "wpos", @@ -22,6 +24,28 @@ namespace rsx "tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9", "ssa" }; + + static const std::vector s_fp32_output_set = + { + {.reg {.id = 0, .f16 = false }, .mask = 0xf }, + {.reg {.id = 2, .f16 = false }, .mask = 0xf }, + {.reg {.id = 3, .f16 = false }, .mask = 0xf }, + {.reg {.id = 4, .f16 = false }, .mask = 0xf }, + }; + + static const std::vector s_fp16_output_set = + { + {.reg {.id = 0, .f16 = true }, .mask = 0xf }, + {.reg {.id = 4, .f16 = true }, .mask = 0xf }, + {.reg {.id = 6, .f16 = true }, .mask = 0xf }, + {.reg {.id = 8, .f16 = true }, .mask = 0xf }, + }; + + static const RegisterRef s_z_export_reg = + { + .reg {.id = 1, .f16 = false }, + .mask = (1u << 2) + }; } } @@ -37,6 +61,26 @@ enum VectorLane : u8 W = 3, }; +std::vector get_fragment_program_output_set(u32 ctrl, u32 mrt_count) +{ + std::vector result; + if (mrt_count > 0) + { + result = (ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS) + ? s_fp32_output_set + : s_fp16_output_set; + + result.resize(mrt_count); + } + + if (ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT) + { + result.push_back(s_z_export_reg); + } + + return result; +} + FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size) : m_size(size) , m_prog(prog) @@ -157,8 +201,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags) } 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 && src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP && src0.tmp_reg_index == reg_index) @@ -171,8 +213,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags) return; } } - - temp_registers[reg_index].tag(dst.dest_reg, !!dst.fp16, dst.mask_x, dst.mask_y, dst.mask_z, dst.mask_w); } void FragmentProgramDecompiler::AddFlowOp(const std::string& code) @@ -528,26 +568,7 @@ template std::string FragmentProgramDecompiler::GetSRC(T src) switch (src.reg_type) { case RSX_FP_REGISTER_TYPE_TEMP: - - if (!src.fp16) - { - if (dst.opcode == RSX_FP_OPCODE_UP16 || - dst.opcode == RSX_FP_OPCODE_UP2 || - dst.opcode == RSX_FP_OPCODE_UP4 || - dst.opcode == RSX_FP_OPCODE_UPB || - dst.opcode == RSX_FP_OPCODE_UPG) - { - auto ® = temp_registers[src.tmp_reg_index]; - if (reg.requires_gather(src.swizzle_x)) - { - properties.has_gather_op = true; - AddReg(src.tmp_reg_index, src.fp16); - ret = getFloatTypeName(4) + reg.gather_r(); - break; - } - } - } - else if (precision_modifier == RSX_FP_PRECISION_HALF) + if (src.fp16 && precision_modifier == RSX_FP_PRECISION_HALF) { // clamp16() is not a cheap operation when emulated; avoid at all costs precision_modifier = RSX_FP_PRECISION_REAL; @@ -778,17 +799,6 @@ std::string FragmentProgramDecompiler::BuildCode() { // 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); - - 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) @@ -816,33 +826,6 @@ std::string FragmentProgramDecompiler::BuildCode() continue; } - const auto block_index = ouput_register_indices[n]; - auto& r = temp_registers[block_index]; - - if (fp16_out) - { - // 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); } @@ -1030,28 +1013,6 @@ std::string FragmentProgramDecompiler::BuildCode() OS << Format(divsq_func); } - // Declare register gather/merge if needed - if (properties.has_gather_op) - { - std::string float2 = getFloatTypeName(2); - - OS << float4 << " gather(" << float4 << " _h0, " << float4 << " _h1)\n"; - OS << "{\n"; - OS << " float x = uintBitsToFloat(packHalf2x16(_h0.xy));\n"; - OS << " float y = uintBitsToFloat(packHalf2x16(_h0.zw));\n"; - OS << " float z = uintBitsToFloat(packHalf2x16(_h1.xy));\n"; - OS << " float w = uintBitsToFloat(packHalf2x16(_h1.zw));\n"; - OS << " return " << float4 << "(x, y, z, w);\n"; - OS << "}\n\n"; - - OS << float2 << " gather(" << float4 << " _h)\n"; - OS << "{\n"; - OS << " float x = uintBitsToFloat(packHalf2x16(_h.xy));\n"; - OS << " float y = uintBitsToFloat(packHalf2x16(_h.zw));\n"; - OS << " return " << float2 << "(x, y);\n"; - OS << "}\n\n"; - } - if (properties.has_dynamic_register_load) { OS << @@ -1303,8 +1264,28 @@ std::string FragmentProgramDecompiler::Decompile() { auto graph = deconstruct_fragment_program(m_prog); - if (g_cfg.video.shader_precision != gpu_preset_level::low) + if (!graph.blocks.empty()) { + // The RSX CFG is missing the output block. We inject a fake tail block that ingests the ROP outputs. + BasicBlock* rop_block = nullptr; + BasicBlock* tail_block = &graph.blocks.back(); + if (tail_block->instructions.size() == 0) + { + // Merge block. Use this directly + rop_block = tail_block; + } + else + { + graph.blocks.push_back({}); + rop_block = &graph.blocks.back(); + + tail_block->insert_succ(rop_block); + rop_block->insert_pred(tail_block); + } + + const auto rop_inputs = get_fragment_program_output_set(m_prog.ctrl, m_prog.mrt_buffers_count); + rop_block->input_list.insert(rop_block->input_list.end(), rop_inputs.begin(), rop_inputs.end()); + FP::RegisterAnnotationPass annotation_pass{ m_prog }; FP::RegisterDependencyPass dependency_pass{}; @@ -1376,6 +1357,9 @@ std::string FragmentProgramDecompiler::Decompile() case EdgeType::ENDLOOP: // Pure merge block? break; + case EdgeType::NONE: + ensure(block.instructions.empty()); + break; default: fmt::throw_exception("Unhandled edge type %d", static_cast(pred.type)); break; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h index b68750bdfc..09a02804c3 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.h @@ -1,6 +1,5 @@ #pragma once #include "ShaderParam.h" -#include "FragmentProgramRegister.h" #include "RSXFragmentProgram.h" #include "Assembler/CFG.h" @@ -53,8 +52,6 @@ class FragmentProgramDecompiler int m_code_level; std::unordered_map m_constant_offsets; - std::array temp_registers; - std::string GetMask() const; void SetDst(std::string code, u32 flags = 0); @@ -175,7 +172,6 @@ public: // Decoded properties (out) bool has_lit_op = false; - bool has_gather_op = false; bool has_no_output = false; bool has_discard_op = false; bool has_tex_op = false; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp deleted file mode 100644 index a14b142df6..0000000000 --- a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.cpp +++ /dev/null @@ -1,196 +0,0 @@ -#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 deleted file mode 100644 index 6cfc8e76c3..0000000000 --- a/rpcs3/Emu/RSX/Program/FragmentProgramRegister.h +++ /dev/null @@ -1,111 +0,0 @@ -#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/emucore.vcxproj b/rpcs3/emucore.vcxproj index 761f42dd07..77deb6088e 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -161,7 +161,6 @@ - @@ -710,7 +709,6 @@ - diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 937b22f5cc..6b15f662e5 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1360,9 +1360,6 @@ Emu\Cell - - Emu\GPU\RSX\Program - Utilities @@ -2764,9 +2761,6 @@ Emu\Audio - - Emu\GPU\RSX\Program - Utilities From 4183d09a52707b468842027a51a35f2cc2a0db2d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 21:23:01 +0300 Subject: [PATCH 31/94] rsx/fp/cfg: Fix input mask for DIV and DIVSQ instructions --- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp index 890653f569..2dc58a471b 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -257,7 +257,7 @@ namespace rsx::assembler::FP return xyz; case RSX_FP_OPCODE_DIV: case RSX_FP_OPCODE_DIVSQ: - return decode({ xyzw, x }); + return decode({ xyzw, x }) & dst_write_mask; case RSX_FP_OPCODE_LIF: return decode({ y | w }); case RSX_FP_OPCODE_FENCT: From 81d657a960f26902e20c213db3febf6d4e11d746 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 21:36:27 +0300 Subject: [PATCH 32/94] rsx/cfg: Fix grouping barrier16 instructions when lane is shared. --- .../Program/Assembler/Passes/FP/RegisterDependencyPass.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 0f528f66f3..b7e3dc2116 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -161,10 +161,11 @@ namespace rsx::assembler::FP const u32 src_reg_id = reg.reg.id / 2; const bool is_odd_reg = !!(reg.reg.id & 1); + const bool is_odd_ch = !!(ch & 1); const bool is_word0 = ch < 2; - // If we're a non-odd register, we should also write the next channel (y/w) - if (!is_odd_reg && (mask & 2)) + // If we're an even channel, we should also write the next channel (y/w) + if (!is_odd_ch && (mask & 2)) { mask >>= 1; ++ch; From 8d2f7ae85fc8d0cfb3c08eb45d3a9f93172cdf5c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 22:59:12 +0300 Subject: [PATCH 33/94] rsx/cfg: Implement partial barriers for 32-bit register channels --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 17 ++- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h | 7 +- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 2 +- .../Passes/FP/RegisterDependencyPass.cpp | 122 ++++++++++++++---- .../RSX/Program/CgBinaryFragmentProgram.cpp | 2 +- rpcs3/Emu/RSX/Program/RSXFragmentProgram.h | 2 +- rpcs3/tests/test_rsx_fp_asm.cpp | 96 ++++++++++++++ 7 files changed, 217 insertions(+), 31 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index cddc562fa5..4be6acb55b 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -263,13 +263,26 @@ namespace rsx::assembler { ensure(reg.length() > 1, "Invalid register specifier"); - const auto index = std::stoi(reg.substr(1)); + const auto parts = fmt::split(reg, { "." }); + ensure(parts.size() > 0 && parts.size() <= 2); + + const auto index = std::stoi(parts[0].substr(1)); RegisterRef ref { .reg { .id = index, .f16 = false }, .mask = 0x0F }; + if (parts.size() > 1 && parts[1].length() > 0) + { + // FIXME: No swizzles for now, just lane masking + ref.mask = 0; + if (parts[1].find("x") != std::string::npos) ref.mask |= (1u << 0); + if (parts[1].find("y") != std::string::npos) ref.mask |= (1u << 1); + if (parts[1].find("z") != std::string::npos) ref.mask |= (1u << 2); + if (parts[1].find("w") != std::string::npos) ref.mask |= (1u << 3); + } + if (reg[0] == 'H' || reg[0] == 'h') { ref.reg.f16 = true; @@ -325,7 +338,7 @@ namespace rsx::assembler do { \ inst->opcode = encoding.op; \ d0.opcode = encoding.op & 0x3F; \ - s1.opcode_is_branch = (encoding.op > 0x3F)? 1 : 0; \ + s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0; \ s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; \ s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; \ s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0; \ diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h index b2297a24ab..4e7f65f22b 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h @@ -75,7 +75,12 @@ namespace rsx::assembler RSX_FP_OPCODE_IFE = 0x42, // If RSX_FP_OPCODE_LOOP = 0x43, // Loop RSX_FP_OPCODE_REP = 0x44, // Repeat - RSX_FP_OPCODE_RET = 0x45 // Return + RSX_FP_OPCODE_RET = 0x45, // Return + + + // Custom opcodes for dependency injection + RSX_FP_OPCODE_OR16_LO = 0x46, // Performs a 16-bit OR, taking one register channel as input and overwriting low 16 bits of the output + RSX_FP_OPCODE_OR16_HI = 0x47, // Same as the lo variant but now overwrites the high 16-bit block }; namespace FP diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 82f5464a0a..577252fd83 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -159,7 +159,7 @@ namespace rsx::assembler src2.HEX = decoded._u32[3]; end = !!dst.end; - const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6); + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); if (opcode == RSX_FP_OPCODE_NOP) { diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index b7e3dc2116..4f8483d91d 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -22,6 +22,20 @@ namespace rsx::assembler::FP std::unordered_map sync_register_map; }; + enum Register32BarrierFlags + { + NONE = 0, + OR_WORD0 = 1, + OR_WORD1 = 2, + DEFAULT = OR_WORD0 | OR_WORD1 + }; + + struct RegisterBarrier32 + { + RegisterRef ref; + u32 flags[4]; + }; + std::vector decode_lanes16(const std::unordered_set& lanes) { std::vector result; @@ -47,34 +61,45 @@ namespace rsx::assembler::FP return result; } - std::vector decode_lanes32(const std::unordered_set& lanes) + std::vector decode_lanes32(const std::unordered_set& lanes) { - std::vector result; + std::vector result; for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 16) { // Each register has 8 16-bit lanes + RegisterBarrier32 barrier{}; + auto& ref = barrier.ref; - u32 mask = 0; - if (lanes.contains(file_offset + 0) || lanes.contains(file_offset + 2)) mask |= (1u << 0); - if (lanes.contains(file_offset + 4) || lanes.contains(file_offset + 6)) mask |= (1u << 1); - if (lanes.contains(file_offset + 8) || lanes.contains(file_offset + 10)) mask |= (1u << 2); - if (lanes.contains(file_offset + 12) || lanes.contains(file_offset + 14)) mask |= (1u << 3); + for (u32 lane = 0; lane < 16; lane += 2) + { + if (!lanes.contains(file_offset + lane)) + { + continue; + } - if (mask == 0) + const u32 ch = (lane / 4); + const u32 flags = (lane & 3) + ? Register32BarrierFlags::OR_WORD1 + : Register32BarrierFlags::OR_WORD0; + + ref.mask |= (1u << ch); + barrier.flags[ch] |= flags; + } + + if (ref.mask == 0) { continue; } - RegisterRef ref{ .reg{.id = static_cast(index), .f16 = false } }; - ref.mask = mask; - result.push_back(ref); + ref.reg = {.id = static_cast(index), .f16 = false }; + result.push_back(barrier); } return result; } - std::vector build_barrier32(const RegisterRef& reg) + std::vector build_barrier32(const RegisterBarrier32& barrier) { // Upto 4 instructions are needed per 32-bit register // R0.x = packHalf2x16(H0.xy) @@ -84,28 +109,27 @@ namespace rsx::assembler::FP std::vector result; - for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch) + for (u32 mask = barrier.ref.mask, ch = 0; mask > 0; mask >>= 1, ++ch) { if (!(mask & 1)) { continue; } + const auto& reg = barrier.ref.reg; + const auto reg_id = reg.id; + Instruction instruction{}; OPDEST dst{}; - dst.opcode = RSX_FP_OPCODE_PK2; dst.prec = RSX_FP_PRECISION_REAL; dst.fp16 = 0; - dst.dest_reg = reg.reg.id; + dst.dest_reg = reg_id; dst.write_mask = (1u << ch); - const u32 src_reg_id = (ch / 2) + (reg.reg.id * 2); + const u32 src_reg_id = (ch / 2) + (reg_id * 2); const bool is_word0 = !(ch & 1); // Only even SRC0 src0{}; - src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; - src0.fp16 = 1; - if (is_word0) { src0.swizzle_x = 0; @@ -121,14 +145,50 @@ namespace rsx::assembler::FP src0.swizzle_w = 3; src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP; src0.tmp_reg_index = src_reg_id; + src0.fp16 = 1; - instruction.opcode = dst.opcode; + // Prepare source 1 to match the output in case we need to encode an OR + SRC1 src1{}; + src1.reg_type = RSX_FP_REGISTER_TYPE_TEMP; + src1.tmp_reg_index = reg_id; + src1.swizzle_x = ch; + src1.swizzle_y = ch; + src1.swizzle_z = ch; + src1.swizzle_w = ch; + + u32 opcode = 0; + switch (barrier.flags[ch]) + { + case Register32BarrierFlags::DEFAULT: + opcode = RSX_FP_OPCODE_PK2; + break; + case Register32BarrierFlags::OR_WORD0: + opcode = RSX_FP_OPCODE_OR16_LO; + // Swap inputs + std::swap(src0.HEX, src1.HEX); + break; + case Register32BarrierFlags::OR_WORD1: + opcode = RSX_FP_OPCODE_OR16_HI; + src0.swizzle_x = src0.swizzle_y; + std::swap(src0.HEX, src1.HEX); + break; + case Register32BarrierFlags::NONE: + default: + fmt::throw_exception("Unexpected lane barrier with no mask."); + } + + dst.opcode = opcode & 0x3F; + src1.opcode_hi = (opcode > 0x3F) ? 1 : 0; + src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1; + + instruction.opcode = opcode; instruction.bytecode[0] = dst.HEX; instruction.bytecode[1] = src0.HEX; + instruction.bytecode[2] = src1.HEX; Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; - instruction.srcs.push_back({ .reg=src_reg, .mask=0xF }); - instruction.dsts.push_back({ .reg{ .id = reg.reg.id, .f16 = false }, .mask = (1u << ch) }); + instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); + instruction.dsts.push_back({ .reg{ .id = reg_id, .f16 = false }, .mask = (1u << ch) }); result.push_back(instruction); } @@ -207,10 +267,22 @@ namespace rsx::assembler::FP { std::vector result; - const auto regs = (f16 ? decode_lanes16 : decode_lanes32)(lanes); - for (const auto& ref : regs) + if (f16) { - auto instructions = (f16 ? build_barrier16 : build_barrier32)(ref); + const auto regs = decode_lanes16(lanes); + for (const auto& ref : regs) + { + auto instructions = build_barrier16(ref); + result.insert(result.end(), instructions.begin(), instructions.end()); + } + + return result; + } + + const auto barriers = decode_lanes32(lanes); + for (const auto& barrier : barriers) + { + auto instructions = build_barrier32(barrier); result.insert(result.end(), instructions.begin(), instructions.end()); } diff --git a/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp b/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp index a06818de10..1dfe83e468 100644 --- a/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp +++ b/rpcs3/Emu/RSX/Program/CgBinaryFragmentProgram.cpp @@ -273,7 +273,7 @@ void CgBinaryDisasm::TaskFP() src2.HEX = GetData(data[3]); m_step = 4 * sizeof(u32); - m_opcode = dst.opcode | (src1.opcode_is_branch << 6); + m_opcode = dst.opcode | (src1.opcode_hi << 6); auto SCT = [&]() { diff --git a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h index e20098ff57..d93ec760e6 100644 --- a/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h +++ b/rpcs3/Emu/RSX/Program/RSXFragmentProgram.h @@ -102,7 +102,7 @@ union SRC1 u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series) u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction) u32 scale : 3; - u32 opcode_is_branch : 1; + u32 opcode_hi : 1; // Opcode high bit }; struct diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 752baf0776..be4f19abc6 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -472,4 +472,100 @@ namespace rsx::assembler EXPECT_EQ(bb4->epilogue.size(), 0); EXPECT_EQ(bb5->epilogue.size(), 0); } + + TEST(TestFPIR, RegisterDependencyPass_Partial32_0) + { + // Instruction 2 partially clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a partial barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1.x, R1.x; + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 4); + + OPDEST dst{ .HEX = block.instructions[2].bytecode[0] }; + SRC0 src0{ .HEX = block.instructions[2].bytecode[1] }; + SRC1 src1{ .HEX = block.instructions[2].bytecode[2] }; + + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(opcode, RSX_FP_OPCODE_OR16_LO); + EXPECT_EQ(dst.fp16, 0); + EXPECT_EQ(dst.dest_reg, 0); + EXPECT_EQ(dst.mask_x, false); + EXPECT_EQ(dst.mask_y, false); + EXPECT_EQ(dst.mask_z, true); + EXPECT_EQ(dst.mask_w, false); + EXPECT_EQ(src0.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src0.tmp_reg_index, 0); + EXPECT_EQ(src0.fp16, 0); + EXPECT_EQ(src0.swizzle_x, 2); + EXPECT_EQ(src1.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src1.tmp_reg_index, 1); + EXPECT_EQ(src1.fp16, 1); + EXPECT_EQ(src1.swizzle_x, 0); + } + + TEST(TestFPIR, RegisterDependencyPass_Partial32_1) + { + // Instruction 2 partially clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 so a partial barrier32 is needed between them. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1.y, R1.y; + MOV R2, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + ASSERT_EQ(block.instructions.size(), 4); + + OPDEST dst{ .HEX = block.instructions[2].bytecode[0] }; + SRC0 src0{ .HEX = block.instructions[2].bytecode[1] }; + SRC1 src1{ .HEX = block.instructions[2].bytecode[2] }; + + const u32 opcode = dst.opcode | (src1.opcode_hi << 6); + + // R0.z = packHalf2(H1.xy); + EXPECT_EQ(opcode, RSX_FP_OPCODE_OR16_HI); + EXPECT_EQ(dst.fp16, 0); + EXPECT_EQ(dst.dest_reg, 0); + EXPECT_EQ(dst.mask_x, false); + EXPECT_EQ(dst.mask_y, false); + EXPECT_EQ(dst.mask_z, true); + EXPECT_EQ(dst.mask_w, false); + EXPECT_EQ(src0.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src0.tmp_reg_index, 0); + EXPECT_EQ(src0.fp16, 0); + EXPECT_EQ(src0.swizzle_x, 2); + EXPECT_EQ(src1.reg_type, RSX_FP_REGISTER_TYPE_TEMP); + EXPECT_EQ(src1.tmp_reg_index, 1); + EXPECT_EQ(src1.fp16, 1); + EXPECT_EQ(src1.swizzle_x, 1); + } } From 8cd241ca10248786158acade0f7a0bcb725bc7e2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 23:18:47 +0300 Subject: [PATCH 34/94] rsx/fp: Implement OR16_LO and OR16_HI instructions --- rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index c8b6156065..73e907130e 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1116,6 +1116,14 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode) return true; case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true; case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true; + + // Custom ISA extensions for 16-bit OR + case RSX_FP_OPCODE_OR16_HI: + SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0x0000ffff) | (packHalf2x16($1.xx) & 0xffff0000)))"); + return true; + case RSX_FP_OPCODE_OR16_LO: + SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0xffff0000) | (packHalf2x16($1.xx) & 0x0000ffff)))"); + return true; } return false; } From 93f89b8a742531480869248bf7d6918f48ad144d Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 23:41:50 +0300 Subject: [PATCH 35/94] rsx/cfg: Fix instruction injection when more than one barrier is needed for a single instruction --- .../Passes/FP/RegisterDependencyPass.cpp | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 4f8483d91d..01cfc6ece6 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -298,7 +298,7 @@ namespace rsx::assembler::FP std::unordered_set barrier32; // This subpass does not care about the prologue and epilogue and assumes each block is unique. - for (auto it = block->instructions.begin(); it != block->instructions.end();) + for (auto it = block->instructions.begin(); it != block->instructions.end(); ++it) { auto& inst = *it; @@ -340,33 +340,37 @@ namespace rsx::assembler::FP } } - if (barrier16.empty() && barrier32.empty()) - { - ++it; - continue; - } - // We need to inject some barrier instructions if (!barrier16.empty()) { auto barrier16_in = decode_lanes16(barrier16); + std::vector instructions; + instructions.reserve(barrier16_in.size()); + for (const auto& reg : barrier16_in) { - auto instructions = build_barrier16(reg); - it = block->instructions.insert(it, instructions.begin(), instructions.end()); - std::advance(it, instructions.size() + 1); + auto barrier = build_barrier16(reg); + instructions.insert(instructions.end(), barrier.begin(), barrier.end()); } + + it = block->instructions.insert(it, instructions.begin(), instructions.end()); + std::advance(it, instructions.size()); } if (!barrier32.empty()) { auto barrier32_in = decode_lanes32(barrier32); + std::vector instructions; + instructions.reserve(barrier32_in.size()); + for (const auto& reg : barrier32_in) { - auto instructions = build_barrier32(reg); - it = block->instructions.insert(it, instructions.begin(), instructions.end()); - std::advance(it, instructions.size() + 1); + auto barrier = build_barrier32(reg); + instructions.insert(instructions.end(), barrier.begin(), barrier.end()); } + + it = block->instructions.insert(it, instructions.begin(), instructions.end()); + std::advance(it, instructions.size()); } } } From 1e6fe1f4ab406a8980695eddc8178dfe28931e09 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sun, 7 Dec 2025 23:59:48 +0300 Subject: [PATCH 36/94] rsx/cfg/fp: Add delay-slot detection to remove unnecessary barriers - Reduces emitted barriers by like 99% --- .../Passes/FP/RegisterAnnotationPass.cpp | 42 ++++++++++++++++++- .../Passes/FP/RegisterAnnotationPass.h | 12 +++++- .../RSX/Program/FragmentProgramDecompiler.cpp | 2 +- rpcs3/tests/test_rsx_fp_asm.cpp | 26 ++++++++++++ 4 files changed, 77 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index b96856b7a6..58a589c6f7 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "RegisterAnnotationPass.h" #include "Emu/RSX/Program/Assembler/FPOpcodes.h" +#include "Emu/RSX/Program/RSXFragmentProgram.h" #include #include @@ -13,6 +14,38 @@ namespace rsx::assembler::FP static constexpr char content_float16 = 'H'; static constexpr char content_dual = 'D'; + bool is_delay_slot(const Instruction& instruction) + { + OPDEST dst{ .HEX = instruction.bytecode[0] }; + SRC0 src0{ .HEX = instruction.bytecode[1] }; + SRC1 src1{ .HEX = instruction.bytecode[2] }; + + if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV + dst.no_dest || // Must have a sink + src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg + dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self + dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter + dst.saturate || // Precision modifier + (dst.prec != RSX_FP_PRECISION_REAL && + dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers + { + return false; + } + + // Check if we have precision modifiers on the source + if (src0.abs || src0.neg || src1.scale) + { + return false; + } + + if (dst.mask_x && src0.swizzle_x != 0) return false; + if (dst.mask_y && src0.swizzle_y != 1) return false; + if (dst.mask_z && src0.swizzle_z != 2) return false; + if (dst.mask_w && src0.swizzle_w != 3) return false; + + return true; + } + std::vector compile_register_file(const std::array& file) { std::vector results; @@ -90,10 +123,15 @@ namespace rsx::assembler::FP } // Decay instructions into register references - void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog) + void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog, bool skip_delay_slots) { for (auto& instruction : block->instructions) { + if (skip_delay_slots && is_delay_slot(instruction)) + { + continue; + } + const u32 operand_count = get_operand_count(static_cast(instruction.opcode)); for (u32 i = 0; i < operand_count; i++) { @@ -178,7 +216,7 @@ namespace rsx::assembler::FP { for (auto& block : graph.blocks) { - annotate_instructions(&block, m_prog); + annotate_instructions(&block, m_prog, m_config.skip_delay_slots); annotate_block_io(&block); } } diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h index c719a80381..b5cab3da85 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.h @@ -6,6 +6,11 @@ struct RSXFragmentProgram; namespace rsx::assembler::FP { + struct RegisterAnnotationPassOptions + { + bool skip_delay_slots = false; // When enabled, detect delay slots and ignore annotating them. + }; + // The annotation pass annotates each basic block with 2 pieces of information: // 1. The "input" register list for a block. // 2. The "output" register list for a block (clobber list). @@ -14,13 +19,16 @@ namespace rsx::assembler::FP class RegisterAnnotationPass : public CFGPass { public: - RegisterAnnotationPass(const RSXFragmentProgram& prog) - : m_prog(prog) + RegisterAnnotationPass( + const RSXFragmentProgram& prog, + const RegisterAnnotationPassOptions& options = {}) + : m_prog(prog), m_config(options) {} void run(FlowGraph& graph) override; private: const RSXFragmentProgram& m_prog; + RegisterAnnotationPassOptions m_config; }; } diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 73e907130e..a524773e83 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1294,7 +1294,7 @@ std::string FragmentProgramDecompiler::Decompile() const auto rop_inputs = get_fragment_program_output_set(m_prog.ctrl, m_prog.mrt_buffers_count); rop_block->input_list.insert(rop_block->input_list.end(), rop_inputs.begin(), rop_inputs.end()); - FP::RegisterAnnotationPass annotation_pass{ m_prog }; + FP::RegisterAnnotationPass annotation_pass{ m_prog, { .skip_delay_slots = true } }; FP::RegisterDependencyPass dependency_pass{}; annotation_pass.run(graph); diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index be4f19abc6..c30f5ff172 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -568,4 +568,30 @@ namespace rsx::assembler EXPECT_EQ(src1.fp16, 1); EXPECT_EQ(src1.swizzle_x, 1); } + + TEST(TestFPIR, RegisterDependencyPass_SkipDelaySlots) + { + // Instruction 2 clobers H1 which in turn clobbers R0. + // Instruction 3 reads from R0 but is a delay slot that does nothing and can be NOPed. + auto graph = CFG_from_source(R"( + ADD R1, R0, R1; + MOV H1, R1 + MOV R0, R0; + )"); + + ASSERT_EQ(graph.blocks.size(), 1); + ASSERT_EQ(graph.blocks.front().instructions.size(), 3); + + auto& block = graph.blocks.front(); + RSXFragmentProgram prog{}; + + FP::RegisterAnnotationPass annotation_pass{ prog, { .skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Delay slot detection will cause no dependency injection + ASSERT_EQ(block.instructions.size(), 3); + } } From 43d8518faa22f600f39903ffe65660bf0bc5dc15 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 8 Dec 2025 00:14:07 +0300 Subject: [PATCH 37/94] rsx/fp: Warning cleanup --- rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index a524773e83..589e677b25 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -789,7 +789,6 @@ std::string FragmentProgramDecompiler::BuildCode() const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4); const std::string init_value = float4_type + "(0.)"; std::array output_register_names; - std::array ouput_register_indices = { 0, 2, 3, 4 }; // Holder for any "cleanup" before exiting main std::stringstream main_epilogue; From 98a12de25685e2034711cdaedd1b5c1c0f4752ea Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 8 Dec 2025 00:47:46 +0300 Subject: [PATCH 38/94] rsx/gtest: Enable unit tests for nix builds --- rpcs3/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/CMakeLists.txt b/rpcs3/CMakeLists.txt index 796351e16c..02a6bfeed4 100644 --- a/rpcs3/CMakeLists.txt +++ b/rpcs3/CMakeLists.txt @@ -193,6 +193,7 @@ if(BUILD_RPCS3_TESTS) tests/test_simple_array.cpp tests/test_address_range.cpp tests/test_rsx_cfg.cpp + tests/test_rsx_fp_asm.cpp ) target_link_libraries(rpcs3_test From 5688573b3de12ca5bc49a038d92319d7bea54726 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 8 Dec 2025 02:44:20 +0300 Subject: [PATCH 39/94] rsx/fp: Fix pre-branch epilogue emit --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 2 + .../RSX/Program/FragmentProgramDecompiler.cpp | 53 ++++++++++--------- 2 files changed, 31 insertions(+), 24 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 577252fd83..1f1e5b3678 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -139,6 +139,7 @@ namespace rsx::assembler default: // Missing an edge type? rsx_log.error("CFG: Unexpected block exit. Report to developers."); + break; } } else if (bb->pred.empty()) @@ -221,6 +222,7 @@ namespace rsx::assembler ir_inst.length += 4; pc++; } + break; } pc++; diff --git a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp index 589e677b25..94b92ce98e 100644 --- a/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp +++ b/rpcs3/Emu/RSX/Program/FragmentProgramDecompiler.cpp @@ -1276,7 +1276,7 @@ std::string FragmentProgramDecompiler::Decompile() // The RSX CFG is missing the output block. We inject a fake tail block that ingests the ROP outputs. BasicBlock* rop_block = nullptr; BasicBlock* tail_block = &graph.blocks.back(); - if (tail_block->instructions.size() == 0) + if (tail_block->instructions.empty()) { // Merge block. Use this directly rop_block = tail_block; @@ -1326,6 +1326,20 @@ std::string FragmentProgramDecompiler::Decompile() block_data[block] = { level, loop }; }; + auto emit_block = [&](const std::vector& instructions) + { + for (auto& inst : instructions) + { + m_instruction = &inst; + dst.HEX = inst.bytecode[0]; + src0.HEX = inst.bytecode[1]; + src1.HEX = inst.bytecode[2]; + src2.HEX = inst.bytecode[3]; + + ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); + } + }; + for (const auto &block : graph.blocks) { auto found = block_data.find(&block); @@ -1376,21 +1390,22 @@ std::string FragmentProgramDecompiler::Decompile() if (!block.prologue.empty()) { AddCode("// Prologue"); - - for (auto& inst : block.prologue) - { - m_instruction = &inst; - dst.HEX = inst.bytecode[0]; - src0.HEX = inst.bytecode[1]; - src1.HEX = inst.bytecode[2]; - src2.HEX = inst.bytecode[3]; - - ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); - } + emit_block(block.prologue); } + const bool early_epilogue = + !block.epilogue.empty() && + !block.succ.empty() && + (block.succ.front().type == EdgeType::IF || block.succ.front().type == EdgeType::LOOP); + for (const auto& inst : block.instructions) { + if (early_epilogue && &inst == &block.instructions.back()) + { + AddCode("// Epilogue"); + emit_block(block.epilogue); + } + m_instruction = &inst; dst.HEX = inst.bytecode[0]; @@ -1459,20 +1474,10 @@ std::string FragmentProgramDecompiler::Decompile() if (dst.end) break; } - if (!block.epilogue.empty()) + if (!early_epilogue && !block.epilogue.empty()) { AddCode("// Epilogue"); - - for (auto& inst : block.epilogue) - { - m_instruction = &inst; - dst.HEX = inst.bytecode[0]; - src0.HEX = inst.bytecode[1]; - src1.HEX = inst.bytecode[2]; - src2.HEX = inst.bytecode[3]; - - ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation"); - } + emit_block(block.epilogue); } for (auto& succ : block.succ) From 4dc52fde82eee7154252150c1ac52be6e013d8b2 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 8 Dec 2025 12:26:56 +0300 Subject: [PATCH 40/94] rsx/common: Implement list insert for simple_array --- rpcs3/Emu/RSX/Common/simple_array.hpp | 29 +++++++++++++++++++++-- rpcs3/tests/test_simple_array.cpp | 34 +++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/Common/simple_array.hpp b/rpcs3/Emu/RSX/Common/simple_array.hpp index 00dd6e7d95..6852e670fb 100644 --- a/rpcs3/Emu/RSX/Common/simple_array.hpp +++ b/rpcs3/Emu/RSX/Common/simple_array.hpp @@ -337,7 +337,7 @@ namespace rsx AUDIT(_loc < _size); const auto remaining = (_size - _loc); - memmove(pos + 1, pos, remaining * sizeof(Ty)); + std::memmove(pos + 1, pos, remaining * sizeof(Ty)); *pos = val; _size++; @@ -365,7 +365,7 @@ namespace rsx AUDIT(_loc < _size); const u32 remaining = (_size - _loc); - memmove(pos + 1, pos, remaining * sizeof(Ty)); + std::memmove(pos + 1, pos, remaining * sizeof(Ty)); *pos = val; _size++; @@ -373,6 +373,31 @@ namespace rsx return pos; } + iterator insert(iterator where, span_like auto const& values) + { + ensure(where >= _data); + const auto _loc = offset(where); + const auto in_size = static_cast(values.size()); + const auto in_size_bytes = in_size * sizeof(Ty); + + reserve(_size + in_size); + + if (_loc >= _size) + { + where = _data + _size; + std::memcpy(where, values.data(), in_size_bytes); + _size += in_size; + return where; + } + + const u32 remaining_bytes = (_size - _loc) * sizeof(Ty); + where = _data + _loc; + std::memmove(where + in_size, where, remaining_bytes); + std::memmove(where, values.data(), in_size_bytes); + _size += in_size; + return where; + } + void operator += (const rsx::simple_array& that) { const auto old_size = _size; diff --git a/rpcs3/tests/test_simple_array.cpp b/rpcs3/tests/test_simple_array.cpp index 8d64599b96..ebedff861d 100644 --- a/rpcs3/tests/test_simple_array.cpp +++ b/rpcs3/tests/test_simple_array.cpp @@ -324,6 +324,40 @@ namespace rsx EXPECT_EQ(arr.find_if(FN(x == 99)), nullptr); } + TEST(SimpleArray, InsertArray) + { + rsx::simple_array arr{ + 0, 1, 2, 6, 7, 8, 9 + }; + + const std::vector tail{ + 10, 11, 12 + }; + + const std::vector mid{ + 3, 4, 5 + }; + + // Insert end + arr.insert(arr.end(), tail); + EXPECT_EQ(arr.size(), 10); + + // Insert mid + auto it = arr.begin(); + std::advance(it, 3); + it = arr.insert(it, mid); + + EXPECT_EQ(arr.size(), 13); + EXPECT_EQ(std::distance(arr.begin(), it), 3); + EXPECT_EQ(*it, 3); + + // Verify + for (unsigned i = 0; i < arr.size(); ++i) + { + EXPECT_EQ(arr[i], static_cast(i)); + } + } + TEST(AlignedAllocator, Alloc) { auto ptr = rsx::aligned_allocator::malloc<256>(16); From a412e34fa6f2364df74f8396be07c489fde41e6c Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 9 Dec 2025 02:03:18 +0300 Subject: [PATCH 41/94] rsx/cfg/fp: Fix back-traversal for IF-ELSE pairs --- .../Passes/FP/RegisterDependencyPass.cpp | 23 ++- rpcs3/tests/test_rsx_fp_asm.cpp | 137 ++++++++++++++++++ 2 files changed, 152 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 01cfc6ece6..971f485d62 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" + #include "RegisterDependencyPass.h" #include "Emu/RSX/Program/Assembler/FPOpcodes.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" @@ -377,11 +378,6 @@ namespace rsx::assembler::FP void insert_block_register_dependency(DependencyPassContext& ctx, BasicBlock* block, const std::unordered_set& lanes, bool f16) { - if (block->pred.empty()) - { - return; - } - std::unordered_set clobbered_lanes; std::unordered_set lanes_to_search; @@ -389,6 +385,18 @@ namespace rsx::assembler::FP { auto target = back_edge.from; + // Quick check - if we've reached an IF-ELSE anchor, don't traverse upwards. + // The IF and ELSE edges are already a complete set and will bre processed before this node. + if (back_edge.type == EdgeType::ENDIF && + &back_edge == &block->pred.back() && + target->succ.size() == 3 && + target->succ[1].type == EdgeType::ELSE && + target->succ[2].type == EdgeType::ENDIF && + target->succ[2].to == block) + { + return; + } + // Did this target even clobber our register? ensure(ctx.exec_register_map.find(target) != ctx.exec_register_map.end(), "Block has not been pre-processed"); @@ -429,15 +437,14 @@ namespace rsx::assembler::FP if (lanes_to_search.empty()) { - break; + continue; } // We have some missing lanes. Search upwards if (!target->pred.empty()) { // We only need to search the last predecessor which is the true "root" of the branch - auto parent = target->pred.back().from; - insert_block_register_dependency(ctx, parent, lanes_to_search, f16); + insert_block_register_dependency(ctx, target, lanes_to_search, f16); } } } diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index c30f5ff172..d14dfacae4 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -594,4 +594,141 @@ namespace rsx::assembler // Delay slot detection will cause no dependency injection ASSERT_EQ(block.instructions.size(), 3); } + + TEST(TestFPIR, RegisterDependencyPass_Skip_IF_ELSE_Ancestors) + { + // R4/H8 is clobbered but an IF-ELSE chain follows it. + // Merge block reads H8, but since both IF-ELSE legs resolve the dependency, we do not need a barrier for H8. + // H6 is included as a control. + auto ir = FPIR::from_source(R"( + MOV R4, #{ 0.25 } + MOV H6.x, #{ 0.125 } + IF.LT + MOV H8, #{ 0.0 } + ELSE + MOV H8, #{ 0.25 } + ENDIF + ADD R0, R3, H8 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 4); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 3); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // We get one barrier on R3 (H6) but nont for R4 (H8) + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 1); + + // No intra-block barriers + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 3); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + } + + TEST(TestFPIR, RegisterDependencyPass_Process_IF_Ancestors) + { + // H8.x is clobbered but only an IF sequence follows with no ELSE. + // Merge block reads r4.x, but since both IF-ELSE legs resolve the dependency, we do not need a barrier. + auto ir = FPIR::from_source(R"( + MOV H8.x, #{ 0.25 } + IF.LT + MOV R4.x, #{ 0.0 } + ENDIF + MOV R0, R4 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 3); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // A barrier will be inserted into block 0 epilogue + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 1); + + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 1); + EXPECT_EQ(get_graph_block(graph, 1)->epilogue.size(), 0); + EXPECT_EQ(get_graph_block(graph, 2)->epilogue.size(), 0); + } + + TEST(TestFPIR, RegisterDependencyPass_Complex_IF_ELSE_Ancestor_Clobber) + { + // 2 clobbered registers up the chain. + // 1 full barrier is needed for R4 (4 instructions) + auto ir = FPIR::from_source(R"( + MOV R4, #{ 0.0 } + IF.LT + MOV H9, #{ 0.25 } + ENDIF + MOV H8, #{ 0.25 } + IF.LT + IF.GT + ADD R0, R0, R0 + ELSE + ADD R0, R1, R0 + ENDIF + ENDIF + ADD R0, R0, R4 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 7); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 4)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 5)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 6)->instructions.size(), 1); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Full-lane barrier on writing blocks + EXPECT_EQ(get_graph_block(graph, 1)->epilogue.size(), 2); + EXPECT_EQ(get_graph_block(graph, 2)->epilogue.size(), 2); + + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 1)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 2)->instructions.size(), 2); + EXPECT_EQ(get_graph_block(graph, 3)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 4)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 5)->instructions.size(), 1); + EXPECT_EQ(get_graph_block(graph, 6)->instructions.size(), 1); + } } From 2c6d3dde672e54aab236e49e903b7f16ba686384 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 9 Dec 2025 03:04:03 +0300 Subject: [PATCH 42/94] C++ Pro - Different from C++ amateur. - Addresses code review suggestions to step up the C++ --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 53 ++++++++----------- rpcs3/Emu/RSX/Program/Assembler/FPASM.h | 2 +- .../Passes/FP/RegisterAnnotationPass.cpp | 23 +++----- .../Passes/FP/RegisterDependencyPass.cpp | 22 ++++---- 4 files changed, 43 insertions(+), 57 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index 4be6acb55b..ee8f4441cb 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -19,7 +19,7 @@ namespace rsx::assembler bool set_cond; }; - static std::unordered_map s_opcode_lookup + static std::unordered_map s_opcode_lookup { // Arithmetic { "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, @@ -168,10 +168,9 @@ namespace rsx::assembler std::vector result; result.reserve(m_instructions.size() * 4); - for (u32 i = 0; i < m_instructions.size(); ++i) + for (const auto& inst : m_instructions) { - const auto& inst = m_instructions[i]; - auto src = reinterpret_cast*>(inst.bytecode); + const auto src = reinterpret_cast*>(inst.bytecode); for (u32 j = 0; j < inst.length; ++j) { const u16 low = src[j * 2]; @@ -184,7 +183,7 @@ namespace rsx::assembler return result; } - FPIR FPIR::from_source(const std::string& asm_) + FPIR FPIR::from_source(std::string_view asm_) { std::vector instructions = fmt::split(asm_, { "\n", ";" }); if (instructions.empty()) @@ -192,13 +191,13 @@ namespace rsx::assembler return {}; } - auto transform_inst = [](const std::string& s) + auto transform_inst = [](std::string_view s) { std::string result; result.reserve(s.size()); bool literal = false; - for (auto& c : s) + for (const auto& c : s) { if (c == ' ') { @@ -235,7 +234,7 @@ namespace rsx::assembler return result; }; - auto decode_instruction = [&](const std::string& inst, std::string& op, std::string& dst, std::vector& sources) + auto decode_instruction = [&](std::string_view inst, std::string& op, std::string& dst, std::vector& sources) { const auto i = transform_inst(inst); if (i.empty()) @@ -259,7 +258,7 @@ namespace rsx::assembler } }; - auto get_ref = [](const std::string& reg) + auto get_ref = [](std::string_view reg) { ensure(reg.length() > 1, "Invalid register specifier"); @@ -291,15 +290,15 @@ namespace rsx::assembler return ref; }; - auto get_constants = [](const std::string& reg) -> std::array + auto get_constants = [](std::string_view reg) -> std::array { float x, y, z, w; - if (sscanf_s(reg.c_str(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4) + if (sscanf_s(reg.data(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4) { return { x, y, z, w }; } - if (sscanf_s(reg.c_str(), "#{%f}", &x) == 1) + if (sscanf_s(reg.data(), "#{%f}", &x) == 1) { return { x, x, x, x }; } @@ -328,35 +327,29 @@ namespace rsx::assembler } }; - auto encode_opcode = [](const std::string& op, Instruction* inst) + auto encode_opcode = [](std::string_view op, Instruction* inst) { OPDEST d0 { .HEX = inst->bytecode[0] }; SRC0 s0 { .HEX = inst->bytecode[1] }; SRC1 s1 { .HEX = inst->bytecode[2] }; -#define SET_OPCODE(encoding) \ - do { \ - inst->opcode = encoding.op; \ - d0.opcode = encoding.op & 0x3F; \ - s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0; \ - s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; \ - s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; \ - s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0; \ - d0.set_cond = encoding.set_cond ? 1 : 0; \ - inst->bytecode[0] = d0.HEX; \ - inst->bytecode[1] = s0.HEX; \ - inst->bytecode[2] = s1.HEX; \ - } while (0) - const auto found = s_opcode_lookup.find(op); if (found == s_opcode_lookup.end()) { fmt::throw_exception("Unhandled instruction '%s'", op); } + const auto& encoding = found->second; - SET_OPCODE(found->second); - -#undef SET_OPCODE + inst->opcode = encoding.op; + d0.opcode = encoding.op & 0x3F; + s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0; + s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0; + s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0; + s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0; + d0.set_cond = encoding.set_cond ? 1 : 0; + inst->bytecode[0] = d0.HEX; + inst->bytecode[1] = s0.HEX; + inst->bytecode[2] = s1.HEX; }; std::string op, dst; diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h index 4f413c21c3..83fc2fb6b1 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.h +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.h @@ -16,7 +16,7 @@ namespace rsx::assembler const std::vector& instructions() const; std::vector compile() const; - static FPIR from_source(const std::string& asm_); + static FPIR from_source(std::string_view asm_); private: Instruction* load(const RegisterRef& reg, int operand, Instruction* target = nullptr); diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index 58a589c6f7..4f63f364b6 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -16,9 +16,9 @@ namespace rsx::assembler::FP bool is_delay_slot(const Instruction& instruction) { - OPDEST dst{ .HEX = instruction.bytecode[0] }; - SRC0 src0{ .HEX = instruction.bytecode[1] }; - SRC1 src1{ .HEX = instruction.bytecode[2] }; + const OPDEST dst{ .HEX = instruction.bytecode[0] }; + const SRC0 src0{ .HEX = instruction.bytecode[1] }; + const SRC1 src1{ .HEX = instruction.bytecode[2] }; if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV dst.no_dest || // Must have a sink @@ -70,21 +70,14 @@ namespace rsx::assembler::FP if (ref) { - results.push_back(ref); + results.push_back(std::move(ref)); } } // Helper to check a span for 32-bit access auto match_any_32 = [](const std::span lanes) { - for (const auto& c : lanes) - { - if (c == content_dual || c == content_float32) - { - return true; - } - } - return false; + return std::any_of(lanes.begin(), lanes.end(), FN(x == content_dual || x == content_float32)); }; // F32 register processing @@ -115,7 +108,7 @@ namespace rsx::assembler::FP if (ref) { - results.push_back(ref); + results.push_back(std::move(ref)); } } @@ -142,13 +135,13 @@ namespace rsx::assembler::FP continue; } - instruction.srcs.push_back(reg); + instruction.srcs.push_back(std::move(reg)); } RegisterRef dst = get_dst_register(&instruction); if (dst) { - instruction.dsts.push_back(dst); + instruction.dsts.push_back(std::move(dst)); } } } diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index 971f485d62..c5b24b35bc 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -57,7 +57,7 @@ namespace rsx::assembler::FP RegisterRef ref{ .reg{.id = static_cast(index), .f16 = true } }; ref.mask = mask; - result.push_back(ref); + result.push_back(std::move(ref)); } return result; } @@ -94,7 +94,7 @@ namespace rsx::assembler::FP } ref.reg = {.id = static_cast(index), .f16 = false }; - result.push_back(barrier); + result.push_back(std::move(barrier)); } return result; @@ -190,7 +190,7 @@ namespace rsx::assembler::FP Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); instruction.dsts.push_back({ .reg{ .id = reg_id, .f16 = false }, .mask = (1u << ch) }); - result.push_back(instruction); + result.push_back(std::move(instruction)); } return result; @@ -258,7 +258,7 @@ namespace rsx::assembler::FP Register src_reg{ .id = static_cast(src_reg_id), .f16 = true }; instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF }); instruction.dsts.push_back({ .reg{.id = reg.reg.id, .f16 = false }, .mask = dst.write_mask }); - result.push_back(instruction); + result.push_back(std::move(instruction)); } return result; @@ -284,7 +284,7 @@ namespace rsx::assembler::FP for (const auto& barrier : barriers) { auto instructions = build_barrier32(barrier); - result.insert(result.end(), instructions.begin(), instructions.end()); + result.insert(result.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); } return result; @@ -351,10 +351,10 @@ namespace rsx::assembler::FP for (const auto& reg : barrier16_in) { auto barrier = build_barrier16(reg); - instructions.insert(instructions.end(), barrier.begin(), barrier.end()); + instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end())); } - it = block->instructions.insert(it, instructions.begin(), instructions.end()); + it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); std::advance(it, instructions.size()); } @@ -367,10 +367,10 @@ namespace rsx::assembler::FP for (const auto& reg : barrier32_in) { auto barrier = build_barrier32(reg); - instructions.insert(instructions.end(), barrier.begin(), barrier.end()); + instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end())); } - it = block->instructions.insert(it, instructions.begin(), instructions.end()); + it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); std::advance(it, instructions.size()); } } @@ -431,8 +431,8 @@ namespace rsx::assembler::FP if (!clobbered_lanes.empty()) { - const auto instructions = resolve_dependencies(clobbered_lanes, f16); - target->epilogue.insert(target->epilogue.end(), instructions.begin(), instructions.end()); + auto instructions = resolve_dependencies(clobbered_lanes, f16); + target->epilogue.insert(target->epilogue.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end())); } if (lanes_to_search.empty()) From 1ea3c121fa5036a28725560e11bd6ec8346d21c9 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 10 Dec 2025 01:40:26 +0300 Subject: [PATCH 43/94] rsx/cfg: Fix delay-slot detection when copying from the same register index but different precision. --- rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp | 1 + .../Passes/FP/RegisterAnnotationPass.cpp | 22 +++++++++--- rpcs3/tests/test_rsx_fp_asm.cpp | 35 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp index ee8f4441cb..2d74fafc73 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp @@ -24,6 +24,7 @@ namespace rsx::assembler // Arithmetic { "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, { "MOV", { .op = RSX_FP_OPCODE_MOV, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, + { "MUL", { .op = RSX_FP_OPCODE_MUL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, { "ADD", { .op = RSX_FP_OPCODE_ADD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, { "MAD", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, { "FMA", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } }, diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index 4f63f364b6..9b031f2a0e 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" + #include "RegisterAnnotationPass.h" #include "Emu/RSX/Program/Assembler/FPOpcodes.h" #include "Emu/RSX/Program/RSXFragmentProgram.h" @@ -24,14 +25,27 @@ namespace rsx::assembler::FP dst.no_dest || // Must have a sink src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self - dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter - dst.saturate || // Precision modifier - (dst.prec != RSX_FP_PRECISION_REAL && - dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers + dst.fp16 != src0.fp16 || // Must really be the same register + src0.abs || src0.neg || + dst.saturate) // Precision modifier { return false; } + switch (dst.prec) + { + case RSX_FP_PRECISION_REAL: + case RSX_FP_PRECISION_UNKNOWN: + break; + case RSX_FP_PRECISION_HALF: + if (!src0.fp16) return false; + break; + case RSX_FP_PRECISION_FIXED12: + case RSX_FP_PRECISION_FIXED9: + case RSX_FP_PRECISION_SATURATE: + return false; + } + // Check if we have precision modifiers on the source if (src0.abs || src0.neg || src1.scale) { diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index d14dfacae4..9be88db5c7 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -731,4 +731,39 @@ namespace rsx::assembler EXPECT_EQ(get_graph_block(graph, 5)->instructions.size(), 1); EXPECT_EQ(get_graph_block(graph, 6)->instructions.size(), 1); } + + TEST(TestFPIR, RegisterDependencyPass_SplinterCell_DelaySlot) + { + // Real shader pattern found in splinter cell blacklist. + // TEX instructions replaced with MOV for simplicity. + // There are no dependent reads here, no barriers are expected. + // In the game, instruction 4 was misclassified as a delay slot, causing a skipped clobber. + auto ir = FPIR::from_source(R"( + MOV R0.w, #{ 0.25 } + MOV H0, H8 + MUL R0.w, H0.w, R0.w + MOV R0.xyz, H0.xyz + MOV R1, #{ 0.25 } + FMA H0, R0, #{ 0.125 }, R1 + )"); + + auto bytecode = ir.compile(); + RSXFragmentProgram prog{}; + prog.data = bytecode.data(); + auto graph = deconstruct_fragment_program(prog); + + // Verify state before + ASSERT_EQ(graph.blocks.size(), 1); + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 6); + + FP::RegisterAnnotationPass annotation_pass{ prog, {.skip_delay_slots = true } }; + FP::RegisterDependencyPass deps_pass{}; + + annotation_pass.run(graph); + deps_pass.run(graph); + + // Verify state after + EXPECT_EQ(get_graph_block(graph, 0)->instructions.size(), 6); + EXPECT_EQ(get_graph_block(graph, 0)->epilogue.size(), 0); + } } From 0f1eadcab038fa6f2d20cb639fe16d72b97f9b4a Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 10 Dec 2025 11:50:09 +0300 Subject: [PATCH 44/94] rsx/gtest: Drop unused function --- rpcs3/tests/test_rsx_fp_asm.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/rpcs3/tests/test_rsx_fp_asm.cpp b/rpcs3/tests/test_rsx_fp_asm.cpp index 9be88db5c7..704df5a23d 100644 --- a/rpcs3/tests/test_rsx_fp_asm.cpp +++ b/rpcs3/tests/test_rsx_fp_asm.cpp @@ -63,14 +63,6 @@ namespace rsx::assembler return graph; } - static BasicBlock* BB_from_source(FlowGraph* graph, const std::string& asm_) - { - auto ir = FPIR::from_source(asm_); - graph->blocks.push_back({}); - BasicBlock& bb = graph->blocks.back(); - bb.instructions = ir.instructions(); - return &bb; - } TEST(TestFPIR, FromSource) { auto ir = FPIR::from_source(R"( From 1b01a9274c7c65263ce90b04c0b80149eb6c72b7 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 04:34:23 +0100 Subject: [PATCH 45/94] cellGem: clarify member descriptions --- rpcs3/Emu/Cell/Modules/cellGem.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.h b/rpcs3/Emu/Cell/Modules/cellGem.h index c70e2386ba..7dfdcb3b14 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.h +++ b/rpcs3/Emu/Cell/Modules/cellGem.h @@ -246,15 +246,15 @@ struct CellGemInfo // z increases towards user (away from the camera) struct CellGemState { - be_t pos[4]; // center of sphere (mm) - be_t vel[4]; // velocity of sphere (mm/s) - be_t accel[4]; // acceleration of sphere (mm/s²) + be_t pos[4]; // center of sphere in world coordinates (mm) + be_t vel[4]; // velocity of sphere in world coordinates (mm/s) + be_t accel[4]; // acceleration of sphere in world coordinates (mm/s²) be_t quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) - be_t angvel[4]; // angular velocity of controller (radians/s) - be_t angaccel[4]; // angular acceleration of controller (radians/s²) - be_t handle_pos[4]; // center of controller handle (mm) - be_t handle_vel[4]; // velocity of controller handle (mm/s) - be_t handle_accel[4]; // acceleration of controller handle (mm/s²) + be_t angvel[4]; // angular velocity of controller in world coordinates (radians/s) + be_t angaccel[4]; // angular acceleration of controller in world coordinates (radians/s²) + be_t handle_pos[4]; // center of controller handle in world coordinates (mm) + be_t handle_vel[4]; // velocity of controller handle in world coordinates (mm/s) + be_t handle_accel[4]; // acceleration of controller handle in world coordinates (mm/s²) CellGemPadData pad; CellGemExtPortData ext; be_t timestamp; // system_time_t (microseconds) From 26ae6f0902b3382aeb970b2f33090da58711372c Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 04:34:56 +0100 Subject: [PATCH 46/94] cellGem: fix default accelerometer value --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 7f02a0eabe..5b19d92746 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -2769,7 +2769,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us); inertial_state->counter = gem.inertial_counter++; - inertial_state->accelerometer[0] = 10; // Current gravity in m/s² + inertial_state->accelerometer[2] = 1.0f; // Current gravity in G units (9.81 == 1 unit) switch (g_cfg.io.move) { From c6ef09500ab21ee77307adadfbb2208a8083c8b3 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 04:35:26 +0100 Subject: [PATCH 47/94] cellGem: fix division by zero --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 5b19d92746..b4902835f0 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -1617,7 +1617,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle const f32 scaling_width = x_max / static_cast(shared_data.width); const f32 scaling_height = y_max / static_cast(shared_data.height); - const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; + const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius); // Image coordinates in pixels const f32 image_x = static_cast(x_pos) / scaling_width; @@ -1670,7 +1670,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con const f32 scaling_width = x_max / static_cast(shared_data.width); const f32 scaling_height = y_max / static_cast(shared_data.height); - const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius; + const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius); // Image coordinates in pixels const f32 image_x = static_cast(x_pos) / scaling_width; From f7cda4b2b4cafe303ae14d40eb1e6077daa04ce4 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 04:47:20 +0100 Subject: [PATCH 48/94] cellGem: fix default orientation --- rpcs3/Emu/Io/PadHandler.cpp | 2 +- rpcs3/Emu/Io/pad_types.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 4fc8fa6376..175cd3e90a 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -950,7 +950,7 @@ void PadDevice::update_orientation(ps_move_data& move_data) // 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); + const f32 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. diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index 8c669a9413..eeeec3c658 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -485,7 +485,7 @@ struct ps_move_data bool magnetometer_enabled = false; bool orientation_enabled = false; - static constexpr std::array default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f }; + static constexpr std::array default_quaternion { 0.0f, 0.0f, 0.0f, 1.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² From 757e9a04939555c10054550688bd4cbff32f4f94 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 04:50:21 +0100 Subject: [PATCH 49/94] cellGem: implement world coordinate orientation in cellGemGetState --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 44 +++++++--- rpcs3/Emu/Io/PadHandler.cpp | 31 +++---- rpcs3/Emu/Io/pad_types.cpp | 132 +++++++++++++++++++++++++++-- rpcs3/Emu/Io/pad_types.h | 62 +++++++++++--- rpcs3/Input/ps_move_handler.cpp | 18 ++-- 5 files changed, 232 insertions(+), 55 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index b4902835f0..fccf164b94 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -276,6 +276,8 @@ public: u64 start_timestamp_us = 0; + std::array mouse_move_data {}; // No need to be in savestate + atomic_t m_wake_up = 0; atomic_t m_done = 0; @@ -1657,7 +1659,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle } } -static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, const ps_move_data& move_data) +static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data) { const auto& shared_data = g_fxo->get(); @@ -1733,6 +1735,17 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con gem_state->quat[3] = q_w; } + if constexpr (!ps_move_data::use_imu_for_velocity) + { + move_data.update_velocity(shared_data.frame_timestamp_us, gem_state->pos); + + for (u32 i = 0; i < 3; i++) + { + gem_state->vel[i] = move_data.vel_world[i]; + gem_state->accel[i] = move_data.accel_world[i]; + } + } + // Update visibility for fake handlers if (g_cfg.io.move != move_handler::real) { @@ -1906,9 +1919,17 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co if constexpr (std::is_same_v>) { gem_state->temperature = pad->move_data.temperature; - gem_state->accel[0] = pad->move_data.accelerometer_x * 1000; // linear velocity in mm/s² - gem_state->accel[1] = pad->move_data.accelerometer_y * 1000; // linear velocity in mm/s² - gem_state->accel[2] = pad->move_data.accelerometer_z * 1000; // linear velocity in mm/s² + + for (u32 i = 0; i < 3; i++) + { + if constexpr (ps_move_data::use_imu_for_velocity) + { + gem_state->vel[i] = pad->move_data.vel_world[i]; + gem_state->accel[i] = pad->move_data.accel_world[i]; + } + gem_state->angvel[i] = pad->move_data.angvel_world[i]; + gem_state->angaccel[i] = pad->move_data.angaccel_world[i]; + } pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data); } @@ -2147,7 +2168,8 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con if constexpr (std::is_same_v>) { - pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, {}); + ps_move_data& move_data = ::at32(g_fxo->get().mouse_move_data, mouse_no); + pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data); } else if constexpr (std::is_same_v>) { @@ -2786,12 +2808,12 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v if (pad && pad->is_connected() && !pad->is_copilot()) { inertial_state->temperature = pad->move_data.temperature; - inertial_state->accelerometer[0] = pad->move_data.accelerometer_x; - inertial_state->accelerometer[1] = pad->move_data.accelerometer_y; - inertial_state->accelerometer[2] = pad->move_data.accelerometer_z; - inertial_state->gyro[0] = pad->move_data.gyro_x; - inertial_state->gyro[1] = pad->move_data.gyro_y; - inertial_state->gyro[2] = pad->move_data.gyro_z; + + for (u32 i = 0; i < 3; i++) + { + inertial_state->accelerometer[i] = pad->move_data.accelerometer[i]; + inertial_state->gyro[i] = pad->move_data.gyro[i]; + } } } diff --git a/rpcs3/Emu/Io/PadHandler.cpp b/rpcs3/Emu/Io/PadHandler.cpp index 175cd3e90a..d25ba1379c 100644 --- a/rpcs3/Emu/Io/PadHandler.cpp +++ b/rpcs3/Emu/Io/PadHandler.cpp @@ -874,12 +874,12 @@ void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f // 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 + 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) @@ -959,17 +959,17 @@ void PadDevice::update_orientation(ps_move_data& move_data) const FusionVector accelerometer{ .axis { - .x = -move_data.accelerometer_x, - .y = +move_data.accelerometer_y, - .z = +move_data.accelerometer_z + .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) + .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()) } }; @@ -979,9 +979,9 @@ void PadDevice::update_orientation(ps_move_data& move_data) { magnetometer = FusionVector{ .axis { - .x = move_data.magnetometer_x, - .y = move_data.magnetometer_y, - .z = move_data.magnetometer_z + .x = move_data.magnetometer.x(), + .y = move_data.magnetometer.y(), + .z = move_data.magnetometer.z() } }; } @@ -995,4 +995,5 @@ void PadDevice::update_orientation(ps_move_data& move_data) move_data.quaternion[1] = quaternion.array[2]; move_data.quaternion[2] = quaternion.array[3]; move_data.quaternion[3] = quaternion.array[0]; + move_data.update_orientation(elapsed_sec); } diff --git a/rpcs3/Emu/Io/pad_types.cpp b/rpcs3/Emu/Io/pad_types.cpp index 9005bc7fe5..2396a2ff74 100644 --- a/rpcs3/Emu/Io/pad_types.cpp +++ b/rpcs3/Emu/Io/pad_types.cpp @@ -162,15 +162,129 @@ 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; + accelerometer = {}; + gyro = {}; + prev_gyro = {}; + angular_acceleration = {}; + magnetometer = {}; + //prev_pos_world = {}; // probably no reset needed ? + vel_world = {}; + prev_vel_world = {}; + accel_world = {}; + angvel_world = {}; + angaccel_world = {}; +} + +void ps_move_data::update_orientation(f32 delta_time) +{ + if (!delta_time) + return; + + // Rotate vector v by quaternion q + const auto rotate_vector = [](const vect<4>& q, const vect<3>& v) + { + const vect<4> qv({0.0f, v.x(), v.y(), v.z()}); + const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()}); + + // t = q * v + vect<4> t; + t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z(); + t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y(); + t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x(); + t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x(); + + // r = t * q_inv + vect<4> r; + r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z(); + r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y(); + r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x(); + r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x(); + + return vect<3>({r.x(), r.y(), r.z()}); + }; + + if constexpr (use_imu_for_velocity) + { + // Gravity in world frame + constexpr f32 gravity = 9.81f; + constexpr vect<3> g({0.0f, 0.0f, -gravity}); + + // Rotate gravity into sensor frame + const vect<3> g_sensor = rotate_vector(quaternion, g); + + // Remove gravity + vect<3> linear_local; + for (u32 i = 0; i < 3; i++) + { + linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i]; + } + + // Linear acceleration (rotate to world coordinates) + accel_world = rotate_vector(quaternion, linear_local); + + // convert to mm/s² + for (u32 i = 0; i < 3; i++) + { + accel_world[i] *= 1000.0f; + } + + // Linear velocity (integrate acceleration) + for (u32 i = 0; i < 3; i++) + { + vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time; + } + + prev_vel_world = vel_world; + } + + // Compute raw angular acceleration + for (u32 i = 0; i < 3; i++) + { + const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time; + + // Filtering + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha; + } + + // Angular velocity (rotate to world coordinates) + angvel_world = rotate_vector(quaternion, gyro); + + // Angular acceleration (rotate to world coordinates) + angaccel_world = rotate_vector(quaternion, angular_acceleration); + + prev_gyro = gyro; +} + +void ps_move_data::update_velocity(u64 timestamp, be_t pos_world[4]) +{ + if constexpr (use_imu_for_velocity) + return; + + if (last_velocity_update_time_us == timestamp) + return; + + // Get elapsed time since last update + const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f); + last_velocity_update_time_us = timestamp; + + if (!delta_time) + return; + + for (u32 i = 0; i < 3; i++) + { + // Linear velocity + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i]; + prev_pos_world[i] = pos_world[i]; + + // Linear acceleration + accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time; + } + + prev_vel_world = vel_world; } bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id) diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index eeeec3c658..f8f86dfd54 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -471,6 +471,33 @@ struct VibrateMotor struct ps_move_data { + template + struct vect + { + public: + vect() = default; + constexpr vect(const std::array& vec) : data(vec) {}; + + template + T& operator[](I i) { return data[i]; } + + template + const T& operator[](I i) const { return data[i]; } + + T x() const requires (Size >= 1) { return data[0]; } + T y() const requires (Size >= 2) { return data[1]; } + T z() const requires (Size >= 3) { return data[2]; } + T w() const requires (Size >= 4) { return data[3]; } + + T& x() requires (Size >= 1) { return data[0]; } + T& y() requires (Size >= 2) { return data[1]; } + T& z() requires (Size >= 3) { return data[2]; } + T& w() requires (Size >= 4) { return data[3]; } + + private: + std::array data {}; + }; + u32 external_device_id = 0; std::array external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE std::array external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE @@ -485,20 +512,33 @@ struct ps_move_data bool magnetometer_enabled = false; bool orientation_enabled = false; - static constexpr std::array default_quaternion { 0.0f, 0.0f, 0.0f, 1.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² - f32 gyro_x = 0.0f; // angular velocity in rad/s - f32 gyro_y = 0.0f; // angular velocity in rad/s - f32 gyro_z = 0.0f; // angular velocity in rad/s - f32 magnetometer_x = 0.0f; - f32 magnetometer_y = 0.0f; - f32 magnetometer_z = 0.0f; + // Disable IMU tracking of velocity and acceleration (massive drift) + static constexpr bool use_imu_for_velocity = false; + u64 last_velocity_update_time_us = 0; + + static constexpr vect<4> default_quaternion = vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f }); + vect<4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) + + // Raw values (local) + vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit) + vect<3> gyro {}; // angular velocity in rad/s + vect<3> prev_gyro {}; // previous angular velocity in rad/s + vect<3> angular_acceleration {}; // angular acceleration in rad/s² + vect<3> magnetometer {}; + + // In world coordinates + vect<3> prev_pos_world {}; + vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s) + vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s) + vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²) + vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s) + vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²) + s16 temperature = 0; void reset_sensors(); + void update_orientation(f32 delta_time); + void update_velocity(u64 timestamp, be_t pos_world[4]); }; struct Pad diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index acf5fac3b7..f37fc8c68f 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -711,21 +711,21 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) 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; + 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.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)); } } From c2284c962b720743424090b0bb3de15948feb252 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 16:22:27 +0100 Subject: [PATCH 50/94] move ps_move_data to own file --- rpcs3/Emu/CMakeLists.txt | 1 + rpcs3/Emu/Io/pad_types.cpp | 128 ------------------------------- rpcs3/Emu/Io/pad_types.h | 75 +------------------ rpcs3/Emu/Io/ps_move_data.cpp | 137 ++++++++++++++++++++++++++++++++++ rpcs3/Emu/Io/ps_move_data.h | 75 +++++++++++++++++++ rpcs3/emucore.vcxproj | 2 + rpcs3/emucore.vcxproj.filters | 6 ++ 7 files changed, 223 insertions(+), 201 deletions(-) create mode 100644 rpcs3/Emu/Io/ps_move_data.cpp create mode 100644 rpcs3/Emu/Io/ps_move_data.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 48674612c7..83e8a28398 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -411,6 +411,7 @@ target_sources(rpcs3_emu PRIVATE Io/pad_config_types.cpp Io/pad_types.cpp Io/PadHandler.cpp + Io/ps_move_data.cpp Io/rb3drums_config.cpp Io/RB3MidiDrums.cpp Io/RB3MidiGuitar.cpp diff --git a/rpcs3/Emu/Io/pad_types.cpp b/rpcs3/Emu/Io/pad_types.cpp index 2396a2ff74..ad3369a7a7 100644 --- a/rpcs3/Emu/Io/pad_types.cpp +++ b/rpcs3/Emu/Io/pad_types.cpp @@ -159,134 +159,6 @@ u32 get_axis_keycode(u32 offset, u16 value) } } -void ps_move_data::reset_sensors() -{ - quaternion = default_quaternion; - accelerometer = {}; - gyro = {}; - prev_gyro = {}; - angular_acceleration = {}; - magnetometer = {}; - //prev_pos_world = {}; // probably no reset needed ? - vel_world = {}; - prev_vel_world = {}; - accel_world = {}; - angvel_world = {}; - angaccel_world = {}; -} - -void ps_move_data::update_orientation(f32 delta_time) -{ - if (!delta_time) - return; - - // Rotate vector v by quaternion q - const auto rotate_vector = [](const vect<4>& q, const vect<3>& v) - { - const vect<4> qv({0.0f, v.x(), v.y(), v.z()}); - const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()}); - - // t = q * v - vect<4> t; - t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z(); - t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y(); - t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x(); - t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x(); - - // r = t * q_inv - vect<4> r; - r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z(); - r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y(); - r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x(); - r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x(); - - return vect<3>({r.x(), r.y(), r.z()}); - }; - - if constexpr (use_imu_for_velocity) - { - // Gravity in world frame - constexpr f32 gravity = 9.81f; - constexpr vect<3> g({0.0f, 0.0f, -gravity}); - - // Rotate gravity into sensor frame - const vect<3> g_sensor = rotate_vector(quaternion, g); - - // Remove gravity - vect<3> linear_local; - for (u32 i = 0; i < 3; i++) - { - linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i]; - } - - // Linear acceleration (rotate to world coordinates) - accel_world = rotate_vector(quaternion, linear_local); - - // convert to mm/s² - for (u32 i = 0; i < 3; i++) - { - accel_world[i] *= 1000.0f; - } - - // Linear velocity (integrate acceleration) - for (u32 i = 0; i < 3; i++) - { - vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time; - } - - prev_vel_world = vel_world; - } - - // Compute raw angular acceleration - for (u32 i = 0; i < 3; i++) - { - const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time; - - // Filtering - constexpr f32 weight = 0.8f; - constexpr f32 weight_inv = 1.0f - weight; - angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha; - } - - // Angular velocity (rotate to world coordinates) - angvel_world = rotate_vector(quaternion, gyro); - - // Angular acceleration (rotate to world coordinates) - angaccel_world = rotate_vector(quaternion, angular_acceleration); - - prev_gyro = gyro; -} - -void ps_move_data::update_velocity(u64 timestamp, be_t pos_world[4]) -{ - if constexpr (use_imu_for_velocity) - return; - - if (last_velocity_update_time_us == timestamp) - return; - - // Get elapsed time since last update - const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f); - last_velocity_update_time_us = timestamp; - - if (!delta_time) - return; - - for (u32 i = 0; i < 3; i++) - { - // Linear velocity - constexpr f32 weight = 0.8f; - constexpr f32 weight_inv = 1.0f - weight; - vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i]; - prev_pos_world[i] = pos_world[i]; - - // Linear acceleration - accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time; - } - - prev_vel_world = vel_world; -} - bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id) { if (m_pressure_intensity_button_index < 0) diff --git a/rpcs3/Emu/Io/pad_types.h b/rpcs3/Emu/Io/pad_types.h index f8f86dfd54..5fd9c8973a 100644 --- a/rpcs3/Emu/Io/pad_types.h +++ b/rpcs3/Emu/Io/pad_types.h @@ -2,7 +2,8 @@ #include "util/types.hpp" #include "util/endian.hpp" -#include "Emu/Io/pad_config_types.h" +#include "pad_config_types.h" +#include "ps_move_data.h" #include #include @@ -469,78 +470,6 @@ struct VibrateMotor {} }; -struct ps_move_data -{ - template - struct vect - { - public: - vect() = default; - constexpr vect(const std::array& vec) : data(vec) {}; - - template - T& operator[](I i) { return data[i]; } - - template - const T& operator[](I i) const { return data[i]; } - - T x() const requires (Size >= 1) { return data[0]; } - T y() const requires (Size >= 2) { return data[1]; } - T z() const requires (Size >= 3) { return data[2]; } - T w() const requires (Size >= 4) { return data[3]; } - - T& x() requires (Size >= 1) { return data[0]; } - T& y() requires (Size >= 2) { return data[1]; } - T& z() requires (Size >= 3) { return data[2]; } - T& w() requires (Size >= 4) { return data[3]; } - - private: - std::array data {}; - }; - - u32 external_device_id = 0; - std::array external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE - std::array external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE - std::array external_device_data{}; - bool external_device_connected = false; - bool external_device_read_requested = false; - bool external_device_write_requested = false; - - bool calibration_requested = false; - bool calibration_succeeded = false; - - bool magnetometer_enabled = false; - bool orientation_enabled = false; - - // Disable IMU tracking of velocity and acceleration (massive drift) - static constexpr bool use_imu_for_velocity = false; - u64 last_velocity_update_time_us = 0; - - static constexpr vect<4> default_quaternion = vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f }); - vect<4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) - - // Raw values (local) - vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit) - vect<3> gyro {}; // angular velocity in rad/s - vect<3> prev_gyro {}; // previous angular velocity in rad/s - vect<3> angular_acceleration {}; // angular acceleration in rad/s² - vect<3> magnetometer {}; - - // In world coordinates - vect<3> prev_pos_world {}; - vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s) - vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s) - vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²) - vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s) - vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²) - - s16 temperature = 0; - - void reset_sensors(); - void update_orientation(f32 delta_time); - void update_velocity(u64 timestamp, be_t pos_world[4]); -}; - struct Pad { const pad_handler m_pad_handler; diff --git a/rpcs3/Emu/Io/ps_move_data.cpp b/rpcs3/Emu/Io/ps_move_data.cpp new file mode 100644 index 0000000000..0a167eed39 --- /dev/null +++ b/rpcs3/Emu/Io/ps_move_data.cpp @@ -0,0 +1,137 @@ +#include "stdafx.h" +#include "ps_move_data.h" + +const ps_move_data::vect<4> ps_move_data::default_quaternion = ps_move_data::vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f }); + +ps_move_data::ps_move_data() + : quaternion(default_quaternion) +{ +} + +void ps_move_data::reset_sensors() +{ + quaternion = default_quaternion; + accelerometer = {}; + gyro = {}; + prev_gyro = {}; + angular_acceleration = {}; + magnetometer = {}; + //prev_pos_world = {}; // probably no reset needed ? + vel_world = {}; + prev_vel_world = {}; + accel_world = {}; + angvel_world = {}; + angaccel_world = {}; +} + +void ps_move_data::update_orientation(f32 delta_time) +{ + if (!delta_time) + return; + + // Rotate vector v by quaternion q + const auto rotate_vector = [](const vect<4>& q, const vect<3>& v) + { + const vect<4> qv({0.0f, v.x(), v.y(), v.z()}); + const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()}); + + // t = q * v + vect<4> t; + t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z(); + t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y(); + t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x(); + t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x(); + + // r = t * q_inv + vect<4> r; + r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z(); + r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y(); + r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x(); + r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x(); + + return vect<3>({r.x(), r.y(), r.z()}); + }; + + if constexpr (use_imu_for_velocity) + { + // Gravity in world frame + constexpr f32 gravity = 9.81f; + constexpr vect<3> g({0.0f, 0.0f, -gravity}); + + // Rotate gravity into sensor frame + const vect<3> g_sensor = rotate_vector(quaternion, g); + + // Remove gravity + vect<3> linear_local; + for (u32 i = 0; i < 3; i++) + { + linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i]; + } + + // Linear acceleration (rotate to world coordinates) + accel_world = rotate_vector(quaternion, linear_local); + + // convert to mm/s² + for (u32 i = 0; i < 3; i++) + { + accel_world[i] *= 1000.0f; + } + + // Linear velocity (integrate acceleration) + for (u32 i = 0; i < 3; i++) + { + vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time; + } + + prev_vel_world = vel_world; + } + + // Compute raw angular acceleration + for (u32 i = 0; i < 3; i++) + { + const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time; + + // Filtering + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha; + } + + // Angular velocity (rotate to world coordinates) + angvel_world = rotate_vector(quaternion, gyro); + + // Angular acceleration (rotate to world coordinates) + angaccel_world = rotate_vector(quaternion, angular_acceleration); + + prev_gyro = gyro; +} + +void ps_move_data::update_velocity(u64 timestamp, be_t pos_world[4]) +{ + if constexpr (use_imu_for_velocity) + return; + + if (last_velocity_update_time_us == timestamp) + return; + + // Get elapsed time since last update + const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f); + last_velocity_update_time_us = timestamp; + + if (!delta_time) + return; + + for (u32 i = 0; i < 3; i++) + { + // Linear velocity + constexpr f32 weight = 0.8f; + constexpr f32 weight_inv = 1.0f - weight; + vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i]; + prev_pos_world[i] = pos_world[i]; + + // Linear acceleration + accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time; + } + + prev_vel_world = vel_world; +} diff --git a/rpcs3/Emu/Io/ps_move_data.h b/rpcs3/Emu/Io/ps_move_data.h new file mode 100644 index 0000000000..1ae30f5c66 --- /dev/null +++ b/rpcs3/Emu/Io/ps_move_data.h @@ -0,0 +1,75 @@ +#pragma once + +struct ps_move_data +{ + template + struct vect + { + public: + constexpr vect() = default; + constexpr vect(const std::array& vec) : data(vec) {}; + + template + T& operator[](I i) { return data[i]; } + + template + const T& operator[](I i) const { return data[i]; } + + T x() const requires (Size >= 1) { return data[0]; } + T y() const requires (Size >= 2) { return data[1]; } + T z() const requires (Size >= 3) { return data[2]; } + T w() const requires (Size >= 4) { return data[3]; } + + T& x() requires (Size >= 1) { return data[0]; } + T& y() requires (Size >= 2) { return data[1]; } + T& z() requires (Size >= 3) { return data[2]; } + T& w() requires (Size >= 4) { return data[3]; } + + private: + std::array data {}; + }; + + ps_move_data(); + + u32 external_device_id = 0; + std::array external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE + std::array external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE + std::array external_device_data{}; + bool external_device_connected = false; + bool external_device_read_requested = false; + bool external_device_write_requested = false; + + bool calibration_requested = false; + bool calibration_succeeded = false; + + bool magnetometer_enabled = false; + bool orientation_enabled = false; + + // Disable IMU tracking of velocity and acceleration (massive drift) + static constexpr bool use_imu_for_velocity = false; + u64 last_velocity_update_time_us = 0; + + static const vect<4> default_quaternion; + vect<4> quaternion {}; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up) + + // Raw values (local) + vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit) + vect<3> gyro {}; // angular velocity in rad/s + vect<3> prev_gyro {}; // previous angular velocity in rad/s + vect<3> angular_acceleration {}; // angular acceleration in rad/s² + vect<3> magnetometer {}; + + // In world coordinates + vect<3> prev_pos_world {}; + vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s) + vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s) + vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²) + vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s) + vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²) + + s16 temperature = 0; + + void reset_sensors(); + void update_orientation(f32 delta_time); + void update_velocity(u64 timestamp, be_t pos_world[4]); +}; diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 77deb6088e..437d79df8a 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -91,6 +91,7 @@ + @@ -604,6 +605,7 @@ + diff --git a/rpcs3/emucore.vcxproj.filters b/rpcs3/emucore.vcxproj.filters index 6b15f662e5..17ccca3792 100644 --- a/rpcs3/emucore.vcxproj.filters +++ b/rpcs3/emucore.vcxproj.filters @@ -1393,6 +1393,9 @@ Emu\GPU\RSX\Program\Assembler + + Emu\Io + @@ -2800,6 +2803,9 @@ Emu\GPU\RSX\Program\Assembler + + Emu\Io + From fa4e2d1b42c68b6b36d46ff8e3e6e5a183780dcc Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 17:41:32 +0100 Subject: [PATCH 51/94] ps_move_handler: fix trigger values depending on ps move version --- rpcs3/Input/ps_move_handler.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index f37fc8c68f..4c5f34a309 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -608,7 +608,17 @@ std::unordered_map ps_move_handler::get_button_values(const std::share const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3; key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0; key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0; - key_buf[ps_move_key_codes::t] = (extra_buttons & button_flags::t) ? input.trigger_2 : 0; + + u16 trigger = 0; + if (extra_buttons & button_flags::t) + { + switch (dev->model) + { + case ps_move_model::ZCM1: trigger = (input.trigger_1 + input.trigger_2) / 2; break; + case ps_move_model::ZCM2: trigger = input.trigger_1; break; + } + } + key_buf[ps_move_key_codes::t] = trigger; dev->battery_level = input.battery_level; From 24745416c5e550cd0269afb3a3f1eea47575c6d5 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 17:44:47 +0100 Subject: [PATCH 52/94] cellGem: fix compilation --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index fccf164b94..f11e3d89f8 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -276,7 +276,7 @@ public: u64 start_timestamp_us = 0; - std::array mouse_move_data {}; // No need to be in savestate + std::array fake_move_data {}; // No need to be in savestate atomic_t m_wake_up = 0; atomic_t m_done = 0; @@ -2168,7 +2168,7 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con if constexpr (std::is_same_v>) { - ps_move_data& move_data = ::at32(g_fxo->get().mouse_move_data, mouse_no); + ps_move_data& move_data = ::at32(g_fxo->get().fake_move_data, mouse_no); pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data); } else if constexpr (std::is_same_v>) @@ -2237,7 +2237,8 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control if constexpr (std::is_same_v>) { - pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, {}); + ps_move_data& move_data = ::at32(g_fxo->get().fake_move_data, gem_no); + pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data); } else if constexpr (std::is_same_v>) { From ee9dc44059dd47cfce8ec3382d05310ec30cbc0b Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 19:34:14 +0100 Subject: [PATCH 53/94] ps_move_handler: fix decoding of ZCM1 sensor values --- rpcs3/Input/ps_move_handler.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index 4c5f34a309..cbb4613b6f 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -685,12 +685,19 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) if (dev->model == ps_move_model::ZCM1) { - 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); + const auto decode_16bit = [](s16 val) + { + const u8* data = reinterpret_cast(&val); + const u8 low = data[0] & 0xFF; + const u8 high = data[1] & 0xFF; + return (low | (high << 8)) - zero_shift; + }; + accel_x = decode_16bit(input.accel_x_1); + accel_y = decode_16bit(input.accel_y_1); + accel_z = decode_16bit(input.accel_z_1); + gyro_x = decode_16bit(input.gyro_x_1); + gyro_y = decode_16bit(input.gyro_y_1); + gyro_z = decode_16bit(input.gyro_z_1); } if (!device->config || !device->config->orientation_enabled) From 26b0f822d8e0b681cfbec723e379a7706ceba3a0 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 11 Dec 2025 21:42:21 +0100 Subject: [PATCH 54/94] ps_move_handler: enable orientation by default --- rpcs3/Input/ps_move_handler.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index cbb4613b6f..2b27202d47 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -176,6 +176,9 @@ void ps_move_handler::init_config(cfg_pad* cfg) cfg->ltriggerthreshold.def = 0; // between 0 and 255 cfg->rtriggerthreshold.def = 0; // between 0 and 255 + // We have to enable orientation by default + cfg->orientation_enabled.def = true; + // apply defaults cfg->from_default(); } From c38146636a5547b9a17731c84fca4c62b26beb81 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 12 Dec 2025 08:01:19 +0100 Subject: [PATCH 55/94] Update fusion to 1.2.11 --- 3rdparty/fusion/fusion | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdparty/fusion/fusion b/3rdparty/fusion/fusion index 759ac5d698..008e03eac0 160000 --- a/3rdparty/fusion/fusion +++ b/3rdparty/fusion/fusion @@ -1 +1 @@ -Subproject commit 759ac5d698baefca53f1975a0bb1d2dcbdb9f836 +Subproject commit 008e03eac0ac1d5f85e16f5fcaefdda3fee75cb8 From 3c0558c8225087cd6a4e14a18641e87b3102b798 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 12 Dec 2025 09:30:28 +0100 Subject: [PATCH 56/94] cellGem: Implement CELL_CAMERA_RAW8 to CELL_GEM_BAYER_RESTORED_RGGB --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 45 +++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f11e3d89f8..f4cfb1db8d 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -1150,6 +1150,43 @@ namespace gem break; } case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B + { + if (input_format == CELL_CAMERA_RAW8) + { + const u32 dst_w = std::min(320u, width / 2); + const u32 dst_h = std::min(240u, height / 2); + const u32 in_pitch = width; + constexpr u32 out_pitch = 320 * 4; + + for (u32 y = 0; y < dst_h; y++) + { + const u8* src0 = &video_data_in[y * 2 * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst = video_data_out + y * out_pitch; + + for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2, dst += 4) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + dst[0] = r; + dst[1] = g0; + dst[2] = g1; + dst[3] = b; + } + } + } + else + { + 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_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters { cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); @@ -1705,10 +1742,10 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con // Calculate orientation 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 - gem_state->quat[2] = move_data.quaternion[2]; // z - gem_state->quat[3] = move_data.quaternion[3]; // w + gem_state->quat[0] = move_data.quaternion.x(); + gem_state->quat[1] = move_data.quaternion.y(); + gem_state->quat[2] = move_data.quaternion.z(); + gem_state->quat[3] = move_data.quaternion.w(); } else { From f739ce732326bbdff1004fcd8875a5dd1607dd63 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 12 Dec 2025 09:30:50 +0100 Subject: [PATCH 57/94] cellGem: Implement CELL_CAMERA_RAW8 to CELL_GEM_BAYER_RESTORED_RASTERIZED --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 47 ++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index f4cfb1db8d..88edf12516 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -1189,9 +1189,50 @@ namespace gem } 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 (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; + if (input_format == CELL_CAMERA_RAW8) + { + const u32 dst_w = std::min(320u, width / 2); + const u32 dst_h = std::min(240u, height / 2); + const u32 in_pitch = width; + constexpr u32 out_plane = 320 * 240; + constexpr u32 out_pitch = 320; + + u8* dst_plane_r = video_data_out; + u8* dst_plane_g1 = video_data_out + out_plane; + u8* dst_plane_g2 = video_data_out + out_plane * 2; + u8* dst_plane_b = video_data_out + out_plane * 3; + + for (u32 y = 0; y < dst_h; y++) + { + const u8* src0 = &video_data_in[y * 2 * in_pitch]; + const u8* src1 = src0 + in_pitch; + + u8* dst_r = dst_plane_r + y * out_pitch; + u8* dst_g1 = dst_plane_g1 + y * out_pitch; + u8* dst_g2 = dst_plane_g2 + y * out_pitch; + u8* dst_b = dst_plane_b + y * out_pitch; + + for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2) + { + const u8 b = src0[0]; + const u8 g0 = src0[1]; + const u8 g1 = src1[0]; + const u8 r = src1[1]; + + dst_r[x] = r; + dst_g1[x] = g0; + dst_g2[x] = g1; + dst_b[x] = b; + } + } + } + else + { + 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_NO_VIDEO_OUTPUT: // Disable video output { From 236f78346695ce8527b707acff6fab6cfeec9082 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 12 Dec 2025 19:51:37 +0100 Subject: [PATCH 58/94] overlays fix c++23 elifndef This is a c++23 feature... --- rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp index 78229d3845..d6fc7eabb9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp @@ -126,7 +126,7 @@ namespace rsx result.font_names.emplace_back("Roboto-Regular.ttf"); result.font_names.emplace_back("OpenSans-Regular.ttf"); result.font_names.emplace_back("FreeSans.ttf"); -#elifndef _WIN32 +#elif !defined(_WIN32) result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch #endif From 0c455d12c9d8c57569ff816f65aa6ba1cc14e16c Mon Sep 17 00:00:00 2001 From: Megamouse Date: Fri, 12 Dec 2025 19:54:03 +0100 Subject: [PATCH 59/94] Fix int -> float conversion warning --- rpcs3/Input/ps_move_handler.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Input/ps_move_handler.cpp b/rpcs3/Input/ps_move_handler.cpp index 2b27202d47..059ffee2bf 100644 --- a/rpcs3/Input/ps_move_handler.cpp +++ b/rpcs3/Input/ps_move_handler.cpp @@ -693,7 +693,8 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding) const u8* data = reinterpret_cast(&val); const u8 low = data[0] & 0xFF; const u8 high = data[1] & 0xFF; - return (low | (high << 8)) - zero_shift; + const s32 res = (low | (high << 8)) - zero_shift; + return static_cast(res); }; accel_x = decode_16bit(input.accel_x_1); accel_y = decode_16bit(input.accel_y_1); From 2b0456520e659adc6a1604f56a216d5c632e1dd3 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Sat, 13 Dec 2025 02:34:49 +0300 Subject: [PATCH 60/94] rsx/cfg: Fix edge case where an empty block is defined --- rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp | 37 +++++++++++++++++---- rpcs3/tests/test_rsx_cfg.cpp | 18 ++++++++++ 2 files changed, 48 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp index 1f1e5b3678..99e7c56f6e 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPToCFG.cpp @@ -194,22 +194,45 @@ namespace rsx::assembler case RSX_FP_OPCODE_IFE: { // Inserts if and else and end blocks - auto parent = bb; - bb = safe_insert_block(parent, pc + 1, EdgeType::IF); - if (src2.end_offset != src1.else_offset) + const u32 end_addr = src2.end_offset >> 2u; + const u32 else_addr = src1.else_offset >> 2u; + if (end_addr == pc + 1u) { - else_blocks.push_back(safe_insert_block(parent, src1.else_offset >> 2, EdgeType::ELSE)); + // NOP. Empty IF block + bb->instructions.pop_back(); + break; } - end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDIF)); + + if (else_addr > end_addr) + { + // Our systems support this, but it is not verified on real hardware. + rsx_log.error("CFG: Non-contiguous branch detected. Report to developers."); + } + + auto parent = bb; + bb = safe_insert_block(parent, pc + 1u, EdgeType::IF); + if (end_addr != else_addr) + { + else_blocks.push_back(safe_insert_block(parent, else_addr, EdgeType::ELSE)); + } + end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDIF)); break; } case RSX_FP_OPCODE_LOOP: case RSX_FP_OPCODE_REP: { // Inserts for and end blocks + const u32 end_addr = src2.end_offset >> 2u; + if (end_addr == pc + 1u) + { + // NOP. Empty LOOP block + bb->instructions.pop_back(); + break; + } + auto parent = bb; - bb = safe_insert_block(parent, pc + 1, EdgeType::LOOP); - end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDLOOP)); + bb = safe_insert_block(parent, pc + 1u, EdgeType::LOOP); + end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDLOOP)); break; } default: diff --git a/rpcs3/tests/test_rsx_cfg.cpp b/rpcs3/tests/test_rsx_cfg.cpp index 5e22311ac3..ded749fd24 100644 --- a/rpcs3/tests/test_rsx_cfg.cpp +++ b/rpcs3/tests/test_rsx_cfg.cpp @@ -210,4 +210,22 @@ namespace rsx::assembler EXPECT_EQ(bb6->pred[1].from, bb3); EXPECT_EQ(bb6->pred[2].from, bb0); } + + TEST(CFG, FpToCFG_EmptyIF) + { + auto ir = FPIR::from_source( + "IF.LT;" // Empty branch + "ENDIF;" + "MOV R0, R1;" // False merge block. + ); + + RSXFragmentProgram program{}; + auto bytecode = ir.compile(); + program.data = bytecode.data(); + + FlowGraph graph = deconstruct_fragment_program(program); + + ASSERT_EQ(graph.blocks.size(), 1); + EXPECT_EQ(graph.blocks.front().instructions.size(), 1); + } } From c3db85c68e6c1724b239d6b538dd436c404613f6 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sat, 13 Dec 2025 03:55:11 +0100 Subject: [PATCH 61/94] cellGem: Fix YUV conversions, implement gain, averaging and basic outlier detection --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 499 +++++++++++++++-------------- rpcs3/Input/ps_move_tracker.cpp | 15 +- rpcs3/Input/ps_move_tracker.h | 2 + 3 files changed, 271 insertions(+), 245 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index 88edf12516..df46a92a05 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -698,22 +698,88 @@ namespace gem { } - 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); } + YUV(const u8 rgb[3]) + { + const u8 r = rgb[0]; + const u8 g = rgb[1]; + const u8 b = rgb[2]; + 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(std::clamp(0.299f * r + 0.587f * g + 0.114f * b, 0.0f, 255.0f)); } + static inline u8 U(u8 r, u8 g, u8 b) { return static_cast(std::clamp(-0.169f * r - 0.331f * g + 0.499f * b + 128, 0.0f, 255.0f)); } + static inline u8 V(u8 r, u8 g, u8 b) { return static_cast(std::clamp(0.499f * r - 0.460f * g - 0.040f * b + 128, 0.0f, 255.0f)); } }; - 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, std::string_view caller) + template + static inline void debayer_raw8_impl(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) { - if (output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) + constexpr u32 in_pitch = 640; + constexpr u32 out_pitch = 640 * 4; + + for (u32 y = 0; y < 480 - 1; y += 2) + { + const u8* src0 = src + y * in_pitch; + const u8* src1 = src0 + in_pitch; + + u8* dst0 = dst + y * out_pitch; + u8* dst1 = dst0 + out_pitch; + + for (u32 x = 0; x < 640 - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) + { + u8 b = src0[0]; + u8 g0 = src0[1]; + u8 g1 = src1[0]; + u8 r = src1[1]; + + if constexpr (use_gain) + { + b = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + g0 = static_cast(std::clamp(g0 * gain_g, 0.0f, 255.0f)); + g1 = static_cast(std::clamp(g1 * gain_g, 0.0f, 255.0f)); + r = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + } + + const u8 top[4] = { r, g0, b, alpha }; + const u8 bottom[4] = { r, g1, b, alpha }; + + // Top-Left + std::memcpy(dst0, top, 4); + + // Top-Right Pixel + std::memcpy(dst0 + 4, top, 4); + + // Bottom-Left Pixel + std::memcpy(dst1, bottom, 4); + + // Bottom-Right Pixel + std::memcpy(dst1 + 4, bottom, 4); + } + } + } + + static void debayer_raw8(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b) + { + if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f) + debayer_raw8_impl(src, dst, alpha, gain_r, gain_g, gain_b); + else + debayer_raw8_impl(src, dst, alpha, gain_r, gain_g, gain_b); + } + + bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc, + const std::vector& video_data_in, u32 width, u32 height, + u8* video_data_out, u32 video_data_out_size, u8* buffer_memory, + std::string_view caller) + { + if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out) { return false; } const u32 required_in_size = get_buffer_size_by_format(static_cast(input_format), width, height); - const s32 required_out_size = cellGemGetVideoConvertSize(output_format); + const s32 required_out_size = cellGemGetVideoConvertSize(vc.output_format); if (video_data_in.size() != required_in_size) { @@ -723,7 +789,7 @@ namespace gem if (required_out_size < 0 || video_data_out_size != static_cast(required_out_size)) { - 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); + cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, vc.output_format, caller); return false; } @@ -732,7 +798,121 @@ namespace gem return false; } - switch (output_format) + thread_local std::vector corrected_buffer; + thread_local std::vector combined_buffer; + thread_local std::vector conversion_buffer; + + const u8* src_data = video_data_in.data(); + const u8 alpha = vc.alpha; + const f32 gain_r = vc.gain * vc.blue_gain; + const f32 gain_g = vc.gain * vc.green_gain; + const f32 gain_b = vc.gain * vc.red_gain; + + // Only RAW8 should be relevant for cellGem unless I'm mistaken + if (input_format == CELL_CAMERA_RAW8) + { + // TODO: CELL_GEM_AUTO_WHITE_BALANCE + // TODO: CELL_GEM_GAMMA_BOOST + + // Correct outliers + if (vc.conversion_flags & CELL_GEM_FILTER_OUTLIER_PIXELS) + { + corrected_buffer.resize(width * height); + + for (u32 y = 0; y < height; y++) + { + const u8* src = src_data + y * 640; + u8* dst = &corrected_buffer[y * 640]; + + for (u32 x = 0; x < width; x++, src++) + { + // Let's just say these 2 are outliers + if (const u8 val = *src; val > 0 && val < 255) + { + *dst++ = val; + continue; + } + + // Just take the 4 neighbours for now + s32 sum = 0; + if (y >= 2) sum += *(src - (2 * 640)); + if (x >= 2) sum += *(src - 2); + if (x < 638) sum += *(src + 2); + if (y < 478) sum += *(src + (2 * 640)); + + *dst++ = sum / 4; // Ignore count. It will only be less than 4 on the edges + } + } + + src_data = corrected_buffer.data(); + } + + // Combine with previous frame + if (buffer_memory && (vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME)) + { + combined_buffer.resize(width * height); + + for (u32 i = 0; i < combined_buffer.size(); i++) + { + const u8 val = src_data[i]; + u8& old = buffer_memory[i]; + combined_buffer[i] = (old + val) / 2; + old = val; + } + + src_data = combined_buffer.data(); + } + + switch (vc.output_format) + { + case CELL_GEM_YUV_640x480: + case CELL_GEM_YUV422_640x480: + case CELL_GEM_YUV411_640x480: + { + // Let's debayer the image first for YUV formats + conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480)); + + debayer_raw8(src_data, conversion_buffer.data(), alpha, gain_r, gain_g, gain_b); + + src_data = conversion_buffer.data(); + input_format = CELL_CAMERA_RGBA; + width = 640; + height = 480; + break; + } + case CELL_GEM_BAYER_RESTORED: + case CELL_GEM_BAYER_RESTORED_RGGB: + case CELL_GEM_BAYER_RESTORED_RASTERIZED: + { + // Let's apply gain + if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f) + { + conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480)); + + const f32 bggr_gains[2][2] = {{gain_b, gain_g}, {gain_g, gain_r}}; + const u8* src = src_data; + u8* dst = conversion_buffer.data(); + + for (u32 y = 0; y < 480; y++) + { + const f32* gains = bggr_gains[y % 2]; + + for (u32 x = 0; x < 640; x++) + { + *dst++ = static_cast(std::clamp(*src++ * gains[x % 2], 0.0f, 255.0f)); + } + } + + src_data = conversion_buffer.data(); + } + break; + } + default: + break; + } + } + + switch (vc.output_format) { case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required { @@ -740,51 +920,18 @@ namespace gem { case CELL_CAMERA_RAW8: { - const u32 in_pitch = width; - const u32 out_pitch = width * 4; - - 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 * out_pitch; - u8* dst1 = dst0 + out_pitch; - - for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) - { - 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); - - // Top-Right Pixel - std::memcpy(dst0 + 4, top, 4); - - // Bottom-Left Pixel - std::memcpy(dst1, bottom, 4); - - // Bottom-Right Pixel - std::memcpy(dst1 + 4, bottom, 4); - } - } + debayer_raw8(src_data, video_data_out, alpha, gain_r, gain_g, gain_b); break; } case CELL_CAMERA_RGBA: { - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -794,17 +941,18 @@ namespace gem { if (input_format == CELL_CAMERA_RAW8) { - std::memcpy(video_data_out, video_data_in.data(), std::min(required_in_size, required_out_size)); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); } else { - cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); return false; } break; } case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous) { + // YUV 4:4:4 planar. 1 value each per pixel const u32 yuv_pitch = width; u8* dst_y = video_data_out; @@ -815,61 +963,21 @@ namespace gem { 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; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: { - const u32 in_pitch = width / 4; + const u32 in_pitch = width * 4; for (u32 y = 0; y < height; y++) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + 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); + const YUV yuv = YUV(src); *dst_y++ = yuv.y; *dst_u++ = yuv.u; @@ -880,8 +988,8 @@ namespace gem } 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -889,6 +997,7 @@ namespace gem } case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous) { + // YUV 4:2:2 planar. 1 Y value per pixel, 1 U/V value per 2 horizontal pixels const u32 y_pitch = width; const u32 uv_pitch = width / 2; @@ -900,43 +1009,7 @@ namespace gem { 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; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: @@ -945,33 +1018,28 @@ namespace gem for (u32 y = 0; y < height; y++) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + 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); + const YUV yuv_0 = YUV(src); + const YUV yuv_1 = YUV(src + 4); dst_y[0] = yuv_0.y; - dst_y[1] = y_1; - *dst_u++ = yuv_0.u; - *dst_v++ = yuv_0.v; + dst_y[1] = yuv_1.y; + + // Average U/V from 2 horizontal pixels + *dst_u++ = (yuv_0.u + yuv_1.u) / 2; + *dst_v++ = (yuv_0.v + yuv_1.v) / 2; } } 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -979,109 +1047,54 @@ namespace gem } 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; - + // YUV 4:1:1 planar. 1 Y value per pixel, 1 U/V value per 2x2 pixel block u8* dst_y = video_data_out; - u8* dst_u = dst_y + y_pitch * height; - u8* dst_v = dst_u + uv_pitch * height; + u8* dst_u = dst_y + 640 * 480; + u8* dst_v = dst_u + 320 * 240; 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; - } - } + fmt::throw_exception("Unreachable: should already be debayered"); break; } case CELL_CAMERA_RGBA: { const u32 in_pitch = width * 4; - for (u32 y = 0; y < height; y++) + // 2 rows at a time to get a 2x2 pixel block + for (u32 y = 0; y < height - 1; y += 2) { - const u8* src = &video_data_in[y * in_pitch]; + const u8* src = src_data + y * in_pitch; + const u8* src2 = src + in_pitch; + u8* dst_y1 = dst_y + y * 640; + u8* dst_y2 = dst_y1 + 640; - for (u32 x = 0; x < width - 3; x += 4, src += 16, dst_y += 4) + for (u32 x = 0; x < width - 1; x += 2, src += 8, src2 += 8, dst_y1 += 2, dst_y2 += 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]; - 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); + const YUV yuv_0 = YUV(src); + const YUV yuv_1 = YUV(src + 4); + const YUV yuv_2 = YUV(src2); + const YUV yuv_3 = YUV(src2 + 4); - 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; + dst_y1[0] = yuv_0.y; + dst_y1[1] = yuv_1.y; + dst_y2[0] = yuv_2.y; + dst_y2[1] = yuv_3.y; + + // Average U/V from 2x2 pixel block + *dst_u++ = (yuv_0.u + yuv_1.u + yuv_2.u + yuv_3.u) / 4; + *dst_v++ = (yuv_0.v + yuv_1.v + yuv_2.v + yuv_3.v) / 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -1098,7 +1111,7 @@ namespace gem for (u32 y = 0; y < height - 1; y += 2) { - const u8* src0 = &video_data_in[y * in_pitch]; + const u8* src0 = src_data + y * in_pitch; const u8* src1 = src0 + in_pitch; u8* dst0 = video_data_out + (y / 2) * out_pitch; @@ -1111,8 +1124,8 @@ namespace gem 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 }; + const u8 top[4] = { r, g0, b, alpha }; + const u8 bottom[4] = { r, g1, b, alpha }; // Top-Left std::memcpy(dst0, top, 4); @@ -1130,7 +1143,7 @@ namespace gem for (u32 y = 0; y < height / 2; y++) { - const u8* src = &video_data_in[y * 2 * in_pitch]; + const u8* src = src_data + 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) @@ -1142,8 +1155,8 @@ namespace gem } 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } } @@ -1160,7 +1173,7 @@ namespace gem for (u32 y = 0; y < dst_h; y++) { - const u8* src0 = &video_data_in[y * 2 * in_pitch]; + const u8* src0 = src_data + y * 2 * in_pitch; const u8* src1 = src0 + in_pitch; u8* dst = video_data_out + y * out_pitch; @@ -1181,8 +1194,8 @@ namespace gem } else { - 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } break; @@ -1204,7 +1217,7 @@ namespace gem for (u32 y = 0; y < dst_h; y++) { - const u8* src0 = &video_data_in[y * 2 * in_pitch]; + const u8* src0 = src_data + y * 2 * in_pitch; const u8* src1 = src0 + in_pitch; u8* dst_r = dst_plane_r + y * out_pitch; @@ -1228,8 +1241,8 @@ namespace gem } else { - 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)); + cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller); + std::memcpy(video_data_out, src_data, std::min(required_in_size, required_out_size)); return false; } break; @@ -1241,7 +1254,7 @@ namespace gem } default: { - cellGem.error("Trying to convert %s to %s (called from %s)", input_format, output_format, caller); + cellGem.error("Trying to convert %s to %s (called from %s)", input_format, vc.output_format, caller); return false; } } @@ -1420,13 +1433,15 @@ void gem_config_data::operator()() 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, "cellGem")) + if (gem::convert_image_format(shared_data.format, vc, video_data_in, shared_data.width, shared_data.height, + vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size, + vc.buffer_memory ? vc.buffer_memory.get_ptr() : nullptr, "cellGem")) { cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get()); if (g_cfg.io.paint_move_spheres) { - paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size); + paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size); } } diff --git a/rpcs3/Input/ps_move_tracker.cpp b/rpcs3/Input/ps_move_tracker.cpp index d3deead687..d7e21a2193 100644 --- a/rpcs3/Input/ps_move_tracker.cpp +++ b/rpcs3/Input/ps_move_tracker.cpp @@ -13,14 +13,21 @@ LOG_CHANNEL(ps_move); namespace gem { - extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format, + extern bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc, const std::vector& video_data_in, u32 width, u32 height, - u8* video_data_out, u32 video_data_out_size, std::string_view caller); + u8* video_data_out, u32 video_data_out_size, u8* buffer_memory, + std::string_view caller); } template ps_move_tracker::ps_move_tracker() { + m_vc_attr.alpha = 255; + m_vc_attr.gain = 1.0f; + m_vc_attr.red_gain = 1.0f; + m_vc_attr.green_gain = 1.0f; + m_vc_attr.blue_gain = 1.0f; + init_workers(); } @@ -238,7 +245,9 @@ 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), "gemTracker")) + m_vc_attr.output_format = CellGemVideoConvertFormatEnum{output_format}; + + if (gem::convert_image_format(CellCameraFormat{m_format}, m_vc_attr, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), nullptr, "gemTracker")) { ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}); } diff --git a/rpcs3/Input/ps_move_tracker.h b/rpcs3/Input/ps_move_tracker.h index a1fa0e8936..29fa8652d5 100644 --- a/rpcs3/Input/ps_move_tracker.h +++ b/rpcs3/Input/ps_move_tracker.h @@ -77,6 +77,8 @@ private: void draw_sphere_size_range(f32 result_radius); + CellGemVideoConvertAttribute m_vc_attr {}; + u32 m_width = 0; u32 m_height = 0; s32 m_format = 0; From 1a3e150a620313d1885838e5dedc635cf1115a86 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 15 Dec 2025 13:34:45 +0300 Subject: [PATCH 62/94] vk: Extend memory allocation system to explicitly allow requesting no VRAM recovery on allocation fail. --- rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp | 24 +++++--- rpcs3/Emu/RSX/VK/vkutils/buffer_object.h | 5 +- rpcs3/Emu/RSX/VK/vkutils/image.cpp | 11 +++- rpcs3/Emu/RSX/VK/vkutils/memory.cpp | 70 ++++++++++++---------- rpcs3/Emu/RSX/VK/vkutils/memory.h | 18 ++++-- 5 files changed, 82 insertions(+), 46 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp index daf60ad03c..47f79ad98a 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.cpp @@ -50,6 +50,7 @@ namespace vk : m_device(dev) { const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3); + const bool no_vmem_recovery = !!(flags & VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3; @@ -69,18 +70,27 @@ namespace vk fmt::throw_exception("No compatible memory type was found!"); } - memory = std::make_unique(m_device, memory_reqs.size, memory_reqs.alignment, allocation_type_info, allocation_pool, nullable); + memory_allocation_request request + { + .size = memory_reqs.size, + .alignment = memory_reqs.alignment, + .memory_type = &allocation_type_info, + .pool = allocation_pool, + .throw_on_fail = !nullable, + .recover_vmem_on_fail = !no_vmem_recovery + }; + memory = std::make_unique(m_device, request); + if (auto device_memory = memory->get_vk_device_memory(); device_memory != VK_NULL_HANDLE) { vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset()); + return; } - else - { - ensure(nullable); - vkDestroyBuffer(m_device, value, nullptr); - value = VK_NULL_HANDLE; - } + + ensure(nullable); + vkDestroyBuffer(m_device, value, nullptr); + value = VK_NULL_HANDLE; } buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size) diff --git a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h index ba5309749a..136b6938bf 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h +++ b/rpcs3/Emu/RSX/VK/vkutils/buffer_object.h @@ -9,9 +9,10 @@ namespace vk { enum : u32 { - VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x80000000, + VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x10000000, // If we cannot allocate memory for the buffer, just return an empty but valid object with a null handle. + VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3 = 0x20000000, // If we cannot allocate memory for the buffer, do not run recovery routine to recover VRAM. Crash or return empty handle immediately instead. - VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3) + VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3) }; struct buffer_view : public unique_resource diff --git a/rpcs3/Emu/RSX/VK/vkutils/image.cpp b/rpcs3/Emu/RSX/VK/vkutils/image.cpp index 31499b0ce1..02d9bf34bd 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/image.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/image.cpp @@ -128,7 +128,16 @@ namespace vk fmt::throw_exception("No compatible memory type was found!"); } - memory = std::make_shared(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool, nullable); + memory_allocation_request alloc_request + { + .size = memory_req.size, + .alignment = memory_req.alignment, + .memory_type = &allocation_type_info, + .pool = allocation_pool, + .throw_on_fail = !nullable + }; + memory = std::make_shared(m_device, alloc_request); + if (auto device_mem = memory->get_vk_device_memory(); device_mem != VK_NULL_HANDLE) [[likely]] { diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp index 2b84e1f61c..fcb1d448d4 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.cpp @@ -224,7 +224,7 @@ namespace vk vmaDestroyAllocator(m_allocator); } - mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) + mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(const memory_allocation_request& request) { VmaAllocation vma_alloc; VkMemoryRequirements mem_req = {}; @@ -233,11 +233,11 @@ namespace vk auto do_vma_alloc = [&]() -> std::tuple { - for (const auto& memory_type_index : memory_type) + for (const auto& memory_type_index : *request.memory_type) { mem_req.memoryTypeBits = 1u << memory_type_index; - mem_req.size = ::align2(block_sz, alignment); - mem_req.alignment = alignment; + mem_req.size = ::align2(request.size, request.alignment); + mem_req.alignment = request.alignment; create_info.memoryTypeBits = 1u << memory_type_index; create_info.flags = m_allocation_flags; @@ -256,26 +256,29 @@ namespace vk const auto [status, type] = do_vma_alloc(); if (status == VK_SUCCESS) { - vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); + vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool); return vma_alloc; } } - const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; - if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(severity)) + if (request.recover_vmem_on_fail) { - // Out of memory. Try again. - const auto [status, type] = do_vma_alloc(); - if (status == VK_SUCCESS) + const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; + if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && + vmm_handle_memory_pressure(severity)) { - rsx_log.warning("Renderer ran out of video memory but successfully recovered."); - vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool); - return vma_alloc; + // Out of memory. Try again. + const auto [status, type] = do_vma_alloc(); + if (status == VK_SUCCESS) + { + rsx_log.warning("Renderer ran out of video memory but successfully recovered."); + vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool); + return vma_alloc; + } } } - if (!throw_on_fail) + if (!request.throw_on_fail) { return VK_NULL_HANDLE; } @@ -361,18 +364,18 @@ namespace vk m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT; } - mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) + mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(const memory_allocation_request& request) { VkResult error_code = VK_ERROR_UNKNOWN; VkDeviceMemory memory; VkMemoryAllocateInfo info = {}; info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - info.allocationSize = block_sz; + info.allocationSize = request.size; auto do_vk_alloc = [&]() -> std::tuple { - for (const auto& memory_type_index : memory_type) + for (const auto& memory_type_index : *request.memory_type) { info.memoryTypeIndex = memory_type_index; error_code = vkAllocateMemory(m_device, &info, nullptr, &memory); @@ -389,26 +392,29 @@ namespace vk const auto [status, type] = do_vk_alloc(); if (status == VK_SUCCESS) { - vmm_notify_memory_allocated(memory, type, block_sz, pool); + vmm_notify_memory_allocated(memory, type, request.size, request.pool); return memory; } } - const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; - if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && - vmm_handle_memory_pressure(severity)) + if (request.recover_vmem_on_fail) { - // Out of memory. Try again. - const auto [status, type] = do_vk_alloc(); - if (status == VK_SUCCESS) + const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe; + if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY && + vmm_handle_memory_pressure(severity)) { - rsx_log.warning("Renderer ran out of video memory but successfully recovered."); - vmm_notify_memory_allocated(memory, type, block_sz, pool); - return memory; + // Out of memory. Try again. + const auto [status, type] = do_vk_alloc(); + if (status == VK_SUCCESS) + { + rsx_log.warning("Renderer ran out of video memory but successfully recovered."); + vmm_notify_memory_allocated(memory, type, request.size, request.pool); + return memory; + } } } - if (!throw_on_fail) + if (!request.throw_on_fail) { return VK_NULL_HANDLE; } @@ -455,11 +461,11 @@ namespace vk return g_render_device->get_allocator(); } - memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable) - : m_device(dev), m_size(block_sz) + memory_block::memory_block(VkDevice dev, const memory_allocation_request& alloc_request) + : m_device(dev), m_size(alloc_request.size) { m_mem_allocator = get_current_mem_allocator(); - m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool, !nullable); + m_mem_handle = m_mem_allocator->alloc(alloc_request); } memory_block::~memory_block() diff --git a/rpcs3/Emu/RSX/VK/vkutils/memory.h b/rpcs3/Emu/RSX/VK/vkutils/memory.h index abe0c75582..5c4d05ecdf 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/memory.h +++ b/rpcs3/Emu/RSX/VK/vkutils/memory.h @@ -66,6 +66,16 @@ namespace vk u64 size; }; + struct memory_allocation_request + { + u64 size = 0; + u64 alignment = 1; + const memory_type_info* memory_type = nullptr; + vmm_allocation_pool pool = VMM_ALLOCATION_POOL_UNDEFINED; + bool throw_on_fail = true; + bool recover_vmem_on_fail = true; + }; + class mem_allocator_base { public: @@ -76,7 +86,7 @@ namespace vk virtual void destroy() = 0; - virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) = 0; + virtual mem_handle_t alloc(const memory_allocation_request& request) = 0; virtual void free(mem_handle_t mem_handle) = 0; virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0; virtual void unmap(mem_handle_t mem_handle) = 0; @@ -104,7 +114,7 @@ namespace vk void destroy() override; - mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; + mem_handle_t alloc(const memory_allocation_request& request) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override; @@ -134,7 +144,7 @@ namespace vk void destroy() override {} - mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override; + mem_handle_t alloc(const memory_allocation_request& request) override; void free(mem_handle_t mem_handle) override; void* map(mem_handle_t mem_handle, u64 offset, u64 size) override; @@ -147,7 +157,7 @@ namespace vk struct memory_block { - memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable = false); + memory_block(VkDevice dev, const memory_allocation_request& alloc_request); virtual ~memory_block(); virtual VkDeviceMemory get_vk_device_memory(); From d9da5f26c0458e36284c6f83daea789e22a4c3b8 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Mon, 15 Dec 2025 14:06:51 +0300 Subject: [PATCH 63/94] vk: Ignore memory pressure handling when allocating data heaps into ReBAR memory. --- rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp index 7fa6a46a81..e3207875f8 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/data_heap.cpp @@ -53,7 +53,7 @@ namespace vk VkFlags create_flags = 0; if (m_prefer_writethrough) { - create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; + create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); } heap = std::make_unique(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); @@ -146,7 +146,7 @@ namespace vk VkFlags create_flags = 0; if (m_prefer_writethrough) { - create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3; + create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3); } heap = std::make_unique(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM); From 12a3818fcf7e954ed29685a168c66998f1a55f78 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 15 Dec 2025 07:56:30 +0100 Subject: [PATCH 64/94] Fix logging of gem configs --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index df46a92a05..bed35276cf 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -626,9 +626,9 @@ public: 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()); + cellGem.notice("Real gem config=%s", g_cfg_gem_real.to_string()); + cellGem.notice("Fake gem config=%s", g_cfg_gem_fake.to_string()); + cellGem.notice("Mouse gem config=%s", g_cfg_gem_mouse.to_string()); } }; From cf87f24587754cd3869430d0df6d396985db3165 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Mon, 15 Dec 2025 13:34:06 +0100 Subject: [PATCH 65/94] cellGem: improve bayer demosaicing --- rpcs3/Emu/Cell/Modules/cellGem.cpp | 129 ++++++++++++++++++++----- rpcs3/rpcs3qt/qt_camera_video_sink.cpp | 63 +++++++----- 2 files changed, 142 insertions(+), 50 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/cellGem.cpp b/rpcs3/Emu/Cell/Modules/cellGem.cpp index bed35276cf..2de2d2cd3c 100644 --- a/rpcs3/Emu/Cell/Modules/cellGem.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGem.cpp @@ -719,43 +719,120 @@ namespace gem constexpr u32 in_pitch = 640; constexpr u32 out_pitch = 640 * 4; - for (u32 y = 0; y < 480 - 1; y += 2) + // Hamilton–Adams demosaicing + for (s32 y = 0; y < 480; y++) { - const u8* src0 = src + y * in_pitch; - const u8* src1 = src0 + in_pitch; + const bool is_even_y = (y % 2) == 0; + const u8* srcc = src + y * in_pitch; + const u8* srcu = src + std::max(0, y - 1) * in_pitch; + const u8* srcd = src + std::min(480 - 1, y + 1) * in_pitch; u8* dst0 = dst + y * out_pitch; - u8* dst1 = dst0 + out_pitch; - for (u32 x = 0; x < 640 - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8) + // Split loops (roughly twice the performance by removing one condition) + if (is_even_y) { - u8 b = src0[0]; - u8 g0 = src0[1]; - u8 g1 = src1[0]; - u8 r = src1[1]; - - if constexpr (use_gain) + for (s32 x = 0; x < 640; x++, dst0 += 4) { - b = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); - g0 = static_cast(std::clamp(g0 * gain_g, 0.0f, 255.0f)); - g1 = static_cast(std::clamp(g1 * gain_g, 0.0f, 255.0f)); - r = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + const bool is_even_x = (x % 2) == 0; + const int xl = std::max(0, x - 1); + const int xr = std::min(640 - 1, x + 1); + + u8 r, b, g; + + if (is_even_x) + { + // Blue pixel + const u8 up = srcu[x]; + const u8 down = srcd[x]; + const u8 left = srcc[xl]; + const u8 right = srcc[xr]; + const int dh = std::abs(int(left) - int(right)); + const int dv = std::abs(int(up) - int(down)); + + r = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4; + if (dh < dv) + g = (left + right) / 2; + else if (dv < dh) + g = (up + down) / 2; + else + g = (up + down + left + right) / 4; + b = srcc[x]; + } + else + { + // Green (on blue row) + r = (srcu[x] + srcd[x]) / 2; + g = srcc[x]; + b = (srcc[xl] + srcc[xr]) / 2; + } + + if constexpr (use_gain) + { + dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + } + else + { + dst0[0] = r; + dst0[1] = g; + dst0[2] = b; + } + dst0[3] = alpha; } + } + else + { + for (s32 x = 0; x < 640; x++, dst0 += 4) + { + const bool is_even_x = (x % 2) == 0; + const int xl = std::max(0, x - 1); + const int xr = std::min(640 - 1, x + 1); - const u8 top[4] = { r, g0, b, alpha }; - const u8 bottom[4] = { r, g1, b, alpha }; + u8 r, b, g; - // Top-Left - std::memcpy(dst0, top, 4); + if (is_even_x) + { + // Green (on red row) + r = (srcc[xl] + srcc[xr]) / 2; + g = srcc[x]; + b = (srcu[x] + srcd[x]) / 2; + } + else + { + // Red pixel + const u8 up = srcu[x]; + const u8 down = srcd[x]; + const u8 left = srcc[xl]; + const u8 right = srcc[xr]; + const int dh = std::abs(int(left) - int(right)); + const int dv = std::abs(int(up) - int(down)); - // Top-Right Pixel - std::memcpy(dst0 + 4, top, 4); + r = srcc[x]; + if (dh < dv) + g = (left + right) / 2; + else if (dv < dh) + g = (up + down) / 2; + else + g = (up + down + left + right) / 4; + b = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4; + } - // Bottom-Left Pixel - std::memcpy(dst1, bottom, 4); - - // Bottom-Right Pixel - std::memcpy(dst1 + 4, bottom, 4); + if constexpr (use_gain) + { + dst0[0] = static_cast(std::clamp(r * gain_r, 0.0f, 255.0f)); + dst0[1] = static_cast(std::clamp(b * gain_b, 0.0f, 255.0f)); + dst0[2] = static_cast(std::clamp(g * gain_g, 0.0f, 255.0f)); + } + else + { + dst0[0] = r; + dst0[1] = g; + dst0[2] = b; + } + dst0[3] = alpha; + } } } } diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp index de63d80b5d..4e1c8b3ccf 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp @@ -114,45 +114,60 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) case CELL_CAMERA_RAW8: // The game seems to expect BGGR { // Let's use a very simple algorithm to convert the image to raw BGGR - const auto convert_to_bggr = [&image_buffer, &image, width, height](u32 y_begin, u32 y_end) + const auto convert_to_bggr = [this, &image_buffer, &image, width, height](u32 y_begin, u32 y_end) { u8* dst = &image_buffer.data[image_buffer.width * y_begin]; for (u32 y = y_begin; y < height && y < y_end; y++) { const u8* src = image.constScanLine(y); + const u8* srcu = image.constScanLine(std::max(0, y - 1)); + const u8* srcd = image.constScanLine(std::min(height - 1, y + 1)); const bool is_top_pixel = (y % 2) == 0; + // We apply gaussian blur to get better demosaicing results later when debayering again + const auto blurred = [&](s32 x, s32 c) + { + const s32 i = x * 4 + c; + const s32 il = std::max(0, x - 1) * 4 + c; + const s32 ir = std::min(width - 1, x + 1) * 4 + c; + const s32 sum = + srcu[i] + + src[il] + 4 * src[i] + src[ir] + + srcd[i]; + return static_cast(std::clamp((sum + 4) / 8, 0, 255)); + }; + // Split loops (roughly twice the performance by removing one condition) if (is_top_pixel) { - for (u32 x = 0; x < width; x++, dst++, src += 4) + for (u32 x = 0; x < width; x++, dst++) { const bool is_left_pixel = (x % 2) == 0; if (is_left_pixel) { - *dst = src[2]; // Blue + *dst = blurred(x, 2); // Blue } else { - *dst = src[1]; // Green + *dst = blurred(x, 1); // Green } } } else { - for (u32 x = 0; x < width; x++, dst++, src += 4) + for (u32 x = 0; x < width; x++, dst++) { const bool is_left_pixel = (x % 2) == 0; if (is_left_pixel) { - *dst = src[1]; // Green + *dst = blurred(x, 1); // Green } else { - *dst = src[0]; // Red + *dst = blurred(x, 0); // Red } } } @@ -182,13 +197,13 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) // Simple RGB to Y0_U_Y1_V conversion from stackoverflow. const auto convert_to_yuv422 = [&image_buffer, &image, width, height, format = m_format](u32 y_begin, u32 y_end) { - constexpr int yuv_bytes_per_pixel = 2; - const int yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; + constexpr s32 yuv_bytes_per_pixel = 2; + const s32 yuv_pitch = image_buffer.width * yuv_bytes_per_pixel; - const int y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; - const int u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; - const int y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; - const int v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; + const s32 y0_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 0 : 3; + const s32 u_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 1 : 2; + const s32 y1_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 2 : 1; + const s32 v_offset = (format == CELL_CAMERA_Y0_U_Y1_V) ? 3 : 0; for (u32 y = y_begin; y < height && y < y_end; y++) { @@ -197,19 +212,19 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) for (u32 x = 0; x < width - 1; x += 2, src += 8) { - const float r1 = src[0]; - const float g1 = src[1]; - const float b1 = src[2]; - const float r2 = src[4]; - const float g2 = src[5]; - const float b2 = src[6]; + const f32 r1 = src[0]; + const f32 g1 = src[1]; + const f32 b1 = src[2]; + const f32 r2 = src[4]; + const f32 g2 = src[5]; + const f32 b2 = src[6]; - const int y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; - const int u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; - const int v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; - const int y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; + const s32 y0 = (0.257f * r1) + (0.504f * g1) + (0.098f * b1) + 16.0f; + const s32 u = -(0.148f * r1) - (0.291f * g1) + (0.439f * b1) + 128.0f; + const s32 v = (0.439f * r1) - (0.368f * g1) - (0.071f * b1) + 128.0f; + const s32 y1 = (0.257f * r2) + (0.504f * g2) + (0.098f * b2) + 16.0f; - const int yuv_index = x * yuv_bytes_per_pixel; + const s32 yuv_index = x * yuv_bytes_per_pixel; yuv_row_ptr[yuv_index + y0_offset] = static_cast(std::clamp(y0, 0, 255)); yuv_row_ptr[yuv_index + u_offset] = static_cast(std::clamp( u, 0, 255)); yuv_row_ptr[yuv_index + y1_offset] = static_cast(std::clamp(y1, 0, 255)); From 711ec69e486b00816a83cb7106f3dd45f5d48ef7 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Tue, 16 Dec 2025 00:12:13 +0100 Subject: [PATCH 66/94] Fix RPCN replies ordering --- rpcs3/Emu/NP/np_handler.cpp | 12 ++++++++++++ rpcs3/Emu/NP/np_handler.h | 12 +----------- rpcs3/Emu/NP/rpcn_client.cpp | 2 +- rpcs3/Emu/NP/rpcn_client.h | 6 +++--- 4 files changed, 17 insertions(+), 15 deletions(-) diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 30eac7f470..5bf637747c 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1702,4 +1702,16 @@ namespace np return ctx; } + void np_handler::callback_info::queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const + { + if (cb) + { + sysutil_register_cb([=, ctx_id = this->ctx_id, event_type = this->event_type, cb = this->cb, cb_arg = this->cb_arg](ppu_thread& cb_ppu) -> s32 + { + cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg); + return 0; + }); + } + } + } // namespace np diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index c7a086f224..20edfe1e2d 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -373,17 +373,7 @@ namespace np vm::ptr cb_arg; SceNpMatching2Event event_type; - void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const - { - if (cb) - { - sysutil_register_cb([=, ctx_id = this->ctx_id, event_type = this->event_type, cb = this->cb, cb_arg = this->cb_arg](ppu_thread& cb_ppu) -> s32 - { - cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg); - return 0; - }); - } - } + void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const; }; u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type); diff --git a/rpcs3/Emu/NP/rpcn_client.cpp b/rpcs3/Emu/NP/rpcn_client.cpp index e02078f512..1ebbfdd14e 100644 --- a/rpcs3/Emu/NP/rpcn_client.cpp +++ b/rpcs3/Emu/NP/rpcn_client.cpp @@ -1495,7 +1495,7 @@ namespace rpcn return notifs; } - std::unordered_map>> rpcn_client::get_replies() + std::map>> rpcn_client::get_replies() { std::lock_guard lock(mutex_replies); auto ret_replies = std::move(replies); diff --git a/rpcs3/Emu/NP/rpcn_client.h b/rpcs3/Emu/NP/rpcn_client.h index 11acb291a5..6d7126ffa6 100644 --- a/rpcs3/Emu/NP/rpcn_client.h +++ b/rpcs3/Emu/NP/rpcn_client.h @@ -314,7 +314,7 @@ namespace rpcn std::optional> get_friend_presence_by_npid(const std::string& npid); std::vector>> get_notifications(); - std::unordered_map>> get_replies(); + std::map>> get_replies(); std::unordered_map get_presence_updates(); std::map get_presence_states(); @@ -428,8 +428,8 @@ namespace rpcn shared_mutex mutex_notifs, mutex_replies, mutex_replies_sync, mutex_presence_updates; std::vector>> notifications; // notif type / data - std::unordered_map>> replies; // req id / (command / data) - std::unordered_map>> replies_sync; // same but for sync replies(see handle_input()) + std::map>> replies; // req id / (command / data) + std::map>> replies_sync; // same but for sync replies(see handle_input()) std::unordered_map presence_updates; // npid / presence data // Messages From 16e41f449295ad41bba0dc9f445e42eee0a61c97 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 15 Dec 2025 14:35:18 +0200 Subject: [PATCH 67/94] Fix compilation with older Qt versions --- rpcs3/rpcs3qt/main_window.cpp | 2 +- rpcs3/rpcs3qt/pkg_install_dialog.cpp | 2 +- rpcs3/rpcs3qt/qt_camera_video_sink.cpp | 6 +++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index 80d71a44c2..c7516dc700 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -870,7 +870,7 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot) const QString installation_info = tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") - .arg(rpcs3::utils::get_hdd0_game_dir()) + .arg(QString::fromStdString(rpcs3::utils::get_hdd0_game_dir())) .arg(gui::utils::format_byte_size(free_space)) .arg(info.data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) .arg(gui::utils::format_byte_size(info.data_size)); diff --git a/rpcs3/rpcs3qt/pkg_install_dialog.cpp b/rpcs3/rpcs3qt/pkg_install_dialog.cpp index 26fb7515c4..370f4d053c 100644 --- a/rpcs3/rpcs3qt/pkg_install_dialog.cpp +++ b/rpcs3/rpcs3qt/pkg_install_dialog.cpp @@ -190,7 +190,7 @@ void pkg_install_dialog::UpdateInfo(QLabel* installation_info, QDialogButtonBox* installation_info->setText(gui::utils::make_paragraph( tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3") - .arg(rpcs3::utils::get_hdd0_game_dir()) + .arg(QString::fromStdString(rpcs3::utils::get_hdd0_game_dir())) .arg(gui::utils::format_byte_size(free_space)) .arg(data_size <= free_space ? QString() : tr(" - NOT ENOUGH SPACE")) .arg(gui::utils::format_byte_size(data_size)))); diff --git a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp index 4e1c8b3ccf..d4736f01e2 100644 --- a/rpcs3/rpcs3qt/qt_camera_video_sink.cpp +++ b/rpcs3/rpcs3qt/qt_camera_video_sink.cpp @@ -71,10 +71,14 @@ bool qt_camera_video_sink::present(const QVideoFrame& frame) // Flip image if necessary if (flip_horizontally || flip_vertically) { - Qt::Orientations orientation {}; +#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0) + Qt::Orientations orientation{}; orientation.setFlag(Qt::Orientation::Horizontal, flip_horizontally); orientation.setFlag(Qt::Orientation::Vertical, flip_vertically); image.flip(orientation); +#else + image.mirror(flip_horizontally, flip_vertically); +#endif } if (image.format() != QImage::Format_RGBA8888) From 65cd4deb777b010c54031a2991c444209da3a598 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 15 Dec 2025 21:30:40 +0200 Subject: [PATCH 68/94] Prevent atomic cache line collisions --- rpcs3/Emu/CPU/CPUThread.cpp | 2 +- rpcs3/Emu/Cell/SPUThread.cpp | 12 +++++++----- rpcs3/Emu/Cell/SPUThread.h | 6 +++--- rpcs3/Emu/Cell/lv2/lv2.cpp | 2 +- rpcs3/Emu/Cell/lv2/sys_mutex.cpp | 2 +- rpcs3/Emu/Memory/vm.cpp | 14 +++++++------- rpcs3/Emu/Memory/vm_locking.h | 14 +++++++------- rpcs3/Emu/Memory/vm_reservation.h | 16 ++++++++-------- rpcs3/util/logs.cpp | 4 ++-- 9 files changed, 37 insertions(+), 35 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUThread.cpp b/rpcs3/Emu/CPU/CPUThread.cpp index bb8e46eee1..afec56f7e1 100644 --- a/rpcs3/Emu/CPU/CPUThread.cpp +++ b/rpcs3/Emu/CPU/CPUThread.cpp @@ -506,7 +506,7 @@ extern f64 get_cpu_program_usage_percent(u64 hash) thread_local DECLARE(cpu_thread::g_tls_this_thread) = nullptr; // Total number of CPU threads -static atomic_t s_cpu_counter{0}; +static atomic_t s_cpu_counter{0}; // List of posted tasks for suspend_all //static atomic_t s_cpu_work[128]{}; diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 2673685bb2..7b70dcdbd2 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -488,7 +488,7 @@ waitpkg_func static void __tpause(u32 cycles, u32 cstate) namespace vm { - std::array, 2048> g_resrv_waiters_count{}; + std::array, 1024> g_resrv_waiters_count{}; } void do_cell_atomic_128_store(u32 addr, const void* to_write); @@ -499,7 +499,7 @@ const spu_decoder s_spu_itype; namespace vm { - extern atomic_t g_range_lock_set[64]; + extern atomic_t g_range_lock_set[64]; // Defined here for performance reasons writer_lock::~writer_lock() noexcept @@ -2000,7 +2000,7 @@ void spu_thread::do_dma_transfer(spu_thread* _this, const spu_mfc_cmd& args, u8* cpu_thread* _cpu = _this ? _this : get_current_cpu_thread(); - atomic_t* range_lock = nullptr; + atomic_t* range_lock = nullptr; if (!_this) [[unlikely]] { @@ -4928,12 +4928,12 @@ bool spu_thread::reservation_check(u32 addr, const decltype(rdata)& data, u32 cu return !res; } -bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t* range_lock) +bool spu_thread::reservation_check(u32 addr, u32 hash, atomic_t* range_lock) { if ((addr >> 28) < 2 || (addr >> 28) == 0xd) { // Always-allocated memory does not need strict checking (vm::main or vm::stack) - return compute_rdata_hash32(*vm::get_super_ptr(addr)) == hash; + return compute_rdata_hash32(*vm::get_super_ptr(addr)) != hash; } // Ensure data is allocated (HACK: would raise LR event if not) @@ -5067,6 +5067,8 @@ void spu_thread::deregister_cache_line_waiter(usz index) return; } + ensure(index < std::size(g_spu_waiters_by_value)); + g_spu_waiters_by_value[index].atomic_op([](u64& x) { x--; diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 9adb15a47c..9596f7b006 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -708,7 +708,7 @@ public: const decltype(rdata)* resrv_mem{}; // Range Lock pointer - atomic_t* range_lock{}; + atomic_t* range_lock{}; u32 srr0 = 0; u32 ch_tag_upd = 0; @@ -903,7 +903,7 @@ public: // It is safe to use on any address, even if not directly accessed by SPU (so it's slower) // Optionally pass a known allocated address for internal optimization (the current Effective-Address of the MFC command) bool reservation_check(u32 addr, const decltype(rdata)& data, u32 current_eal = 0) const; - static bool reservation_check(u32 addr, u32 hash, atomic_t* range_lock); + static bool reservation_check(u32 addr, u32 hash, atomic_t* range_lock); usz register_cache_line_waiter(u32 addr); void deregister_cache_line_waiter(usz index); @@ -915,7 +915,7 @@ public: static atomic_t g_raw_spu_id[5]; static atomic_t g_spu_work_count; - static atomic_t g_spu_waiters_by_value[6]; + static atomic_t g_spu_waiters_by_value[6]; static u32 find_raw_spu(u32 id) { diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index c405b98a2c..263b32992e 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -2260,7 +2260,7 @@ void lv2_obj::notify_all() noexcept // There may be 6 waiters, but checking them all may be performance expensive // Instead, check 2 at max, but use the CPU ID index to tell which index to start checking so the work would be distributed across all threads - atomic_t* range_lock = nullptr; + atomic_t* range_lock = nullptr; if (cpu->get_class() == thread_class::spu) { diff --git a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp index 63c0c16af9..e6c96ffd64 100644 --- a/rpcs3/Emu/Cell/lv2/sys_mutex.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_mutex.cpp @@ -347,7 +347,7 @@ error_code sys_mutex_unlock(ppu_thread& ppu, u32 mutex_id) const auto mutex = idm::check(mutex_id, [&, notify = lv2_obj::notify_all_t()](lv2_mutex& mutex) -> CellError { // At unlock, we have some time to do other jobs when the thread is unlikely to be in other critical sections - notify.enqueue_on_top(vm::reservation_notifier_notify(ppu.res_notify, ppu.res_notify_time)); + notify.enqueue_on_top(vm::reservation_notifier_notify(ppu.res_notify, ppu.res_notify_time, true)); auto result = mutex.try_unlock(ppu); diff --git a/rpcs3/Emu/Memory/vm.cpp b/rpcs3/Emu/Memory/vm.cpp index 6181c2c6bb..112b1f8354 100644 --- a/rpcs3/Emu/Memory/vm.cpp +++ b/rpcs3/Emu/Memory/vm.cpp @@ -74,7 +74,7 @@ namespace vm std::array, g_cfg.core.ppu_threads.max> g_locks{}; // Range lock slot allocation bits - atomic_t g_range_lock_bits[2]{}; + atomic_t g_range_lock_bits[2]{}; auto& get_range_lock_bits(bool is_exclusive_range) { @@ -82,7 +82,7 @@ namespace vm } // Memory range lock slots (sparse atomics) - atomic_t g_range_lock_set[64]{}; + atomic_t g_range_lock_set[64]{}; // Memory pages std::array g_pages; @@ -142,7 +142,7 @@ namespace vm } } - atomic_t* alloc_range_lock() + atomic_t* alloc_range_lock() { const auto [bits, ok] = get_range_lock_bits(false).fetch_op([](u64& bits) { @@ -167,7 +167,7 @@ namespace vm template static u64 for_all_range_locks(u64 input, F func); - void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size) + void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size) { perf_meter<"RHW_LOCK"_u64> perf0(0); @@ -275,7 +275,7 @@ namespace vm } } - void free_range_lock(atomic_t* range_lock) noexcept + void free_range_lock(atomic_t* range_lock) noexcept { if (range_lock < g_range_lock_set || range_lock >= std::end(g_range_lock_set)) { @@ -316,7 +316,7 @@ namespace vm return result; } - static atomic_t* _lock_main_range_lock(u64 flags, u32 addr, u32 size) + static atomic_t* _lock_main_range_lock(u64 flags, u32 addr, u32 size) { // Shouldn't really happen if (size == 0) @@ -460,7 +460,7 @@ namespace vm { } - writer_lock::writer_lock(u32 const addr, atomic_t* range_lock, u32 const size, u64 const flags) noexcept + writer_lock::writer_lock(u32 const addr, atomic_t* range_lock, u32 const size, u64 const flags) noexcept : range_lock(range_lock) { cpu_thread* cpu{}; diff --git a/rpcs3/Emu/Memory/vm_locking.h b/rpcs3/Emu/Memory/vm_locking.h index c4d805554f..d2b45ddd13 100644 --- a/rpcs3/Emu/Memory/vm_locking.h +++ b/rpcs3/Emu/Memory/vm_locking.h @@ -28,7 +28,7 @@ namespace vm range_bits = 3, }; - extern atomic_t g_range_lock_bits[2]; + extern atomic_t g_range_lock_bits[2]; extern atomic_t g_shmem[]; @@ -36,13 +36,13 @@ namespace vm void passive_lock(cpu_thread& cpu); // Register range lock for further use - atomic_t* alloc_range_lock(); + atomic_t* alloc_range_lock(); - void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size); + void range_lock_internal(atomic_t* range_lock, u32 begin, u32 size); // Lock memory range ignoring memory protection (Size!=0 also implies aligned begin) template - FORCE_INLINE void range_lock(atomic_t* range_lock, u32 begin, u32 _size) + FORCE_INLINE void range_lock(atomic_t* range_lock, u32 begin, u32 _size) { if constexpr (Size == 0) { @@ -80,7 +80,7 @@ namespace vm } // Release it - void free_range_lock(atomic_t*) noexcept; + void free_range_lock(atomic_t*) noexcept; // Unregister reader void passive_unlock(cpu_thread& cpu); @@ -91,12 +91,12 @@ namespace vm struct writer_lock final { - atomic_t* range_lock; + atomic_t* range_lock; writer_lock(const writer_lock&) = delete; writer_lock& operator=(const writer_lock&) = delete; writer_lock() noexcept; - writer_lock(u32 addr, atomic_t* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept; + writer_lock(u32 addr, atomic_t* range_lock = nullptr, u32 size = 128, u64 flags = range_locked) noexcept; ~writer_lock() noexcept; }; } // namespace vm diff --git a/rpcs3/Emu/Memory/vm_reservation.h b/rpcs3/Emu/Memory/vm_reservation.h index d21a593959..b543f4ad5d 100644 --- a/rpcs3/Emu/Memory/vm_reservation.h +++ b/rpcs3/Emu/Memory/vm_reservation.h @@ -34,22 +34,22 @@ namespace vm void reservation_update(u32 addr); std::pair try_reservation_update(u32 addr); - struct reservation_waiter_t + struct alignas(8) reservation_waiter_t { u32 wait_flag = 0; u32 waiters_count = 0; }; - static inline atomic_t* reservation_notifier(u32 raddr, u64 rtime) + static inline atomic_t* reservation_notifier(u32 raddr, u64 rtime) { - constexpr u32 wait_vars_for_each = 64; + constexpr u32 wait_vars_for_each = 32; constexpr u32 unique_address_bit_mask = 0b1111; constexpr u32 unique_rtime_bit_mask = 0b1; - extern std::array, wait_vars_for_each * (unique_address_bit_mask + 1) * (unique_rtime_bit_mask + 1)> g_resrv_waiters_count; + extern std::array, wait_vars_for_each * (unique_address_bit_mask + 1) * (unique_rtime_bit_mask + 1)> g_resrv_waiters_count; // Storage efficient method to distinguish different nearby addresses (which are likely) - const usz index = std::popcount(raddr & -2048) * (1 << 5) + ((rtime / 128) & unique_rtime_bit_mask) * (1 << 4) + ((raddr / 128) & unique_address_bit_mask); + const usz index = std::min(std::popcount(raddr & -2048), 31) * (1 << 5) + ((rtime / 128) & unique_rtime_bit_mask) * (1 << 4) + ((raddr / 128) & unique_address_bit_mask); return &g_resrv_waiters_count[index]; } @@ -59,7 +59,7 @@ namespace vm return reservation_notifier(raddr, rtime)->load().waiters_count; } - static inline void reservation_notifier_end_wait(atomic_t& waiter) + static inline void reservation_notifier_end_wait(atomic_t& waiter) { waiter.atomic_op([](reservation_waiter_t& value) { @@ -73,9 +73,9 @@ namespace vm }); } - static inline std::pair*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime) + static inline std::pair*, u32> reservation_notifier_begin_wait(u32 raddr, u64 rtime) { - atomic_t& waiter = *reservation_notifier(raddr, rtime); + atomic_t& waiter = *reservation_notifier(raddr, rtime); u32 wait_flag = 0; diff --git a/rpcs3/util/logs.cpp b/rpcs3/util/logs.cpp index d48a22c0aa..0276f90e0b 100644 --- a/rpcs3/util/logs.cpp +++ b/rpcs3/util/logs.cpp @@ -89,8 +89,8 @@ namespace logs z_stream m_zs{}; shared_mutex m_m{}; - atomic_t m_buf{0}; // MSB (39 bits): push begin, LSB (25 bis): push size - atomic_t m_out{0}; // Amount of bytes written to file + atomic_t m_buf{0}; // MSB (39 bits): push begin, LSB (25 bis): push size + atomic_t m_out{0}; // Amount of bytes written to file uchar m_zout[65536]{}; From de8552048f5855d2a9d9fa863b77673b4f69b873 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 15 Dec 2025 22:40:56 +0200 Subject: [PATCH 69/94] PPU: Prevent repeated notifications --- rpcs3/Emu/Memory/vm_reservation.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rpcs3/Emu/Memory/vm_reservation.h b/rpcs3/Emu/Memory/vm_reservation.h index b543f4ad5d..1a1e818075 100644 --- a/rpcs3/Emu/Memory/vm_reservation.h +++ b/rpcs3/Emu/Memory/vm_reservation.h @@ -56,7 +56,8 @@ namespace vm // Returns waiter count static inline u32 reservation_notifier_count(u32 raddr, u64 rtime) { - return reservation_notifier(raddr, rtime)->load().waiters_count; + reservation_waiter_t v = reservation_notifier(raddr, rtime)->load(); + return v.wait_flag % 2 == 1 ? v.waiters_count : 0; } static inline void reservation_notifier_end_wait(atomic_t& waiter) From 103d580d9a91f4029b45e2a74264062451971560 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Tue, 16 Dec 2025 00:16:02 +0200 Subject: [PATCH 70/94] Fixup futex emulation std::unordered_multimap::find may return any matching element.. unlike equal_range. (code relied on matching the first) There was also UB there of reference to an element after it has deleted. --- Utilities/sync.h | 10 +++++----- rpcs3/Emu/Cell/lv2/lv2.cpp | 7 ++----- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/Utilities/sync.h b/Utilities/sync.h index db46d6104f..6667ccbcf3 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -81,9 +81,9 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { struct waiter { - uint val; - uint mask; - std::condition_variable cv; + uint val; + uint mask; + std::condition_variable cv; }; std::mutex mutex; @@ -111,7 +111,7 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t waiter rec; rec.val = val; rec.mask = mask; - const auto& ref = *map.emplace(uaddr, &rec); + const auto itr = map.emplace(uaddr, &rec); int res = 0; @@ -134,7 +134,7 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t // TODO: absolute timeout } - map.erase(std::find(map.find(uaddr), map.end(), ref)); + map.erase(itr); return res; } diff --git a/rpcs3/Emu/Cell/lv2/lv2.cpp b/rpcs3/Emu/Cell/lv2/lv2.cpp index 263b32992e..3b923e8c41 100644 --- a/rpcs3/Emu/Cell/lv2/lv2.cpp +++ b/rpcs3/Emu/Cell/lv2/lv2.cpp @@ -2228,11 +2228,8 @@ void lv2_obj::notify_all() noexcept break; } - if (cpu != &g_to_notify) - { - // Note: by the time of notification the thread could have been deallocated which is why the direct function is used - atomic_wait_engine::notify_all(cpu); - } + // Note: by the time of notification the thread could have been deallocated which is why the direct function is used + atomic_wait_engine::notify_all(cpu); } g_to_notify[0] = nullptr; From 812d84e7f4263905e751f330e3660b8f7ace41af Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Tue, 16 Dec 2025 11:33:57 +0200 Subject: [PATCH 71/94] Util/sync.h: Fix iterator invalidation in futex emulation --- Utilities/sync.h | 47 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 5 deletions(-) diff --git a/Utilities/sync.h b/Utilities/sync.h index 6667ccbcf3..f6b1a4fc17 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -86,11 +86,21 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t std::condition_variable cv; }; - std::mutex mutex; - std::unordered_multimap map; + struct bucket_t + { + std::mutex mutex; + std::unordered_multimap map; + }; + + // Not a power of 2 on purpose (for alignment optimiations) + bucket_t bucks[63]; int operator()(volatile void* uaddr, int futex_op, uint val, const timespec* timeout, uint mask) { + auto& bucket = bucks[(reinterpret_cast(uaddr) / 8) % std::size(bucks)]; + auto& mutex = bucket.mutex; + auto& map = bucket.map; + std::unique_lock lock(mutex); switch (futex_op) @@ -111,7 +121,9 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t waiter rec; rec.val = val; rec.mask = mask; - const auto itr = map.emplace(uaddr, &rec); + + // Announce the waiter + map.emplace(uaddr, &rec); int res = 0; @@ -127,6 +139,16 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { res = -1; errno = ETIMEDOUT; + + // Cleanup + for (auto range = map.equal_range(uaddr); range.first != range.second; range.first++) + { + if (range.first->second == &rec) + { + map.erase(range.first); + break; + } + } } } else @@ -134,7 +156,6 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t // TODO: absolute timeout } - map.erase(itr); return res; } @@ -153,13 +174,29 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t if (entry.mask & mask) { - entry.cv.notify_one(); entry.mask = 0; + entry.cv.notify_one(); res++; val--; } } + if (res) + { + // Cleanup + for (auto range = map.equal_range(uaddr); range.first != range.second;) + { + if (range.first->second->mask == 0) + { + map.erase(range.first); + range = map.equal_range(uaddr); + continue; + } + + range.first++; + } + } + return res; } } From b1089ab1a3150bd5810c7bf3956987d2ad438639 Mon Sep 17 00:00:00 2001 From: kd-11 <15904127+kd-11@users.noreply.github.com> Date: Thu, 4 Dec 2025 16:11:08 +0300 Subject: [PATCH 72/94] Fix hypervisor context memory placement --- rpcs3/Emu/Cell/PPUThread.h | 6 +++--- rpcs3/Emu/Cell/SPUThread.h | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index 97c705aed5..66d6be2bcb 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -257,6 +257,9 @@ public: // Sticky saturation bit v128 sat{}; + // Hypervisor context data + rpcs3::hypervisor_context_t hv_ctx; // HV context for gate enter exit. Keep at a low struct offset. + // Optimization: precomputed java-mode mask for handling denormals u32 jm_mask = 0x7f80'0000; @@ -307,9 +310,6 @@ public: // Thread name atomic_ptr ppu_tname; - // Hypervisor context data - rpcs3::hypervisor_context_t hv_ctx; // HV context for gate enter exit. Keep at a low struct offset. - u64 last_ftsc = 0; u64 last_ftime = 0; u32 last_faddr = 0; diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index 9596f7b006..b54fc830f9 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -673,6 +673,9 @@ public: std::array gpr{}; SPU_FPSCR fpscr{}; + // Hypervisor context data + rpcs3::hypervisor_context_t hv_ctx; // NOTE: The offset within the class must be within the first 1MiB (10 bits max) + // MFC command data spu_mfc_cmd ch_mfc_cmd{}; @@ -788,8 +791,6 @@ public: u64 block_recover = 0; u64 block_failure = 0; - rpcs3::hypervisor_context_t hv_ctx; // NOTE: The offset within the class must be within the first 1MiB - u64 ftx = 0; // Failed transactions u64 stx = 0; // Succeeded transactions (pure counters) From 061fb5d112c1d27e907118942db108babc082408 Mon Sep 17 00:00:00 2001 From: Ani Date: Tue, 16 Dec 2025 22:22:32 +0100 Subject: [PATCH 73/94] Revert "Fix hypervisor context memory placement" This reverts commit b1089ab1a3150bd5810c7bf3956987d2ad438639. --- rpcs3/Emu/Cell/PPUThread.h | 6 +++--- rpcs3/Emu/Cell/SPUThread.h | 5 ++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index 66d6be2bcb..97c705aed5 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -257,9 +257,6 @@ public: // Sticky saturation bit v128 sat{}; - // Hypervisor context data - rpcs3::hypervisor_context_t hv_ctx; // HV context for gate enter exit. Keep at a low struct offset. - // Optimization: precomputed java-mode mask for handling denormals u32 jm_mask = 0x7f80'0000; @@ -310,6 +307,9 @@ public: // Thread name atomic_ptr ppu_tname; + // Hypervisor context data + rpcs3::hypervisor_context_t hv_ctx; // HV context for gate enter exit. Keep at a low struct offset. + u64 last_ftsc = 0; u64 last_ftime = 0; u32 last_faddr = 0; diff --git a/rpcs3/Emu/Cell/SPUThread.h b/rpcs3/Emu/Cell/SPUThread.h index b54fc830f9..9596f7b006 100644 --- a/rpcs3/Emu/Cell/SPUThread.h +++ b/rpcs3/Emu/Cell/SPUThread.h @@ -673,9 +673,6 @@ public: std::array gpr{}; SPU_FPSCR fpscr{}; - // Hypervisor context data - rpcs3::hypervisor_context_t hv_ctx; // NOTE: The offset within the class must be within the first 1MiB (10 bits max) - // MFC command data spu_mfc_cmd ch_mfc_cmd{}; @@ -791,6 +788,8 @@ public: u64 block_recover = 0; u64 block_failure = 0; + rpcs3::hypervisor_context_t hv_ctx; // NOTE: The offset within the class must be within the first 1MiB + u64 ftx = 0; // Failed transactions u64 stx = 0; // Succeeded transactions (pure counters) From 4c7d19fc69075f614f67c78e3fe53ebf3bc8fc5e Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Mon, 15 Dec 2025 19:56:12 +0000 Subject: [PATCH 74/94] [macOS] Update to MVK 1.4.1 with private API support (fixes panics, improves feature support) --- .ci/deploy-mac-arm64.sh | 8 +++++--- .ci/deploy-mac.sh | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.ci/deploy-mac-arm64.sh b/.ci/deploy-mac-arm64.sh index 41f27a2082..8a27d04676 100755 --- a/.ci/deploy-mac-arm64.sh +++ b/.ci/deploy-mac-arm64.sh @@ -16,9 +16,11 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin mkdir "rpcs3.app/Contents/lib/" || true mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true -cp "$(realpath /opt/homebrew/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" -cp "$(realpath /opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" -sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +wget https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar +tar -xvf MoltenVK-macos-privateapi.tar +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib" diff --git a/.ci/deploy-mac.sh b/.ci/deploy-mac.sh index 02d5945727..ec71fe0262 100755 --- a/.ci/deploy-mac.sh +++ b/.ci/deploy-mac.sh @@ -16,9 +16,11 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env cd bin mkdir "rpcs3.app/Contents/lib/" || true mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true -cp "$(realpath /usr/local/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" -cp "$(realpath /usr/local/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" -sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +wget https://github.com/KhronosGroup/MoltenVK/releases/download/v1.4.1/MoltenVK-macos-privateapi.tar +tar -xvf MoltenVK-macos-privateapi.tar +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/libMoltenVK.dylib" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib" +cp "MoltenVK/MoltenVK/dynamic/dylib/macOS/MoltenVK_icd.json" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" +sed -i '' "s/.\//..\/..\/..\/Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib" cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib" From e0cdafddb594289fa9833be6bb6b7d2f04d31e3b Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Mon, 15 Dec 2025 09:03:42 +0200 Subject: [PATCH 75/94] SPU Analyzer: Fix missing jump table entries in Mega mode --- rpcs3/Emu/Cell/SPUCommonRecompiler.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp index 2fbb08a97d..4d04b13666 100644 --- a/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPUCommonRecompiler.cpp @@ -3931,6 +3931,17 @@ spu_program spu_recompiler_base::analyse(const be_t* ls, u32 entry_point, s continue; } + for (auto it2 = it->second.begin(); it2 != it->second.end();) + { + if (*it2 < lsa || *it2 >= limit) + { + it2 = it->second.erase(it2); + continue; + } + + it2++; + } + it++; } From 03a91aa052526a319d23f2a0430be85f18d7f799 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Tue, 16 Dec 2025 01:18:31 +0300 Subject: [PATCH 76/94] rsx/cfg: Increase valid register file size to 768 bytes per pixel pipe --- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp | 4 +--- rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h | 15 +++++++++++++++ .../Passes/FP/RegisterAnnotationPass.cpp | 16 ++++++---------- .../Passes/FP/RegisterDependencyPass.cpp | 12 +++--------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp index 2dc58a471b..3ab4f3d893 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp @@ -402,12 +402,10 @@ namespace rsx::assembler::FP return {}; } - constexpr u32 register_file_max_len = 48 * 8; // H0 - H47, R0 - R23 - const u32 lane_width = reg.reg.f16 ? 2 : 4; const u32 file_offset = reg.reg.id * lane_width * 4; - ensure(file_offset < register_file_max_len, "Invalid register index"); + ensure(file_offset < constants::register_file_max_len, "Invalid register index"); rsx::simple_array result{}; auto insert_lane = [&](u32 word_offset) diff --git a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h index 4e7f65f22b..1de0985a35 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h +++ b/rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h @@ -85,6 +85,21 @@ namespace rsx::assembler namespace FP { + namespace constants + { + // The ISA can encode for 48 registers of any width. + // This allows to encode R0-R64 and H0-H95, though there aren't enough addressing bits for the latter. + constexpr u32 register_file_max_len = 48 * 16; + + // Enums for analysis passes. + constexpr char content_unknown = 0; + constexpr char content_float32 = 'R'; + constexpr char content_float16 = 'H'; + constexpr char content_dual = 'D'; + } + + using register_file_t = std::array; + // Returns number of operands consumed by an instruction u8 get_operand_count(FP_opcode opcode); diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp index 9b031f2a0e..1b34f53091 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp @@ -9,11 +9,7 @@ namespace rsx::assembler::FP { - static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers - static constexpr char content_unknown = 0; - static constexpr char content_float32 = 'R'; - static constexpr char content_float16 = 'H'; - static constexpr char content_dual = 'D'; + using namespace constants; bool is_delay_slot(const Instruction& instruction) { @@ -60,7 +56,7 @@ namespace rsx::assembler::FP return true; } - std::vector compile_register_file(const std::array& file) + std::vector compile_register_file(const register_file_t& file) { std::vector results; @@ -163,11 +159,11 @@ namespace rsx::assembler::FP // Annotate each block with input and output lanes (read and clobber list) void annotate_block_io(BasicBlock* block) { - alignas(16) std::array output_register_file; - alignas(16) std::array input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now + alignas(16) register_file_t output_register_file; + alignas(16) register_file_t input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now - std::memset(output_register_file.data(), content_unknown, register_file_length); - std::memset(input_register_file.data(), content_unknown, register_file_length); + std::memset(output_register_file.data(), content_unknown, register_file_max_len); + std::memset(input_register_file.data(), content_unknown, register_file_max_len); for (const auto& instruction : block->instructions) { diff --git a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp index c5b24b35bc..27fcd488a7 100644 --- a/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp +++ b/rpcs3/Emu/RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp @@ -9,13 +9,7 @@ namespace rsx::assembler::FP { - static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers - static constexpr char content_unknown = 0; - static constexpr char content_float32 = 'R'; - static constexpr char content_float16 = 'H'; - static constexpr char content_dual = 'D'; - - using register_file_t = std::array; + using namespace constants; struct DependencyPassContext { @@ -293,7 +287,7 @@ namespace rsx::assembler::FP void insert_dependency_barriers(DependencyPassContext& ctx, BasicBlock* block) { register_file_t& register_file = ctx.exec_register_map[block]; - std::memset(register_file.data(), content_unknown, register_file_length); + std::memset(register_file.data(), content_unknown, register_file_max_len); std::unordered_set barrier16; std::unordered_set barrier32; @@ -403,7 +397,7 @@ namespace rsx::assembler::FP if (ctx.sync_register_map.find(target) == ctx.sync_register_map.end()) { auto& blob = ctx.sync_register_map[target]; - std::memset(blob.data(), content_unknown, register_file_length); + std::memset(blob.data(), content_unknown, register_file_max_len); } auto& sync_register_file = ctx.sync_register_map[target]; From 1147f47a8a98261f75977f5a75352641df7cfd27 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Tue, 16 Dec 2025 23:11:55 +0000 Subject: [PATCH 77/94] Switch macOS atomics to futexes, bump min ver to 14.4 Co-authored-by: elad335 <18193363+elad335@users.noreply.github.com> --- .ci/build-mac-arm64.sh | 8 ++++---- .ci/build-mac.sh | 8 ++++---- rpcs3/rpcs3.plist.in | 2 +- rpcs3/util/atomic.cpp | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index f4e194ca0e..ec7c4ddcae 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -58,8 +58,8 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib" -export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" -export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" +export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_PATH/lib" export LD_LIBRARY_PATH="$BREW_PATH/lib" @@ -78,7 +78,7 @@ sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVer mkdir build && cd build || exit 1 -export MACOSX_DEPLOYMENT_TARGET=14.0 +export MACOSX_DEPLOYMENT_TARGET=14.4 "$BREW_PATH/bin/cmake" .. \ -DBUILD_RPCS3_TESTS="${RUN_UNIT_TESTS}" \ @@ -110,7 +110,7 @@ export MACOSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_OSX_ARCHITECTURES=arm64 \ -DCMAKE_IGNORE_PATH="$BREW_PATH/lib" \ -DCMAKE_IGNORE_PREFIX_PATH=/opt/homebrew/opt \ - -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" \ + -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ -G Ninja diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index 6dc6977f82..abe54a4c77 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -57,8 +57,8 @@ export SDL3_DIR="$BREW_X64_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="/opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" # shellcheck disable=SC2155 export LDFLAGS="-L$BREW_X64_PATH/lib -Wl,-rpath,$BREW_X64_PATH/lib,-L$(brew --prefix llvm)/lib/c++" -export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" -export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" +export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" @@ -77,7 +77,7 @@ sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVer mkdir build && cd build || exit 1 -export MACOSX_DEPLOYMENT_TARGET=14.0 +export MACOSX_DEPLOYMENT_TARGET=14.4 "/opt/homebrew/bin/cmake" .. \ -DBUILD_RPCS3_TESTS=OFF \ @@ -111,7 +111,7 @@ export MACOSX_DEPLOYMENT_TARGET=14.0 -DCMAKE_TOOLCHAIN_FILE=buildfiles/cmake/TCDarwinX86_64.cmake \ -DCMAKE_IGNORE_PATH="$BREW_X64_PATH/lib" \ -DCMAKE_IGNORE_PREFIX_PATH=/usr/local/opt \ - -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000" \ + -DCMAKE_CXX_FLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" \ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \ -DCMAKE_OSX_SYSROOT="$(xcrun --sdk macosx --show-sdk-path)" \ -G Ninja diff --git a/rpcs3/rpcs3.plist.in b/rpcs3/rpcs3.plist.in index 53902adea3..a854779d9d 100644 --- a/rpcs3/rpcs3.plist.in +++ b/rpcs3/rpcs3.plist.in @@ -33,7 +33,7 @@ LSApplicationCategoryType public.app-category.games LSMinimumSystemVersion - 14.0 + 14.4 NSCameraUsageDescription The camera will be used for PlayStation Eye emulation NSMicrophoneUsageDescription diff --git a/rpcs3/util/atomic.cpp b/rpcs3/util/atomic.cpp index 67f48f8dc7..595162cd04 100644 --- a/rpcs3/util/atomic.cpp +++ b/rpcs3/util/atomic.cpp @@ -1,6 +1,6 @@ #include "atomic.hpp" -#if defined(__linux__) +#if defined(__linux__) || defined(__APPLE__) #define USE_FUTEX #elif !defined(_WIN32) #define USE_STD From 36af9f54d36e0cd576f7abcf6ace3316c5c62eb3 Mon Sep 17 00:00:00 2001 From: Ani Date: Sun, 14 Dec 2025 23:43:57 +0100 Subject: [PATCH 78/94] Revert "SPU/config: Make SPU LLVM Mega block size default" This reverts commit 4a042ae84ff43d35f631b62c20ab69a7934e862c. --- rpcs3/Emu/system_config.h | 2 +- rpcs3/rpcs3qt/emu_settings.cpp | 6 +++--- rpcs3/rpcs3qt/emu_settings_type.h | 2 +- rpcs3/rpcs3qt/tooltips.h | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index 2b45ce82a0..e2ad7cbd8b 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -36,7 +36,7 @@ struct cfg_root : cfg::node cfg::_int<0, 16> spu_delay_penalty{ this, "SPU delay penalty", 3 }; // Number of milliseconds to block a thread if a virtual 'core' isn't free cfg::_bool spu_loop_detection{ this, "SPU loop detection", false }; // Try to detect wait loops and trigger thread yield cfg::_int<1, 6> max_spurs_threads{ this, "Max SPURS Threads", 6, true }; // HACK. If less then 6, max number of running SPURS threads in each thread group. - cfg::_enum spu_block_size{ this, "SPU Analyzer Block Size", spu_block_size_type::mega }; + cfg::_enum spu_block_size{ this, "SPU Block Size", spu_block_size_type::safe }; cfg::_bool spu_accurate_dma{ this, "Accurate SPU DMA", false }; cfg::_bool spu_accurate_reservations{ this, "Accurate SPU Reservations", true }; cfg::_bool accurate_cache_line_stores{ this, "Accurate Cache Line Stores", false }; diff --git a/rpcs3/rpcs3qt/emu_settings.cpp b/rpcs3/rpcs3qt/emu_settings.cpp index 769c843a66..935bfd2cc8 100644 --- a/rpcs3/rpcs3qt/emu_settings.cpp +++ b/rpcs3/rpcs3qt/emu_settings.cpp @@ -973,9 +973,9 @@ QString emu_settings::GetLocalizedSetting(const QString& original, emu_settings_ case emu_settings_type::SPUBlockSize: switch (static_cast(index)) { - case spu_block_size_type::safe: return tr("Safe", "SPU Analyzer Block Size"); - case spu_block_size_type::mega: return tr("Mega", "SPU Analyzer Block Size"); - case spu_block_size_type::giga: return tr("Giga", "SPU Analyzer Block Size"); + case spu_block_size_type::safe: return tr("Safe", "SPU block size"); + case spu_block_size_type::mega: return tr("Mega", "SPU block size"); + case spu_block_size_type::giga: return tr("Giga", "SPU block size"); } break; case emu_settings_type::ThreadSchedulerMode: diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 7e69656eac..3007e0a1d9 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -239,7 +239,7 @@ inline static const std::map settings_location { emu_settings_type::XFloatAccuracy, { "Core", "XFloat Accuracy"}}, { emu_settings_type::MFCCommandsShuffling, { "Core", "MFC Commands Shuffling Limit"}}, { emu_settings_type::SetDAZandFTZ, { "Core", "Set DAZ and FTZ"}}, - { emu_settings_type::SPUBlockSize, { "Core", "SPU Analyzer Block Size"}}, + { emu_settings_type::SPUBlockSize, { "Core", "SPU Block Size"}}, { emu_settings_type::SPUCache, { "Core", "SPU Cache"}}, { emu_settings_type::DebugConsoleMode, { "Core", "Debug Console Mode"}}, { emu_settings_type::MaxSPURSThreads, { "Core", "Max SPURS Threads"}}, diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index 3b84a66270..f7ec927332 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -91,7 +91,7 @@ public: const QString xfloat = tr("Control accuracy to SPU float vectors processing.\nFixes bugs in various games at the cost of performance.\nThis setting is only applied when SPU Decoder is set to Dynamic or LLVM."); const QString enable_thread_scheduler = tr("Control how RPCS3 utilizes the threads of your system.\nEach option heavily depends on the game and on your CPU. It's recommended to try each option to find out which performs the best.\nChanging the thread scheduler is not supported on CPUs with less than 12 threads."); const QString spu_loop_detection = tr("Try to detect loop conditions in SPU kernels and use them as scheduling hints.\nImproves performance and reduces CPU usage.\nMay cause severe audio stuttering in rare cases."); - const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility at the cost of lower performance."); + const QString spu_block_size = tr("This option controls the SPU analyser, particularly the size of compiled units. The Mega and Giga modes may improve performance by tying smaller units together, decreasing the number of compiled units but increasing their size.\nUse the Safe mode for maximum compatibility."); const QString preferred_spu_threads = tr("Some SPU stages are sensitive to race conditions and allowing a limited number at a time helps alleviate performance stalls.\nSetting this to a smaller value might improve performance and reduce stuttering in some games.\nLeave this on auto if performance is negatively affected when setting a small value."); const QString max_cpu_preempt = tr("Reduces CPU usage and power consumption, improving battery life on mobile devices. (0 means disabled)\nHigher values cause a more pronounced effect, but may cause audio or performance issues. A value of 50 or less is recommended.\nThis option forces an FPS limit because it's active when framerate is stable.\nThe lighter the game is on the hardware, the more power is saved by it. (until the preemption count barrier is reached)"); From ddfa773675faabee3f58e26cb57c0dce75994585 Mon Sep 17 00:00:00 2001 From: Elad <18193363+elad335@users.noreply.github.com> Date: Tue, 16 Dec 2025 21:19:01 +0200 Subject: [PATCH 79/94] sys_fs: Optimize split files handling --- rpcs3/Emu/Cell/lv2/sys_fs.cpp | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/rpcs3/Emu/Cell/lv2/sys_fs.cpp b/rpcs3/Emu/Cell/lv2/sys_fs.cpp index 5bb74808be..92fc21b747 100644 --- a/rpcs3/Emu/Cell/lv2/sys_fs.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_fs.cpp @@ -848,7 +848,7 @@ lv2_file::open_raw_result_t lv2_file::open_raw(const std::string& local_path, s3 fs::file file(local_path, open_mode); - if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent) + if (!file && open_mode == fs::read && fs::g_tls_error == fs::error::noent && mp.mp != &g_mp_sys_dev_hdd1) { // Try to gather split file (TODO) std::vector fragments; @@ -1389,7 +1389,7 @@ error_code sys_fs_opendir(ppu_thread& ppu, vm::cptr path, vm::ptr fd) } // Add additional entries for split file candidates (while ends with .66600) - while (data.back().name.ends_with(".66600")) + while (mp.mp != &g_mp_sys_dev_hdd1 && data.back().name.ends_with(".66600")) { data.emplace_back(data.back()).name.resize(data.back().name.size() - 6); } @@ -1505,6 +1505,7 @@ error_code sys_fs_closedir(ppu_thread& ppu, u32 fd) error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr sb) { ppu.state += cpu_flag::wait; + lv2_obj::sleep(ppu); sys_fs.warning("sys_fs_stat(path=%s, sb=*0x%x)", path, sb); @@ -1552,8 +1553,18 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr // Try to analyse split file (TODO) u64 total_size = 0; - for (u32 i = 66601; i <= 66699; i++) + // Use attributes from the first fragment (consistently with sys_fs_open+fstat + fs::stat_t info_split{}; + if (mp.mp != &g_mp_sys_dev_hdd1 && fs::get_stat(local_path + ".66600", info_split) && !info_split.is_directory) { + // Success + total_size += info_split.size; + } + + for (u32 i = 66601; total_size && i <= 66699; i++) + { + info = {}; + if (fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) { total_size += info.size; @@ -1564,11 +1575,11 @@ error_code sys_fs_stat(ppu_thread& ppu, vm::cptr path, vm::ptr } } - // Use attributes from the first fragment (consistently with sys_fs_open+fstat) - if (fs::get_stat(local_path + ".66600", info) && !info.is_directory) + if (total_size) { // Success - info.size += total_size; + info_split.size = total_size; + info = info_split; break; } @@ -2895,7 +2906,7 @@ error_code sys_fs_chmod(ppu_thread&, vm::cptr path, s32 mode) for (u32 i = 66601; i <= 66699; i++) { - if (!fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) + if (mp != &g_mp_sys_dev_hdd1 && !fs::get_stat(fmt::format("%s.%u", local_path, i), info) && !info.is_directory) { break; } From 5507078bd88260a73157d1039a2ba3eca015ffde Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Wed, 17 Dec 2025 18:10:48 +0000 Subject: [PATCH 80/94] [macOS] Use native os_sync calls for futexes --- Utilities/sync.h | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/Utilities/sync.h b/Utilities/sync.h index f6b1a4fc17..2d805ff14c 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -17,6 +17,8 @@ #include #include #include +#elif __APPLE__ +#include #endif #ifdef _WIN32 @@ -76,6 +78,46 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { #ifdef __linux__ return syscall(SYS_futex, uaddr, futex_op, static_cast(val), timeout, nullptr, static_cast(mask)); +#elif __APPLE__ + switch (futex_op) + { + case FUTEX_WAIT_PRIVATE: + case FUTEX_WAIT_BITSET_PRIVATE: + { + if (timeout) + { + const uint64_t nsec = timeout->tv_nsec + timeout->tv_sec * 1000000000ull; + return os_sync_wait_on_address_with_timeout(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE, OS_CLOCK_MACH_ABSOLUTE_TIME, nsec); + } + else + { + return os_sync_wait_on_address(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE); + } + } + + case FUTEX_WAKE_PRIVATE: + case FUTEX_WAKE_BITSET_PRIVATE: + { + for (;;) + { + int ret = 0; + if (val == INT32_MAX) + { + ret = os_sync_wake_by_address_all(const_cast(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE); + } + else if (val-- >= 0) + { + ret = os_sync_wake_by_address_any(const_cast(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE); + } + if (val <= 0 || val == INT32_MAX || (ret < 0 && errno == ENOENT)) + { + return ret; + } + } + } + } + errno = EINVAL; + return -1; #else static struct futex_manager { From b99bd12d5ed723bb476c09beb277c28d69545673 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 19 Dec 2025 05:47:50 +0100 Subject: [PATCH 81/94] Add Extra sceNp2 logging --- rpcs3/Emu/NP/np_cache.cpp | 3 +++ rpcs3/Emu/NP/np_handler.cpp | 1 + rpcs3/Emu/NP/np_structs_extra.cpp | 18 +++++++++++++++--- rpcs3/Emu/NP/np_structs_extra.h | 1 + 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/rpcs3/Emu/NP/np_cache.cpp b/rpcs3/Emu/NP/np_cache.cpp index 3b1c38bd9e..edb0ab1903 100644 --- a/rpcs3/Emu/NP/np_cache.cpp +++ b/rpcs3/Emu/NP/np_cache.cpp @@ -1,3 +1,4 @@ +#include "Emu/NP/np_structs_extra.h" #include "stdafx.h" #include @@ -155,6 +156,8 @@ namespace np slots.openPublicSlotNum = open_public_slots; slots.openPrivateSlotNum = open_private_slots; + extra_nps::print_SceNpMatching2RoomSlotInfo(&slots); + return {CELL_OK, slots}; } diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 5bf637747c..01d947d32e 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1708,6 +1708,7 @@ namespace np { sysutil_register_cb([=, ctx_id = this->ctx_id, event_type = this->event_type, cb = this->cb, cb_arg = this->cb_arg](ppu_thread& cb_ppu) -> s32 { + sceNp2.trace("Calling callback 0x%x with req_id %d, event_type: 0x%x, error_code: 0x%x", cb, req_id, event_type, error_code); cb(cb_ppu, ctx_id, req_id, event_type, event_key, error_code, data_size, cb_arg); return 0; }); diff --git a/rpcs3/Emu/NP/np_structs_extra.cpp b/rpcs3/Emu/NP/np_structs_extra.cpp index 2acba3d045..52f394ff2e 100644 --- a/rpcs3/Emu/NP/np_structs_extra.cpp +++ b/rpcs3/Emu/NP/np_structs_extra.cpp @@ -104,7 +104,7 @@ namespace extra_nps for (u32 i = 0; i < req->roomSearchableIntAttrExternalNum && req->roomSearchableIntAttrExternal; i++) print_int_attr(&req->roomSearchableIntAttrExternal[i]); - + sceNp2.warning("roomSearchableBinAttrExternal: *0x%x", req->roomSearchableBinAttrExternal); sceNp2.warning("roomSearchableBinAttrExternalNum: %d", req->roomSearchableBinAttrExternalNum); @@ -186,7 +186,7 @@ namespace extra_nps sceNp2.warning("SceNpMatching2SearchRoomResponse:"); print_range(&resp->range); - const SceNpMatching2RoomDataExternal *room_ptr = resp->roomDataExternal.get_ptr(); + const SceNpMatching2RoomDataExternal* room_ptr = resp->roomDataExternal.get_ptr(); for (u32 i = 0; i < resp->range.total; i++) { sceNp2.warning("SceNpMatching2SearchRoomResponse[%d]:", i); @@ -471,7 +471,7 @@ namespace extra_nps { sceNp.warning("ptr: *0x%x", data->value.data.ptr); sceNp.warning("size: %d", data->value.data.size); - sceNp.warning("data:\n%s", fmt::buf_to_hexstring(static_cast(data->value.data.ptr.get_ptr()), data->value.data.size)); + sceNp.warning("data:\n%s", fmt::buf_to_hexstring(static_cast(data->value.data.ptr.get_ptr()), data->value.data.size)); } else { @@ -577,4 +577,16 @@ namespace extra_nps } } + void print_SceNpMatching2RoomSlotInfo(const SceNpMatching2RoomSlotInfo* data) + { + sceNp.warning("SceNpMatching2RoomSlotInfo:"); + sceNp.warning("roomId: %d", data->roomId); + sceNp.warning("joinedSlotMask: %x", data->joinedSlotMask); + sceNp.warning("passwordSlotMask: %x", data->passwordSlotMask); + sceNp.warning("publicSlotNum: %d", data->publicSlotNum); + sceNp.warning("privateSlotNum: %d", data->privateSlotNum); + sceNp.warning("openPublicSlotNum: %d", data->openPublicSlotNum); + sceNp.warning("openPrivateSlotNum: %d", data->openPrivateSlotNum); + } + } // namespace extra_nps diff --git a/rpcs3/Emu/NP/np_structs_extra.h b/rpcs3/Emu/NP/np_structs_extra.h index d05711fabd..1c722a0cf2 100644 --- a/rpcs3/Emu/NP/np_structs_extra.h +++ b/rpcs3/Emu/NP/np_structs_extra.h @@ -41,4 +41,5 @@ namespace extra_nps void print_SceNpMatchingRoomStatus(const SceNpMatchingRoomStatus* data); void print_SceNpMatchingJoinedRoomInfo(const SceNpMatchingJoinedRoomInfo* data); void print_SceNpMatchingSearchJoinRoomInfo(const SceNpMatchingSearchJoinRoomInfo* data); + void print_SceNpMatching2RoomSlotInfo(const SceNpMatching2RoomSlotInfo* data); } // namespace extra_nps From 371af2d94f4015e460a553451b230695c214d8e5 Mon Sep 17 00:00:00 2001 From: RipleyTom Date: Fri, 19 Dec 2025 06:07:18 +0100 Subject: [PATCH 82/94] Refuse to abort sceNp2 requests that change state --- rpcs3/Emu/Cell/Modules/sceNp2.cpp | 5 +---- rpcs3/Emu/NP/np_cache.cpp | 2 +- rpcs3/Emu/NP/np_handler.cpp | 33 +++++++++++++++++----------- rpcs3/Emu/NP/np_handler.h | 5 +++-- rpcs3/Emu/NP/np_requests.cpp | 36 +++++++++++++++---------------- 5 files changed, 43 insertions(+), 38 deletions(-) diff --git a/rpcs3/Emu/Cell/Modules/sceNp2.cpp b/rpcs3/Emu/Cell/Modules/sceNp2.cpp index 7a2ee50bd8..3c33375fa3 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp2.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp2.cpp @@ -819,10 +819,7 @@ error_code sceNpMatching2AbortRequest(SceNpMatching2ContextId ctxId, SceNpMatchi return SCE_NP_MATCHING2_ERROR_CONTEXT_NOT_FOUND; } - if (!nph.abort_request(reqId)) - return SCE_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND; - - return CELL_OK; + return nph.abort_request(reqId); } error_code sceNpMatching2GetServerInfo( diff --git a/rpcs3/Emu/NP/np_cache.cpp b/rpcs3/Emu/NP/np_cache.cpp index edb0ab1903..fc90a641a5 100644 --- a/rpcs3/Emu/NP/np_cache.cpp +++ b/rpcs3/Emu/NP/np_cache.cpp @@ -1,10 +1,10 @@ -#include "Emu/NP/np_structs_extra.h" #include "stdafx.h" #include #include "Emu/NP/np_allocator.h" #include "Emu/NP/np_cache.h" #include "Emu/NP/np_helpers.h" +#include "Emu/NP/np_structs_extra.h" LOG_CHANNEL(np_cache); diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 01d947d32e..09770f0908 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -1273,19 +1273,20 @@ namespace np return false; } - u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type) + u32 np_handler::generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type, bool abortable) { - callback_info ret; - const auto ctx = get_match2_context(ctx_id); ensure(ctx); const u32 req_id = get_req_id(optParam ? optParam->appReqId : ctx->default_match2_optparam.appReqId); - ret.ctx_id = ctx_id; - ret.cb_arg = (optParam && optParam->cbFuncArg) ? optParam->cbFuncArg : ctx->default_match2_optparam.cbFuncArg; - ret.cb = (optParam && optParam->cbFunc) ? optParam->cbFunc : ctx->default_match2_optparam.cbFunc; - ret.event_type = event_type; + callback_info ret{ + .ctx_id = ctx_id, + .cb = (optParam && optParam->cbFunc) ? optParam->cbFunc : ctx->default_match2_optparam.cbFunc, + .cb_arg = (optParam && optParam->cbFuncArg) ? optParam->cbFuncArg : ctx->default_match2_optparam.cbFuncArg, + .event_type = event_type, + .abortable = abortable, + }; nph_log.trace("Callback used is 0x%x with req_id %d", ret.cb, req_id); @@ -1310,16 +1311,22 @@ namespace np return cb_info; } - bool np_handler::abort_request(u32 req_id) + error_code np_handler::abort_request(u32 req_id) { - auto cb_info_opt = take_pending_request(req_id); + std::lock_guard lock(mutex_pending_requests); - if (!cb_info_opt) - return false; + if (!pending_requests.contains(req_id)) + return SCE_NP_MATCHING2_ERROR_REQUEST_NOT_FOUND; - cb_info_opt->queue_callback(req_id, 0, SCE_NP_MATCHING2_ERROR_ABORTED, 0); + if (!::at32(pending_requests, req_id).abortable) + return SCE_NP_MATCHING2_ERROR_CANNOT_ABORT; - return true; + const auto cb_info = std::move(::at32(pending_requests, req_id)); + pending_requests.erase(req_id); + + cb_info.queue_callback(req_id, 0, SCE_NP_MATCHING2_ERROR_ABORTED, 0); + + return CELL_OK; } event_data& np_handler::allocate_req_result(u32 event_key, u32 max_size, u32 initial_size) diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index 20edfe1e2d..999ae7cb86 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -257,7 +257,7 @@ namespace np u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count); u32 get_players_history_count(u32 options); bool get_player_history_entry(u32 options, u32 index, SceNpId* npid); - bool abort_request(u32 req_id); + error_code abort_request(u32 req_id); // For signaling void req_sign_infos(const std::string& npid, u32 conn_id); @@ -372,11 +372,12 @@ namespace np vm::ptr cb; vm::ptr cb_arg; SceNpMatching2Event event_type; + bool abortable; void queue_callback(u32 req_id, u32 event_key, s32 error_code, u32 data_size) const; }; - u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type); + u32 generate_callback_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, SceNpMatching2Event event_type, bool abortable); std::optional take_pending_request(u32 req_id); private: diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 23e18f1353..115348ac6d 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -69,7 +69,7 @@ namespace np u32 np_handler::get_server_status(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 server_id) { // TODO: actually implement interaction with server for this? - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetServerInfo, true); const u32 event_key = get_event_key(); auto& edata = allocate_req_result(event_key, SCE_NP_MATCHING2_EVENT_DATA_MAX_SIZE_GetServerInfo, sizeof(SceNpMatching2GetServerInfoResponse)); @@ -87,7 +87,7 @@ namespace np u32 np_handler::create_server_context(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 /*server_id*/) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateServerContext, false); const auto cb_info_opt = take_pending_request(req_id); ensure(cb_info_opt); @@ -98,7 +98,7 @@ namespace np u32 np_handler::delete_server_context(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 /*server_id*/) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_DeleteServerContext, false); const auto cb_info_opt = take_pending_request(req_id); ensure(cb_info_opt); @@ -109,7 +109,7 @@ namespace np u32 np_handler::get_world_list(SceNpMatching2ContextId ctx_id, vm::cptr optParam, u16 server_id) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetWorldInfoList, true); if (!get_rpcn()->get_world_list(req_id, get_match2_context(ctx_id)->communicationId, server_id)) { @@ -159,7 +159,7 @@ namespace np u32 np_handler::create_join_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2CreateJoinRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_CreateJoinRoom, false); extra_nps::print_SceNpMatching2CreateJoinRoomRequest(req); @@ -221,7 +221,7 @@ namespace np u32 np_handler::join_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2JoinRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_JoinRoom, false); extra_nps::print_SceNpMatching2JoinRoomRequest(req); @@ -311,7 +311,7 @@ namespace np u32 np_handler::leave_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2LeaveRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_LeaveRoom, false); if (!get_rpcn()->leave_room(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -356,7 +356,7 @@ namespace np u32 np_handler::search_room(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SearchRoomRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SearchRoom, true); extra_nps::print_SceNpMatching2SearchRoomRequest(req); @@ -395,7 +395,7 @@ namespace np u32 np_handler::get_roomdata_external_list(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomDataExternalListRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataExternalList, true); extra_nps::print_SceNpMatching2GetRoomDataExternalListRequest(req); @@ -435,7 +435,7 @@ namespace np u32 np_handler::set_roomdata_external(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomDataExternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataExternal, false); extra_nps::print_SceNpMatching2SetRoomDataExternalRequest(req); @@ -470,7 +470,7 @@ namespace np u32 np_handler::get_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomDataInternal, true); if (!get_rpcn()->get_roomdata_internal(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -524,7 +524,7 @@ namespace np u32 np_handler::set_roomdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomDataInternal, false); extra_nps::print_SceNpMatching2SetRoomDataInternalRequest(req); @@ -558,7 +558,7 @@ namespace np u32 np_handler::get_roommemberdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2GetRoomMemberDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomMemberDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetRoomMemberDataInternal, true); extra_nps::print_SceNpMatching2GetRoomMemberDataInternalRequest(req); if (!get_rpcn()->get_roommemberdata_internal(req_id, get_match2_context(ctx_id)->communicationId, req)) @@ -610,7 +610,7 @@ namespace np u32 np_handler::set_roommemberdata_internal(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetRoomMemberDataInternalRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetRoomMemberDataInternal, false); extra_nps::print_SceNpMatching2SetRoomMemberDataInternalRequest(req); @@ -646,7 +646,7 @@ namespace np u32 np_handler::set_userinfo(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SetUserInfoRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetUserInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SetUserInfo, false); if (!get_rpcn()->set_userinfo(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -675,7 +675,7 @@ namespace np u32 np_handler::get_ping_info(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SignalingGetPingInfoRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SignalingGetPingInfo, true); if (!get_rpcn()->ping_room_owner(req_id, get_match2_context(ctx_id)->communicationId, req->roomId)) { @@ -722,7 +722,7 @@ namespace np u32 np_handler::send_room_message(SceNpMatching2ContextId ctx_id, vm::cptr optParam, const SceNpMatching2SendRoomMessageRequest* req) { - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_SendRoomMessage, false); if (!get_rpcn()->send_room_message(req_id, get_match2_context(ctx_id)->communicationId, req)) { @@ -803,7 +803,7 @@ namespace np extra_nps::print_SceNpMatching2GetLobbyInfoListRequest(req); - const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetLobbyInfoList); + const u32 req_id = generate_callback_info(ctx_id, optParam, SCE_NP_MATCHING2_REQUEST_EVENT_GetLobbyInfoList, false); auto cb_info_opt = take_pending_request(req_id); if (!cb_info_opt) From 752155d5ddbfd0e903630829e4d7cd0c68b97d46 Mon Sep 17 00:00:00 2001 From: Marin Baron Date: Sun, 30 Nov 2025 12:29:28 +0100 Subject: [PATCH 83/94] Build LLVM-22 fix removed getDeclaration --- rpcs3/Emu/CPU/CPUTranslator.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index bb5b262294..e2b768c74e 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -1149,7 +1149,7 @@ struct llvm_fshl static llvm::Function* get_fshl(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); - return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); + return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -1221,7 +1221,7 @@ struct llvm_fshr static llvm::Function* get_fshr(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); - return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); + return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -2220,7 +2220,7 @@ struct llvm_add_sat static llvm::Function* get_add_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); - return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); + return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -2303,7 +2303,7 @@ struct llvm_sub_sat static llvm::Function* get_sub_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); - return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); + return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -3592,7 +3592,7 @@ public: llvm::Function* get_intrinsic(llvm::Intrinsic::ID id) { const auto _module = m_ir->GetInsertBlock()->getParent()->getParent(); - return llvm::Intrinsic::getDeclaration(_module, id, {get_type()...}); + return llvm::Intrinsic::getOrInsertDeclaration(_module, id, {get_type()...}); } template From 46db5c35eb5293ef03d7abead3c2b76a1a266383 Mon Sep 17 00:00:00 2001 From: Marin Baron Date: Sun, 30 Nov 2025 12:32:01 +0100 Subject: [PATCH 84/94] Build LLVM deprecated getFirstNonPHI --- rpcs3/Emu/CPU/CPUTranslator.cpp | 2 +- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index d17b1ec977..250086a89a 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -71,7 +71,7 @@ cpu_translator::cpu_translator(llvm::Module* _module, bool is_be) result = m_ir->CreateInsertElement(v, m_ir->CreateExtractElement(data0, m_ir->CreateExtractElement(mask, i)), i); v->addIncoming(result, loop); m_ir->CreateCondBr(m_ir->CreateICmpULT(i, m_ir->getInt32(16)), loop, next); - m_ir->SetInsertPoint(next->getFirstNonPHI()); + m_ir->SetInsertPoint(next->getFirstNonPHIIt()); result = m_ir->CreateSelect(m_ir->CreateICmpSLT(index, zeros), zeros, result); return result; diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 989ba2e84f..c5a594b2e7 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2617,7 +2617,7 @@ public: { if (b2 != bqbi) { - auto ins = b2->block->getFirstNonPHI(); + auto ins = b2->block->getFirstNonPHIIt(); if (b2->bb->preds.size() == 1) { From 78690282eb9cd765c0373f7151c3a1d3639a82fa Mon Sep 17 00:00:00 2001 From: Marin Baron Date: Sun, 30 Nov 2025 13:34:52 +0100 Subject: [PATCH 85/94] Build LLVM some API dont support iterator so use pointer --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index c5a594b2e7..0b33875436 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2617,7 +2617,7 @@ public: { if (b2 != bqbi) { - auto ins = b2->block->getFirstNonPHIIt(); + auto ins = &*b2->block->getFirstNonPHIIt(); if (b2->bb->preds.size() == 1) { From c9725b79fca56fd50670a55d6e7ff5257ad07080 Mon Sep 17 00:00:00 2001 From: Marin Baron Date: Sun, 30 Nov 2025 15:58:15 +0100 Subject: [PATCH 86/94] Build 3rdparty include as system --- 3rdparty/7zip/CMakeLists.txt | 4 ++-- 3rdparty/CMakeLists.txt | 12 ++++++------ 3rdparty/asmjit/CMakeLists.txt | 2 +- 3rdparty/discord-rpc/CMakeLists.txt | 2 +- 3rdparty/feralinteractive/CMakeLists.txt | 2 +- 3rdparty/glslang/CMakeLists.txt | 2 +- 3rdparty/hidapi/CMakeLists.txt | 2 +- 3rdparty/libpng/CMakeLists.txt | 4 ++-- 3rdparty/llvm/CMakeLists.txt | 2 +- 3rdparty/miniupnp/CMakeLists.txt | 6 +++--- 3rdparty/rtmidi/CMakeLists.txt | 2 +- 3rdparty/stblib/CMakeLists.txt | 2 +- 3rdparty/zlib/CMakeLists.txt | 2 +- 3rdparty/zstd/CMakeLists.txt | 2 +- 14 files changed, 23 insertions(+), 23 deletions(-) diff --git a/3rdparty/7zip/CMakeLists.txt b/3rdparty/7zip/CMakeLists.txt index 706d869472..a8f5d87cb3 100644 --- a/3rdparty/7zip/CMakeLists.txt +++ b/3rdparty/7zip/CMakeLists.txt @@ -59,11 +59,11 @@ if(WIN32 OR APPLE) 7zip/C/XzEnc.c 7zip/C/XzIn.c 7zip/C/ZstdDec.c) - target_include_directories(3rdparty_7zip INTERFACE + target_include_directories(3rdparty_7zip SYSTEM INTERFACE $ $) - target_include_directories(3rdparty_7zip INTERFACE 7zip) + target_include_directories(3rdparty_7zip SYSTEM INTERFACE 7zip) set_property(TARGET 3rdparty_7zip PROPERTY FOLDER "3rdparty/") diff --git a/3rdparty/CMakeLists.txt b/3rdparty/CMakeLists.txt index 324069e363..a800ba1dd5 100644 --- a/3rdparty/CMakeLists.txt +++ b/3rdparty/CMakeLists.txt @@ -35,7 +35,7 @@ if (USE_SYSTEM_FLATBUFFERS) message(FATAL_ERROR "flatc failed to regenerate flatbuffers headers.") endif() else() - target_include_directories(3rdparty_flatbuffers INTERFACE flatbuffers/include) + target_include_directories(3rdparty_flatbuffers SYSTEM INTERFACE flatbuffers/include) endif() # libPNG @@ -56,7 +56,7 @@ if (USE_SYSTEM_VULKAN_MEMORY_ALLOCATOR) add_library(3rdparty::vulkanmemoryallocator ALIAS GPUOpen::VulkanMemoryAllocator) else() add_library(3rdparty_vulkanmemoryallocator INTERFACE) - target_include_directories(3rdparty_vulkanmemoryallocator INTERFACE GPUOpen/VulkanMemoryAllocator/include) + target_include_directories(3rdparty_vulkanmemoryallocator SYSTEM INTERFACE GPUOpen/VulkanMemoryAllocator/include) add_library(3rdparty::vulkanmemoryallocator ALIAS 3rdparty_vulkanmemoryallocator) endif() @@ -111,7 +111,7 @@ if (NOT ANDROID) find_package(OpenGL REQUIRED OPTIONAL_COMPONENTS EGL) add_library(3rdparty_opengl INTERFACE) - target_include_directories(3rdparty_opengl INTERFACE GL) + target_include_directories(3rdparty_opengl SYSTEM INTERFACE GL) if (WIN32) if(NOT MSVC) @@ -204,7 +204,7 @@ if(USE_VULKAN) find_package(Wayland) if (WAYLAND_FOUND) target_include_directories(3rdparty_vulkan - INTERFACE ${WAYLAND_INCLUDE_DIR}) + SYSTEM INTERFACE ${WAYLAND_INCLUDE_DIR}) endif() endif() @@ -298,7 +298,7 @@ if(NOT ANDROID) message(STATUS "RPCS3: using shared ffmpeg") find_package(FFMPEG REQUIRED) - target_include_directories(3rdparty_ffmpeg INTERFACE ${FFMPEG_INCLUDE_DIR}) + target_include_directories(3rdparty_ffmpeg SYSTEM INTERFACE ${FFMPEG_INCLUDE_DIR}) target_link_libraries(3rdparty_ffmpeg INTERFACE ${FFMPEG_LIBRARIES}) else() message(STATUS "RPCS3: using builtin ffmpeg") @@ -328,7 +328,7 @@ if(NOT ANDROID) ${FFMPEG_LIB_SWSCALE} ${FFMPEG_LIB_SWRESAMPLE} ) - target_include_directories(3rdparty_ffmpeg INTERFACE "ffmpeg/include") + target_include_directories(3rdparty_ffmpeg SYSTEM INTERFACE "ffmpeg/include") endif() endif() diff --git a/3rdparty/asmjit/CMakeLists.txt b/3rdparty/asmjit/CMakeLists.txt index ae88ce2fe8..a11de388f3 100644 --- a/3rdparty/asmjit/CMakeLists.txt +++ b/3rdparty/asmjit/CMakeLists.txt @@ -9,7 +9,7 @@ set(ASMJIT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/asmjit" CACHE PATH "Location of 'asm include("${ASMJIT_DIR}/CMakeLists.txt") add_library(asmjit ${ASMJIT_SRC}) -target_include_directories(asmjit PUBLIC ${ASMJIT_DIR}/src) +target_include_directories(asmjit SYSTEM PUBLIC ${ASMJIT_DIR}/src) target_link_libraries(asmjit PRIVATE ${ASMJIT_DEPS}) # ASMJIT should have a option for disabling installing and this wouldnt diff --git a/3rdparty/discord-rpc/CMakeLists.txt b/3rdparty/discord-rpc/CMakeLists.txt index 2ad705d52c..9212ca5a60 100644 --- a/3rdparty/discord-rpc/CMakeLists.txt +++ b/3rdparty/discord-rpc/CMakeLists.txt @@ -9,7 +9,7 @@ if (USE_DISCORD_RPC AND (WIN32 OR CMAKE_SYSTEM MATCHES "Linux" OR APPLE)) set(WARNINGS_AS_ERRORS FALSE CACHE BOOL "When enabled, compiles with `-Werror` (on *nix platforms).") add_subdirectory(discord-rpc EXCLUDE_FROM_ALL) - target_include_directories(3rdparty_discordRPC INTERFACE discord-rpc/include) + target_include_directories(3rdparty_discordRPC SYSTEM INTERFACE discord-rpc/include) target_compile_definitions(3rdparty_discordRPC INTERFACE -DWITH_DISCORD_RPC) target_link_libraries(3rdparty_discordRPC INTERFACE discord-rpc) endif() diff --git a/3rdparty/feralinteractive/CMakeLists.txt b/3rdparty/feralinteractive/CMakeLists.txt index c7b136e5f0..0b1f6fc7e7 100644 --- a/3rdparty/feralinteractive/CMakeLists.txt +++ b/3rdparty/feralinteractive/CMakeLists.txt @@ -3,7 +3,7 @@ add_library(3rdparty_feralinteractive INTERFACE) if (CMAKE_SYSTEM MATCHES "Linux") - target_include_directories(3rdparty_feralinteractive INTERFACE feralinteractive/lib) + target_include_directories(3rdparty_feralinteractive SYSTEM INTERFACE feralinteractive/lib) target_compile_definitions(3rdparty_feralinteractive INTERFACE -DGAMEMODE_AVAILABLE) target_link_libraries(3rdparty_feralinteractive INTERFACE feralinteractive) endif() diff --git a/3rdparty/glslang/CMakeLists.txt b/3rdparty/glslang/CMakeLists.txt index c86d0b384c..10cdecd63b 100644 --- a/3rdparty/glslang/CMakeLists.txt +++ b/3rdparty/glslang/CMakeLists.txt @@ -7,7 +7,7 @@ if(USE_SYSTEM_GLSLANG) target_link_libraries(3rdparty_glslang INTERFACE glslang::SPIRV) get_target_property(SPIRV_INCLUDE_DIRS glslang::SPIRV INTERFACE_INCLUDE_DIRECTORIES) list(TRANSFORM SPIRV_INCLUDE_DIRS APPEND "/glslang") - target_include_directories(3rdparty_glslang INTERFACE ${SPIRV_INCLUDE_DIRS}) + target_include_directories(3rdparty_glslang SYSTEM INTERFACE ${SPIRV_INCLUDE_DIRS}) else() set(ENABLE_PCH OFF CACHE BOOL "Enables Precompiled header" FORCE) set(BUILD_EXTERNAL OFF CACHE BOOL "Build external dependencies in /External" FORCE) diff --git a/3rdparty/hidapi/CMakeLists.txt b/3rdparty/hidapi/CMakeLists.txt index 2d043d6936..cdaac856b0 100644 --- a/3rdparty/hidapi/CMakeLists.txt +++ b/3rdparty/hidapi/CMakeLists.txt @@ -4,7 +4,7 @@ if(USE_SYSTEM_HIDAPI) pkg_check_modules(hidapi-hidraw REQUIRED IMPORTED_TARGET hidapi-hidraw) add_library(3rdparty_hidapi INTERFACE) target_link_libraries(3rdparty_hidapi INTERFACE PkgConfig::hidapi-hidraw) - target_include_directories(3rdparty_hidapi INTERFACE PkgConfig::hidapi-hidraw) + target_include_directories(3rdparty_hidapi SYSTEM INTERFACE PkgConfig::hidapi-hidraw) else() set(BUILD_SHARED_LIBS FALSE CACHE BOOL "Don't build shared libs") set(HIDAPI_INSTALL_TARGETS FALSE CACHE BOOL "Don't install anything") diff --git a/3rdparty/libpng/CMakeLists.txt b/3rdparty/libpng/CMakeLists.txt index f24c2c9709..9ac9c976c4 100644 --- a/3rdparty/libpng/CMakeLists.txt +++ b/3rdparty/libpng/CMakeLists.txt @@ -6,14 +6,14 @@ if (NOT USE_SYSTEM_LIBPNG) set(PNG_TESTS OFF CACHE BOOL "Build libpng tests") set(SKIP_INSTALL_ALL ON) add_subdirectory(libpng EXCLUDE_FROM_ALL) - target_include_directories(png_static INTERFACE "${libpng_BINARY_DIR}" "${libpng_SOURCE_DIR}") + target_include_directories(png_static SYSTEM INTERFACE "${libpng_BINARY_DIR}" "${libpng_SOURCE_DIR}") set(LIBPNG_TARGET png_static PARENT_SCOPE) else() find_package(PNG REQUIRED) add_library(3rdparty_system_libpng INTERFACE) - target_include_directories(3rdparty_system_libpng INTERFACE ${PNG_INCLUDE_DIR}) + target_include_directories(3rdparty_system_libpng SYSTEM INTERFACE ${PNG_INCLUDE_DIR}) target_link_libraries(3rdparty_system_libpng INTERFACE ${PNG_LIBRARY}) target_compile_definitions(3rdparty_system_libpng INTERFACE ${PNG_DEFINITIONS}) diff --git a/3rdparty/llvm/CMakeLists.txt b/3rdparty/llvm/CMakeLists.txt index d1295886d8..a4af3b3ef5 100644 --- a/3rdparty/llvm/CMakeLists.txt +++ b/3rdparty/llvm/CMakeLists.txt @@ -107,7 +107,7 @@ if(WITH_LLVM) add_library(3rdparty_llvm INTERFACE) target_link_libraries(3rdparty_llvm INTERFACE ${LLVM_LIBS}) - target_include_directories(3rdparty_llvm INTERFACE ${LLVM_INCLUDE_DIRS}) + target_include_directories(3rdparty_llvm SYSTEM INTERFACE ${LLVM_INCLUDE_DIRS}) separate_arguments(LLVM_DEFINITIONS_LIST NATIVE_COMMAND ${LLVM_DEFINITIONS}) target_compile_definitions(3rdparty_llvm INTERFACE ${LLVM_DEFINITIONS_LIST} LLVM_AVAILABLE) diff --git a/3rdparty/miniupnp/CMakeLists.txt b/3rdparty/miniupnp/CMakeLists.txt index c40d4a5ebd..1baab6dbdd 100644 --- a/3rdparty/miniupnp/CMakeLists.txt +++ b/3rdparty/miniupnp/CMakeLists.txt @@ -3,9 +3,9 @@ if(USE_SYSTEM_MINIUPNPC) pkg_check_modules(MiniUPnPc REQUIRED IMPORTED_TARGET miniupnpc>=2.3.3) add_library(3rdparty_miniupnpc INTERFACE) target_link_libraries(3rdparty_miniupnpc INTERFACE PkgConfig::MiniUPnPc) - target_include_directories(3rdparty_miniupnpc INTERFACE PkgConfig::MiniUPnPc) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE PkgConfig::MiniUPnPc) list(TRANSFORM MiniUPnPc_INCLUDE_DIRS APPEND "/miniupnpc") - target_include_directories(3rdparty_miniupnpc INTERFACE ${MiniUPnPc_INCLUDE_DIRS}) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE ${MiniUPnPc_INCLUDE_DIRS}) else() option (UPNPC_BUILD_STATIC "Build static library" TRUE) option (UPNPC_BUILD_SHARED "Build shared library" FALSE) @@ -17,5 +17,5 @@ else() add_subdirectory(miniupnp/miniupnpc EXCLUDE_FROM_ALL) add_library(3rdparty_miniupnpc INTERFACE) target_link_libraries(3rdparty_miniupnpc INTERFACE libminiupnpc-static) - target_include_directories(3rdparty_miniupnpc INTERFACE libminiupnpc-static) + target_include_directories(3rdparty_miniupnpc SYSTEM INTERFACE libminiupnpc-static) endif() diff --git a/3rdparty/rtmidi/CMakeLists.txt b/3rdparty/rtmidi/CMakeLists.txt index b9dd286ce0..3369f78aa6 100644 --- a/3rdparty/rtmidi/CMakeLists.txt +++ b/3rdparty/rtmidi/CMakeLists.txt @@ -3,7 +3,7 @@ if(USE_SYSTEM_RTMIDI) pkg_check_modules(RtMidi REQUIRED IMPORTED_TARGET rtmidi>=6.0.0) add_library(rtmidi INTERFACE) target_link_libraries(rtmidi INTERFACE PkgConfig::RtMidi) - target_include_directories(rtmidi INTERFACE PkgConfig::RtMidi) + target_include_directories(rtmidi SYSTEM INTERFACE PkgConfig::RtMidi) else() option(RTMIDI_API_JACK "Compile with JACK support." OFF) option(RTMIDI_BUILD_TESTING "Build test programs" OFF) diff --git a/3rdparty/stblib/CMakeLists.txt b/3rdparty/stblib/CMakeLists.txt index 3426459edb..9afd906060 100644 --- a/3rdparty/stblib/CMakeLists.txt +++ b/3rdparty/stblib/CMakeLists.txt @@ -1,2 +1,2 @@ add_library(3rdparty_stblib INTERFACE) -target_include_directories(3rdparty_stblib INTERFACE stb) +target_include_directories(3rdparty_stblib SYSTEM INTERFACE stb) diff --git a/3rdparty/zlib/CMakeLists.txt b/3rdparty/zlib/CMakeLists.txt index d9dffb07be..55d7353acf 100644 --- a/3rdparty/zlib/CMakeLists.txt +++ b/3rdparty/zlib/CMakeLists.txt @@ -13,6 +13,6 @@ else() add_library(3rdparty_zlib INTERFACE) target_link_libraries(3rdparty_zlib INTERFACE zlibstatic) - target_include_directories(3rdparty_zlib INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib) + target_include_directories(3rdparty_zlib SYSTEM INTERFACE zlib ${CMAKE_CURRENT_BINARY_DIR}/zlib) target_compile_definitions(3rdparty_zlib INTERFACE -DZLIB_CONST=1) endif() diff --git a/3rdparty/zstd/CMakeLists.txt b/3rdparty/zstd/CMakeLists.txt index 431272966d..bff61148c2 100644 --- a/3rdparty/zstd/CMakeLists.txt +++ b/3rdparty/zstd/CMakeLists.txt @@ -3,7 +3,7 @@ if(USE_SYSTEM_ZSTD) pkg_check_modules(zstd REQUIRED IMPORTED_TARGET libzstd) add_library(3rdparty_zstd INTERFACE) target_link_libraries(3rdparty_zstd INTERFACE PkgConfig::zstd) - target_include_directories(3rdparty_zstd INTERFACE PkgConfig::RtMidi) + target_include_directories(3rdparty_zstd SYSTEM INTERFACE PkgConfig::RtMidi) else() option(ZSTD_BUILD_PROGRAMS "BUILD PROGRAMS" OFF) option(ZSTD_BUILD_SHARED "BUILD SHARED LIBRARIES" OFF) From a65eb3eb30a20da4513a5bf6adb5f7c476e9c5c4 Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Tue, 2 Dec 2025 20:05:33 +0000 Subject: [PATCH 87/94] Build LLVM wrap getDeclaration by version --- rpcs3/Emu/CPU/CPUTranslator.h | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/rpcs3/Emu/CPU/CPUTranslator.h b/rpcs3/Emu/CPU/CPUTranslator.h index e2b768c74e..c709349080 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.h +++ b/rpcs3/Emu/CPU/CPUTranslator.h @@ -1149,7 +1149,11 @@ struct llvm_fshl static llvm::Function* get_fshl(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); +#else + return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshl, {llvm_value_t::get_type(ir->getContext())}); +#endif } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -1221,7 +1225,11 @@ struct llvm_fshr static llvm::Function* get_fshr(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) return llvm::Intrinsic::getOrInsertDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); +#else + return llvm::Intrinsic::getDeclaration(_module, llvm::Intrinsic::fshr, {llvm_value_t::get_type(ir->getContext())}); +#endif } static llvm::Value* fold(llvm::IRBuilder<>* ir, llvm::Value* v1, llvm::Value* v2, llvm::Value* v3) @@ -2220,7 +2228,11 @@ struct llvm_add_sat static llvm::Function* get_add_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#else + return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#endif } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -2303,7 +2315,11 @@ struct llvm_sub_sat static llvm::Function* get_sub_sat(llvm::IRBuilder<>* ir) { const auto _module = ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) return llvm::Intrinsic::getOrInsertDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#else + return llvm::Intrinsic::getDeclaration(_module, intr, {llvm_value_t::get_type(ir->getContext())}); +#endif } llvm::Value* eval(llvm::IRBuilder<>* ir) const @@ -3592,7 +3608,11 @@ public: llvm::Function* get_intrinsic(llvm::Intrinsic::ID id) { const auto _module = m_ir->GetInsertBlock()->getParent()->getParent(); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) return llvm::Intrinsic::getOrInsertDeclaration(_module, id, {get_type()...}); +#else + return llvm::Intrinsic::getDeclaration(_module, id, {get_type()...}); +#endif } template From 8c9d2662847286353a24a6aaa078602bd479645c Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Tue, 2 Dec 2025 20:15:50 +0000 Subject: [PATCH 88/94] Build LLVM wrap getFirstNonPHI by version --- rpcs3/Emu/CPU/CPUTranslator.cpp | 4 ++++ rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/rpcs3/Emu/CPU/CPUTranslator.cpp b/rpcs3/Emu/CPU/CPUTranslator.cpp index 250086a89a..7cb9186d4d 100644 --- a/rpcs3/Emu/CPU/CPUTranslator.cpp +++ b/rpcs3/Emu/CPU/CPUTranslator.cpp @@ -71,7 +71,11 @@ cpu_translator::cpu_translator(llvm::Module* _module, bool is_be) result = m_ir->CreateInsertElement(v, m_ir->CreateExtractElement(data0, m_ir->CreateExtractElement(mask, i)), i); v->addIncoming(result, loop); m_ir->CreateCondBr(m_ir->CreateICmpULT(i, m_ir->getInt32(16)), loop, next); +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) m_ir->SetInsertPoint(next->getFirstNonPHIIt()); +#else + m_ir->SetInsertPoint(next->getFirstNonPHI()); +#endif result = m_ir->CreateSelect(m_ir->CreateICmpSLT(index, zeros), zeros, result); return result; diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 0b33875436..eb44289320 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2617,7 +2617,11 @@ public: { if (b2 != bqbi) { +#if LLVM_VERSION_MAJOR >= 21 || (LLVM_VERSION_MAJOR == 20 && LLVM_VERSION_MINOR >= 1) auto ins = &*b2->block->getFirstNonPHIIt(); +#else + auto ins = b2->block->getFirstNonPHI(); +#endif if (b2->bb->preds.size() == 1) { From cbecb91b943e6a5b304a58e120ce845e413952ec Mon Sep 17 00:00:00 2001 From: Niram7777 Date: Thu, 4 Dec 2025 20:19:18 +0100 Subject: [PATCH 89/94] Build 3rdparty/soundtouch include as system --- 3rdparty/SoundTouch/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/3rdparty/SoundTouch/CMakeLists.txt b/3rdparty/SoundTouch/CMakeLists.txt index acc7d02714..75b6ca5ac9 100644 --- a/3rdparty/SoundTouch/CMakeLists.txt +++ b/3rdparty/SoundTouch/CMakeLists.txt @@ -11,11 +11,11 @@ add_library(soundtouch STATIC EXCLUDE_FROM_ALL soundtouch/source/SoundTouch/TDStretch.cpp ) -target_include_directories(soundtouch PRIVATE +target_include_directories(soundtouch SYSTEM PRIVATE soundtouch/source/SoundTouch soundtouch/include) -target_include_directories(soundtouch INTERFACE +target_include_directories(soundtouch SYSTEM INTERFACE $ $) From 6546cf9e3a8a74f987a1dd527144546de18739ea Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Fri, 19 Dec 2025 23:37:26 +0000 Subject: [PATCH 90/94] [macOS] General cleanup/fixups --- .ci/build-mac-arm64.sh | 12 ++++-------- .ci/build-mac.sh | 15 +++++---------- rpcs3/Emu/RSX/VK/vkutils/device.cpp | 8 +++++++- rpcs3/Emu/RSX/VK/vkutils/device.h | 1 + 4 files changed, 17 insertions(+), 19 deletions(-) diff --git a/.ci/build-mac-arm64.sh b/.ci/build-mac-arm64.sh index ec7c4ddcae..49b82737c7 100755 --- a/.ci/build-mac-arm64.sh +++ b/.ci/build-mac-arm64.sh @@ -6,7 +6,7 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader +brew install -f --overwrite --quiet googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 @@ -43,11 +43,10 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" - "$BREW_PATH/bin/pipenv" run pip3 uninstall py7zr requests semantic_version lxml - "$BREW_PATH/bin/pipenv" run pip3 install py7zr requests semantic_version lxml --no-cache + pip3 install py7zr requests semantic_version lxml --no-cache --break-system-packages mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" - "$BREW_PATH/bin/pipenv" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" + python3 "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" @@ -58,7 +57,7 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib" -export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CPPFLAGS="-I$BREW_PATH/include -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_PATH/lib" export LD_LIBRARY_PATH="$BREW_PATH/lib" @@ -73,9 +72,6 @@ LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC2046 git submodule -q update --init --depth=1 --jobs=8 $(awk '/path/ && !/ffmpeg/ && !/llvm/ && !/opencv/ && !/SDL/ && !/feralinteractive/ { print $3 }' .gitmodules) -# 3rdparty fixes -sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVersionNumber = 1343;/g" 3rdparty/hidapi/hidapi/mac/hid.c - mkdir build && cd build || exit 1 export MACOSX_DEPLOYMENT_TARGET=14.4 diff --git a/.ci/build-mac.sh b/.ci/build-mac.sh index abe54a4c77..6328ce05d3 100755 --- a/.ci/build-mac.sh +++ b/.ci/build-mac.sh @@ -6,13 +6,11 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 export HOMEBREW_NO_ENV_HINTS=1 export HOMEBREW_NO_INSTALL_CLEANUP=1 -brew install -f --overwrite --quiet ccache pipenv "llvm@$LLVM_COMPILER_VER" +brew install -f --overwrite --quiet ccache "llvm@$LLVM_COMPILER_VER" brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC3009 -rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \ -rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config} arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader +arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet python@3.14 opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5 @@ -42,10 +40,10 @@ if [ ! -d "/tmp/Qt/$QT_VER" ]; then sed -i '' "s/'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/'qt{0}_{0}{1}{2}'.format(major, minor, patch), 'qt{0}_{0}{1}{2}'.format(major, minor, patch)]))/g" qt-downloader sed -i '' "s/'{}\/{}\/qt{}_{}\/'/'{0}\/{1}\/qt{2}_{3}\/qt{2}_{3}\/'/g" qt-downloader cd "/tmp/Qt" - "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run pip3 install py7zr requests semantic_version lxml + pip3 install py7zr requests semantic_version lxml --no-cache --break-system-packages mkdir -p "$QT_VER/macos" ; ln -s "macos" "$QT_VER/clang_64" sed -i '' 's/args\.version \/ derive_toolchain_dir(args) \/ //g' "$WORKDIR/qt-downloader/qt-downloader" - "/opt/homebrew/bin/pipenv" --python "/opt/homebrew/bin/python3" run "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" + python3 "$WORKDIR/qt-downloader/qt-downloader" macos desktop "$QT_VER" clang_64 --opensource --addons qtmultimedia qtimageformats -o "$QT_VER/clang_64" fi cd "$WORKDIR" @@ -57,7 +55,7 @@ export SDL3_DIR="$BREW_X64_PATH/opt/sdl3/lib/cmake/SDL3" export PATH="/opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH" # shellcheck disable=SC2155 export LDFLAGS="-L$BREW_X64_PATH/lib -Wl,-rpath,$BREW_X64_PATH/lib,-L$(brew --prefix llvm)/lib/c++" -export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" +export CPPFLAGS="-I$BREW_X64_PATH/include -msse -msse2 -mcx16 -D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=144000" export LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64_PATH/lib" @@ -72,9 +70,6 @@ LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER" # shellcheck disable=SC2046 git submodule -q update --init --depth=1 --jobs=8 $(awk '/path/ && !/ffmpeg/ && !/llvm/ && !/opencv/ && !/SDL/ && !/feralinteractive/ { print $3 }' .gitmodules) -# 3rdparty fixes -sed -i '' "s/extern const double NSAppKitVersionNumber;/const double NSAppKitVersionNumber = 1343;/g" 3rdparty/hidapi/hidapi/mac/hid.c - mkdir build && cd build || exit 1 export MACOSX_DEPLOYMENT_TARGET=14.4 diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.cpp b/rpcs3/Emu/RSX/VK/vkutils/device.cpp index 34b7758b50..f7b7ffb19c 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.cpp +++ b/rpcs3/Emu/RSX/VK/vkutils/device.cpp @@ -123,6 +123,9 @@ namespace vk optional_features_support.external_memory_host = device_extensions.is_supported(VK_EXT_EXTERNAL_MEMORY_HOST_EXTENSION_NAME); optional_features_support.synchronization_2 = device_extensions.is_supported(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME); optional_features_support.unrestricted_depth_range = device_extensions.is_supported(VK_EXT_DEPTH_RANGE_UNRESTRICTED_EXTENSION_NAME); +#ifdef __APPLE__ + optional_features_support.portability = device_extensions.is_supported(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); +#endif optional_features_support.debug_utils = instance_extensions.is_supported(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); optional_features_support.surface_capabilities_2 = instance_extensions.is_supported(VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME); @@ -560,7 +563,10 @@ namespace vk } #ifdef __APPLE__ - requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + if (pgpu->optional_features_support.portability) + { + requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); + } #endif enabled_features.robustBufferAccess = VK_TRUE; diff --git a/rpcs3/Emu/RSX/VK/vkutils/device.h b/rpcs3/Emu/RSX/VK/vkutils/device.h index c121d1b20a..5ca00eb622 100644 --- a/rpcs3/Emu/RSX/VK/vkutils/device.h +++ b/rpcs3/Emu/RSX/VK/vkutils/device.h @@ -102,6 +102,7 @@ namespace vk bool unrestricted_depth_range = false; bool extended_device_fault = false; bool texture_compression_bc = false; + bool portability = false; } optional_features_support; friend class render_device; From f946054a3781b2ca1aa6c82a21d7ab169cee7823 Mon Sep 17 00:00:00 2001 From: zeph <35661622+ZephyrCodesStuff@users.noreply.github.com> Date: Sat, 20 Dec 2025 16:43:17 +0100 Subject: [PATCH 91/94] Clans: Emulator and server implementation (#17835) This PR implements the Sony Clans subsystem for the RPCS3 emulator. Used in: - PlayStation Home [NPIA00005] - ? Addresses and closes [#16464](https://github.com/RPCS3/rpcs3/issues/16464). --- The source code for the server is available [here](https://github.com/ZephyrCodesStuff/clans-rs) and is licensed AGPLv3. - It's written in Rust, with my best efforts in readability and documentation. - Every feature not-pertaining to RPCS3 has been feature-gated at compile-time. - Fully Dockerized deployment - Ticket signature verification NOTE: The server is fully compatible with the PS3 console as well, as it follows the PS3 Clans Library specification. --- The emulator code tries its best to follow on the steps of the RPCN client code wherever possible; both in the Client as well as the Settings and Config components. Features: - Clans client implementation in `clans_client.[h,cpp]` - Syscalls implementation in `sceNpClans.[h,cpp]` - A couple of missing structs and/or enum members have been added in `sceNp.h` - Qt GUI for selecting the desired Clans server to use (default: [HTTPS] `clans.rpcs3.net`) - Implemented in `clans_settings_dialog.[h,cpp]` - Prevents modifying during emulator usage - `clans.yml` config file for persistence - Implemented in `clans_config.[h,cpp]` --------- Signed-off-by: zeph <35661622+ZephyrCodesStuff@users.noreply.github.com> Signed-off-by: zeph --- rpcs3/Emu/CMakeLists.txt | 2 + rpcs3/Emu/Cell/Modules/sceNp.h | 1 + rpcs3/Emu/Cell/Modules/sceNp2.cpp | 5 +- rpcs3/Emu/Cell/Modules/sceNpClans.cpp | 579 ++++++++++-- rpcs3/Emu/Cell/Modules/sceNpClans.h | 27 +- rpcs3/Emu/NP/clans_client.cpp | 1143 +++++++++++++++++++++++ rpcs3/Emu/NP/clans_client.h | 129 +++ rpcs3/Emu/NP/clans_config.cpp | 154 +++ rpcs3/Emu/NP/clans_config.h | 29 + rpcs3/Emu/NP/np_handler.cpp | 37 + rpcs3/Emu/NP/np_handler.h | 8 + rpcs3/Emu/NP/np_requests.cpp | 18 +- rpcs3/Emu/system_config.h | 1 + rpcs3/emucore.vcxproj | 4 +- rpcs3/rpcs3.vcxproj | 17 + rpcs3/rpcs3.vcxproj.filters | 12 + rpcs3/rpcs3qt/CMakeLists.txt | 1 + rpcs3/rpcs3qt/clans_settings_dialog.cpp | 187 ++++ rpcs3/rpcs3qt/clans_settings_dialog.h | 30 + rpcs3/rpcs3qt/emu_settings_type.h | 2 + rpcs3/rpcs3qt/main_window.cpp | 13 + rpcs3/rpcs3qt/main_window.ui | 9 + rpcs3/rpcs3qt/settings_dialog.cpp | 5 + rpcs3/rpcs3qt/settings_dialog.ui | 7 + rpcs3/rpcs3qt/tooltips.h | 1 + 25 files changed, 2320 insertions(+), 101 deletions(-) create mode 100644 rpcs3/Emu/NP/clans_client.cpp create mode 100644 rpcs3/Emu/NP/clans_client.h create mode 100644 rpcs3/Emu/NP/clans_config.cpp create mode 100644 rpcs3/Emu/NP/clans_config.h create mode 100644 rpcs3/rpcs3qt/clans_settings_dialog.cpp create mode 100644 rpcs3/rpcs3qt/clans_settings_dialog.h diff --git a/rpcs3/Emu/CMakeLists.txt b/rpcs3/Emu/CMakeLists.txt index 83e8a28398..defa1ef670 100644 --- a/rpcs3/Emu/CMakeLists.txt +++ b/rpcs3/Emu/CMakeLists.txt @@ -442,6 +442,8 @@ target_sources(rpcs3_emu PRIVATE NP/np_requests.cpp NP/signaling_handler.cpp NP/np_structs_extra.cpp + NP/clans_client.cpp + NP/clans_config.cpp NP/rpcn_client.cpp NP/rpcn_config.cpp NP/rpcn_countries.cpp diff --git a/rpcs3/Emu/Cell/Modules/sceNp.h b/rpcs3/Emu/Cell/Modules/sceNp.h index 12ca388ba2..6f29a2f8a9 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp.h +++ b/rpcs3/Emu/Cell/Modules/sceNp.h @@ -32,6 +32,7 @@ using SceNpBasicMessageRecvAction = u32; using SceNpClanId = u32; using SceNpClansMessageId = u32; +using SceNpClansMemberRole = u32; using SceNpClansMemberStatus = s32; using SceNpCustomMenuIndexMask = u32; diff --git a/rpcs3/Emu/Cell/Modules/sceNp2.cpp b/rpcs3/Emu/Cell/Modules/sceNp2.cpp index 3c33375fa3..820fbdf1c0 100644 --- a/rpcs3/Emu/Cell/Modules/sceNp2.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNp2.cpp @@ -1,4 +1,5 @@ #include "stdafx.h" +#include "Emu/Cell/ErrorCodes.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" @@ -540,7 +541,9 @@ error_code sceNpMatching2GetClanLobbyId(SceNpMatching2ContextId ctxId, SceNpClan return SCE_NP_MATCHING2_ERROR_NOT_INITIALIZED; } - return CELL_OK; + // Returning this rather than `CELL_OK` allows for games to + // not need Matching2 Clans support to connect, when Clans are enabled. + return SCE_NP_MATCHING2_SERVER_ERROR_SERVICE_UNAVAILABLE; } error_code sceNpMatching2GetLobbyMemberDataInternal( diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp index 91d22079fe..64ac967d8e 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.cpp @@ -1,10 +1,14 @@ #include "stdafx.h" #include "Emu/Cell/PPUModule.h" #include "Emu/IdManager.h" +#include "Emu/NP/np_handler.h" +#include "Emu/NP/clans_client.h" #include "sceNp.h" +#include #include "sceNpClans.h" + LOG_CHANNEL(sceNpClans); template<> @@ -14,6 +18,7 @@ void fmt_class_string::format(std::string& out, u64 arg) { switch (error) { + STR_CASE(SCE_NP_CLANS_SUCCESS); STR_CASE(SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_INITIALIZED); STR_CASE(SCE_NP_CLANS_ERROR_NOT_SUPPORTED); @@ -94,6 +99,9 @@ error_code sceNpClansInit(vm::cptr commId, vm::cptr client = std::make_shared(); + clans_manager.client = client; clans_manager.is_initialized = true; return CELL_OK; @@ -110,6 +118,7 @@ error_code sceNpClansTerm() return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + clans_manager.client.reset(); clans_manager.is_initialized = false; return CELL_OK; @@ -117,7 +126,7 @@ error_code sceNpClansTerm() error_code sceNpClansCreateRequest(vm::ptr handle, u64 flags) { - sceNpClans.todo("sceNpClansCreateRequest(handle=*0x%x, flags=0x%llx)", handle, flags); + sceNpClans.warning("sceNpClansCreateRequest(handle=*0x%x, flags=0x%x)", handle, flags); if (!g_fxo->get().is_initialized) { @@ -134,36 +143,58 @@ error_code sceNpClansCreateRequest(vm::ptr handle, u64 return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; } + auto& clans_manager = g_fxo->get(); + + s32 reqId = 0; + SceNpClansError res = clans_manager.client->create_request(&reqId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + + *handle = reqId; + return CELL_OK; } -error_code sceNpClansDestroyRequest(vm::ptr handle) +error_code sceNpClansDestroyRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansDestroyRequest(handle=*0x%x)", handle); + sceNpClans.warning("sceNpClansDestroyRequest(handle=*0x%x)", handle); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + + SceNpClansError res = clans_manager.client->destroy_request(handle); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansAbortRequest(vm::ptr handle) +error_code sceNpClansAbortRequest(SceNpClansRequestHandle handle) { - sceNpClans.todo("sceNpClansAbortRequest(handle=*0x%x)", handle); + sceNpClans.warning("sceNpClansAbortRequest(handle=*0x%x)", handle); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + clans_manager.client->destroy_request(handle); + return CELL_OK; } -error_code sceNpClansCreateClan(vm::ptr handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) +error_code sceNpClansCreateClan(SceNpClansRequestHandle handle, vm::cptr name, vm::cptr tag, vm::ptr clanId) { - sceNpClans.todo("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); + sceNpClans.warning("sceNpClansCreateClan(handle=*0x%x, name=%s, tag=%s, clanId=*0x%x)", handle, name, tag, clanId); if (!g_fxo->get().is_initialized) { @@ -180,31 +211,60 @@ error_code sceNpClansCreateClan(vm::ptr handle, vm::cpt return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + std::string name_str; + vm::read_string(name.addr(), SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH, name_str); + + std::string tag_str; + vm::read_string(tag.addr(), SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH, tag_str); + + SceNpClansError res = clans_manager.client->create_clan(nph, handle, name_str, tag_str, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansDisbandClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansDisbandClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); + sceNpClans.warning("sceNpClansDisbandClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + if (!clanId) + { + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError res = clans_manager.client->disband_dlan(nph, handle, clanId); + if (res != SCE_NP_CLANS_SUCCESS) + { + return res; + } + return CELL_OK; } -error_code sceNpClansGetClanList(vm::ptr handle, vm::cptr paging, vm::ptr clanList, vm::ptr pageResult) +error_code sceNpClansGetClanList(SceNpClansRequestHandle handle, vm::cptr paging, vm::ptr clanList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetClanList(handle=*0x%x, paging=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, clanList, pageResult); + sceNpClans.warning("sceNpClansGetClanList(handle=*0x%x, paging=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, clanList, pageResult); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !clanList)) // TODO: confirm + if (!pageResult || (paging && !clanList)) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -217,10 +277,35 @@ error_code sceNpClansGetClanList(vm::ptr handle, vm::cp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansEntry host_clanList[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_clan_list(nph, handle, &host_paging, host_clanList, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (clanList && host_pageResult.count > 0) + { + std::memcpy(clanList.get_ptr(), host_clanList, sizeof(SceNpClansEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetClanListByNpId(vm::ptr handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansGetClanListByNpId(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr npid, vm::ptr clanList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansGetClanListByNpId(handle=*0x%x, paging=*0x%x, npid=*0x%x, clanList=*0x%x, pageResult=*0x%x)", handle, paging, npid, clanList, pageResult); @@ -245,7 +330,8 @@ error_code sceNpClansGetClanListByNpId(vm::ptr handle, return CELL_OK; } -error_code sceNpClansSearchByProfile(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +// TODO: seems to not be needed, even by the PS3..? +error_code sceNpClansSearchByProfile(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { sceNpClans.todo("sceNpClansSearchByProfile(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); @@ -270,9 +356,9 @@ error_code sceNpClansSearchByProfile(vm::ptr handle, vm return CELL_OK; } -error_code sceNpClansSearchByName(vm::ptr handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) +error_code sceNpClansSearchByName(SceNpClansRequestHandle handle, vm::cptr paging, vm::cptr search, vm::ptr results, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansSearchByName(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); + sceNpClans.warning("sceNpClansSearchByName(handle=*0x%x, paging=*0x%x, search=*0x%x, results=*0x%x, pageResult=*0x%x)", handle, paging, search, results, pageResult); if (!g_fxo->get().is_initialized) { @@ -292,12 +378,38 @@ error_code sceNpClansSearchByName(vm::ptr handle, vm::c } } + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansSearchableName host_search = {}; + std::memcpy(&host_search, search.get_ptr(), sizeof(SceNpClansSearchableName)); + + SceNpClansClanBasicInfo host_results[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->clan_search(handle, &host_paging, &host_search, host_results, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (results && host_pageResult.count > 0) + { + std::memcpy(results.get_ptr(), host_results, sizeof(SceNpClansClanBasicInfo) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpClanId clanId, vm::ptr info) +error_code sceNpClansGetClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr info) { - sceNpClans.todo("sceNpClansGetClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansGetClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -310,12 +422,24 @@ error_code sceNpClansGetClanInfo(vm::ptr handle, SceNpC return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& clans_manager = g_fxo->get(); + + SceNpClansClanInfo host_info = {}; + + SceNpClansError ret = clans_manager.client->get_clan_info(handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(info.get_ptr(), &host_info, sizeof(SceNpClansClanInfo)); + return CELL_OK; } -error_code sceNpClansUpdateClanInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) +error_code sceNpClansUpdateClanInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=%d, info=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateClanInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -328,17 +452,24 @@ error_code sceNpClansUpdateClanInfo(vm::ptr handle, Sce return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableClanInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableClanInfo)); + + SceNpClansError ret = clans_manager.client->update_clan_info(nph, handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } -error_code sceNpClansGetMemberList(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, SceNpClansMemberStatus status, vm::ptr memList, vm::ptr pageResult) +error_code sceNpClansGetMemberList(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, SceNpClansMemberStatus status, vm::ptr memList, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetMemberList(handle=*0x%x, clanId=%d, paging=*0x%x, status=%d, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); + sceNpClans.warning("sceNpClansGetMemberList(handle=*0x%x, clanId=*0x%x, paging=*0x%x, status=0x%x, memList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, status, memList, pageResult); if (!g_fxo->get().is_initialized) { @@ -358,12 +489,36 @@ error_code sceNpClansGetMemberList(vm::ptr handle, SceN } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMemberEntry host_memList_addr[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_member_list(nph, handle, clanId, &host_paging, status, host_memList_addr, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (memList && host_pageResult.count > 0) + { + std::memcpy(memList.get_ptr(), host_memList_addr, sizeof(SceNpClansMemberEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansGetMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::ptr memInfo) +error_code sceNpClansGetMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::ptr memInfo) { - sceNpClans.todo("sceNpClansGetMemberInfo(handle=*0x%x, clanId=%d, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); + sceNpClans.warning("sceNpClansGetMemberInfo(handle=*0x%x, clanId=*0x%x, npid=*0x%x, memInfo=*0x%x)", handle, clanId, npid, memInfo); if (!g_fxo->get().is_initialized) { @@ -375,12 +530,28 @@ error_code sceNpClansGetMemberInfo(vm::ptr handle, SceN return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMemberEntry host_memInfo = {}; + + SceNpClansError ret = clans_manager.client->get_member_info(nph, handle, clanId, host_npid, &host_memInfo); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + std::memcpy(memInfo.get_ptr(), &host_memInfo, sizeof(SceNpClansMemberEntry)); + return CELL_OK; } -error_code sceNpClansUpdateMemberInfo(vm::ptr handle, SceNpClanId clanId, vm::cptr info) +error_code sceNpClansUpdateMemberInfo(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr info) { - sceNpClans.todo("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=%d, memInfo=*0x%x)", handle, clanId, info); + sceNpClans.warning("sceNpClansUpdateMemberInfo(handle=*0x%x, clanId=*0x%x, info=*0x%x)", handle, clanId, info); if (!g_fxo->get().is_initialized) { @@ -389,21 +560,27 @@ error_code sceNpClansUpdateMemberInfo(vm::ptr handle, S if (!info) { - // TODO: add more checks for info return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - //if (info->something > X) - //{ - // return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; - //} + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansUpdatableMemberInfo host_info = {}; + std::memcpy(&host_info, info.get_ptr(), sizeof(SceNpClansUpdatableMemberInfo)); + + SceNpClansError ret = clans_manager.client->update_member_info(nph, handle, clanId, &host_info); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } return CELL_OK; } -error_code sceNpClansChangeMemberRole(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, u32 role) +error_code sceNpClansChangeMemberRole(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, u32 role) { - sceNpClans.todo("sceNpClansChangeMemberRole(handle=*0x%x, clanId=%d, npid=*0x%x, role=%d)", handle, clanId, npid, role); + sceNpClans.warning("sceNpClansChangeMemberRole(handle=*0x%x, clanId=*0x%x, npid=*0x%x, role=0x%x)", handle, clanId, npid, role); if (!g_fxo->get().is_initialized) { @@ -415,10 +592,23 @@ error_code sceNpClansChangeMemberRole(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->change_member_role(nph, handle, clanId, host_npid, static_cast(role)); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, vm::ptr enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansGetAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::ptr enable) { sceNpClans.todo("sceNpClansGetAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=*0x%x)", handle, clanId, enable); @@ -435,7 +625,8 @@ error_code sceNpClansGetAutoAcceptStatus(vm::ptr handle return CELL_OK; } -error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr handle, SceNpClanId clanId, b8 enable) +// TODO: no struct currently implements `autoAccept` as a field +error_code sceNpClansUpdateAutoAcceptStatus(SceNpClansRequestHandle handle, SceNpClanId clanId, b8 enable) { sceNpClans.todo("sceNpClansUpdateAutoAcceptStatus(handle=*0x%x, clanId=%d, enable=%d)", handle, clanId, enable); @@ -447,33 +638,51 @@ error_code sceNpClansUpdateAutoAcceptStatus(vm::ptr han return CELL_OK; } -error_code sceNpClansJoinClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansJoinClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansJoinClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansJoinClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->join_clan(nph, handle, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansLeaveClan(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansLeaveClan(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansLeaveClan(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansLeaveClan(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->leave_clan(nph, handle, clanId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansKickMember(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) +error_code sceNpClansKickMember(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansKickMember(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); + sceNpClans.warning("sceNpClansKickMember(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -493,12 +702,30 @@ error_code sceNpClansKickMember(vm::ptr handle, SceNpCl } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->kick_member(nph, handle, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) +error_code sceNpClansSendInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message) { - sceNpClans.todo("sceNpClansSendInvitation(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); + sceNpClans.warning("sceNpClansSendInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x)", handle, clanId, npid, message); if (!g_fxo->get().is_initialized) { @@ -518,12 +745,30 @@ error_code sceNpClansSendInvitation(vm::ptr handle, Sce } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_invitation(nph, handle, clanId, host_npid, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansCancelInvitation(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansCancelInvitation(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid) { - sceNpClans.todo("sceNpClansCancelInvitation(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansCancelInvitation(handle=*0x%x, clanId=*0x%x, npid=*0x%x)", handle, clanId, npid); if (!g_fxo->get().is_initialized) { @@ -535,12 +780,24 @@ error_code sceNpClansCancelInvitation(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->cancel_invitation(nph, handle, clanId, host_npid); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendInvitationResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr message, b8 accept) +error_code sceNpClansSendInvitationResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr message, b8 accept) { - sceNpClans.todo("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=%d, message=*0x%x, accept=%d)", handle, clanId, message, accept); + sceNpClans.warning("sceNpClansSendInvitationResponse(handle=*0x%x, clanId=*0x%x, message=*0x%x, accept=%d)", handle, clanId, message, accept); if (!g_fxo->get().is_initialized) { @@ -555,12 +812,32 @@ error_code sceNpClansSendInvitationResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_invitation_response(nph, handle, clanId, &host_message, accept); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendMembershipRequest(vm::ptr handle, u32 clanId, vm::cptr message) +error_code sceNpClansSendMembershipRequest(SceNpClansRequestHandle handle, u32 clanId, vm::cptr message) { - sceNpClans.todo("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=%d, message=*0x%x)", handle, clanId, message); + sceNpClans.warning("sceNpClansSendMembershipRequest(handle=*0x%x, clanId=*0x%x, message=*0x%x)", handle, clanId, message); if (!g_fxo->get().is_initialized) { @@ -575,24 +852,49 @@ error_code sceNpClansSendMembershipRequest(vm::ptr hand } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->request_membership(nph, handle, clanId, &host_message); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansCancelMembershipRequest(vm::ptr handle, SceNpClanId clanId) +error_code sceNpClansCancelMembershipRequest(SceNpClansRequestHandle handle, SceNpClanId clanId) { - sceNpClans.todo("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=%d)", handle, clanId); + sceNpClans.warning("sceNpClansCancelMembershipRequest(handle=*0x%x, clanId=*0x%x)", handle, clanId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansError ret = clans_manager.client->cancel_request_membership(nph, handle, clanId); + + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansSendMembershipResponse(vm::ptr handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message, b8 allow) +error_code sceNpClansSendMembershipResponse(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr npid, vm::cptr message, b8 allow) { - sceNpClans.todo("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=%d, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); + sceNpClans.warning("sceNpClansSendMembershipResponse(handle=*0x%x, clanId=*0x%x, npid=*0x%x, message=*0x%x, allow=%d)", handle, clanId, npid, message, allow); if (!g_fxo->get().is_initialized) { @@ -612,12 +914,30 @@ error_code sceNpClansSendMembershipResponse(vm::ptr han } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_npid = {}; + std::memcpy(&host_npid, npid.get_ptr(), sizeof(SceNpId)); + + SceNpClansMessage host_message = {}; + if (message) + { + std::memcpy(&host_message, message.get_ptr(), sizeof(SceNpClansMessage)); + } + + SceNpClansError ret = clans_manager.client->send_membership_response(nph, handle, clanId, host_npid, &host_message, allow); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansGetBlacklist(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr bl, vm::ptr pageResult) +error_code sceNpClansGetBlacklist(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr bl, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansGetBlacklist(handle=*0x%x, clanId=%d, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); + sceNpClans.warning("sceNpClansGetBlacklist(handle=*0x%x, clanId=*0x%x, paging=*0x%x, bl=*0x%x, pageResult=*0x%x)", handle, clanId, paging, bl, pageResult); if (!g_fxo->get().is_initialized) { @@ -637,53 +957,101 @@ error_code sceNpClansGetBlacklist(vm::ptr handle, SceNp } } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansBlacklistEntry host_blacklist[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->get_blacklist(nph, handle, clanId, &host_paging, host_blacklist, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (bl && host_pageResult.count > 0) + { + std::memcpy(bl.get_ptr(), host_blacklist, sizeof(SceNpClansBlacklistEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansAddBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansAddBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansAddBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->add_blacklist_entry(nph, handle, clanId, host_member); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansRemoveBlacklistEntry(vm::ptr handle, SceNpClanId clanId, vm::cptr npid) +error_code sceNpClansRemoveBlacklistEntry(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr member) { - sceNpClans.todo("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=%d, npid=*0x%x)", handle, clanId, npid); + sceNpClans.warning("sceNpClansRemoveBlacklistEntry(handle=*0x%x, clanId=*0x%x, member=*0x%x)", handle, clanId, member); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!npid) + if (!member) { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } + auto& nph = g_fxo->get>(); + auto& clans_manager = g_fxo->get(); + + SceNpId host_member = {}; + std::memcpy(&host_member, member.get_ptr(), sizeof(SceNpId)); + + SceNpClansError ret = clans_manager.client->remove_blacklist_entry(nph, handle, clanId, host_member); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansRetrieveAnnouncements(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mlist, vm::ptr pageResult) +error_code sceNpClansRetrieveAnnouncements(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mlist, vm::ptr pageResult) { - sceNpClans.todo("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=%d, paging=*0x%x, mlist=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mlist, pageResult); + sceNpClans.warning("sceNpClansRetrieveAnnouncements(handle=*0x%x, clanId=*0x%x, paging=*0x%x, mlist=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mlist, pageResult); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } - if (!pageResult || (paging && !mlist)) // TODO: confirm + if (!pageResult || (paging && !mlist) || clanId == UINT32_MAX) // TODO: confirm { return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } @@ -696,12 +1064,36 @@ error_code sceNpClansRetrieveAnnouncements(vm::ptr hand } } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansPagingRequest host_paging = {}; + if (paging) + { + std::memcpy(&host_paging, paging.get_ptr(), sizeof(SceNpClansPagingRequest)); + } + + SceNpClansMessageEntry host_announcements[SCE_NP_CLANS_PAGING_REQUEST_PAGE_MAX] = {}; + SceNpClansPagingResult host_pageResult = {}; + + SceNpClansError ret = clans_manager.client->retrieve_announcements(nph, handle, clanId, &host_paging, host_announcements, &host_pageResult); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + if (mlist && host_pageResult.count > 0) + { + std::memcpy(mlist.get_ptr(), host_announcements, sizeof(SceNpClansMessageEntry) * host_pageResult.count); + } + std::memcpy(pageResult.get_ptr(), &host_pageResult, sizeof(SceNpClansPagingResult)); + return CELL_OK; } -error_code sceNpClansPostAnnouncement(vm::ptr handle, SceNpClanId clanId, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) +error_code sceNpClansPostAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, message, data, duration, mId); + sceNpClans.warning("sceNpClansPostAnnouncement(handle=*0x%x, clanId=*0x%x, message=*0x%x, data=*0x%x, duration=*0x%x, mId=*0x%x)", handle, clanId, message, data, duration, mId); if (!g_fxo->get().is_initialized) { @@ -713,32 +1105,57 @@ error_code sceNpClansPostAnnouncement(vm::ptr handle, S return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; } - if (!data) // TODO verify - { - return SCE_NP_CLANS_ERROR_NOT_SUPPORTED; - } - if (strlen(message->body) > SCE_NP_CLANS_ANNOUNCEMENT_MESSAGE_BODY_MAX_LENGTH || strlen(message->subject) > SCE_NP_CLANS_MESSAGE_SUBJECT_MAX_LENGTH) // TODO: correct max? { return SCE_NP_CLANS_ERROR_EXCEEDS_MAX; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansMessage host_announcement = {}; + std::memcpy(&host_announcement, message.get_ptr(), sizeof(SceNpClansMessage)); + + SceNpClansMessageData host_data = {}; + if (data) + { + std::memcpy(&host_data, data.get_ptr(), sizeof(SceNpClansMessageData)); + } + + SceNpClansMessageId host_announcementId = 0; + SceNpClansError ret = clans_manager.client->post_announcement(nph, handle, clanId, &host_announcement, &host_data, duration, &host_announcementId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + + *mId = host_announcementId; + return CELL_OK; } -error_code sceNpClansRemoveAnnouncement(vm::ptr handle, SceNpClanId clanId, SceNpClansMessageId mId) +error_code sceNpClansRemoveAnnouncement(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClansMessageId mId) { - sceNpClans.todo("sceNpClansPostAnnouncement(handle=*0x%x, clanId=%d, mId=%d)", handle, clanId, mId); + sceNpClans.warning("sceNpClansRemoveAnnouncement(handle=*0x%x, clanId=*0x%x, mId=*0x%x)", handle, clanId, mId); if (!g_fxo->get().is_initialized) { return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; } + auto& clans_manager = g_fxo->get(); + auto& nph = g_fxo->get>(); + + SceNpClansError ret = clans_manager.client->delete_announcement(nph, handle, clanId, mId); + if (ret != SCE_NP_CLANS_SUCCESS) + { + return ret; + } + return CELL_OK; } -error_code sceNpClansPostChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) +error_code sceNpClansPostChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr message, vm::cptr data, u32 duration, vm::ptr mId) { sceNpClans.todo("sceNpClansPostChallenge(handle=*0x%x, clanId=%d, targetClan=%d, message=*0x%x, data=*0x%x, duration=%d, mId=*0x%x)", handle, clanId, targetClan, message, data, duration, mId); @@ -765,7 +1182,7 @@ error_code sceNpClansPostChallenge(vm::ptr handle, SceN return CELL_OK; } -error_code sceNpClansRetrievePostedChallenges(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrievePostedChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrievePostedChallenges(handle=*0x%x, clanId=%d, targetClan=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, targetClan, paging, mList, pageResult); @@ -790,7 +1207,7 @@ error_code sceNpClansRetrievePostedChallenges(vm::ptr h return CELL_OK; } -error_code sceNpClansRemovePostedChallenge(vm::ptr handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) +error_code sceNpClansRemovePostedChallenge(SceNpClansRequestHandle handle, SceNpClanId clanId, SceNpClanId targetClan, SceNpClansMessageId mId) { sceNpClans.todo("sceNpClansRemovePostedChallenge(handle=*0x%x, clanId=%d, targetClan=%d, mId=%d)", handle, clanId, targetClan, mId); @@ -802,7 +1219,7 @@ error_code sceNpClansRemovePostedChallenge(vm::ptr hand return CELL_OK; } -error_code sceNpClansRetrieveChallenges(vm::ptr handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) +error_code sceNpClansRetrieveChallenges(SceNpClansRequestHandle handle, SceNpClanId clanId, vm::cptr paging, vm::ptr mList, vm::ptr pageResult) { sceNpClans.todo("sceNpClansRetrieveChallenges(handle=*0x%x, clanId=%d, paging=*0x%x, mList=*0x%x, pageResult=*0x%x)", handle, clanId, paging, mList, pageResult); diff --git a/rpcs3/Emu/Cell/Modules/sceNpClans.h b/rpcs3/Emu/Cell/Modules/sceNpClans.h index f0d1d54630..9b31c87d25 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpClans.h +++ b/rpcs3/Emu/Cell/Modules/sceNpClans.h @@ -5,6 +5,8 @@ // Return codes enum SceNpClansError : u32 { + SCE_NP_CLANS_SUCCESS = CELL_OK, + SCE_NP_CLANS_ERROR_ALREADY_INITIALIZED = 0x80022701, SCE_NP_CLANS_ERROR_NOT_INITIALIZED = 0x80022702, SCE_NP_CLANS_ERROR_NOT_SUPPORTED = 0x80022703, @@ -138,7 +140,7 @@ enum }; // Request handle for clan API -using SceNpClansRequestHandle = vm::ptr; +using SceNpClansRequestHandle = u32; // Paging request structure struct SceNpClansPagingRequest @@ -159,8 +161,8 @@ struct SceNpClansClanBasicInfo { be_t clanId; be_t numMembers; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[2]; }; @@ -197,7 +199,7 @@ struct SceNpClansSearchableProfile be_t intAttr2SearchOp; be_t intAttr3SearchOp; be_t binAttr1SearchOp; - s8 tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; + char tag[SCE_NP_CLANS_CLAN_TAG_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -205,7 +207,7 @@ struct SceNpClansSearchableProfile struct SceNpClansSearchableName { be_t nameSearchOp; - s8 name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; + char name[SCE_NP_CLANS_CLAN_NAME_MAX_LENGTH + 1]; u8 reserved[3]; }; @@ -213,7 +215,7 @@ struct SceNpClansSearchableName struct SceNpClansUpdatableClanInfo { be_t fields; - s8 description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; + char description[SCE_NP_CLANS_CLAN_DESCRIPTION_MAX_LENGTH + 1]; SceNpClansSearchableAttr attr; u8 binData1; be_t binData1Size; @@ -233,8 +235,8 @@ struct SceNpClansUpdatableMemberInfo be_t fields; u8 binData1; be_t binData1Size; - u8 binAttr1[SCE_NP_CLANS_CLAN_BINARY_ATTRIBUTE1_MAX_SIZE + 1]; - s8 description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; + u8 binAttr1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE]; + char description[SCE_NP_CLANS_MEMBER_DESCRIPTION_MAX_LENGTH + 1]; b8 allowMsg; u8 reserved[3]; }; @@ -271,7 +273,7 @@ struct SceNpClansMessageEntry SceNpClansMessage message; SceNpClansMessageData data; SceNpId npid; - u8 reserved[4]; + SceNpClanId postedBy; }; // Blacklist entry structure @@ -280,10 +282,3 @@ struct SceNpClansBlacklistEntry SceNpId entry; SceNpId registeredBy; }; - -// fxm objects - -struct sce_np_clans_manager -{ - atomic_t is_initialized = false; -}; diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp new file mode 100644 index 0000000000..dd2d926d24 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -0,0 +1,1143 @@ +#include "stdafx.h" + +#include +#include + +// wolfssl uses old-style casts which break clang builds +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wextern-c-compat" +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#include + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include +#include +#include + +#include "Emu/Cell/Modules/sceNp.h" +#include "Emu/Cell/Modules/sceNpClans.h" +#include "Emu/NP/clans_client.h" +#include "Emu/NP/clans_config.h" +#include "Emu/NP/np_helpers.h" +#include "Emu/system_config.h" + +LOG_CHANNEL(clan_log, "clans"); + + +const char* REQ_TYPE_FUNC = "func"; +const char* REQ_TYPE_SEC = "sec"; + +constexpr const char JID_FORMAT[] = "%s@un.br.np.playstation.net"; + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestType::FUNC: return "func"; + case clan::ClanRequestType::SEC: return "sec"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanManagerOperationType::VIEW: return "view"; + case clan::ClanManagerOperationType::UPDATE: return "update"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanSearchFilterOperator::Equal: return "eq"; + case clan::ClanSearchFilterOperator::NotEqual: return "ne"; + case clan::ClanSearchFilterOperator::GreaterThan: return "gt"; + case clan::ClanSearchFilterOperator::GreaterThanOrEqual: return "ge"; + case clan::ClanSearchFilterOperator::LessThan: return "lt"; + case clan::ClanSearchFilterOperator::LessThanOrEqual: return "le"; + case clan::ClanSearchFilterOperator::Like: return "lk"; + } + + return unknown; + }); +} + +template <> +void fmt_class_string::format(std::string& out, u64 arg) +{ + format_enum(out, arg, [](auto value) + { + switch (value) + { + case clan::ClanRequestAction::GetClanList: return "get_clan_list"; + case clan::ClanRequestAction::GetClanInfo: return "get_clan_info"; + case clan::ClanRequestAction::GetMemberInfo: return "get_member_info"; + case clan::ClanRequestAction::GetMemberList: return "get_member_list"; + case clan::ClanRequestAction::GetBlacklist: return "get_blacklist"; + case clan::ClanRequestAction::RecordBlacklistEntry: return "record_blacklist_entry"; + case clan::ClanRequestAction::DeleteBlacklistEntry: return "delete_blacklist_entry"; + case clan::ClanRequestAction::ClanSearch: return "clan_search"; + case clan::ClanRequestAction::CreateClan: return "create_clan"; + case clan::ClanRequestAction::DisbandClan: return "disband_clan"; + case clan::ClanRequestAction::RequestMembership: return "request_membership"; + case clan::ClanRequestAction::CancelRequestMembership: return "cancel_request_membership"; + case clan::ClanRequestAction::AcceptMembershipRequest: return "accept_membership_request"; + case clan::ClanRequestAction::DeclineMembershipRequest: return "decline_membership_request"; + case clan::ClanRequestAction::SendInvitation: return "send_invitation"; + case clan::ClanRequestAction::CancelInvitation: return "cancel_invitation"; + case clan::ClanRequestAction::AcceptInvitation: return "accept_invitation"; + case clan::ClanRequestAction::DeclineInvitation: return "decline_invitation"; + case clan::ClanRequestAction::UpdateMemberInfo: return "update_member_info"; + case clan::ClanRequestAction::UpdateClanInfo: return "update_clan_info"; + case clan::ClanRequestAction::JoinClan: return "join_clan"; + case clan::ClanRequestAction::LeaveClan: return "leave_clan"; + case clan::ClanRequestAction::KickMember: return "kick_member"; + case clan::ClanRequestAction::ChangeMemberRole: return "change_member_role"; + case clan::ClanRequestAction::RetrieveAnnouncements: return "retrieve_announcements"; + case clan::ClanRequestAction::PostAnnouncement: return "post_announcement"; + case clan::ClanRequestAction::DeleteAnnouncement: return "delete_announcement"; + } + + return unknown; + }); +} + +namespace clan +{ + struct curl_memory + { + char* response; + size_t size; + }; + + size_t clans_client::curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp) + { + size_t realsize = size * nmemb; + std::vector* mem = static_cast*>(clientp); + + size_t old_size = mem->size(); + mem->resize(old_size + realsize); + memcpy(mem->data() + old_size, data, realsize); + + return realsize; + } + + struct clan_request_ctx + { + clan_request_ctx() + { + curl = curl_easy_init(); + if (curl) + { + curl_easy_setopt(curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_NATIVE_CA); + } + } + + ~clan_request_ctx() + { + if (curl) + { + curl_easy_cleanup(curl); + curl = nullptr; + } + } + + CURL* curl = nullptr; + + // TODO: this was arbitrarily chosen -- see if there's a real amount + static const u32 SCE_NP_CLANS_MAX_CTX_NUM = 16; + + static const u32 id_base = 0xA001; + static const u32 id_step = 1; + static const u32 id_count = SCE_NP_CLANS_MAX_CTX_NUM; + SAVESTATE_INIT_POS(55); + }; + + clans_client::clans_client() + { + g_cfg_clans.load(); + } + + clans_client::~clans_client() + { + idm::clear(); + } + + SceNpClansError clans_client::create_request(s32* req_id) + { + if (!g_cfg.net.clans_enabled) + { + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + } + + const s32 id = idm::make(); + + if (id == id_manager::id_traits::invalid) + { + return SceNpClansError::SCE_NP_CLANS_ERROR_EXCEEDS_MAX; + } + + auto ctx = idm::get_unlocked(id); + if (!ctx || !ctx->curl) + { + idm::remove(id); + return SceNpClansError::SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + } + + *req_id = id; + + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::destroy_request(u32 req_id) + { + if (idm::remove(req_id)) + return SceNpClansError::SCE_NP_CLANS_SUCCESS; + + return SceNpClansError::SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + } + + SceNpClansError clans_client::send_request(u32 req_id, ClanRequestAction action, ClanManagerOperationType op_type, pugi::xml_document* xml_body, pugi::xml_document* out_response) + { + auto ctx = idm::get_unlocked(req_id); + + if (!ctx || !ctx->curl) + return SCE_NP_CLANS_ERROR_NOT_INITIALIZED; + + CURL* curl = ctx->curl; + + ClanRequestType req_type = ClanRequestType::FUNC; + pugi::xml_node clan = xml_body->child("clan"); + if (clan && clan.child("ticket")) + { + req_type = ClanRequestType::SEC; + } + + std::string host = g_cfg_clans.get_host(); + std::string protocol = g_cfg_clans.get_use_https() ? "https" : "http"; + std::string url = fmt::format("%s://%s/clan_manager_%s/%s/%s", protocol, host, op_type, req_type, action); + + std::ostringstream oss; + xml_body->save(oss, "\t", 8U); + + std::string xml = oss.str(); + + char err_buf[CURL_ERROR_SIZE]; + err_buf[0] = '\0'; + + std::vector response_buffer; + + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_write_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_buffer); + curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, err_buf); + + // WARN: This disables certificate verification! + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); + + curl_easy_setopt(curl, CURLOPT_POST, 1); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, xml.c_str()); + curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, xml.size()); + + CURLcode res = curl_easy_perform(curl); + + if (res != CURLE_OK) + { + out_response = nullptr; + clan_log.error("curl_easy_perform() failed: %s", curl_easy_strerror(res)); + clan_log.error("Error buffer: %s", err_buf); + return SCE_NP_CLANS_ERROR_BAD_REQUEST; + } + + response_buffer.push_back('\0'); + + pugi::xml_parse_result xml_res = out_response->load_string(response_buffer.data()); + if (!xml_res) + { + clan_log.error("XML parsing failed: %s", xml_res.description()); + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + } + + pugi::xml_node clan_result = out_response->child("clan"); + if (!clan_result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + pugi::xml_attribute result = clan_result.attribute("result"); + if (!result) + return SCE_NP_CLANS_ERROR_BAD_RESPONSE; + + std::string result_str = result.as_string(); + if (result_str != "00") + return static_cast(0x80022800 | std::stoul(result_str, nullptr, 16)); + + return SCE_NP_CLANS_SUCCESS; + } + + std::string clans_client::get_clan_ticket(np::np_handler& nph) + { + // Pretend we failed to get a ticket if the emulator isn't + // connected to RPCN. + if (nph.get_psn_status() != SCE_NP_MANAGER_STATUS_ONLINE) + return ""; + + const auto& npid = nph.get_npid(); + + const char* service_id = CLANS_SERVICE_ID; + const unsigned char* cookie = nullptr; + const u32 cookie_size = 0; + const char* entitlement_id = CLANS_ENTITLEMENT_ID; + const u32 consumed_count = 0; + + // Use the cached ticket if available + np::ticket clan_ticket; + if (!nph.get_clan_ticket_ready()) + { + // If not cached, request a new ticket + nph.req_ticket(0x00020001, &npid, service_id, cookie, cookie_size, entitlement_id, consumed_count); + } + + clan_ticket = nph.get_clan_ticket(); + if (clan_ticket.empty()) + { + clan_log.error("Failed to get clan ticket"); + return ""; + } + + std::vector ticket_bytes(1024); + uint32_t ticket_size = UINT32_MAX; + + Base64_Encode_NoNl(clan_ticket.data(), clan_ticket.size(), ticket_bytes.data(), &ticket_size); + std::string ticket_str = std::string(reinterpret_cast(ticket_bytes.data()), ticket_size); + + return ticket_str; + } + +#pragma region Outgoing API Requests + SceNpClansError clans_client::get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info"), i++) + { + pugi::xml_attribute id = info.attribute("id"); + uint32_t clan_id = id.as_uint(); + + pugi::xml_node name = info.child("name"); + std::string name_str = name.text().as_string(); + + pugi::xml_node tag = info.child("tag"); + std::string tag_str = tag.text().as_string(); + + pugi::xml_node role = info.child("role"); + int32_t role_int = role.text().as_uint(); + + pugi::xml_node status = info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node onlinename = info.child("onlinename"); + std::string onlinename_str = onlinename.text().as_string(); + + pugi::xml_node members = info.child("members"); + uint32_t members_int = members.text().as_uint(); + + SceNpClansEntry entry = SceNpClansEntry{ + .info = SceNpClansClanBasicInfo{ + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }, + .role = static_cast(role_int), + .status = static_cast(status_int)}; + + strcpy_trunc(entry.info.name, name_str); + strcpy_trunc(entry.info.tag, tag_str); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult{ + .count = results_count, + .total = total_count}; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetClanInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node info = clan_result.child("info"); + + std::string name_str = info.child("name").text().as_string(); + std::string tag_str = info.child("tag").text().as_string(); + uint32_t members_int = info.child("members").text().as_uint(); + std::string date_created_str = info.child("date-created").text().as_string(); + std::string description_str = info.child("description").text().as_string(); + + *clan_info = SceNpClansClanInfo{ + .info = SceNpClansClanBasicInfo{ + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + }, + .updatable = SceNpClansUpdatableClanInfo{ + .description = "", + }}; + + strcpy_trunc(clan_info->info.name, name_str); + strcpy_trunc(clan_info->info.tag, tag_str); + strcpy_trunc(clan_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberInfo, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node info = clan_result.child("info"); + + pugi::xml_attribute jid = info.attribute("jid"); + std::string npid_str = jid.as_string(); + std::string username = fmt::split(npid_str, {"@"})[0]; + + SceNpId npid; + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + pugi::xml_node role = info.child("role"); + uint32_t role_int = role.text().as_uint(); + + pugi::xml_node status = info.child("status"); + uint32_t status_int = status.text().as_uint(); + + pugi::xml_node description = info.child("description"); + std::string description_str = description.text().as_string(); + + *mem_info = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + .updatable = SceNpClansUpdatableMemberInfo{ + .description = "", + } + }; + + strcpy_trunc(mem_info->updatable.description, description_str); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus /*status*/, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetMemberList, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info")) + { + std::string npid_str = info.attribute("jid").as_string(); + std::string username = fmt::split(npid_str, {"@"})[0]; + + SceNpId npid; + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + uint32_t role_int = info.child("role").text().as_uint(); + uint32_t status_int = info.child("status").text().as_uint(); + std::string description_str = info.child("description").text().as_string(); + + SceNpClansMemberEntry entry = SceNpClansMemberEntry + { + .npid = npid, + .role = static_cast(role_int), + .status = static_cast(status_int), + }; + + strcpy_trunc(entry.updatable.description, description_str); + + mem_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::GetBlacklist, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("entry"); node; node = node.next_sibling("entry")) + { + pugi::xml_node id = node.child("jid"); + std::string npid_str = id.text().as_string(); + + SceNpId npid = {}; + np::string_to_npid(npid_str.c_str(), npid); + + SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry + { + .entry = npid, + }; + + bl[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RecordBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::DeleteBlacklistEntry, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result) + { + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_node filter = clan.append_child("filter"); + pugi::xml_node name = filter.append_child("name"); + + std::string op_name = fmt::format("%s", static_cast(static_cast(search->nameSearchOp))); + name.append_attribute("op").set_value(op_name.c_str()); + name.append_attribute("value").set_value(search->name); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::ClanSearch, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("info"); node; node = node.next_sibling("info")) + { + uint32_t clan_id = node.attribute("id").as_uint(); + std::string name_str = node.child("name").text().as_string(); + std::string tag_str = node.child("tag").text().as_string(); + uint32_t members_int = node.child("members").text().as_uint(); + + SceNpClansClanBasicInfo entry = SceNpClansClanBasicInfo + { + .clanId = clan_id, + .numMembers = members_int, + .name = "", + .tag = "", + .reserved = {0, 0}, + }; + + strcpy_trunc(entry.name, name_str); + strcpy_trunc(entry.tag, tag_str); + + clan_list[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + + clan.append_child("name").text().set(name.data()); + clan.append_child("tag").text().set(tag.data()); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::CreateClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_attribute id = clan_result.attribute("id"); + uint32_t clan_id_int = id.as_uint(); + + *clan_id = clan_id_int; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::DisbandClan, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::RequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelRequestMembership, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/, b8 allow) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, allow ? ClanRequestAction::AcceptMembershipRequest : ClanRequestAction::DeclineMembershipRequest, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::SendInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::CancelInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* /*message*/, b8 accept) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, accept ? ClanRequestAction::AcceptInvitation : ClanRequestAction::DeclineInvitation, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_node role = clan.append_child("onlinename"); + role.text().set(nph.get_npid().handle.data); + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_node status = clan.append_child("bin-attr1"); + + byte bin_attr_1[SCE_NP_CLANS_MEMBER_BINARY_ATTRIBUTE1_MAX_SIZE * 2 + 1] = {0}; + uint32_t bin_attr_1_size = UINT32_MAX; + Base64_Encode_NoNl(info->binAttr1, info->binData1Size, bin_attr_1, &bin_attr_1_size); + + if (bin_attr_1_size == UINT32_MAX) + return SCE_NP_CLANS_ERROR_INVALID_ARGUMENT; + + // `reinterpret_cast` used to let the compiler select the correct overload of `set` + status.text().set(reinterpret_cast(bin_attr_1)); + + pugi::xml_node allow_msg = clan.append_child("allow-msg"); + allow_msg.text().set(static_cast(info->allowMsg)); + + pugi::xml_node size = clan.append_child("size"); + size.text().set(info->binData1Size); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::UpdateMemberInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + // TODO: implement binary and integer attributes (not implemented in server yet) + + pugi::xml_node description = clan.append_child("description"); + description.text().set(info->description); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::UpdateClanInfo, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::JoinClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::LeaveClan, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* /*message*/) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::KickMember, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + std::string jid_str = fmt::format(JID_FORMAT, np_id.handle.data); + clan.append_child("jid").text().set(jid_str.c_str()); + + pugi::xml_node role_node = clan.append_child("role"); + role_node.text().set(static_cast(role)); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::ChangeMemberRole, ClanManagerOperationType::UPDATE, &doc, &response); + } + + SceNpClansError clans_client::retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("start").text().set(paging->startPos); + clan.append_child("max").text().set(paging->max); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::RetrieveAnnouncements, ClanManagerOperationType::VIEW, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node list = clan_result.child("list"); + + pugi::xml_attribute results = list.attribute("results"); + uint32_t results_count = results.as_uint(); + + pugi::xml_attribute total = list.attribute("total"); + uint32_t total_count = total.as_uint(); + + int i = 0; + for (pugi::xml_node node = list.child("msg-info"); node; node = node.next_sibling("msg-info")) + { + pugi::xml_attribute id = node.attribute("id"); + uint32_t msg_id = id.as_uint(); + + std::string subject_str = node.child("subject").text().as_string(); + std::string msg_str = node.child("msg").text().as_string(); + std::string npid_str = node.child("jid").text().as_string(); + std::string msg_date = node.child("msg-date").text().as_string(); + + SceNpId npid; + std::string username = fmt::split(npid_str, {"@"})[0]; + + if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + { + npid = nph.get_npid(); + } + else + { + np::string_to_npid(npid_str, npid); + } + + // TODO: implement `binData` and `fromId` + + SceNpClansMessageEntry entry = SceNpClansMessageEntry + { + .mId = msg_id, + .message = SceNpClansMessage { + .subject = "", + .body = "", + }, + .npid = npid, + .postedBy = clan_id, + }; + + strcpy_trunc(entry.message.subject, subject_str); + strcpy_trunc(entry.message.body, msg_str); + + announcements[i] = entry; + i++; + } + + *page_result = SceNpClansPagingResult + { + .count = results_count, + .total = total_count + }; + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* /*data*/, u32 duration, SceNpClansMessageId* msg_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + + pugi::xml_node subject = clan.append_child("subject"); + subject.text().set(announcement->subject); + + pugi::xml_node msg = clan.append_child("msg"); + msg.text().set(announcement->body); + + pugi::xml_node expire_date = clan.append_child("expire-date"); + expire_date.text().set(duration); + + pugi::xml_document response = pugi::xml_document(); + SceNpClansError clan_res = send_request(req_id, ClanRequestAction::PostAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + + if (clan_res != SCE_NP_CLANS_SUCCESS) + return clan_res; + + pugi::xml_node clan_result = response.child("clan"); + pugi::xml_node msg_id_node = clan_result.child("id"); + *msg_id = msg_id_node.text().as_uint(); + + return SCE_NP_CLANS_SUCCESS; + } + + SceNpClansError clans_client::delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id) + { + std::string ticket = get_clan_ticket(nph); + if (ticket.empty()) + return SCE_NP_CLANS_ERROR_SERVICE_UNAVAILABLE; + + pugi::xml_document doc = pugi::xml_document(); + pugi::xml_node clan = doc.append_child("clan"); + clan.append_child("ticket").text().set(ticket.c_str()); + clan.append_child("id").text().set(clan_id); + clan.append_child("msg-id").text().set(announcement_id); + + pugi::xml_document response = pugi::xml_document(); + return send_request(req_id, ClanRequestAction::DeleteAnnouncement, ClanManagerOperationType::UPDATE, &doc, &response); + } +} +#pragma endregion diff --git a/rpcs3/Emu/NP/clans_client.h b/rpcs3/Emu/NP/clans_client.h new file mode 100644 index 0000000000..68ed4a1cf1 --- /dev/null +++ b/rpcs3/Emu/NP/clans_client.h @@ -0,0 +1,129 @@ +#pragma once + +#include +#include + +#include +#include +#include + +inline const char* CLANS_ENTITLEMENT_ID = "NPWR00432_00"; +inline const char* CLANS_SERVICE_ID = "IV0001-NPXS01001_00"; + +namespace clan +{ + enum class ClanManagerOperationType + { + VIEW, + UPDATE + }; + + enum class ClanRequestType + { + FUNC, + SEC + }; + + enum class ClanSearchFilterOperator : u8 + { + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + Like, + }; + + enum class ClanRequestAction + { + GetClanList, + GetClanInfo, + GetMemberInfo, + GetMemberList, + GetBlacklist, + RecordBlacklistEntry, + DeleteBlacklistEntry, + ClanSearch, + CreateClan, + DisbandClan, + RequestMembership, + CancelRequestMembership, + AcceptMembershipRequest, + DeclineMembershipRequest, + SendInvitation, + CancelInvitation, + AcceptInvitation, + DeclineInvitation, + UpdateMemberInfo, + UpdateClanInfo, + JoinClan, + LeaveClan, + KickMember, + ChangeMemberRole, + RetrieveAnnouncements, + PostAnnouncement, + DeleteAnnouncement + }; + + class clans_client + { + private: + + + static size_t curl_write_callback(void* data, size_t size, size_t nmemb, void* clientp); + SceNpClansError send_request(u32 reqId, ClanRequestAction action, ClanManagerOperationType type, pugi::xml_document* xml_body, pugi::xml_document* out_response); + + /// @brief Forge and get a V2.1 Ticket for clan operations + std::string get_clan_ticket(np::np_handler& nph); + + public: + clans_client(); + ~clans_client(); + + SceNpClansError create_request(s32* req_id); + SceNpClansError destroy_request(u32 req_id); + + SceNpClansError clan_search(u32 req_id, SceNpClansPagingRequest* paging, SceNpClansSearchableName* search, SceNpClansClanBasicInfo* clan_list, SceNpClansPagingResult* page_result); + + SceNpClansError create_clan(np::np_handler& nph, u32 req_id, std::string_view name, std::string_view tag, vm::ptr clan_id); + SceNpClansError disband_dlan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError get_clan_list(np::np_handler& nph, u32 req_id, SceNpClansPagingRequest* paging, SceNpClansEntry* clan_list, SceNpClansPagingResult* page_result); + SceNpClansError get_clan_info(u32 req_id, SceNpClanId clan_id, SceNpClansClanInfo* clan_info); + + SceNpClansError get_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberEntry* mem_info); + SceNpClansError get_member_list(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMemberStatus status, SceNpClansMemberEntry* mem_list, SceNpClansPagingResult* page_result); + + SceNpClansError get_blacklist(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansBlacklistEntry* bl, SceNpClansPagingResult* page_result); + SceNpClansError add_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError remove_blacklist_entry(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + + SceNpClansError request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message); + SceNpClansError cancel_request_membership(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError send_membership_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message, b8 allow); + + SceNpClansError send_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError cancel_invitation(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id); + SceNpClansError send_invitation_response(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* message, b8 accept); + + SceNpClansError join_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + SceNpClansError leave_clan(np::np_handler& nph, u32 req_id, SceNpClanId clan_id); + + SceNpClansError update_member_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableMemberInfo* info); + SceNpClansError update_clan_info(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansUpdatableClanInfo* info); + + SceNpClansError kick_member(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMessage* message); + SceNpClansError change_member_role(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpId np_id, SceNpClansMemberRole role); + + SceNpClansError retrieve_announcements(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansPagingRequest* paging, SceNpClansMessageEntry* announcements, SceNpClansPagingResult* page_result); + SceNpClansError post_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessage* announcement, SceNpClansMessageData* data, u32 duration, SceNpClansMessageId* announcement_id); + SceNpClansError delete_announcement(np::np_handler& nph, u32 req_id, SceNpClanId clan_id, SceNpClansMessageId announcement_id); + }; +} // namespace clan + +struct sce_np_clans_manager +{ + atomic_t is_initialized = false; + std::shared_ptr client; +}; diff --git a/rpcs3/Emu/NP/clans_config.cpp b/rpcs3/Emu/NP/clans_config.cpp new file mode 100644 index 0000000000..08fd09c4b2 --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.cpp @@ -0,0 +1,154 @@ +#include "Utilities/StrUtil.h" +#include "stdafx.h" +#include "clans_config.h" +#include "Utilities/File.h" + +cfg_clans g_cfg_clans; + +LOG_CHANNEL(clans_config_log, "clans_config"); + +void cfg_clans::load() +{ + const std::string path = cfg_clans::get_path(); + + fs::file cfg_file(path, fs::read); + if (cfg_file) + { + clans_config_log.notice("Loading Clans config. Path: %s", path); + from_string(cfg_file.to_string()); + } + else + { + clans_config_log.notice("Clans config missing. Using default settings. Path: %s", path); + from_default(); + } +} + +void cfg_clans::save() const +{ +#ifdef _WIN32 + const std::string path_to_cfg = fs::get_config_dir(true); + if (!fs::create_path(path_to_cfg)) + { + clans_config_log.error("Could not create path: %s", path_to_cfg); + } +#endif + + const std::string path = cfg_clans::get_path(); + + if (!cfg::node::save(path)) + { + clans_config_log.error("Could not save config: %s (error=%s)", path, fs::g_tls_error); + } +} + +std::string cfg_clans::get_path() +{ + return fs::get_config_dir(true) + "clans.yml"; +} + +std::string cfg_clans::get_host() const +{ + return host.to_string(); +} + +bool cfg_clans::get_use_https() const +{ + return use_https.get(); +} + +std::vector> cfg_clans::get_hosts() +{ + std::vector> vec_hosts; + const std::string host_str = hosts.to_string(); + const auto hosts_list = fmt::split_sv(host_str, {"|||"}); + + for (const auto& cur_host : hosts_list) + { + const auto desc_and_host = fmt::split(cur_host, {"|"}); + if (desc_and_host.size() != 2) + { + clans_config_log.error("Invalid host in the list of hosts: %s", cur_host); + continue; + } + vec_hosts.push_back(std::make_pair(std::move(desc_and_host[0]), std::move(desc_and_host[1]))); + } + + if (vec_hosts.empty()) + { + hosts.from_default(); + save(); + return get_hosts(); + } + + return vec_hosts; +} + +void cfg_clans::set_host(std::string_view host) +{ + this->host.from_string(host); +} + +void cfg_clans::set_use_https(bool use_https) +{ + this->use_https.set(use_https); +} + +void cfg_clans::set_hosts(const std::vector>& vec_hosts) +{ + std::string final_string; + for (const auto& [cur_desc, cur_host] : vec_hosts) + { + fmt::append(final_string, "%s|%s|||", cur_desc, cur_host); + } + + if (final_string.empty()) + { + hosts.from_default(); + return; + } + + final_string.resize(final_string.size() - 3); + hosts.from_string(final_string); +} + +bool cfg_clans::add_host(std::string_view new_description, std::string_view new_host) +{ + auto cur_hosts = get_hosts(); + + for (const auto& [cur_desc, cur_host] : cur_hosts) + { + if (cur_desc == new_description && cur_host == new_host) + return false; + } + + cur_hosts.push_back(std::make_pair(std::string(new_description), std::string(new_host))); + set_hosts(cur_hosts); + + return true; +} + +bool cfg_clans::del_host(std::string_view del_description, std::string_view del_host) +{ + // Do not delete default servers + const auto def_desc_and_host = fmt::split_sv(hosts.def, {"|"}); + ensure(def_desc_and_host.size() == 2); + if (del_description == def_desc_and_host[0] && del_host == def_desc_and_host[1]) + { + return true; + } + + auto cur_hosts = get_hosts(); + + for (auto it = cur_hosts.begin(); it != cur_hosts.end(); it++) + { + if (it->first == del_description && it->second == del_host) + { + cur_hosts.erase(it); + set_hosts(cur_hosts); + return true; + } + } + + return false; +} diff --git a/rpcs3/Emu/NP/clans_config.h b/rpcs3/Emu/NP/clans_config.h new file mode 100644 index 0000000000..96dcde497d --- /dev/null +++ b/rpcs3/Emu/NP/clans_config.h @@ -0,0 +1,29 @@ +#pragma once + +#include "Utilities/Config.h" + +struct cfg_clans : cfg::node +{ + cfg::uint32 version{this, "Version", 1}; + cfg::string host{this, "Host", "clans.rpcs3.net"}; + cfg::string hosts{this, "Hosts", "Official Clans Server|clans.rpcs3.net"}; + cfg::_bool use_https{this, "Use HTTPS", true}; + + void load(); + void save() const; + + std::string get_host() const; + bool get_use_https() const; + std::vector> get_hosts(); + + void set_host(std::string_view host); + void set_use_https(bool use_https); + bool add_host(std::string_view description, std::string_view host); + bool del_host(std::string_view description, std::string_view host); + +private: + static std::string get_path(); + void set_hosts(const std::vector>& vec_hosts); +}; + +extern cfg_clans g_cfg_clans; diff --git a/rpcs3/Emu/NP/np_handler.cpp b/rpcs3/Emu/NP/np_handler.cpp index 09770f0908..f76788287b 100644 --- a/rpcs3/Emu/NP/np_handler.cpp +++ b/rpcs3/Emu/NP/np_handler.cpp @@ -239,6 +239,25 @@ namespace np return true; } + std::string ticket::get_service_id() const + { + if (!parse_success) + { + return ""; + } + + const auto& node = nodes[0].data.data_nodes[8]; + if (node.len != SCE_NP_SERVICE_ID_SIZE) + { + return ""; + } + + // Trim null characters + const auto& vec = node.data.data_vec; + auto it = std::find(vec.begin(), vec.end(), 0); + return std::string(vec.begin(), it); + } + std::optional ticket::parse_node(std::size_t index) const { if ((index + MIN_TICKET_DATA_SIZE) > size()) @@ -1352,6 +1371,24 @@ namespace np return history; } + u32 np_handler::get_clan_ticket_ready() + { + return clan_ticket_ready.load(); + } + + ticket np_handler::get_clan_ticket() + { + clan_ticket_ready.wait(0, atomic_wait_timeout{60'000'000'000}); // 60 seconds + + if (!clan_ticket_ready.load()) + { + rpcn_log.error("Failed to get clan ticket within timeout."); + return ticket{}; + } + + return clan_ticket; + } + constexpr usz MAX_HISTORY_ENTRIES = 200; void np_handler::add_player_to_history(const SceNpId* npid, const char* description) diff --git a/rpcs3/Emu/NP/np_handler.h b/rpcs3/Emu/NP/np_handler.h index 999ae7cb86..d3c1213cc6 100644 --- a/rpcs3/Emu/NP/np_handler.h +++ b/rpcs3/Emu/NP/np_handler.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "Emu/Memory/vm_ptr.h" #include "Emu/Cell/Modules/sceNp.h" @@ -69,6 +70,7 @@ namespace np bool empty() const; bool get_value(s32 param_id, vm::ptr param) const; + std::string get_service_id() const; private: std::optional parse_node(std::size_t index) const; @@ -253,6 +255,8 @@ namespace np // Misc stuff void req_ticket(u32 version, const SceNpId* npid, const char* service_id, const u8* cookie, u32 cookie_size, const char* entitlement_id, u32 consumed_count); const ticket& get_ticket() const; + u32 get_clan_ticket_ready(); + ticket get_clan_ticket(); void add_player_to_history(const SceNpId* npid, const char* description); u32 add_players_to_history(const SceNpId* npids, const char* description, u32 count); u32 get_players_history_count(u32 options); @@ -406,6 +410,10 @@ namespace np ticket current_ticket; + // Clan ticket + atomic_t clan_ticket_ready = 0; + ticket clan_ticket; + // IP & DNS info std::string hostname = "localhost"; std::array ether_address{}; diff --git a/rpcs3/Emu/NP/np_requests.cpp b/rpcs3/Emu/NP/np_requests.cpp index 115348ac6d..23e3d7ca75 100644 --- a/rpcs3/Emu/NP/np_requests.cpp +++ b/rpcs3/Emu/NP/np_requests.cpp @@ -1,8 +1,9 @@ +#include "stdafx.h" #include "Emu/Cell/Modules/sceNp.h" #include "Emu/Cell/Modules/sceNp2.h" +#include "Emu/NP/clans_client.h" #include "Emu/NP/rpcn_types.h" #include "Utilities/StrFmt.h" -#include "stdafx.h" #include "Emu/Cell/PPUCallback.h" #include "Emu/Cell/lv2/sys_sync.h" #include "Emu/system_config.h" @@ -863,7 +864,20 @@ namespace np auto ticket_raw = reply.get_rawdata(); ensure(!reply.is_error(), "Malformed reply to RequestTicket command"); - current_ticket = ticket(std::move(ticket_raw)); + auto incoming_ticket = ticket(std::move(ticket_raw)); + + // Clans: check if ticket belongs to the clan service. + // If so, hijack the ticket and cache it for future use. + if (incoming_ticket.get_service_id() == CLANS_SERVICE_ID) + { + clan_ticket = incoming_ticket; + clan_ticket_ready.store(1); + clan_ticket_ready.notify_all(); + + return; + } + + current_ticket = incoming_ticket; auto ticket_size = static_cast(current_ticket.size()); if (manager_cb) diff --git a/rpcs3/Emu/system_config.h b/rpcs3/Emu/system_config.h index e2ad7cbd8b..7c7b89fcf9 100644 --- a/rpcs3/Emu/system_config.h +++ b/rpcs3/Emu/system_config.h @@ -322,6 +322,7 @@ struct cfg_root : cfg::node cfg::_enum psn_status{this, "PSN status", np_psn_status::disabled}; cfg::string country{this, "PSN Country", "us"}; + cfg::_bool clans_enabled{this, "Clans Enabled", false}; } net{this}; struct node_savestate : cfg::node diff --git a/rpcs3/emucore.vcxproj b/rpcs3/emucore.vcxproj index 437d79df8a..63f080c5c9 100644 --- a/rpcs3/emucore.vcxproj +++ b/rpcs3/emucore.vcxproj @@ -40,7 +40,7 @@ Use - ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang + ..\3rdparty\miniupnp\miniupnp\miniupnpc\include;..\3rdparty\wolfssl\wolfssl;..\3rdparty\flatbuffers\include;..\3rdparty\libusb\libusb\libusb;..\3rdparty\yaml-cpp\yaml-cpp\include;..\3rdparty\SoundTouch\soundtouch\include;..\3rdparty\rtmidi\rtmidi;..\3rdparty\zlib\zlib;$(SolutionDir)build\lib\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm\include;$(SolutionDir)build\lib_ext\$(Configuration)-$(Platform)\llvm_build\include;$(VULKAN_SDK)\Include;..\3rdparty\zstd\zstd\lib;$(SolutionDir)3rdparty\fusion\fusion\Fusion;$(SolutionDir)3rdparty\wolfssl\extra\win32;$(SolutionDir)3rdparty\libsdl-org\SDL\include;$(SolutionDir)3rdparty\glslang\glslang;$(SolutionDir)3rdparty\curl\curl\include MaxSpeed AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) AL_LIBTYPE_STATIC;MINIUPNP_STATICLIB;HAVE_VULKAN;HAVE_SDL3;ZLIB_CONST;WOLFSSL_USER_SETTINGS;%(PreprocessorDefinitions) @@ -112,6 +112,7 @@ + @@ -188,6 +189,7 @@ + diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index b5ffb8ebd8..d8d5053179 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -406,6 +406,9 @@ true + + true + true @@ -697,6 +700,9 @@ true + + true + true @@ -807,6 +813,7 @@ + @@ -1488,6 +1495,16 @@ .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\flatbuffers\include" "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing %(Identity)... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl\wolfssl" "-I.\..\3rdparty\curl\curl\include" "-I.\..\3rdparty\libusb\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtConcurrent" + $(QTDIR)\bin\moc.exe;%(FullPath) Moc%27ing %(Identity)... diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index aa90e50cd0..2e9340b01b 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -864,6 +864,12 @@ Generated Files\Release + + Generated Files\Debug + + + Generated Files\Release + Generated Files\Debug @@ -909,6 +915,9 @@ Gui\rpcn + + Gui\clans + Gui\message dialog @@ -1699,6 +1708,9 @@ Gui\rpcn + + Gui\rpcn + Gui\message dialog diff --git a/rpcs3/rpcs3qt/CMakeLists.txt b/rpcs3/rpcs3qt/CMakeLists.txt index 43c0024905..98be856a25 100644 --- a/rpcs3/rpcs3qt/CMakeLists.txt +++ b/rpcs3/rpcs3qt/CMakeLists.txt @@ -8,6 +8,7 @@ add_library(rpcs3_ui STATIC camera_settings_dialog.cpp cg_disasm_window.cpp cheat_manager.cpp + clans_settings_dialog.cpp config_adapter.cpp config_checker.cpp curl_handle.cpp diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.cpp b/rpcs3/rpcs3qt/clans_settings_dialog.cpp new file mode 100644 index 0000000000..9745d11294 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.cpp @@ -0,0 +1,187 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "clans_settings_dialog.h" +#include "Emu/NP/clans_config.h" + +clans_settings_dialog::clans_settings_dialog(QWidget* parent) + : QDialog(parent) +{ + g_cfg_clans.load(); + + setWindowTitle(tr("Clans Configuration")); + setObjectName("clans_settings_dialog"); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QGroupBox* grp_server = new QGroupBox(tr("Server:")); + QVBoxLayout* vbox_server = new QVBoxLayout(); + + QHBoxLayout* hbox_lbl_combo = new QHBoxLayout(); + QLabel* lbl_server = new QLabel(tr("Server:")); + m_cbx_servers = new QComboBox(); + m_cbx_protocol = new QComboBox(); + + m_cbx_protocol->addItem("HTTPS"); + m_cbx_protocol->addItem("HTTP"); + m_cbx_protocol->setCurrentIndex(g_cfg_clans.get_use_https() ? 0 : 1); + + refresh_combobox(); + + hbox_lbl_combo->addWidget(lbl_server); + hbox_lbl_combo->addWidget(m_cbx_servers); + hbox_lbl_combo->addWidget(m_cbx_protocol); + + QHBoxLayout* hbox_buttons = new QHBoxLayout(); + QPushButton* btn_add_server = new QPushButton(tr("Add")); + QPushButton* btn_del_server = new QPushButton(tr("Del")); + hbox_buttons->addStretch(); + hbox_buttons->addWidget(btn_add_server); + hbox_buttons->addWidget(btn_del_server); + + vbox_server->addLayout(hbox_lbl_combo); + vbox_server->addLayout(hbox_buttons); + + grp_server->setLayout(vbox_server); + vbox_global->addWidget(grp_server); + + setLayout(vbox_global); + + connect(m_cbx_servers, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + QVariant host = m_cbx_servers->itemData(index); + + if (!host.isValid() || !host.canConvert()) + return; + + g_cfg_clans.set_host(host.toString().toStdString()); + g_cfg_clans.save(); + }); + + connect(m_cbx_protocol, &QComboBox::currentIndexChanged, this, [this](int index) + { + if (index < 0) + return; + + g_cfg_clans.set_use_https(index == 0); + g_cfg_clans.save(); + }); + + connect(btn_add_server, &QAbstractButton::clicked, this, [this]() + { + clans_add_server_dialog dlg(this); + dlg.exec(); + const auto& new_server = dlg.get_new_server(); + if (new_server) + { + if (!g_cfg_clans.add_host(new_server->first, new_server->second)) + { + QMessageBox::critical(this, tr("Existing Server"), tr("You already have a server with this description & hostname in the list."), QMessageBox::Ok); + return; + } + + g_cfg_clans.save(); + refresh_combobox(); + } + }); + + connect(btn_del_server, &QAbstractButton::clicked, this, [this]() + { + const int index = m_cbx_servers->currentIndex(); + + if (index < 0) + return; + + const std::string desc = m_cbx_servers->itemText(index).toStdString(); + const std::string host = m_cbx_servers->itemData(index).toString().toStdString(); + + if (g_cfg_clans.del_host(desc, host)) + { + g_cfg_clans.save(); + refresh_combobox(); + } + else + { + QMessageBox::warning(this, tr("Cannot Delete"), tr("This server cannot be deleted."), QMessageBox::Ok); + } + }); +} + +void clans_settings_dialog::refresh_combobox() +{ + g_cfg_clans.load(); + const auto vec_hosts = g_cfg_clans.get_hosts(); + const auto cur_host = g_cfg_clans.get_host(); + int i = 0, index = 0; + + m_cbx_servers->clear(); + + for (const auto& [desc, host] : vec_hosts) + { + m_cbx_servers->addItem(QString::fromStdString(desc), QString::fromStdString(host)); + if (cur_host == host) + index = i; + + i++; + } + + m_cbx_servers->setCurrentIndex(index); +} + +clans_add_server_dialog::clans_add_server_dialog(QWidget* parent) + : QDialog(parent) +{ + setWindowTitle(tr("Clans: Add Server")); + setObjectName("clans_add_server_dialog"); + setMinimumSize(QSize(400, 200)); + + QVBoxLayout* vbox_global = new QVBoxLayout(); + + QLabel* lbl_description = new QLabel(tr("Description:")); + QLineEdit* edt_description = new QLineEdit(); + QLabel* lbl_host = new QLabel(tr("Host:")); + QLineEdit* edt_host = new QLineEdit(); + QDialogButtonBox* btn_box = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + vbox_global->addWidget(lbl_description); + vbox_global->addWidget(edt_description); + vbox_global->addWidget(lbl_host); + vbox_global->addWidget(edt_host); + vbox_global->addWidget(btn_box); + + setLayout(vbox_global); + + connect(btn_box, &QDialogButtonBox::accepted, this, [this, edt_description, edt_host]() + { + const QString description = edt_description->text(); + const QString host = edt_host->text(); + + if (description.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Description!"), tr("You must enter a description!"), QMessageBox::Ok); + return; + } + if (host.isEmpty()) + { + QMessageBox::critical(this, tr("Missing Hostname!"), tr("You must enter a hostname for the server!"), QMessageBox::Ok); + return; + } + + m_new_server = std::make_pair(description.toStdString(), host.toStdString()); + QDialog::accept(); + }); + connect(btn_box, &QDialogButtonBox::rejected, this, &QDialog::reject); +} + +const std::optional>& clans_add_server_dialog::get_new_server() const +{ + return m_new_server; +} diff --git a/rpcs3/rpcs3qt/clans_settings_dialog.h b/rpcs3/rpcs3qt/clans_settings_dialog.h new file mode 100644 index 0000000000..7de6105382 --- /dev/null +++ b/rpcs3/rpcs3qt/clans_settings_dialog.h @@ -0,0 +1,30 @@ +#pragma once + +#include +#include +#include + +class clans_settings_dialog : public QDialog +{ + Q_OBJECT +public: + clans_settings_dialog(QWidget* parent = nullptr); + +private: + void refresh_combobox(); + +private: + QComboBox* m_cbx_servers = nullptr; + QComboBox* m_cbx_protocol = nullptr; +}; + +class clans_add_server_dialog : public QDialog +{ + Q_OBJECT +public: + clans_add_server_dialog(QWidget* parent = nullptr); + const std::optional>& get_new_server() const; + +private: + std::optional> m_new_server; +}; diff --git a/rpcs3/rpcs3qt/emu_settings_type.h b/rpcs3/rpcs3qt/emu_settings_type.h index 3007e0a1d9..cc8ab57b96 100644 --- a/rpcs3/rpcs3qt/emu_settings_type.h +++ b/rpcs3/rpcs3qt/emu_settings_type.h @@ -200,6 +200,7 @@ enum class emu_settings_type BindAddress, EnableUpnp, PSNCountry, + EnableClans, // System LicenseArea, @@ -411,6 +412,7 @@ inline static const std::map settings_location { emu_settings_type::BindAddress, { "Net", "Bind address"}}, { emu_settings_type::EnableUpnp, { "Net", "UPNP Enabled"}}, { emu_settings_type::PSNCountry, { "Net", "PSN Country"}}, + { emu_settings_type::EnableClans, { "Net", "Clans Enabled"}}, // System { emu_settings_type::LicenseArea, { "System", "License Area"}}, diff --git a/rpcs3/rpcs3qt/main_window.cpp b/rpcs3/rpcs3qt/main_window.cpp index c7516dc700..168d08a344 100644 --- a/rpcs3/rpcs3qt/main_window.cpp +++ b/rpcs3/rpcs3qt/main_window.cpp @@ -12,6 +12,7 @@ #include "log_frame.h" #include "settings_dialog.h" #include "rpcn_settings_dialog.h" +#include "clans_settings_dialog.h" #include "auto_pause_settings_dialog.h" #include "cg_disasm_window.h" #include "log_viewer.h" @@ -2991,6 +2992,18 @@ void main_window::CreateConnects() dlg.exec(); }); + connect(ui->confClansAct, &QAction::triggered, this, [this]() + { + if (!Emu.IsStopped()) + { + QMessageBox::critical(this, tr("Error: Emulation Running"), tr("You need to stop the emulator before editing Clans connection information!"), QMessageBox::Ok); + return; + } + + clans_settings_dialog dlg(this); + dlg.exec(); + }); + connect(ui->confIPCAct, &QAction::triggered, this, [this]() { ipc_settings_dialog dlg(this); diff --git a/rpcs3/rpcs3qt/main_window.ui b/rpcs3/rpcs3qt/main_window.ui index 72861a5d72..c9c8c0645d 100644 --- a/rpcs3/rpcs3qt/main_window.ui +++ b/rpcs3/rpcs3qt/main_window.ui @@ -282,6 +282,7 @@ + @@ -1252,6 +1253,14 @@ Configure RPCN + + + Clans + + + Configure Clans + + IPC diff --git a/rpcs3/rpcs3qt/settings_dialog.cpp b/rpcs3/rpcs3qt/settings_dialog.cpp index 902da72cff..81e85546e9 100644 --- a/rpcs3/rpcs3qt/settings_dialog.cpp +++ b/rpcs3/rpcs3qt/settings_dialog.cpp @@ -1435,10 +1435,12 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std { ui->psnStatusBox->setCurrentIndex(find_item(ui->psnStatusBox, static_cast(g_cfg.net.psn_status.def))); ui->psnStatusBox->setEnabled(false); + ui->enable_clans->setEnabled(false); } else { ui->psnStatusBox->setEnabled(true); + ui->enable_clans->setEnabled(true); } }); m_emu_settings->EnhanceComboBox(ui->netStatusBox, emu_settings_type::InternetStatus); @@ -1447,6 +1449,9 @@ settings_dialog::settings_dialog(std::shared_ptr gui_settings, std m_emu_settings->EnhanceComboBox(ui->psnStatusBox, emu_settings_type::PSNStatus); SubscribeTooltip(ui->gb_psnStatusBox, tooltips.settings.psn_status); + m_emu_settings->EnhanceCheckBox(ui->enable_clans, emu_settings_type::EnableClans); + SubscribeTooltip(ui->enable_clans, tooltips.settings.enable_clans); + settings_dialog::refresh_countrybox(); connect(ui->psnCountryBox, &QComboBox::currentIndexChanged, this, [this](int index) { diff --git a/rpcs3/rpcs3qt/settings_dialog.ui b/rpcs3/rpcs3qt/settings_dialog.ui index 4556ae9356..6a27f45be3 100644 --- a/rpcs3/rpcs3qt/settings_dialog.ui +++ b/rpcs3/rpcs3qt/settings_dialog.ui @@ -2319,6 +2319,13 @@ + + + + Enable Clans + + + diff --git a/rpcs3/rpcs3qt/tooltips.h b/rpcs3/rpcs3qt/tooltips.h index f7ec927332..abdc60f3dd 100644 --- a/rpcs3/rpcs3qt/tooltips.h +++ b/rpcs3/rpcs3qt/tooltips.h @@ -260,6 +260,7 @@ public: const QString bind = tr("Interface IP Address to bind to.\nOnly available in custom configurations."); const QString enable_upnp = tr("Enable UPNP.\nThis will automatically forward ports bound on 0.0.0.0 if your router has UPNP enabled."); const QString psn_country = tr("Changes the RPCN country."); + const QString enable_clans = tr("Enable connection to the Clans server.\nOnly affects games supporting the Clans feature."); // system From 3819b9d57e8e566e3622fa9dd89887aa64483066 Mon Sep 17 00:00:00 2001 From: schm1dtmac Date: Sat, 20 Dec 2025 22:13:46 +0000 Subject: [PATCH 92/94] [macOS] Force max pthread priority, fix throttling --- Utilities/Thread.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Utilities/Thread.cpp b/Utilities/Thread.cpp index 9b6d250c19..810b8fd7c5 100644 --- a/Utilities/Thread.cpp +++ b/Utilities/Thread.cpp @@ -2165,10 +2165,17 @@ void thread_base::start() ensure(m_thread); ensure(::ResumeThread(reinterpret_cast(+m_thread)) != static_cast(-1)); #elif defined(__APPLE__) - pthread_attr_t stack_size_attr; - pthread_attr_init(&stack_size_attr); - pthread_attr_setstacksize(&stack_size_attr, 0x800000); - ensure(pthread_create(reinterpret_cast(&m_thread.raw()), &stack_size_attr, entry_point, this) == 0); + pthread_attr_t attrs; + struct sched_param sp; + memset(&sp, 0, sizeof(struct sched_param)); + sp.sched_priority=99; + pthread_attr_init(&attrs); + pthread_attr_setstacksize(&attrs, 0x800000); + + pthread_attr_set_qos_class_np(&attrs, QOS_CLASS_USER_INTERACTIVE, 0); + pthread_attr_setschedpolicy(&attrs, SCHED_RR); + pthread_attr_setschedparam(&attrs, &sp); + ensure(pthread_create(reinterpret_cast(&m_thread.raw()), &attrs, entry_point, this) == 0); #else ensure(pthread_create(reinterpret_cast(&m_thread.raw()), nullptr, entry_point, this) == 0); #endif From 73bcabf7d52d27510c1085cd3939ba09d4c1f224 Mon Sep 17 00:00:00 2001 From: zeph <35661622+ZephyrCodesStuff@users.noreply.github.com> Date: Sun, 21 Dec 2025 13:11:33 +0100 Subject: [PATCH 93/94] Clans: fix graphical bug displaying JID instead of username (#17906) This PR fixes a visual bug with the Clans system, incorrectly converting the `jid` to a user's NPID, instead of using their username. Variable names should now be a bit clearer and avoid similar issues in the future. --------- Signed-off-by: zeph --- rpcs3/Emu/NP/clans_client.cpp | 80 +++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/rpcs3/Emu/NP/clans_client.cpp b/rpcs3/Emu/NP/clans_client.cpp index dd2d926d24..7883901476 100644 --- a/rpcs3/Emu/NP/clans_client.cpp +++ b/rpcs3/Emu/NP/clans_client.cpp @@ -475,34 +475,34 @@ namespace clan return clan_res; pugi::xml_node clan_result = response.child("clan"); - pugi::xml_node info = clan_result.child("info"); + pugi::xml_node member_info = clan_result.child("info"); - pugi::xml_attribute jid = info.attribute("jid"); - std::string npid_str = jid.as_string(); - std::string username = fmt::split(npid_str, {"@"})[0]; + pugi::xml_attribute member_jid = member_info.attribute("jid"); + std::string member_jid_str = member_jid.as_string(); + std::string member_username = fmt::split(member_jid_str, {"@"})[0]; - SceNpId npid; - if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + SceNpId member_npid; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) { - npid = nph.get_npid(); + member_npid = nph.get_npid(); } else { - np::string_to_npid(npid_str, npid); + np::string_to_npid(member_username, member_npid); } - pugi::xml_node role = info.child("role"); + pugi::xml_node role = member_info.child("role"); uint32_t role_int = role.text().as_uint(); - pugi::xml_node status = info.child("status"); + pugi::xml_node status = member_info.child("status"); uint32_t status_int = status.text().as_uint(); - pugi::xml_node description = info.child("description"); + pugi::xml_node description = member_info.child("description"); std::string description_str = description.text().as_string(); *mem_info = SceNpClansMemberEntry { - .npid = npid, + .npid = member_npid, .role = static_cast(role_int), .status = static_cast(status_int), .updatable = SceNpClansUpdatableMemberInfo{ @@ -544,28 +544,28 @@ namespace clan uint32_t total_count = total.as_uint(); int i = 0; - for (pugi::xml_node info = list.child("info"); info; info = info.next_sibling("info")) + for (pugi::xml_node member_info = list.child("info"); member_info; member_info = member_info.next_sibling("info")) { - std::string npid_str = info.attribute("jid").as_string(); - std::string username = fmt::split(npid_str, {"@"})[0]; + std::string member_jid = member_info.attribute("jid").as_string(); + std::string member_username = fmt::split(member_jid, {"@"})[0]; - SceNpId npid; - if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + SceNpId member_npid; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) { - npid = nph.get_npid(); + member_npid = nph.get_npid(); } else { - np::string_to_npid(npid_str, npid); + np::string_to_npid(member_username, member_npid); } - uint32_t role_int = info.child("role").text().as_uint(); - uint32_t status_int = info.child("status").text().as_uint(); - std::string description_str = info.child("description").text().as_string(); + uint32_t role_int = member_info.child("role").text().as_uint(); + uint32_t status_int = member_info.child("status").text().as_uint(); + std::string description_str = member_info.child("description").text().as_string(); SceNpClansMemberEntry entry = SceNpClansMemberEntry { - .npid = npid, + .npid = member_npid, .role = static_cast(role_int), .status = static_cast(status_int), }; @@ -612,17 +612,25 @@ namespace clan uint32_t total_count = total.as_uint(); int i = 0; - for (pugi::xml_node node = list.child("entry"); node; node = node.next_sibling("entry")) + for (pugi::xml_node member = list.child("entry"); member; member = member.next_sibling("entry")) { - pugi::xml_node id = node.child("jid"); - std::string npid_str = id.text().as_string(); + pugi::xml_node member_jid = member.child("jid"); + std::string member_jid_str = member_jid.text().as_string(); + std::string member_username = fmt::split(member_jid_str, {"@"})[0]; - SceNpId npid = {}; - np::string_to_npid(npid_str.c_str(), npid); + SceNpId member_npid = {}; + if (strncmp(member_username.c_str(), nph.get_npid().handle.data, 16) == 0) + { + member_npid = nph.get_npid(); + } + else + { + np::string_to_npid(member_username.c_str(), member_npid); + } SceNpClansBlacklistEntry entry = SceNpClansBlacklistEntry { - .entry = npid, + .entry = member_npid, }; bl[i] = entry; @@ -1047,19 +1055,19 @@ namespace clan std::string subject_str = node.child("subject").text().as_string(); std::string msg_str = node.child("msg").text().as_string(); - std::string npid_str = node.child("jid").text().as_string(); + std::string author_jid = node.child("jid").text().as_string(); std::string msg_date = node.child("msg-date").text().as_string(); - SceNpId npid; - std::string username = fmt::split(npid_str, {"@"})[0]; + SceNpId author_npid; + std::string author_username = fmt::split(author_jid, {"@"})[0]; - if (!strcmp(username.c_str(), nph.get_npid().handle.data)) + if (strncmp(author_username.c_str(), nph.get_npid().handle.data, 16) == 0) { - npid = nph.get_npid(); + author_npid = nph.get_npid(); } else { - np::string_to_npid(npid_str, npid); + np::string_to_npid(author_username, author_npid); } // TODO: implement `binData` and `fromId` @@ -1071,7 +1079,7 @@ namespace clan .subject = "", .body = "", }, - .npid = npid, + .npid = author_npid, .postedBy = clan_id, }; From 2fb697322f77fc30969692c63972a567b8bfb573 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Sun, 21 Dec 2025 14:00:36 +0100 Subject: [PATCH 94/94] overlays: log font lookup info on exception (#17903) - Logs the font lookup dirs and the target fonts when no font was found in the native overlay --- Utilities/sync.h | 20 ++++++++++---------- rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp | 4 ++-- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Utilities/sync.h b/Utilities/sync.h index 2d805ff14c..513a45ee51 100644 --- a/Utilities/sync.h +++ b/Utilities/sync.h @@ -84,15 +84,15 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t case FUTEX_WAIT_PRIVATE: case FUTEX_WAIT_BITSET_PRIVATE: { - if (timeout) - { - const uint64_t nsec = timeout->tv_nsec + timeout->tv_sec * 1000000000ull; - return os_sync_wait_on_address_with_timeout(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE, OS_CLOCK_MACH_ABSOLUTE_TIME, nsec); - } - else - { - return os_sync_wait_on_address(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE); - } + if (timeout) + { + const uint64_t nsec = timeout->tv_nsec + timeout->tv_sec * 1000000000ull; + return os_sync_wait_on_address_with_timeout(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE, OS_CLOCK_MACH_ABSOLUTE_TIME, nsec); + } + else + { + return os_sync_wait_on_address(const_cast(uaddr), static_cast(val), sizeof(uint), OS_SYNC_WAIT_ON_ADDRESS_NONE); + } } case FUTEX_WAKE_PRIVATE: @@ -100,7 +100,7 @@ inline int futex(volatile void* uaddr, int futex_op, uint val, const timespec* t { for (;;) { - int ret = 0; + int ret = 0; if (val == INT32_MAX) { ret = os_sync_wake_by_address_all(const_cast(uaddr), sizeof(uint), OS_SYNC_WAKE_BY_ADDRESS_NONE); diff --git a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp index d6fc7eabb9..91ef3f13d9 100644 --- a/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp +++ b/rpcs3/Emu/RSX/Overlays/overlay_fonts.cpp @@ -213,7 +213,7 @@ namespace rsx return font_found; }; - for (const auto& font_file : fs_settings.font_names) + for (const std::string& font_file : fs_settings.font_names) { if (fs::is_file(font_file)) { @@ -262,7 +262,7 @@ namespace rsx { if (fallback_bytes.empty()) { - fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.", static_cast(c), static_cast(codepage_id)); + fmt::throw_exception("Failed to initialize font for character 0x%x on codepage %d.\nLookup dirs:\n%s\nTarget fonts:\n%s", static_cast(c), static_cast(codepage_id), fmt::merge(fs_settings.lookup_font_dirs, "\n"), fmt::merge(fs_settings.font_names, "\n")); } rsx_log.error("Failed to initialize font for character 0x%x on codepage %d. Falling back to font '%s'", static_cast(c), static_cast(codepage_id), fallback_file);