diff --git a/.github/ISSUE_TEMPLATE/game-bug-report.yaml b/.github/ISSUE_TEMPLATE/game-bug-report.yaml index a9c669ff9..d9ebd8347 100644 --- a/.github/ISSUE_TEMPLATE/game-bug-report.yaml +++ b/.github/ISSUE_TEMPLATE/game-bug-report.yaml @@ -35,7 +35,7 @@ body: required: true - label: I have disabled all patches and cheats and the issue is still present. required: true - - label: I have all the required [system modules](https://github.com/shadps4-emu/shadps4-game-compatibility?tab=readme-ov-file#informations) installed. + - label: I have all the required [system modules](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D#4-adding-modules) installed. required: true - type: textarea id: desc diff --git a/CMakeLinuxPresets.json b/CMakeLinuxPresets.json new file mode 100644 index 000000000..5c820774c --- /dev/null +++ b/CMakeLinuxPresets.json @@ -0,0 +1,21 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 3, + "minor": 30, + "patch": 0 + }, + "configurePresets": [ + { + "name": "x64-Clang-Base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/Build/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang", + "CMAKE_CXX_COMPILER": "clang++", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}" + } + } + ] +} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index b0685cffa..c0e8b0dbf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -203,7 +203,7 @@ execute_process( # Set Version set(EMULATOR_VERSION_MAJOR "0") -set(EMULATOR_VERSION_MINOR "9") +set(EMULATOR_VERSION_MINOR "10") set(EMULATOR_VERSION_PATCH "1") set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}") @@ -685,11 +685,13 @@ set(COMMON src/common/logging/backend.cpp src/common/path_util.h src/common/object_pool.h src/common/polyfill_thread.h + src/common/range_lock.h src/common/rdtsc.cpp src/common/rdtsc.h src/common/recursive_lock.cpp src/common/recursive_lock.h src/common/sha1.h + src/common/shared_first_mutex.h src/common/signal_context.h src/common/signal_context.cpp src/common/singleton.h @@ -1072,6 +1074,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/gui_settings.h src/qt_gui/settings.cpp src/qt_gui/settings.h + src/qt_gui/sdl_event_wrapper.cpp + src/qt_gui/sdl_event_wrapper.h ${EMULATOR} ${RESOURCE_FILES} ${TRANSLATIONS} diff --git a/CMakePresets.json b/CMakePresets.json index 7d23903d6..bd1aba36e 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -1,27 +1,12 @@ { - "version": 5, + "version": 9, "cmakeMinimumRequired": { "major": 3, - "minor": 24, + "minor": 30, "patch": 0 }, + "include": ["CMake${hostSystemName}Presets.json"], "configurePresets": [ - { - "name": "x64-Clang-Base", - "hidden": true, - "generator": "Ninja", - "binaryDir": "${sourceDir}/Build/${presetName}", - "cacheVariables": { - "CMAKE_C_COMPILER": "clang-cl", - "CMAKE_CXX_COMPILER": "clang-cl", - "CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}" - }, - "vendor": { - "microsoft.com/VisualStudioSettings/CMake/1.0": { - "intelliSenseMode": "windows-clang-x64" - } - } - }, { "name": "x64-Clang-Debug", "displayName": "Clang x64 Debug", @@ -74,4 +59,4 @@ } } ] -} +} \ No newline at end of file diff --git a/CMakeWindowsPresets.json b/CMakeWindowsPresets.json new file mode 100644 index 000000000..605fbfa94 --- /dev/null +++ b/CMakeWindowsPresets.json @@ -0,0 +1,26 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 3, + "minor": 30, + "patch": 0 + }, + "configurePresets": [ + { + "name": "x64-Clang-Base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/Build/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "clang-cl", + "CMAKE_CXX_COMPILER": "clang-cl", + "CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}" + }, + "vendor": { + "microsoft.com/VisualStudioSettings/CMake/1.0": { + "intelliSenseMode": "windows-clang-x64" + } + } + } + ] +} \ No newline at end of file diff --git a/REUSE.toml b/REUSE.toml index 4012ff19a..4b1c94d21 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -5,6 +5,8 @@ path = [ "REUSE.toml", "crowdin.yml", "CMakeSettings.json", + "CMakeLinuxPresets.json", + "CMakeWindowsPresets.json", "CMakePresets.json", ".github/FUNDING.yml", ".github/shadps4.png", diff --git a/cmake/DetectQtInstallation.cmake b/cmake/DetectQtInstallation.cmake index e95e8980f..650cc9745 100644 --- a/cmake/DetectQtInstallation.cmake +++ b/cmake/DetectQtInstallation.cmake @@ -1,14 +1,28 @@ # SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project # SPDX-License-Identifier: GPL-2.0-or-later -file(GLOB QT_KITS LIST_DIRECTORIES true "C:/Qt/*/msvc*_64") -list(SORT QT_KITS COMPARE NATURAL) -list(REVERSE QT_KITS) -if(QT_KITS) - list(GET QT_KITS 0 QT_PREFIX) - set(CMAKE_PREFIX_PATH "${QT_PREFIX}" CACHE PATH "Qt prefix auto‑detected" FORCE) - message(STATUS "Auto-detected Qt prefix: ${QT_PREFIX}") -else() - message(STATUS "findQt.cmake: no Qt‑Directory found in C:/Qt – please set CMAKE_PREFIX_PATH manually") -endif() +set(highest_version "0") +set(CANDIDATE_DRIVES A B C D E F G H I J K L M N O P Q R S T U V W X Y Z) +foreach(drive ${CANDIDATE_DRIVES}) + file(GLOB kits LIST_DIRECTORIES true CONFIGURE_DEPENDS "${drive}:/Qt/*/msvc*_64") + foreach(kit IN LISTS kits) + get_filename_component(version_dir "${kit}" DIRECTORY) + get_filename_component(kit_version "${version_dir}" NAME) + + message(STATUS "DetectQtInstallation.cmake: Detected Qt: ${kit}") + + if (kit_version VERSION_GREATER highest_version) + set(highest_version "${kit_version}") + set(QT_PREFIX "${kit}") + + endif() + endforeach() +endforeach() + +if(QT_PREFIX) + set(CMAKE_PREFIX_PATH "${QT_PREFIX}" CACHE PATH "Qt prefix auto‑detected" FORCE) + message(STATUS "DetectQtInstallation.cmake: Choose newest Qt: ${QT_PREFIX}") +else() + message(STATUS "DetectQtInstallation.cmake: No Qt‑Directory found in :/Qt – please set CMAKE_PREFIX_PATH manually") +endif() diff --git a/dist/net.shadps4.shadPS4.metainfo.xml b/dist/net.shadps4.shadPS4.metainfo.xml index 493dc0df6..f9bd7c7c2 100644 --- a/dist/net.shadps4.shadPS4.metainfo.xml +++ b/dist/net.shadps4.shadPS4.metainfo.xml @@ -37,6 +37,9 @@ Game + + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.10.0 + https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.9.0 diff --git a/externals/sirit b/externals/sirit index 6b450704f..b4eccb336 160000 --- a/externals/sirit +++ b/externals/sirit @@ -1 +1 @@ -Subproject commit 6b450704f6fedb9413d0c89a9eb59d028eb1e6c0 +Subproject commit b4eccb336f1b1169af48dac1e04015985af86e3e diff --git a/src/common/adaptive_mutex.h b/src/common/adaptive_mutex.h index f174f5996..2ab385bdb 100644 --- a/src/common/adaptive_mutex.h +++ b/src/common/adaptive_mutex.h @@ -18,6 +18,9 @@ public: void unlock() { pthread_mutex_unlock(&mutex); } + [[nodiscard]] bool try_lock() { + return pthread_mutex_trylock(&mutex) == 0; + } private: pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; diff --git a/src/common/bit_array.h b/src/common/bit_array.h index f211bbf95..0ab464390 100644 --- a/src/common/bit_array.h +++ b/src/common/bit_array.h @@ -361,13 +361,6 @@ public: return *this; } - inline constexpr BitArray& operator~() { - for (size_t i = 0; i < WORD_COUNT; ++i) { - data[i] = ~data[i]; - } - return *this; - } - inline constexpr BitArray operator|(const BitArray& other) const { BitArray result = *this; result |= other; @@ -388,7 +381,9 @@ public: inline constexpr BitArray operator~() const { BitArray result = *this; - result = ~result; + for (size_t i = 0; i < WORD_COUNT; ++i) { + result.data[i] = ~result.data[i]; + } return result; } @@ -408,4 +403,4 @@ private: std::array data{}; }; -} // namespace Common \ No newline at end of file +} // namespace Common diff --git a/src/common/config.cpp b/src/common/config.cpp index 9c316949a..6f8563377 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -31,29 +31,53 @@ std::filesystem::path find_fs_path_or(const basic_value& v, const K& ky, namespace Config { +// General static bool isNeo = false; static bool isDevKit = false; +static bool isPSNSignedIn = false; static bool isTrophyPopupDisabled = false; +static double trophyNotificationDuration = 6.0; static bool enableDiscordRPC = false; -static u32 screenWidth = 1280; -static u32 screenHeight = 720; -static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select -static std::string logFilter; +static std::string logFilter = ""; static std::string logType = "sync"; static std::string userName = "shadPS4"; -static std::string chooseHomeTab; +static std::string chooseHomeTab = "General"; +static bool isShowSplash = false; +static std::string isSideTrophy = "right"; +static bool compatibilityData = false; +static bool checkCompatibilityOnStartup = false; + +// Input +static int cursorState = HideCursorState::Idle; +static int cursorHideTimeout = 5; // 5 seconds (default) static bool useSpecialPad = false; static int specialPadClass = 1; static bool isMotionControlsEnabled = true; -static bool isDebugDump = false; -static bool isShaderDebug = false; -static bool isShowSplash = false; -static std::string isSideTrophy = "right"; +static bool useUnifiedInputConfig = true; + +// These two entries aren't stored in the config +static bool overrideControllerColor = false; +static int controllerCustomColorRGB[3] = {0, 0, 255}; + +// GPU +static u32 windowWidth = 1280; +static u32 windowHeight = 720; +static u32 internalScreenWidth = 1280; +static u32 internalScreenHeight = 720; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; +static bool readbacksEnabled = false; +static bool readbackLinearImagesEnabled = false; +static bool directMemoryAccessEnabled = false; static bool shouldDumpShaders = false; -static bool shouldPatchShaders = true; +static bool shouldPatchShaders = false; static u32 vblankDivider = 1; +static bool isFullscreen = false; +static std::string fullscreenMode = "Windowed"; +static bool isHDRAllowed = false; + +// Vulkan +static s32 gpuId = -1; static bool vkValidation = false; static bool vkValidationSync = false; static bool vkValidationGpu = false; @@ -61,32 +85,29 @@ static bool vkCrashDiagnostic = false; static bool vkHostMarkers = false; static bool vkGuestMarkers = false; static bool rdocEnable = false; -static bool isFpsColor = true; -static bool isSeparateLogFilesEnabled = false; -static int cursorState = HideCursorState::Idle; -static int cursorHideTimeout = 5; // 5 seconds (default) -static double trophyNotificationDuration = 6.0; -static bool useUnifiedInputConfig = true; -static bool overrideControllerColor = false; -static int controllerCustomColorRGB[3] = {0, 0, 255}; -static bool compatibilityData = false; -static bool checkCompatibilityOnStartup = false; -static std::string trophyKey; -static bool isPSNSignedIn = false; -// Gui +// Debug +static bool isDebugDump = false; +static bool isShaderDebug = false; +static bool isSeparateLogFilesEnabled = false; +static bool isFpsColor = true; + +// GUI static bool load_game_size = true; static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; -static bool isFullscreen = false; -static std::string fullscreenMode = "Windowed"; -static bool isHDRAllowed = false; -// Language +// Settings u32 m_language = 1; // english +// Keys +static std::string trophyKey = ""; + +// Expected number of items in the config file +static constexpr u64 total_entries = 54; + bool allowHDR() { return isHDRAllowed; } @@ -176,12 +197,20 @@ double getTrophyNotificationDuration() { return trophyNotificationDuration; } -u32 getScreenWidth() { - return screenWidth; +u32 getWindowWidth() { + return windowWidth; } -u32 getScreenHeight() { - return screenHeight; +u32 getWindowHeight() { + return windowHeight; +} + +u32 getInternalScreenWidth() { + return internalScreenHeight; +} + +u32 getInternalScreenHeight() { + return internalScreenHeight; } s32 getGpuId() { @@ -240,6 +269,18 @@ bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers; } +bool readbacks() { + return readbacksEnabled; +} + +bool readbackLinearImages() { + return readbackLinearImagesEnabled; +} + +bool directMemoryAccess() { + return directMemoryAccessEnabled; +} + bool dumpShaders() { return shouldDumpShaders; } @@ -308,12 +349,20 @@ void setGpuId(s32 selectedGpuId) { gpuId = selectedGpuId; } -void setScreenWidth(u32 width) { - screenWidth = width; +void setWindowWidth(u32 width) { + windowWidth = width; } -void setScreenHeight(u32 height) { - screenHeight = height; +void setWindowHeight(u32 height) { + windowHeight = height; +} + +void setInternalScreenWidth(u32 width) { + internalScreenWidth = width; +} + +void setInternalScreenHeight(u32 height) { + internalScreenHeight = height; } void setDebugDump(bool enable) { @@ -344,6 +393,14 @@ void setCopyGPUCmdBuffers(bool enable) { shouldCopyGPUBuffers = enable; } +void setReadbacks(bool enable) { + readbacksEnabled = enable; +} + +void setDirectMemoryAccess(bool enable) { + directMemoryAccessEnabled = enable; +} + void setDumpShaders(bool enable) { shouldDumpShaders = enable; } @@ -387,6 +444,7 @@ void setCursorState(s16 newCursorState) { void setCursorHideTimeout(int newcursorHideTimeout) { cursorHideTimeout = newcursorHideTimeout; } + void setTrophyNotificationDuration(double newTrophyNotificationDuration) { trophyNotificationDuration = newTrophyNotificationDuration; } @@ -547,79 +605,104 @@ void load(const std::filesystem::path& path) { fmt::print("Got exception trying to load config file. Exception: {}\n", ex.what()); return; } + + u64 entry_count = 0; + if (data.contains("General")) { const toml::value& general = data.at("General"); - isNeo = toml::find_or(general, "isPS4Pro", false); - isDevKit = toml::find_or(general, "isDevKit", false); - isPSNSignedIn = toml::find_or(general, "isPSNSignedIn", false); - isTrophyPopupDisabled = toml::find_or(general, "isTrophyPopupDisabled", false); - trophyNotificationDuration = - toml::find_or(general, "trophyNotificationDuration", 5.0); - enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", true); - logFilter = toml::find_or(general, "logFilter", ""); - logType = toml::find_or(general, "logType", "sync"); - userName = toml::find_or(general, "userName", "shadPS4"); - isShowSplash = toml::find_or(general, "showSplash", true); - isSideTrophy = toml::find_or(general, "sideTrophy", "right"); - compatibilityData = toml::find_or(general, "compatibilityEnabled", false); - checkCompatibilityOnStartup = - toml::find_or(general, "checkCompatibilityOnStartup", false); - chooseHomeTab = toml::find_or(general, "chooseHomeTab", "Release"); + isNeo = toml::find_or(general, "isPS4Pro", isNeo); + isDevKit = toml::find_or(general, "isDevKit", isDevKit); + isPSNSignedIn = toml::find_or(general, "isPSNSignedIn", isPSNSignedIn); + isTrophyPopupDisabled = + toml::find_or(general, "isTrophyPopupDisabled", isTrophyPopupDisabled); + trophyNotificationDuration = toml::find_or(general, "trophyNotificationDuration", + trophyNotificationDuration); + enableDiscordRPC = toml::find_or(general, "enableDiscordRPC", enableDiscordRPC); + logFilter = toml::find_or(general, "logFilter", logFilter); + logType = toml::find_or(general, "logType", logType); + userName = toml::find_or(general, "userName", userName); + isShowSplash = toml::find_or(general, "showSplash", isShowSplash); + isSideTrophy = toml::find_or(general, "sideTrophy", isSideTrophy); + compatibilityData = toml::find_or(general, "compatibilityEnabled", compatibilityData); + checkCompatibilityOnStartup = toml::find_or(general, "checkCompatibilityOnStartup", + checkCompatibilityOnStartup); + chooseHomeTab = toml::find_or(general, "chooseHomeTab", chooseHomeTab); + + entry_count += general.size(); } if (data.contains("Input")) { const toml::value& input = data.at("Input"); - cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); - cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); - useSpecialPad = toml::find_or(input, "useSpecialPad", false); - specialPadClass = toml::find_or(input, "specialPadClass", 1); - isMotionControlsEnabled = toml::find_or(input, "isMotionControlsEnabled", true); - useUnifiedInputConfig = toml::find_or(input, "useUnifiedInputConfig", true); + cursorState = toml::find_or(input, "cursorState", cursorState); + cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", cursorHideTimeout); + useSpecialPad = toml::find_or(input, "useSpecialPad", useSpecialPad); + specialPadClass = toml::find_or(input, "specialPadClass", specialPadClass); + isMotionControlsEnabled = + toml::find_or(input, "isMotionControlsEnabled", isMotionControlsEnabled); + useUnifiedInputConfig = + toml::find_or(input, "useUnifiedInputConfig", useUnifiedInputConfig); + + entry_count += input.size(); } if (data.contains("GPU")) { const toml::value& gpu = data.at("GPU"); - screenWidth = toml::find_or(gpu, "screenWidth", screenWidth); - screenHeight = toml::find_or(gpu, "screenHeight", screenHeight); - isNullGpu = toml::find_or(gpu, "nullGpu", false); - shouldCopyGPUBuffers = toml::find_or(gpu, "copyGPUBuffers", false); - shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); - shouldPatchShaders = toml::find_or(gpu, "patchShaders", true); - vblankDivider = toml::find_or(gpu, "vblankDivider", 1); - isFullscreen = toml::find_or(gpu, "Fullscreen", false); - fullscreenMode = toml::find_or(gpu, "FullscreenMode", "Windowed"); - isHDRAllowed = toml::find_or(gpu, "allowHDR", false); + windowWidth = toml::find_or(gpu, "screenWidth", windowWidth); + windowHeight = toml::find_or(gpu, "screenHeight", windowHeight); + internalScreenWidth = toml::find_or(gpu, "internalScreenWidth", internalScreenWidth); + internalScreenHeight = + toml::find_or(gpu, "internalScreenHeight", internalScreenHeight); + isNullGpu = toml::find_or(gpu, "nullGpu", isNullGpu); + shouldCopyGPUBuffers = toml::find_or(gpu, "copyGPUBuffers", shouldCopyGPUBuffers); + readbacksEnabled = toml::find_or(gpu, "readbacks", readbacksEnabled); + readbackLinearImagesEnabled = + toml::find_or(gpu, "readbackLinearImages", readbackLinearImagesEnabled); + directMemoryAccessEnabled = + toml::find_or(gpu, "directMemoryAccess", directMemoryAccessEnabled); + shouldDumpShaders = toml::find_or(gpu, "dumpShaders", shouldDumpShaders); + shouldPatchShaders = toml::find_or(gpu, "patchShaders", shouldPatchShaders); + vblankDivider = toml::find_or(gpu, "vblankDivider", vblankDivider); + isFullscreen = toml::find_or(gpu, "Fullscreen", isFullscreen); + fullscreenMode = toml::find_or(gpu, "FullscreenMode", fullscreenMode); + isHDRAllowed = toml::find_or(gpu, "allowHDR", isHDRAllowed); + + entry_count += gpu.size(); } if (data.contains("Vulkan")) { const toml::value& vk = data.at("Vulkan"); - gpuId = toml::find_or(vk, "gpuId", -1); - vkValidation = toml::find_or(vk, "validation", false); - vkValidationSync = toml::find_or(vk, "validation_sync", false); - vkValidationGpu = toml::find_or(vk, "validation_gpu", true); - vkCrashDiagnostic = toml::find_or(vk, "crashDiagnostic", false); - vkHostMarkers = toml::find_or(vk, "hostMarkers", false); - vkGuestMarkers = toml::find_or(vk, "guestMarkers", false); - rdocEnable = toml::find_or(vk, "rdocEnable", false); + gpuId = toml::find_or(vk, "gpuId", gpuId); + vkValidation = toml::find_or(vk, "validation", vkValidation); + vkValidationSync = toml::find_or(vk, "validation_sync", vkValidationSync); + vkValidationGpu = toml::find_or(vk, "validation_gpu", vkValidationGpu); + vkCrashDiagnostic = toml::find_or(vk, "crashDiagnostic", vkCrashDiagnostic); + vkHostMarkers = toml::find_or(vk, "hostMarkers", vkHostMarkers); + vkGuestMarkers = toml::find_or(vk, "guestMarkers", vkGuestMarkers); + rdocEnable = toml::find_or(vk, "rdocEnable", rdocEnable); + + entry_count += vk.size(); } if (data.contains("Debug")) { const toml::value& debug = data.at("Debug"); - isDebugDump = toml::find_or(debug, "DebugDump", false); - isSeparateLogFilesEnabled = toml::find_or(debug, "isSeparateLogFilesEnabled", false); - isShaderDebug = toml::find_or(debug, "CollectShader", false); - isFpsColor = toml::find_or(debug, "FPSColor", true); + isDebugDump = toml::find_or(debug, "DebugDump", isDebugDump); + isSeparateLogFilesEnabled = + toml::find_or(debug, "isSeparateLogFilesEnabled", isSeparateLogFilesEnabled); + isShaderDebug = toml::find_or(debug, "CollectShader", isShaderDebug); + isFpsColor = toml::find_or(debug, "FPSColor", isFpsColor); + + entry_count += debug.size(); } if (data.contains("GUI")) { const toml::value& gui = data.at("GUI"); - load_game_size = toml::find_or(gui, "loadGameSizeEnabled", true); + load_game_size = toml::find_or(gui, "loadGameSizeEnabled", load_game_size); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); @@ -641,20 +724,32 @@ void load(const std::filesystem::path& path) { {std::filesystem::path{install_dir_array[i]}, install_dirs_enabled[i]}); } - save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); + save_data_path = toml::find_fs_path_or(gui, "saveDataPath", save_data_path); - settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); + settings_addon_install_dir = + toml::find_fs_path_or(gui, "addonInstallDir", settings_addon_install_dir); + + entry_count += gui.size(); } if (data.contains("Settings")) { const toml::value& settings = data.at("Settings"); + m_language = toml::find_or(settings, "consoleLanguage", m_language); - m_language = toml::find_or(settings, "consoleLanguage", 1); + entry_count += settings.size(); } if (data.contains("Keys")) { const toml::value& keys = data.at("Keys"); - trophyKey = toml::find_or(keys, "TrophyKey", ""); + trophyKey = toml::find_or(keys, "TrophyKey", trophyKey); + + entry_count += keys.size(); + } + + // Run save after loading to generate any missing fields with default values. + if (entry_count != total_entries) { + fmt::print("Outdated config detected, updating config file.\n"); + save(path); } } @@ -731,10 +826,15 @@ void save(const std::filesystem::path& path) { data["Input"]["specialPadClass"] = specialPadClass; data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled; data["Input"]["useUnifiedInputConfig"] = useUnifiedInputConfig; - data["GPU"]["screenWidth"] = screenWidth; - data["GPU"]["screenHeight"] = screenHeight; + data["GPU"]["screenWidth"] = windowWidth; + data["GPU"]["screenHeight"] = windowHeight; + data["GPU"]["internalScreenWidth"] = internalScreenWidth; + data["GPU"]["internalScreenHeight"] = internalScreenHeight; data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; + data["GPU"]["readbacks"] = readbacksEnabled; + data["GPU"]["readbackLinearImages"] = readbackLinearImagesEnabled; + data["GPU"]["directMemoryAccess"] = directMemoryAccessEnabled; data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["patchShaders"] = shouldPatchShaders; data["GPU"]["vblankDivider"] = vblankDivider; @@ -800,32 +900,53 @@ void save(const std::filesystem::path& path) { } void setDefaultValues() { - isHDRAllowed = false; + // General isNeo = false; isDevKit = false; isPSNSignedIn = false; - isFullscreen = false; isTrophyPopupDisabled = false; - enableDiscordRPC = true; - screenWidth = 1280; - screenHeight = 720; + trophyNotificationDuration = 6.0; + enableDiscordRPC = false; logFilter = ""; logType = "sync"; userName = "shadPS4"; - chooseHomeTab = "General"; - cursorState = HideCursorState::Idle; - cursorHideTimeout = 5; - trophyNotificationDuration = 6.0; - useSpecialPad = false; - specialPadClass = 1; - isDebugDump = false; - isShaderDebug = false; isShowSplash = false; isSideTrophy = "right"; + compatibilityData = false; + checkCompatibilityOnStartup = false; + + // Input + cursorState = HideCursorState::Idle; + cursorHideTimeout = 5; + useSpecialPad = false; + specialPadClass = 1; + isMotionControlsEnabled = true; + useUnifiedInputConfig = true; + overrideControllerColor = false; + controllerCustomColorRGB[0] = 0; + controllerCustomColorRGB[1] = 0; + controllerCustomColorRGB[2] = 255; + + // GPU + windowWidth = 1280; + windowHeight = 720; + internalScreenWidth = 1280; + internalScreenHeight = 720; isNullGpu = false; + shouldCopyGPUBuffers = false; + readbacksEnabled = false; + readbackLinearImagesEnabled = false; + directMemoryAccessEnabled = false; shouldDumpShaders = false; + shouldPatchShaders = false; vblankDivider = 1; + isFullscreen = false; + fullscreenMode = "Windowed"; + isHDRAllowed = false; + + // Vulkan + gpuId = -1; vkValidation = false; vkValidationSync = false; vkValidationGpu = false; @@ -833,10 +954,18 @@ void setDefaultValues() { vkHostMarkers = false; vkGuestMarkers = false; rdocEnable = false; + + // Debug + isDebugDump = false; + isShaderDebug = false; + isSeparateLogFilesEnabled = false; + isFpsColor = true; + + // GUI + load_game_size = true; + + // Settings m_language = 1; - gpuId = -1; - compatibilityData = false; - checkCompatibilityOnStartup = false; } constexpr std::string_view GetDefaultKeyboardConfig() { diff --git a/src/common/config.h b/src/common/config.h index 38114983f..e54425676 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -25,10 +25,14 @@ bool getIsFullscreen(); void setIsFullscreen(bool enable); std::string getFullscreenMode(); void setFullscreenMode(std::string mode); -u32 getScreenWidth(); -u32 getScreenHeight(); -void setScreenWidth(u32 width); -void setScreenHeight(u32 height); +u32 getWindowWidth(); +u32 getWindowHeight(); +void setWindowWidth(u32 width); +void setWindowHeight(u32 height); +u32 getInternalScreenWidth(); +u32 getInternalScreenHeight(); +void setInternalScreenWidth(u32 width); +void setInternalScreenHeight(u32 height); bool debugDump(); void setDebugDump(bool enable); s32 getGpuId(); @@ -45,6 +49,11 @@ bool nullGpu(); void setNullGpu(bool enable); bool copyGPUCmdBuffers(); void setCopyGPUCmdBuffers(bool enable); +bool readbacks(); +void setReadbacks(bool enable); +bool readbackLinearImages(); +bool directMemoryAccess(); +void setDirectMemoryAccess(bool enable); bool dumpShaders(); void setDumpShaders(bool enable); u32 vblankDiv(); diff --git a/src/common/range_lock.h b/src/common/range_lock.h new file mode 100644 index 000000000..efe6eb549 --- /dev/null +++ b/src/common/range_lock.h @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +// From boost thread locking + +template +struct RangeLockGuard { + Iterator begin; + Iterator end; + + RangeLockGuard(Iterator begin_, Iterator end_) : begin(begin_), end(end_) { + LockRange(begin, end); + } + + void release() { + begin = end; + } + + ~RangeLockGuard() { + for (; begin != end; ++begin) { + begin->unlock(); + } + } +}; + +template +Iterator TryLockRange(Iterator begin, Iterator end) { + using LockType = typename std::iterator_traits::value_type; + + if (begin == end) { + return end; + } + + std::unique_lock guard(*begin, std::try_to_lock); + if (!guard.owns_lock()) { + return begin; + } + + Iterator failed = TryLockRange(++begin, end); + if (failed == end) { + guard.release(); + } + + return failed; +} + +template +void LockRange(Iterator begin, Iterator end) { + using LockType = typename std::iterator_traits::value_type; + + if (begin == end) { + return; + } + + bool start_with_begin = true; + Iterator second = begin; + ++second; + Iterator next = second; + + while (true) { + std::unique_lock begin_lock(*begin, std::defer_lock); + if (start_with_begin) { + begin_lock.lock(); + + const Iterator failed_lock = TryLockRange(next, end); + if (failed_lock == end) { + begin_lock.release(); + return; + } + + start_with_begin = false; + next = failed_lock; + } else { + RangeLockGuard guard(next, end); + + if (begin_lock.try_lock()) { + const Iterator failed_lock = TryLockRange(second, next); + if (failed_lock == next) { + begin_lock.release(); + guard.release(); + return; + } + + start_with_begin = false; + next = failed_lock; + } else { + start_with_begin = true; + next = second; + } + } + } +} + +} // namespace Common \ No newline at end of file diff --git a/src/common/shared_first_mutex.h b/src/common/shared_first_mutex.h new file mode 100644 index 000000000..b150c956b --- /dev/null +++ b/src/common/shared_first_mutex.h @@ -0,0 +1,46 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +namespace Common { + +// Like std::shared_mutex, but reader has priority over writer. +class SharedFirstMutex { +public: + void lock() { + std::unique_lock lock(mtx); + cv.wait(lock, [this]() { return !writer_active && readers == 0; }); + writer_active = true; + } + + void unlock() { + std::lock_guard lock(mtx); + writer_active = false; + cv.notify_all(); + } + + void lock_shared() { + std::unique_lock lock(mtx); + cv.wait(lock, [this]() { return !writer_active; }); + ++readers; + } + + void unlock_shared() { + std::lock_guard lock(mtx); + if (--readers == 0) { + cv.notify_all(); + } + } + +private: + std::mutex mtx; + std::condition_variable cv; + int readers = 0; + bool writer_active = false; +}; + +} // namespace Common diff --git a/src/core/address_space.cpp b/src/core/address_space.cpp index 2e66bdf83..846bb5eb4 100644 --- a/src/core/address_space.cpp +++ b/src/core/address_space.cpp @@ -302,14 +302,15 @@ struct AddressSpace::Impl { new_flags = PAGE_READWRITE; } else if (read && !write) { new_flags = PAGE_READONLY; - } else if (execute && !read && not write) { + } else if (execute && !read && !write) { new_flags = PAGE_EXECUTE; } else if (!read && !write && !execute) { new_flags = PAGE_NOACCESS; } else { LOG_CRITICAL(Common_Memory, - "Unsupported protection flag combination for address {:#x}, size {}", - virtual_addr, size); + "Unsupported protection flag combination for address {:#x}, size {}, " + "read={}, write={}, execute={}", + virtual_addr, size, read, write, execute); return; } @@ -357,9 +358,17 @@ enum PosixPageProtection { [[nodiscard]] constexpr PosixPageProtection ToPosixProt(Core::MemoryProt prot) { if (True(prot & Core::MemoryProt::CpuReadWrite) || True(prot & Core::MemoryProt::GpuReadWrite)) { - return PAGE_READWRITE; + if (True(prot & Core::MemoryProt::CpuExec)) { + return PAGE_EXECUTE_READWRITE; + } else { + return PAGE_READWRITE; + } } else if (True(prot & Core::MemoryProt::CpuRead) || True(prot & Core::MemoryProt::GpuRead)) { - return PAGE_READONLY; + if (True(prot & Core::MemoryProt::CpuExec)) { + return PAGE_EXECUTE_READ; + } else { + return PAGE_READONLY; + } } else { return PAGE_NOACCESS; } diff --git a/src/core/address_space.h b/src/core/address_space.h index d7f3efc75..85b4c36ac 100644 --- a/src/core/address_space.h +++ b/src/core/address_space.h @@ -11,6 +11,7 @@ namespace Core { enum class MemoryPermission : u32 { + None = 0, Read = 1 << 0, Write = 1 << 1, ReadWrite = Read | Write, diff --git a/src/core/cpu_patches.cpp b/src/core/cpu_patches.cpp index 8512858e9..e4f65cd31 100644 --- a/src/core/cpu_patches.cpp +++ b/src/core/cpu_patches.cpp @@ -163,7 +163,9 @@ static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operan mask = (1ULL << length) - 1; } - ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64."); + if (length + index > 64) { + mask = 0xFFFF'FFFF'FFFF'FFFF; + } // Get lower qword from xmm register c.vmovq(scratch1, xmm_dst); @@ -177,8 +179,8 @@ static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operan c.mov(scratch2, mask); c.and_(scratch1, scratch2); - // Writeback to xmm register, extrq instruction says top 64-bits are undefined so we don't - // care to preserve them + // Writeback to xmm register, extrq instruction says top 64-bits are undefined but zeroed on + // AMD CPUs c.vmovq(xmm_dst, scratch1); c.pop(scratch2); @@ -287,7 +289,9 @@ static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* oper mask_value = (1ULL << length) - 1; } - ASSERT_MSG(length + index <= 64, "length + index must be less than or equal to 64."); + if (length + index > 64) { + mask_value = 0xFFFF'FFFF'FFFF'FFFF; + } c.vmovq(scratch1, xmm_src); c.vmovq(scratch2, xmm_dst); @@ -307,8 +311,9 @@ static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* oper // dst |= src c.or_(scratch2, scratch1); - // Insert scratch2 into low 64 bits of dst, upper 64 bits are unaffected - c.vpinsrq(xmm_dst, xmm_dst, scratch2, 0); + // Insert scratch2 into low 64 bits of dst, upper 64 bits are undefined but zeroed on AMD + // CPUs + c.vmovq(xmm_dst, scratch2); c.pop(mask); c.pop(scratch2); @@ -374,7 +379,7 @@ static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* oper c.and_(scratch2, mask); c.or_(scratch2, scratch1); - // Upper 64 bits are undefined in insertq + // Upper 64 bits are undefined in insertq but AMD CPUs zero them c.vmovq(xmm_dst, scratch2); c.pop(mask); @@ -635,6 +640,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) { lowQWordDst >>= index; lowQWordDst &= mask; + memset((u8*)dst + sizeof(u64), 0, sizeof(u64)); memcpy(dst, &lowQWordDst, sizeof(lowQWordDst)); Common::IncrementRip(ctx, 4); @@ -675,6 +681,7 @@ static bool TryExecuteIllegalInstruction(void* ctx, void* code_address) { lowQWordDst &= ~(mask << index); lowQWordDst |= lowQWordSrc << index; + memset((u8*)dst + sizeof(u64), 0, sizeof(u64)); memcpy(dst, &lowQWordDst, sizeof(lowQWordDst)); Common::IncrementRip(ctx, 4); diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 9cf340050..8c3ab1612 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -2834,7 +2834,7 @@ void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) { } if (Config::copyGPUCmdBuffers()) { - liverpool->reserveCopyBufferSpace(); + liverpool->ReserveCopyBufferSpace(); } Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock, diff --git a/src/core/libraries/ime/ime.cpp b/src/core/libraries/ime/ime.cpp index 1c61bc276..54e856e87 100644 --- a/src/core/libraries/ime/ime.cpp +++ b/src/core/libraries/ime/ime.cpp @@ -43,8 +43,8 @@ public: openEvent.param.rect.x = m_param.ime.posx; openEvent.param.rect.y = m_param.ime.posy; } else { - openEvent.param.resource_id_array.userId = 1; - openEvent.param.resource_id_array.resourceId[0] = 1; + openEvent.param.resource_id_array.user_id = 1; + openEvent.param.resource_id_array.resource_id[0] = 1; } // Are we supposed to call the event handler on init with @@ -59,10 +59,10 @@ public: } } - s32 Update(OrbisImeEventHandler handler) { + Error Update(OrbisImeEventHandler handler) { if (!m_ime_mode) { /* We don't handle any events for ImeKeyboard */ - return ORBIS_OK; + return Error::OK; } std::unique_lock lock{g_ime_state.queue_mutex}; @@ -73,7 +73,7 @@ public: Execute(handler, &event, false); } - return ORBIS_OK; + return Error::OK; } void Execute(OrbisImeEventHandler handler, OrbisImeEvent* event, bool use_param_handler) { @@ -94,14 +94,14 @@ public: } } - s32 SetText(const char16_t* text, u32 length) { + Error SetText(const char16_t* text, u32 length) { g_ime_state.SetText(text, length); - return ORBIS_OK; + return Error::OK; } - s32 SetCaret(const OrbisImeCaret* caret) { + Error SetCaret(const OrbisImeCaret* caret) { g_ime_state.SetCaret(caret->index); - return ORBIS_OK; + return Error::OK; } bool IsIme() { @@ -222,11 +222,11 @@ int PS4_SYSV_ABI sceImeGetPanelPositionAndForm() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { +Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height) { LOG_INFO(Lib_Ime, "called"); if (!width || !height) { - return ORBIS_IME_ERROR_INVALID_ADDRESS; + return Error::INVALID_ADDRESS; } switch (param->type) { @@ -244,18 +244,18 @@ s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* break; } - return ORBIS_OK; + return Error::OK; } -s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) { +Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId) { LOG_INFO(Lib_Ime, "(STUBBED) called"); if (!g_keyboard_handler) { - return ORBIS_IME_ERROR_NOT_OPENED; + return Error::NOT_OPENED; } g_keyboard_handler.release(); - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceImeKeyboardGetInfo() { @@ -268,25 +268,25 @@ int PS4_SYSV_ABI sceImeKeyboardGetResourceId() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) { +Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param) { LOG_INFO(Lib_Ime, "called"); if (!param) { - return ORBIS_IME_ERROR_INVALID_ADDRESS; + return Error::INVALID_ADDRESS; } if (!param->arg) { - return ORBIS_IME_ERROR_INVALID_ARG; + return Error::INVALID_ARG; } if (!param->handler) { - return ORBIS_IME_ERROR_INVALID_HANDLER; + return Error::INVALID_HANDLER; } if (g_keyboard_handler) { - return ORBIS_IME_ERROR_BUSY; + return Error::BUSY; } g_keyboard_handler = std::make_unique(param); - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceImeKeyboardOpenInternal() { @@ -304,18 +304,18 @@ int PS4_SYSV_ABI sceImeKeyboardUpdate() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended) { +Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended) { LOG_INFO(Lib_Ime, "called"); if (!param) { - return ORBIS_IME_ERROR_INVALID_ADDRESS; + return Error::INVALID_ADDRESS; } if (g_ime_handler) { - return ORBIS_IME_ERROR_BUSY; + return Error::BUSY; } g_ime_handler = std::make_unique(param); - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceImeOpenInternal() { @@ -339,27 +339,27 @@ int PS4_SYSV_ABI sceImeSetCandidateIndex() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) { +Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret) { LOG_TRACE(Lib_Ime, "called"); if (!g_ime_handler) { - return ORBIS_IME_ERROR_NOT_OPENED; + return Error::NOT_OPENED; } if (!caret) { - return ORBIS_IME_ERROR_INVALID_ADDRESS; + return Error::INVALID_ADDRESS; } return g_ime_handler->SetCaret(caret); } -s32 PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) { +Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length) { LOG_TRACE(Lib_Ime, "called"); if (!g_ime_handler) { - return ORBIS_IME_ERROR_NOT_OPENED; + return Error::NOT_OPENED; } if (!text) { - return ORBIS_IME_ERROR_INVALID_ADDRESS; + return Error::INVALID_ADDRESS; } return g_ime_handler->SetText(text, length); @@ -370,7 +370,7 @@ int PS4_SYSV_ABI sceImeSetTextGeometry() { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { +Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { if (g_ime_handler) { g_ime_handler->Update(handler); } @@ -380,10 +380,10 @@ s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler) { } if (!g_ime_handler || !g_keyboard_handler) { - return ORBIS_IME_ERROR_NOT_OPENED; + return Error::NOT_OPENED; } - return ORBIS_OK; + return Error::OK; } int PS4_SYSV_ABI sceImeVshClearPreedit() { diff --git a/src/core/libraries/ime/ime.h b/src/core/libraries/ime/ime.h index fcf381048..c2b80809c 100644 --- a/src/core/libraries/ime/ime.h +++ b/src/core/libraries/ime/ime.h @@ -13,72 +13,6 @@ class SymbolsResolver; namespace Libraries::Ime { -constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048; - -enum class OrbisImeKeyboardOption : u32 { - Default = 0, - Repeat = 1, - RepeatEachKey = 2, - AddOsk = 4, - EffectiveWithIme = 8, - DisableResume = 16, - DisableCapslockWithoutShift = 32, -}; -DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption) - -enum class OrbisImeOption : u32 { - DEFAULT = 0, - MULTILINE = 1, - NO_AUTO_CAPITALIZATION = 2, - PASSWORD = 4, - LANGUAGES_FORCED = 8, - EXT_KEYBOARD = 16, - NO_LEARNING = 32, - FIXED_POSITION = 64, - DISABLE_RESUME = 256, - DISABLE_AUTO_SPACE = 512, - DISABLE_POSITION_ADJUSTMENT = 2048, - EXPANDED_PREEDIT_BUFFER = 4096, - USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192, - USE_2K_COORDINATES = 16384, -}; -DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption) - -struct OrbisImeKeyboardParam { - OrbisImeKeyboardOption option; - s8 reserved1[4]; - void* arg; - OrbisImeEventHandler handler; - s8 reserved2[8]; -}; - -struct OrbisImeParam { - s32 user_id; - OrbisImeType type; - u64 supported_languages; - OrbisImeEnterLabel enter_label; - OrbisImeInputMethod input_method; - OrbisImeTextFilter filter; - OrbisImeOption option; - u32 maxTextLength; - char16_t* inputTextBuffer; - float posx; - float posy; - OrbisImeHorizontalAlignment horizontal_alignment; - OrbisImeVerticalAlignment vertical_alignment; - void* work; - void* arg; - OrbisImeEventHandler handler; - s8 reserved[8]; -}; - -struct OrbisImeCaret { - f32 x; - f32 y; - u32 height; - u32 index; -}; - int PS4_SYSV_ABI FinalizeImeModule(); int PS4_SYSV_ABI InitializeImeModule(); int PS4_SYSV_ABI sceImeCheckFilterText(); @@ -98,22 +32,22 @@ int PS4_SYSV_ABI sceImeDisableController(); int PS4_SYSV_ABI sceImeFilterText(); int PS4_SYSV_ABI sceImeForTestFunction(); int PS4_SYSV_ABI sceImeGetPanelPositionAndForm(); -s32 PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height); -s32 PS4_SYSV_ABI sceImeKeyboardClose(s32 userId); +Error PS4_SYSV_ABI sceImeGetPanelSize(const OrbisImeParam* param, u32* width, u32* height); +Error PS4_SYSV_ABI sceImeKeyboardClose(s32 userId); int PS4_SYSV_ABI sceImeKeyboardGetInfo(); int PS4_SYSV_ABI sceImeKeyboardGetResourceId(); -s32 PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param); +Error PS4_SYSV_ABI sceImeKeyboardOpen(s32 userId, const OrbisImeKeyboardParam* param); int PS4_SYSV_ABI sceImeKeyboardOpenInternal(); int PS4_SYSV_ABI sceImeKeyboardSetMode(); int PS4_SYSV_ABI sceImeKeyboardUpdate(); -s32 PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const void* extended); +Error PS4_SYSV_ABI sceImeOpen(const OrbisImeParam* param, const OrbisImeParamExtended* extended); int PS4_SYSV_ABI sceImeOpenInternal(); void PS4_SYSV_ABI sceImeParamInit(OrbisImeParam* param); int PS4_SYSV_ABI sceImeSetCandidateIndex(); -s32 PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret); -s32 PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length); +Error PS4_SYSV_ABI sceImeSetCaret(const OrbisImeCaret* caret); +Error PS4_SYSV_ABI sceImeSetText(const char16_t* text, u32 length); int PS4_SYSV_ABI sceImeSetTextGeometry(); -s32 PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler); +Error PS4_SYSV_ABI sceImeUpdate(OrbisImeEventHandler handler); int PS4_SYSV_ABI sceImeVshClearPreedit(); int PS4_SYSV_ABI sceImeVshClose(); int PS4_SYSV_ABI sceImeVshConfirmPreedit(); diff --git a/src/core/libraries/ime/ime_common.h b/src/core/libraries/ime/ime_common.h index 96f073dc5..5c0030030 100644 --- a/src/core/libraries/ime/ime_common.h +++ b/src/core/libraries/ime/ime_common.h @@ -3,9 +3,108 @@ #pragma once +#include "common/enum.h" #include "common/types.h" #include "core/libraries/rtc/rtc.h" +constexpr u32 ORBIS_IME_MAX_TEXT_LENGTH = 2048; +constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048; + +enum class Error : u32 { + OK = 0x0, + BUSY = 0x80bc0001, + NOT_OPENED = 0x80bc0002, + NO_MEMORY = 0x80bc0003, + CONNECTION_FAILED = 0x80bc0004, + TOO_MANY_REQUESTS = 0x80bc0005, + INVALID_TEXT = 0x80bc0006, + EVENT_OVERFLOW = 0x80bc0007, + NOT_ACTIVE = 0x80bc0008, + IME_SUSPENDING = 0x80bc0009, + DEVICE_IN_USE = 0x80bc000a, + INVALID_USER_ID = 0x80bc0010, + INVALID_TYPE = 0x80bc0011, + INVALID_SUPPORTED_LANGUAGES = 0x80bc0012, + INVALID_ENTER_LABEL = 0x80bc0013, + INVALID_INPUT_METHOD = 0x80bc0014, + INVALID_OPTION = 0x80bc0015, + INVALID_MAX_TEXT_LENGTH = 0x80bc0016, + INVALID_INPUT_TEXT_BUFFER = 0x80bc0017, + INVALID_POSX = 0x80bc0018, + INVALID_POSY = 0x80bc0019, + INVALID_HORIZONTALIGNMENT = 0x80bc001a, + INVALID_VERTICALALIGNMENT = 0x80bc001b, + INVALID_EXTENDED = 0x80bc001c, + INVALID_KEYBOARD_TYPE = 0x80bc001d, + INVALID_WORK = 0x80bc0020, + INVALID_ARG = 0x80bc0021, + INVALID_HANDLER = 0x80bc0022, + NO_RESOURCE_ID = 0x80bc0023, + INVALID_MODE = 0x80bc0024, + INVALID_PARAM = 0x80bc0030, + INVALID_ADDRESS = 0x80bc0031, + INVALID_RESERVED = 0x80bc0032, + INVALID_TIMING = 0x80bc0033, + INTERNAL = 0x80bc00ff, + DIALOG_INVALID_TITLE = 0x80bc0101, + DIALOG_NOT_RUNNING = 0x80bc0105, + DIALOG_NOT_FINISHED = 0x80bc0106, + DIALOG_NOT_IN_USE = 0x80bc0107 +}; + +enum class OrbisImeOption : u32 { + DEFAULT = 0, + MULTILINE = 1, + NO_AUTO_CAPITALIZATION = 2, + PASSWORD = 4, + LANGUAGES_FORCED = 8, + EXT_KEYBOARD = 16, + NO_LEARNING = 32, + FIXED_POSITION = 64, + DISABLE_COPY_PASTE = 128, + DISABLE_RESUME = 256, + DISABLE_AUTO_SPACE = 512, + DISABLE_POSITION_ADJUSTMENT = 2048, + EXPANDED_PREEDIT_BUFFER = 4096, + USE_JAPANESE_EISUU_KEY_AS_CAPSLOCK = 8192, + USE_2K_COORDINATES = 16384, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeOption); + +enum class OrbisImeLanguage : u64 { + DANISH = 0x0000000000000001, + GERMAN = 0x0000000000000002, + ENGLISH_US = 0x0000000000000004, + SPANISH = 0x0000000000000008, + FRENCH = 0x0000000000000010, + ITALIAN = 0x0000000000000020, + DUTCH = 0x0000000000000040, + NORWEGIAN = 0x0000000000000080, + POLISH = 0x0000000000000100, + PORTUGUESE_PT = 0x0000000000000200, + RUSSIAN = 0x0000000000000400, + FINNISH = 0x0000000000000800, + SWEDISH = 0x0000000000001000, + JAPANESE = 0x0000000000002000, + KOREAN = 0x0000000000004000, + SIMPLIFIED_CHINESE = 0x0000000000008000, + TRADITIONAL_CHINESE = 0x0000000000010000, + PORTUGUESE_BR = 0x0000000000020000, + ENGLISH_GB = 0x0000000000040000, + TURKISH = 0x0000000000080000, + SPANISH_LA = 0x0000000000100000, + ARABIC = 0x0000000001000000, + FRENCH_CA = 0x0000000002000000, + THAI = 0x0000000004000000, + CZECH = 0x0000000008000000, + GREEK = 0x0000000010000000, + INDONESIAN = 0x0000000020000000, + VIETNAMESE = 0x0000000040000000, + ROMANIAN = 0x0000000080000000, + HUNGARIAN = 0x0000000100000000, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeLanguage); + enum class OrbisImeType : u32 { Default = 0, BasicLatin = 1, @@ -41,6 +140,7 @@ enum class OrbisImeEventId : u32 { Open = 0, UpdateText = 1, UpdateCaret = 2, + ChangeSize = 3, PressClose = 4, PressEnter = 5, Abort = 6, @@ -51,6 +151,10 @@ enum class OrbisImeEventId : u32 { CandidateDone = 11, CandidateCancel = 12, ChangeDevice = 14, + JumpToNextObject = 15, + JumpToBeforeObject = 16, + ChangeWindowType = 17, + ChangeInputMethodState = 18, KeyboardOpen = 256, @@ -110,6 +214,13 @@ enum class OrbisImeDeviceType : u32 { RemoteOsk = 3, }; +enum class OrbisImePanelPriority : u32 { + Default = 0, + Alphabet = 1, + Symbol = 2, + Accent = 3, +}; + struct OrbisImeRect { f32 x; f32 y; @@ -117,8 +228,22 @@ struct OrbisImeRect { u32 height; }; +struct OrbisImeColor { + u8 r; + u8 g; + u8 b; + u8 a; +}; + +enum class OrbisImeTextAreaMode : u32 { + Disable = 0, + Edit = 1, + Preedit = 2, + Select = 3, +}; + struct OrbisImeTextAreaProperty { - u32 mode; // OrbisImeTextAreaMode + OrbisImeTextAreaMode mode; u32 index; s32 length; }; @@ -135,14 +260,14 @@ struct OrbisImeKeycode { char16_t character; u32 status; OrbisImeKeyboardType type; - s32 user_id; + s32 user_id; // Todo: switch to OrbisUserServiceUserId u32 resource_id; Libraries::Rtc::OrbisRtcTick timestamp; }; struct OrbisImeKeyboardResourceIdArray { - s32 userId; - u32 resourceId[5]; + s32 user_id; // Todo: switch to OrbisUserServiceUserId + u32 resource_id[5]; }; enum class OrbisImeCaretMovementDirection : u32 { @@ -159,6 +284,16 @@ enum class OrbisImeCaretMovementDirection : u32 { Bottom = 10, }; +enum class OrbisImePanelType : u32 { + Hide = 0, + Osk = 1, + Dialog = 2, + Candidate = 3, + Edit = 4, + EditAndCandidate = 5, + Accessibility = 6, +}; + union OrbisImeEventParam { OrbisImeRect rect; OrbisImeEditText text; @@ -168,6 +303,7 @@ union OrbisImeEventParam { char16_t* candidate_word; s32 candidate_index; OrbisImeDeviceType device_type; + OrbisImePanelType panel_type; u32 input_method_state; s8 reserved[64]; }; @@ -177,7 +313,95 @@ struct OrbisImeEvent { OrbisImeEventParam param; }; +using OrbisImeExtKeyboardFilter = PS4_SYSV_ABI int (*)(const OrbisImeKeycode* srcKeycode, + u16* outKeycode, u32* outStatus, + void* reserved); + using OrbisImeTextFilter = PS4_SYSV_ABI int (*)(char16_t* outText, u32* outTextLength, const char16_t* srcText, u32 srcTextLength); using OrbisImeEventHandler = PS4_SYSV_ABI void (*)(void* arg, const OrbisImeEvent* e); + +enum class OrbisImeKeyboardOption : u32 { + Default = 0, + Repeat = 1, + RepeatEachKey = 2, + AddOsk = 4, + EffectiveWithIme = 8, + DisableResume = 16, + DisableCapslockWithoutShift = 32, +}; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeKeyboardOption) + +struct OrbisImeKeyboardParam { + OrbisImeKeyboardOption option; + s8 reserved1[4]; + void* arg; + OrbisImeEventHandler handler; + s8 reserved2[8]; +}; + +struct OrbisImeParam { + s32 user_id; // Todo: switch to OrbisUserServiceUserId + OrbisImeType type; + u64 supported_languages; // OrbisImeLanguage flags + OrbisImeEnterLabel enter_label; + OrbisImeInputMethod input_method; + OrbisImeTextFilter filter; + OrbisImeOption option; + u32 maxTextLength; + char16_t* inputTextBuffer; + f32 posx; + f32 posy; + OrbisImeHorizontalAlignment horizontal_alignment; + OrbisImeVerticalAlignment vertical_alignment; + void* work; + void* arg; + OrbisImeEventHandler handler; + s8 reserved[8]; +}; + +struct OrbisImeCaret { + f32 x; + f32 y; + u32 height; + u32 index; +}; + +struct OrbisImeDialogParam { + s32 user_id; + OrbisImeType type; + u64 supported_languages; // OrbisImeLanguage flags + OrbisImeEnterLabel enter_label; + OrbisImeInputMethod input_method; + OrbisImeTextFilter filter; + OrbisImeOption option; + u32 max_text_length; + char16_t* input_text_buffer; + f32 posx; + f32 posy; + OrbisImeHorizontalAlignment horizontal_alignment; + OrbisImeVerticalAlignment vertical_alignment; + const char16_t* placeholder; + const char16_t* title; + s8 reserved[16]; +}; + +struct OrbisImeParamExtended { + u32 option; // OrbisImeExtOption flags + OrbisImeColor color_base; + OrbisImeColor color_line; + OrbisImeColor color_text_field; + OrbisImeColor color_preedit; + OrbisImeColor color_button_default; + OrbisImeColor color_button_function; + OrbisImeColor color_button_symbol; + OrbisImeColor color_text; + OrbisImeColor color_special; + OrbisImePanelPriority priority; + char* additional_dictionary_path; + OrbisImeExtKeyboardFilter ext_keyboard_filter; + u32 disable_device; + u32 ext_keyboard_mode; + s8 reserved[60]; +}; diff --git a/src/core/libraries/ime/ime_dialog.cpp b/src/core/libraries/ime/ime_dialog.cpp index bee185787..6f808636b 100644 --- a/src/core/libraries/ime/ime_dialog.cpp +++ b/src/core/libraries/ime/ime_dialog.cpp @@ -20,19 +20,19 @@ static OrbisImeDialogResult g_ime_dlg_result{}; static ImeDialogState g_ime_dlg_state{}; static ImeDialogUi g_ime_dlg_ui; -static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) { - if (False(~option & - (OrbisImeDialogOption::Multiline | OrbisImeDialogOption::NoAutoCompletion))) { +static bool IsValidOption(OrbisImeOption option, OrbisImeType type) { + if (False(~option & (OrbisImeOption::MULTILINE | + OrbisImeOption::NO_AUTO_CAPITALIZATION /* NoAutoCompletion */))) { return false; } - if (True(option & OrbisImeDialogOption::Multiline) && type != OrbisImeType::Default && + if (True(option & OrbisImeOption::MULTILINE) && type != OrbisImeType::Default && type != OrbisImeType::BasicLatin) { return false; } - if (True(option & OrbisImeDialogOption::NoAutoCompletion) && type != OrbisImeType::Number && - type != OrbisImeType::BasicLatin) { + if (True(option & OrbisImeOption::NO_AUTO_CAPITALIZATION /* NoAutoCompletion */) && + type != OrbisImeType::Number && type != OrbisImeType::BasicLatin) { return false; } @@ -96,7 +96,7 @@ Error PS4_SYSV_ABI sceImeDialogGetPanelSize(const OrbisImeDialogParam* param, u3 case OrbisImeType::Url: case OrbisImeType::Mail: *width = 500; // original: 793 - if (True(param->option & OrbisImeDialogOption::Multiline)) { + if (True(param->option & OrbisImeOption::MULTILINE)) { *height = 300; // original: 576 } else { *height = 150; // original: 476 @@ -149,18 +149,20 @@ OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() { } Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { + LOG_INFO(Lib_ImeDialog, ">> sceImeDialogInit: entering, param={}, extended={}", + static_cast(param), static_cast(extended)); if (g_ime_dlg_status != OrbisImeDialogStatus::None) { - LOG_INFO(Lib_ImeDialog, "IME dialog is already running"); + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: busy (status=%u)", (u32)g_ime_dlg_status); return Error::BUSY; } if (param == nullptr) { - LOG_INFO(Lib_ImeDialog, "called with param (NULL)"); + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: param is null"); return Error::INVALID_ADDRESS; } if (!magic_enum::enum_contains(param->type)) { - LOG_INFO(Lib_ImeDialog, "Invalid param->type"); + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid param->type=%u", (u32)param->type); return Error::INVALID_ADDRESS; } @@ -168,16 +170,14 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt // TODO: do correct param->supportedLanguages validation if (param->posx < 0.0f || - param->posx >= - MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LargeResolution)]) { - LOG_INFO(Lib_ImeDialog, "Invalid param->posx"); + param->posx >= MAX_X_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) { + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posx=%f", param->posx); return Error::INVALID_POSX; } if (param->posy < 0.0f || - param->posy >= - MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LargeResolution)]) { - LOG_INFO(Lib_ImeDialog, "Invalid param->posy"); + param->posy >= MAX_Y_POSITIONS[False(param->option & OrbisImeOption::USE_2K_COORDINATES)]) { + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid posy=%f", param->posy); return Error::INVALID_POSY; } @@ -192,12 +192,13 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt } if (!IsValidOption(param->option, param->type)) { - LOG_INFO(Lib_ImeDialog, "Invalid param->option"); + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid option=0x%X for type=%u", + static_cast(param->option), (u32)param->type); return Error::INVALID_PARAM; } if (param->input_text_buffer == nullptr) { - LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer"); + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: input_text_buffer is null"); return Error::INVALID_INPUT_TEXT_BUFFER; } @@ -220,16 +221,24 @@ Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExt } } - if (param->max_text_length > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) { - LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength"); + if (param->max_text_length == 0 || param->max_text_length > ORBIS_IME_MAX_TEXT_LENGTH) { + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: invalid max_text_length=%u", + param->max_text_length); return Error::INVALID_MAX_TEXT_LENGTH; } + // Title string validation + if (param->title != nullptr && !std::char_traits::length(param->title)) { + LOG_ERROR(Lib_ImeDialog, "sceImeDialogInit: title is empty"); + return Error::INVALID_PARAM; + } + g_ime_dlg_result = {}; g_ime_dlg_state = ImeDialogState(param, extended); g_ime_dlg_status = OrbisImeDialogStatus::Running; g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result); + LOG_INFO(Lib_ImeDialog, "<< sceImeDialogInit: successful, status now=Running"); return Error::OK; } diff --git a/src/core/libraries/ime/ime_dialog.h b/src/core/libraries/ime/ime_dialog.h index 526e5f022..a056fdd5e 100644 --- a/src/core/libraries/ime/ime_dialog.h +++ b/src/core/libraries/ime/ime_dialog.h @@ -13,50 +13,6 @@ class SymbolsResolver; namespace Libraries::ImeDialog { -constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 2048; - -enum class Error : u32 { - OK = 0x0, - BUSY = 0x80bc0001, - NOT_OPENED = 0x80bc0002, - NO_MEMORY = 0x80bc0003, - CONNECTION_FAILED = 0x80bc0004, - TOO_MANY_REQUESTS = 0x80bc0005, - INVALID_TEXT = 0x80bc0006, - EVENT_OVERFLOW = 0x80bc0007, - NOT_ACTIVE = 0x80bc0008, - IME_SUSPENDING = 0x80bc0009, - DEVICE_IN_USE = 0x80bc000a, - INVALID_USER_ID = 0x80bc0010, - INVALID_TYPE = 0x80bc0011, - INVALID_SUPPORTED_LANGUAGES = 0x80bc0012, - INVALID_ENTER_LABEL = 0x80bc0013, - INVALID_INPUT_METHOD = 0x80bc0014, - INVALID_OPTION = 0x80bc0015, - INVALID_MAX_TEXT_LENGTH = 0x80bc0016, - INVALID_INPUT_TEXT_BUFFER = 0x80bc0017, - INVALID_POSX = 0x80bc0018, - INVALID_POSY = 0x80bc0019, - INVALID_HORIZONTALIGNMENT = 0x80bc001a, - INVALID_VERTICALALIGNMENT = 0x80bc001b, - INVALID_EXTENDED = 0x80bc001c, - INVALID_KEYBOARD_TYPE = 0x80bc001d, - INVALID_WORK = 0x80bc0020, - INVALID_ARG = 0x80bc0021, - INVALID_HANDLER = 0x80bc0022, - NO_RESOURCE_ID = 0x80bc0023, - INVALID_MODE = 0x80bc0024, - INVALID_PARAM = 0x80bc0030, - INVALID_ADDRESS = 0x80bc0031, - INVALID_RESERVED = 0x80bc0032, - INVALID_TIMING = 0x80bc0033, - INTERNAL = 0x80bc00ff, - DIALOG_INVALID_TITLE = 0x80bc0101, - DIALOG_NOT_RUNNING = 0x80bc0105, - DIALOG_NOT_FINISHED = 0x80bc0106, - DIALOG_NOT_IN_USE = 0x80bc0107, -}; - enum class OrbisImeDialogStatus : u32 { None = 0, Running = 1, @@ -69,87 +25,11 @@ enum class OrbisImeDialogEndStatus : u32 { Aborted = 2, }; -enum class OrbisImeDialogOption : u32 { - Default = 0, - Multiline = 1, - NoAutoCorrection = 2, - NoAutoCompletion = 4, - // TODO: Document missing options - LargeResolution = 1024, -}; -DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption) - -enum class OrbisImePanelPriority : u32 { - Default = 0, - Alphabet = 1, - Symbol = 2, - Accent = 3, -}; - -struct OrbisImeColor { - u8 r; - u8 g; - u8 b; - u8 a; -}; - struct OrbisImeDialogResult { OrbisImeDialogEndStatus endstatus; s32 reserved[12]; }; -struct OrbisImeKeycode { - u16 keycode; - char16_t character; - u32 status; - OrbisImeKeyboardType type; - s32 user_id; - u32 resource_id; - u64 timestamp; -}; - -using OrbisImeExtKeyboardFilter = PS4_SYSV_ABI int (*)(const OrbisImeKeycode* srcKeycode, - u16* outKeycode, u32* outStatus, - void* reserved); - -struct OrbisImeDialogParam { - s32 user_id; - OrbisImeType type; - u64 supported_languages; - OrbisImeEnterLabel enter_label; - OrbisImeInputMethod input_method; - OrbisImeTextFilter filter; - OrbisImeDialogOption option; - u32 max_text_length; - char16_t* input_text_buffer; - float posx; - float posy; - OrbisImeHorizontalAlignment horizontal_alignment; - OrbisImeVerticalAlignment vertical_alignment; - const char16_t* placeholder; - const char16_t* title; - s8 reserved[16]; -}; - -struct OrbisImeParamExtended { - u32 option; // OrbisImeDialogOptionExtended - OrbisImeColor color_base; - OrbisImeColor color_line; - OrbisImeColor color_text_field; - OrbisImeColor color_preedit; - OrbisImeColor color_button_default; - OrbisImeColor color_button_function; - OrbisImeColor color_button_symbol; - OrbisImeColor color_text; - OrbisImeColor color_special; - OrbisImePanelPriority priority; - char* additional_dictionary_path; - OrbisImeExtKeyboardFilter ext_keyboard_filter; - uint32_t disable_device; - uint32_t ext_keyboard_mode; - int8_t reserved[60]; -}; - Error PS4_SYSV_ABI sceImeDialogAbort(); Error PS4_SYSV_ABI sceImeDialogForceClose(); Error PS4_SYSV_ABI sceImeDialogForTestFunction(); diff --git a/src/core/libraries/ime/ime_dialog_ui.cpp b/src/core/libraries/ime/ime_dialog_ui.cpp index 51183c79b..800ba1124 100644 --- a/src/core/libraries/ime/ime_dialog_ui.cpp +++ b/src/core/libraries/ime/ime_dialog_ui.cpp @@ -21,12 +21,16 @@ namespace Libraries::ImeDialog { ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, const OrbisImeParamExtended* extended) { + LOG_INFO(Lib_ImeDialog, ">> ImeDialogState::Ctor: param={}, text_buffer={}", + static_cast(param), + static_cast(param ? param->input_text_buffer : nullptr)); if (!param) { + LOG_ERROR(Lib_ImeDialog, " param==nullptr, returning without init"); return; } user_id = param->user_id; - is_multi_line = True(param->option & OrbisImeDialogOption::Multiline); + is_multi_line = True(param->option & OrbisImeOption::MULTILINE); is_numeric = param->type == OrbisImeType::Number; type = param->type; enter_label = param->enter_label; @@ -220,6 +224,7 @@ void ImeDialogUi::Free() { void ImeDialogUi::Draw() { std::unique_lock lock{draw_mutex}; + LOG_INFO(Lib_ImeDialog, ">> ImeDialogUi::Draw: first_render=%d", first_render); if (!state) { return; @@ -259,9 +264,13 @@ void ImeDialogUi::Draw() { } if (state->is_multi_line) { + LOG_INFO(Lib_ImeDialog, " Drawing multi-line widget…"); DrawMultiLineInputText(); + LOG_INFO(Lib_ImeDialog, " Done DrawMultiLineInputText"); } else { + LOG_INFO(Lib_ImeDialog, " Drawing input text widget…"); DrawInputText(); + LOG_INFO(Lib_ImeDialog, " Done DrawInputText"); } SetCursorPosY(GetCursorPosY() + 10.0f); @@ -306,6 +315,7 @@ void ImeDialogUi::Draw() { End(); first_render = false; + LOG_INFO(Lib_ImeDialog, "<< ImeDialogUi::Draw complete"); } void ImeDialogUi::DrawInputText() { @@ -316,7 +326,7 @@ void ImeDialogUi::DrawInputText() { } const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), - state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter, + state->max_text_length + 1, input_size, ImGuiInputTextFlags_CallbackCharFilter, InputTextCallback, this)) { state->input_changed = true; } @@ -332,7 +342,7 @@ void ImeDialogUi::DrawMultiLineInputText() { } const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), - state->max_text_length, input_size, flags, InputTextCallback, this)) { + state->max_text_length + 1, input_size, flags, InputTextCallback, this)) { state->input_changed = true; } } @@ -341,13 +351,19 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { ImeDialogUi* ui = static_cast(data->UserData); ASSERT(ui); + LOG_DEBUG(Lib_ImeDialog, ">> InputTextCallback: EventFlag={}, EventChar={}", data->EventFlag, + data->EventChar); + // Should we filter punctuation? if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') && data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') { + LOG_INFO(Lib_ImeDialog, "InputTextCallback: rejecting non-digit char '{}'", + static_cast(data->EventChar)); return 1; } if (!ui->state->keyboard_filter) { + LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: no keyboard_filter, accepting char"); return 0; } @@ -363,20 +379,24 @@ int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { // the current language?) .user_id = ui->state->user_id, .resource_id = 0, - .timestamp = 0, + .timestamp = {0}, }; if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { - LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8"); + LOG_ERROR(Lib_ImeDialog, "InputTextCallback: ConvertUTF8ToOrbis failed"); return 0; } + LOG_DEBUG(Lib_ImeDialog, "InputTextCallback: converted to Orbis char={:#X}", + static_cast(src_keycode.character)); src_keycode.keycode = src_keycode.character; // TODO set this to the correct value u16 out_keycode; u32 out_status; - ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); - + bool keep = ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); + LOG_DEBUG(Lib_ImeDialog, + "InputTextCallback: CallKeyboardFilter returned %s (keycode=0x%X, status=0x%X)", + keep ? "true" : "false", out_keycode, out_status); // TODO. set the keycode return 0; diff --git a/src/core/libraries/ime/ime_ui.cpp b/src/core/libraries/ime/ime_ui.cpp index 37f25e200..c49c70ede 100644 --- a/src/core/libraries/ime/ime_ui.cpp +++ b/src/core/libraries/ime/ime_ui.cpp @@ -199,7 +199,7 @@ int ImeUi::InputTextCallback(ImGuiInputTextCallbackData* data) { eventParam.caret_index = data->CursorPos; eventParam.area_num = 1; - eventParam.text_area[0].mode = 1; // Edit mode + eventParam.text_area[0].mode = OrbisImeTextAreaMode::Edit; eventParam.text_area[0].index = data->CursorPos; eventParam.text_area[0].length = data->BufTextLen; diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index 114a096ca..e0c359f2c 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -573,11 +573,12 @@ void* PS4_SYSV_ABI posix_mmap(void* addr, u64 len, s32 prot, s32 flags, s32 fd, auto* memory = Core::Memory::Instance(); const auto mem_prot = static_cast(prot); const auto mem_flags = static_cast(flags); + const auto is_exec = True(mem_prot & Core::MemoryProt::CpuExec); s32 result = ORBIS_OK; if (fd == -1) { result = memory->MapMemory(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags, - Core::VMAType::Flexible); + Core::VMAType::Flexible, "anon", is_exec); } else { result = memory->MapFile(&addr_out, std::bit_cast(addr), len, mem_prot, mem_flags, fd, phys_addr); @@ -711,6 +712,7 @@ void RegisterMemory(Core::Loader::SymbolsResolver* sym) { sceKernelConfiguredFlexibleMemorySize); LIB_FUNCTION("vSMAm3cxYTY", "libkernel", 1, "libkernel", 1, 1, sceKernelMprotect); + LIB_FUNCTION("YQOfxL4QfeU", "libkernel", 1, "libkernel", 1, 1, posix_mprotect); LIB_FUNCTION("YQOfxL4QfeU", "libScePosix", 1, "libkernel", 1, 1, posix_mprotect); LIB_FUNCTION("9bfdLIyuwCY", "libkernel", 1, "libkernel", 1, 1, sceKernelMtypeprotect); diff --git a/src/core/libraries/videodec/videodec2.cpp b/src/core/libraries/videodec/videodec2.cpp index 1c6044fe2..8c91e2bf1 100644 --- a/src/core/libraries/videodec/videodec2.cpp +++ b/src/core/libraries/videodec/videodec2.cpp @@ -171,19 +171,40 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; } - if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { + if (outputInfo->pictureCount == 0) { LOG_ERROR(Lib_Vdec2, "No picture info available"); return ORBIS_OK; } - if (p1stPictureInfoOut) { - OrbisVideodec2AvcPictureInfo* picInfo = - static_cast(p1stPictureInfoOut); - if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) { - LOG_ERROR(Lib_Vdec2, "Invalid struct size"); - return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + // If the game uses the older Videodec2 structs, we need to accomodate that. + if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) { + if (gLegacyPictureInfos.empty()) { + LOG_ERROR(Lib_Vdec2, "No picture info available"); + return ORBIS_OK; + } + if (p1stPictureInfoOut) { + OrbisVideodec2LegacyAvcPictureInfo* picInfo = + static_cast(p1stPictureInfoOut); + if (picInfo->thisSize != sizeof(OrbisVideodec2LegacyAvcPictureInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + *picInfo = gLegacyPictureInfos.back(); + } + } else { + if (gPictureInfos.empty()) { + LOG_ERROR(Lib_Vdec2, "No picture info available"); + return ORBIS_OK; + } + if (p1stPictureInfoOut) { + OrbisVideodec2AvcPictureInfo* picInfo = + static_cast(p1stPictureInfoOut); + if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) { + LOG_ERROR(Lib_Vdec2, "Invalid struct size"); + return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; + } + *picInfo = gPictureInfos.back(); } - *picInfo = gPictureInfos.back(); } if (outputInfo->pictureCount > 1) { diff --git a/src/core/libraries/videodec/videodec2_avc.h b/src/core/libraries/videodec/videodec2_avc.h index 1975209cb..725d2335f 100644 --- a/src/core/libraries/videodec/videodec2_avc.h +++ b/src/core/libraries/videodec/videodec2_avc.h @@ -74,4 +74,57 @@ struct OrbisVideodec2AvcPictureInfo { }; static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78); +// An older version of the OrbisVideodec2AvcPictureInfo struct +// Keeping this is needed for compatiblity with older games. +struct OrbisVideodec2LegacyAvcPictureInfo { + u64 thisSize; + + bool isValid; + + u64 ptsData; + u64 dtsData; + u64 attachedData; + + u8 idrPictureflag; + + u8 profile_idc; + u8 level_idc; + u32 pic_width_in_mbs_minus1; + u32 pic_height_in_map_units_minus1; + u8 frame_mbs_only_flag; + + u8 frame_cropping_flag; + u32 frameCropLeftOffset; + u32 frameCropRightOffset; + u32 frameCropTopOffset; + u32 frameCropBottomOffset; + + u8 aspect_ratio_info_present_flag; + u8 aspect_ratio_idc; + u16 sar_width; + u16 sar_height; + + u8 video_signal_type_present_flag; + u8 video_format; + u8 video_full_range_flag; + u8 colour_description_present_flag; + u8 colour_primaries; + u8 transfer_characteristics; + u8 matrix_coefficients; + + u8 timing_info_present_flag; + u32 num_units_in_tick; + u32 time_scale; + u8 fixed_frame_rate_flag; + + u8 bitstream_restriction_flag; + u8 max_dec_frame_buffering; + + u8 pic_struct_present_flag; + u8 pic_struct; + u8 field_pic_flag; + u8 bottom_field_flag; +}; +static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68); + } // namespace Libraries::Vdec2 \ No newline at end of file diff --git a/src/core/libraries/videodec/videodec2_impl.cpp b/src/core/libraries/videodec/videodec2_impl.cpp index 373809c14..667fb79ac 100644 --- a/src/core/libraries/videodec/videodec2_impl.cpp +++ b/src/core/libraries/videodec/videodec2_impl.cpp @@ -12,6 +12,7 @@ namespace Libraries::Vdec2 { std::vector gPictureInfos; +std::vector gLegacyPictureInfos; static inline void CopyNV12Data(u8* dst, const AVFrame& src) { std::memcpy(dst, src.data[0], src.width * src.height); @@ -117,27 +118,46 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData, outputInfo.isErrorFrame = false; outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video - // Only set framePitchInBytes if the game uses the newer struct version. + // For proper compatibility with older games, check the inputted OutputInfo struct size. if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { + // framePitchInBytes only exists in the newer struct. outputInfo.framePitchInBytes = frame->linesize[0]; - } + if (outputInfo.isValid) { + OrbisVideodec2AvcPictureInfo pictureInfo = {}; - if (outputInfo.isValid) { - OrbisVideodec2AvcPictureInfo pictureInfo = {}; + pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); + pictureInfo.isValid = true; - pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); - pictureInfo.isValid = true; + pictureInfo.ptsData = inputData.ptsData; + pictureInfo.dtsData = inputData.dtsData; + pictureInfo.attachedData = inputData.attachedData; - pictureInfo.ptsData = inputData.ptsData; - pictureInfo.dtsData = inputData.dtsData; - pictureInfo.attachedData = inputData.attachedData; + pictureInfo.frameCropLeftOffset = frame->crop_left; + pictureInfo.frameCropRightOffset = frame->crop_right; + pictureInfo.frameCropTopOffset = frame->crop_top; + pictureInfo.frameCropBottomOffset = frame->crop_bottom; - pictureInfo.frameCropLeftOffset = frame->crop_left; - pictureInfo.frameCropRightOffset = frame->crop_right; - pictureInfo.frameCropTopOffset = frame->crop_top; - pictureInfo.frameCropBottomOffset = frame->crop_bottom; + gPictureInfos.push_back(pictureInfo); + } + } else { + if (outputInfo.isValid) { + // If the game uses the older struct versions, we need to use it too. + OrbisVideodec2LegacyAvcPictureInfo pictureInfo = {}; - gPictureInfos.push_back(pictureInfo); + pictureInfo.thisSize = sizeof(OrbisVideodec2LegacyAvcPictureInfo); + pictureInfo.isValid = true; + + pictureInfo.ptsData = inputData.ptsData; + pictureInfo.dtsData = inputData.dtsData; + pictureInfo.attachedData = inputData.attachedData; + + pictureInfo.frameCropLeftOffset = frame->crop_left; + pictureInfo.frameCropRightOffset = frame->crop_right; + pictureInfo.frameCropTopOffset = frame->crop_top; + pictureInfo.frameCropBottomOffset = frame->crop_bottom; + + gLegacyPictureInfos.push_back(pictureInfo); + } } } diff --git a/src/core/libraries/videodec/videodec2_impl.h b/src/core/libraries/videodec/videodec2_impl.h index c8e8ea253..7ee3339db 100644 --- a/src/core/libraries/videodec/videodec2_impl.h +++ b/src/core/libraries/videodec/videodec2_impl.h @@ -16,6 +16,7 @@ extern "C" { namespace Libraries::Vdec2 { extern std::vector gPictureInfos; +extern std::vector gLegacyPictureInfos; class VdecDecoder { public: diff --git a/src/core/libraries/videoout/video_out.cpp b/src/core/libraries/videoout/video_out.cpp index da715b3bf..0f961923a 100644 --- a/src/core/libraries/videoout/video_out.cpp +++ b/src/core/libraries/videoout/video_out.cpp @@ -445,7 +445,8 @@ s32 PS4_SYSV_ABI sceVideoOutConfigureOutputMode_(s32 handle, u32 reserved, const } void RegisterLib(Core::Loader::SymbolsResolver* sym) { - driver = std::make_unique(Config::getScreenWidth(), Config::getScreenHeight()); + driver = std::make_unique(Config::getInternalScreenWidth(), + Config::getInternalScreenHeight()); LIB_FUNCTION("SbU3dwp80lQ", "libSceVideoOut", 1, "libSceVideoOut", 0, 0, sceVideoOutGetFlipStatus); diff --git a/src/core/memory.cpp b/src/core/memory.cpp index f70751f3a..3d9bf58a7 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -342,7 +342,7 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo } } - // Limit the minumum address to SystemManagedVirtualBase to prevent hardware-specific issues. + // Limit the minimum address to SystemManagedVirtualBase to prevent hardware-specific issues. VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; // Fixed mapping means the virtual address must exactly match the provided one. @@ -414,9 +414,10 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo rasterizer->MapMemory(mapped_addr, size); } *out_addr = impl.Map(mapped_addr, size, alignment, phys_addr, is_exec); + + TRACK_ALLOC(*out_addr, size, "VMEM"); } - TRACK_ALLOC(*out_addr, size, "VMEM"); return ORBIS_OK; } @@ -630,6 +631,9 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size if (True(prot & MemoryProt::CpuReadWrite)) { perms |= Core::MemoryPermission::ReadWrite; } + if (True(prot & MemoryProt::CpuExec)) { + perms |= Core::MemoryPermission::Execute; + } if (True(prot & MemoryProt::GpuRead)) { perms |= Core::MemoryPermission::Read; } @@ -649,9 +653,9 @@ s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { std::scoped_lock lk{mutex}; // Validate protection flags - constexpr static MemoryProt valid_flags = MemoryProt::NoAccess | MemoryProt::CpuRead | - MemoryProt::CpuReadWrite | MemoryProt::GpuRead | - MemoryProt::GpuWrite | MemoryProt::GpuReadWrite; + constexpr static MemoryProt valid_flags = + MemoryProt::NoAccess | MemoryProt::CpuRead | MemoryProt::CpuReadWrite | + MemoryProt::CpuExec | MemoryProt::GpuRead | MemoryProt::GpuWrite | MemoryProt::GpuReadWrite; MemoryProt invalid_flags = prot & ~valid_flags; if (invalid_flags != MemoryProt::NoAccess) { diff --git a/src/core/memory.h b/src/core/memory.h index c800ef763..285d7dbed 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -31,6 +31,7 @@ enum class MemoryProt : u32 { NoAccess = 0, CpuRead = 1, CpuReadWrite = 2, + CpuExec = 4, GpuRead = 16, GpuWrite = 32, GpuReadWrite = 48, diff --git a/src/emulator.cpp b/src/emulator.cpp index 283cc9ae2..e2ee3e103 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -132,6 +132,9 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar LOG_INFO(Config, "General LogType: {}", Config::getLogType()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); + LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks()); + LOG_INFO(Config, "GPU readbackLinearImages: {}", Config::readbackLinearImages()); + LOG_INFO(Config, "GPU directMemoryAccess: {}", Config::directMemoryAccess()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv()); LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); @@ -220,7 +223,7 @@ void Emulator::Run(std::filesystem::path file, const std::vector ar } } window = std::make_unique( - Config::getScreenWidth(), Config::getScreenHeight(), controller, window_title); + Config::getWindowWidth(), Config::getWindowHeight(), controller, window_title); g_window = window.get(); diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 7c4e19103..67980ed0c 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -514,17 +514,14 @@ void ControllerOutput::FinalizeUpdate() { if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: - LOG_INFO(Input, "Topuchpad left"); controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: - LOG_INFO(Input, "Topuchpad center"); controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: - LOG_INFO(Input, "Topuchpad right"); controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 745906620..daef22f21 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -177,38 +177,38 @@ const std::map string_to_keyboard_key_map = { {"9", SDLK_9}, // symbols - {"`", SDLK_GRAVE}, - {"~", SDLK_TILDE}, - {"!", SDLK_EXCLAIM}, - {"@", SDLK_AT}, - {"#", SDLK_HASH}, - {"$", SDLK_DOLLAR}, - {"%", SDLK_PERCENT}, - {"^", SDLK_CARET}, - {"&", SDLK_AMPERSAND}, - {"*", SDLK_ASTERISK}, - {"(", SDLK_LEFTPAREN}, - {")", SDLK_RIGHTPAREN}, - {"-", SDLK_MINUS}, - {"_", SDLK_UNDERSCORE}, - {"=", SDLK_EQUALS}, - {"+", SDLK_PLUS}, - {"[", SDLK_LEFTBRACKET}, - {"]", SDLK_RIGHTBRACKET}, - {"{", SDLK_LEFTBRACE}, - {"}", SDLK_RIGHTBRACE}, - {"\\", SDLK_BACKSLASH}, - {"|", SDLK_PIPE}, - {";", SDLK_SEMICOLON}, - {":", SDLK_COLON}, - {"'", SDLK_APOSTROPHE}, - {"\"", SDLK_DBLAPOSTROPHE}, - {",", SDLK_COMMA}, - {"<", SDLK_LESS}, - {".", SDLK_PERIOD}, - {">", SDLK_GREATER}, - {"/", SDLK_SLASH}, - {"?", SDLK_QUESTION}, + {"grave", SDLK_GRAVE}, + {"tilde", SDLK_TILDE}, + {"exclamation", SDLK_EXCLAIM}, + {"at", SDLK_AT}, + {"hash", SDLK_HASH}, + {"dollar", SDLK_DOLLAR}, + {"percent", SDLK_PERCENT}, + {"caret", SDLK_CARET}, + {"ampersand", SDLK_AMPERSAND}, + {"asterisk", SDLK_ASTERISK}, + {"lparen", SDLK_LEFTPAREN}, + {"rparen", SDLK_RIGHTPAREN}, + {"minus", SDLK_MINUS}, + {"underscore", SDLK_UNDERSCORE}, + {"equals", SDLK_EQUALS}, + {"plus", SDLK_PLUS}, + {"lbracket", SDLK_LEFTBRACKET}, + {"rbracket", SDLK_RIGHTBRACKET}, + {"lbrace", SDLK_LEFTBRACE}, + {"rbrace", SDLK_RIGHTBRACE}, + {"backslash", SDLK_BACKSLASH}, + {"pipe", SDLK_PIPE}, + {"semicolon", SDLK_SEMICOLON}, + {"colon", SDLK_COLON}, + {"apostrophe", SDLK_APOSTROPHE}, + {"quote", SDLK_DBLAPOSTROPHE}, + {"comma", SDLK_COMMA}, + {"less", SDLK_LESS}, + {"period", SDLK_PERIOD}, + {"greater", SDLK_GREATER}, + {"slash", SDLK_SLASH}, + {"question", SDLK_QUESTION}, // special keys {"escape", SDLK_ESCAPE}, @@ -252,13 +252,13 @@ const std::map string_to_keyboard_key_map = { {"kp7", SDLK_KP_7}, {"kp8", SDLK_KP_8}, {"kp9", SDLK_KP_9}, - {"kp.", SDLK_KP_PERIOD}, - {"kp,", SDLK_KP_COMMA}, - {"kp/", SDLK_KP_DIVIDE}, - {"kp*", SDLK_KP_MULTIPLY}, - {"kp-", SDLK_KP_MINUS}, - {"kp+", SDLK_KP_PLUS}, - {"kp=", SDLK_KP_EQUALS}, + {"kpperiod", SDLK_KP_PERIOD}, + {"kpcomma", SDLK_KP_COMMA}, + {"kpslash", SDLK_KP_DIVIDE}, + {"kpasterisk", SDLK_KP_MULTIPLY}, + {"kpminus", SDLK_KP_MINUS}, + {"kpplus", SDLK_KP_PLUS}, + {"kpequals", SDLK_KP_EQUALS}, {"kpenter", SDLK_KP_ENTER}, // mouse diff --git a/src/qt_gui/control_settings.cpp b/src/qt_gui/control_settings.cpp index 4206e45b8..319daecdd 100644 --- a/src/qt_gui/control_settings.cpp +++ b/src/qt_gui/control_settings.cpp @@ -2,21 +2,67 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include +#include "common/logging/log.h" #include "common/path_util.h" #include "control_settings.h" +#include "input/input_handler.h" #include "ui_control_settings.h" -ControlSettings::ControlSettings(std::shared_ptr game_info_get, QWidget* parent) - : QDialog(parent), m_game_info(game_info_get), ui(new Ui::ControlSettings) { +ControlSettings::ControlSettings(std::shared_ptr game_info_get, bool isGameRunning, + std::string GameRunningSerial, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), GameRunning(isGameRunning), + RunningGameSerial(GameRunningSerial), ui(new Ui::ControlSettings) { ui->setupUi(this); - ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + + if (!GameRunning) { + SDL_InitSubSystem(SDL_INIT_GAMEPAD); + SDL_InitSubSystem(SDL_INIT_EVENTS); + CheckGamePad(); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + } AddBoxItems(); SetUIValuestoMappings(); UpdateLightbarColor(); + installEventFilter(this); + + ButtonsList = {ui->CrossButton, + ui->CircleButton, + ui->TriangleButton, + ui->SquareButton, + ui->L1Button, + ui->R1Button, + ui->L2Button, + ui->R2Button, + ui->L3Button, + ui->R3Button, + ui->OptionsButton, + ui->TouchpadLeftButton, + ui->TouchpadCenterButton, + ui->TouchpadRightButton, + ui->DpadUpButton, + ui->DpadDownButton, + ui->DpadLeftButton, + ui->DpadRightButton}; + + AxisList = {ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, + ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, + ui->RStickLeftButton, ui->RStickRightButton}; + + for (auto& button : ButtonsList) { + connect(button, &QPushButton::clicked, this, + [this, &button]() { StartTimer(button, true); }); + } + + for (auto& button : AxisList) { + connect(button, &QPushButton::clicked, this, + [this, &button]() { StartTimer(button, false); }); + } connect(ui->buttonBox, &QDialogButtonBox::clicked, this, [this](QAbstractButton* button) { if (button == ui->buttonBox->button(QDialogButtonBox::Save)) { @@ -33,6 +79,8 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); ui->buttonBox->button(QDialogButtonBox::Cancel)->setText(tr("Cancel")); + ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); + connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close); connect(ui->ProfileComboBox, &QComboBox::currentTextChanged, this, [this] { @@ -45,24 +93,6 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q connect(ui->RightDeadzoneSlider, &QSlider::valueChanged, this, [this](int value) { ui->RightDeadzoneValue->setText(QString::number(value)); }); - connect(ui->LStickUpBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickDownBox->setCurrentIndex(value); }); - connect(ui->LStickDownBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickUpBox->setCurrentIndex(value); }); - connect(ui->LStickRightBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickLeftBox->setCurrentIndex(value); }); - connect(ui->LStickLeftBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->LStickRightBox->setCurrentIndex(value); }); - - connect(ui->RStickUpBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickDownBox->setCurrentIndex(value); }); - connect(ui->RStickDownBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickUpBox->setCurrentIndex(value); }); - connect(ui->RStickRightBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickLeftBox->setCurrentIndex(value); }); - connect(ui->RStickLeftBox, &QComboBox::currentIndexChanged, this, - [this](int value) { ui->RStickRightBox->setCurrentIndex(value); }); - connect(ui->RSlider, &QSlider::valueChanged, this, [this](int value) { QString RedValue = QString("%1").arg(value, 3, 10, QChar('0')); QString RValue = tr("R:") + " " + RedValue; @@ -83,30 +113,44 @@ ControlSettings::ControlSettings(std::shared_ptr game_info_get, Q ui->BLabel->setText(BValue); UpdateLightbarColor(); }); + + connect(this, &ControlSettings::PushGamepadEvent, this, + [this]() { CheckMapping(MappingButton); }); + connect(this, &ControlSettings::AxisChanged, this, + [this]() { ConnectAxisInputs(MappingButton); }); + + RemapWrapper = SdlEventWrapper::Wrapper::GetInstance(); + SdlEventWrapper::Wrapper::wrapperActive = true; + QObject::connect(RemapWrapper, &SdlEventWrapper::Wrapper::SDLEvent, this, + &ControlSettings::processSDLEvents); + + if (!GameRunning) { + Polling = QtConcurrent::run(&ControlSettings::pollSDLEvents, this); + } } void ControlSettings::SaveControllerConfig(bool CloseOnSave) { - QList list; - list << ui->RStickUpBox << ui->RStickRightBox << ui->LStickUpBox << ui->LStickRightBox; + QList list; + list << ui->RStickUpButton << ui->RStickRightButton << ui->LStickUpButton + << ui->LStickRightButton; int count_axis_left_x = 0, count_axis_left_y = 0, count_axis_right_x = 0, count_axis_right_y = 0; for (const auto& i : list) { - if (i->currentText() == "axis_left_x") { + if (i->text() == "axis_left_x") { count_axis_left_x = count_axis_left_x + 1; - } else if (i->currentText() == "axis_left_y") { + } else if (i->text() == "axis_left_y") { count_axis_left_y = count_axis_left_y + 1; - } else if (i->currentText() == "axis_right_x") { + } else if (i->text() == "axis_right_x") { count_axis_right_x = count_axis_right_x + 1; - } else if (i->currentText() == "axis_right_y") { + } else if (i->text() == "axis_right_y") { count_axis_right_y = count_axis_right_y + 1; } } if (count_axis_left_x > 1 | count_axis_left_y > 1 | count_axis_right_x > 1 | count_axis_right_y > 1) { - QMessageBox::StandardButton nosave; - nosave = QMessageBox::information(this, tr("Unable to Save"), - tr("Cannot bind axis values more than once")); + QMessageBox::information(this, tr("Unable to Save"), + tr("Cannot bind axis values more than once")); return; } @@ -118,7 +162,7 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { int lineCount = 0; std::string line; - std::vector lines; + std::vector lines, inputs; std::string output_string = "", input_string = ""; std::fstream file(config_file); @@ -141,9 +185,17 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected || output_string == "analog_deadzone" || + output_string == "override_controller_color") { line.erase(); continue; } @@ -152,92 +204,60 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { file.close(); - input_string = "cross"; - output_string = ui->ABox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + // Lambda to reduce repetitive code for mapping buttons to config lines + auto add_mapping = [&](const QString& buttonText, const std::string& output_name) { + input_string = buttonText.toStdString(); + output_string = output_name; + if (input_string != "unmapped") { + lines.push_back(output_string + " = " + input_string); + inputs.push_back(input_string); + } + }; - input_string = "circle"; - output_string = ui->BBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "square"; - output_string = ui->XBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "triangle"; - output_string = ui->YBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->CrossButton->text(), "cross"); + add_mapping(ui->CircleButton->text(), "circle"); + add_mapping(ui->SquareButton->text(), "square"); + add_mapping(ui->TriangleButton->text(), "triangle"); lines.push_back(""); - input_string = "l1"; - output_string = ui->LBBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r1"; - output_string = ui->RBBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "l2"; - output_string = ui->LTBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r2"; - output_string = ui->RTBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "l3"; - output_string = ui->LClickBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "r3"; - output_string = ui->RClickBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->L1Button->text(), "l1"); + add_mapping(ui->R1Button->text(), "r1"); + add_mapping(ui->L2Button->text(), "l2"); + add_mapping(ui->R2Button->text(), "r2"); + add_mapping(ui->L3Button->text(), "l3"); + add_mapping(ui->R3Button->text(), "r3"); lines.push_back(""); - input_string = "back"; - output_string = ui->BackBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "options"; - output_string = ui->StartBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->TouchpadLeftButton->text(), "touchpad_left"); + add_mapping(ui->TouchpadCenterButton->text(), "touchpad_center"); + add_mapping(ui->TouchpadRightButton->text(), "touchpad_right"); + add_mapping(ui->OptionsButton->text(), "options"); lines.push_back(""); - input_string = "pad_up"; - output_string = ui->DpadUpBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_down"; - output_string = ui->DpadDownBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_left"; - output_string = ui->DpadLeftBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); - - input_string = "pad_right"; - output_string = ui->DpadRightBox->currentText().toStdString(); - lines.push_back(output_string + " = " + input_string); + add_mapping(ui->DpadUpButton->text(), "pad_up"); + add_mapping(ui->DpadDownButton->text(), "pad_down"); + add_mapping(ui->DpadLeftButton->text(), "pad_left"); + add_mapping(ui->DpadRightButton->text(), "pad_right"); lines.push_back(""); - input_string = "axis_left_x"; - output_string = ui->LStickRightBox->currentText().toStdString(); + output_string = "axis_left_x"; + input_string = ui->LStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_left_y"; - output_string = ui->LStickUpBox->currentText().toStdString(); + output_string = "axis_left_y"; + input_string = ui->LStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_right_x"; - output_string = ui->RStickRightBox->currentText().toStdString(); + output_string = "axis_right_x"; + input_string = ui->RStickRightButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); - input_string = "axis_right_y"; - output_string = ui->RStickUpBox->currentText().toStdString(); + output_string = "axis_right_y"; + input_string = ui->RStickUpButton->text().toStdString(); lines.push_back(output_string + " = " + input_string); lines.push_back(""); @@ -257,6 +277,33 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { lines.push_back("override_controller_color = " + OverrideLB + ", " + LightBarR + ", " + LightBarG + ", " + LightBarB); + // Prevent duplicate inputs that break the input engine + bool duplicateFound = false; + QSet duplicateMappings; + + for (auto it = inputs.begin(); it != inputs.end(); ++it) { + if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { + duplicateFound = true; + duplicateMappings.insert(QString::fromStdString(*it)); + } + } + + if (duplicateFound) { + QStringList duplicatesList; + for (const QString mapping : duplicateMappings) { + for (const auto& button : ButtonsList) { + if (button->text() == mapping) + duplicatesList.append(button->objectName() + " - " + mapping); + } + } + QMessageBox::information( + this, tr("Unable to Save"), + // clang-format off + QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n")))); + // clang-format on + return; + } + std::vector save; bool CurrentLineEmpty = false, LastLineEmpty = false; for (auto const& line : lines) { @@ -278,36 +325,43 @@ void ControlSettings::SaveControllerConfig(bool CloseOnSave) { ui->BSlider->value()); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + if (GameRunning) { + Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default") + : Input::ParseInputConfig(RunningGameSerial); + } + if (CloseOnSave) QWidget::close(); } void ControlSettings::SetDefault() { - ui->ABox->setCurrentIndex(0); - ui->BBox->setCurrentIndex(1); - ui->XBox->setCurrentIndex(2); - ui->YBox->setCurrentIndex(3); - ui->DpadUpBox->setCurrentIndex(11); - ui->DpadDownBox->setCurrentIndex(12); - ui->DpadLeftBox->setCurrentIndex(13); - ui->DpadRightBox->setCurrentIndex(14); - ui->LClickBox->setCurrentIndex(8); - ui->RClickBox->setCurrentIndex(9); - ui->LBBox->setCurrentIndex(4); - ui->RBBox->setCurrentIndex(5); - ui->LTBox->setCurrentIndex(6); - ui->RTBox->setCurrentIndex(7); - ui->StartBox->setCurrentIndex(10); - ui->BackBox->setCurrentIndex(15); + ui->CrossButton->setText("cross"); + ui->CircleButton->setText("circle"); + ui->SquareButton->setText("square"); + ui->TriangleButton->setText("triangle"); + ui->DpadUpButton->setText("pad_up"); + ui->DpadDownButton->setText("pad_down"); + ui->DpadLeftButton->setText("pad_left"); + ui->DpadRightButton->setText("pad_right"); + ui->L3Button->setText("l3"); + ui->R3Button->setText("r3"); + ui->L1Button->setText("l1"); + ui->R1Button->setText("r1"); + ui->L2Button->setText("l2"); + ui->R2Button->setText("r2"); + ui->OptionsButton->setText("options"); + ui->TouchpadLeftButton->setText("back"); + ui->TouchpadCenterButton->setText("unmapped"); + ui->TouchpadRightButton->setText("unmapped"); - ui->LStickUpBox->setCurrentIndex(1); - ui->LStickDownBox->setCurrentIndex(1); - ui->LStickLeftBox->setCurrentIndex(0); - ui->LStickRightBox->setCurrentIndex(0); - ui->RStickUpBox->setCurrentIndex(3); - ui->RStickDownBox->setCurrentIndex(3); - ui->RStickLeftBox->setCurrentIndex(2); - ui->RStickRightBox->setCurrentIndex(2); + ui->LStickUpButton->setText("axis_left_y"); + ui->LStickDownButton->setText("axis_left_y"); + ui->LStickLeftButton->setText("axis_left_x"); + ui->LStickRightButton->setText("axis_left_x"); + ui->RStickUpButton->setText("axis_right_y"); + ui->RStickDownButton->setText("axis_right_y"); + ui->RStickLeftButton->setText("axis_right_x"); + ui->RStickRightButton->setText("axis_right_x"); ui->LeftDeadzoneSlider->setValue(2); ui->RightDeadzoneSlider->setValue(2); @@ -320,32 +374,6 @@ void ControlSettings::SetDefault() { } void ControlSettings::AddBoxItems() { - ui->DpadUpBox->addItems(ButtonOutputs); - ui->DpadDownBox->addItems(ButtonOutputs); - ui->DpadLeftBox->addItems(ButtonOutputs); - ui->DpadRightBox->addItems(ButtonOutputs); - ui->LBBox->addItems(ButtonOutputs); - ui->RBBox->addItems(ButtonOutputs); - ui->LTBox->addItems(ButtonOutputs); - ui->RTBox->addItems(ButtonOutputs); - ui->RClickBox->addItems(ButtonOutputs); - ui->LClickBox->addItems(ButtonOutputs); - ui->StartBox->addItems(ButtonOutputs); - ui->ABox->addItems(ButtonOutputs); - ui->BBox->addItems(ButtonOutputs); - ui->XBox->addItems(ButtonOutputs); - ui->YBox->addItems(ButtonOutputs); - ui->BackBox->addItems(ButtonOutputs); - - ui->LStickUpBox->addItems(StickOutputs); - ui->LStickDownBox->addItems(StickOutputs); - ui->LStickLeftBox->addItems(StickOutputs); - ui->LStickRightBox->addItems(StickOutputs); - ui->RStickUpBox->addItems(StickOutputs); - ui->RStickDownBox->addItems(StickOutputs); - ui->RStickLeftBox->addItems(StickOutputs); - ui->RStickRightBox->addItems(StickOutputs); - ui->ProfileComboBox->addItem("Common Config"); for (int i = 0; i < m_game_info->m_games.size(); i++) { ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial)); @@ -366,7 +394,8 @@ void ControlSettings::SetUIValuestoMappings() { bool CrossExists = false, CircleExists = false, SquareExists = false, TriangleExists = false, L1Exists = false, L2Exists = false, L3Exists = false, R1Exists = false, R2Exists = false, R3Exists = false, DPadUpExists = false, DPadDownExists = false, DPadLeftExists = false, - DPadRightExists = false, StartExists = false, BackExists = false, LStickXExists = false, + DPadRightExists = false, OptionsExists = false, TouchpadLeftExists = false, + TouchpadCenterExists = false, TouchpadRightExists = false, LStickXExists = false, LStickYExists = false, RStickXExists = false, RStickYExists = false; int lineCount = 0; std::string line = ""; @@ -388,127 +417,144 @@ void ControlSettings::SetUIValuestoMappings() { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { - if (input_string == "cross") { - ui->ABox->setCurrentText(QString::fromStdString(output_string)); + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected) { + if (output_string == "cross") { + ui->CrossButton->setText(QString::fromStdString(input_string)); CrossExists = true; - } else if (input_string == "circle") { - ui->BBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "circle") { + ui->CircleButton->setText(QString::fromStdString(input_string)); CircleExists = true; - } else if (input_string == "square") { - ui->XBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "square") { + ui->SquareButton->setText(QString::fromStdString(input_string)); SquareExists = true; - } else if (input_string == "triangle") { - ui->YBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "triangle") { + ui->TriangleButton->setText(QString::fromStdString(input_string)); TriangleExists = true; - } else if (input_string == "l1") { - ui->LBBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l1") { + ui->L1Button->setText(QString::fromStdString(input_string)); L1Exists = true; - } else if (input_string == "l2") { - ui->LTBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l2") { + ui->L2Button->setText(QString::fromStdString(input_string)); L2Exists = true; - } else if (input_string == "r1") { - ui->RBBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r1") { + ui->R1Button->setText(QString::fromStdString(input_string)); R1Exists = true; - } else if (input_string == "r2") { - ui->RTBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r2") { + ui->R2Button->setText(QString::fromStdString(input_string)); R2Exists = true; - } else if (input_string == "l3") { - ui->LClickBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "l3") { + ui->L3Button->setText(QString::fromStdString(input_string)); L3Exists = true; - } else if (input_string == "r3") { - ui->RClickBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "r3") { + ui->R3Button->setText(QString::fromStdString(input_string)); R3Exists = true; - } else if (input_string == "pad_up") { - ui->DpadUpBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_up") { + ui->DpadUpButton->setText(QString::fromStdString(input_string)); DPadUpExists = true; - } else if (input_string == "pad_down") { - ui->DpadDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_down") { + ui->DpadDownButton->setText(QString::fromStdString(input_string)); DPadDownExists = true; - } else if (input_string == "pad_left") { - ui->DpadLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_left") { + ui->DpadLeftButton->setText(QString::fromStdString(input_string)); DPadLeftExists = true; - } else if (input_string == "pad_right") { - ui->DpadRightBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "pad_right") { + ui->DpadRightButton->setText(QString::fromStdString(input_string)); DPadRightExists = true; - } else if (input_string == "options") { - ui->StartBox->setCurrentText(QString::fromStdString(output_string)); - StartExists = true; - } else if (input_string == "back") { - ui->BackBox->setCurrentText(QString::fromStdString(output_string)); - BackExists = true; - } else if (input_string == "axis_left_x") { - ui->LStickRightBox->setCurrentText(QString::fromStdString(output_string)); - ui->LStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "options") { + ui->OptionsButton->setText(QString::fromStdString(input_string)); + OptionsExists = true; + } else if (output_string == "touchpad_left") { + ui->TouchpadLeftButton->setText(QString::fromStdString(input_string)); + TouchpadLeftExists = true; + } else if (output_string == "touchpad_center") { + ui->TouchpadCenterButton->setText(QString::fromStdString(input_string)); + TouchpadCenterExists = true; + } else if (output_string == "touchpad_right") { + ui->TouchpadRightButton->setText(QString::fromStdString(input_string)); + TouchpadRightExists = true; + } else if (output_string == "axis_left_x") { + ui->LStickRightButton->setText(QString::fromStdString(input_string)); + ui->LStickLeftButton->setText(QString::fromStdString(input_string)); LStickXExists = true; - } else if (input_string == "axis_left_y") { - ui->LStickUpBox->setCurrentText(QString::fromStdString(output_string)); - ui->LStickDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_left_y") { + ui->LStickUpButton->setText(QString::fromStdString(input_string)); + ui->LStickDownButton->setText(QString::fromStdString(input_string)); LStickYExists = true; - } else if (input_string == "axis_right_x") { - ui->RStickRightBox->setCurrentText(QString::fromStdString(output_string)); - ui->RStickLeftBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_right_x") { + ui->RStickRightButton->setText(QString::fromStdString(input_string)); + ui->RStickLeftButton->setText(QString::fromStdString(input_string)); RStickXExists = true; - } else if (input_string == "axis_right_y") { - ui->RStickUpBox->setCurrentText(QString::fromStdString(output_string)); - ui->RStickDownBox->setCurrentText(QString::fromStdString(output_string)); + } else if (output_string == "axis_right_y") { + ui->RStickUpButton->setText(QString::fromStdString(input_string)); + ui->RStickDownButton->setText(QString::fromStdString(input_string)); RStickYExists = true; - } else if (input_string.contains("leftjoystick")) { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); - ui->LeftDeadzoneSlider->setValue(deadzonevalue); - ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); - } else { - ui->LeftDeadzoneSlider->setValue(2); - ui->LeftDeadzoneValue->setText("2"); + } + } + + if (input_string.contains("leftjoystick")) { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->LeftDeadzoneSlider->setValue(deadzonevalue); + ui->LeftDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->LeftDeadzoneSlider->setValue(2); + ui->LeftDeadzoneValue->setText("2"); + } + } + + if (input_string.contains("rightjoystick")) { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); + ui->RightDeadzoneSlider->setValue(deadzonevalue); + ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); + } else { + ui->RightDeadzoneSlider->setValue(2); + ui->RightDeadzoneValue->setText("2"); + } + } + + if (output_string == "override_controller_color") { + std::size_t comma_pos = line.find(','); + if (comma_pos != std::string::npos) { + std::string overridestring = line.substr(equal_pos + 1, comma_pos); + bool override = overridestring.contains("true") ? true : false; + ui->LightbarCheckBox->setChecked(override); + + std::string lightbarstring = line.substr(comma_pos + 1); + std::size_t comma_pos2 = lightbarstring.find(','); + if (comma_pos2 != std::string::npos) { + std::string Rstring = lightbarstring.substr(0, comma_pos2); + ui->RSlider->setValue(std::stoi(Rstring)); + QString RedValue = QString("%1").arg(std::stoi(Rstring), 3, 10, QChar('0')); + QString RValue = tr("R:") + " " + RedValue; + ui->RLabel->setText(RValue); } - } else if (input_string.contains("rightjoystick")) { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - int deadzonevalue = std::stoi(line.substr(comma_pos + 1)); - ui->RightDeadzoneSlider->setValue(deadzonevalue); - ui->RightDeadzoneValue->setText(QString::number(deadzonevalue)); - } else { - ui->RightDeadzoneSlider->setValue(2); - ui->RightDeadzoneValue->setText("2"); - } - } else if (output_string == "override_controller_color") { - std::size_t comma_pos = line.find(','); - if (comma_pos != std::string::npos) { - std::string overridestring = line.substr(equal_pos + 1, comma_pos); - bool override = overridestring.contains("true") ? true : false; - ui->LightbarCheckBox->setChecked(override); - std::string lightbarstring = line.substr(comma_pos + 1); - std::size_t comma_pos2 = lightbarstring.find(','); - if (comma_pos2 != std::string::npos) { - std::string Rstring = lightbarstring.substr(0, comma_pos2); - ui->RSlider->setValue(std::stoi(Rstring)); - QString RedValue = QString("%1").arg(std::stoi(Rstring), 3, 10, QChar('0')); - QString RValue = tr("R:") + " " + RedValue; - ui->RLabel->setText(RValue); - } + std::string GBstring = lightbarstring.substr(comma_pos2 + 1); + std::size_t comma_pos3 = GBstring.find(','); + if (comma_pos3 != std::string::npos) { + std::string Gstring = GBstring.substr(0, comma_pos3); + ui->GSlider->setValue(std::stoi(Gstring)); + QString GreenValue = QString("%1").arg(std::stoi(Gstring), 3, 10, QChar('0')); + QString GValue = tr("G:") + " " + GreenValue; + ui->GLabel->setText(GValue); - std::string GBstring = lightbarstring.substr(comma_pos2 + 1); - std::size_t comma_pos3 = GBstring.find(','); - if (comma_pos3 != std::string::npos) { - std::string Gstring = GBstring.substr(0, comma_pos3); - ui->GSlider->setValue(std::stoi(Gstring)); - QString GreenValue = - QString("%1").arg(std::stoi(Gstring), 3, 10, QChar('0')); - QString GValue = tr("G:") + " " + GreenValue; - ui->GLabel->setText(GValue); - - std::string Bstring = GBstring.substr(comma_pos3 + 1); - ui->BSlider->setValue(std::stoi(Bstring)); - QString BlueValue = - QString("%1").arg(std::stoi(Bstring), 3, 10, QChar('0')); - QString BValue = tr("B:") + " " + BlueValue; - ui->BLabel->setText(BValue); - } + std::string Bstring = GBstring.substr(comma_pos3 + 1); + ui->BSlider->setValue(std::stoi(Bstring)); + QString BlueValue = QString("%1").arg(std::stoi(Bstring), 3, 10, QChar('0')); + QString BValue = tr("B:") + " " + BlueValue; + ui->BLabel->setText(BValue); } } } @@ -517,53 +563,57 @@ void ControlSettings::SetUIValuestoMappings() { // If an entry does not exist in the config file, we assume the user wants it unmapped if (!CrossExists) - ui->ABox->setCurrentText("unmapped"); + ui->CrossButton->setText("unmapped"); if (!CircleExists) - ui->BBox->setCurrentText("unmapped"); + ui->CircleButton->setText("unmapped"); if (!SquareExists) - ui->XBox->setCurrentText("unmapped"); + ui->SquareButton->setText("unmapped"); if (!TriangleExists) - ui->YBox->setCurrentText("unmapped"); + ui->TriangleButton->setText("unmapped"); if (!L1Exists) - ui->LBBox->setCurrentText("unmapped"); + ui->L1Button->setText("unmapped"); if (!L2Exists) - ui->LTBox->setCurrentText("unmapped"); + ui->L2Button->setText("unmapped"); if (!L3Exists) - ui->LClickBox->setCurrentText("unmapped"); + ui->L3Button->setText("unmapped"); if (!R1Exists) - ui->RBBox->setCurrentText("unmapped"); + ui->R1Button->setText("unmapped"); if (!R2Exists) - ui->RTBox->setCurrentText("unmapped"); + ui->R2Button->setText("unmapped"); if (!R3Exists) - ui->RClickBox->setCurrentText("unmapped"); + ui->R3Button->setText("unmapped"); if (!DPadUpExists) - ui->DpadUpBox->setCurrentText("unmapped"); + ui->DpadUpButton->setText("unmapped"); if (!DPadDownExists) - ui->DpadDownBox->setCurrentText("unmapped"); + ui->DpadDownButton->setText("unmapped"); if (!DPadLeftExists) - ui->DpadLeftBox->setCurrentText("unmapped"); + ui->DpadLeftButton->setText("unmapped"); if (!DPadRightExists) - ui->DpadRightBox->setCurrentText("unmapped"); - if (!BackExists) - ui->BackBox->setCurrentText("unmapped"); - if (!StartExists) - ui->StartBox->setCurrentText("unmapped"); + ui->DpadRightButton->setText("unmapped"); + if (!TouchpadLeftExists) + ui->TouchpadLeftButton->setText("unmapped"); + if (!TouchpadCenterExists) + ui->TouchpadCenterButton->setText("unmapped"); + if (!TouchpadRightExists) + ui->TouchpadRightButton->setText("unmapped"); + if (!OptionsExists) + ui->OptionsButton->setText("unmapped"); if (!LStickXExists) { - ui->LStickRightBox->setCurrentText("unmapped"); - ui->LStickLeftBox->setCurrentText("unmapped"); + ui->LStickRightButton->setText("unmapped"); + ui->LStickLeftButton->setText("unmapped"); } if (!LStickYExists) { - ui->LStickUpBox->setCurrentText("unmapped"); - ui->LStickDownBox->setCurrentText("unmapped"); + ui->LStickUpButton->setText("unmapped"); + ui->LStickDownButton->setText("unmapped"); } if (!RStickXExists) { - ui->RStickRightBox->setCurrentText("unmapped"); - ui->RStickLeftBox->setCurrentText("unmapped"); + ui->RStickRightButton->setText("unmapped"); + ui->RStickLeftButton->setText("unmapped"); } if (!RStickYExists) { - ui->RStickUpBox->setCurrentText("unmapped"); - ui->RStickDownBox->setCurrentText("unmapped"); + ui->RStickUpButton->setText("unmapped"); + ui->RStickDownButton->setText("unmapped"); } } @@ -589,4 +639,305 @@ void ControlSettings::UpdateLightbarColor() { ui->LightbarColorFrame->setStyleSheet(colorstring); } +void ControlSettings::CheckGamePad() { + if (GameRunning) + return; + + if (gamepad) { + SDL_CloseGamepad(gamepad); + gamepad = nullptr; + } + + int gamepad_count; + SDL_JoystickID* gamepads = SDL_GetGamepads(&gamepad_count); + + if (!gamepads) { + LOG_ERROR(Input, "Cannot get gamepad list: {}", SDL_GetError()); + return; + } + + if (gamepad_count == 0) { + LOG_INFO(Input, "No gamepad found!"); + SDL_free(gamepads); + return; + } + + LOG_INFO(Input, "Got {} gamepads. Opening the first one.", gamepad_count); + gamepad = SDL_OpenGamepad(gamepads[0]); + + if (!gamepad) { + LOG_ERROR(Input, "Failed to open gamepad 0: {}", SDL_GetError()); + SDL_free(gamepads); + return; + } + + SDL_free(gamepads); +} + +void ControlSettings::DisableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(false); + } + + for (const auto& i : AxisList) { + i->setEnabled(false); + } +} + +void ControlSettings::EnableMappingButtons() { + for (const auto& i : ButtonsList) { + i->setEnabled(true); + } + + for (const auto& i : AxisList) { + i->setEnabled(true); + } +} + +void ControlSettings::ConnectAxisInputs(QPushButton*& button) { + QString input = button->text(); + if (button == ui->LStickUpButton) { + ui->LStickDownButton->setText(input); + } else if (button == ui->LStickDownButton) { + ui->LStickUpButton->setText(input); + } else if (button == ui->LStickLeftButton) { + ui->LStickRightButton->setText(input); + } else if (button == ui->LStickRightButton) { + ui->LStickLeftButton->setText(input); + } else if (button == ui->RStickUpButton) { + ui->RStickDownButton->setText(input); + } else if (button == ui->RStickDownButton) { + ui->RStickUpButton->setText(input); + } else if (button == ui->RStickLeftButton) { + ui->RStickRightButton->setText(input); + } else if (button == ui->RStickRightButton) { + ui->RStickLeftButton->setText(input); + } +} + +void ControlSettings::StartTimer(QPushButton*& button, bool isButton) { + MappingTimer = 3; + isButton ? EnableButtonMapping = true : EnableAxisMapping = true; + MappingCompleted = false; + mapping = button->text(); + DisableMappingButtons(); + + EnableButtonMapping + ? button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]") + : button->setText(tr("Move analog stick") + " [" + QString::number(MappingTimer) + "]"); + + timer = new QTimer(this); + MappingButton = button; + timer->start(1000); + connect(timer, &QTimer::timeout, this, [this]() { CheckMapping(MappingButton); }); +} + +void ControlSettings::CheckMapping(QPushButton*& button) { + MappingTimer -= 1; + EnableButtonMapping + ? button->setText(tr("Press a button") + " [" + QString::number(MappingTimer) + "]") + : button->setText(tr("Move analog stick") + " [" + QString::number(MappingTimer) + "]"); + + if (pressedButtons.size() > 0) { + QStringList keyStrings; + + for (const QString& buttonAction : pressedButtons) { + keyStrings << buttonAction; + } + + QString combo = keyStrings.join(","); + SetMapping(combo); + MappingButton->setText(combo); + pressedButtons.clear(); + } + + if (MappingCompleted || MappingTimer <= 0) { + button->setText(mapping); + EnableButtonMapping = false; + EnableAxisMapping = false; + L2Pressed = false; + R2Pressed = false; + EnableMappingButtons(); + timer->stop(); + } +} + +void ControlSettings::SetMapping(QString input) { + mapping = input; + MappingCompleted = true; + if (EnableAxisMapping) { + emit PushGamepadEvent(); + emit AxisChanged(); + } +} + +// use QT events instead of SDL to override default event closing the window with escape +bool ControlSettings::eventFilter(QObject* obj, QEvent* event) { + if (event->type() == QEvent::KeyPress && EnableButtonMapping) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Escape) { + SetMapping("unmapped"); + return true; + } + } + return QDialog::eventFilter(obj, event); +} + +void ControlSettings::processSDLEvents(int Type, int Input, int Value) { + if (EnableButtonMapping) { + if (Type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + switch (Input) { + case SDL_GAMEPAD_BUTTON_SOUTH: + pressedButtons.insert("cross"); + break; + case SDL_GAMEPAD_BUTTON_EAST: + pressedButtons.insert("circle"); + break; + case SDL_GAMEPAD_BUTTON_NORTH: + pressedButtons.insert("triangle"); + break; + case SDL_GAMEPAD_BUTTON_WEST: + pressedButtons.insert("square"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + pressedButtons.insert("l1"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + pressedButtons.insert("r1"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + pressedButtons.insert("l3"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + pressedButtons.insert("r3"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + pressedButtons.insert("pad_up"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + pressedButtons.insert("pad_down"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + pressedButtons.insert("pad_left"); + break; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + pressedButtons.insert("pad_right"); + break; + case SDL_GAMEPAD_BUTTON_BACK: + pressedButtons.insert("back"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE1: + pressedButtons.insert("lpaddle_high"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1: + pressedButtons.insert("rpaddle_high"); + break; + case SDL_GAMEPAD_BUTTON_LEFT_PADDLE2: + pressedButtons.insert("lpaddle_low"); + break; + case SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2: + pressedButtons.insert("rpaddle_low"); + break; + case SDL_GAMEPAD_BUTTON_START: + pressedButtons.insert("options"); + break; + default: + break; + } + } + + if (Type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + // SDL trigger axis values range from 0 to 32000, set mapping on half movement + // Set zone for trigger release signal arbitrarily at 5000 + switch (Input) { + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + if (Value > 16000) { + pressedButtons.insert("l2"); + L2Pressed = true; + } else if (Value < 5000) { + if (L2Pressed) + emit PushGamepadEvent(); + } + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + if (Value > 16000) { + pressedButtons.insert("r2"); + R2Pressed = true; + } else if (Value < 5000) { + if (R2Pressed) + emit PushGamepadEvent(); + } + break; + default: + break; + } + } + + if (Type == SDL_EVENT_GAMEPAD_BUTTON_UP) + emit PushGamepadEvent(); + + } else if (EnableAxisMapping) { + if (Type == SDL_EVENT_GAMEPAD_AXIS_MOTION) { + // SDL stick axis values range from -32000 to 32000, set mapping on half movement + if (Value > 16000 || Value < -16000) { + switch (Input) { + case SDL_GAMEPAD_AXIS_LEFTX: + SetMapping("axis_left_x"); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + SetMapping("axis_left_y"); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + SetMapping("axis_right_x"); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + SetMapping("axis_right_y"); + break; + default: + break; + } + } + } + } +} + +void ControlSettings::pollSDLEvents() { + SDL_Event event; + while (SdlEventWrapper::Wrapper::wrapperActive) { + + if (!SDL_WaitEvent(&event)) { + return; + } + + if (event.type == SDL_EVENT_QUIT) { + return; + } + + if (event.type == SDL_EVENT_GAMEPAD_ADDED) { + CheckGamePad(); + } + + SdlEventWrapper::Wrapper::GetInstance()->Wrapper::ProcessEvent(&event); + } +} + +void ControlSettings::Cleanup() { + SdlEventWrapper::Wrapper::wrapperActive = false; + if (gamepad) + SDL_CloseGamepad(gamepad); + + if (!GameRunning) { + SDL_Event quitLoop{}; + quitLoop.type = SDL_EVENT_QUIT; + SDL_PushEvent(&quitLoop); + Polling.waitForFinished(); + + SDL_QuitSubSystem(SDL_INIT_GAMEPAD); + SDL_QuitSubSystem(SDL_INIT_EVENTS); + SDL_Quit(); + } else { + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "0"); + } +} + ControlSettings::~ControlSettings() {} diff --git a/src/qt_gui/control_settings.h b/src/qt_gui/control_settings.h index b1fff1dad..76d16b84e 100644 --- a/src/qt_gui/control_settings.h +++ b/src/qt_gui/control_settings.h @@ -2,7 +2,10 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include +#include #include "game_info.h" +#include "sdl_event_wrapper.h" namespace Ui { class ControlSettings; @@ -11,22 +14,56 @@ class ControlSettings; class ControlSettings : public QDialog { Q_OBJECT public: - explicit ControlSettings(std::shared_ptr game_info_get, - QWidget* parent = nullptr); + explicit ControlSettings(std::shared_ptr game_info_get, bool GameRunning, + std::string GameRunningSerial, QWidget* parent = nullptr); ~ControlSettings(); +signals: + void PushGamepadEvent(); + void AxisChanged(); + private Q_SLOTS: void SaveControllerConfig(bool CloseOnSave); void SetDefault(); void UpdateLightbarColor(); + void CheckMapping(QPushButton*& button); + void StartTimer(QPushButton*& button, bool isButton); + void ConnectAxisInputs(QPushButton*& button); private: std::unique_ptr ui; std::shared_ptr m_game_info; + bool eventFilter(QObject* obj, QEvent* event) override; void AddBoxItems(); void SetUIValuestoMappings(); void GetGameTitle(); + void CheckGamePad(); + void processSDLEvents(int Type, int Input, int Value); + void pollSDLEvents(); + void SetMapping(QString input); + void DisableMappingButtons(); + void EnableMappingButtons(); + void Cleanup(); + + QList ButtonsList; + QList AxisList; + QSet pressedButtons; + + std::string RunningGameSerial; + bool GameRunning; + bool L2Pressed = false; + bool R2Pressed = false; + bool EnableButtonMapping = false; + bool EnableAxisMapping = false; + bool MappingCompleted = false; + QString mapping; + int MappingTimer; + QTimer* timer; + QPushButton* MappingButton; + SDL_Gamepad* gamepad = nullptr; + SdlEventWrapper::Wrapper* RemapWrapper; + QFuture Polling; const std::vector ControllerInputs = { "cross", "circle", "square", "triangle", "l1", @@ -39,29 +76,8 @@ private: "pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", "back"}; - const QStringList ButtonOutputs = {"cross", - "circle", - "square", - "triangle", - "l1", - "r1", - "l2", - "r2", - "l3", - - "r3", - "options", - "pad_up", - - "pad_down", - - "pad_left", - "pad_right", - "touchpad_left", - "touchpad_center", - "touchpad_right", - "unmapped"}; - - const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", - "unmapped"}; +protected: + void closeEvent(QCloseEvent* event) override { + Cleanup(); + } }; diff --git a/src/qt_gui/control_settings.ui b/src/qt_gui/control_settings.ui index 41fb005c6..2eb4c754c 100644 --- a/src/qt_gui/control_settings.ui +++ b/src/qt_gui/control_settings.ui @@ -11,8 +11,8 @@ 0 0 - 1043 - 792 + 1114 + 794 @@ -33,8 +33,8 @@ 0 0 - 1019 - 732 + 1094 + 744 @@ -42,8 +42,8 @@ 0 0 - 1021 - 731 + 1091 + 741 @@ -110,7 +110,7 @@ - 124 + 152 0 @@ -125,12 +125,9 @@ - - - false - - - QComboBox::SizeAdjustPolicy::AdjustToContents + + + unmapped @@ -161,7 +158,11 @@ 5 - + + + unmapped + + @@ -185,9 +186,9 @@ 5 - - - false + + + unmapped @@ -213,6 +214,12 @@ + + + 152 + 0 + + 124 @@ -224,21 +231,9 @@ - - - true - - - - 0 - 0 - - - - - 0 - 0 - + + + unmapped @@ -378,7 +373,7 @@ - 124 + 152 16777215 @@ -387,9 +382,9 @@ - - - true + + + unmapped @@ -420,9 +415,9 @@ 5 - - - true + + + unmapped @@ -454,9 +449,9 @@ 5 - - - true + + + unmapped @@ -484,7 +479,7 @@ - 124 + 152 0 @@ -499,15 +494,9 @@ - - - true - - - false - - - false + + + unmapped @@ -617,149 +606,190 @@ 0 - - - - - - L1 / LB - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - L2 / LT - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - - - Qt::Orientation::Vertical - - - QSizePolicy::Policy::Preferred - - - - 20 - 40 - - - - - - 10 - - - - Back - - - - - - - + + + + + + 0 + 0 + + + + L1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 133 + 20 + + + + + + + + + 0 + 0 + + + + R1 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + + + + + L2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + R2 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + - - - - - - R1 / RB - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - R2 / RT - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - @@ -806,7 +836,7 @@ - + 10 @@ -814,76 +844,144 @@ QLayout::SizeConstraint::SetDefaultConstraint - - - L3 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - + + + + + + 0 + 0 + + + + L3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Fixed + + + + 133 + 20 + + + + + + + + + 0 + 0 + + + + R3 + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + unmapped + + + + + + + - - - Options / Start - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - - - - - - R3 - - - - 5 - - - 5 - - - 5 - - - 5 - - - - - - + + + + + Touchpad Left + + + + + + unmapped + + + + + + + + + + Touchpad Center + + + + + + unmapped + + + + + + + + + + Touchpad Right + + + + + + unmapped + + + + + + + @@ -1104,7 +1202,7 @@ - 124 + 152 0 @@ -1115,19 +1213,13 @@ - Triangle / Y + Triangle - - - true - - - - 0 - 0 - + + + unmapped @@ -1142,7 +1234,7 @@ - Square / X + Square @@ -1158,7 +1250,11 @@ 5 - + + + unmapped + + @@ -1166,7 +1262,7 @@ - Circle / B + Circle @@ -1182,7 +1278,11 @@ 5 - + + + unmapped + + @@ -1208,7 +1308,7 @@ - 124 + 152 0 @@ -1219,11 +1319,15 @@ - Cross / A + Cross - + + + unmapped + + @@ -1361,7 +1465,7 @@ - 124 + 152 1231321 @@ -1370,9 +1474,9 @@ - - - true + + + unmapped @@ -1403,9 +1507,9 @@ 5 - - - true + + + unmapped @@ -1431,7 +1535,11 @@ 5 - + + + unmapped + + @@ -1457,7 +1565,7 @@ - 124 + 152 0 @@ -1472,9 +1580,9 @@ - - - true + + + unmapped diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 1f7743412..ab1a7b845 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -7,16 +7,20 @@ #include #include #include +#include #include "common/path_util.h" +#include "input/input_handler.h" #include "kbm_config_dialog.h" #include "kbm_gui.h" #include "kbm_help_dialog.h" #include "ui_kbm_gui.h" HelpDialog* HelpWindow; -KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* parent) - : QDialog(parent), m_game_info(game_info_get), ui(new Ui::KBMSettings) { +KBMSettings::KBMSettings(std::shared_ptr game_info_get, bool isGameRunning, + std::string GameRunningSerial, QWidget* parent) + : QDialog(parent), m_game_info(game_info_get), GameRunning(isGameRunning), + RunningGameSerial(GameRunningSerial), ui(new Ui::KBMSettings) { ui->setupUi(this); ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); @@ -144,6 +148,8 @@ tr("Do you want to overwrite existing mappings with the mappings from the Common QString SOSString = tr("Speed Offset (def 0.125):") + " " + SOSValue; ui->SpeedOffsetLabel->setText(SOSString); }); + + connect(this, &KBMSettings::PushKBMEvent, this, [this]() { CheckMapping(MappingButton); }); } void KBMSettings::ButtonConnects() { @@ -269,9 +275,17 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) { output_string = line.substr(0, equal_pos - 1); input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != - ControllerInputs.end() || - output_string == "analog_deadzone" || output_string == "override_controller_color") { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (controllerInputdetected || output_string == "analog_deadzone" || + output_string == "override_controller_color") { lines.push_back(line); } } @@ -322,6 +336,11 @@ QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); + if (GameRunning) { + Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default") + : Input::ParseInputConfig(RunningGameSerial); + } + if (close_on_save) QWidget::close(); } @@ -388,8 +407,16 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::string output_string = line.substr(0, equal_pos - 1); std::string input_string = line.substr(equal_pos + 2); - if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == - ControllerInputs.end()) { + bool controllerInputdetected = false; + for (std::string input : ControllerInputs) { + // Needed to avoid detecting backspace while detecting back + if (input_string.contains(input) && !input_string.contains("backspace")) { + controllerInputdetected = true; + break; + } + } + + if (!controllerInputdetected) { if (output_string == "cross") { ui->CrossButton->setText(QString::fromStdString(input_string)); } else if (output_string == "circle") { @@ -518,7 +545,6 @@ void KBMSettings::StartTimer(QPushButton*& button) { MappingTimer = 3; EnableMapping = true; MappingCompleted = false; - modifier = ""; mapping = button->text(); DisableMappingButtons(); @@ -711,92 +737,98 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { break; // symbols + case Qt::Key_QuoteLeft: + pressedKeys.insert("grave"); + break; + case Qt::Key_AsciiTilde: + pressedKeys.insert("tilde"); + break; case Qt::Key_Exclam: - pressedKeys.insert("!"); + pressedKeys.insert("exclamation"); break; case Qt::Key_At: - pressedKeys.insert("@"); + pressedKeys.insert("at"); break; case Qt::Key_NumberSign: - pressedKeys.insert("#"); + pressedKeys.insert("hash"); break; case Qt::Key_Dollar: - pressedKeys.insert("$"); + pressedKeys.insert("dollar"); break; case Qt::Key_Percent: - pressedKeys.insert("%"); + pressedKeys.insert("percent"); break; case Qt::Key_AsciiCircum: - pressedKeys.insert("^"); + pressedKeys.insert("caret"); break; case Qt::Key_Ampersand: - pressedKeys.insert("&"); + pressedKeys.insert("ampersand"); break; case Qt::Key_Asterisk: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp*", "*")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpasterisk", "asterisk")); break; case Qt::Key_ParenLeft: - pressedKeys.insert("("); + pressedKeys.insert("lparen"); break; case Qt::Key_ParenRight: - pressedKeys.insert(")"); + pressedKeys.insert("rparen"); break; case Qt::Key_Minus: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp-", "-")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpminus", "minus")); break; case Qt::Key_Underscore: - pressedKeys.insert("_"); + pressedKeys.insert("underscore"); break; case Qt::Key_Equal: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp=", "=")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpequals", "equals")); break; case Qt::Key_Plus: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp+", "+")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpplus", "plus")); break; case Qt::Key_BracketLeft: - pressedKeys.insert("["); + pressedKeys.insert("lbracket"); break; case Qt::Key_BracketRight: - pressedKeys.insert("]"); + pressedKeys.insert("rbracket"); break; case Qt::Key_BraceLeft: - pressedKeys.insert("{"); + pressedKeys.insert("lbrace"); break; case Qt::Key_BraceRight: - pressedKeys.insert("}"); + pressedKeys.insert("rbrace"); break; case Qt::Key_Backslash: - pressedKeys.insert("\\"); + pressedKeys.insert("backslash"); break; case Qt::Key_Bar: - pressedKeys.insert("|"); + pressedKeys.insert("pipe"); break; case Qt::Key_Semicolon: - pressedKeys.insert(";"); + pressedKeys.insert("semicolon"); break; case Qt::Key_Colon: - pressedKeys.insert(":"); + pressedKeys.insert("colon"); break; case Qt::Key_Apostrophe: - pressedKeys.insert("'"); + pressedKeys.insert("apostrophe"); break; case Qt::Key_QuoteDbl: - pressedKeys.insert("\""); + pressedKeys.insert("quote"); break; case Qt::Key_Comma: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp,", ",")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpcomma", "comma")); break; case Qt::Key_Less: - pressedKeys.insert("<"); + pressedKeys.insert("less"); break; case Qt::Key_Period: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp.", ".")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpperiod", "period")); break; case Qt::Key_Greater: - pressedKeys.insert(">"); + pressedKeys.insert("greater"); break; case Qt::Key_Slash: - pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp/", "/")); + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpslash", "slash")); break; case Qt::Key_Question: pressedKeys.insert("question"); @@ -867,7 +899,6 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } break; case Qt::Key_Meta: - activateWindow(); #ifdef _WIN32 pressedKeys.insert("lwin"); #else @@ -878,7 +909,6 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { pressedKeys.insert("space"); break; case Qt::Key_Up: - activateWindow(); pressedKeys.insert("up"); break; case Qt::Key_Down: @@ -903,81 +933,99 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } return true; } - } - if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent* mouseEvent = static_cast(event); - if (pressedKeys.size() < 3) { - switch (mouseEvent->button()) { - case Qt::LeftButton: - pressedKeys.insert("leftbutton"); - break; - case Qt::RightButton: - pressedKeys.insert("rightbutton"); - break; - case Qt::MiddleButton: - pressedKeys.insert("middlebutton"); - break; - case Qt::XButton1: - pressedKeys.insert("sidebuttonback"); - break; - case Qt::XButton2: - pressedKeys.insert("sidebuttonforward"); - break; + if (event->type() == QEvent::MouseButtonPress) { + QMouseEvent* mouseEvent = static_cast(event); + if (pressedKeys.size() < 3) { + switch (mouseEvent->button()) { + case Qt::LeftButton: + pressedKeys.insert("leftbutton"); + break; + case Qt::RightButton: + pressedKeys.insert("rightbutton"); + break; + case Qt::MiddleButton: + pressedKeys.insert("middlebutton"); + break; + case Qt::XButton1: + pressedKeys.insert("sidebuttonback"); + break; + case Qt::XButton2: + pressedKeys.insert("sidebuttonforward"); + break; - // default case - default: - break; - // bottom text + // default case + default: + break; + // bottom text + } + return true; } - return true; } - } - const QList AxisList = { - ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, - ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; + const QList AxisList = { + ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, + ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; - if (event->type() == QEvent::Wheel) { - QWheelEvent* wheelEvent = static_cast(event); - if (pressedKeys.size() < 3) { - if (wheelEvent->angleDelta().y() > 5) { - if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - pressedKeys.insert("mousewheelup"); - } else { - QMessageBox::information(this, tr("Cannot set mapping"), - tr("Mousewheel cannot be mapped to stick outputs")); + if (event->type() == QEvent::Wheel) { + QWheelEvent* wheelEvent = static_cast(event); + if (pressedKeys.size() < 3) { + if (wheelEvent->angleDelta().y() > 5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == + AxisList.end()) { + pressedKeys.insert("mousewheelup"); + if (QApplication::keyboardModifiers() == Qt::NoModifier) + emit PushKBMEvent(); + } else { + QMessageBox::information( + this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); + } + } else if (wheelEvent->angleDelta().y() < -5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == + AxisList.end()) { + pressedKeys.insert("mousewheeldown"); + if (QApplication::keyboardModifiers() == Qt::NoModifier) + emit PushKBMEvent(); + } else { + QMessageBox::information( + this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); + } } - } else if (wheelEvent->angleDelta().y() < -5) { - if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - pressedKeys.insert("mousewheeldown"); - } else { - QMessageBox::information(this, tr("Cannot set mapping"), - tr("Mousewheel cannot be mapped to stick outputs")); - } - } - if (wheelEvent->angleDelta().x() > 5) { - if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - // QT changes scrolling to horizontal for all widgets with the alt modifier - pressedKeys.insert( - GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright")); - } else { - QMessageBox::information(this, tr("Cannot set mapping"), - tr("Mousewheel cannot be mapped to stick outputs")); - } - } else if (wheelEvent->angleDelta().x() < -5) { - if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - pressedKeys.insert( - GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft")); - } else { - QMessageBox::information(this, tr("Cannot set mapping"), - tr("Mousewheel cannot be mapped to stick outputs")); + if (wheelEvent->angleDelta().x() > 5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == + AxisList.end()) { + // QT changes scrolling to horizontal for all widgets with the alt modifier + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright")); + if (QApplication::keyboardModifiers() == Qt::NoModifier) + emit PushKBMEvent(); + } else { + QMessageBox::information( + this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); + } + } else if (wheelEvent->angleDelta().x() < -5) { + if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == + AxisList.end()) { + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft")); + if (QApplication::keyboardModifiers() == Qt::NoModifier) + emit PushKBMEvent(); + } else { + QMessageBox::information( + this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); + } } } } - } + if (event->type() == QEvent::KeyRelease || event->type() == QEvent::MouseButtonRelease) + emit PushKBMEvent(); + } return QDialog::eventFilter(obj, event); } -KBMSettings::~KBMSettings() {} \ No newline at end of file +KBMSettings::~KBMSettings() {} diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index 09a9166b9..1161fc30d 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -23,9 +23,13 @@ class KBMSettings; class KBMSettings : public QDialog { Q_OBJECT public: - explicit KBMSettings(std::shared_ptr game_info_get, QWidget* parent = nullptr); + explicit KBMSettings(std::shared_ptr game_info_get, bool GameRunning, + std::string GameRunningSerial, QWidget* parent = nullptr); ~KBMSettings(); +signals: + void PushKBMEvent(); + private Q_SLOTS: void SaveKBMConfig(bool CloseOnSave); void SetDefault(); @@ -44,13 +48,15 @@ private: void DisableMappingButtons(); void EnableMappingButtons(); void SetMapping(QString input); + void Cleanup(); + std::string RunningGameSerial; QSet pressedKeys; + bool GameRunning; bool EnableMapping = false; bool MappingCompleted = false; bool HelpWindowOpen = false; QString mapping; - QString modifier; int MappingTimer; QTimer* timer; QPushButton* MappingButton; diff --git a/src/qt_gui/kbm_help_dialog.cpp b/src/qt_gui/kbm_help_dialog.cpp index 1c40c6c4d..b9055222c 100644 --- a/src/qt_gui/kbm_help_dialog.cpp +++ b/src/qt_gui/kbm_help_dialog.cpp @@ -121,35 +121,6 @@ To view the config file's syntax, check out the Syntax tab, for keybind names, v This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)"; } -QString HelpDialog::faq() { - return R"( -Q: What are the emulator-wide keybinds? -A: --F12: Triggers Renderdoc capture --F11: Toggles fullscreen --F10: Toggles FPS counter --Ctrl+F10: Open the debug menu --F9: Pauses the emulator if the debug menu is open --F8: Reparses the config file while in-game --F7: Toggles mouse capture and mouse input --F6: Toggles mouse-to-gyro emulation - -Q: How do I switch between mouse and controller joystick input? Why is it even required? -A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it. - -Q: What happens if I accidentally make a typo in the config? -A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'. - -Q: I want to bind to , but your code doesn't support ! -A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4. - -Q: What does default.ini do? -A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead. - -Q: What does the use Per-game Config checkbox do? -A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)"; -} - QString HelpDialog::syntax() { return R"( Below is the file format for mouse, keyboard, and controller inputs: @@ -187,13 +158,12 @@ Keyboard: Numbers: '0', '1', ..., '9' Keypad: - 'kp 0', 'kp 1', ..., 'kp 9', - 'kp .', 'kp ,', 'kp /', 'kp *', 'kp -', 'kp +', 'kp =', 'kp enter' + 'kp0', 'kp1', ..., 'kp9', + 'kpperiod', 'kpcomma', 'kpslash', 'kpasterisk', 'kpminus', 'kpplus', 'kpequals', 'kpenter' Symbols: - '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '{', '}', '[', ']', '\', '|', - ';', ':', ''', '"', ',', '<', '.', '>', '/', '?' + (See below) Special keys: - 'escape (text editor only)', 'printscreen', 'scrolllock', 'pausebreak', + 'escape' (text editor only), 'printscreen', 'scrolllock', 'pausebreak', 'backspace', 'insert', 'delete', 'home', 'end', 'pgup', 'pgdown', 'tab', 'capslock', 'enter', 'space' Arrow keys: @@ -228,7 +198,38 @@ Controller: 'l2' Invalid Inputs: - 'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs.)"; + 'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs. + +Symbols (expanded): + ` 'grave' + ~ 'tilde' + ! 'exclamation' + @ 'at' + # 'hash' + $ 'dollar' + % 'percent' + ^ 'caret' + & 'ampersand' + * 'asterisk' + ( 'lparen' + - 'minus' + _ 'underscore' + = 'equals' + + 'plus' + [ 'lbracket' + { 'lbrace' + \ 'backslash' + | 'pipe' + ; 'semicolon' + : 'colon' + ' 'apostrophe' + " 'quote' + , 'comma' + < 'less' + . 'period' + > 'greater' + / 'slash' + ? 'question')"; } QString HelpDialog::special() { @@ -267,3 +268,35 @@ You can find these here, with detailed comments, examples, and suggestions for m 'mouse_gyro_roll_mode': Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active.)"; } + +QString HelpDialog::faq() { + return R"( +Q: What are the emulator-wide keybinds? +A: +-F12: Triggers Renderdoc capture +-F11: Toggles fullscreen +-F10: Toggles FPS counter +-Ctrl+F10: Open the debug menu +-F9: Pauses the emulator if the debug menu is open +-F8: Reparses the config file while in-game +-F7: Toggles mouse capture and mouse input +-F6: Toggles mouse-to-gyro emulation + +Q: How do I switch between mouse and controller joystick input? Why is it even required? +A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it. + +Q: What in the world is a 'grave' key? +A: (`). It represents one of the many symbols you can bind to a key. You can find the various symbols and their names in the Bindings tab. + +Q: What happens if I accidentally make a typo in the config? +A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'. + +Q: I want to bind to , but your code doesn't support ! +A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4. + +Q: What does default.ini do? +A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead. + +Q: What does the use Per-game Config checkbox do? +A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)"; +} \ No newline at end of file diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 166a31d72..f561bf392 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -473,12 +473,13 @@ void MainWindow::CreateConnects() { }); connect(ui->controllerButton, &QPushButton::clicked, this, [this]() { - auto configWindow = new ControlSettings(m_game_info, this); - configWindow->exec(); + ControlSettings* remapWindow = + new ControlSettings(m_game_info, isGameRunning, runningGameSerial, this); + remapWindow->exec(); }); connect(ui->keyboardButton, &QPushButton::clicked, this, [this]() { - auto kbmWindow = new KBMSettings(m_game_info, this); + auto kbmWindow = new KBMSettings(m_game_info, isGameRunning, runningGameSerial, this); kbmWindow->exec(); }); @@ -846,12 +847,14 @@ void MainWindow::StartGame() { if (m_game_list_frame->currentItem()) { int itemID = m_game_list_frame->currentItem()->row(); Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); + runningGameSerial = m_game_info->m_games[itemID].serial; } } else if (table_mode == 1) { if (m_game_grid_frame->cellClicked) { int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + m_game_grid_frame->crtColumn; Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); + runningGameSerial = m_game_info->m_games[itemID].serial; } } else { if (m_elf_viewer->currentItem()) { diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index eec1a65de..5b880c15e 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -75,11 +75,13 @@ private: void PlayBackgroundMusic(); QIcon RecolorIcon(const QIcon& icon, bool isWhite); void StartEmulator(std::filesystem::path); + bool isIconBlack = false; bool isTableList = true; bool isGameRunning = false; bool isWhite = false; bool is_paused = false; + std::string runningGameSerial = ""; QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr; diff --git a/src/qt_gui/sdl_event_wrapper.cpp b/src/qt_gui/sdl_event_wrapper.cpp new file mode 100644 index 000000000..608acbbc5 --- /dev/null +++ b/src/qt_gui/sdl_event_wrapper.cpp @@ -0,0 +1,47 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "sdl_event_wrapper.h" + +using namespace SdlEventWrapper; + +Wrapper* Wrapper::WrapperInstance = nullptr; +bool Wrapper::wrapperActive = false; + +Wrapper::Wrapper(QObject* parent) : QObject(parent) {} + +Wrapper* Wrapper::GetInstance() { + if (WrapperInstance == nullptr) { + WrapperInstance = new Wrapper(); + } + return WrapperInstance; +} + +bool Wrapper::ProcessEvent(SDL_Event* event) { + switch (event->type) { + case SDL_EVENT_WINDOW_RESTORED: + return false; + case SDL_EVENT_WINDOW_EXPOSED: + return false; + case SDL_EVENT_GAMEPAD_ADDED: + return false; + case SDL_EVENT_GAMEPAD_REMOVED: + return false; + case SDL_EVENT_QUIT: + emit SDLEvent(SDL_EVENT_QUIT, 0, 0); + return true; + case SDL_EVENT_GAMEPAD_BUTTON_DOWN: + emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_DOWN, event->gbutton.button, 0); + return true; + case SDL_EVENT_GAMEPAD_BUTTON_UP: + emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_UP, event->gbutton.button, 0); + return true; + case SDL_EVENT_GAMEPAD_AXIS_MOTION: + emit SDLEvent(SDL_EVENT_GAMEPAD_AXIS_MOTION, event->gaxis.axis, event->gaxis.value); + return true; + // block all other SDL events while wrapper is active + default: + return true; + } +} +Wrapper::~Wrapper() {} diff --git a/src/qt_gui/sdl_event_wrapper.h b/src/qt_gui/sdl_event_wrapper.h new file mode 100644 index 000000000..54d8c9cd1 --- /dev/null +++ b/src/qt_gui/sdl_event_wrapper.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once +#include +#include + +namespace SdlEventWrapper { + +class Wrapper : public QObject { + Q_OBJECT + +public: + explicit Wrapper(QObject* parent = nullptr); + ~Wrapper(); + bool ProcessEvent(SDL_Event* event); + static Wrapper* GetInstance(); + static bool wrapperActive; + static Wrapper* WrapperInstance; + +signals: + void SDLEvent(int Type, int Input, int Value); +}; + +} // namespace SdlEventWrapper diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index c9d264587..ed2a17e25 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -762,8 +762,8 @@ void SettingsDialog::UpdateSettings() { m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value()); Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]); Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); - Config::setScreenWidth(ui->widthSpinBox->value()); - Config::setScreenHeight(ui->heightSpinBox->value()); + Config::setWindowWidth(ui->widthSpinBox->value()); + Config::setWindowHeight(ui->heightSpinBox->value()); Config::setVblankDiv(ui->vblankSpinBox->value()); Config::setDumpShaders(ui->dumpShadersCheckBox->isChecked()); Config::setNullGpu(ui->nullGpuCheckBox->isChecked()); diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index a090c8b9b..9f36c3b82 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -453,34 +453,10 @@ Use per-game configs استخدام إعدادات كل لُعْبَة - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - رجوع - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - الخيارات / البَدْء - R3 R3 @@ -489,22 +465,6 @@ Face Buttons الأزرار - - Triangle / Y - مثلث / Y - - - Square / X - مربع / X - - - Circle / B - دائرة / B - - - Cross / A - إكس / A - Right Stick Deadzone (def:2, max:127) النقطة العمياء للعصا اليمنى (الافتراضي: 2، الحد الأقصى: 127) @@ -565,6 +525,74 @@ Cancel إلغاء + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated آخر تحديث + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for عارض SFO لـ + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index bb9dc3915..9b7f47c75 100644 --- a/src/qt_gui/translations/ca_ES.ts +++ b/src/qt_gui/translations/ca_ES.ts @@ -453,34 +453,10 @@ Use per-game configs Fes servir configuracions per cada joc - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Torna - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Opcions / Executa - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Botons d'acció - - Triangle / Y - Triangle / Y - - - Square / X - Quadrat / X - - - Circle / B - Cercle / B - - - Cross / A - Creu / A - Right Stick Deadzone (def:2, max:127) Zona morta de la palanca dreta (per defecte:2 màxim:127) @@ -565,6 +525,74 @@ Cancel Cancel·la + + unmapped + sense assignar + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Opcions + + + R2 + R2 + + + Touchpad Left + Touchpad esquerra + + + Touchpad Center + Touchpad centre + + + Touchpad Right + Touchpad dreta + + + Triangle + Triangle + + + Square + Quadrat + + + Circle + Cercle + + + Cross + Creu + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + No es pot assignar una entrada més d'una vegada. S'han assignat de manera duplicada pels següents botons: + +%1 + + + Press a button + Clica un botó + + + Move analog stick + Mou la palanca + EditorDialog @@ -748,6 +776,10 @@ Last updated Darrera actualització + + Favorite + Preferit + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualitzador SFO per + + Remove from Favorites + Esborra dels preferits + + + Add to Favorites + Afegeix a preferits + HelpDialog diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 871a05af4..a359bd148 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Sidst opdateret + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 771e4d2e4..46295c164 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -453,34 +453,10 @@ Use per-game configs Benutze Per-Game Einstellungen - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Zurück - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Aktionstasten - - Triangle / Y - Dreieck / Y - - - Square / X - Quadrat / X - - - Circle / B - Kreis / B - - - Cross / A - Kreuz / A - Right Stick Deadzone (def:2, max:127) Rechter Stick tote Zone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Abbrechen + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Zuletzt aktualisiert + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO-Betrachter für + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 6d6a629e2..824a2a970 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Τελευταία ενημέρωση + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 14394ea72..c2e35f4ee 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -453,34 +453,10 @@ Use per-game configs - - L1 / LB - - - - L2 / LT - - - - Back - - - - R1 / RB - - - - R2 / RT - - L3 - - Options / Start - - R3 @@ -489,22 +465,6 @@ Face Buttons - - Triangle / Y - - - - Square / X - - - - Circle / B - - - - Cross / A - - Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,72 @@ Cancel Cancel + + unmapped + + + + L1 + + + + R1 + + + + L2 + + + + Options + + + + R2 + + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + + + + Square + + + + Circle + + + + Cross + + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + + Press a button + + + + Move analog stick + + EditorDialog diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index 6e469c1fa..72b18437c 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -453,34 +453,10 @@ Use per-game configs Usar configuraciones por juego - - L1 / LB - L1/LB - - - L2 / LT - L2/LT - - - Back - Back - - - R1 / RB - R1/RB - - - R2 / RT - R2/RT - L3 L3 - - Options / Start - Options/Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Botones de acción - - Triangle / Y - Triángulo/Y - - - Square / X - Cuadrado/X - - - Circle / B - Círculo/B - - - Cross / A - Cruz / A - Right Stick Deadzone (def:2, max:127) Zona muerta del stick derecho (defecto: 2, máx.: 127) @@ -565,6 +525,72 @@ Cancel Cancelar + + unmapped + sin vincular + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Opciones + + + R2 + R2 + + + Touchpad Left + Izquierda del Touchpad + + + Touchpad Center + Centro del Touchpad + + + Touchpad Right + Derecha del Touchpad + + + Triangle + Triángulo + + + Square + Cuadrado + + + Circle + Círculo + + + Cross + Equis + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + No se puede asignar un control único más de una vez. Controles duplicados asignados a los siguientes botones + + + Press a button + Presiona un botón + + + Move analog stick + Mueve el stick analógico + EditorDialog @@ -748,6 +774,10 @@ Last updated Última actualización + + Favorite + Favorito + GameListUtils @@ -950,6 +980,14 @@ SFO Viewer for Visualizador de SFO para + + Remove from Favorites + Eliminar de Favoritos + + + Add to Favorites + Añadir a favoritos + HelpDialog @@ -1180,21 +1218,19 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: - -%1 + No se puede asignar un control único más de una vez. Controles duplicados asignados a los siguientes botones: Touchpad Left - Touchpad Left + Izquierda del Touchpad Touchpad Center - Touchpad Center + Centro del Touchpad Touchpad Right - Touchpad Right + Derecha del Touchpad diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index c270dd64a..bb91cc11b 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -453,34 +453,10 @@ Use per-game configs از پیکربندی‌های مخصوص هر بازی استفاده کنید - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - مثلث / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated آخرین به‌روزرسانی + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO مشاهده + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 49b6381e6..99cf2ea3e 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -453,34 +453,10 @@ Use per-game configs Käytä pelikohtaisia asetuksia - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Etunäppäimet - - Triangle / Y - Kolmio / Y - - - Square / X - Neliö / X - - - Circle / B - Ympyrä / B - - - Cross / A - Rasti / A - Right Stick Deadzone (def:2, max:127) Oikean Analogin Deadzone (oletus:2 max:127) @@ -565,6 +525,74 @@ Cancel Peruuta + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Viimeksi päivitetty + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 803063979..c7833737a 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -453,34 +453,10 @@ Use per-game configs Utiliser les configurations par jeu - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Retour - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Touches d'action - - Triangle / Y - Triangle / Y - - - Square / X - Carré / X - - - Circle / B - Rond / B - - - Cross / A - Croix / A - Right Stick Deadzone (def:2, max:127) Joystick Gauche Deadzone (def:2 max:127) @@ -565,6 +525,74 @@ Cancel Annuler + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Dernière mise à jour + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visionneuse SFO pour + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index d6f50a274..febfb1b73 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Utoljára frissítve + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index d35ec509f..5f3661395 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Terakhir diperbarui + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 65334a6f8..5cd1a01f8 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -453,34 +453,10 @@ Use per-game configs Usa configurazioni per gioco - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Indietro - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Opzioni / Avvio - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Pulsanti Frontali - - Triangle / Y - Triangolo / Y - - - Square / X - Quadrato / X - - - Circle / B - Cerchio / B - - - Cross / A - Croce / A - Right Stick Deadzone (def:2, max:127) Zona Morta Levetta Destra (def:2 max:127) @@ -565,6 +525,74 @@ Cancel Annulla + + unmapped + non mappato + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Opzioni + + + R2 + R2 + + + Touchpad Left + Touchpad Sinistra + + + Touchpad Center + Touchpad Centrale + + + Touchpad Right + Touchpad Destra + + + Triangle + Triangolo + + + Square + Quadrato + + + Circle + Cerchio + + + Cross + Croce + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti: + +%1 + + + Press a button + Premi un pulsante + + + Move analog stick + Muovi levetta analogica + EditorDialog @@ -748,6 +776,10 @@ Last updated Ultimo aggiornamento + + Favorite + Preferiti + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualizzatore SFO per + + Remove from Favorites + Rimuovi dai Preferiti + + + Add to Favorites + Aggiungi ai preferiti + HelpDialog @@ -1186,15 +1226,15 @@ Touchpad Left - Touchpad Left + Touchpad Sinistra Touchpad Center - Touchpad Center + Touchpad Centrale Touchpad Right - Touchpad Right + Touchpad Destra diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index dd10956f3..ce917b991 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - 戻る - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - 三角 / Y - - - Square / X - 四角 / X - - - Circle / B - 丸 / B - - - Cross / A - バツ / A - Right Stick Deadzone (def:2, max:127) 右スティックデッドゾーン(既定:2, 最大:127) @@ -565,6 +525,74 @@ Cancel キャンセル + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated 最終更新 + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index cbe8d00f9..827b1c881 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -453,34 +453,10 @@ Use per-game configs 게임 별 설정 사용 - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - 뒤로 - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - 옵션 / 시작 - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated 마지막 업데이트 + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index fda6f595f..b915b9adb 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Atgal - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Atšaukti + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Paskutinį kartą atnaujinta + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 1e022a5b4..eea0f6eb7 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -453,34 +453,10 @@ Use per-game configs Bruk oppsett per spill - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Tilbake - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Handlingsknapper - - Triangle / Y - Triangel / Y - - - Square / X - Firkant / X - - - Circle / B - Sirkel / B - - - Cross / A - Kryss / A - Right Stick Deadzone (def:2, max:127) Høyre analog dødsone (def:2, maks:127) @@ -535,7 +495,7 @@ Override Lightbar Color - Overstyr farge på lyslinja + Overstyr farge på lyslisten Override Color @@ -565,6 +525,74 @@ Cancel Avbryt + + unmapped + Ikke tildelt + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Venstre berøringsplate + + + Touchpad Center + Midt berøringsplate + + + Touchpad Right + Høyre berøringsplate + + + Triangle + Triangel + + + Square + Firkant + + + Circle + Sirkel + + + Cross + Kryss + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster: + +%1 + + + Press a button + Trykk på en knapp + + + Move analog stick + Flytt på analog stikke + EditorDialog @@ -748,6 +776,10 @@ Last updated Sist oppdatert + + Favorite + Favoritter + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO-viser for + + Remove from Favorites + Fjern fra favoritter + + + Add to Favorites + Legg til i favoritter + HelpDialog @@ -1078,7 +1118,7 @@ note: click Help Button/Special Keybindings for more information - Merk: Trykk på hjelpeknappen for mer informasjon + Merk: Trykk på «Hjelp»-knappen for mer informasjon Face Buttons @@ -1186,15 +1226,15 @@ Touchpad Left - Berøringsplate venstre + Venstre berøringsplate Touchpad Center - Berøringsplate midten + Midt berøringsplate Touchpad Right - Berøringsplate høyre + Høyre berøringsplate diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 7dc03bbc5..5a6674beb 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -582,7 +610,7 @@ Could not open the file for reading - + Could not open the file for reading Could not open the file for writing @@ -748,6 +776,10 @@ Last updated Laatst bijgewerkt + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 38036c07f..c5611c5b1 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -453,34 +453,10 @@ Use per-game configs Użyj osobnej konfiguracji dla każdej gry - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Wstecz - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Opcje / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Przyciski akcji - - Triangle / Y - Trójkąt / Y - - - Square / X - Kwadrat / X - - - Circle / B - Kółko / B - - - Cross / A - Krzyżyk / A - Right Stick Deadzone (def:2, max:127) Martwa strefa prawego drążka (def:2 max:127) @@ -565,6 +525,74 @@ Cancel Anuluj + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Ostatnia aktualizacja + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Menedżer plików SFO dla + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index acc75790e..7fa0aea67 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -453,34 +453,10 @@ Use per-game configs Usar configurações por jogo - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Voltar - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Botões de Ação - - Triangle / Y - Triângulo / Y - - - Square / X - Quadrado / X - - - Circle / B - Círculo / B - - - Cross / A - Cruz / A - Right Stick Deadzone (def:2, max:127) Zona Morta do Analógico Direito (Pad: 2, Máx: 127) @@ -565,6 +525,74 @@ Cancel Cancelar + + unmapped + não mapeado + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Opções + + + R2 + R2 + + + Touchpad Left + Touchpad Esquerdo + + + Touchpad Center + Centro do Touchpad + + + Touchpad Right + Touchpad Direito + + + Triangle + Triângulo + + + Square + Quadrado + + + Circle + Círculo + + + Cross + Cruz + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Não é possível atribuir a mesma entrada mais de uma vez. Entradas duplicadas foram atribuídas aos seguintes botões: + +%1 + + + Press a button + Pressione um botão + + + Move analog stick + Mover analógico + EditorDialog @@ -748,6 +776,10 @@ Last updated Última atualização + + Favorite + Favorito + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualizador de SFO para + + Remove from Favorites + Remover dos Favoritos + + + Add to Favorites + Adicionar aos Favoritos + HelpDialog diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index fba315859..53497b7b9 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -453,34 +453,10 @@ Use per-game configs Utilizar configurações por jogo - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Voltar - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Opções / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Botões Frontais - - Triangle / Y - Triângulo / Y - - - Square / X - Quadrado / X - - - Circle / B - Círculo / B - - - Cross / A - Cruz / A - Right Stick Deadzone (def:2, max:127) Zona Morta do Manípulo Direito (def: 2, max: 127) @@ -565,6 +525,74 @@ Cancel Cancelar + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Última atualização + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualizador SFO para + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 1a626d1a8..0e1647245 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Ultima actualizare + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index 7579078e6..1bbc2b3b8 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -453,34 +453,10 @@ Use per-game configs Использовать настройки для каждой игры - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Назад - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Кнопки действий - - Triangle / Y - Треугольник / Y - - - Square / X - Квадрат / X - - - Circle / B - Круг / B - - - Cross / A - Крест / A - Right Stick Deadzone (def:2, max:127) Мёртвая зона правого стика (по умолч:2 макс:127) @@ -565,6 +525,74 @@ Cancel Отмена + + unmapped + не назначено + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Тачпад слева + + + Touchpad Center + Тачпад центр + + + Touchpad Right + Тачпад справа + + + Triangle + Треугольник + + + Square + Квадрат + + + Circle + Круг + + + Cross + Крест + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки: + +%1 + + + Press a button + Нажмите кнопку + + + Move analog stick + Двиньте аналоговый стик + EditorDialog @@ -748,6 +776,10 @@ Last updated Последнее обновление + + Favorite + Избранное + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Просмотр SFO для + + Remove from Favorites + Удалить из избранного + + + Add to Favorites + Добавить в избранное + HelpDialog diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index 1a0c5df5b..97d36b223 100644 --- a/src/qt_gui/translations/sl_SI.ts +++ b/src/qt_gui/translations/sl_SI.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Last updated + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 26daf7419..9908dc564 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -453,34 +453,10 @@ Use per-game configs Përdor konfigurime të veçanta për secilën lojë - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Mbrapa - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Butonat kryesore - - Triangle / Y - Trekëndësh / Y - - - Square / X - Katror / X - - - Circle / B - Rreth / B - - - Cross / A - Kryq / A - Right Stick Deadzone (def:2, max:127) Zona e vdekur e levës së djathtë (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Anulo + + unmapped + pacaktuar + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Paneli me Prekje Majtas + + + Touchpad Center + Paneli me Prekje në Qendër + + + Touchpad Right + Paneli me Prekje Djathtas + + + Triangle + Trekëndësh + + + Square + Katror + + + Circle + Rreth + + + Cross + Kryq + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Nuk mund të caktohet e njëjta hyrje unike më shumë se një herë. Hyrjet e dyfishta janë caktuar në butonët e mëposhtëm: + +%1 + + + Press a button + Shtyp një buton + + + Move analog stick + Lëviz levën + EditorDialog @@ -748,6 +776,10 @@ Last updated Përditësuar për herë të fundit + + Favorite + Të Preferuarat + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Shikuesi SFO për + + Remove from Favorites + Hiq nga të Preferuarat + + + Add to Favorites + Shto në të Preferuarat + HelpDialog @@ -1186,15 +1226,15 @@ Touchpad Left - Touchpad Left + Paneli me Prekje Majtas Touchpad Center - Touchpad Center + Paneli me Prekje në Qendër Touchpad Right - Touchpad Right + Paneli me Prekje Djathtas diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts index e6527006d..1619d7b70 100644 --- a/src/qt_gui/translations/sr_CS.ts +++ b/src/qt_gui/translations/sr_CS.ts @@ -453,34 +453,10 @@ Use per-game configs Use per-game configs - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Face Buttons - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Cancel + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Last updated + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index c10f0058f..65762f974 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -453,34 +453,10 @@ Use per-game configs Använd konfigurationer per spel - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Bakåt - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Handlingsknappar - - Triangle / Y - Triangel / Y - - - Square / X - Fyrkant / X - - - Circle / B - Cirkel / B - - - Cross / A - Kryss / A - Right Stick Deadzone (def:2, max:127) Dödläge för höger spak (standard:2, max:127) @@ -565,6 +525,74 @@ Cancel Avbryt + + unmapped + omappad + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Pekplatta vänster + + + Touchpad Center + Pekplatta i mitten + + + Touchpad Right + Pekplatta höger + + + Triangle + Triangel + + + Square + Fyrkant + + + Circle + Cirkel + + + Cross + Kors + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Det går inte att binda samma unika inmatning mer än en gång. Dubbletta inmatningar är mappade till följande knappar: + +%1 + + + Press a button + Tryck på en knapp + + + Move analog stick + Rör analog spak + EditorDialog @@ -748,6 +776,10 @@ Last updated Senast uppdaterad + + Favorite + Favorit + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO-visare för + + Remove from Favorites + Ta bort från favoriter + + + Add to Favorites + Lägg till i favoriter + HelpDialog diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 8838b3132..d4a94b42f 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -453,34 +453,10 @@ Use per-game configs Oyuna özel yapılandırma kullan - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Geri - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Seçenekler / Başlat - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Eylem Düğmeleri - - Triangle / Y - Üçgen / Y - - - Square / X - Kare / X - - - Circle / B - Daire / B - - - Cross / A - Çarpı / A - Right Stick Deadzone (def:2, max:127) Sağ Analog Ölü Bölgesi (varsayılan: 2, en çok: 127) @@ -565,6 +525,74 @@ Cancel İptal + + unmapped + atanmamış + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Seçenekler + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Üçgen + + + Square + Kare + + + Circle + Yuvarlak + + + Cross + Çarpı + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Aynı tuş birden fazla kez atanamaz. Aşağıdaki tuşlara birden fazla giriş atanmış: + +%1 + + + Press a button + Bir Düğmeye Bas + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Son güncelleme + + Favorite + Sık Kullanılan + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO Görüntüleyici: + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Favorilere Ekle + HelpDialog @@ -1180,7 +1220,7 @@ Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: %1 - Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + Aynı tuş birden fazla kez atanamaz. Aşağıdaki tuşlara birden fazla giriş atanmış: %1 diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 070194fc3..73aa2c352 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -453,34 +453,10 @@ Use per-game configs Використовувати ігрові конфігурації - - L1 / LB - L1 / Лівий Бампер - - - L2 / LT - L2 / Лівий Тригер - - - Back - Назад - - - R1 / RB - R1 / Правий Бампер - - - R2 / RT - R2 / Правий Тригер - L3 Кнопка лівого стику - - Options / Start - Опції / Старт - R3 Кнопка правого стику @@ -489,22 +465,6 @@ Face Buttons Лицьові кнопки - - Triangle / Y - Трикутник / Y - - - Square / X - Квадрат / X - - - Circle / B - Коло / B - - - Cross / A - Хрест / A - Right Stick Deadzone (def:2, max:127) Мертва зона правого стику (за замов.: 2, максимум: 127) @@ -565,6 +525,74 @@ Cancel Відмінити + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Останнє оновлення + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Перегляд SFO + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 961f9272b..ebc6a8a52 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -453,34 +453,10 @@ Use per-game configs Cấu hình riêng cho từng game - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Quay Lại - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Tuỳ chọn / Bắt đầu - R3 R3 @@ -489,22 +465,6 @@ Face Buttons Nút bấm mặt trước - - Triangle / Y - Tam giác / Y - - - Square / X - Vuông / X - - - Circle / B - Tròn / B - - - Cross / A - Chéo / A - Right Stick Deadzone (def:2, max:127) Right Stick Deadzone (def:2, max:127) @@ -565,6 +525,74 @@ Cancel Hủy + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated Cập nhật lần cuối + + Favorite + Favorite + GameListUtils @@ -924,7 +956,7 @@ No log file found for this game! - ! + Failed to convert icon. @@ -950,6 +982,14 @@ SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 9533e95ea..4504a2c11 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -453,34 +453,10 @@ Use per-game configs 使用每个游戏的配置 - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons 功能键(动作键) - - Triangle / Y - 三角 / Y - - - Square / X - 方框 / X - - - Circle / B - 圈 / B - - - Cross / A - 叉 / A - Right Stick Deadzone (def:2, max:127) 右摇杆死区(默认:2 最大:127) @@ -565,6 +525,74 @@ Cancel 取消 + + unmapped + 未映射 + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + 选项 + + + R2 + R2 + + + Touchpad Left + 触摸板左侧 + + + Touchpad Center + 触控板中间 + + + Touchpad Right + 触摸板右侧 + + + Triangle + 三角 + + + Square + 方框 + + + Circle + + + + Cross + + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + 不能多次绑定任何同一输入。请重新映射以下按键的输入: + +%1 + + + Press a button + 请按一个按键 + + + Move analog stick + 移动模拟摇杆 + EditorDialog @@ -748,6 +776,10 @@ Last updated 最后更新 + + Favorite + 收藏 + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO 查看器 - + + Remove from Favorites + 从收藏中移除 + + + Add to Favorites + 添加至收藏 + HelpDialog diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 2b33053e0..8d2d88966 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -453,34 +453,10 @@ Use per-game configs 使用個別遊戲組態 - - L1 / LB - L1 / LB - - - L2 / LT - L2 / LT - - - Back - Back - - - R1 / RB - R1 / RB - - - R2 / RT - R2 / RT - L3 L3 - - Options / Start - Options / Start - R3 R3 @@ -489,22 +465,6 @@ Face Buttons 功能鍵(動作按鈕) - - Triangle / Y - 三角 / Y - - - Square / X - 正方 / X - - - Circle / B - 圖形 / B - - - Cross / A - 交叉 / A - Right Stick Deadzone (def:2, max:127) 右搖桿無效區域(預設:2 最大:127) @@ -565,6 +525,74 @@ Cancel 取消 + + unmapped + unmapped + + + L1 + L1 + + + R1 + R1 + + + L2 + L2 + + + Options + Options + + + R2 + R2 + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + + + Triangle + Triangle + + + Square + Square + + + Circle + Circle + + + Cross + Cross + + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + Press a button + Press a button + + + Move analog stick + Move analog stick + EditorDialog @@ -748,6 +776,10 @@ Last updated 最後更新 + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO 檢視器: + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index 735f14639..69819a00f 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -20,6 +20,10 @@ #include "sdl_window.h" #include "video_core/renderdoc.h" +#ifdef ENABLE_QT_GUI +#include "qt_gui/sdl_event_wrapper.h" +#endif + #ifdef __APPLE__ #include "SDL3/SDL_metal.h" #endif @@ -340,6 +344,13 @@ void WindowSDL::WaitEvent() { return; } +#ifdef ENABLE_QT_GUI + if (SdlEventWrapper::Wrapper::wrapperActive) { + if (SdlEventWrapper::Wrapper::GetInstance()->ProcessEvent(&event)) + return; + } +#endif + if (ImGui::Core::ProcessEvent(&event)) { return; } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 02f290140..c4c310586 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -300,7 +300,7 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct if (stage == LogicalStage::TessellationControl || stage == LogicalStage::TessellationEval) { ctx.AddCapability(spv::Capability::Tessellation); } - if (info.dma_types != IR::Type::Void) { + if (info.uses_dma) { ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses); ctx.AddExtension("SPV_KHR_physical_storage_buffer"); } @@ -310,6 +310,19 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); } + if (info.uses_buffer_int64_atomics || info.uses_shared_int64_atomics) { + if (info.uses_buffer_int64_atomics) { + ASSERT_MSG(ctx.profile.supports_buffer_int64_atomics, + "Shader requires support for atomic Int64 buffer operations that your " + "Vulkan instance does not advertise"); + } + if (info.uses_shared_int64_atomics) { + ASSERT_MSG(ctx.profile.supports_shared_int64_atomics, + "Shader requires support for atomic Int64 shared memory operations that " + "your Vulkan instance does not advertise"); + } + ctx.AddCapability(spv::Capability::Int64Atomics); + } } void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) { diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 97e455ff8..80c8b836b 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -7,7 +7,11 @@ #include "shader_recompiler/backend/spirv/spirv_emit_context.h" namespace Shader::Backend::SPIRV { + namespace { +using PointerType = EmitContext::PointerType; +using PointerSize = EmitContext::PointerSize; + std::pair AtomicArgs(EmitContext& ctx) { const Id scope{ctx.ConstU32(static_cast(spv::Scope::Device))}; const Id semantics{ctx.u32_zero_value}; @@ -50,25 +54,30 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, }); } +Id SharedAtomicU64IncDec(EmitContext& ctx, Id offset, + Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { + const Id shift_id{ctx.ConstU32(3U)}; + const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)}; + const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index)}; + const auto [scope, semantics]{AtomicArgs(ctx)}; + return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { + return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics); + }); +} + template Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; - const auto type = [&] { - if constexpr (is_float) { - return ctx.F32[1]; - } else { - return ctx.U32[1]; - } - }(); - if (Sirit::ValidId(buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + const Id type = is_float ? ctx.F32[1] : ctx.U32[1]; + if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; - const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [id, pointer_type] = buffer.Alias(PointerType::U32); + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address); const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<32, 1, is_float>(ctx, index, buffer.size_dwords, [&] { + return AccessBoundsCheck<32, 1, is_float>(ctx, address, buffer.Size(PointerSize::B32), [&] { return (ctx.*atomic_func)(type, ptr, scope, semantics, value); }); } @@ -76,14 +85,13 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; - if (Sirit::ValidId(buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; - const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [id, pointer_type] = buffer.Alias(PointerType::U32); + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address); const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { + return AccessBoundsCheck<32>(ctx, address, buffer.Size(PointerSize::B32), [&] { return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics); }); } @@ -92,14 +100,13 @@ Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre Id cmp_value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; - if (Sirit::ValidId(buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; - const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [id, pointer_type] = buffer.Alias(PointerType::U32); + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address); const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] { + return AccessBoundsCheck<32>(ctx, address, buffer.Size(PointerSize::B32), [&] { return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value); }); } @@ -107,14 +114,13 @@ Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre Id BufferAtomicU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { const auto& buffer = ctx.buffers[handle]; - if (Sirit::ValidId(buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + if (const Id offset = buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U64]; - const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); + const auto [id, pointer_type] = buffer.Alias(PointerType::U64); + const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address); const auto [scope, semantics]{AtomicArgs(ctx)}; - return AccessBoundsCheck<64>(ctx, index, buffer.size_qwords, [&] { + return AccessBoundsCheck<64>(ctx, address, buffer.Size(PointerSize::B64), [&] { return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value); }); } @@ -148,42 +154,82 @@ Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMax); } +Id EmitSharedAtomicUMax64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicUMax); +} + Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicSMax); } +Id EmitSharedAtomicSMax64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicSMax); +} + Id EmitSharedAtomicUMin32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMin); } +Id EmitSharedAtomicUMin64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicUMin); +} + Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicSMin); } +Id EmitSharedAtomicSMin64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicSMin); +} + Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicAnd); } +Id EmitSharedAtomicAnd64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicAnd); +} + Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicOr); } +Id EmitSharedAtomicOr64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicOr); +} + Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicXor); } +Id EmitSharedAtomicXor64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicXor); +} + Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value) { return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub); } +Id EmitSharedAtomicISub64(EmitContext& ctx, Id offset, Id value) { + return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicISub); +} + Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset) { return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); } +Id EmitSharedAtomicInc64(EmitContext& ctx, Id offset) { + return SharedAtomicU64IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); +} + Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset) { return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); } +Id EmitSharedAtomicDec64(EmitContext& ctx, Id offset) { + return SharedAtomicU64IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); +} + Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); } @@ -200,10 +246,18 @@ Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin); } +Id EmitBufferAtomicSMin64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin); +} + Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMin); } +Id EmitBufferAtomicUMin64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMin); +} + Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { if (ctx.profile.supports_buffer_fp32_atomic_min_max) { return BufferAtomicU32(ctx, inst, handle, address, value, @@ -226,10 +280,18 @@ Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax); } +Id EmitBufferAtomicSMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax); +} + Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax); } +Id EmitBufferAtomicUMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax); +} + Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { if (ctx.profile.supports_buffer_fp32_atomic_min_max) { return BufferAtomicU32(ctx, inst, handle, address, value, @@ -360,7 +422,7 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { const auto& buffer = ctx.buffers[binding]; - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; + const auto [id, pointer_type] = buffer.Alias(PointerType::U32); const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const auto [scope, semantics]{AtomicArgs(ctx)}; return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics); @@ -368,7 +430,7 @@ Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) { const auto& buffer = ctx.buffers[binding]; - const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; + const auto [id, pointer_type] = buffer.Alias(PointerType::U32); const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const auto [scope, semantics]{AtomicArgs(ctx)}; return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp index ccbe54d0a..f3a8c518c 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp @@ -2,7 +2,9 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "common/assert.h" +#include "common/config.h" #include "common/logging/log.h" +#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/ir/attribute.h" @@ -11,8 +13,6 @@ #include -#include "emit_spirv_bounds.h" - namespace Shader::Backend::SPIRV { namespace { @@ -164,9 +164,13 @@ void EmitGetGotoVariable(EmitContext&) { } using PointerType = EmitContext::PointerType; +using PointerSize = EmitContext::PointerSize; Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) { const u32 flatbuf_off_dw = inst->Flags(); + if (!Config::directMemoryAccess()) { + return ctx.EmitFlatbufferLoad(ctx.ConstU32(flatbuf_off_dw)); + } // We can only provide a fallback for immediate offsets. if (flatbuf_off_dw == 0) { return ctx.OpFunctionCall(ctx.U32[1], ctx.read_const_dynamic, addr, offset); @@ -179,14 +183,15 @@ Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) { template Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { const auto& buffer = ctx.buffers[handle]; - index = ctx.OpIAdd(ctx.U32[1], index, buffer.offset_dwords); - const auto [id, pointer_type] = buffer[type]; + if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + index = ctx.OpIAdd(ctx.U32[1], index, offset); + } + const auto [id, pointer_type] = buffer.Alias(type); const auto value_type = type == PointerType::U32 ? ctx.U32[1] : ctx.F32[1]; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; const Id result{ctx.OpLoad(value_type, ptr)}; - - if (Sirit::ValidId(buffer.size_dwords)) { - const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer.size_dwords); + if (const Id size = buffer.Size(PointerSize::B32); Sirit::ValidId(size)) { + const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, size); return ctx.OpSelect(value_type, in_bounds, result, ctx.u32_zero_value); } return result; @@ -419,25 +424,24 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) { template static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { + constexpr bool is_float = alias == PointerType::F32; const auto flags = inst->Flags(); const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; - const auto [id, pointer_type] = spv_buffer[alias]; + const auto [id, pointer_type] = spv_buffer.Alias(alias); boost::container::static_vector ids; for (u32 i = 0; i < N; i++) { - const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); + const Id index_i = i == 0 ? address : ctx.OpIAdd(ctx.U32[1], address, ctx.ConstU32(i)); const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i); const Id result_i = ctx.OpLoad(data_types[1], ptr_i); if (!flags.typed) { // Untyped loads have bounds checking per-component. - ids.push_back(LoadAccessBoundsCheck < 32, 1, - alias == - PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i)); + ids.push_back(LoadAccessBoundsCheck<32, 1, is_float>( + ctx, index_i, spv_buffer.Size(PointerSize::B32), result_i)); } else { ids.push_back(result_i); } @@ -446,33 +450,32 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids); if (flags.typed) { // Typed loads have single bounds check for the whole load. - return LoadAccessBoundsCheck < 32, N, - alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result); + return LoadAccessBoundsCheck<32, N, is_float>(ctx, address, + spv_buffer.Size(PointerSize::B32), result); } return result; } Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B8); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U8]; + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U8); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id result{ctx.OpLoad(ctx.U8, ptr)}; - return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result); + return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.Size(PointerSize::B8), result); } Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B16); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U16]; - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); - const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U16); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id result{ctx.OpLoad(ctx.U16, ptr)}; - return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result); + return LoadAccessBoundsCheck<16>(ctx, address, spv_buffer.Size(PointerSize::B16), result); } Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -493,14 +496,13 @@ Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U64]; - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); - const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)}; + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U64); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, address)}; const Id result{ctx.OpLoad(ctx.U64, ptr)}; - return LoadAccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, result); + return LoadAccessBoundsCheck<64>(ctx, address, spv_buffer.Size(PointerSize::B64), result); } Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { @@ -526,18 +528,18 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr template static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { + constexpr bool is_float = alias == PointerType::F32; const auto flags = inst->Flags(); const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; - const auto [id, pointer_type] = spv_buffer[alias]; + const auto [id, pointer_type] = spv_buffer.Alias(alias); auto store = [&] { for (u32 i = 0; i < N; i++) { - const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); + const Id index_i = i == 0 ? address : ctx.OpIAdd(ctx.U32[1], address, ctx.ConstU32(i)); const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i); const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i); auto store_i = [&] { @@ -546,8 +548,8 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I }; if (!flags.typed) { // Untyped stores have bounds checking per-component. - AccessBoundsCheck<32, 1, alias == PointerType::F32>( - ctx, index_i, spv_buffer.size_dwords, store_i); + AccessBoundsCheck<32, 1, is_float>(ctx, index_i, spv_buffer.Size(PointerSize::B32), + store_i); } else { store_i(); } @@ -557,8 +559,7 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I if (flags.typed) { // Typed stores have single bounds check for the whole store. - AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords, - store); + AccessBoundsCheck<32, N, is_float>(ctx, address, spv_buffer.Size(PointerSize::B32), store); } else { store(); } @@ -566,12 +567,12 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B8); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U8]; + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U8); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; - AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] { + AccessBoundsCheck<8>(ctx, address, spv_buffer.Size(PointerSize::B8), [&] { ctx.OpStore(ptr, value); return Id{}; }); @@ -579,13 +580,12 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B16); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U16]; - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); - const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; - AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] { + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U16); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; + AccessBoundsCheck<16>(ctx, address, spv_buffer.Size(PointerSize::B16), [&] { ctx.OpStore(ptr, value); return Id{}; }); @@ -609,13 +609,12 @@ void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { const auto& spv_buffer = ctx.buffers[handle]; - if (Sirit::ValidId(spv_buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); + if (const Id offset = spv_buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) { + address = ctx.OpIAdd(ctx.U32[1], address, offset); } - const auto [id, pointer_type] = spv_buffer[PointerType::U64]; - const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); - const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)}; - AccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, [&] { + const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U64); + const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, address)}; + AccessBoundsCheck<64>(ctx, address, spv_buffer.Size(PointerSize::B64), [&] { ctx.OpStore(ptr, value); return Id{}; }); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 12d4fa671..74c94754d 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -91,10 +91,14 @@ Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicSMin64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicUMin64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicFMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicSMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); +Id EmitBufferAtomicUMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address); @@ -135,15 +139,25 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicUMax64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicSMax64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicUMin32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicUMin64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicSMin64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicAnd64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicOr64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicXor64(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicInc64(EmitContext& ctx, Id offset); Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset); +Id EmitSharedAtomicDec64(EmitContext& ctx, Id offset); Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value); +Id EmitSharedAtomicISub64(EmitContext& ctx, Id offset, Id value); Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2); Id EmitCompositeConstructU32x3(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2, Id e3); @@ -349,7 +363,7 @@ Id EmitFPIsInf32(EmitContext& ctx, Id value); Id EmitFPIsInf64(EmitContext& ctx, Id value); Id EmitIAdd32(EmitContext& ctx, IR::Inst* inst, Id a, Id b); Id EmitIAdd64(EmitContext& ctx, Id a, Id b); -Id EmitIAddCary32(EmitContext& ctx, Id a, Id b); +Id EmitIAddCarry32(EmitContext& ctx, Id a, Id b); Id EmitISub32(EmitContext& ctx, Id a, Id b); Id EmitISub64(EmitContext& ctx, Id a, Id b); Id EmitSMulHi(EmitContext& ctx, Id a, Id b); @@ -404,14 +418,20 @@ Id EmitULessThan32(EmitContext& ctx, Id lhs, Id rhs); Id EmitULessThan64(EmitContext& ctx, Id lhs, Id rhs); Id EmitIEqual32(EmitContext& ctx, Id lhs, Id rhs); Id EmitIEqual64(EmitContext& ctx, Id lhs, Id rhs); -Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs); -Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs); -Id EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs); -Id EmitUGreaterThan(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs); +Id EmitULessThanEqual32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitULessThanEqual64(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSGreaterThan32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSGreaterThan64(EmitContext& ctx, Id lhs, Id rhs); +Id EmitUGreaterThan32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitUGreaterThan64(EmitContext& ctx, Id lhs, Id rhs); Id EmitINotEqual32(EmitContext& ctx, Id lhs, Id rhs); Id EmitINotEqual64(EmitContext& ctx, Id lhs, Id rhs); -Id EmitSGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs); -Id EmitUGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitSGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs); +Id EmitUGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs); +Id EmitUGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs); Id EmitLogicalOr(EmitContext& ctx, Id a, Id b); Id EmitLogicalAnd(EmitContext& ctx, Id a, Id b); Id EmitLogicalXor(EmitContext& ctx, Id a, Id b); @@ -509,8 +529,10 @@ Id EmitLaneId(EmitContext& ctx); Id EmitWarpId(EmitContext& ctx); Id EmitQuadShuffle(EmitContext& ctx, Id value, Id index); Id EmitReadFirstLane(EmitContext& ctx, Id value); -Id EmitReadLane(EmitContext& ctx, Id value, u32 lane); +Id EmitReadLane(EmitContext& ctx, Id value, Id lane); Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane); +Id EmitBallot(EmitContext& ctx, Id bit); +Id EmitBallotFindLsb(EmitContext& ctx, Id mask); Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding); Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 1a995354d..01652c1cf 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -60,7 +60,7 @@ Id EmitIAdd64(EmitContext& ctx, Id a, Id b) { return ctx.OpIAdd(ctx.U64, a, b); } -Id EmitIAddCary32(EmitContext& ctx, Id a, Id b) { +Id EmitIAddCarry32(EmitContext& ctx, Id a, Id b) { return ctx.OpIAddCarry(ctx.full_result_u32x2, a, b); } @@ -371,19 +371,35 @@ Id EmitIEqual64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpIEqual(ctx.U1[1], lhs, rhs); } -Id EmitSLessThanEqual(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSLessThanEqual32(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpSLessThanEqual(ctx.U1[1], lhs, rhs); } -Id EmitULessThanEqual(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSLessThanEqual64(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpSLessThanEqual(ctx.U1[1], lhs, rhs); +} + +Id EmitULessThanEqual32(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpULessThanEqual(ctx.U1[1], lhs, rhs); } -Id EmitSGreaterThan(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitULessThanEqual64(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpULessThanEqual(ctx.U1[1], lhs, rhs); +} + +Id EmitSGreaterThan32(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpSGreaterThan(ctx.U1[1], lhs, rhs); } -Id EmitUGreaterThan(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSGreaterThan64(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpSGreaterThan(ctx.U1[1], lhs, rhs); +} + +Id EmitUGreaterThan32(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpUGreaterThan(ctx.U1[1], lhs, rhs); +} + +Id EmitUGreaterThan64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpUGreaterThan(ctx.U1[1], lhs, rhs); } @@ -395,11 +411,19 @@ Id EmitINotEqual64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpINotEqual(ctx.U1[1], lhs, rhs); } -Id EmitSGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpSGreaterThanEqual(ctx.U1[1], lhs, rhs); } -Id EmitUGreaterThanEqual(EmitContext& ctx, Id lhs, Id rhs) { +Id EmitSGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpSGreaterThanEqual(ctx.U1[1], lhs, rhs); +} + +Id EmitUGreaterThanEqual32(EmitContext& ctx, Id lhs, Id rhs) { + return ctx.OpUGreaterThanEqual(ctx.U1[1], lhs, rhs); +} + +Id EmitUGreaterThanEqual64(EmitContext& ctx, Id lhs, Id rhs) { return ctx.OpUGreaterThanEqual(ctx.U1[1], lhs, rhs); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp index 2d13d09f0..951c76001 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_warp.cpp @@ -26,13 +26,20 @@ Id EmitReadFirstLane(EmitContext& ctx, Id value) { return ctx.OpGroupNonUniformBroadcastFirst(ctx.U32[1], SubgroupScope(ctx), value); } -Id EmitReadLane(EmitContext& ctx, Id value, u32 lane) { - return ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, - ctx.ConstU32(lane)); +Id EmitReadLane(EmitContext& ctx, Id value, Id lane) { + return ctx.OpGroupNonUniformBroadcast(ctx.U32[1], SubgroupScope(ctx), value, lane); } Id EmitWriteLane(EmitContext& ctx, Id value, Id write_value, u32 lane) { return ctx.u32_zero_value; } +Id EmitBallot(EmitContext& ctx, Id bit) { + return ctx.OpGroupNonUniformBallot(ctx.U32[4], SubgroupScope(ctx), bit); +} + +Id EmitBallotFindLsb(EmitContext& ctx, Id mask) { + return ctx.OpGroupNonUniformBallotFindLSB(ctx.U32[1], SubgroupScope(ctx), mask); +} + } // namespace Shader::Backend::SPIRV diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 567c059ae..6a731d05c 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -71,11 +71,12 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf Bindings& binding_) : Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_}, profile{profile_}, stage{info.stage}, l_stage{info.l_stage}, binding{binding_} { - if (info.dma_types != IR::Type::Void) { + if (info.uses_dma) { SetMemoryModel(spv::AddressingModel::PhysicalStorageBuffer64, spv::MemoryModel::GLSL450); } else { SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); } + String(fmt::format("{:#x}", info.pgm_hash)); AddCapability(spv::Capability::Shader); DefineArithmeticTypes(); @@ -169,34 +170,8 @@ void EmitContext::DefineArithmeticTypes() { if (info.uses_fp64) { frexp_result_f64 = Name(TypeStruct(F64[1], S32[1]), "frexp_result_f64"); } - - if (True(info.dma_types & IR::Type::F64)) { - physical_pointer_types[PointerType::F64] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, F64[1]); - } - if (True(info.dma_types & IR::Type::U64)) { - physical_pointer_types[PointerType::U64] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, U64); - } - if (True(info.dma_types & IR::Type::F32)) { - physical_pointer_types[PointerType::F32] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, F32[1]); - } - if (True(info.dma_types & IR::Type::U32)) { - physical_pointer_types[PointerType::U32] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]); - } - if (True(info.dma_types & IR::Type::F16)) { - physical_pointer_types[PointerType::F16] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, F16[1]); - } - if (True(info.dma_types & IR::Type::U16)) { - physical_pointer_types[PointerType::U16] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, U16); - } - if (True(info.dma_types & IR::Type::U8)) { - physical_pointer_types[PointerType::U8] = - TypePointer(spv::StorageClass::PhysicalStorageBuffer, U8); + if (info.uses_dma) { + physical_pointer_type_u32 = TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]); } } @@ -239,7 +214,7 @@ Id EmitContext::GetBufferSize(const u32 sharp_idx) { // Can this be done with memory access? Like we do now with ReadConst const auto& srt_flatbuf = buffers[flatbuf_index]; ASSERT(srt_flatbuf.buffer_type == BufferType::Flatbuf); - const auto [id, pointer_type] = srt_flatbuf[PointerType::U32]; + const auto [id, pointer_type] = srt_flatbuf.Alias(PointerType::U32); const auto rsrc1{ OpLoad(U32[1], OpAccessChain(pointer_type, id, u32_zero_value, ConstU32(sharp_idx + 1)))}; @@ -255,39 +230,70 @@ Id EmitContext::GetBufferSize(const u32 sharp_idx) { } void EmitContext::DefineBufferProperties() { + if (!profile.needs_buffer_offsets && profile.supports_robust_buffer_access) { + return; + } for (u32 i = 0; i < buffers.size(); i++) { - BufferDefinition& buffer = buffers[i]; + auto& buffer = buffers[i]; + const auto& desc = info.buffers[i]; + const u32 binding = buffer.binding; if (buffer.buffer_type != BufferType::Guest) { continue; } - const u32 binding = buffer.binding; - const u32 half = PushData::BufOffsetIndex + (binding >> 4); - const u32 comp = (binding & 0xf) >> 2; - const u32 offset = (binding & 0x3) << 3; - const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), - push_data_block, ConstU32(half), ConstU32(comp))}; - const Id value{OpLoad(U32[1], ptr)}; - buffer.offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U)); - Name(buffer.offset, fmt::format("buf{}_off", binding)); - buffer.offset_dwords = OpShiftRightLogical(U32[1], buffer.offset, ConstU32(2U)); - Name(buffer.offset_dwords, fmt::format("buf{}_dword_off", binding)); - // Only need to load size if performing bounds checks and the buffer is both guest and not - // inline. - if (!profile.supports_robust_buffer_access && buffer.buffer_type == BufferType::Guest) { - const BufferResource& desc = info.buffers[i]; - if (desc.sharp_idx == std::numeric_limits::max()) { - buffer.size = ConstU32(desc.inline_cbuf.GetSize()); - } else { - buffer.size = GetBufferSize(desc.sharp_idx); + // Only load and apply buffer offsets if host GPU alignment is larger than guest. + if (profile.needs_buffer_offsets) { + const u32 half = PushData::BufOffsetIndex + (binding >> 4); + const u32 comp = (binding & 0xf) >> 2; + const u32 offset = (binding & 0x3) << 3; + const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]), + push_data_block, ConstU32(half), ConstU32(comp))}; + const Id value{OpLoad(U32[1], ptr)}; + + const Id buf_offset{OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U))}; + Name(buf_offset, fmt::format("buf{}_off", binding)); + buffer.Offset(PointerSize::B8) = buf_offset; + + if (True(desc.used_types & IR::Type::U16)) { + const Id buf_word_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(1U))}; + Name(buf_word_offset, fmt::format("buf{}_word_off", binding)); + buffer.Offset(PointerSize::B16) = buf_word_offset; + } + if (True(desc.used_types & IR::Type::U32)) { + const Id buf_dword_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(2U))}; + Name(buf_dword_offset, fmt::format("buf{}_dword_off", binding)); + buffer.Offset(PointerSize::B32) = buf_dword_offset; + } + if (True(desc.used_types & IR::Type::U64)) { + const Id buf_qword_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(3U))}; + Name(buf_qword_offset, fmt::format("buf{}_qword_off", binding)); + buffer.Offset(PointerSize::B64) = buf_qword_offset; + } + } + + // Only load size if performing bounds checks. + if (!profile.supports_robust_buffer_access) { + const Id buf_size{desc.sharp_idx == std::numeric_limits::max() + ? ConstU32(desc.inline_cbuf.GetSize()) + : GetBufferSize(desc.sharp_idx)}; + Name(buf_size, fmt::format("buf{}_size", binding)); + buffer.Size(PointerSize::B8) = buf_size; + + if (True(desc.used_types & IR::Type::U16)) { + const Id buf_word_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(1U))}; + Name(buf_word_size, fmt::format("buf{}_short_size", binding)); + buffer.Size(PointerSize::B16) = buf_word_size; + } + if (True(desc.used_types & IR::Type::U32)) { + const Id buf_dword_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(2U))}; + Name(buf_dword_size, fmt::format("buf{}_dword_size", binding)); + buffer.Size(PointerSize::B32) = buf_dword_size; + } + if (True(desc.used_types & IR::Type::U64)) { + const Id buf_qword_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(3U))}; + Name(buf_qword_size, fmt::format("buf{}_qword_size", binding)); + buffer.Size(PointerSize::B64) = buf_qword_size; } - Name(buffer.size, fmt::format("buf{}_size", binding)); - buffer.size_shorts = OpShiftRightLogical(U32[1], buffer.size, ConstU32(1U)); - Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding)); - buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U)); - Name(buffer.size_dwords, fmt::format("buf{}_dword_size", binding)); - buffer.size_qwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(3U)); - Name(buffer.size_qwords, fmt::format("buf{}_qword_size", binding)); } } } @@ -695,7 +701,7 @@ void EmitContext::DefineOutputs() { void EmitContext::DefinePushDataBlock() { // Create push constants block for instance steps rates const Id struct_type{Name(TypeStruct(U32[1], U32[1], F32[1], F32[1], F32[1], F32[1], U32[4], - U32[4], U32[4], U32[4], U32[4], U32[4]), + U32[4], U32[4], U32[4], U32[4], U32[4], U32[2]), "AuxData")}; Decorate(struct_type, spv::Decoration::Block); MemberName(struct_type, PushData::Step0Index, "sr0"); @@ -710,6 +716,7 @@ void EmitContext::DefinePushDataBlock() { MemberName(struct_type, PushData::UdRegsIndex + 3, "ud_regs3"); MemberName(struct_type, PushData::BufOffsetIndex + 0, "buf_offsets0"); MemberName(struct_type, PushData::BufOffsetIndex + 1, "buf_offsets1"); + MemberName(struct_type, PushData::BufOffsetIndex + 2, "buf_offsets2"); MemberDecorate(struct_type, PushData::Step0Index, spv::Decoration::Offset, 0U); MemberDecorate(struct_type, PushData::Step1Index, spv::Decoration::Offset, 4U); MemberDecorate(struct_type, PushData::XOffsetIndex, spv::Decoration::Offset, 8U); @@ -722,6 +729,7 @@ void EmitContext::DefinePushDataBlock() { MemberDecorate(struct_type, PushData::UdRegsIndex + 3, spv::Decoration::Offset, 72U); MemberDecorate(struct_type, PushData::BufOffsetIndex + 0, spv::Decoration::Offset, 88U); MemberDecorate(struct_type, PushData::BufOffsetIndex + 1, spv::Decoration::Offset, 104U); + MemberDecorate(struct_type, PushData::BufOffsetIndex + 2, spv::Decoration::Offset, 120U); push_data_block = DefineVar(struct_type, spv::StorageClass::PushConstant); Name(push_data_block, "push_data"); interfaces.push_back(push_data_block); @@ -779,20 +787,6 @@ EmitContext::BufferSpv EmitContext::DefineBuffer(bool is_storage, bool is_writte }; void EmitContext::DefineBuffers() { - if (!profile.supports_robust_buffer_access && - info.readconst_types == Info::ReadConstType::None) { - // In case Flatbuf has not already been bound by IR and is needed - // to query buffer sizes, bind it now. - info.buffers.push_back({ - .used_types = IR::Type::U32, - // We can't guarantee that flatbuf will not grow past UBO - // limit if there are a lot of ReadConsts. (We could specialize) - .inline_cbuf = AmdGpu::Buffer::Placeholder(std::numeric_limits::max()), - .buffer_type = BufferType::Flatbuf, - }); - // In the future we may want to read buffer sizes from GPU memory if available. - // info.readconst_types |= Info::ReadConstType::Immediate; - } for (const auto& desc : info.buffers) { const auto buf_sharp = desc.GetSharp(info); const bool is_storage = desc.IsStorage(buf_sharp, profile); @@ -809,23 +803,23 @@ void EmitContext::DefineBuffers() { // Define aliases depending on the shader usage. auto& spv_buffer = buffers.emplace_back(binding.buffer++, desc.buffer_type); if (True(desc.used_types & IR::Type::U64)) { - spv_buffer[PointerType::U64] = + spv_buffer.Alias(PointerType::U64) = DefineBuffer(is_storage, desc.is_written, 3, desc.buffer_type, U64); } if (True(desc.used_types & IR::Type::U32)) { - spv_buffer[PointerType::U32] = + spv_buffer.Alias(PointerType::U32) = DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, U32[1]); } if (True(desc.used_types & IR::Type::F32)) { - spv_buffer[PointerType::F32] = + spv_buffer.Alias(PointerType::F32) = DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, F32[1]); } if (True(desc.used_types & IR::Type::U16)) { - spv_buffer[PointerType::U16] = + spv_buffer.Alias(PointerType::U16) = DefineBuffer(is_storage, desc.is_written, 1, desc.buffer_type, U16); } if (True(desc.used_types & IR::Type::U8)) { - spv_buffer[PointerType::U8] = + spv_buffer.Alias(PointerType::U8) = DefineBuffer(is_storage, desc.is_written, 0, desc.buffer_type, U8); } ++binding.unified; @@ -1154,7 +1148,7 @@ Id EmitContext::DefineGetBdaPointer() { const auto page{OpShiftRightLogical(U64, address, caching_pagebits)}; const auto page32{OpUConvert(U32[1], page)}; const auto& bda_buffer{buffers[bda_pagetable_index]}; - const auto [bda_buffer_id, bda_pointer_type] = bda_buffer[PointerType::U64]; + const auto [bda_buffer_id, bda_pointer_type] = bda_buffer.Alias(PointerType::U64); const auto bda_ptr{OpAccessChain(bda_pointer_type, bda_buffer_id, u32_zero_value, page32)}; const auto bda{OpLoad(U64, bda_ptr)}; @@ -1166,14 +1160,14 @@ Id EmitContext::DefineGetBdaPointer() { // First time acces, mark as fault AddLabel(fault_label); const auto& fault_buffer{buffers[fault_buffer_index]}; - const auto [fault_buffer_id, fault_pointer_type] = fault_buffer[PointerType::U8]; - const auto page_div8{OpShiftRightLogical(U32[1], page32, ConstU32(3U))}; - const auto page_mod8{OpBitwiseAnd(U32[1], page32, ConstU32(7U))}; - const auto page_mask{OpShiftLeftLogical(U8, u8_one_value, page_mod8)}; + const auto [fault_buffer_id, fault_pointer_type] = fault_buffer.Alias(PointerType::U32); + const auto page_div32{OpShiftRightLogical(U32[1], page32, ConstU32(5U))}; + const auto page_mod32{OpBitwiseAnd(U32[1], page32, ConstU32(31U))}; + const auto page_mask{OpShiftLeftLogical(U32[1], u32_one_value, page_mod32)}; const auto fault_ptr{ - OpAccessChain(fault_pointer_type, fault_buffer_id, u32_zero_value, page_div8)}; - const auto fault_value{OpLoad(U8, fault_ptr)}; - const auto fault_value_masked{OpBitwiseOr(U8, fault_value, page_mask)}; + OpAccessChain(fault_pointer_type, fault_buffer_id, u32_zero_value, page_div32)}; + const auto fault_value{OpLoad(U32[1], fault_ptr)}; + const auto fault_value_masked{OpBitwiseOr(U32[1], fault_value, page_mask)}; OpStore(fault_ptr, fault_value_masked); // Return null pointer @@ -1211,17 +1205,11 @@ Id EmitContext::DefineReadConst(bool dynamic) { const auto offset_bytes{OpShiftLeftLogical(U32[1], offset, ConstU32(2U))}; const auto addr{OpIAdd(U64, base_addr, OpUConvert(U64, offset_bytes))}; - const auto result = EmitMemoryRead(U32[1], addr, [&]() { + const auto result = EmitDwordMemoryRead(addr, [&]() { if (dynamic) { return u32_zero_value; } else { - const auto& flatbuf_buffer{buffers[flatbuf_index]}; - ASSERT(flatbuf_buffer.binding >= 0 && - flatbuf_buffer.buffer_type == BufferType::Flatbuf); - const auto [flatbuf_buffer_id, flatbuf_pointer_type] = flatbuf_buffer[PointerType::U32]; - const auto ptr{OpAccessChain(flatbuf_pointer_type, flatbuf_buffer_id, u32_zero_value, - flatbuf_offset)}; - return OpLoad(U32[1], ptr); + return EmitFlatbufferLoad(flatbuf_offset); } }); @@ -1239,7 +1227,7 @@ void EmitContext::DefineFunctions() { uf11_to_f32 = DefineUfloatM5ToFloat32(6, "uf11_to_f32"); uf10_to_f32 = DefineUfloatM5ToFloat32(5, "uf10_to_f32"); } - if (info.dma_types != IR::Type::Void) { + if (info.uses_dma) { get_bda_pointer = DefineGetBdaPointer(); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 1eb7d05c6..28e9099d8 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -42,17 +42,6 @@ public: Bindings& binding); ~EmitContext(); - enum class PointerType : u32 { - U8, - U16, - F16, - U32, - F32, - U64, - F64, - NumAlias, - }; - Id Def(const IR::Value& value); void DefineBufferProperties(); @@ -155,25 +144,7 @@ public: return last_label; } - PointerType PointerTypeFromType(Id type) { - if (type.value == U8.value) - return PointerType::U8; - if (type.value == U16.value) - return PointerType::U16; - if (type.value == F16[1].value) - return PointerType::F16; - if (type.value == U32[1].value) - return PointerType::U32; - if (type.value == F32[1].value) - return PointerType::F32; - if (type.value == U64.value) - return PointerType::U64; - if (type.value == F64[1].value) - return PointerType::F64; - UNREACHABLE_MSG("Unknown type for pointer"); - } - - Id EmitMemoryRead(Id type, Id address, auto&& fallback) { + Id EmitDwordMemoryRead(Id address, auto&& fallback) { const Id available_label = OpLabel(); const Id fallback_label = OpLabel(); const Id merge_label = OpLabel(); @@ -185,10 +156,8 @@ public: // Available AddLabel(available_label); - const auto pointer_type = PointerTypeFromType(type); - const Id pointer_type_id = physical_pointer_types[pointer_type]; - const Id addr_ptr = OpConvertUToPtr(pointer_type_id, addr); - const Id result = OpLoad(type, addr_ptr, spv::MemoryAccessMask::Aligned, 4u); + const Id addr_ptr = OpConvertUToPtr(physical_pointer_type_u32, addr); + const Id result = OpLoad(U32[1], addr_ptr, spv::MemoryAccessMask::Aligned, 4u); OpBranch(merge_label); // Fallback @@ -199,7 +168,7 @@ public: // Merge AddLabel(merge_label); const Id final_result = - OpPhi(type, fallback_result, fallback_label, result, available_label); + OpPhi(U32[1], fallback_result, fallback_label, result, available_label); return final_result; } @@ -211,6 +180,16 @@ public: return OpAccessChain(result_type, shared_mem, index); } + Id EmitFlatbufferLoad(Id flatbuf_offset) { + const auto& flatbuf_buffer{buffers[flatbuf_index]}; + ASSERT(flatbuf_buffer.binding >= 0 && flatbuf_buffer.buffer_type == BufferType::Flatbuf); + const auto [flatbuf_buffer_id, flatbuf_pointer_type] = + flatbuf_buffer.aliases[u32(PointerType::U32)]; + const auto ptr{ + OpAccessChain(flatbuf_pointer_type, flatbuf_buffer_id, u32_zero_value, flatbuf_offset)}; + return OpLoad(U32[1], ptr); + } + Info& info; const RuntimeInfo& runtime_info; const Profile& profile; @@ -314,6 +293,24 @@ public: bool is_storage = false; }; + enum class PointerType : u32 { + U8, + U16, + U32, + F32, + U64, + F64, + NumAlias, + }; + + enum class PointerSize : u32 { + B8, + B16, + B32, + B64, + NumClass, + }; + struct BufferSpv { Id id; Id pointer_type; @@ -322,32 +319,23 @@ public: struct BufferDefinition { u32 binding; BufferType buffer_type; - Id offset; - Id offset_dwords; - Id size; - Id size_shorts; - Id size_dwords; - Id size_qwords; + std::array offsets; + std::array sizes; std::array aliases; - const BufferSpv& operator[](PointerType alias) const { - return aliases[u32(alias)]; + template + auto& Alias(this Self& self, PointerType alias) { + return self.aliases[u32(alias)]; } - BufferSpv& operator[](PointerType alias) { - return aliases[u32(alias)]; - } - }; - - struct PhysicalPointerTypes { - std::array types; - - const Id& operator[](PointerType type) const { - return types[u32(type)]; + template + auto& Offset(this Self& self, PointerSize size) { + return self.offsets[u32(size)]; } - Id& operator[](PointerType type) { - return types[u32(type)]; + template + auto& Size(this Self& self, PointerSize size) { + return self.sizes[u32(size)]; } }; @@ -356,12 +344,12 @@ public: boost::container::small_vector buffers; boost::container::small_vector images; boost::container::small_vector samplers; - PhysicalPointerTypes physical_pointer_types; std::unordered_map first_to_last_label_map; size_t flatbuf_index{}; size_t bda_pagetable_index{}; size_t fault_buffer_index{}; + Id physical_pointer_type_u32; Id sampler_type{}; Id sampler_pointer_type{}; diff --git a/src/shader_recompiler/frontend/control_flow_graph.cpp b/src/shader_recompiler/frontend/control_flow_graph.cpp index b53db9e94..805fdb108 100644 --- a/src/shader_recompiler/frontend/control_flow_graph.cpp +++ b/src/shader_recompiler/frontend/control_flow_graph.cpp @@ -188,14 +188,15 @@ void CFG::SplitDivergenceScopes() { const bool is_close = is_close_scope(inst); if ((is_close || index == blk->end_index) && curr_begin != -1) { // If there are no instructions inside scope don't do anything. - if (index - curr_begin == 1) { + if (index - curr_begin == 1 && is_close) { curr_begin = -1; continue; } // If all instructions in the scope ignore exec masking, we shouldn't insert a // scope. const auto start = inst_list.begin() + curr_begin + 1; - if (!std::ranges::all_of(start, inst_list.begin() + index, IgnoresExecMask)) { + if (!std::ranges::all_of(start, inst_list.begin() + index + !is_close, + IgnoresExecMask)) { // Determine the first instruction affected by the exec mask. do { ++curr_begin; diff --git a/src/shader_recompiler/frontend/format.cpp b/src/shader_recompiler/frontend/format.cpp index 52c8c733e..6c4427e5f 100644 --- a/src/shader_recompiler/frontend/format.cpp +++ b/src/shader_recompiler/frontend/format.cpp @@ -397,7 +397,7 @@ constexpr std::array InstructionFormatSOPP = {{ // 17 = S_SENDMSGHALT {InstClass::ScalarProgFlow, InstCategory::FlowControl, 0, 1, ScalarType::Any, ScalarType::Any}, // 18 = S_TRAP - {InstClass::Undefined, InstCategory::Undefined, 0, 1, ScalarType::Any, ScalarType::Any}, + {InstClass::Undefined, InstCategory::FlowControl, 0, 1, ScalarType::Any, ScalarType::Any}, // 19 = S_ICACHE_INV {InstClass::ScalarCache, InstCategory::FlowControl, 0, 1, ScalarType::Any, ScalarType::Any}, // 20 = S_INCPERFLEVEL diff --git a/src/shader_recompiler/frontend/translate/data_share.cpp b/src/shader_recompiler/frontend/translate/data_share.cpp index 8ead93f78..634486fc4 100644 --- a/src/shader_recompiler/frontend/translate/data_share.cpp +++ b/src/shader_recompiler/frontend/translate/data_share.cpp @@ -3,7 +3,6 @@ #include "shader_recompiler/frontend/translate/translate.h" #include "shader_recompiler/ir/reg.h" -#include "shader_recompiler/profile.h" #include "shader_recompiler/runtime_info.h" namespace Shader::Gcn { @@ -12,29 +11,29 @@ void Translator::EmitDataShare(const GcnInst& inst) { switch (inst.opcode) { // DS case Opcode::DS_ADD_U32: - return DS_ADD_U32(inst, false); + return DS_OP(inst, AtomicOp::Add, false); case Opcode::DS_ADD_U64: - return DS_ADD_U64(inst, false); + return DS_OP(inst, AtomicOp::Add, false); case Opcode::DS_SUB_U32: - return DS_SUB_U32(inst, false); + return DS_OP(inst, AtomicOp::Sub, false); case Opcode::DS_INC_U32: - return DS_INC_U32(inst, false); + return DS_OP(inst, AtomicOp::Inc, false); case Opcode::DS_DEC_U32: - return DS_DEC_U32(inst, false); + return DS_OP(inst, AtomicOp::Dec, false); case Opcode::DS_MIN_I32: - return DS_MIN_U32(inst, true, false); + return DS_OP(inst, AtomicOp::Smin, false); case Opcode::DS_MAX_I32: - return DS_MAX_U32(inst, true, false); + return DS_OP(inst, AtomicOp::Smax, false); case Opcode::DS_MIN_U32: - return DS_MIN_U32(inst, false, false); + return DS_OP(inst, AtomicOp::Umin, false); case Opcode::DS_MAX_U32: - return DS_MAX_U32(inst, false, false); + return DS_OP(inst, AtomicOp::Umax, false); case Opcode::DS_AND_B32: - return DS_AND_B32(inst, false); + return DS_OP(inst, AtomicOp::And, false); case Opcode::DS_OR_B32: - return DS_OR_B32(inst, false); + return DS_OP(inst, AtomicOp::Or, false); case Opcode::DS_XOR_B32: - return DS_XOR_B32(inst, false); + return DS_OP(inst, AtomicOp::Xor, false); case Opcode::DS_WRITE_B32: return DS_WRITE(32, false, false, false, inst); case Opcode::DS_WRITE2_B32: @@ -42,19 +41,19 @@ void Translator::EmitDataShare(const GcnInst& inst) { case Opcode::DS_WRITE2ST64_B32: return DS_WRITE(32, false, true, true, inst); case Opcode::DS_ADD_RTN_U32: - return DS_ADD_U32(inst, true); + return DS_OP(inst, AtomicOp::Add, true); case Opcode::DS_SUB_RTN_U32: - return DS_SUB_U32(inst, true); + return DS_OP(inst, AtomicOp::Sub, true); case Opcode::DS_MIN_RTN_U32: - return DS_MIN_U32(inst, false, true); + return DS_OP(inst, AtomicOp::Umin, true); case Opcode::DS_MAX_RTN_U32: - return DS_MAX_U32(inst, false, true); + return DS_OP(inst, AtomicOp::Umax, true); case Opcode::DS_AND_RTN_B32: - return DS_AND_B32(inst, true); + return DS_OP(inst, AtomicOp::And, true); case Opcode::DS_OR_RTN_B32: - return DS_OR_B32(inst, true); + return DS_OP(inst, AtomicOp::Or, true); case Opcode::DS_XOR_RTN_B32: - return DS_XOR_B32(inst, true); + return DS_OP(inst, AtomicOp::Xor, true); case Opcode::DS_SWIZZLE_B32: return DS_SWIZZLE_B32(inst); case Opcode::DS_READ_B32: @@ -117,92 +116,63 @@ void Translator::V_WRITELANE_B32(const GcnInst& inst) { // DS -void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) { +template +void Translator::DS_OP(const GcnInst& inst, AtomicOp op, bool rtn) { + const bool is_gds = inst.control.ds.gds; const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; + const T data = [&] { + if (op == AtomicOp::Inc || op == AtomicOp::Dec) { + return T{}; + } + if constexpr (std::is_same_v) { + return GetSrc(inst.src[1]); + } else { + return GetSrc64(inst.src[1]); + } + }(); const IR::U32 offset = ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data); + const T original_val = [&] -> T { + switch (op) { + case AtomicOp::Add: + return ir.SharedAtomicIAdd(addr_offset, data, is_gds); + case AtomicOp::Umin: + return ir.SharedAtomicIMin(addr_offset, data, false, is_gds); + case AtomicOp::Smin: + return ir.SharedAtomicIMin(addr_offset, data, true, is_gds); + case AtomicOp::Umax: + return ir.SharedAtomicIMax(addr_offset, data, false, is_gds); + case AtomicOp::Smax: + return ir.SharedAtomicIMax(addr_offset, data, true, is_gds); + case AtomicOp::And: + return ir.SharedAtomicAnd(addr_offset, data, is_gds); + case AtomicOp::Or: + return ir.SharedAtomicOr(addr_offset, data, is_gds); + case AtomicOp::Xor: + return ir.SharedAtomicXor(addr_offset, data, is_gds); + case AtomicOp::Sub: + return ir.SharedAtomicISub(addr_offset, data, is_gds); + case AtomicOp::Inc: + return ir.SharedAtomicInc(addr_offset, is_gds); + case AtomicOp::Dec: + return ir.SharedAtomicDec(addr_offset, is_gds); + default: + UNREACHABLE(); + } + }(); if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_ADD_U64(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U64 data{GetSrc64(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data); - if (rtn) { - SetDst64(inst.dst[0], IR::U64{original_val}); - } -} - -void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIMin(addr_offset, data, is_signed); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicIMax(addr_offset, data, is_signed); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_AND_B32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicAnd(addr_offset, data); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_OR_B32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicOr(addr_offset, data); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_XOR_B32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicXor(addr_offset, data); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); + if constexpr (std::is_same_v) { + SetDst(inst.dst[0], original_val); + } else { + SetDst64(inst.dst[0], original_val); + } } } void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst) { + const bool is_gds = inst.control.ds.gds; const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; const IR::VectorReg data0{inst.src[1].code}; const IR::VectorReg data1{inst.src[2].code}; @@ -220,33 +190,85 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid ir.WriteShared(64, ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1))), - addr0); + addr0, is_gds); } else if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data0), addr0); + ir.WriteShared(32, ir.GetVectorReg(data0), addr0, is_gds); } else if (bit_size == 16) { - ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0); + ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0, is_gds); } const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); if (bit_size == 64) { ir.WriteShared(64, ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1))), - addr1); + addr1, is_gds); } else if (bit_size == 32) { - ir.WriteShared(32, ir.GetVectorReg(data1), addr1); + ir.WriteShared(32, ir.GetVectorReg(data1), addr1, is_gds); } else if (bit_size == 16) { - ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1); + ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1, is_gds); } } else { const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); if (bit_size == 64) { const IR::Value data = ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)); - ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0); + ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0, is_gds); } else if (bit_size == 32) { - ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); + ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0, is_gds); } else if (bit_size == 16) { - ir.WriteShared(bit_size, ir.UConvert(16, ir.GetVectorReg(data0)), addr0); + ir.WriteShared(bit_size, ir.UConvert(16, ir.GetVectorReg(data0)), addr0, is_gds); + } + } +} + +void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, + const GcnInst& inst) { + const bool is_gds = inst.control.ds.gds; + const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; + IR::VectorReg dst_reg{inst.dst[0].code}; + const u32 offset = (inst.control.ds.offset1 << 8u) + inst.control.ds.offset0; + if (info.stage == Stage::Fragment) { + ASSERT_MSG(!is_pair && bit_size == 32 && offset % 256 == 0, + "Unexpected shared memory offset alignment: {}", offset); + ir.SetVectorReg(dst_reg, ir.GetVectorReg(GetScratchVgpr(offset))); + return; + } + if (is_pair) { + // Pair loads are either 32 or 64-bit + const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); + const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0, is_gds); + if (bit_size == 64) { + const auto vector = ir.UnpackUint2x32(IR::U64{data0}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data0}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})}); + } + const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); + const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1, is_gds); + if (bit_size == 64) { + const auto vector = ir.UnpackUint2x32(IR::U64{data1}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg++, IR::U32{data1}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})}); + } + } else { + const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); + const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0, is_gds); + if (bit_size == 64) { + const auto vector = ir.UnpackUint2x32(IR::U64{data}); + ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)}); + ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)}); + } else if (bit_size == 32) { + ir.SetVectorReg(dst_reg, IR::U32{data}); + } else if (bit_size == 16) { + ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})}); } } } @@ -263,91 +285,6 @@ void Translator::DS_SWIZZLE_B32(const GcnInst& inst) { SetDst(inst.dst[0], ir.QuadShuffle(src, index)); } -void Translator::DS_INC_U32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicInc(addr_offset); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_DEC_U32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicDec(addr_offset); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_SUB_U32(const GcnInst& inst, bool rtn) { - const IR::U32 addr{GetSrc(inst.src[0])}; - const IR::U32 data{GetSrc(inst.src[1])}; - const IR::U32 offset = - ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); - const IR::U32 addr_offset = ir.IAdd(addr, offset); - const IR::Value original_val = ir.SharedAtomicISub(addr_offset, data); - if (rtn) { - SetDst(inst.dst[0], IR::U32{original_val}); - } -} - -void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, - const GcnInst& inst) { - const IR::U32 addr{ir.GetVectorReg(IR::VectorReg(inst.src[0].code))}; - IR::VectorReg dst_reg{inst.dst[0].code}; - const u32 offset = (inst.control.ds.offset1 << 8u) + inst.control.ds.offset0; - if (info.stage == Stage::Fragment) { - ASSERT_MSG(!is_pair && bit_size == 32 && offset % 256 == 0, - "Unexpected shared memory offset alignment: {}", offset); - ir.SetVectorReg(dst_reg, ir.GetVectorReg(GetScratchVgpr(offset))); - return; - } - if (is_pair) { - // Pair loads are either 32 or 64-bit - const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj))); - const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); - if (bit_size == 64) { - const auto vector = ir.UnpackUint2x32(IR::U64{data0}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); - } else if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data0}); - } else if (bit_size == 16) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})}); - } - const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); - const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1); - if (bit_size == 64) { - const auto vector = ir.UnpackUint2x32(IR::U64{data1}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)}); - ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)}); - } else if (bit_size == 32) { - ir.SetVectorReg(dst_reg++, IR::U32{data1}); - } else if (bit_size == 16) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})}); - } - } else { - const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); - const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0); - if (bit_size == 64) { - const auto vector = ir.UnpackUint2x32(IR::U64{data}); - ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)}); - ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)}); - } else if (bit_size == 32) { - ir.SetVectorReg(dst_reg, IR::U32{data}); - } else if (bit_size == 16) { - ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})}); - } - } -} - void Translator::DS_APPEND(const GcnInst& inst) { const u32 inst_offset = (u32(inst.control.ds.offset1) << 8u) + inst.control.ds.offset0; const IR::U32 gds_offset = ir.IAdd(ir.GetM0(), ir.Imm32(inst_offset)); diff --git a/src/shader_recompiler/frontend/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 7beb594c3..e3134c300 100644 --- a/src/shader_recompiler/frontend/translate/scalar_alu.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_alu.cpp @@ -1,7 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include #include "common/assert.h" #include "shader_recompiler/frontend/translate/translate.h" @@ -587,6 +586,15 @@ void Translator::S_MOV(const GcnInst& inst) { } void Translator::S_MOV_B64(const GcnInst& inst) { + // Moving SGPR to SGPR is used for thread masks, like most operations, but it can also be used + // for moving sharps. + if (inst.dst[0].field == OperandField::ScalarGPR && + inst.src[0].field == OperandField::ScalarGPR) { + ir.SetScalarReg(IR::ScalarReg(inst.dst[0].code), + ir.GetScalarReg(IR::ScalarReg(inst.src[0].code))); + ir.SetScalarReg(IR::ScalarReg(inst.dst[0].code + 1), + ir.GetScalarReg(IR::ScalarReg(inst.src[0].code + 1))); + } const IR::U1 src = [&] { switch (inst.src[0].field) { case OperandField::VccLo: @@ -672,8 +680,9 @@ void Translator::S_FF1_I32_B32(const GcnInst& inst) { } void Translator::S_FF1_I32_B64(const GcnInst& inst) { - const IR::U64 src0{GetSrc64(inst.src[0])}; - const IR::U32 result{ir.FindILsb(src0)}; + ASSERT(inst.src[0].field == OperandField::ScalarGPR); + const IR::U32 result{ + ir.BallotFindLsb(ir.Ballot(ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[0].code))))}; SetDst(inst.dst[0], result); } diff --git a/src/shader_recompiler/frontend/translate/scalar_flow.cpp b/src/shader_recompiler/frontend/translate/scalar_flow.cpp index cd1cf51f0..7b57d89ca 100644 --- a/src/shader_recompiler/frontend/translate/scalar_flow.cpp +++ b/src/shader_recompiler/frontend/translate/scalar_flow.cpp @@ -16,6 +16,9 @@ void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) { case Opcode::S_SETPRIO: LOG_WARNING(Render_Vulkan, "S_SETPRIO instruction!"); return; + case Opcode::S_TRAP: + LOG_WARNING(Render_Vulkan, "S_TRAP instruction!"); + return; case Opcode::S_GETPC_B64: return S_GETPC_B64(pc, inst); case Opcode::S_SETPC_B64: diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 086b325aa..4b5ff827b 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -20,7 +20,7 @@ namespace Shader::Gcn { enum class ConditionOp : u32 { F, EQ, - LG, + LG, // NE GT, GE, LT, @@ -230,7 +230,7 @@ public: // VOPC void V_CMP_F32(ConditionOp op, bool set_exec, const GcnInst& inst); void V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); - void V_CMP_NE_U64(const GcnInst& inst); + void V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst); void V_CMP_CLASS_F32(const GcnInst& inst); // VOP3a @@ -270,27 +270,20 @@ public: // Data share // DS - void DS_ADD_U32(const GcnInst& inst, bool rtn); - void DS_ADD_U64(const GcnInst& inst, bool rtn); - void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn); - void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn); + template + void DS_OP(const GcnInst& inst, AtomicOp op, bool rtn); void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); - void DS_SWIZZLE_B32(const GcnInst& inst); - void DS_AND_B32(const GcnInst& inst, bool rtn); - void DS_OR_B32(const GcnInst& inst, bool rtn); - void DS_XOR_B32(const GcnInst& inst, bool rtn); void DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst); + void DS_SWIZZLE_B32(const GcnInst& inst); void DS_APPEND(const GcnInst& inst); void DS_CONSUME(const GcnInst& inst); - void DS_SUB_U32(const GcnInst& inst, bool rtn); - void DS_INC_U32(const GcnInst& inst, bool rtn); - void DS_DEC_U32(const GcnInst& inst, bool rtn); // Buffer Memory // MUBUF / MTBUF void BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst); void BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst); + template void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); // Image Memory diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index 3b88e4dec..017c77fb0 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -327,8 +327,10 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_CMP_U32(ConditionOp::TRU, false, true, inst); // V_CMP_{OP8}_U64 + case Opcode::V_CMP_EQ_U64: + return V_CMP_U64(ConditionOp::EQ, false, false, inst); case Opcode::V_CMP_NE_U64: - return V_CMP_NE_U64(inst); + return V_CMP_U64(ConditionOp::LG, false, false, inst); case Opcode::V_CMP_CLASS_F32: return V_CMP_CLASS_F32(inst); @@ -556,27 +558,33 @@ void Translator::V_BCNT_U32_B32(const GcnInst& inst) { void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) { if (!is_low) { - // v_mbcnt_hi_u32_b32 v2, -1, 0 + // v_mbcnt_hi_u32_b32 vX, -1, 0 if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193 && inst.src[1].field == OperandField::ConstZero) { return; } - // v_mbcnt_hi_u32_b32 vX, exec_hi, 0 - if (inst.src[0].field == OperandField::ExecHi && - inst.src[1].field == OperandField::ConstZero) { - return; + // v_mbcnt_hi_u32_b32 vX, exec_hi, 0/vZ + if ((inst.src[0].field == OperandField::ExecHi || + inst.src[0].field == OperandField::VccHi || + inst.src[0].field == OperandField::ScalarGPR) && + (inst.src[1].field == OperandField::ConstZero || + inst.src[1].field == OperandField::VectorGPR)) { + return SetDst(inst.dst[0], GetSrc(inst.src[1])); } + UNREACHABLE(); } else { - // v_mbcnt_lo_u32_b32 v2, -1, vX + // v_mbcnt_lo_u32_b32 vY, -1, vX // used combined with above to fetch lane id in non-compute stages if (inst.src[0].field == OperandField::SignedConstIntNeg && inst.src[0].code == 193) { - SetDst(inst.dst[0], ir.LaneId()); + return SetDst(inst.dst[0], ir.LaneId()); } - // v_mbcnt_lo_u32_b32 v20, exec_lo, vX - // used combined in above for append buffer indexing. - if (inst.src[0].field == OperandField::ExecLo) { - SetDst(inst.dst[0], ir.Imm32(0)); + // v_mbcnt_lo_u32_b32 vY, exec_lo, vX + // used combined with above for append buffer indexing. + if (inst.src[0].field == OperandField::ExecLo || inst.src[0].field == OperandField::VccLo || + inst.src[0].field == OperandField::ScalarGPR) { + return SetDst(inst.dst[0], GetSrc(inst.src[1])); } + UNREACHABLE(); } } @@ -617,12 +625,15 @@ void Translator::V_ADDC_U32(const GcnInst& inst) { const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 carry{GetCarryIn(inst)}; - const IR::U32 result{ir.IAdd(ir.IAdd(src0, src1), carry)}; - SetDst(inst.dst[0], result); + const IR::Value tmp1{ir.IAddCarry(src0, src1)}; + const IR::U32 result1{ir.CompositeExtract(tmp1, 0)}; + const IR::U32 carry_out1{ir.CompositeExtract(tmp1, 1)}; + const IR::Value tmp2{ir.IAddCarry(result1, carry)}; + const IR::U32 result2{ir.CompositeExtract(tmp2, 0)}; + const IR::U32 carry_out2{ir.CompositeExtract(tmp2, 1)}; + SetDst(inst.dst[0], result2); - const IR::U1 less_src0{ir.ILessThan(result, src0, false)}; - const IR::U1 less_src1{ir.ILessThan(result, src1, false)}; - const IR::U1 did_overflow{ir.LogicalOr(less_src0, less_src1)}; + const IR::U1 did_overflow{ir.INotEqual(ir.BitwiseOr(carry_out1, carry_out2), ir.Imm32(0))}; SetCarryOut(inst, did_overflow); } @@ -996,39 +1007,32 @@ void Translator::V_CMP_U32(ConditionOp op, bool is_signed, bool set_exec, const } } -void Translator::V_CMP_NE_U64(const GcnInst& inst) { - const auto get_src = [&](const InstOperand& operand) { - switch (operand.field) { - case OperandField::VccLo: - return ir.GetVcc(); - case OperandField::ExecLo: - return ir.GetExec(); - case OperandField::ScalarGPR: - return ir.GetThreadBitScalarReg(IR::ScalarReg(operand.code)); - case OperandField::ConstZero: - return ir.Imm1(false); +void Translator::V_CMP_U64(ConditionOp op, bool is_signed, bool set_exec, const GcnInst& inst) { + const IR::U64 src0{GetSrc64(inst.src[0])}; + const IR::U64 src1{GetSrc64(inst.src[1])}; + const IR::U1 result = [&] { + switch (op) { + case ConditionOp::EQ: + return ir.IEqual(src0, src1); + case ConditionOp::LG: // NE + return ir.INotEqual(src0, src1); default: - UNREACHABLE(); + UNREACHABLE_MSG("Unsupported V_CMP_U64 condition operation: {}", u32(op)); } - }; - const IR::U1 src0{get_src(inst.src[0])}; - auto op = [&inst, this](auto x) { - switch (inst.src[1].field) { - case OperandField::ConstZero: - return x; - case OperandField::SignedConstIntNeg: - return ir.LogicalNot(x); - default: - UNREACHABLE_MSG("unhandled V_CMP_NE_U64 source argument {}", u32(inst.src[1].field)); - } - }; + }(); + + if (is_signed) { + UNREACHABLE_MSG("V_CMP_U64 with signed integers is not supported"); + } + if (set_exec) { + UNREACHABLE_MSG("Exec setting for V_CMP_U64 is not supported"); + } + switch (inst.dst[1].field) { case OperandField::VccLo: - ir.SetVcc(op(src0)); - break; + return ir.SetVcc(result); case OperandField::ScalarGPR: - ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), op(src0)); - break; + return ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), result); default: UNREACHABLE(); } diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index a102ebf99..df20f7f73 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -74,12 +74,20 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_ATOMIC(AtomicOp::CmpSwap, inst); case Opcode::BUFFER_ATOMIC_SMIN: return BUFFER_ATOMIC(AtomicOp::Smin, inst); + case Opcode::BUFFER_ATOMIC_SMIN_X2: + return BUFFER_ATOMIC(AtomicOp::Smin, inst); case Opcode::BUFFER_ATOMIC_UMIN: return BUFFER_ATOMIC(AtomicOp::Umin, inst); + case Opcode::BUFFER_ATOMIC_UMIN_X2: + return BUFFER_ATOMIC(AtomicOp::Umin, inst); case Opcode::BUFFER_ATOMIC_SMAX: return BUFFER_ATOMIC(AtomicOp::Smax, inst); + case Opcode::BUFFER_ATOMIC_SMAX_X2: + return BUFFER_ATOMIC(AtomicOp::Smax, inst); case Opcode::BUFFER_ATOMIC_UMAX: return BUFFER_ATOMIC(AtomicOp::Umax, inst); + case Opcode::BUFFER_ATOMIC_UMAX_X2: + return BUFFER_ATOMIC(AtomicOp::Umax, inst); case Opcode::BUFFER_ATOMIC_AND: return BUFFER_ATOMIC(AtomicOp::And, inst); case Opcode::BUFFER_ATOMIC_OR: @@ -184,9 +192,10 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_ const IR::VectorReg vaddr{inst.src[0].code}; const IR::ScalarReg sharp{inst.src[2].code * 4}; const IR::Value soffset{GetSrc(inst.src[3])}; + const bool has_soffset = !soffset.IsImmediate() || soffset.U32() != 0; if (info.stage != Stage::Geometry) { - ASSERT_MSG(soffset.IsImmediate() && soffset.U32() == 0, - "Non immediate offset not supported"); + ASSERT_MSG(!has_soffset || !mubuf.offen, + "Having both scalar and vector offsets is not supported"); } const IR::Value address = [&] -> IR::Value { @@ -196,15 +205,21 @@ void Translator::BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_ if (mubuf.idxen && mubuf.offen) { return ir.CompositeConstruct(ir.GetVectorReg(vaddr), ir.GetVectorReg(vaddr + 1)); } + if (mubuf.idxen && has_soffset) { + return ir.CompositeConstruct(ir.GetVectorReg(vaddr), soffset); + } if (mubuf.idxen || mubuf.offen) { return ir.GetVectorReg(vaddr); } + if (has_soffset) { + return soffset; + } return {}; }(); IR::BufferInstInfo buffer_info{}; buffer_info.index_enable.Assign(mubuf.idxen); - buffer_info.offset_enable.Assign(mubuf.offen); + buffer_info.offset_enable.Assign(mubuf.offen || has_soffset); buffer_info.inst_offset.Assign(mubuf.offset); buffer_info.globally_coherent.Assign(mubuf.glc); buffer_info.system_coherent.Assign(mubuf.slc); @@ -304,6 +319,7 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer } } +template void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { const auto& mubuf = inst.control.mubuf; const IR::VectorReg vaddr{inst.src[0].code}; @@ -328,7 +344,17 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { buffer_info.globally_coherent.Assign(mubuf.glc); buffer_info.system_coherent.Assign(mubuf.slc); - IR::Value vdata_val = ir.GetVectorReg(vdata); + IR::Value vdata_val = [&] { + if constexpr (std::is_same_v) { + return ir.GetVectorReg(vdata); + } else if constexpr (std::is_same_v) { + return ir.PackUint2x32( + ir.CompositeConstruct(ir.GetVectorReg(vdata), + ir.GetVectorReg(vdata + 1))); + } else { + static_assert(false, "buffer_atomic: type not supported"); + } + }(); const IR::Value handle = ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1), ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3)); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 6777c4769..9703643e8 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -25,7 +25,7 @@ namespace Shader { static constexpr size_t NumUserDataRegs = 16; static constexpr size_t NumImages = 64; -static constexpr size_t NumBuffers = 32; +static constexpr size_t NumBuffers = 40; static constexpr size_t NumSamplers = 16; static constexpr size_t NumFMasks = 8; @@ -225,6 +225,8 @@ struct Info { bool uses_fp64{}; bool uses_pack_10_11_11{}; bool uses_unpack_10_11_11{}; + bool uses_buffer_int64_atomics{}; + bool uses_shared_int64_atomics{}; bool stores_tess_level_outer{}; bool stores_tess_level_inner{}; bool translation_failed{}; @@ -238,7 +240,7 @@ struct Info { Dynamic = 1 << 1, }; ReadConstType readconst_types{}; - IR::Type dma_types{IR::Type::Void}; + bool uses_dma{false}; explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params) : stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()}, diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index ab6535af2..4997145d7 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -291,78 +291,137 @@ void IREmitter::SetPatch(Patch patch, const F32& value) { Inst(Opcode::SetPatch, patch, value); } -Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) { +Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset, bool is_gds) { switch (bit_size) { case 16: - return Inst(Opcode::LoadSharedU16, offset); + return Inst(Opcode::LoadSharedU16, Flags{is_gds}, offset); case 32: - return Inst(Opcode::LoadSharedU32, offset); + return Inst(Opcode::LoadSharedU32, Flags{is_gds}, offset); case 64: - return Inst(Opcode::LoadSharedU64, offset); + return Inst(Opcode::LoadSharedU64, Flags{is_gds}, offset); default: UNREACHABLE_MSG("Invalid bit size {}", bit_size); } } -void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) { +void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset, bool is_gds) { switch (bit_size) { case 16: - Inst(Opcode::WriteSharedU16, offset, value); + Inst(Opcode::WriteSharedU16, Flags{is_gds}, offset, value); break; case 32: - Inst(Opcode::WriteSharedU32, offset, value); + Inst(Opcode::WriteSharedU32, Flags{is_gds}, offset, value); break; case 64: - Inst(Opcode::WriteSharedU64, offset, value); + Inst(Opcode::WriteSharedU64, Flags{is_gds}, offset, value); break; default: UNREACHABLE_MSG("Invalid bit size {}", bit_size); } } -U32U64 IREmitter::SharedAtomicIAdd(const U32& address, const U32U64& data) { +U32U64 IREmitter::SharedAtomicIAdd(const U32& address, const U32U64& data, bool is_gds) { switch (data.Type()) { case Type::U32: - return Inst(Opcode::SharedAtomicIAdd32, address, data); + return Inst(Opcode::SharedAtomicIAdd32, Flags{is_gds}, address, data); case Type::U64: - return Inst(Opcode::SharedAtomicIAdd64, address, data); + return Inst(Opcode::SharedAtomicIAdd64, Flags{is_gds}, address, data); default: ThrowInvalidType(data.Type()); } } -U32 IREmitter::SharedAtomicIMin(const U32& address, const U32& data, bool is_signed) { - return is_signed ? Inst(Opcode::SharedAtomicSMin32, address, data) - : Inst(Opcode::SharedAtomicUMin32, address, data); +U32U64 IREmitter::SharedAtomicIMin(const U32& address, const U32U64& data, bool is_signed, + bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SharedAtomicSMin32 : Opcode::SharedAtomicUMin32, + Flags{is_gds}, address, data); + case Type::U64: + return Inst(is_signed ? Opcode::SharedAtomicSMin64 : Opcode::SharedAtomicUMin64, + Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } } -U32 IREmitter::SharedAtomicIMax(const U32& address, const U32& data, bool is_signed) { - return is_signed ? Inst(Opcode::SharedAtomicSMax32, address, data) - : Inst(Opcode::SharedAtomicUMax32, address, data); +U32U64 IREmitter::SharedAtomicIMax(const U32& address, const U32U64& data, bool is_signed, + bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SharedAtomicSMax32 : Opcode::SharedAtomicUMax32, + Flags{is_gds}, address, data); + case Type::U64: + return Inst(is_signed ? Opcode::SharedAtomicSMax64 : Opcode::SharedAtomicUMax64, + Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } } -U32 IREmitter::SharedAtomicAnd(const U32& address, const U32& data) { - return Inst(Opcode::SharedAtomicAnd32, address, data); +U32U64 IREmitter::SharedAtomicAnd(const U32& address, const U32U64& data, bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(Opcode::SharedAtomicAnd32, Flags{is_gds}, address, data); + case Type::U64: + return Inst(Opcode::SharedAtomicAnd64, Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } } -U32 IREmitter::SharedAtomicOr(const U32& address, const U32& data) { +U32U64 IREmitter::SharedAtomicOr(const U32& address, const U32U64& data, bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(Opcode::SharedAtomicAnd32, Flags{is_gds}, address, data); + case Type::U64: + return Inst(Opcode::SharedAtomicAnd64, Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } return Inst(Opcode::SharedAtomicOr32, address, data); } -U32 IREmitter::SharedAtomicXor(const U32& address, const U32& data) { - return Inst(Opcode::SharedAtomicXor32, address, data); +U32U64 IREmitter::SharedAtomicXor(const U32& address, const U32U64& data, bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(Opcode::SharedAtomicXor32, Flags{is_gds}, address, data); + case Type::U64: + return Inst(Opcode::SharedAtomicXor64, Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } } -U32 IREmitter::SharedAtomicInc(const U32& address) { - return Inst(Opcode::SharedAtomicInc32, address); +U32U64 IREmitter::SharedAtomicISub(const U32& address, const U32U64& data, bool is_gds) { + switch (data.Type()) { + case Type::U32: + return Inst(Opcode::SharedAtomicISub32, Flags{is_gds}, address, data); + case Type::U64: + return Inst(Opcode::SharedAtomicISub64, Flags{is_gds}, address, data); + default: + ThrowInvalidType(data.Type()); + } } -U32 IREmitter::SharedAtomicDec(const U32& address) { - return Inst(Opcode::SharedAtomicDec32, address); +template <> +U32 IREmitter::SharedAtomicInc(const U32& address, bool is_gds) { + return Inst(Opcode::SharedAtomicInc32, Flags{is_gds}, address); } -U32 IREmitter::SharedAtomicISub(const U32& address, const U32& data) { - return Inst(Opcode::SharedAtomicISub32, address, data); +template <> +U64 IREmitter::SharedAtomicInc(const U32& address, bool is_gds) { + return Inst(Opcode::SharedAtomicInc64, Flags{is_gds}, address); +} + +template <> +U32 IREmitter::SharedAtomicDec(const U32& address, bool is_gds) { + return Inst(Opcode::SharedAtomicDec32, Flags{is_gds}, address); +} + +template <> +U64 IREmitter::SharedAtomicDec(const U32& address, bool is_gds) { + return Inst(Opcode::SharedAtomicDec64, Flags{is_gds}, address); } U32 IREmitter::ReadConst(const Value& base, const U32& offset) { @@ -500,8 +559,16 @@ Value IREmitter::BufferAtomicISub(const Value& handle, const Value& address, con Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info) { - return is_signed ? Inst(Opcode::BufferAtomicSMin32, Flags{info}, handle, address, value) - : Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value); + switch (value.Type()) { + case Type::U32: + return is_signed ? Inst(Opcode::BufferAtomicSMin32, Flags{info}, handle, address, value) + : Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value); + case Type::U64: + return is_signed ? Inst(Opcode::BufferAtomicSMin64, Flags{info}, handle, address, value) + : Inst(Opcode::BufferAtomicUMin64, Flags{info}, handle, address, value); + default: + ThrowInvalidType(value.Type()); + } } Value IREmitter::BufferAtomicFMin(const Value& handle, const Value& address, const Value& value, @@ -511,8 +578,16 @@ Value IREmitter::BufferAtomicFMin(const Value& handle, const Value& address, con Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info) { - return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value) - : Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value); + switch (value.Type()) { + case Type::U32: + return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value) + : Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value); + case Type::U64: + return is_signed ? Inst(Opcode::BufferAtomicSMax64, Flags{info}, handle, address, value) + : Inst(Opcode::BufferAtomicUMax64, Flags{info}, handle, address, value); + default: + ThrowInvalidType(value.Type()); + } } Value IREmitter::BufferAtomicFMax(const Value& handle, const Value& address, const Value& value, @@ -585,6 +660,14 @@ U32 IREmitter::WriteLane(const U32& value, const U32& write_value, const U32& la return Inst(Opcode::WriteLane, value, write_value, lane); } +Value IREmitter::Ballot(const U1& bit) { + return Inst(Opcode::Ballot, bit); +} + +U32 IREmitter::BallotFindLsb(const Value& mask) { + return Inst(Opcode::BallotFindLsb, mask); +} + F32F64 IREmitter::FPAdd(const F32F64& a, const F32F64& b) { if (a.Type() != b.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type()); @@ -1408,13 +1491,13 @@ U32U64 IREmitter::IAdd(const U32U64& a, const U32U64& b) { } } -Value IREmitter::IAddCary(const U32& a, const U32& b) { +Value IREmitter::IAddCarry(const U32& a, const U32& b) { if (a.Type() != b.Type()) { UNREACHABLE_MSG("Mismatching types {} and {}", a.Type(), b.Type()); } switch (a.Type()) { case Type::U32: - return Inst(Opcode::IAddCary32, a, b); + return Inst(Opcode::IAddCarry32, a, b); default: ThrowInvalidType(a.Type()); } @@ -1704,12 +1787,32 @@ U1 IREmitter::IEqual(const U32U64& lhs, const U32U64& rhs) { } } -U1 IREmitter::ILessThanEqual(const U32& lhs, const U32& rhs, bool is_signed) { - return Inst(is_signed ? Opcode::SLessThanEqual : Opcode::ULessThanEqual, lhs, rhs); +U1 IREmitter::ILessThanEqual(const U32U64& lhs, const U32U64& rhs, bool is_signed) { + if (lhs.Type() != rhs.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); + } + switch (lhs.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SLessThanEqual32 : Opcode::ULessThanEqual32, lhs, rhs); + case Type::U64: + return Inst(is_signed ? Opcode::SLessThanEqual64 : Opcode::ULessThanEqual64, lhs, rhs); + default: + ThrowInvalidType(lhs.Type()); + } } -U1 IREmitter::IGreaterThan(const U32& lhs, const U32& rhs, bool is_signed) { - return Inst(is_signed ? Opcode::SGreaterThan : Opcode::UGreaterThan, lhs, rhs); +U1 IREmitter::IGreaterThan(const U32U64& lhs, const U32U64& rhs, bool is_signed) { + if (lhs.Type() != rhs.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); + } + switch (lhs.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SGreaterThan32 : Opcode::UGreaterThan32, lhs, rhs); + case Type::U64: + return Inst(is_signed ? Opcode::SGreaterThan64 : Opcode::UGreaterThan64, lhs, rhs); + default: + ThrowInvalidType(lhs.Type()); + } } U1 IREmitter::INotEqual(const U32U64& lhs, const U32U64& rhs) { @@ -1726,8 +1829,20 @@ U1 IREmitter::INotEqual(const U32U64& lhs, const U32U64& rhs) { } } -U1 IREmitter::IGreaterThanEqual(const U32& lhs, const U32& rhs, bool is_signed) { - return Inst(is_signed ? Opcode::SGreaterThanEqual : Opcode::UGreaterThanEqual, lhs, rhs); +U1 IREmitter::IGreaterThanEqual(const U32U64& lhs, const U32U64& rhs, bool is_signed) { + if (lhs.Type() != rhs.Type()) { + UNREACHABLE_MSG("Mismatching types {} and {}", lhs.Type(), rhs.Type()); + } + switch (lhs.Type()) { + case Type::U32: + return Inst(is_signed ? Opcode::SGreaterThanEqual32 : Opcode::UGreaterThanEqual32, lhs, + rhs); + case Type::U64: + return Inst(is_signed ? Opcode::SGreaterThanEqual64 : Opcode::UGreaterThanEqual64, lhs, + rhs); + default: + ThrowInvalidType(lhs.Type()); + } } U1 IREmitter::LogicalOr(const U1& a, const U1& b) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 9e2f79978..6055df565 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -96,18 +96,24 @@ public: [[nodiscard]] F32 GetPatch(Patch patch); void SetPatch(Patch patch, const F32& value); - [[nodiscard]] Value LoadShared(int bit_size, bool is_signed, const U32& offset); - void WriteShared(int bit_size, const Value& value, const U32& offset); + [[nodiscard]] Value LoadShared(int bit_size, bool is_signed, const U32& offset, + bool is_gds = false); + void WriteShared(int bit_size, const Value& value, const U32& offset, bool is_gds = false); - [[nodiscard]] U32U64 SharedAtomicIAdd(const U32& address, const U32U64& data); - [[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data); - [[nodiscard]] U32 SharedAtomicIMin(const U32& address, const U32& data, bool is_signed); - [[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed); - [[nodiscard]] U32 SharedAtomicInc(const U32& address); - [[nodiscard]] U32 SharedAtomicDec(const U32& address); - [[nodiscard]] U32 SharedAtomicAnd(const U32& address, const U32& data); - [[nodiscard]] U32 SharedAtomicOr(const U32& address, const U32& data); - [[nodiscard]] U32 SharedAtomicXor(const U32& address, const U32& data); + [[nodiscard]] U32U64 SharedAtomicIAdd(const U32& address, const U32U64& data, bool is_gds); + [[nodiscard]] U32U64 SharedAtomicISub(const U32& address, const U32U64& data, bool is_gds); + [[nodiscard]] U32U64 SharedAtomicIMin(const U32& address, const U32U64& data, bool is_signed, + bool is_gds); + [[nodiscard]] U32U64 SharedAtomicIMax(const U32& address, const U32U64& data, bool is_signed, + bool is_gds); + [[nodiscard]] U32U64 SharedAtomicAnd(const U32& address, const U32U64& data, bool is_gds); + [[nodiscard]] U32U64 SharedAtomicOr(const U32& address, const U32U64& data, bool is_gds); + [[nodiscard]] U32U64 SharedAtomicXor(const U32& address, const U32U64& data, bool is_gds); + + template + [[nodiscard]] T SharedAtomicInc(const U32& address, bool is_gds); + template + [[nodiscard]] T SharedAtomicDec(const U32& address, bool is_gds); [[nodiscard]] U32 ReadConst(const Value& base, const U32& offset); [[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index); @@ -170,6 +176,8 @@ public: [[nodiscard]] U32 ReadFirstLane(const U32& value); [[nodiscard]] U32 ReadLane(const U32& value, const U32& lane); [[nodiscard]] U32 WriteLane(const U32& value, const U32& write_value, const U32& lane); + [[nodiscard]] Value Ballot(const U1& bit); + [[nodiscard]] U32 BallotFindLsb(const Value& mask); [[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2); [[nodiscard]] Value CompositeConstruct(const Value& e1, const Value& e2, const Value& e3); @@ -254,7 +262,7 @@ public: [[nodiscard]] F32F64 FPMedTri(const F32F64& a, const F32F64& b, const F32F64& c); [[nodiscard]] U32U64 IAdd(const U32U64& a, const U32U64& b); - [[nodiscard]] Value IAddCary(const U32& a, const U32& b); + [[nodiscard]] Value IAddCarry(const U32& a, const U32& b); [[nodiscard]] U32U64 ISub(const U32U64& a, const U32U64& b); [[nodiscard]] U32 IMulHi(const U32& a, const U32& b, bool is_signed = false); [[nodiscard]] U32U64 IMul(const U32U64& a, const U32U64& b); @@ -299,10 +307,10 @@ public: [[nodiscard]] U1 ILessThan(const U32U64& lhs, const U32U64& rhs, bool is_signed); [[nodiscard]] U1 IEqual(const U32U64& lhs, const U32U64& rhs); - [[nodiscard]] U1 ILessThanEqual(const U32& lhs, const U32& rhs, bool is_signed); - [[nodiscard]] U1 IGreaterThan(const U32& lhs, const U32& rhs, bool is_signed); + [[nodiscard]] U1 ILessThanEqual(const U32U64& lhs, const U32U64& rhs, bool is_signed); + [[nodiscard]] U1 IGreaterThan(const U32U64& lhs, const U32U64& rhs, bool is_signed); [[nodiscard]] U1 INotEqual(const U32U64& lhs, const U32U64& rhs); - [[nodiscard]] U1 IGreaterThanEqual(const U32& lhs, const U32& rhs, bool is_signed); + [[nodiscard]] U1 IGreaterThanEqual(const U32U64& lhs, const U32U64& rhs, bool is_signed); [[nodiscard]] U1 LogicalOr(const U1& a, const U1& b); [[nodiscard]] U1 LogicalAnd(const U1& a, const U1& b); diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index 1ea5c0967..eaab05cb7 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -70,10 +70,14 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicIAdd64: case Opcode::BufferAtomicISub32: case Opcode::BufferAtomicSMin32: + case Opcode::BufferAtomicSMin64: case Opcode::BufferAtomicUMin32: + case Opcode::BufferAtomicUMin64: case Opcode::BufferAtomicFMin32: case Opcode::BufferAtomicSMax32: + case Opcode::BufferAtomicSMax64: case Opcode::BufferAtomicUMax32: + case Opcode::BufferAtomicUMax64: case Opcode::BufferAtomicFMax32: case Opcode::BufferAtomicInc32: case Opcode::BufferAtomicDec32: @@ -88,7 +92,6 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::WriteSharedU32: case Opcode::WriteSharedU64: case Opcode::SharedAtomicIAdd32: - case Opcode::SharedAtomicIAdd64: case Opcode::SharedAtomicISub32: case Opcode::SharedAtomicSMin32: case Opcode::SharedAtomicUMin32: @@ -99,6 +102,17 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::SharedAtomicAnd32: case Opcode::SharedAtomicOr32: case Opcode::SharedAtomicXor32: + case Opcode::SharedAtomicIAdd64: + case Opcode::SharedAtomicISub64: + case Opcode::SharedAtomicSMin64: + case Opcode::SharedAtomicUMin64: + case Opcode::SharedAtomicSMax64: + case Opcode::SharedAtomicUMax64: + case Opcode::SharedAtomicInc64: + case Opcode::SharedAtomicDec64: + case Opcode::SharedAtomicAnd64: + case Opcode::SharedAtomicOr64: + case Opcode::SharedAtomicXor64: case Opcode::ImageWrite: case Opcode::ImageAtomicIAdd32: case Opcode::ImageAtomicSMin32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 179a01945..747a27e35 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -41,15 +41,25 @@ OPCODE(WriteSharedU64, Void, U32, OPCODE(SharedAtomicIAdd32, U32, U32, U32, ) OPCODE(SharedAtomicIAdd64, U64, U32, U64, ) OPCODE(SharedAtomicISub32, U32, U32, U32, ) +OPCODE(SharedAtomicISub64, U64, U32, U64, ) OPCODE(SharedAtomicSMin32, U32, U32, U32, ) +OPCODE(SharedAtomicSMin64, U64, U32, U64, ) OPCODE(SharedAtomicUMin32, U32, U32, U32, ) +OPCODE(SharedAtomicUMin64, U64, U32, U64, ) OPCODE(SharedAtomicSMax32, U32, U32, U32, ) +OPCODE(SharedAtomicSMax64, U64, U32, U64, ) OPCODE(SharedAtomicUMax32, U32, U32, U32, ) +OPCODE(SharedAtomicUMax64, U64, U32, U64, ) OPCODE(SharedAtomicInc32, U32, U32, ) +OPCODE(SharedAtomicInc64, U64, U32, ) OPCODE(SharedAtomicDec32, U32, U32, ) +OPCODE(SharedAtomicDec64, U64, U32, ) OPCODE(SharedAtomicAnd32, U32, U32, U32, ) +OPCODE(SharedAtomicAnd64, U64, U32, U64, ) OPCODE(SharedAtomicOr32, U32, U32, U32, ) +OPCODE(SharedAtomicOr64, U64, U32, U64, ) OPCODE(SharedAtomicXor32, U32, U32, U32, ) +OPCODE(SharedAtomicXor64, U64, U32, U64, ) // Context getters/setters OPCODE(GetUserData, U32, ScalarReg, ) @@ -124,10 +134,14 @@ OPCODE(BufferAtomicIAdd32, U32, Opaq OPCODE(BufferAtomicIAdd64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicSMin64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicUMin64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicSMax64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicUMax64, U64, Opaque, Opaque, U64 ) OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, ) @@ -324,7 +338,7 @@ OPCODE(FPCmpClass32, U1, F32, // Integer operations OPCODE(IAdd32, U32, U32, U32, ) OPCODE(IAdd64, U64, U64, U64, ) -OPCODE(IAddCary32, U32x2, U32, U32, ) +OPCODE(IAddCarry32, U32x2, U32, U32, ) OPCODE(ISub32, U32, U32, U32, ) OPCODE(ISub64, U64, U64, U64, ) OPCODE(IMul32, U32, U32, U32, ) @@ -380,14 +394,20 @@ OPCODE(ULessThan32, U1, U32, OPCODE(ULessThan64, U1, U64, U64, ) OPCODE(IEqual32, U1, U32, U32, ) OPCODE(IEqual64, U1, U64, U64, ) -OPCODE(SLessThanEqual, U1, U32, U32, ) -OPCODE(ULessThanEqual, U1, U32, U32, ) -OPCODE(SGreaterThan, U1, U32, U32, ) -OPCODE(UGreaterThan, U1, U32, U32, ) +OPCODE(SLessThanEqual32, U1, U32, U32, ) +OPCODE(SLessThanEqual64, U1, U64, U64, ) +OPCODE(ULessThanEqual32, U1, U32, U32, ) +OPCODE(ULessThanEqual64, U1, U64, U64, ) +OPCODE(SGreaterThan32, U1, U32, U32, ) +OPCODE(SGreaterThan64, U1, U64, U64, ) +OPCODE(UGreaterThan32, U1, U32, U32, ) +OPCODE(UGreaterThan64, U1, U64, U64, ) OPCODE(INotEqual32, U1, U32, U32, ) OPCODE(INotEqual64, U1, U64, U64, ) -OPCODE(SGreaterThanEqual, U1, U32, U32, ) -OPCODE(UGreaterThanEqual, U1, U32, U32, ) +OPCODE(SGreaterThanEqual32, U1, U32, U32, ) +OPCODE(SGreaterThanEqual64, U1, U64, U64, ) +OPCODE(UGreaterThanEqual32, U1, U32, U32, ) +OPCODE(UGreaterThanEqual64, U1, U64, U64, ) // Logical operations OPCODE(LogicalOr, U1, U1, U1, ) @@ -452,5 +472,7 @@ OPCODE(QuadShuffle, U32, U32, OPCODE(ReadFirstLane, U32, U32, ) OPCODE(ReadLane, U32, U32, U32 ) OPCODE(WriteLane, U32, U32, U32, U32 ) +OPCODE(Ballot, U32x4, U1, ) +OPCODE(BallotFindLsb, U32, U32x4, ) OPCODE(DataAppend, U32, U32, U32 ) OPCODE(DataConsume, U32, U32, U32 ) diff --git a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp index 5c66b1115..2a39d3a2e 100644 --- a/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp +++ b/src/shader_recompiler/ir/passes/constant_propagation_pass.cpp @@ -381,24 +381,42 @@ void ConstantPropagation(IR::Block& block, IR::Inst& inst) { case IR::Opcode::ULessThan64: FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a < b; }); return; - case IR::Opcode::SLessThanEqual: + case IR::Opcode::SLessThanEqual32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a <= b; }); return; - case IR::Opcode::ULessThanEqual: + case IR::Opcode::SLessThanEqual64: + FoldWhenAllImmediates(inst, [](s64 a, s64 b) { return a <= b; }); + return; + case IR::Opcode::ULessThanEqual32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a <= b; }); return; - case IR::Opcode::SGreaterThan: + case IR::Opcode::ULessThanEqual64: + FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a <= b; }); + return; + case IR::Opcode::SGreaterThan32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a > b; }); return; - case IR::Opcode::UGreaterThan: + case IR::Opcode::SGreaterThan64: + FoldWhenAllImmediates(inst, [](s64 a, s64 b) { return a > b; }); + return; + case IR::Opcode::UGreaterThan32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a > b; }); return; - case IR::Opcode::SGreaterThanEqual: + case IR::Opcode::UGreaterThan64: + FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a > b; }); + return; + case IR::Opcode::SGreaterThanEqual32: FoldWhenAllImmediates(inst, [](s32 a, s32 b) { return a >= b; }); return; - case IR::Opcode::UGreaterThanEqual: + case IR::Opcode::SGreaterThanEqual64: + FoldWhenAllImmediates(inst, [](s64 a, s64 b) { return a >= b; }); + return; + case IR::Opcode::UGreaterThanEqual32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a >= b; }); return; + case IR::Opcode::UGreaterThanEqual64: + FoldWhenAllImmediates(inst, [](u64 a, u64 b) { return a >= b; }); + return; case IR::Opcode::IEqual32: FoldWhenAllImmediates(inst, [](u32 a, u32 b) { return a == b; }); return; diff --git a/src/shader_recompiler/ir/passes/ir_passes.h b/src/shader_recompiler/ir/passes/ir_passes.h index 57d36f6df..fdae9d3cf 100644 --- a/src/shader_recompiler/ir/passes/ir_passes.h +++ b/src/shader_recompiler/ir/passes/ir_passes.h @@ -19,7 +19,7 @@ void ConstantPropagationPass(IR::BlockList& program); void FlattenExtendedUserdataPass(IR::Program& program); void ReadLaneEliminationPass(IR::Program& program); void ResourceTrackingPass(IR::Program& program); -void CollectShaderInfoPass(IR::Program& program); +void CollectShaderInfoPass(IR::Program& program, const Profile& profile); void LowerBufferFormatToRaw(IR::Program& program); void LowerFp64ToFp32(IR::Program& program); void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info); diff --git a/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp index 9c5f64f84..d6586bda0 100644 --- a/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp +++ b/src/shader_recompiler/ir/passes/readlane_elimination_pass.cpp @@ -95,6 +95,10 @@ void ReadLaneEliminationPass(IR::Program& program) { if (inst.GetOpcode() != IR::Opcode::ReadLane) { continue; } + if (!inst.Arg(1).IsImmediate()) { + continue; + } + const u32 lane = inst.Arg(1).U32(); IR::Inst* prod = inst.Arg(0).InstRecursive(); diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 2e9b78f0e..e5a4beb8b 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -20,10 +20,14 @@ bool IsBufferAtomic(const IR::Inst& inst) { case IR::Opcode::BufferAtomicIAdd64: case IR::Opcode::BufferAtomicISub32: case IR::Opcode::BufferAtomicSMin32: + case IR::Opcode::BufferAtomicSMin64: case IR::Opcode::BufferAtomicUMin32: + case IR::Opcode::BufferAtomicUMin64: case IR::Opcode::BufferAtomicFMin32: case IR::Opcode::BufferAtomicSMax32: + case IR::Opcode::BufferAtomicSMax64: case IR::Opcode::BufferAtomicUMax32: + case IR::Opcode::BufferAtomicUMax64: case IR::Opcode::BufferAtomicFMax32: case IR::Opcode::BufferAtomicInc32: case IR::Opcode::BufferAtomicDec32: @@ -80,8 +84,42 @@ bool IsBufferInstruction(const IR::Inst& inst) { } bool IsDataRingInstruction(const IR::Inst& inst) { - return inst.GetOpcode() == IR::Opcode::DataAppend || - inst.GetOpcode() == IR::Opcode::DataConsume; + switch (inst.GetOpcode()) { + case IR::Opcode::DataAppend: + case IR::Opcode::DataConsume: + return true; + case IR::Opcode::LoadSharedU16: + case IR::Opcode::LoadSharedU32: + case IR::Opcode::LoadSharedU64: + case IR::Opcode::WriteSharedU16: + case IR::Opcode::WriteSharedU32: + case IR::Opcode::WriteSharedU64: + case IR::Opcode::SharedAtomicIAdd32: + case IR::Opcode::SharedAtomicIAdd64: + case IR::Opcode::SharedAtomicUMin32: + case IR::Opcode::SharedAtomicUMin64: + case IR::Opcode::SharedAtomicSMin32: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicUMax64: + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicAnd32: + case IR::Opcode::SharedAtomicAnd64: + case IR::Opcode::SharedAtomicOr32: + case IR::Opcode::SharedAtomicOr64: + case IR::Opcode::SharedAtomicXor32: + case IR::Opcode::SharedAtomicXor64: + case IR::Opcode::SharedAtomicISub32: + case IR::Opcode::SharedAtomicISub64: + case IR::Opcode::SharedAtomicInc32: + case IR::Opcode::SharedAtomicInc64: + case IR::Opcode::SharedAtomicDec32: + case IR::Opcode::SharedAtomicDec64: + return inst.Flags(); // is_gds + default: + return false; + } } IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { @@ -95,6 +133,10 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { case IR::Opcode::LoadBufferU64: case IR::Opcode::StoreBufferU64: case IR::Opcode::BufferAtomicIAdd64: + case IR::Opcode::BufferAtomicSMax64: + case IR::Opcode::BufferAtomicSMin64: + case IR::Opcode::BufferAtomicUMax64: + case IR::Opcode::BufferAtomicUMin64: return IR::Type::U64; case IR::Opcode::LoadBufferFormatF32: case IR::Opcode::StoreBufferFormatF32: @@ -105,6 +147,53 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) { } } +u32 BufferAddressShift(const IR::Inst& inst, AmdGpu::DataFormat data_format) { + switch (inst.GetOpcode()) { + case IR::Opcode::LoadBufferU8: + case IR::Opcode::StoreBufferU8: + return 0; + case IR::Opcode::LoadBufferU16: + case IR::Opcode::StoreBufferU16: + return 1; + case IR::Opcode::LoadBufferU64: + case IR::Opcode::StoreBufferU64: + case IR::Opcode::BufferAtomicIAdd64: + case IR::Opcode::BufferAtomicSMax64: + case IR::Opcode::BufferAtomicSMin64: + case IR::Opcode::BufferAtomicUMax64: + case IR::Opcode::BufferAtomicUMin64: + return 3; + case IR::Opcode::LoadBufferFormatF32: + case IR::Opcode::StoreBufferFormatF32: { + switch (data_format) { + case AmdGpu::DataFormat::Format8: + return 0; + case AmdGpu::DataFormat::Format8_8: + case AmdGpu::DataFormat::Format16: + return 1; + case AmdGpu::DataFormat::Format8_8_8_8: + case AmdGpu::DataFormat::Format16_16: + case AmdGpu::DataFormat::Format10_11_11: + case AmdGpu::DataFormat::Format2_10_10_10: + case AmdGpu::DataFormat::Format16_16_16_16: + case AmdGpu::DataFormat::Format32: + case AmdGpu::DataFormat::Format32_32: + case AmdGpu::DataFormat::Format32_32_32: + case AmdGpu::DataFormat::Format32_32_32_32: + return 2; + default: + return 0; + } + break; + } + case IR::Opcode::ReadConstBuffer: + // Provided address is already in dwords + return 0; + default: + return 2; + } +} + bool IsImageAtomicInstruction(const IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::ImageAtomicIAdd32: @@ -452,7 +541,8 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& } } -void PatchDataRingAccess(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { +void PatchGlobalDataShareAccess(IR::Block& block, IR::Inst& inst, Info& info, + Descriptors& descriptors) { const u32 binding = descriptors.Add(BufferResource{ .used_types = IR::Type::U32, .inline_cbuf = AmdGpu::Buffer::Null(), @@ -460,42 +550,132 @@ void PatchDataRingAccess(IR::Block& block, IR::Inst& inst, Info& info, Descripto .is_written = true, }); - const auto pred = [](const IR::Inst* inst) -> std::optional { - if (inst->GetOpcode() == IR::Opcode::GetUserData) { - return inst; - } - return std::nullopt; - }; - - // Attempt to deduce the GDS address of counter at compile time. - u32 gds_addr = 0; - const IR::Value& gds_offset = inst.Arg(0); - if (gds_offset.IsImmediate()) { - // Nothing to do, offset is known. - gds_addr = gds_offset.U32() & 0xFFFF; - } else { - const auto result = IR::BreadthFirstSearch(&inst, pred); - ASSERT_MSG(result, "Unable to track M0 source"); - - // M0 must be set by some user data register. - const IR::Inst* prod = gds_offset.InstRecursive(); - const u32 ud_reg = u32(result.value()->Arg(0).ScalarReg()); - u32 m0_val = info.user_data[ud_reg] >> 16; - if (prod->GetOpcode() == IR::Opcode::IAdd32) { - m0_val += prod->Arg(1).U32(); - } - gds_addr = m0_val & 0xFFFF; - } - - // Patch instruction. IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; - inst.SetArg(0, ir.Imm32(gds_addr >> 2)); - inst.SetArg(1, ir.Imm32(binding)); + + // For data append/consume operations attempt to deduce the GDS address. + if (inst.GetOpcode() == IR::Opcode::DataAppend || inst.GetOpcode() == IR::Opcode::DataConsume) { + const auto pred = [](const IR::Inst* inst) -> std::optional { + if (inst->GetOpcode() == IR::Opcode::GetUserData) { + return inst; + } + return std::nullopt; + }; + + u32 gds_addr = 0; + const IR::Value& gds_offset = inst.Arg(0); + if (gds_offset.IsImmediate()) { + // Nothing to do, offset is known. + gds_addr = gds_offset.U32() & 0xFFFF; + } else { + const auto result = IR::BreadthFirstSearch(&inst, pred); + ASSERT_MSG(result, "Unable to track M0 source"); + + // M0 must be set by some user data register. + const IR::Inst* prod = gds_offset.InstRecursive(); + const u32 ud_reg = u32(result.value()->Arg(0).ScalarReg()); + u32 m0_val = info.user_data[ud_reg] >> 16; + if (prod->GetOpcode() == IR::Opcode::IAdd32) { + m0_val += prod->Arg(1).U32(); + } + gds_addr = m0_val & 0xFFFF; + } + + // Patch instruction. + inst.SetArg(0, ir.Imm32(gds_addr >> 2)); + inst.SetArg(1, ir.Imm32(binding)); + } else { + // Convert shared memory opcode to storage buffer atomic to GDS buffer. + const IR::U32 offset = IR::U32{inst.Arg(0)}; + const IR::U32 address_words = ir.ShiftRightLogical(offset, ir.Imm32(1)); + const IR::U32 address_dwords = ir.ShiftRightLogical(offset, ir.Imm32(2)); + const IR::U32 address_qwords = ir.ShiftRightLogical(offset, ir.Imm32(3)); + const IR::U32 handle = ir.Imm32(binding); + switch (inst.GetOpcode()) { + case IR::Opcode::SharedAtomicIAdd32: + inst.ReplaceUsesWith(ir.BufferAtomicIAdd(handle, address_dwords, inst.Arg(1), {})); + break; + case IR::Opcode::SharedAtomicIAdd64: + inst.ReplaceUsesWith( + ir.BufferAtomicIAdd(handle, address_qwords, IR::U64{inst.Arg(1)}, {})); + break; + case IR::Opcode::SharedAtomicISub32: + inst.ReplaceUsesWith(ir.BufferAtomicISub(handle, address_dwords, inst.Arg(1), {})); + break; + case IR::Opcode::SharedAtomicSMin32: + case IR::Opcode::SharedAtomicUMin32: { + const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32; + inst.ReplaceUsesWith( + ir.BufferAtomicIMin(handle, address_dwords, inst.Arg(1), is_signed, {})); + break; + } + case IR::Opcode::SharedAtomicSMax32: + case IR::Opcode::SharedAtomicUMax32: { + const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32; + inst.ReplaceUsesWith( + ir.BufferAtomicIMax(handle, address_dwords, inst.Arg(1), is_signed, {})); + break; + } + case IR::Opcode::SharedAtomicInc32: + inst.ReplaceUsesWith(ir.BufferAtomicInc(handle, address_dwords, {})); + break; + case IR::Opcode::SharedAtomicDec32: + inst.ReplaceUsesWith(ir.BufferAtomicDec(handle, address_dwords, {})); + break; + case IR::Opcode::SharedAtomicAnd32: + inst.ReplaceUsesWith(ir.BufferAtomicAnd(handle, address_dwords, inst.Arg(1), {})); + break; + case IR::Opcode::SharedAtomicOr32: + inst.ReplaceUsesWith(ir.BufferAtomicOr(handle, address_dwords, inst.Arg(1), {})); + break; + case IR::Opcode::SharedAtomicXor32: + inst.ReplaceUsesWith(ir.BufferAtomicXor(handle, address_dwords, inst.Arg(1), {})); + break; + case IR::Opcode::LoadSharedU16: + inst.ReplaceUsesWith(ir.LoadBufferU16(handle, address_words, {})); + break; + case IR::Opcode::LoadSharedU32: + inst.ReplaceUsesWith(ir.LoadBufferU32(1, handle, address_dwords, {})); + break; + case IR::Opcode::LoadSharedU64: + inst.ReplaceUsesWith(ir.LoadBufferU64(handle, address_qwords, {})); + break; + case IR::Opcode::WriteSharedU16: + ir.StoreBufferU16(handle, address_words, IR::U16{inst.Arg(1)}, {}); + inst.Invalidate(); + break; + case IR::Opcode::WriteSharedU32: + ir.StoreBufferU32(1, handle, address_dwords, inst.Arg(1), {}); + inst.Invalidate(); + break; + case IR::Opcode::WriteSharedU64: + ir.StoreBufferU64(handle, address_qwords, IR::U64{inst.Arg(1)}, {}); + inst.Invalidate(); + break; + default: + UNREACHABLE(); + } + } } IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const Info& info, const AmdGpu::Buffer& buffer, u32 stride) { const auto inst_info = inst.Flags(); + const u32 inst_offset = inst_info.inst_offset.Value(); + const auto is_inst_typed = inst_info.inst_data_fmt != AmdGpu::DataFormat::FormatInvalid; + const auto data_format = is_inst_typed + ? AmdGpu::RemapDataFormat(inst_info.inst_data_fmt.Value()) + : buffer.GetDataFmt(); + const u32 shift = BufferAddressShift(inst, data_format); + const u32 mask = (1 << shift) - 1; + + // If address calculation is of the form "index * const_stride + offset" with offset constant + // and both const_stride and offset are divisible with the element size, apply shift directly. + if (inst_info.index_enable && !inst_info.offset_enable && !buffer.swizzle_enable && + !buffer.add_tid_enable && (stride & mask) == 0 && (inst_offset & mask) == 0) { + // buffer_offset = index * (const_stride >> shift) + (inst_offset >> shift) + const IR::U32 index = IR::U32{inst.Arg(1)}; + return ir.IAdd(ir.IMul(index, ir.Imm32(stride >> shift)), ir.Imm32(inst_offset >> shift)); + } // index = (inst_idxen ? vgpr_index : 0) + (const_add_tid_enable ? thread_id[5:0] : 0) IR::U32 index = ir.Imm32(0U); @@ -512,7 +692,7 @@ IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const In index = ir.IAdd(index, thread_id); } // offset = (inst_offen ? vgpr_offset : 0) + inst_offset - IR::U32 offset = ir.Imm32(inst_info.inst_offset.Value()); + IR::U32 offset = ir.Imm32(inst_offset); if (inst_info.offset_enable) { const IR::U32 vgpr_offset = inst_info.index_enable ? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)} @@ -545,6 +725,9 @@ IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const In // buffer_offset = index * const_stride + offset buffer_offset = ir.IAdd(ir.IMul(index, const_stride), offset); } + if (shift != 0) { + buffer_offset = ir.ShiftRightLogical(buffer_offset, ir.Imm32(shift)); + } return buffer_offset; } @@ -842,8 +1025,6 @@ void ResourceTrackingPass(IR::Program& program) { PatchBufferSharp(*block, inst, info, descriptors); } else if (IsImageInstruction(inst)) { PatchImageSharp(*block, inst, info, descriptors); - } else if (IsDataRingInstruction(inst)) { - PatchDataRingAccess(*block, inst, info, descriptors); } } } @@ -855,6 +1036,8 @@ void ResourceTrackingPass(IR::Program& program) { PatchBufferArgs(*block, inst, info); } else if (IsImageInstruction(inst)) { PatchImageArgs(*block, inst, info); + } else if (IsDataRingInstruction(inst)) { + PatchGlobalDataShareAccess(*block, inst, info, descriptors); } } } diff --git a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp index b3b4ac36a..079827866 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -1,6 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "common/config.h" #include "shader_recompiler/ir/program.h" #include "video_core/buffer_cache/buffer_cache.h" @@ -53,9 +54,21 @@ void Visit(Info& info, const IR::Inst& inst) { case IR::Opcode::SharedAtomicXor32: info.shared_types |= IR::Type::U32; break; + case IR::Opcode::SharedAtomicIAdd64: + case IR::Opcode::SharedAtomicISub64: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMin64: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicUMax64: + case IR::Opcode::SharedAtomicInc64: + case IR::Opcode::SharedAtomicDec64: + case IR::Opcode::SharedAtomicAnd64: + case IR::Opcode::SharedAtomicOr64: + case IR::Opcode::SharedAtomicXor64: + info.uses_shared_int64_atomics = true; + [[fallthrough]]; case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU64: - case IR::Opcode::SharedAtomicIAdd64: info.shared_types |= IR::Type::U64; break; case IR::Opcode::ConvertF16F32: @@ -98,11 +111,18 @@ void Visit(Info& info, const IR::Inst& inst) { case IR::Opcode::BufferAtomicFMin32: info.uses_buffer_atomic_float_min_max = true; break; + case IR::Opcode::BufferAtomicIAdd64: + case IR::Opcode::BufferAtomicSMax64: + case IR::Opcode::BufferAtomicSMin64: + case IR::Opcode::BufferAtomicUMax64: + case IR::Opcode::BufferAtomicUMin64: + info.uses_buffer_int64_atomics = true; + break; case IR::Opcode::LaneId: info.uses_lane_id = true; break; case IR::Opcode::ReadConst: - if (info.readconst_types == Info::ReadConstType::None) { + if (!info.uses_dma) { info.buffers.push_back({ .used_types = IR::Type::U32, // We can't guarantee that flatbuf will not grow past UBO @@ -116,7 +136,7 @@ void Visit(Info& info, const IR::Inst& inst) { } else { info.readconst_types |= Info::ReadConstType::Dynamic; } - info.dma_types |= IR::Type::U32; + info.uses_dma = true; break; case IR::Opcode::PackUfloat10_11_11: info.uses_pack_10_11_11 = true; @@ -129,22 +149,42 @@ void Visit(Info& info, const IR::Inst& inst) { } } -void CollectShaderInfoPass(IR::Program& program) { +void CollectShaderInfoPass(IR::Program& program, const Profile& profile) { + auto& info = program.info; for (IR::Block* const block : program.post_order_blocks) { for (IR::Inst& inst : block->Instructions()) { - Visit(program.info, inst); + Visit(info, inst); } } - if (program.info.dma_types != IR::Type::Void) { - program.info.buffers.push_back({ + // In case Flatbuf has not already been bound by IR and is needed + // to query buffer sizes, bind it now. + if (!profile.supports_robust_buffer_access && !info.uses_dma) { + info.buffers.push_back({ + .used_types = IR::Type::U32, + // We can't guarantee that flatbuf will not grow past UBO + // limit if there are a lot of ReadConsts. (We could specialize) + .inline_cbuf = AmdGpu::Buffer::Placeholder(std::numeric_limits::max()), + .buffer_type = BufferType::Flatbuf, + }); + // In the future we may want to read buffer sizes from GPU memory if available. + // info.readconst_types |= Info::ReadConstType::Immediate; + } + + if (!Config::directMemoryAccess()) { + info.uses_dma = false; + info.readconst_types = Info::ReadConstType::None; + } + + if (info.uses_dma) { + info.buffers.push_back({ .used_types = IR::Type::U64, .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::BDA_PAGETABLE_SIZE), .buffer_type = BufferType::BdaPagetable, .is_written = true, }); - program.info.buffers.push_back({ - .used_types = IR::Type::U8, + info.buffers.push_back({ + .used_types = IR::Type::U32, .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::FAULT_BUFFER_SIZE), .buffer_type = BufferType::FaultBuffer, .is_written = true, diff --git a/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp index 0f80a3b28..555fd505b 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp @@ -15,6 +15,16 @@ static bool Requires16BitSharedAtomic(const IR::Inst& inst) { static bool Requires64BitSharedAtomic(const IR::Inst& inst) { switch (inst.GetOpcode()) { case IR::Opcode::SharedAtomicIAdd64: + case IR::Opcode::SharedAtomicISub64: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMin64: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicUMax64: + case IR::Opcode::SharedAtomicInc64: + case IR::Opcode::SharedAtomicDec64: + case IR::Opcode::SharedAtomicAnd64: + case IR::Opcode::SharedAtomicOr64: + case IR::Opcode::SharedAtomicXor64: return true; default: return false; diff --git a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp index a6900e180..b84011acc 100644 --- a/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp +++ b/src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp @@ -17,7 +17,6 @@ static bool IsSharedAccess(const IR::Inst& inst) { case IR::Opcode::WriteSharedU32: case IR::Opcode::WriteSharedU64: case IR::Opcode::SharedAtomicIAdd32: - case IR::Opcode::SharedAtomicIAdd64: case IR::Opcode::SharedAtomicISub32: case IR::Opcode::SharedAtomicSMin32: case IR::Opcode::SharedAtomicUMin32: @@ -28,6 +27,17 @@ static bool IsSharedAccess(const IR::Inst& inst) { case IR::Opcode::SharedAtomicAnd32: case IR::Opcode::SharedAtomicOr32: case IR::Opcode::SharedAtomicXor32: + case IR::Opcode::SharedAtomicIAdd64: + case IR::Opcode::SharedAtomicISub64: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMin64: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicUMax64: + case IR::Opcode::SharedAtomicInc64: + case IR::Opcode::SharedAtomicDec64: + case IR::Opcode::SharedAtomicAnd64: + case IR::Opcode::SharedAtomicOr64: + case IR::Opcode::SharedAtomicXor64: return true; default: return false; @@ -64,6 +74,16 @@ IR::Type CalculateSharedMemoryTypes(IR::Program& program) { case IR::Opcode::LoadSharedU64: case IR::Opcode::WriteSharedU64: case IR::Opcode::SharedAtomicIAdd64: + case IR::Opcode::SharedAtomicISub64: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMin64: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicUMax64: + case IR::Opcode::SharedAtomicInc64: + case IR::Opcode::SharedAtomicDec64: + case IR::Opcode::SharedAtomicAnd64: + case IR::Opcode::SharedAtomicOr64: + case IR::Opcode::SharedAtomicXor64: used_types |= IR::Type::U64; break; default: @@ -119,19 +139,26 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {})); continue; case IR::Opcode::SharedAtomicISub32: + case IR::Opcode::SharedAtomicISub64: inst.ReplaceUsesWithAndRemove( ir.BufferAtomicISub(handle, address, inst.Arg(1), {})); continue; case IR::Opcode::SharedAtomicSMin32: - case IR::Opcode::SharedAtomicUMin32: { - const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32; + case IR::Opcode::SharedAtomicUMin32: + case IR::Opcode::SharedAtomicSMin64: + case IR::Opcode::SharedAtomicUMin64: { + const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32 || + inst.GetOpcode() == IR::Opcode::SharedAtomicSMin64; inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIMin(handle, address, inst.Arg(1), is_signed, {})); continue; } case IR::Opcode::SharedAtomicSMax32: - case IR::Opcode::SharedAtomicUMax32: { - const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32; + case IR::Opcode::SharedAtomicUMax32: + case IR::Opcode::SharedAtomicSMax64: + case IR::Opcode::SharedAtomicUMax64: { + const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32 || + inst.GetOpcode() == IR::Opcode::SharedAtomicSMax64; inst.ReplaceUsesWithAndRemove( ir.BufferAtomicIMax(handle, address, inst.Arg(1), is_signed, {})); continue; @@ -143,12 +170,15 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ inst.ReplaceUsesWithAndRemove(ir.BufferAtomicDec(handle, address, {})); continue; case IR::Opcode::SharedAtomicAnd32: + case IR::Opcode::SharedAtomicAnd64: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicAnd(handle, address, inst.Arg(1), {})); continue; case IR::Opcode::SharedAtomicOr32: + case IR::Opcode::SharedAtomicOr64: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicOr(handle, address, inst.Arg(1), {})); continue; case IR::Opcode::SharedAtomicXor32: + case IR::Opcode::SharedAtomicXor64: inst.ReplaceUsesWithAndRemove(ir.BufferAtomicXor(handle, address, inst.Arg(1), {})); continue; case IR::Opcode::LoadSharedU16: @@ -173,7 +203,7 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_ inst.Invalidate(); break; default: - break; + UNREACHABLE(); } } } diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index bcdf86962..ad36a2e13 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -30,12 +30,14 @@ struct Profile { bool supports_robust_buffer_access{}; bool supports_buffer_fp32_atomic_min_max{}; bool supports_image_fp32_atomic_min_max{}; + bool supports_buffer_int64_atomics{}; + bool supports_shared_int64_atomics{}; bool supports_workgroup_explicit_memory_layout{}; bool has_broken_spirv_clamp{}; bool lower_left_origin_mode{}; bool needs_manual_interpolation{}; bool needs_lds_barriers{}; - u64 min_ssbo_alignment{}; + bool needs_buffer_offsets{}; u64 max_ubo_size{}; u32 max_viewport_width{}; u32 max_viewport_height{}; diff --git a/src/shader_recompiler/recompiler.cpp b/src/shader_recompiler/recompiler.cpp index e17fb1c9e..2da9e7b01 100644 --- a/src/shader_recompiler/recompiler.cpp +++ b/src/shader_recompiler/recompiler.cpp @@ -84,7 +84,7 @@ IR::Program TranslateProgram(std::span code, Pools& pools, Info& info Shader::Optimization::IdentityRemovalPass(program.blocks); Shader::Optimization::DeadCodeEliminationPass(program); Shader::Optimization::ConstantPropagationPass(program.post_order_blocks); - Shader::Optimization::CollectShaderInfoPass(program); + Shader::Optimization::CollectShaderInfoPass(program, profile); Shader::IR::DumpProgram(program, info); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 464f02e3a..3e66fba6a 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -72,8 +72,23 @@ Liverpool::~Liverpool() { process_thread.join(); } +void Liverpool::ProcessCommands() { + // Process incoming commands with high priority + while (num_commands) { + Common::UniqueFunction callback{}; + { + std::scoped_lock lk{submit_mutex}; + callback = std::move(command_queue.front()); + command_queue.pop(); + --num_commands; + } + callback(); + } +} + void Liverpool::Process(std::stop_token stoken) { Common::SetCurrentThreadName("shadPS4:GpuCommandProcessor"); + gpu_id = std::this_thread::get_id(); while (!stoken.stop_requested()) { { @@ -90,18 +105,7 @@ void Liverpool::Process(std::stop_token stoken) { curr_qid = -1; while (num_submits || num_commands) { - - // Process incoming commands with high priority - while (num_commands) { - Common::UniqueFunction callback{}; - { - std::unique_lock lk{submit_mutex}; - callback = std::move(command_queue.front()); - command_queue.pop(); - --num_commands; - } - callback(); - } + ProcessCommands(); curr_qid = (curr_qid + 1) % num_mapped_queues; @@ -131,9 +135,8 @@ void Liverpool::Process(std::stop_token stoken) { if (submit_done) { VideoCore::EndCapture(); - if (rasterizer) { - rasterizer->ProcessFaults(); + rasterizer->EndCommandList(); rasterizer->Flush(); } submit_done = false; @@ -147,6 +150,8 @@ Liverpool::Task Liverpool::ProcessCeUpdate(std::span ccb) { FIBER_ENTER(ccb_task_name); while (!ccb.empty()) { + ProcessCommands(); + const auto* header = reinterpret_cast(ccb.data()); const u32 type = header->type; if (type != 3) { @@ -224,6 +229,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(dcb.data()); while (!dcb.empty()) { + ProcessCommands(); + const auto* header = reinterpret_cast(dcb.data()); const u32 type = header->type; @@ -596,6 +603,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanevent_index.Value() == EventIndex::ZpassDone) { + LOG_WARNING(Render, "Unimplemented occlusion query"); } break; } @@ -638,9 +647,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spansrc_sel == DmaDataSrc::Memory || dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->dst_sel == DmaDataDst::Gds) { - rasterizer->InlineData(dma_data->dst_addr_lo, - dma_data->SrcAddress(), - dma_data->NumBytes(), true); + rasterizer->CopyBuffer(dma_data->dst_addr_lo, dma_data->SrcAddress(), + dma_data->NumBytes(), true, false); } else if (dma_data->src_sel == DmaDataSrc::Data && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { @@ -649,14 +657,15 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spansrc_sel == DmaDataSrc::Gds && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { - // LOG_WARNING(Render_Vulkan, "GDS memory read"); + rasterizer->CopyBuffer(dma_data->DstAddress(), dma_data->src_addr_lo, + dma_data->NumBytes(), false, true); } else if ((dma_data->src_sel == DmaDataSrc::Memory || dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { - rasterizer->InlineData(dma_data->DstAddress(), - dma_data->SrcAddress(), - dma_data->NumBytes(), false); + rasterizer->CopyBuffer(dma_data->DstAddress(), + dma_data->SrcAddress(), dma_data->NumBytes(), + false, false); } else { UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); @@ -702,6 +711,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); while (!rewind->Valid()) { YIELD_GFX(); @@ -801,29 +813,32 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span -Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid) { +Liverpool::Task Liverpool::ProcessCompute(std::span acb, u32 vqid) { FIBER_ENTER(acb_task_name[vqid]); auto& queue = asc_queues[{vqid}]; - auto base_addr = reinterpret_cast(acb); - while (acb_dwords > 0) { - auto* header = reinterpret_cast(acb); + auto base_addr = reinterpret_cast(acb.data()); + while (!acb.empty()) { + ProcessCommands(); + + auto* header = reinterpret_cast(acb.data()); u32 next_dw_off = header->type3.NumWords() + 1; // If we have a buffered packet, use it. if (queue.tmp_dwords > 0) [[unlikely]] { header = reinterpret_cast(queue.tmp_packet.data()); next_dw_off = header->type3.NumWords() + 1 - queue.tmp_dwords; - std::memcpy(queue.tmp_packet.data() + queue.tmp_dwords, acb, next_dw_off * sizeof(u32)); + std::memcpy(queue.tmp_packet.data() + queue.tmp_dwords, acb.data(), + next_dw_off * sizeof(u32)); queue.tmp_dwords = 0; } // If the packet is split across ring boundary, buffer until next submission - if (next_dw_off > acb_dwords) [[unlikely]] { - std::memcpy(queue.tmp_packet.data(), acb, acb_dwords * sizeof(u32)); - queue.tmp_dwords = acb_dwords; + if (next_dw_off > acb.size()) [[unlikely]] { + std::memcpy(queue.tmp_packet.data(), acb.data(), acb.size_bytes()); + queue.tmp_dwords = acb.size(); if constexpr (!is_indirect) { - *queue.read_addr += acb_dwords; + *queue.read_addr += acb.size(); *queue.read_addr %= queue.ring_size_dw; } break; @@ -832,9 +847,7 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq if (header->type == 2) { // Type-2 packet are used for padding purposes next_dw_off = 1; - acb += next_dw_off; - acb_dwords -= next_dw_off; - + acb = NextPacket(acb, next_dw_off); if constexpr (!is_indirect) { *queue.read_addr += next_dw_off; *queue.read_addr %= queue.ring_size_dw; @@ -856,8 +869,8 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq } case PM4ItOpcode::IndirectBuffer: { const auto* indirect_buffer = reinterpret_cast(header); - auto task = ProcessCompute(indirect_buffer->Address(), - indirect_buffer->ib_size, vqid); + auto task = ProcessCompute( + {indirect_buffer->Address(), indirect_buffer->ib_size}, vqid); RESUME_ASC(task, vqid); while (!task.handle.done()) { @@ -876,8 +889,8 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq } else if ((dma_data->src_sel == DmaDataSrc::Memory || dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->dst_sel == DmaDataDst::Gds) { - rasterizer->InlineData(dma_data->dst_addr_lo, dma_data->SrcAddress(), - dma_data->NumBytes(), true); + rasterizer->CopyBuffer(dma_data->dst_addr_lo, dma_data->SrcAddress(), + dma_data->NumBytes(), true, false); } else if (dma_data->src_sel == DmaDataSrc::Data && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { @@ -886,14 +899,14 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq } else if (dma_data->src_sel == DmaDataSrc::Gds && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { - // LOG_WARNING(Render_Vulkan, "GDS memory read"); + rasterizer->CopyBuffer(dma_data->DstAddress(), dma_data->src_addr_lo, + dma_data->NumBytes(), false, true); } else if ((dma_data->src_sel == DmaDataSrc::Memory || dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && (dma_data->dst_sel == DmaDataDst::Memory || dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { - rasterizer->InlineData(dma_data->DstAddress(), - dma_data->SrcAddress(), dma_data->NumBytes(), - false); + rasterizer->CopyBuffer(dma_data->DstAddress(), dma_data->SrcAddress(), + dma_data->NumBytes(), false, false); } else { UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); @@ -904,6 +917,9 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq break; } case PM4ItOpcode::Rewind: { + if (!rasterizer) { + break; + } const PM4CmdRewind* rewind = reinterpret_cast(header); while (!rewind->Valid()) { YIELD_ASC(vqid); @@ -1016,8 +1032,7 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq static_cast(opcode), header->type3.NumWords()); } - acb += next_dw_off; - acb_dwords -= next_dw_off; + acb = NextPacket(acb, next_dw_off); if constexpr (!is_indirect) { *queue.read_addr += next_dw_off; @@ -1087,7 +1102,7 @@ void Liverpool::SubmitAsc(u32 gnm_vqid, std::span acb) { auto& queue = mapped_queues[gnm_vqid]; const auto vqid = gnm_vqid - 1; - const auto& task = ProcessCompute(acb.data(), acb.size(), vqid); + const auto& task = ProcessCompute(acb, vqid); { std::scoped_lock lock{queue.m_access}; queue.submits.emplace(task.handle); diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index d88a44375..c07e9f63a 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -87,7 +88,7 @@ struct Liverpool { } }; - static const BinaryInfo& SearchBinaryInfo(const u32* code, size_t search_limit = 0x1000) { + static const BinaryInfo& SearchBinaryInfo(const u32* code, size_t search_limit = 0x2000) { constexpr u32 token_mov_vcchi = 0xBEEB03FF; if (code[0] == token_mov_vcchi) { @@ -1512,14 +1513,32 @@ public: rasterizer = rasterizer_; } - void SendCommand(Common::UniqueFunction&& func) { - std::scoped_lock lk{submit_mutex}; - command_queue.emplace(std::move(func)); - ++num_commands; - submit_cv.notify_one(); + template + void SendCommand(auto&& func) { + if (std::this_thread::get_id() == gpu_id) { + return func(); + } + if constexpr (wait_done) { + std::binary_semaphore sem{0}; + { + std::scoped_lock lk{submit_mutex}; + command_queue.emplace([&sem, &func] { + func(); + sem.release(); + }); + ++num_commands; + submit_cv.notify_one(); + } + sem.acquire(); + } else { + std::scoped_lock lk{submit_mutex}; + command_queue.emplace(std::move(func)); + ++num_commands; + submit_cv.notify_one(); + } } - void reserveCopyBufferSpace() { + void ReserveCopyBufferSpace() { GpuQueue& gfx_queue = mapped_queues[GfxQueueId]; std::scoped_lock lk(gfx_queue.m_access); @@ -1581,8 +1600,9 @@ private: Task ProcessGraphics(std::span dcb, std::span ccb); Task ProcessCeUpdate(std::span ccb); template - Task ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid); + Task ProcessCompute(std::span acb, u32 vqid); + void ProcessCommands(); void Process(std::stop_token stoken); struct GpuQueue { @@ -1626,6 +1646,7 @@ private: std::mutex submit_mutex; std::condition_variable_any submit_cv; std::queue> command_queue{}; + std::thread::id gpu_id; int curr_qid{-1}; }; diff --git a/src/video_core/buffer_cache/buffer.cpp b/src/video_core/buffer_cache/buffer.cpp index 15bf0d81e..e85a6eb18 100644 --- a/src/video_core/buffer_cache/buffer.cpp +++ b/src/video_core/buffer_cache/buffer.cpp @@ -137,12 +137,15 @@ StreamBuffer::StreamBuffer(const Vulkan::Instance& instance, Vulkan::Scheduler& size_bytes); } -std::pair StreamBuffer::Map(u64 size, u64 alignment) { +std::pair StreamBuffer::Map(u64 size, u64 alignment, bool allow_wait) { if (!is_coherent && usage == MemoryUsage::Stream) { size = Common::AlignUp(size, instance->NonCoherentAtomSize()); } - ASSERT(size <= this->size_bytes); + if (size > this->size_bytes) { + return {nullptr, 0}; + } + mapped_size = size; if (alignment > 0) { @@ -162,8 +165,11 @@ std::pair StreamBuffer::Map(u64 size, u64 alignment) { } const u64 mapped_upper_bound = offset + size; - WaitPendingOperations(mapped_upper_bound); - return std::make_pair(mapped_data.data() + offset, offset); + if (!WaitPendingOperations(mapped_upper_bound, allow_wait)) { + return {nullptr, 0}; + } + + return {mapped_data.data() + offset, offset}; } void StreamBuffer::Commit() { @@ -177,6 +183,12 @@ void StreamBuffer::Commit() { } offset += mapped_size; + if (current_watch_cursor != 0 && + current_watches[current_watch_cursor].tick == scheduler->CurrentTick()) { + current_watches[current_watch_cursor].upper_bound = offset; + return; + } + if (current_watch_cursor + 1 >= current_watches.size()) { // Ensure that there are enough watches. ReserveWatches(current_watches, WATCHES_RESERVE_CHUNK); @@ -191,16 +203,20 @@ void StreamBuffer::ReserveWatches(std::vector& watches, std::size_t grow_ watches.resize(watches.size() + grow_size); } -void StreamBuffer::WaitPendingOperations(u64 requested_upper_bound) { +bool StreamBuffer::WaitPendingOperations(u64 requested_upper_bound, bool allow_wait) { if (!invalidation_mark) { - return; + return true; } while (requested_upper_bound > wait_bound && wait_cursor < *invalidation_mark) { auto& watch = previous_watches[wait_cursor]; - wait_bound = watch.upper_bound; + if (!scheduler->IsFree(watch.tick) && !allow_wait) { + return false; + } scheduler->Wait(watch.tick); + wait_bound = watch.upper_bound; ++wait_cursor; } + return true; } } // namespace VideoCore diff --git a/src/video_core/buffer_cache/buffer.h b/src/video_core/buffer_cache/buffer.h index 530968787..a7a0ce84f 100644 --- a/src/video_core/buffer_cache/buffer.h +++ b/src/video_core/buffer_cache/buffer.h @@ -168,7 +168,7 @@ public: MemoryUsage usage, u64 size_bytes_); /// Reserves a region of memory from the stream buffer. - std::pair Map(u64 size, u64 alignment = 0); + std::pair Map(u64 size, u64 alignment = 0, bool allow_wait = true); /// Ensures that reserved bytes of memory are available to the GPU. void Commit(); @@ -181,10 +181,6 @@ public: return offset; } - u64 GetFreeSize() const { - return size_bytes - offset - mapped_size; - } - private: struct Watch { u64 tick{}; @@ -195,7 +191,7 @@ private: void ReserveWatches(std::vector& watches, std::size_t grow_size); /// Waits pending watches until requested upper bound. - void WaitPendingOperations(u64 requested_upper_bound); + bool WaitPendingOperations(u64 requested_upper_bound, bool allow_wait); private: u64 offset{}; diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 23f9dc0bc..28444ac60 100644 --- a/src/video_core/buffer_cache/buffer_cache.cpp +++ b/src/video_core/buffer_cache/buffer_cache.cpp @@ -2,6 +2,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include "common/alignment.h" #include "common/debug.h" #include "common/scope_exit.h" @@ -9,6 +10,7 @@ #include "core/memory.h" #include "video_core/amdgpu/liverpool.h" #include "video_core/buffer_cache/buffer_cache.h" +#include "video_core/buffer_cache/memory_tracker.h" #include "video_core/host_shaders/fault_buffer_process_comp.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -27,10 +29,10 @@ static constexpr size_t DeviceBufferSize = 128_MB; static constexpr size_t MaxPageFaults = 1024; BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, - Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_, - TextureCache& texture_cache_, PageManager& tracker_) - : instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_}, - memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, tracker{tracker_}, + AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_, + PageManager& tracker) + : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, + memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize}, @@ -38,13 +40,16 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize}, bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, BDA_PAGETABLE_SIZE}, - fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE), - memory_tracker{tracker} { + fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE) { Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); Vulkan::SetObjectName(instance.GetDevice(), bda_pagetable_buffer.Handle(), "BDA Page Table Buffer"); Vulkan::SetObjectName(instance.GetDevice(), fault_buffer.Handle(), "Fault Buffer"); + memory_tracker = std::make_unique(tracker); + + std::memset(gds_buffer.mapped_data.data(), 0, DataShareBufferSize); + // Ensure the first slot is used for the null buffer const auto null_id = slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16); @@ -129,22 +134,25 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s BufferCache::~BufferCache() = default; -void BufferCache::InvalidateMemory(VAddr device_addr, u64 size, bool unmap) { - const bool is_tracked = IsRegionRegistered(device_addr, size); - if (is_tracked) { - // Mark the page as CPU modified to stop tracking writes. - memory_tracker.MarkRegionAsCpuModified(device_addr, size); - - if (unmap) { - return; - } +void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) { + if (!IsRegionRegistered(device_addr, size)) { + return; } + memory_tracker->InvalidateRegion( + device_addr, size, [this, device_addr, size] { ReadMemory(device_addr, size, true); }); } -void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { +void BufferCache::ReadMemory(VAddr device_addr, u64 size, bool is_write) { + liverpool->SendCommand([this, device_addr, size, is_write] { + Buffer& buffer = slot_buffers[FindBuffer(device_addr, size)]; + DownloadBufferMemory(buffer, device_addr, size, is_write); + }); +} + +void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size, bool is_write) { boost::container::small_vector copies; u64 total_size_bytes = 0; - memory_tracker.ForEachDownloadRange( + memory_tracker->ForEachDownloadRange( device_addr, size, [&](u64 device_addr_out, u64 range_size) { const VAddr buffer_addr = buffer.CpuAddr(); const auto add_download = [&](VAddr start, VAddr end) { @@ -155,7 +163,10 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si .dstOffset = total_size_bytes, .size = new_size, }); - total_size_bytes += new_size; + // Align up to avoid cache conflicts + constexpr u64 align = 64ULL; + constexpr u64 mask = ~(align - 1ULL); + total_size_bytes += (new_size + align - 1) & mask; }; gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); gpu_modified_ranges.Subtract(device_addr_out, range_size); @@ -173,10 +184,16 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si const auto cmdbuf = scheduler.CommandBuffer(); cmdbuf.copyBuffer(buffer.buffer, download_buffer.Handle(), copies); scheduler.Finish(); + auto* memory = Core::Memory::Instance(); for (const auto& copy : copies) { const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset; const u64 dst_offset = copy.dstOffset - offset; - std::memcpy(std::bit_cast(copy_device_addr), download + dst_offset, copy.size); + memory->TryWriteBacking(std::bit_cast(copy_device_addr), download + dst_offset, + copy.size); + } + memory_tracker->UnmarkRegionAsGpuModified(device_addr, size); + if (is_write) { + memory_tracker->MarkRegionAsCpuModified(device_addr, size); } } @@ -296,9 +313,14 @@ void BufferCache::BindIndexBuffer(u32 index_offset) { void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); - if (!is_gds && !IsRegionGpuModified(address, num_bytes)) { - memcpy(std::bit_cast(address), value, num_bytes); - return; + if (!is_gds) { + if (!memory->TryWriteBacking(std::bit_cast(address), value, num_bytes)) { + std::memcpy(std::bit_cast(address), value, num_bytes); + return; + } + if (!IsRegionRegistered(address, num_bytes)) { + return; + } } Buffer* buffer = [&] { if (is_gds) { @@ -326,25 +348,107 @@ void BufferCache::WriteData(VAddr address, const void* value, u32 num_bytes, boo WriteDataBuffer(*buffer, address, value, num_bytes); } +void BufferCache::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) { + if (!dst_gds && !IsRegionGpuModified(dst, num_bytes)) { + if (!src_gds && !IsRegionGpuModified(src, num_bytes)) { + // Both buffers were not transferred to GPU yet. Can safely copy in host memory. + memcpy(std::bit_cast(dst), std::bit_cast(src), num_bytes); + return; + } + // Without a readback there's nothing we can do with this + // Fallback to creating dst buffer on GPU to at least have this data there + } + auto& src_buffer = [&] -> const Buffer& { + if (src_gds) { + return gds_buffer; + } + // Avoid using ObtainBuffer here as that might give us the stream buffer. + const BufferId buffer_id = FindBuffer(src, num_bytes); + auto& buffer = slot_buffers[buffer_id]; + SynchronizeBuffer(buffer, src, num_bytes, false, false); + return buffer; + }(); + auto& dst_buffer = [&] -> const Buffer& { + if (dst_gds) { + return gds_buffer; + } + // Prefer using ObtainBuffer here as that will auto-mark the region as GPU modified. + const auto [buffer, offset] = ObtainBuffer(dst, num_bytes, true); + return *buffer; + }(); + vk::BufferCopy region{ + .srcOffset = src_buffer.Offset(src), + .dstOffset = dst_buffer.Offset(dst), + .size = num_bytes, + }; + const vk::BufferMemoryBarrier2 buf_barriers_before[2] = { + { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eMemoryRead, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eTransferWrite, + .buffer = dst_buffer.Handle(), + .offset = dst_buffer.Offset(dst), + .size = num_bytes, + }, + { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eMemoryWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eTransferRead, + .buffer = src_buffer.Handle(), + .offset = src_buffer.Offset(src), + .size = num_bytes, + }, + }; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 2, + .pBufferMemoryBarriers = buf_barriers_before, + }); + cmdbuf.copyBuffer(src_buffer.Handle(), dst_buffer.Handle(), region); + const vk::BufferMemoryBarrier2 buf_barriers_after[2] = { + { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eTransferWrite, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryRead, + .buffer = dst_buffer.Handle(), + .offset = dst_buffer.Offset(dst), + .size = num_bytes, + }, + { + .srcStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .srcAccessMask = vk::AccessFlagBits2::eTransferRead, + .dstStageMask = vk::PipelineStageFlagBits2::eAllCommands, + .dstAccessMask = vk::AccessFlagBits2::eMemoryWrite, + .buffer = src_buffer.Handle(), + .offset = src_buffer.Offset(src), + .size = num_bytes, + }, + }; + cmdbuf.pipelineBarrier2(vk::DependencyInfo{ + .dependencyFlags = vk::DependencyFlagBits::eByRegion, + .bufferMemoryBarrierCount = 2, + .pBufferMemoryBarriers = buf_barriers_after, + }); +} + std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, bool is_texel_buffer, BufferId buffer_id) { - // For small uniform buffers that have not been modified by gpu - // use device local stream buffer to reduce renderpass breaks. - // Maybe we want to modify the threshold now that the page size is 16KB? - static constexpr u64 StreamThreshold = CACHING_PAGESIZE; - const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size); - if (!is_written && size <= StreamThreshold && !is_gpu_dirty) { + // For read-only buffers use device local stream buffer to reduce renderpass breaks. + if (!is_written && size <= CACHING_PAGESIZE && !IsRegionGpuModified(device_addr, size)) { const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); return {&stream_buffer, offset}; } - - if (!buffer_id || slot_buffers[buffer_id].is_deleted) { + if (IsBufferInvalid(buffer_id)) { buffer_id = FindBuffer(device_addr, size); } Buffer& buffer = slot_buffers[buffer_id]; - SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); + SynchronizeBuffer(buffer, device_addr, size, is_written, is_texel_buffer); if (is_written) { - memory_tracker.MarkRegionAsGpuModified(device_addr, size); gpu_modified_ranges.Add(device_addr, size); } return {&buffer, buffer.Offset(device_addr)}; @@ -352,21 +456,17 @@ std::pair BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b std::pair BufferCache::ObtainBufferForImage(VAddr gpu_addr, u32 size) { // Check if any buffer contains the full requested range. - const u64 page = gpu_addr >> CACHING_PAGEBITS; - const BufferId buffer_id = page_table[page].buffer_id; + const BufferId buffer_id = page_table[gpu_addr >> CACHING_PAGEBITS].buffer_id; if (buffer_id) { - Buffer& buffer = slot_buffers[buffer_id]; - if (buffer.IsInBounds(gpu_addr, size)) { - SynchronizeBuffer(buffer, gpu_addr, size, false); + if (Buffer& buffer = slot_buffers[buffer_id]; buffer.IsInBounds(gpu_addr, size)) { + SynchronizeBuffer(buffer, gpu_addr, size, false, false); return {&buffer, buffer.Offset(gpu_addr)}; } } - // If no buffer contains the full requested range but some buffer within was GPU-modified, - // fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications. - if (memory_tracker.IsRegionGpuModified(gpu_addr, size)) { + // If some buffer within was GPU modified create a full buffer to avoid losing GPU data. + if (IsRegionGpuModified(gpu_addr, size)) { return ObtainBuffer(gpu_addr, size, false, false); } - // In all other cases, just do a CPU copy to the staging buffer. const auto [data, offset] = staging_buffer.Map(size, 16); memory->CopySparseMemory(gpu_addr, data, size); @@ -380,11 +480,11 @@ bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) { } bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { - return memory_tracker.IsRegionCpuModified(addr, size); + return memory_tracker->IsRegionCpuModified(addr, size); } bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) { - return memory_tracker.IsRegionGpuModified(addr, size); + return memory_tracker->IsRegionGpuModified(addr, size); } BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { @@ -718,56 +818,27 @@ void BufferCache::ChangeRegister(BufferId buffer_id) { } } -void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, +void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_written, bool is_texel_buffer) { boost::container::small_vector copies; - u64 total_size_bytes = 0; + size_t total_size_bytes = 0; VAddr buffer_start = buffer.CpuAddr(); - memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { - copies.push_back(vk::BufferCopy{ - .srcOffset = total_size_bytes, - .dstOffset = device_addr_out - buffer_start, - .size = range_size, - }); - total_size_bytes += range_size; - }); + vk::Buffer src_buffer = VK_NULL_HANDLE; + memory_tracker->ForEachUploadRange( + device_addr, size, is_written, + [&](u64 device_addr_out, u64 range_size) { + copies.emplace_back(total_size_bytes, device_addr_out - buffer_start, range_size); + total_size_bytes += range_size; + }, + [&] { src_buffer = UploadCopies(buffer, copies, total_size_bytes); }); SCOPE_EXIT { if (is_texel_buffer) { SynchronizeBufferFromImage(buffer, device_addr, size); } }; - if (total_size_bytes == 0) { + if (!src_buffer) { return; } - vk::Buffer src_buffer = staging_buffer.Handle(); - if (total_size_bytes < StagingBufferSize) { - const auto [staging, offset] = staging_buffer.Map(total_size_bytes); - for (auto& copy : copies) { - u8* const src_pointer = staging + copy.srcOffset; - const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; - std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); - // Apply the staging offset - copy.srcOffset += offset; - } - staging_buffer.Commit(); - } else { - // For large one time transfers use a temporary host buffer. - // RenderDoc can lag quite a bit if the stream buffer is too large. - Buffer temp_buffer{instance, - scheduler, - MemoryUsage::Upload, - 0, - vk::BufferUsageFlagBits::eTransferSrc, - total_size_bytes}; - src_buffer = temp_buffer.Handle(); - u8* const staging = temp_buffer.mapped_data.data(); - for (auto& copy : copies) { - u8* const src_pointer = staging + copy.srcOffset; - const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; - std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); - } - scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable {}); - } scheduler.EndRendering(); const auto cmdbuf = scheduler.CommandBuffer(); const vk::BufferMemoryBarrier2 pre_barrier = { @@ -802,6 +873,39 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, }); } +vk::Buffer BufferCache::UploadCopies(Buffer& buffer, std::span copies, + size_t total_size_bytes) { + if (copies.empty()) { + return VK_NULL_HANDLE; + } + const auto [staging, offset] = staging_buffer.Map(total_size_bytes); + if (staging) { + for (auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + // Apply the staging offset + copy.srcOffset += offset; + } + staging_buffer.Commit(); + return staging_buffer.Handle(); + } else { + // For large one time transfers use a temporary host buffer. + auto temp_buffer = + std::make_unique(instance, scheduler, MemoryUsage::Upload, 0, + vk::BufferUsageFlagBits::eTransferSrc, total_size_bytes); + const vk::Buffer src_buffer = temp_buffer->Handle(); + u8* const staging = temp_buffer->mapped_data.data(); + for (const auto& copy : copies) { + u8* const src_pointer = staging + copy.srcOffset; + const VAddr device_addr = buffer.CpuAddr() + copy.dstOffset; + std::memcpy(src_pointer, std::bit_cast(device_addr), copy.size); + } + scheduler.DeferOperation([buffer = std::move(temp_buffer)]() mutable { buffer.reset(); }); + return src_buffer; + } +} + bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) { boost::container::small_vector image_ids; texture_cache.ForEachImageInRegion(device_addr, size, [&](ImageId image_id, Image& image) { @@ -925,7 +1029,7 @@ void BufferCache::SynchronizeBuffersInRange(VAddr device_addr, u64 size) { VAddr start = std::max(buffer.CpuAddr(), device_addr); VAddr end = std::min(buffer.CpuAddr() + buffer.SizeBytes(), device_addr_end); u32 size = static_cast(end - start); - SynchronizeBuffer(buffer, start, size, false); + SynchronizeBuffer(buffer, start, size, false, false); }); } diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 651ba84dc..b509ce2d0 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -9,7 +9,6 @@ #include "common/slot_vector.h" #include "common/types.h" #include "video_core/buffer_cache/buffer.h" -#include "video_core/buffer_cache/memory_tracker.h" #include "video_core/buffer_cache/range_set.h" #include "video_core/multi_level_page_table.h" @@ -21,13 +20,6 @@ namespace Core { class MemoryManager; } -namespace Shader { -namespace Gcn { -struct FetchShaderData; -} -struct Info; -} // namespace Shader - namespace Vulkan { class GraphicsPipeline; } @@ -39,6 +31,8 @@ using BufferId = Common::SlotId; static constexpr BufferId NULL_BUFFER_ID{0}; class TextureCache; +class MemoryTracker; +class PageManager; class BufferCache { public: @@ -69,10 +63,16 @@ public: bool has_stream_leap = false; }; + using IntervalSet = + boost::icl::interval_set; + using IntervalType = typename IntervalSet::interval_type; + public: explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, - Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool, - TextureCache& texture_cache, PageManager& tracker); + AmdGpu::Liverpool* liverpool, TextureCache& texture_cache, + PageManager& tracker); ~BufferCache(); /// Returns a pointer to GDS device local buffer. @@ -110,7 +110,10 @@ public: } /// Invalidates any buffer in the logical page range. - void InvalidateMemory(VAddr device_addr, u64 size, bool unmap); + void InvalidateMemory(VAddr device_addr, u64 size); + + /// Flushes any GPU modified buffer in the logical page range back to CPU memory. + void ReadMemory(VAddr device_addr, u64 size, bool is_write = false); /// Binds host vertex buffers for the current draw. void BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline); @@ -124,6 +127,9 @@ public: /// Writes a value to GPU buffer. (uses staging buffer to temporarily store the data) void WriteData(VAddr address, const void* value, u32 num_bytes, bool is_gds); + /// Performs buffer to buffer data copy on the GPU. + void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds); + /// Obtains a buffer for the specified region. [[nodiscard]] std::pair ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, bool is_texel_buffer = false, @@ -166,7 +172,11 @@ private: }); } - void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); + inline bool IsBufferInvalid(BufferId buffer_id) const { + return !buffer_id || slot_buffers[buffer_id].is_deleted; + } + + void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size, bool is_write); [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); @@ -181,7 +191,11 @@ private: template void ChangeRegister(BufferId buffer_id); - void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_texel_buffer); + void SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, bool is_written, + bool is_texel_buffer); + + vk::Buffer UploadCopies(Buffer& buffer, std::span copies, + size_t total_size_bytes); bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); @@ -193,11 +207,10 @@ private: const Vulkan::Instance& instance; Vulkan::Scheduler& scheduler; - Vulkan::Rasterizer& rasterizer; AmdGpu::Liverpool* liverpool; Core::MemoryManager* memory; TextureCache& texture_cache; - PageManager& tracker; + std::unique_ptr memory_tracker; StreamBuffer staging_buffer; StreamBuffer stream_buffer; StreamBuffer download_buffer; @@ -209,7 +222,6 @@ private: Common::SlotVector slot_buffers; RangeSet gpu_modified_ranges; SplitRangeMap buffer_ranges; - MemoryTracker memory_tracker; PageTable page_table; vk::UniqueDescriptorSetLayout fault_process_desc_layout; vk::UniquePipeline fault_process_pipeline; diff --git a/src/video_core/buffer_cache/memory_tracker.h b/src/video_core/buffer_cache/memory_tracker.h index 37fafa2d6..ec0878c3b 100644 --- a/src/video_core/buffer_cache/memory_tracker.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -16,7 +16,7 @@ namespace VideoCore { class MemoryTracker { public: static constexpr size_t MAX_CPU_PAGE_BITS = 40; - static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS); + static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - TRACKER_HIGHER_PAGE_BITS); static constexpr size_t MANAGER_POOL_SIZE = 32; public: @@ -27,6 +27,7 @@ public: bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages( query_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { + std::scoped_lock lk{manager->lock}; return manager->template IsRegionModified(offset, size); }); } @@ -35,6 +36,7 @@ public: bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept { return IteratePages( query_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { + std::scoped_lock lk{manager->lock}; return manager->template IsRegionModified(offset, size); }); } @@ -43,27 +45,68 @@ public: void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) { IteratePages(dirty_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { + std::scoped_lock lk{manager->lock}; manager->template ChangeRegionState( manager->GetCpuAddr() + offset, size); }); } - /// Mark region as modified from the host GPU - void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { + /// Unmark region as modified from the host GPU + void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept { IteratePages(dirty_cpu_addr, query_size, [](RegionManager* manager, u64 offset, size_t size) { - manager->template ChangeRegionState( + std::scoped_lock lk{manager->lock}; + manager->template ChangeRegionState( manager->GetCpuAddr() + offset, size); }); } + /// Removes all protection from a page and ensures GPU data has been flushed if requested + void InvalidateRegion(VAddr cpu_addr, u64 size, auto&& on_flush) noexcept { + IteratePages( + cpu_addr, size, [&on_flush](RegionManager* manager, u64 offset, size_t size) { + const bool should_flush = [&] { + // Perform both the GPU modification check and CPU state change with the lock + // in case we are racing with GPU thread trying to mark the page as GPU + // modified. If we need to flush the flush function is going to perform CPU + // state change. + std::scoped_lock lk{manager->lock}; + if (Config::readbacks() && + manager->template IsRegionModified(offset, size)) { + return true; + } + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + return false; + }(); + if (should_flush) { + on_flush(); + } + }); + } + /// Call 'func' for each CPU modified range and unmark those pages as CPU modified - void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, auto&& func) { + void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, bool is_written, auto&& func, + auto&& on_upload) { IteratePages(query_cpu_range, query_size, - [&func](RegionManager* manager, u64 offset, size_t size) { + [&func, is_written](RegionManager* manager, u64 offset, size_t size) { + manager->lock.lock(); manager->template ForEachModifiedRange( manager->GetCpuAddr() + offset, size, func); + if (!is_written) { + manager->lock.unlock(); + } }); + on_upload(); + if (!is_written) { + return; + } + IteratePages(query_cpu_range, query_size, + [&func, is_written](RegionManager* manager, u64 offset, size_t size) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + manager->lock.unlock(); + }); } /// Call 'func' for each GPU modified range and unmark those pages as GPU modified @@ -71,6 +114,7 @@ public: void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, auto&& func) { IteratePages(query_cpu_range, query_size, [&func](RegionManager* manager, u64 offset, size_t size) { + std::scoped_lock lk{manager->lock}; manager->template ForEachModifiedRange( manager->GetCpuAddr() + offset, size, func); }); @@ -90,11 +134,11 @@ private: using FuncReturn = typename std::invoke_result::type; static constexpr bool BOOL_BREAK = std::is_same_v; std::size_t remaining_size{size}; - std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; - u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; + std::size_t page_index{cpu_address >> TRACKER_HIGHER_PAGE_BITS}; + u64 page_offset{cpu_address & TRACKER_HIGHER_PAGE_MASK}; while (remaining_size > 0) { const std::size_t copy_amount{ - std::min(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; + std::min(TRACKER_HIGHER_PAGE_SIZE - page_offset, remaining_size)}; auto* manager{top_tier[page_index]}; if (manager) { if constexpr (BOOL_BREAK) { @@ -123,7 +167,7 @@ private: } void CreateRegion(std::size_t page_index) { - const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; + const VAddr base_cpu_addr = page_index << TRACKER_HIGHER_PAGE_BITS; if (free_managers.empty()) { manager_pool.emplace_back(); auto& last_pool = manager_pool.back(); diff --git a/src/video_core/buffer_cache/region_definitions.h b/src/video_core/buffer_cache/region_definitions.h index 80c6afdc6..76e7ee263 100644 --- a/src/video_core/buffer_cache/region_definitions.h +++ b/src/video_core/buffer_cache/region_definitions.h @@ -3,26 +3,24 @@ #pragma once -#include #include "common/bit_array.h" #include "common/types.h" namespace VideoCore { -constexpr u64 PAGES_PER_WORD = 64; -constexpr u64 BYTES_PER_PAGE = 4_KB; +constexpr u64 TRACKER_PAGE_BITS = 12; // 4K pages +constexpr u64 TRACKER_BYTES_PER_PAGE = 1ULL << TRACKER_PAGE_BITS; -constexpr u64 HIGHER_PAGE_BITS = 22; -constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; -constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; -constexpr u64 NUM_REGION_PAGES = HIGHER_PAGE_SIZE / BYTES_PER_PAGE; +constexpr u64 TRACKER_HIGHER_PAGE_BITS = 24; // each region is 16MB +constexpr u64 TRACKER_HIGHER_PAGE_SIZE = 1ULL << TRACKER_HIGHER_PAGE_BITS; +constexpr u64 TRACKER_HIGHER_PAGE_MASK = TRACKER_HIGHER_PAGE_SIZE - 1ULL; +constexpr u64 NUM_PAGES_PER_REGION = TRACKER_HIGHER_PAGE_SIZE / TRACKER_BYTES_PER_PAGE; enum class Type { CPU, GPU, - Writeable, }; -using RegionBits = Common::BitArray; +using RegionBits = Common::BitArray; -} // namespace VideoCore \ No newline at end of file +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h index 07ffee36b..608b16fb3 100644 --- a/src/video_core/buffer_cache/region_manager.h +++ b/src/video_core/buffer_cache/region_manager.h @@ -3,9 +3,9 @@ #pragma once -#include -#include +#include "common/config.h" #include "common/div_ceil.h" +#include "common/logging/log.h" #ifdef __linux__ #include "common/adaptive_mutex.h" @@ -19,8 +19,14 @@ namespace VideoCore { +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP +using LockType = Common::AdaptiveMutex; +#else +using LockType = Common::SpinLock; +#endif + /** - * Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region. + * Allows tracking CPU and GPU modification of pages in a contigious 16MB virtual address region. * Information is stored in bitsets for spacial locality and fast update of single pages. */ class RegionManager { @@ -30,6 +36,7 @@ public: cpu.Fill(); gpu.Clear(); writeable.Fill(); + readable.Fill(); } explicit RegionManager() = default; @@ -47,29 +54,19 @@ public: template RegionBits& GetRegionBits() noexcept { - static_assert(type != Type::Writeable); if constexpr (type == Type::CPU) { return cpu; } else if constexpr (type == Type::GPU) { return gpu; - } else if constexpr (type == Type::Writeable) { - return writeable; - } else { - static_assert(false, "Invalid type"); } } template const RegionBits& GetRegionBits() const noexcept { - static_assert(type != Type::Writeable); if constexpr (type == Type::CPU) { return cpu; } else if constexpr (type == Type::GPU) { return gpu; - } else if constexpr (type == Type::Writeable) { - return writeable; - } else { - static_assert(false, "Invalid type"); } } @@ -83,13 +80,12 @@ public: void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { RENDERER_TRACE; const size_t offset = dirty_addr - cpu_addr; - const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; - const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); - if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE; + const size_t end_page = + Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE); + if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) { return; } - std::scoped_lock lk{lock}; - static_assert(type != Type::Writeable); RegionBits& bits = GetRegionBits(); if constexpr (enable) { @@ -98,7 +94,9 @@ public: bits.UnsetRange(start_page, end_page); } if constexpr (type == Type::CPU) { - UpdateProtection(); + UpdateProtection(); + } else if (Config::readbacks()) { + UpdateProtection(); } } @@ -114,32 +112,28 @@ public: void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { RENDERER_TRACE; const size_t offset = query_cpu_range - cpu_addr; - const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; - const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); - if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE; + const size_t end_page = + Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE); + if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) { return; } - std::scoped_lock lk{lock}; - static_assert(type != Type::Writeable); RegionBits& bits = GetRegionBits(); RegionBits mask(bits, start_page, end_page); - // TODO: this will not be needed once we handle readbacks - if constexpr (type == Type::GPU) { - mask &= ~writeable; - } - - for (const auto& [start, end] : mask) { - func(cpu_addr + start * BYTES_PER_PAGE, (end - start) * BYTES_PER_PAGE); - } - if constexpr (clear) { bits.UnsetRange(start_page, end_page); if constexpr (type == Type::CPU) { - UpdateProtection(); + UpdateProtection(); + } else if (Config::readbacks()) { + UpdateProtection(); } } + + for (const auto& [start, end] : mask) { + func(cpu_addr + start * TRACKER_BYTES_PER_PAGE, (end - start) * TRACKER_BYTES_PER_PAGE); + } } /** @@ -149,27 +143,22 @@ public: * @param size Size in bytes of the region to query for modifications */ template - [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) noexcept { RENDERER_TRACE; - const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; - const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); - if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE; + const size_t end_page = + Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE); + if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) { return false; } - // std::scoped_lock lk{lock}; // Is this needed? - static_assert(type != Type::Writeable); const RegionBits& bits = GetRegionBits(); RegionBits test(bits, start_page, end_page); - - // TODO: this will not be needed once we handle readbacks - if constexpr (type == Type::GPU) { - test &= ~writeable; - } - return test.Any(); } + LockType lock; + private: /** * Notify tracker about changes in the CPU tracking state of a word in the buffer @@ -178,31 +167,29 @@ private: * @param current_bits Current state of the word * @param new_bits New state of the word * - * @tparam add_to_tracker True when the tracker should start tracking the new pages + * @tparam track True when the tracker should start tracking the new pages */ - template + template void UpdateProtection() { RENDERER_TRACE; - RegionBits mask = cpu ^ writeable; - + RegionBits mask = is_read ? (~gpu ^ readable) : (cpu ^ writeable); if (mask.None()) { - return; // No changes to the CPU tracking state + return; } - - writeable = cpu; - tracker->UpdatePageWatchersForRegion(cpu_addr, mask); + if constexpr (is_read) { + readable = ~gpu; + } else { + writeable = cpu; + } + tracker->UpdatePageWatchersForRegion(cpu_addr, mask); } -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - Common::AdaptiveMutex lock; -#else - Common::SpinLock lock; -#endif PageManager* tracker; VAddr cpu_addr = 0; RegionBits cpu; RegionBits gpu; RegionBits writeable; + RegionBits readable; }; } // namespace VideoCore diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 145779070..63297bfdc 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -4,6 +4,7 @@ #include #include "common/assert.h" #include "common/debug.h" +#include "common/range_lock.h" #include "common/signal_context.h" #include "core/memory.h" #include "core/signals.h" @@ -12,6 +13,7 @@ #ifndef _WIN64 #include +#include "common/adaptive_mutex.h" #ifdef ENABLE_USERFAULTFD #include #include @@ -22,6 +24,7 @@ #endif #else #include +#include "common/spin_lock.h" #endif #ifdef __linux__ @@ -37,28 +40,52 @@ constexpr size_t PAGE_BITS = 12; struct PageManager::Impl { struct PageState { - u8 num_watchers{}; + u8 num_write_watchers : 7; + // At the moment only buffer cache can request read watchers. + // And buffers cannot overlap, thus only 1 can exist per page. + u8 num_read_watchers : 1; - Core::MemoryPermission Perm() const noexcept { - return num_watchers == 0 ? Core::MemoryPermission::ReadWrite - : Core::MemoryPermission::Read; + Core::MemoryPermission WritePerm() const noexcept { + return num_write_watchers == 0 ? Core::MemoryPermission::Write + : Core::MemoryPermission::None; } - template + Core::MemoryPermission ReadPerm() const noexcept { + return num_read_watchers == 0 ? Core::MemoryPermission::Read + : Core::MemoryPermission::None; + } + + Core::MemoryPermission Perms() const noexcept { + return ReadPerm() | WritePerm(); + } + + template u8 AddDelta() { - if constexpr (delta == 1) { - return ++num_watchers; - } else if constexpr (delta == -1) { - ASSERT_MSG(num_watchers > 0, "Not enough watchers"); - return --num_watchers; + if constexpr (is_read) { + if constexpr (delta == 1) { + return ++num_read_watchers; + } else if (delta == -1) { + ASSERT_MSG(num_read_watchers > 0, "Not enough watchers"); + return --num_read_watchers; + } else { + return num_read_watchers; + } } else { - return num_watchers; + if constexpr (delta == 1) { + return ++num_write_watchers; + } else if (delta == -1) { + ASSERT_MSG(num_write_watchers > 0, "Not enough watchers"); + return --num_write_watchers; + } else { + return num_write_watchers; + } } } }; static constexpr size_t ADDRESS_BITS = 40; static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS); + static constexpr size_t NUM_ADDRESS_LOCKS = NUM_ADDRESS_PAGES / PAGES_PER_LOCK; inline static Vulkan::Rasterizer* rasterizer; #ifdef ENABLE_USERFAULTFD Impl(Vulkan::Rasterizer* rasterizer_) { @@ -174,26 +201,38 @@ struct PageManager::Impl { RENDERER_TRACE; auto* memory = Core::Memory::Instance(); auto& impl = memory->GetAddressSpace(); + ASSERT_MSG(perms != Core::MemoryPermission::Write, + "Attempted to protect region as write-only which is not a valid permission"); impl.Protect(address, size, perms); } static bool GuestFaultSignalHandler(void* context, void* fault_address) { const auto addr = reinterpret_cast(fault_address); if (Common::IsWriteError(context)) { - return rasterizer->InvalidateMemory(addr, 1); + return rasterizer->InvalidateMemory(addr, 8); + } else { + return rasterizer->ReadMemory(addr, 8); } return false; } - #endif - template + + template void UpdatePageWatchers(VAddr addr, u64 size) { RENDERER_TRACE; size_t page = addr >> PAGE_BITS; - auto perms = cached_pages[page].Perm(); + const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); + + // Acquire locks for the range of pages + const auto lock_start = locks.begin() + (page / PAGES_PER_LOCK); + const auto lock_end = locks.begin() + Common::DivCeil(page_end, PAGES_PER_LOCK); + Common::RangeLockGuard lk(lock_start, lock_end); + + auto perms = cached_pages[page].Perms(); u64 range_begin = 0; u64 range_bytes = 0; + u64 potential_range_bytes = 0; const auto release_pending = [&] { if (range_bytes > 0) { @@ -201,13 +240,11 @@ struct PageManager::Impl { // Perform pending (un)protect action Protect(range_begin << PAGE_BITS, range_bytes, perms); range_bytes = 0; + potential_range_bytes = 0; } }; - std::scoped_lock lk(lock); - // Iterate requested pages - const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); const u64 aligned_addr = page << PAGE_BITS; const u64 aligned_end = page_end << PAGE_BITS; ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr), @@ -218,21 +255,26 @@ struct PageManager::Impl { PageState& state = cached_pages[page]; // Apply the change to the page state - const u8 new_count = state.AddDelta(); + const u8 new_count = state.AddDelta(); - if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { + if (auto new_perms = state.Perms(); new_perms != perms) [[unlikely]] { // If the protection changed add pending (un)protect action release_pending(); perms = new_perms; } else if (range_bytes != 0) { - // If the protection did not change, extend the current range - range_bytes += PAGE_SIZE; + // If the protection did not change, extend the potential range + potential_range_bytes += PAGE_SIZE; } // Only start a new range if the page must be (un)protected - if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { - range_begin = page; - range_bytes = PAGE_SIZE; + if ((new_count == 0 && !track) || (new_count == 1 && track)) { + if (range_bytes == 0) { + // Start a new potential range + range_begin = page; + potential_range_bytes = PAGE_SIZE; + } + // Extend current range up to potential range + range_bytes = potential_range_bytes; } } @@ -240,25 +282,26 @@ struct PageManager::Impl { release_pending(); } - template + template void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) { RENDERER_TRACE; auto start_range = mask.FirstRange(); auto end_range = mask.LastRange(); if (start_range.second == end_range.second) { - // Optimization: if all pages are contiguous, use the regular UpdatePageWatchers + // if all pages are contiguous, use the regular UpdatePageWatchers const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS); const u64 size = (start_range.second - start_range.first) << PAGE_BITS; - - UpdatePageWatchers(start_addr, size); - return; + return UpdatePageWatchers(start_addr, size); } size_t base_page = (base_addr >> PAGE_BITS); - auto perms = cached_pages[base_page + start_range.first].Perm(); + ASSERT(base_page % PAGES_PER_LOCK == 0); + std::scoped_lock lk(locks[base_page / PAGES_PER_LOCK]); + auto perms = cached_pages[base_page + start_range.first].Perms(); u64 range_begin = 0; u64 range_bytes = 0; + u64 potential_range_bytes = 0; const auto release_pending = [&] { if (range_bytes > 0) { @@ -266,26 +309,26 @@ struct PageManager::Impl { // Perform pending (un)protect action Protect((range_begin << PAGE_BITS), range_bytes, perms); range_bytes = 0; + potential_range_bytes = 0; } }; - std::scoped_lock lk(lock); - // Iterate pages for (size_t page = start_range.first; page < end_range.second; ++page) { PageState& state = cached_pages[base_page + page]; const bool update = mask.Get(page); // Apply the change to the page state - const u8 new_count = update ? state.AddDelta() : state.AddDelta<0>(); + const u8 new_count = + update ? state.AddDelta() : state.AddDelta<0, is_read>(); - if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { + if (auto new_perms = state.Perms(); new_perms != perms) [[unlikely]] { // If the protection changed add pending (un)protect action release_pending(); perms = new_perms; } else if (range_bytes != 0) { - // If the protection did not change, extend the current range - range_bytes += PAGE_SIZE; + // If the protection did not change, extend the potential range + potential_range_bytes += PAGE_SIZE; } // If the page is not being updated, skip it @@ -293,10 +336,15 @@ struct PageManager::Impl { continue; } - // Only start a new range if the page must be (un)protected - if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { - range_begin = base_page + page; - range_bytes = PAGE_SIZE; + // If the page must be (un)protected + if ((new_count == 0 && !track) || (new_count == 1 && track)) { + if (range_bytes == 0) { + // Start a new potential range + range_begin = base_page + page; + potential_range_bytes = PAGE_SIZE; + } + // Extend current rango up to potential range + range_bytes = potential_range_bytes; } } @@ -306,10 +354,11 @@ struct PageManager::Impl { std::array cached_pages{}; #ifdef __linux__ - Common::AdaptiveMutex lock; + using LockType = Common::AdaptiveMutex; #else - Common::SpinLock lock; + using LockType = Common::SpinLock; #endif + std::array locks{}; }; PageManager::PageManager(Vulkan::Rasterizer* rasterizer_) @@ -327,19 +376,23 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) { template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const { - impl->UpdatePageWatchers(addr, size); + impl->UpdatePageWatchers(addr, size); } -template +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const { - impl->UpdatePageWatchersForRegion(base_addr, mask); + impl->UpdatePageWatchersForRegion(base_addr, mask); } template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const; template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const; -template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, - RegionBits& mask) const; -template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, - RegionBits& mask) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; +template void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, + RegionBits& mask) const; } // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index 157b34984..4ca41cb43 100644 --- a/src/video_core/page_manager.h +++ b/src/video_core/page_manager.h @@ -15,8 +15,13 @@ class Rasterizer; namespace VideoCore { class PageManager { - static constexpr size_t PAGE_BITS = 12; - static constexpr size_t PAGE_SIZE = 1ULL << PAGE_BITS; + // Use the same page size as the tracker. + static constexpr size_t PAGE_BITS = TRACKER_PAGE_BITS; + static constexpr size_t PAGE_SIZE = TRACKER_BYTES_PER_PAGE; + + // Keep the lock granularity the same as region granularity. (since each regions has + // itself a lock) + static constexpr size_t PAGES_PER_LOCK = NUM_PAGES_PER_REGION; public: explicit PageManager(Vulkan::Rasterizer* rasterizer); @@ -32,9 +37,8 @@ public: template void UpdatePageWatchers(VAddr addr, u64 size) const; - /// Updates watches in the pages touching the specified region - /// using a mask. - template + /// Updates watches in the pages touching the specified region using a mask. + template void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const; /// Returns page aligned address. diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 61ddd3f05..237fa202d 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -341,7 +341,7 @@ bool Instance::CreateDevice() { const auto topology_list_restart_features = feature_chain.get(); const auto vk11_features = feature_chain.get(); - const auto vk12_features = feature_chain.get(); + vk12_features = feature_chain.get(); const auto vk13_features = feature_chain.get(); vk::StructureChain device_chain = { vk::DeviceCreateInfo{ @@ -387,6 +387,8 @@ bool Instance::CreateDevice() { .drawIndirectCount = vk12_features.drawIndirectCount, .storageBuffer8BitAccess = vk12_features.storageBuffer8BitAccess, .uniformAndStorageBuffer8BitAccess = vk12_features.uniformAndStorageBuffer8BitAccess, + .shaderBufferInt64Atomics = vk12_features.shaderBufferInt64Atomics, + .shaderSharedInt64Atomics = vk12_features.shaderSharedInt64Atomics, .shaderFloat16 = vk12_features.shaderFloat16, .shaderInt8 = vk12_features.shaderInt8, .scalarBlockLayout = vk12_features.scalarBlockLayout, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index 991bfb031..830b1d5c2 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -178,6 +178,16 @@ public: return shader_atomic_float2 && shader_atomic_float2_features.shaderImageFloat32AtomicMinMax; } + /// Returns true if 64-bit integer atomic operations can be used on buffers + bool IsBufferInt64AtomicsSupported() const { + return vk12_features.shaderBufferInt64Atomics; + } + + /// Returns true if 64-bit integer atomic operations can be used on shared memory + bool IsSharedInt64AtomicsSupported() const { + return vk12_features.shaderSharedInt64Atomics; + } + /// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported. bool IsWorkgroupMemoryExplicitLayoutSupported() const { return workgroup_memory_explicit_layout && @@ -314,11 +324,21 @@ public: return properties.limits.maxViewportDimensions[0]; } - /// Returns the maximum viewport height. + /// Returns the maximum viewport height. u32 GetMaxViewportHeight() const { return properties.limits.maxViewportDimensions[1]; } + /// Returns the maximum render area width. + u32 GetMaxFramebufferWidth() const { + return properties.limits.maxFramebufferWidth; + } + + /// Returns the maximum render area height. + u32 GetMaxFramebufferHeight() const { + return properties.limits.maxFramebufferHeight; + } + /// Returns the sample count flags supported by framebuffers. vk::SampleCountFlags GetFramebufferSampleCounts() const { return properties.limits.framebufferColorSampleCounts & @@ -358,6 +378,7 @@ private: vk::PhysicalDeviceVulkan12Properties vk12_props; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDeviceFeatures features; + vk::PhysicalDeviceVulkan12Features vk12_features; vk::PhysicalDevicePortabilitySubsetFeaturesKHR portability_features; vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features; vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index c7ad5b331..0e54c46d0 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -220,12 +220,15 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .supports_buffer_fp32_atomic_min_max = instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(), .supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(), + .supports_buffer_int64_atomics = instance_.IsBufferInt64AtomicsSupported(), + .supports_shared_int64_atomics = instance_.IsSharedInt64AtomicsSupported(), .supports_workgroup_explicit_memory_layout = instance_.IsWorkgroupMemoryExplicitLayoutSupported(), .needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() && instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary || instance.GetDriverID() == vk::DriverId::eMoltenvk, + .needs_buffer_offsets = instance.StorageMinAlignment() > 4, // When binding a UBO, we calculate its size considering the offset in the larger buffer // cache underlying resource. In some cases, it may produce sizes exceeding the system // maximum allowed UBO range, so we need to reduce the threshold to prevent issues. diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9dea5ceea..5d0a14ce3 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -36,7 +36,7 @@ static Shader::PushData MakeUserData(const AmdGpu::Liverpool::Regs& regs) { Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, page_manager{this}, - buffer_cache{instance, scheduler, *this, liverpool_, texture_cache, page_manager}, + buffer_cache{instance, scheduler, liverpool_, texture_cache, page_manager}, texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { if (!Config::nullGpu()) { @@ -113,6 +113,8 @@ RenderState Rasterizer::PrepareRenderState(u32 mrt_mask) { // Prefetch color and depth buffers to let texture cache handle possible overlaps with bound // textures (e.g. mipgen) RenderState state; + state.width = instance.GetMaxFramebufferWidth(); + state.height = instance.GetMaxFramebufferHeight(); cb_descs.clear(); db_desc.reset(); @@ -272,6 +274,8 @@ void Rasterizer::EliminateFastClear() { void Rasterizer::Draw(bool is_indexed, u32 index_offset) { RENDERER_TRACE; + scheduler.PopPendingOperations(); + if (!FilterDraw()) { return; } @@ -317,6 +321,8 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3 u32 max_count, VAddr count_address) { RENDERER_TRACE; + scheduler.PopPendingOperations(); + if (!FilterDraw()) { return; } @@ -380,6 +386,8 @@ void Rasterizer::DrawIndirect(bool is_indexed, VAddr arg_address, u32 offset, u3 void Rasterizer::DispatchDirect() { RENDERER_TRACE; + scheduler.PopPendingOperations(); + const auto& cs_program = liverpool->GetCsRegs(); const ComputePipeline* pipeline = pipeline_cache.GetComputePipeline(); if (!pipeline) { @@ -407,6 +415,8 @@ void Rasterizer::DispatchDirect() { void Rasterizer::DispatchIndirect(VAddr address, u32 offset, u32 size) { RENDERER_TRACE; + scheduler.PopPendingOperations(); + const auto& cs_program = liverpool->GetCsRegs(); const ComputePipeline* pipeline = pipeline_cache.GetComputePipeline(); if (!pipeline) { @@ -439,11 +449,12 @@ void Rasterizer::Finish() { scheduler.Finish(); } -void Rasterizer::ProcessFaults() { +void Rasterizer::EndCommandList() { if (fault_process_pending) { fault_process_pending = false; buffer_cache.ProcessFaultBuffer(); } + texture_cache.ProcessDownloadImages(); } bool Rasterizer::BindResources(const Pipeline* pipeline) { @@ -468,17 +479,12 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { stage->PushUd(binding, push_data); BindBuffers(*stage, binding, push_data); BindTextures(*stage, binding); - - uses_dma |= stage->dma_types != Shader::IR::Type::Void; + uses_dma |= stage->uses_dma; } - pipeline->BindResources(set_writes, buffer_barriers, push_data); - - if (uses_dma && !fault_process_pending) { + if (uses_dma) { // We only use fault buffer for DMA right now. { - // TODO: GPU might have written to memory (for example with EVENT_WRITE_EOP) - // we need to account for that and synchronize. Common::RecursiveSharedLock lock{mapped_ranges_mutex}; for (auto& range : mapped_ranges) { buffer_cache.SynchronizeBuffersInRange(range.lower(), @@ -490,6 +496,8 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { fault_process_pending |= uses_dma; + pipeline->BindResources(set_writes, buffer_barriers, push_data); + return true; } @@ -652,8 +660,7 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin if (instance.IsNullDescriptorSupported()) { image_infos.emplace_back(VK_NULL_HANDLE, VK_NULL_HANDLE, vk::ImageLayout::eGeneral); } else { - auto& null_image_view = - texture_cache.FindTexture(VideoCore::NULL_IMAGE_ID, desc.view_info); + auto& null_image_view = texture_cache.FindTexture(VideoCore::NULL_IMAGE_ID, desc); image_infos.emplace_back(VK_NULL_HANDLE, *null_image_view.image_view, vk::ImageLayout::eGeneral); } @@ -667,7 +674,7 @@ void Rasterizer::BindTextures(const Shader::Info& stage, Shader::Backend::Bindin bound_images.emplace_back(image_id); auto& image = texture_cache.GetImage(image_id); - auto& image_view = texture_cache.FindTexture(image_id, desc.view_info); + auto& image_view = texture_cache.FindTexture(image_id, desc); if (image.binding.force_general || image.binding.is_target) { image.Transit(vk::ImageLayout::eGeneral, @@ -947,6 +954,10 @@ void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, boo buffer_cache.InlineData(address, value, num_bytes, is_gds); } +void Rasterizer::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) { + buffer_cache.CopyBuffer(dst, src, num_bytes, dst_gds, src_gds); +} + u32 Rasterizer::ReadDataFromGds(u32 gds_offset) { auto* gds_buf = buffer_cache.GetGdsBuffer(); u32 value; @@ -959,11 +970,20 @@ bool Rasterizer::InvalidateMemory(VAddr addr, u64 size) { // Not GPU mapped memory, can skip invalidation logic entirely. return false; } - buffer_cache.InvalidateMemory(addr, size, false); + buffer_cache.InvalidateMemory(addr, size); texture_cache.InvalidateMemory(addr, size); return true; } +bool Rasterizer::ReadMemory(VAddr addr, u64 size) { + if (!IsMapped(addr, size)) { + // Not GPU mapped memory, can skip invalidation logic entirely. + return false; + } + buffer_cache.ReadMemory(addr, size); + return true; +} + bool Rasterizer::IsMapped(VAddr addr, u64 size) { if (size == 0) { // There is no memory, so not mapped. @@ -984,7 +1004,7 @@ void Rasterizer::MapMemory(VAddr addr, u64 size) { } void Rasterizer::UnmapMemory(VAddr addr, u64 size) { - buffer_cache.InvalidateMemory(addr, size, true); + buffer_cache.InvalidateMemory(addr, size); texture_cache.UnmapMemory(addr, size); page_manager.OnGpuUnmap(addr, size); { diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index fb9ca4bbe..1e1680258 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -5,6 +5,7 @@ #include #include "common/recursive_lock.h" +#include "common/shared_first_mutex.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h" @@ -56,8 +57,10 @@ public: bool from_guest = false); void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); + void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds); u32 ReadDataFromGds(u32 gsd_offset); bool InvalidateMemory(VAddr addr, u64 size); + bool ReadMemory(VAddr addr, u64 size); bool IsMapped(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size); void UnmapMemory(VAddr addr, u64 size); @@ -65,7 +68,7 @@ public: void CpSync(); u64 Flush(); void Finish(); - void ProcessFaults(); + void EndCommandList(); PipelineCache& GetPipelineCache() { return pipeline_cache; @@ -120,7 +123,7 @@ private: AmdGpu::Liverpool* liverpool; Core::MemoryManager* memory; boost::icl::interval_set mapped_ranges; - std::shared_mutex mapped_ranges_mutex; + Common::SharedFirstMutex mapped_ranges_mutex; PipelineCache pipeline_cache; boost::container::static_vector< diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index e75a69924..ac645c9ce 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -34,16 +34,11 @@ void Scheduler::BeginRendering(const RenderState& new_state) { is_rendering = true; render_state = new_state; - const auto width = - render_state.width != std::numeric_limits::max() ? render_state.width : 1; - const auto height = - render_state.height != std::numeric_limits::max() ? render_state.height : 1; - const vk::RenderingInfo rendering_info = { .renderArea = { .offset = {0, 0}, - .extent = {width, height}, + .extent = {render_state.width, render_state.height}, }, .layerCount = 1, .colorAttachmentCount = render_state.num_color_attachments, @@ -101,6 +96,14 @@ void Scheduler::Wait(u64 tick) { } } +void Scheduler::PopPendingOperations() { + master_semaphore.Refresh(); + while (!pending_ops.empty() && master_semaphore.IsFree(pending_ops.front().gpu_tick)) { + pending_ops.front().callback(); + pending_ops.pop(); + } +} + void Scheduler::AllocateWorkerCommandBuffers() { const vk::CommandBufferBeginInfo begin_info = { .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit, @@ -175,10 +178,7 @@ void Scheduler::SubmitExecution(SubmitInfo& info) { AllocateWorkerCommandBuffers(); // Apply pending operations - while (!pending_ops.empty() && IsFree(pending_ops.front().gpu_tick)) { - pending_ops.front().callback(); - pending_ops.pop(); - } + PopPendingOperations(); } void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) { diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h index 8ddf00f6a..b5678edbc 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -26,8 +26,8 @@ struct RenderState { u32 num_color_attachments{}; bool has_depth{}; bool has_stencil{}; - u32 width = std::numeric_limits::max(); - u32 height = std::numeric_limits::max(); + u32 width{}; + u32 height{}; bool operator==(const RenderState& other) const noexcept { return std::memcmp(this, &other, sizeof(RenderState)) == 0; @@ -317,6 +317,9 @@ public: /// Waits for the given tick to trigger on the GPU. void Wait(u64 tick); + /// Attempts to execute operations whose tick the GPU has caught up with. + void PopPendingOperations(); + /// Starts a new rendering scope with provided state. void BeginRendering(const RenderState& new_state); @@ -344,7 +347,11 @@ public: } /// Returns true when a tick has been triggered by the GPU. - [[nodiscard]] bool IsFree(u64 tick) const noexcept { + [[nodiscard]] bool IsFree(u64 tick) noexcept { + if (master_semaphore.IsFree(tick)) { + return true; + } + master_semaphore.Refresh(); return master_semaphore.IsFree(tick); } diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a50601af6..723b95892 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -5,7 +5,9 @@ #include #include "common/assert.h" +#include "common/config.h" #include "common/debug.h" +#include "core/memory.h" #include "video_core/buffer_cache/buffer_cache.h" #include "video_core/page_manager.h" #include "video_core/renderer_vulkan/vk_instance.h" @@ -58,6 +60,50 @@ ImageId TextureCache::GetNullImage(const vk::Format format) { return null_id; } +void TextureCache::ProcessDownloadImages() { + for (const ImageId image_id : download_images) { + DownloadImageMemory(image_id); + } + download_images.clear(); +} + +void TextureCache::DownloadImageMemory(ImageId image_id) { + Image& image = slot_images[image_id]; + if (False(image.flags & ImageFlagBits::GpuModified)) { + return; + } + auto& download_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::Download); + const u32 download_size = image.info.pitch * image.info.size.height * + image.info.resources.layers * (image.info.num_bits / 8); + ASSERT(download_size <= image.info.guest_size); + const auto [download, offset] = download_buffer.Map(download_size); + download_buffer.Commit(); + const vk::BufferImageCopy image_download = { + .bufferOffset = offset, + .bufferRowLength = image.info.pitch, + .bufferImageHeight = image.info.size.height, + .imageSubresource = + { + .aspectMask = image.info.IsDepthStencil() ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eColor, + .mipLevel = 0, + .baseArrayLayer = 0, + .layerCount = image.info.resources.layers, + }, + .imageOffset = {0, 0, 0}, + .imageExtent = {image.info.size.width, image.info.size.height, 1}, + }; + scheduler.EndRendering(); + const auto cmdbuf = scheduler.CommandBuffer(); + image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}); + cmdbuf.copyImageToBuffer(image.image, vk::ImageLayout::eTransferSrcOptimal, + download_buffer.Handle(), image_download); + scheduler.DeferOperation([device_addr = image.info.guest_address, download, download_size] { + auto* memory = Core::Memory::Instance(); + memory->TryWriteBacking(std::bit_cast(device_addr), download, download_size); + }); +} + void TextureCache::MarkAsMaybeDirty(ImageId image_id, Image& image) { if (image.hash == 0) { // Initialize hash @@ -169,7 +215,7 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Bindi if (recreate) { auto new_info = requested_info; - new_info.resources = std::min(requested_info.resources, cache_image.info.resources); + new_info.resources = std::max(requested_info.resources, cache_image.info.resources); const auto new_image_id = slot_images.insert(instance, scheduler, new_info); RegisterImage(new_image_id); @@ -437,16 +483,27 @@ ImageView& TextureCache::RegisterImageView(ImageId image_id, const ImageViewInfo return slot_image_views[view_id]; } -ImageView& TextureCache::FindTexture(ImageId image_id, const ImageViewInfo& view_info) { +ImageView& TextureCache::FindTexture(ImageId image_id, const BaseDesc& desc) { Image& image = slot_images[image_id]; + if (desc.type == BindingType::Storage) { + image.flags |= ImageFlagBits::GpuModified; + if (Config::readbackLinearImages() && + image.info.tiling_mode == AmdGpu::TilingMode::Display_Linear) { + download_images.emplace(image_id); + } + } UpdateImage(image_id); - return RegisterImageView(image_id, view_info); + return RegisterImageView(image_id, desc.view_info); } ImageView& TextureCache::FindRenderTarget(BaseDesc& desc) { const ImageId image_id = FindImage(desc); Image& image = slot_images[image_id]; image.flags |= ImageFlagBits::GpuModified; + if (Config::readbackLinearImages() && + image.info.tiling_mode == AmdGpu::TilingMode::Display_Linear) { + download_images.emplace(image_id); + } image.usage.render_target = 1u; UpdateImage(image_id); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 87228b84f..ff8ffb61c 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -3,6 +3,7 @@ #pragma once +#include #include #include @@ -105,11 +106,14 @@ public: /// Evicts any images that overlap the unmapped range. void UnmapMemory(VAddr cpu_addr, size_t size); + /// Schedules a copy of pending images for download back to CPU memory. + void ProcessDownloadImages(); + /// Retrieves the image handle of the image with the provided attributes. [[nodiscard]] ImageId FindImage(BaseDesc& desc, FindFlags flags = {}); /// Retrieves an image view with the properties of the specified image id. - [[nodiscard]] ImageView& FindTexture(ImageId image_id, const ImageViewInfo& view_info); + [[nodiscard]] ImageView& FindTexture(ImageId image_id, const BaseDesc& desc); /// Retrieves the render target with specified properties [[nodiscard]] ImageView& FindRenderTarget(BaseDesc& desc); @@ -252,6 +256,9 @@ private: /// Gets or creates a null image for a particular format. ImageId GetNullImage(vk::Format format); + /// Copies image memory back to CPU. + void DownloadImageMemory(ImageId image_id); + /// Create an image from the given parameters [[nodiscard]] ImageId InsertImage(const ImageInfo& info, VAddr cpu_addr); @@ -293,6 +300,7 @@ private: Common::SlotVector slot_image_views; tsl::robin_map samplers; tsl::robin_map null_images; + std::unordered_set download_images; PageTable page_table; std::mutex mutex;