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/.github/workflows/build.yml b/.github/workflows/build.yml index 588236b14..b098896f5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -224,7 +224,10 @@ jobs: arch: clang_64 archives: qtbase qttools modules: qtmultimedia - + + - name: Workaround Qt <=6.9.1 issue + run: sed -i '' '/target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_agl_fw_path})/d' ${{env.QT_ROOT_DIR}}/lib/cmake/Qt6/FindWrapOpenGL.cmake + - name: Cache CMake Configuration uses: actions/cache@v4 env: 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 37f52c091..90189fbd9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -214,6 +214,10 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/src/common/scm_rev.cpp.in" "${CMAKE_ message("end git things, remote: ${GIT_REMOTE_NAME}, branch: ${GIT_BRANCH}") +if(WIN32 AND ENABLE_QT_GUI AND NOT CMAKE_PREFIX_PATH) + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/DetectQtInstallation.cmake") +endif () + list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") find_package(Boost 1.84.0 CONFIG) find_package(FFmpeg 5.1.2 MODULE) @@ -655,6 +659,7 @@ set(COMMON src/common/logging/backend.cpp src/common/arch.h src/common/assert.cpp src/common/assert.h + src/common/bit_array.h src/common/bit_field.h src/common/bounded_threadsafe_queue.h src/common/concepts.h @@ -680,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 @@ -915,9 +922,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/buffer_cache/buffer.h src/video_core/buffer_cache/buffer_cache.cpp src/video_core/buffer_cache/buffer_cache.h - src/video_core/buffer_cache/memory_tracker_base.h + src/video_core/buffer_cache/memory_tracker.h src/video_core/buffer_cache/range_set.h - src/video_core/buffer_cache/word_manager.h + src/video_core/buffer_cache/region_definitions.h + src/video_core/buffer_cache/region_manager.h src/video_core/renderer_vulkan/liverpool_to_vk.cpp src/video_core/renderer_vulkan/liverpool_to_vk.h src/video_core/renderer_vulkan/vk_common.cpp @@ -954,6 +962,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/renderer_vulkan/host_passes/fsr_pass.h src/video_core/renderer_vulkan/host_passes/pp_pass.cpp src/video_core/renderer_vulkan/host_passes/pp_pass.h + src/video_core/texture_cache/blit_helper.cpp + src/video_core/texture_cache/blit_helper.h + src/video_core/texture_cache/host_compatibility.cpp + src/video_core/texture_cache/host_compatibility.h src/video_core/texture_cache/image.cpp src/video_core/texture_cache/image.h src/video_core/texture_cache/image_info.cpp @@ -967,8 +979,6 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp src/video_core/texture_cache/tile_manager.cpp src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/types.h - src/video_core/texture_cache/host_compatibility.cpp - src/video_core/texture_cache/host_compatibility.h src/video_core/page_manager.cpp src/video_core/page_manager.h src/video_core/multi_level_page_table.h @@ -1061,6 +1071,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 new file mode 100644 index 000000000..bd1aba36e --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,62 @@ +{ + "version": 9, + "cmakeMinimumRequired": { + "major": 3, + "minor": 30, + "patch": 0 + }, + "include": ["CMake${hostSystemName}Presets.json"], + "configurePresets": [ + { + "name": "x64-Clang-Debug", + "displayName": "Clang x64 Debug", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-Clang-Debug-Qt", + "displayName": "Clang x64 Debug with Qt", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_QT_GUI": "ON" + } + }, + { + "name": "x64-Clang-Release", + "displayName": "Clang x64 Release", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x64-Clang-Release-Qt", + "displayName": "Clang x64 Release with Qt", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "ENABLE_QT_GUI": "ON" + } + }, + { + "name": "x64-Clang-RelWithDebInfo", + "displayName": "Clang x64 RelWithDebInfo", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo" + } + }, + { + "name": "x64-Clang-RelWithDebInfo-Qt", + "displayName": "Clang x64 RelWithDebInfo with Qt", + "inherits": ["x64-Clang-Base"], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "RelWithDebInfo", + "ENABLE_QT_GUI": "ON" + } + } + ] +} \ No newline at end of file diff --git a/CMakeSettings.json b/CMakeSettings.json index bb522fcfc..e1ed36887 100644 --- a/CMakeSettings.json +++ b/CMakeSettings.json @@ -12,6 +12,18 @@ "inheritEnvironments": [ "clang_cl_x64_x64" ], "intelliSenseMode": "windows-clang-x64" }, + { + "name": "x64-Clang-Release-Qt", + "generator": "Ninja", + "configurationType": "Release", + "buildRoot": "${projectDir}\\Build\\${name}", + "installRoot": "${projectDir}\\Install\\${name}", + "cmakeCommandArgs": "-DENABLE_QT_GUI=ON", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "intelliSenseMode": "windows-clang-x64" + }, { "name": "x64-Clang-Debug", "generator": "Ninja", @@ -24,6 +36,18 @@ "inheritEnvironments": [ "clang_cl_x64_x64" ], "intelliSenseMode": "windows-clang-x64" }, + { + "name": "x64-Clang-Debug-Qt", + "generator": "Ninja", + "configurationType": "Debug", + "buildRoot": "${projectDir}\\Build\\${name}", + "installRoot": "${projectDir}\\Install\\${name}", + "cmakeCommandArgs": "-DENABLE_QT_GUI=ON", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "intelliSenseMode": "windows-clang-x64" + }, { "name": "x64-Clang-RelWithDebInfo", "generator": "Ninja", @@ -35,6 +59,18 @@ "ctestCommandArgs": "", "inheritEnvironments": [ "clang_cl_x64_x64" ], "intelliSenseMode": "windows-clang-x64" + }, + { + "name": "x64-Clang-RelWithDebInfo-Qt", + "generator": "Ninja", + "configurationType": "RelWithDebInfo", + "buildRoot": "${projectDir}\\Build\\${name}", + "installRoot": "${projectDir}\\Install\\${name}", + "cmakeCommandArgs": "-DENABLE_QT_GUI=ON", + "buildCommandArgs": "", + "ctestCommandArgs": "", + "inheritEnvironments": [ "clang_cl_x64_x64" ], + "intelliSenseMode": "windows-clang-x64" } ] } \ 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 662987611..4b1c94d21 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -5,10 +5,13 @@ path = [ "REUSE.toml", "crowdin.yml", "CMakeSettings.json", + "CMakeLinuxPresets.json", + "CMakeWindowsPresets.json", + "CMakePresets.json", ".github/FUNDING.yml", ".github/shadps4.png", - ".github/workflows/scripts/update_translation.sh", - ".github/workflows/update_translation.yml", + ".github/workflows/scripts/update_translation.sh", + ".github/workflows/update_translation.yml", ".gitmodules", "dist/MacOSBundleInfo.plist.in", "dist/net.shadps4.shadPS4.desktop", @@ -29,6 +32,7 @@ path = [ "src/images/discord.png", "src/images/dump_icon.png", "src/images/exit_icon.png", + "src/images/favorite_icon.png", "src/images/file_icon.png", "src/images/trophy_icon.png", "src/images/flag_china.png", @@ -71,7 +75,7 @@ path = [ "src/images/youtube.svg", "src/shadps4.qrc", "src/shadps4.rc", - "src/qt_gui/translations/update_translation.sh", + "src/qt_gui/translations/update_translation.sh", ] precedence = "aggregate" SPDX-FileCopyrightText = "shadPS4 Emulator Project" diff --git a/cmake/DetectQtInstallation.cmake b/cmake/DetectQtInstallation.cmake new file mode 100644 index 000000000..e95e8980f --- /dev/null +++ b/cmake/DetectQtInstallation.cmake @@ -0,0 +1,14 @@ +# 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() + diff --git a/documents/building-linux.md b/documents/building-linux.md index 61d067881..00d73280e 100644 --- a/documents/building-linux.md +++ b/documents/building-linux.md @@ -74,6 +74,7 @@ and install the dependencies on that container as cited above. This option is **highly recommended** for distributions with immutable/atomic filesystems (example: Fedora Kinoite, SteamOS). ### Cloning +The project uses submodules to manage dependencies, and they need to be initialized before you can build the project. To achieve this, make sure you've cloned the repository with the --recursive flag ```bash git clone --recursive https://github.com/shadps4-emu/shadPS4.git 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 new file mode 100644 index 000000000..0ab464390 --- /dev/null +++ b/src/common/bit_array.h @@ -0,0 +1,406 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/types.h" + +#ifdef __AVX2__ +#define BIT_ARRAY_USE_AVX +#include +#endif + +namespace Common { + +template +class BitArray { + static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits."); + + static constexpr size_t BITS_PER_WORD = 64; + static constexpr size_t WORD_COUNT = N / BITS_PER_WORD; + static constexpr size_t WORDS_PER_AVX = 4; + static constexpr size_t AVX_WORD_COUNT = WORD_COUNT / WORDS_PER_AVX; + +public: + using Range = std::pair; + + class Iterator { + public: + explicit Iterator(const BitArray& bit_array_, u64 start) : bit_array(bit_array_) { + range = bit_array.FirstRangeFrom(start); + } + + Iterator& operator++() { + range = bit_array.FirstRangeFrom(range.second); + return *this; + } + + bool operator==(const Iterator& other) const { + return range == other.range; + } + + bool operator!=(const Iterator& other) const { + return !(*this == other); + } + + const Range& operator*() const { + return range; + } + + const Range* operator->() const { + return ⦥ + } + + private: + const BitArray& bit_array; + Range range; + }; + + using const_iterator = Iterator; + using iterator_category = std::forward_iterator_tag; + using value_type = Range; + using difference_type = std::ptrdiff_t; + using pointer = const Range*; + using reference = const Range&; + + BitArray() = default; + BitArray(const BitArray& other) = default; + BitArray& operator=(const BitArray& other) = default; + BitArray(BitArray&& other) noexcept = default; + BitArray& operator=(BitArray&& other) noexcept = default; + ~BitArray() = default; + + BitArray(const BitArray& other, size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + const size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = ~((1ULL << start_bit) - 1); + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1; + if (first_word == last_word) { + data[first_word] = other.data[first_word] & (start_mask & end_mask); + } else { + data[first_word] = other.data[first_word] & start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&other.data[i])); + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), current); + } +#endif + for (; i < last_word; ++i) { + data[i] = other.data[i]; + } + data[last_word] = other.data[last_word] & end_mask; + } + } + + BitArray(const BitArray& other, const Range& range) + : BitArray(other, range.first, range.second) {} + + const_iterator begin() const { + return Iterator(*this, 0); + } + const_iterator end() const { + return Iterator(*this, N); + } + + inline constexpr void Set(size_t idx) { + data[idx / BITS_PER_WORD] |= (1ULL << (idx % BITS_PER_WORD)); + } + + inline constexpr void Unset(size_t idx) { + data[idx / BITS_PER_WORD] &= ~(1ULL << (idx % BITS_PER_WORD)); + } + + inline constexpr bool Get(size_t idx) const { + return (data[idx / BITS_PER_WORD] & (1ULL << (idx % BITS_PER_WORD))) != 0; + } + + inline void SetRange(size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + const size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = ~((1ULL << start_bit) - 1); + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1; + if (first_word == last_word) { + data[first_word] |= start_mask & end_mask; + } else { + data[first_word] |= start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + const __m256i value = _mm256_set1_epi64x(-1); + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value); + } +#endif + for (; i < last_word; ++i) { + data[i] = ~0ULL; + } + data[last_word] |= end_mask; + } + } + + inline void UnsetRange(size_t start, size_t end) { + if (start >= end || end > N) { + return; + } + size_t first_word = start / BITS_PER_WORD; + const size_t last_word = (end - 1) / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + const u64 start_mask = (1ULL << start_bit) - 1; + const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? 0ULL : ~((1ULL << (end_bit + 1)) - 1); + if (first_word == last_word) { + data[first_word] &= start_mask | end_mask; + } else { + data[first_word] &= start_mask; + size_t i = first_word + 1; +#ifdef BIT_ARRAY_USE_AVX + const __m256i value = _mm256_setzero_si256(); + for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) { + _mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value); + } +#endif + for (; i < last_word; ++i) { + data[i] = 0ULL; + } + data[last_word] &= end_mask; + } + } + + inline constexpr void SetRange(const Range& range) { + SetRange(range.first, range.second); + } + + inline constexpr void UnsetRange(const Range& range) { + UnsetRange(range.first, range.second); + } + + inline constexpr void Clear() { + data.fill(0); + } + + inline constexpr void Fill() { + data.fill(~0ULL); + } + + inline constexpr bool None() const { + u64 result = 0; + for (const auto& word : data) { + result |= word; + } + return result == 0; + } + + inline constexpr bool Any() const { + return !None(); + } + + Range FirstRangeFrom(size_t start) const { + if (start >= N) { + return {N, N}; + } + const auto find_end_bit = [&](size_t word) { +#ifdef BIT_ARRAY_USE_AVX + const __m256i all_one = _mm256_set1_epi64x(-1); + for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word])); + const __m256i cmp = _mm256_cmpeq_epi64(current, all_one); + if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { + break; + } + } +#endif + for (; word < WORD_COUNT; ++word) { + if (data[word] != ~0ULL) { + return (word * BITS_PER_WORD) + std::countr_one(data[word]); + } + } + return N; + }; + + const auto word_bits = [&](size_t index, u64 word) { + const int empty_bits = std::countr_zero(word); + const int ones_count = std::countr_one(word >> empty_bits); + const size_t start_bit = index * BITS_PER_WORD + empty_bits; + if (ones_count + empty_bits < BITS_PER_WORD) { + return Range{start_bit, start_bit + ones_count}; + } + return Range{start_bit, find_end_bit(index + 1)}; + }; + + const size_t start_word = start / BITS_PER_WORD; + const size_t start_bit = start % BITS_PER_WORD; + const u64 masked_first = data[start_word] & (~((1ULL << start_bit) - 1)); + if (masked_first) { + return word_bits(start_word, masked_first); + } + + size_t word = start_word + 1; +#ifdef BIT_ARRAY_USE_AVX + for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word])); + if (!_mm256_testz_si256(current, current)) { + break; + } + } +#endif + for (; word < WORD_COUNT; ++word) { + if (data[word] != 0) { + return word_bits(word, data[word]); + } + } + return {N, N}; + } + + inline constexpr Range FirstRange() const { + return FirstRangeFrom(0); + } + + Range LastRangeFrom(size_t end) const { + if (end == 0) { + return {0, 0}; + } + if (end > N) { + end = N; + } + const auto find_start_bit = [&](size_t word) { +#ifdef BIT_ARRAY_USE_AVX + const __m256i all_zero = _mm256_setzero_si256(); + for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) { + const __m256i current = _mm256_loadu_si256( + reinterpret_cast(&data[word - WORDS_PER_AVX])); + const __m256i cmp = _mm256_cmpeq_epi64(current, all_zero); + if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) { + break; + } + } +#endif + for (; word > 0; --word) { + if (data[word - 1] != ~0ULL) { + return word * BITS_PER_WORD - std::countl_one(data[word - 1]); + } + } + return size_t(0); + }; + const auto word_bits = [&](size_t index, u64 word) { + const int empty_bits = std::countl_zero(word); + const int ones_count = std::countl_one(word << empty_bits); + const size_t end_bit = index * BITS_PER_WORD - empty_bits; + if (empty_bits + ones_count < BITS_PER_WORD) { + return Range{end_bit - ones_count, end_bit}; + } + return Range{find_start_bit(index - 1), end_bit}; + }; + const size_t end_word = ((end - 1) / BITS_PER_WORD) + 1; + const size_t end_bit = (end - 1) % BITS_PER_WORD; + u64 masked_last = data[end_word - 1]; + if (end_bit < BITS_PER_WORD - 1) { + masked_last &= (1ULL << (end_bit + 1)) - 1; + } + if (masked_last) { + return word_bits(end_word, masked_last); + } + size_t word = end_word - 1; +#ifdef BIT_ARRAY_USE_AVX + for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) { + const __m256i current = + _mm256_loadu_si256(reinterpret_cast(&data[word - WORDS_PER_AVX])); + if (!_mm256_testz_si256(current, current)) { + break; + } + } +#endif + for (; word > 0; --word) { + if (data[word - 1] != 0) { + return word_bits(word, data[word - 1]); + } + } + return {0, 0}; + } + + inline constexpr Range LastRange() const { + return LastRangeFrom(N); + } + + inline constexpr size_t Size() const { + return N; + } + + inline constexpr BitArray& operator|=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] |= other.data[i]; + } + return *this; + } + + inline constexpr BitArray& operator&=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] &= other.data[i]; + } + return *this; + } + + inline constexpr BitArray& operator^=(const BitArray& other) { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] ^= other.data[i]; + } + return *this; + } + + inline constexpr BitArray operator|(const BitArray& other) const { + BitArray result = *this; + result |= other; + return result; + } + + inline constexpr BitArray operator&(const BitArray& other) const { + BitArray result = *this; + result &= other; + return result; + } + + inline constexpr BitArray operator^(const BitArray& other) const { + BitArray result = *this; + result ^= other; + return result; + } + + inline constexpr BitArray operator~() const { + BitArray result = *this; + for (size_t i = 0; i < WORD_COUNT; ++i) { + result.data[i] = ~result.data[i]; + } + return result; + } + + inline constexpr bool operator==(const BitArray& other) const { + u64 result = 0; + for (size_t i = 0; i < WORD_COUNT; ++i) { + result |= data[i] ^ other.data[i]; + } + return result == 0; + } + + inline constexpr bool operator!=(const BitArray& other) const { + return !(*this == other); + } + +private: + std::array data{}; +}; + +} // namespace Common diff --git a/src/common/config.cpp b/src/common/config.cpp index d8f46a17d..4a764a4c6 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -42,7 +42,6 @@ static std::string logFilter; static std::string logType = "sync"; static std::string userName = "shadPS4"; static std::string chooseHomeTab; -static std::string backButtonBehavior = "left"; static bool useSpecialPad = false; static int specialPadClass = 1; static bool isMotionControlsEnabled = true; @@ -52,6 +51,8 @@ static bool isShowSplash = false; static std::string isSideTrophy = "right"; static bool isNullGpu = false; static bool shouldCopyGPUBuffers = false; +static bool readbacksEnabled = false; +static bool directMemoryAccessEnabled = false; static bool shouldDumpShaders = false; static bool shouldPatchShaders = true; static u32 vblankDivider = 1; @@ -81,10 +82,6 @@ static std::vector settings_install_dirs = {}; std::vector install_dirs_enabled = {}; std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path save_data_path = {}; -u32 mw_themes = 0; -std::vector m_elf_viewer; -std::vector m_recent_files; -std::string emulator_language = "en_US"; static bool isFullscreen = false; static std::string fullscreenMode = "Windowed"; static bool isHDRAllowed = false; @@ -209,10 +206,6 @@ std::string getChooseHomeTab() { return chooseHomeTab; } -std::string getBackButtonBehavior() { - return backButtonBehavior; -} - bool getUseSpecialPad() { return useSpecialPad; } @@ -249,6 +242,14 @@ bool copyGPUCmdBuffers() { return shouldCopyGPUBuffers; } +bool readbacks() { + return readbacksEnabled; +} + +bool directMemoryAccess() { + return directMemoryAccessEnabled; +} + bool dumpShaders() { return shouldDumpShaders; } @@ -353,6 +354,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; } @@ -428,10 +437,6 @@ void setChooseHomeTab(const std::string& type) { chooseHomeTab = type; } -void setBackButtonBehavior(const std::string& type) { - backButtonBehavior = type; -} - void setUseSpecialPad(bool use) { useSpecialPad = use; } @@ -484,24 +489,6 @@ void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; } -void setMainWindowTheme(u32 theme) { - mw_themes = theme; -} - -void setElfViewer(const std::vector& elfList) { - m_elf_viewer.resize(elfList.size()); - m_elf_viewer = elfList; -} - -void setRecentFiles(const std::vector& recentFiles) { - m_recent_files.resize(recentFiles.size()); - m_recent_files = recentFiles; -} - -void setEmulatorLanguage(std::string language) { - emulator_language = language; -} - void setGameInstallDirs(const std::vector& dirs_config) { settings_install_dirs.clear(); for (const auto& dir : dirs_config) { @@ -543,22 +530,6 @@ std::filesystem::path getAddonInstallDir() { return settings_addon_install_dir; } -u32 getMainWindowTheme() { - return mw_themes; -} - -std::vector getElfViewer() { - return m_elf_viewer; -} - -std::vector getRecentFiles() { - return m_recent_files; -} - -std::string getEmulatorLanguage() { - return emulator_language; -} - u32 GetLanguage() { return m_language; } @@ -620,7 +591,6 @@ void load(const std::filesystem::path& path) { cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); - backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left"); useSpecialPad = toml::find_or(input, "useSpecialPad", false); specialPadClass = toml::find_or(input, "specialPadClass", 1); isMotionControlsEnabled = toml::find_or(input, "isMotionControlsEnabled", true); @@ -634,6 +604,8 @@ void load(const std::filesystem::path& path) { screenHeight = toml::find_or(gpu, "screenHeight", screenHeight); isNullGpu = toml::find_or(gpu, "nullGpu", false); shouldCopyGPUBuffers = toml::find_or(gpu, "copyGPUBuffers", false); + readbacksEnabled = toml::find_or(gpu, "readbacks", false); + directMemoryAccessEnabled = toml::find_or(gpu, "directMemoryAccess", false); shouldDumpShaders = toml::find_or(gpu, "dumpShaders", false); shouldPatchShaders = toml::find_or(gpu, "patchShaders", true); vblankDivider = toml::find_or(gpu, "vblankDivider", 1); @@ -668,7 +640,6 @@ void load(const std::filesystem::path& path) { const toml::value& gui = data.at("GUI"); load_game_size = toml::find_or(gui, "loadGameSizeEnabled", true); - mw_themes = toml::find_or(gui, "theme", 0); const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); @@ -693,9 +664,6 @@ void load(const std::filesystem::path& path) { save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {}); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); - m_elf_viewer = toml::find_or>(gui, "elfDirs", {}); - m_recent_files = toml::find_or>(gui, "recentFiles", {}); - emulator_language = toml::find_or(gui, "emulatorLanguage", "en_US"); } if (data.contains("Settings")) { @@ -708,19 +676,6 @@ void load(const std::filesystem::path& path) { const toml::value& keys = data.at("Keys"); trophyKey = toml::find_or(keys, "TrophyKey", ""); } - - // Check if the loaded language is in the allowed list - const std::vector allowed_languages = { - "ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", - "fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", - "nl_NL", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sq_AL", "sv_SE", - "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW", "ca_ES", "sr_CS"}; - - if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) == - allowed_languages.end()) { - emulator_language = "en_US"; // Default to en_US if not in the list - save(path); - } } void sortTomlSections(toml::ordered_value& data) { @@ -792,7 +747,6 @@ void save(const std::filesystem::path& path) { data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; - data["Input"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled; @@ -801,6 +755,8 @@ void save(const std::filesystem::path& path) { data["GPU"]["screenHeight"] = screenHeight; data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; + data["GPU"]["readbacks"] = readbacksEnabled; + data["GPU"]["directMemoryAccess"] = directMemoryAccessEnabled; data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["patchShaders"] = shouldPatchShaders; data["GPU"]["vblankDivider"] = vblankDivider; @@ -855,7 +811,6 @@ void save(const std::filesystem::path& path) { data["GUI"]["addonInstallDir"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; - data["GUI"]["emulatorLanguage"] = emulator_language; data["Settings"]["consoleLanguage"] = m_language; // Sorting of TOML sections @@ -864,42 +819,6 @@ void save(const std::filesystem::path& path) { std::ofstream file(path, std::ios::binary); file << data; file.close(); - - saveMainWindow(path); -} - -void saveMainWindow(const std::filesystem::path& path) { - toml::ordered_value data; - - std::error_code error; - if (std::filesystem::exists(path, error)) { - try { - std::ifstream ifs; - ifs.exceptions(std::ifstream::failbit | std::ifstream::badbit); - ifs.open(path, std::ios_base::binary); - data = toml::parse( - ifs, std::string{fmt::UTF(path.filename().u8string()).data}); - } catch (const std::exception& ex) { - fmt::print("Exception trying to parse config file. Exception: {}\n", ex.what()); - return; - } - } else { - if (error) { - fmt::print("Filesystem error: {}\n", error.message()); - } - fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string())); - } - - data["GUI"]["theme"] = mw_themes; - data["GUI"]["elfDirs"] = m_elf_viewer; - data["GUI"]["recentFiles"] = m_recent_files; - - // Sorting of TOML sections - sortTomlSections(data); - - std::ofstream file(path, std::ios::binary); - file << data; - file.close(); } void setDefaultValues() { @@ -920,7 +839,6 @@ void setDefaultValues() { cursorState = HideCursorState::Idle; cursorHideTimeout = 5; trophyNotificationDuration = 6.0; - backButtonBehavior = "left"; useSpecialPad = false; specialPadClass = 1; isDebugDump = false; @@ -937,7 +855,6 @@ void setDefaultValues() { vkHostMarkers = false; vkGuestMarkers = false; rdocEnable = false; - emulator_language = "en_US"; m_language = 1; gpuId = -1; compatibilityData = false; @@ -967,7 +884,7 @@ l3 = x r3 = m options = enter -touchpad = space +touchpad_center = space pad_up = up pad_down = down @@ -999,7 +916,7 @@ r2 = r2 r3 = r3 options = options -touchpad = back +touchpad_center = back pad_up = pad_up pad_down = pad_down diff --git a/src/common/config.h b/src/common/config.h index 414bc122e..931fa68e2 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -18,77 +18,100 @@ enum HideCursorState : int { Never, Idle, Always }; void load(const std::filesystem::path& path); void save(const std::filesystem::path& path); -void saveMainWindow(const std::filesystem::path& path); std::string getTrophyKey(); void setTrophyKey(std::string key); +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); +bool debugDump(); +void setDebugDump(bool enable); +s32 getGpuId(); +void setGpuId(s32 selectedGpuId); +bool allowHDR(); +void setAllowHDR(bool enable); +bool collectShadersForDebug(); +void setCollectShaderForDebug(bool enable); +bool showSplash(); +void setShowSplash(bool enable); +std::string sideTrophy(); +void setSideTrophy(std::string side); +bool nullGpu(); +void setNullGpu(bool enable); +bool copyGPUCmdBuffers(); +void setCopyGPUCmdBuffers(bool enable); +bool readbacks(); +void setReadbacks(bool enable); +bool directMemoryAccess(); +void setDirectMemoryAccess(bool enable); +bool dumpShaders(); +void setDumpShaders(bool enable); +u32 vblankDiv(); +void setVblankDiv(u32 value); +bool getisTrophyPopupDisabled(); +void setisTrophyPopupDisabled(bool disable); +s16 getCursorState(); +void setCursorState(s16 cursorState); +bool vkValidationEnabled(); +void setVkValidation(bool enable); +bool vkValidationSyncEnabled(); +void setVkSyncValidation(bool enable); +bool getVkCrashDiagnosticEnabled(); +void setVkCrashDiagnosticEnabled(bool enable); +bool getVkHostMarkersEnabled(); +void setVkHostMarkersEnabled(bool enable); +bool getVkGuestMarkersEnabled(); +void setVkGuestMarkersEnabled(bool enable); +bool getEnableDiscordRPC(); +void setEnableDiscordRPC(bool enable); +bool isRdocEnabled(); +void setRdocEnabled(bool enable); +std::string getLogType(); +void setLogType(const std::string& type); +std::string getLogFilter(); +void setLogFilter(const std::string& type); +double getTrophyNotificationDuration(); +void setTrophyNotificationDuration(double newTrophyNotificationDuration); +int getCursorHideTimeout(); +void setCursorHideTimeout(int newcursorHideTimeout); +void setSeparateLogFilesEnabled(bool enabled); +bool getSeparateLogFilesEnabled(); +u32 GetLanguage(); +void setLanguage(u32 language); +void setUseSpecialPad(bool use); +bool getUseSpecialPad(); +void setSpecialPadClass(int type); +int getSpecialPadClass(); +bool getPSNSignedIn(); +void setPSNSignedIn(bool sign); // no ui setting +bool patchShaders(); // no set +bool fpsColor(); // no set +bool isNeoModeConsole(); +void setNeoMode(bool enable); // no ui setting +bool isDevKitConsole(); // no set +bool vkValidationGpuEnabled(); // no set +bool getIsMotionControlsEnabled(); +void setIsMotionControlsEnabled(bool use); + +// TODO bool GetLoadGameSizeEnabled(); std::filesystem::path GetSaveDataPath(); void setLoadGameSizeEnabled(bool enable); -bool getIsFullscreen(); -std::string getFullscreenMode(); -bool isNeoModeConsole(); -bool isDevKitConsole(); -bool getisTrophyPopupDisabled(); -bool getEnableDiscordRPC(); bool getCompatibilityEnabled(); bool getCheckCompatibilityOnStartup(); -bool getPSNSignedIn(); - -std::string getLogFilter(); -std::string getLogType(); std::string getUserName(); std::string getChooseHomeTab(); - -s16 getCursorState(); -int getCursorHideTimeout(); -double getTrophyNotificationDuration(); -std::string getBackButtonBehavior(); -bool getUseSpecialPad(); -int getSpecialPadClass(); -bool getIsMotionControlsEnabled(); bool GetUseUnifiedInputConfig(); void SetUseUnifiedInputConfig(bool use); bool GetOverrideControllerColor(); void SetOverrideControllerColor(bool enable); int* GetControllerCustomColor(); void SetControllerCustomColor(int r, int b, int g); - -u32 getScreenWidth(); -u32 getScreenHeight(); -s32 getGpuId(); -bool allowHDR(); - -bool debugDump(); -bool collectShadersForDebug(); -bool showSplash(); -std::string sideTrophy(); -bool nullGpu(); -bool copyGPUCmdBuffers(); -bool dumpShaders(); -bool patchShaders(); -bool isRdocEnabled(); -bool fpsColor(); -u32 vblankDiv(); - -void setDebugDump(bool enable); -void setCollectShaderForDebug(bool enable); -void setShowSplash(bool enable); -void setSideTrophy(std::string side); -void setNullGpu(bool enable); -void setAllowHDR(bool enable); -void setCopyGPUCmdBuffers(bool enable); -void setDumpShaders(bool enable); -void setVblankDiv(u32 value); -void setGpuId(s32 selectedGpuId); -void setScreenWidth(u32 width); -void setScreenHeight(u32 height); -void setIsFullscreen(bool enable); -void setFullscreenMode(std::string mode); -void setisTrophyPopupDisabled(bool disable); -void setEnableDiscordRPC(bool enable); -void setLanguage(u32 language); -void setNeoMode(bool enable); void setUserName(const std::string& type); void setChooseHomeTab(const std::string& type); void setGameInstallDirs(const std::vector& dirs_config); @@ -96,57 +119,19 @@ void setAllGameInstallDirs(const std::vector& dirs_config); void setSaveDataPath(const std::filesystem::path& path); void setCompatibilityEnabled(bool use); void setCheckCompatibilityOnStartup(bool use); -void setPSNSignedIn(bool sign); - -void setCursorState(s16 cursorState); -void setCursorHideTimeout(int newcursorHideTimeout); -void setTrophyNotificationDuration(double newTrophyNotificationDuration); -void setBackButtonBehavior(const std::string& type); -void setUseSpecialPad(bool use); -void setSpecialPadClass(int type); -void setIsMotionControlsEnabled(bool use); - -void setLogType(const std::string& type); -void setLogFilter(const std::string& type); -void setSeparateLogFilesEnabled(bool enabled); -bool getSeparateLogFilesEnabled(); -void setVkValidation(bool enable); -void setVkSyncValidation(bool enable); -void setRdocEnabled(bool enable); - -bool vkValidationEnabled(); -bool vkValidationSyncEnabled(); -bool vkValidationGpuEnabled(); -bool getVkCrashDiagnosticEnabled(); -bool getVkHostMarkersEnabled(); -bool getVkGuestMarkersEnabled(); -void setVkCrashDiagnosticEnabled(bool enable); -void setVkHostMarkersEnabled(bool enable); -void setVkGuestMarkersEnabled(bool enable); - // Gui bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true); void removeGameInstallDir(const std::filesystem::path& dir); void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled); void setAddonInstallDir(const std::filesystem::path& dir); -void setMainWindowTheme(u32 theme); -void setElfViewer(const std::vector& elfList); -void setRecentFiles(const std::vector& recentFiles); -void setEmulatorLanguage(std::string language); const std::vector getGameInstallDirs(); const std::vector getGameInstallDirsEnabled(); std::filesystem::path getAddonInstallDir(); -u32 getMainWindowTheme(); -std::vector getElfViewer(); -std::vector getRecentFiles(); -std::string getEmulatorLanguage(); void setDefaultValues(); // todo: name and function location pending std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = ""); -// settings -u32 GetLanguage(); }; // namespace Config 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..2e29f70ee 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; } 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/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/kernel/kernel.cpp b/src/core/libraries/kernel/kernel.cpp index 930640d0e..61d2e2f2b 100644 --- a/src/core/libraries/kernel/kernel.cpp +++ b/src/core/libraries/kernel/kernel.cpp @@ -76,21 +76,21 @@ static PS4_SYSV_ABI void stack_chk_fail() { UNREACHABLE(); } -static thread_local int g_posix_errno = 0; +static thread_local s32 g_posix_errno = 0; -int* PS4_SYSV_ABI __Error() { +s32* PS4_SYSV_ABI __Error() { return &g_posix_errno; } -void ErrSceToPosix(int error) { +void ErrSceToPosix(s32 error) { g_posix_errno = error - ORBIS_KERNEL_ERROR_UNKNOWN; } -int ErrnoToSceKernelError(int error) { +s32 ErrnoToSceKernelError(s32 error) { return error + ORBIS_KERNEL_ERROR_UNKNOWN; } -void SetPosixErrno(int e) { +void SetPosixErrno(s32 e) { // Some error numbers are different between supported OSes switch (e) { case EPERM: @@ -132,15 +132,15 @@ void SetPosixErrno(int e) { } } -static uint64_t g_mspace_atomic_id_mask = 0; -static uint64_t g_mstate_table[64] = {0}; +static u64 g_mspace_atomic_id_mask = 0; +static u64 g_mstate_table[64] = {0}; struct HeapInfoInfo { - uint64_t size = sizeof(HeapInfoInfo); - uint32_t flag; - uint32_t getSegmentInfo; - uint64_t* mspace_atomic_id_mask; - uint64_t* mstate_table; + u64 size = sizeof(HeapInfoInfo); + u32 flag; + u32 getSegmentInfo; + u64* mspace_atomic_id_mask; + u64* mstate_table; }; void PS4_SYSV_ABI sceLibcHeapGetTraceInfo(HeapInfoInfo* info) { @@ -159,7 +159,7 @@ struct OrbisKernelUuid { }; static_assert(sizeof(OrbisKernelUuid) == 0x10); -int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { +s32 PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { if (!orbisUuid) { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -176,7 +176,7 @@ int PS4_SYSV_ABI sceKernelUuidCreate(OrbisKernelUuid* orbisUuid) { return ORBIS_OK; } -int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { +s32 PS4_SYSV_ABI kernel_ioctl(s32 fd, u64 cmd, VA_ARGS) { auto* h = Common::Singleton::Instance(); auto* file = h->GetFile(fd); if (file == nullptr) { @@ -190,7 +190,7 @@ int PS4_SYSV_ABI kernel_ioctl(int fd, u64 cmd, VA_ARGS) { return -1; } VA_CTX(ctx); - int result = file->device->ioctl(cmd, &ctx); + s32 result = file->device->ioctl(cmd, &ctx); LOG_TRACE(Lib_Kernel, "ioctl: fd = {:X} cmd = {:X} result = {}", fd, cmd, result); if (result < 0) { ErrSceToPosix(result); @@ -204,15 +204,15 @@ const char* PS4_SYSV_ABI sceKernelGetFsSandboxRandomWord() { return path; } -int PS4_SYSV_ABI _sigprocmask() { +s32 PS4_SYSV_ABI _sigprocmask() { return ORBIS_OK; } -int PS4_SYSV_ABI posix_getpagesize() { +s32 PS4_SYSV_ABI posix_getpagesize() { return 16_KB; } -int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, +s32 PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, Libraries::Net::OrbisNetSockaddr* addr, u32* paddrlen) { auto* netcall = Common::Singleton::Instance(); auto sock = netcall->FindSocket(s); @@ -221,7 +221,7 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, LOG_ERROR(Lib_Net, "socket id is invalid = {}", s); return -1; } - int returncode = sock->GetSocketAddress(addr, paddrlen); + s32 returncode = sock->GetSocketAddress(addr, paddrlen); if (returncode >= 0) { LOG_ERROR(Lib_Net, "return code : {:#x}", (u32)returncode); return 0; @@ -230,6 +230,19 @@ int PS4_SYSV_ABI posix_getsockname(Libraries::Net::OrbisNetId s, LOG_ERROR(Lib_Net, "error code returned : {:#x}", (u32)returncode); return -1; } + +// stubbed on non-devkit consoles +s32 PS4_SYSV_ABI sceKernelGetGPI() { + LOG_DEBUG(Kernel, "called"); + return ORBIS_OK; +} + +// stubbed on non-devkit consoles +s32 PS4_SYSV_ABI sceKernelSetGPO() { + LOG_DEBUG(Kernel, "called"); + return ORBIS_OK; +} + void RegisterKernel(Core::Loader::SymbolsResolver* sym) { service_thread = std::jthread{KernelServiceThread}; @@ -277,6 +290,9 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) { LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept); LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg); LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen); + + LIB_FUNCTION("4oXYe9Xmk0Q", "libkernel", 1, "libkernel", 1, 1, sceKernelGetGPI); + LIB_FUNCTION("ca7v6Cxulzs", "libkernel", 1, "libkernel", 1, 1, sceKernelSetGPO); } } // namespace Libraries::Kernel diff --git a/src/core/libraries/kernel/kernel.h b/src/core/libraries/kernel/kernel.h index aaa22aec1..0529c06d5 100644 --- a/src/core/libraries/kernel/kernel.h +++ b/src/core/libraries/kernel/kernel.h @@ -12,10 +12,10 @@ class SymbolsResolver; namespace Libraries::Kernel { -void ErrSceToPosix(int result); -int ErrnoToSceKernelError(int e); -void SetPosixErrno(int e); -int* PS4_SYSV_ABI __Error(); +void ErrSceToPosix(s32 result); +s32 ErrnoToSceKernelError(s32 e); +void SetPosixErrno(s32 e); +s32* PS4_SYSV_ABI __Error(); template struct OrbisWrapperImpl; @@ -33,7 +33,7 @@ struct OrbisWrapperImpl { #define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl::wrap) -int* PS4_SYSV_ABI __Error(); +s32* PS4_SYSV_ABI __Error(); void RegisterKernel(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/memory.cpp b/src/core/libraries/kernel/memory.cpp index ea3998ddd..114a096ca 100644 --- a/src/core/libraries/kernel/memory.cpp +++ b/src/core/libraries/kernel/memory.cpp @@ -23,8 +23,8 @@ u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize() { return memory->GetTotalDirectSize(); } -int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, - u64 alignment, int memoryType, s64* physAddrOut) { +s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, + u64 alignment, s32 memoryType, s64* physAddrOut) { if (searchStart < 0 || searchEnd < 0) { LOG_ERROR(Kernel_Vmm, "Invalid parameters!"); return ORBIS_KERNEL_ERROR_EINVAL; @@ -71,13 +71,13 @@ int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType, s64* physAddrOut) { const auto searchEnd = static_cast(sceKernelGetDirectMemorySize()); return sceKernelAllocateDirectMemory(0, searchEnd, len, alignment, memoryType, physAddrOut); } -s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { +s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len) { if (len == 0) { return ORBIS_OK; } @@ -87,7 +87,7 @@ s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { +s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len) { if (len == 0) { return ORBIS_OK; } @@ -96,9 +96,8 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) { return ORBIS_OK; } -s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, - size_t alignment, u64* physAddrOut, - size_t* sizeOut) { +s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment, + u64* physAddrOut, u64* sizeOut) { LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}", searchStart, searchEnd, alignment); @@ -109,7 +108,7 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE auto* memory = Core::Memory::Instance(); PAddr physAddr{}; - size_t size{}; + u64 size{}; s32 result = memory->DirectQueryAvailable(searchStart, searchEnd, alignment, &physAddr, &size); if (size == 0) { @@ -122,14 +121,14 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE return result; } -s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, - size_t infoSize) { +s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info, + u64 infoSize) { LOG_INFO(Kernel_Vmm, "called addr = {}, flags = {:#x}", fmt::ptr(addr), flags); auto* memory = Core::Memory::Instance(); return memory->VirtualQuery(std::bit_cast(addr), flags, info); } -s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment) { +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}, flags = {:#x}, alignment = {:#x}", fmt::ptr(*addr), len, flags, alignment); if (addr == nullptr) { @@ -159,7 +158,7 @@ s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u return result; } -int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment, const char* name) { LOG_INFO(Kernel_Vmm, @@ -202,7 +201,7 @@ int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, i return ret; } -int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment) { LOG_INFO(Kernel_Vmm, "called, redirected to sceKernelMapNamedDirectMemory"); return sceKernelMapNamedDirectMemory(addr, len, prot, flags, directMemoryStart, alignment, @@ -255,7 +254,7 @@ s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 pro return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon"); } -int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) { +s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot) { auto* memory = Core::Memory::Instance(); return memory->QueryProtection(std::bit_cast(addr), start, end, prot); } @@ -285,14 +284,14 @@ s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s3 return memory_manager->Protect(std::bit_cast(addr), size, protection_flags); } -int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, - size_t infoSize) { +s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info, + u64 infoSize) { LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags); auto* memory = Core::Memory::Instance(); return memory->DirectMemoryQuery(offset, flags == 1, query_info); } -s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* out_size) { +s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* out_size) { auto* memory = Core::Memory::Instance(); *out_size = memory->GetAvailableFlexibleSize(); LOG_INFO(Kernel_Vmm, "called size = {:#x}", *out_size); @@ -304,7 +303,7 @@ void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]) { linker->SetHeapAPI(func); } -int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, +s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut) { LOG_WARNING(Kernel_Vmm, "called, direct memory addr = {:#x}", addr); @@ -313,23 +312,23 @@ int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut directMemoryEndOut); } -int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { +s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end) { LOG_DEBUG(Kernel_Vmm, "called, addr = {}", fmt::ptr(addr)); auto* memory = Core::Memory::Instance(); return memory->IsStack(std::bit_cast(addr), start, end); } -s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut) { +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut) { return sceKernelBatchMap2(entries, numEntries, numEntriesOut, MemoryFlags::SCE_KERNEL_MAP_FIXED); // 0x10, 0x410? } -s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut, int flags) { - int result = ORBIS_OK; - int processed = 0; - for (int i = 0; i < numEntries; i++, processed++) { +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut, s32 flags) { + s32 result = ORBIS_OK; + s32 processed = 0; + for (s32 i = 0; i < numEntries; i++, processed++) { if (entries == nullptr || entries[i].length == 0 || entries[i].operation > 4) { result = ORBIS_KERNEL_ERROR_EINVAL; break; // break and assign a value to numEntriesOut. @@ -619,7 +618,7 @@ s32 PS4_SYSV_ABI sceKernelConfiguredFlexibleMemorySize(u64* sizeOut) { return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { +s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len) { LOG_INFO(Kernel_Vmm, "addr = {}, len = {:#x}", fmt::ptr(addr), len); if (len == 0) { return ORBIS_KERNEL_ERROR_EINVAL; @@ -628,8 +627,8 @@ int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len) { return memory->UnmapMemory(std::bit_cast(addr), len); } -int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) { - int result = sceKernelMunmap(addr, len); +s32 PS4_SYSV_ABI posix_munmap(void* addr, u64 len) { + s32 result = sceKernelMunmap(addr, len); if (result < 0) { LOG_ERROR(Kernel_Pthread, "posix_munmap: error = {}", result); ErrSceToPosix(result); @@ -638,12 +637,12 @@ int PS4_SYSV_ABI posix_munmap(void* addr, size_t len) { return result; } -static constexpr int MAX_PRT_APERTURES = 3; +static constexpr s32 MAX_PRT_APERTURES = 3; static constexpr VAddr PRT_AREA_START_ADDR = 0x1000000000; -static constexpr size_t PRT_AREA_SIZE = 0xec00000000; -static std::array, MAX_PRT_APERTURES> PrtApertures{}; +static constexpr u64 PRT_AREA_SIZE = 0xec00000000; +static std::array, MAX_PRT_APERTURES> PrtApertures{}; -int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { +s32 PS4_SYSV_ABI sceKernelSetPrtAperture(s32 id, VAddr address, u64 size) { if (id < 0 || id >= MAX_PRT_APERTURES) { return ORBIS_KERNEL_ERROR_EINVAL; } @@ -667,7 +666,7 @@ int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) { return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelGetPrtAperture(int id, VAddr* address, size_t* size) { +s32 PS4_SYSV_ABI sceKernelGetPrtAperture(s32 id, VAddr* address, u64* size) { if (id < 0 || id >= MAX_PRT_APERTURES) { return ORBIS_KERNEL_ERROR_EINVAL; } diff --git a/src/core/libraries/kernel/memory.h b/src/core/libraries/kernel/memory.h index ea42e7546..ce4ec64fe 100644 --- a/src/core/libraries/kernel/memory.h +++ b/src/core/libraries/kernel/memory.h @@ -52,13 +52,13 @@ constexpr u32 ORBIS_KERNEL_MAXIMUM_NAME_LENGTH = 32; struct OrbisQueryInfo { uintptr_t start; uintptr_t end; - int memoryType; + s32 memoryType; }; struct OrbisVirtualQueryInfo { uintptr_t start; uintptr_t end; - size_t offset; + u64 offset; s32 protection; s32 memory_type; u8 is_flexible : 1; @@ -73,12 +73,12 @@ static_assert(sizeof(OrbisVirtualQueryInfo) == 72, struct OrbisKernelBatchMapEntry { void* start; - size_t offset; - size_t length; + u64 offset; + u64 length; char protection; char type; - short reserved; - int operation; + s16 reserved; + s32 operation; }; enum class OrbisKernelMemoryPoolOpcode : u32 { @@ -124,45 +124,44 @@ struct OrbisKernelMemoryPoolBatchEntry { }; u64 PS4_SYSV_ABI sceKernelGetDirectMemorySize(); -int PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, - u64 alignment, int memoryType, s64* physAddrOut); -int PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelAllocateDirectMemory(s64 searchStart, s64 searchEnd, u64 len, + u64 alignment, s32 memoryType, s64* physAddrOut); +s32 PS4_SYSV_ABI sceKernelMapNamedDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment, const char* name); -int PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, int prot, int flags, +s32 PS4_SYSV_ABI sceKernelMapDirectMemory(void** addr, u64 len, s32 prot, s32 flags, s64 directMemoryStart, u64 alignment); -s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(size_t len, size_t alignment, int memoryType, +s32 PS4_SYSV_ABI sceKernelAllocateMainDirectMemory(u64 len, u64 alignment, s32 memoryType, s64* physAddrOut); -s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len); -s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, size_t len); -s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, - size_t alignment, u64* physAddrOut, - size_t* sizeOut); -s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info, - size_t infoSize); -s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); +s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, u64 len); +s32 PS4_SYSV_ABI sceKernelCheckedReleaseDirectMemory(u64 start, u64 len); +s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, u64 alignment, + u64* physAddrOut, u64* sizeOut); +s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, s32 flags, OrbisVirtualQueryInfo* info, + u64 infoSize); +s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, s32 flags, u64 alignment); s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags, const char* name); s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags); -int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); +s32 PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot); s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s32 prot); -int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info, - size_t infoSize); -s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(size_t* sizeOut); +s32 PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, s32 flags, OrbisQueryInfo* query_info, + u64 infoSize); +s32 PS4_SYSV_ABI sceKernelAvailableFlexibleMemorySize(u64* sizeOut); void PS4_SYSV_ABI _sceKernelRtldSetApplicationHeapAPI(void* func[]); -int PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, int* directMemoryTypeOut, +s32 PS4_SYSV_ABI sceKernelGetDirectMemoryType(u64 addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); -int PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); +s32 PS4_SYSV_ABI sceKernelIsStack(void* addr, void** start, void** end); -s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut); -s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, int numEntries, - int* numEntriesOut, int flags); +s32 PS4_SYSV_ABI sceKernelBatchMap(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut); +s32 PS4_SYSV_ABI sceKernelBatchMap2(OrbisKernelBatchMapEntry* entries, s32 numEntries, + s32* numEntriesOut, s32 flags); s32 PS4_SYSV_ABI sceKernelSetVirtualRangeName(const void* addr, u64 len, const char* name); @@ -175,7 +174,7 @@ s32 PS4_SYSV_ABI sceKernelMemoryPoolDecommit(void* addr, u64 len, s32 flags); s32 PS4_SYSV_ABI sceKernelMemoryPoolBatch(const OrbisKernelMemoryPoolBatchEntry* entries, s32 count, s32* num_processed, s32 flags); -int PS4_SYSV_ABI sceKernelMunmap(void* addr, size_t len); +s32 PS4_SYSV_ABI sceKernelMunmap(void* addr, u64 len); void RegisterMemory(Core::Loader::SymbolsResolver* sym); diff --git a/src/core/libraries/kernel/process.cpp b/src/core/libraries/kernel/process.cpp index 8a37e78d5..e22509a3a 100644 --- a/src/core/libraries/kernel/process.cpp +++ b/src/core/libraries/kernel/process.cpp @@ -103,7 +103,7 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags, auto* linker = Common::Singleton::Instance(); auto* module = linker->FindByAddress(addr); if (!module) { - return ORBIS_KERNEL_ERROR_EFAULT; + return ORBIS_KERNEL_ERROR_ESRCH; } const auto mod_info = module->GetModuleInfoEx(); @@ -118,11 +118,23 @@ s32 PS4_SYSV_ABI sceKernelGetModuleInfoForUnwind(VAddr addr, s32 flags, return ORBIS_OK; } -int PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, int flags, +s32 PS4_SYSV_ABI sceKernelGetModuleInfoFromAddr(VAddr addr, s32 flags, Core::OrbisKernelModuleInfoEx* info) { + if (flags >= 3) { + std::memset(info, 0, sizeof(Core::OrbisKernelModuleInfoEx)); + return ORBIS_KERNEL_ERROR_EINVAL; + } + if (info == nullptr) { + return ORBIS_KERNEL_ERROR_EFAULT; + } + LOG_INFO(Lib_Kernel, "called addr = {:#x}, flags = {:#x}", addr, flags); auto* linker = Common::Singleton::Instance(); auto* module = linker->FindByAddress(addr); + if (!module) { + return ORBIS_KERNEL_ERROR_ESRCH; + } + *info = module->GetModuleInfoEx(); return ORBIS_OK; } diff --git a/src/core/libraries/ngs2/ngs2_impl.cpp b/src/core/libraries/ngs2/ngs2_impl.cpp index 1248f76d7..141ac41ba 100644 --- a/src/core/libraries/ngs2/ngs2_impl.cpp +++ b/src/core/libraries/ngs2/ngs2_impl.cpp @@ -100,6 +100,11 @@ s32 SystemSetupCore(StackBuffer* stackBuffer, const OrbisNgs2SystemOption* optio return ORBIS_NGS2_ERROR_INVALID_SAMPLE_RATE; } + if (outSystem) { + // dummy handle + outSystem->systemHandle = 1; + } + return ORBIS_OK; } diff --git a/src/core/libraries/np_trophy/np_trophy.cpp b/src/core/libraries/np_trophy/np_trophy.cpp index e3c5ce35e..c0642f81c 100644 --- a/src/core/libraries/np_trophy/np_trophy.cpp +++ b/src/core/libraries/np_trophy/np_trophy.cpp @@ -199,6 +199,10 @@ int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { Common::SlotId contextId; contextId.index = context - 1; + if (contextId.index >= trophy_contexts.size()) { + return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT; + } + ContextKey contextkey = trophy_contexts[contextId]; trophy_contexts.erase(contextId); contexts_internal.erase(contextkey); diff --git a/src/core/libraries/pad/pad.cpp b/src/core/libraries/pad/pad.cpp index 42582783b..59964fa58 100644 --- a/src/core/libraries/pad/pad.cpp +++ b/src/core/libraries/pad/pad.cpp @@ -447,21 +447,18 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) { // Only do this on handle 1 for now if (engine && handle == 1) { - const auto gyro_poll_rate = engine->GetAccelPollRate(); - if (gyro_poll_rate != 0.0f) { - auto now = std::chrono::steady_clock::now(); - float deltaTime = std::chrono::duration_cast( - now - controller->GetLastUpdate()) - .count() / - 1000000.0f; - controller->SetLastUpdate(now); - Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); - Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; - GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, - deltaTime, lastOrientation, outputOrientation); - pData->orientation = outputOrientation; - controller->SetLastOrientation(outputOrientation); - } + auto now = std::chrono::steady_clock::now(); + float deltaTime = + std::chrono::duration_cast(now - controller->GetLastUpdate()) + .count() / + 1000000.0f; + controller->SetLastUpdate(now); + Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation(); + Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f}; + GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime, + lastOrientation, outputOrientation); + pData->orientation = outputOrientation; + controller->SetLastOrientation(outputOrientation); } pData->touchData.touchNum = (state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0); diff --git a/src/core/libraries/save_data/save_instance.cpp b/src/core/libraries/save_data/save_instance.cpp index a7ce3d35f..05253eb23 100644 --- a/src/core/libraries/save_data/save_instance.cpp +++ b/src/core/libraries/save_data/save_instance.cpp @@ -22,25 +22,25 @@ static Core::FileSys::MntPoints* g_mnt = Common::Singleton default_title = { - {"ja_JP", "セーブデータ"}, - {"en_US", "Saved Data"}, - {"fr_FR", "Données sauvegardées"}, - {"es_ES", "Datos guardados"}, - {"de_DE", "Gespeicherte Daten"}, - {"it_IT", "Dati salvati"}, - {"nl_NL", "Opgeslagen data"}, - {"pt_PT", "Dados guardados"}, - {"ru_RU", "Сохраненные данные"}, - {"ko_KR", "저장 데이터"}, - {"zh_CN", "保存数据"}, - {"fi_FI", "Tallennetut tiedot"}, - {"sv_SE", "Sparade data"}, - {"da_DK", "Gemte data"}, - {"no_NO", "Lagrede data"}, - {"pl_PL", "Zapisane dane"}, - {"pt_BR", "Dados salvos"}, - {"tr_TR", "Kayıtlı Veriler"}, +static const std::unordered_map default_title = { + {0/*"ja_JP"*/, "セーブデータ"}, + {1/*"en_US"*/, "Saved Data"}, + {2/*"fr_FR"*/, "Données sauvegardées"}, + {3/*"es_ES"*/, "Datos guardados"}, + {4/*"de_DE"*/, "Gespeicherte Daten"}, + {5/*"it_IT"*/, "Dati salvati"}, + {6/*"nl_NL"*/, "Opgeslagen data"}, + {7/*"pt_PT"*/, "Dados guardados"}, + {8/*"ru_RU"*/, "Сохраненные данные"}, + {9/*"ko_KR"*/, "저장 데이터"}, + {10/*"zh_CN"*/, "保存数据"}, + {12/*"fi_FI"*/, "Tallennetut tiedot"}, + {13/*"sv_SE"*/, "Sparade data"}, + {14/*"da_DK"*/, "Gemte data"}, + {15/*"no_NO"*/, "Lagrede data"}, + {16/*"pl_PL"*/, "Zapisane dane"}, + {17/*"pt_BR"*/, "Dados salvos"}, + {19/*"tr_TR"*/, "Kayıtlı Veriler"}, }; // clang-format on @@ -71,9 +71,9 @@ fs::path SaveInstance::GetParamSFOPath(const fs::path& dir_path) { void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name, std::string game_serial) { - std::string locale = Config::getEmulatorLanguage(); + int locale = Config::GetLanguage(); if (!default_title.contains(locale)) { - locale = "en_US"; + locale = 1; // default to en_US if not found } #define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__) 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/memory.cpp b/src/core/memory.cpp index dad42347a..2372fafb5 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp @@ -17,11 +17,11 @@ namespace Core { MemoryManager::MemoryManager() { // Insert a virtual memory area that covers the entire area we manage. const VAddr system_managed_base = impl.SystemManagedVirtualBase(); - const size_t system_managed_size = impl.SystemManagedVirtualSize(); + const u64 system_managed_size = impl.SystemManagedVirtualSize(); const VAddr system_reserved_base = impl.SystemReservedVirtualBase(); - const size_t system_reserved_size = impl.SystemReservedVirtualSize(); + const u64 system_reserved_size = impl.SystemReservedVirtualSize(); const VAddr user_base = impl.UserVirtualBase(); - const size_t user_size = impl.UserVirtualSize(); + const u64 user_size = impl.UserVirtualSize(); vma_map.emplace(system_managed_base, VirtualMemoryArea{system_managed_base, system_managed_size}); vma_map.emplace(system_reserved_base, @@ -148,7 +148,7 @@ bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_byt return true; } -PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment) { +PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment) { std::scoped_lock lk{mutex}; alignment = alignment > 0 ? alignment : 64_KB; @@ -188,8 +188,8 @@ PAddr MemoryManager::PoolExpand(PAddr search_start, PAddr search_end, size_t siz return mapping_start; } -PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, - int memory_type) { +PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, + s32 memory_type) { std::scoped_lock lk{mutex}; alignment = alignment > 0 ? alignment : 16_KB; @@ -226,7 +226,7 @@ PAddr MemoryManager::Allocate(PAddr search_start, PAddr search_end, size_t size, return mapping_start; } -void MemoryManager::Free(PAddr phys_addr, size_t size) { +void MemoryManager::Free(PAddr phys_addr, u64 size) { std::scoped_lock lk{mutex}; auto dmem_area = CarveDmemArea(phys_addr, size); @@ -256,7 +256,7 @@ void MemoryManager::Free(PAddr phys_addr, size_t size) { MergeAdjacent(dmem_map, dmem_area); } -int MemoryManager::PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot) { +s32 MemoryManager::PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot) { std::scoped_lock lk{mutex}; const u64 alignment = 64_KB; @@ -320,6 +320,28 @@ s32 MemoryManager::MapMemory(void** out_addr, VAddr virtual_addr, u64 size, Memo return ORBIS_KERNEL_ERROR_ENOMEM; } + // Validate the requested physical address range + if (phys_addr != -1) { + u64 validated_size = 0; + do { + auto dmem_area = FindDmemArea(phys_addr + validated_size)->second; + // If any requested dmem area is not allocated, return an error. + if (dmem_area.is_free) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, + phys_addr); + return ORBIS_KERNEL_ERROR_ENOMEM; + } + // Track how much we've validated. + validated_size += dmem_area.size - (phys_addr + validated_size - dmem_area.base); + } while (validated_size < size && phys_addr + validated_size < GetTotalDirectSize()); + // If the requested range goes outside the dmem map, return an error. + if (validated_size < size) { + LOG_ERROR(Kernel_Vmm, "Unable to map {:#x} bytes at physical address {:#x}", size, + phys_addr); + return ORBIS_KERNEL_ERROR_ENOMEM; + } + } + // Limit the minumum address to SystemManagedVirtualBase to prevent hardware-specific issues. VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; @@ -392,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; } @@ -403,7 +426,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory auto* h = Common::Singleton::Instance(); VAddr mapped_addr = (virtual_addr == 0) ? impl.SystemManagedVirtualBase() : virtual_addr; - const size_t size_aligned = Common::AlignUp(size, 16_KB); + const u64 size_aligned = Common::AlignUp(size, 16_KB); // Find first free area to map the file. if (False(flags & MemoryMapFlags::Fixed)) { @@ -416,7 +439,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory if (True(flags & MemoryMapFlags::Fixed)) { const auto& vma = FindVMA(virtual_addr)->second; - const size_t remaining_size = vma.base + vma.size - virtual_addr; + const u64 remaining_size = vma.base + vma.size - virtual_addr; ASSERT_MSG(!vma.IsMapped() && remaining_size >= size, "Memory region {:#x} to {:#x} isn't free enough to map region {:#x} to {:#x}", vma.base, vma.base + vma.size, virtual_addr, virtual_addr + size); @@ -448,7 +471,7 @@ s32 MemoryManager::MapFile(void** out_addr, VAddr virtual_addr, u64 size, Memory return ORBIS_OK; } -s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) { +s32 MemoryManager::PoolDecommit(VAddr virtual_addr, u64 size) { std::scoped_lock lk{mutex}; const auto it = FindVMA(virtual_addr); @@ -498,7 +521,7 @@ s32 MemoryManager::PoolDecommit(VAddr virtual_addr, size_t size) { return ORBIS_OK; } -s32 MemoryManager::UnmapMemory(VAddr virtual_addr, size_t size) { +s32 MemoryManager::UnmapMemory(VAddr virtual_addr, u64 size) { std::scoped_lock lk{mutex}; return UnmapMemoryImpl(virtual_addr, size); } @@ -564,7 +587,7 @@ s32 MemoryManager::UnmapMemoryImpl(VAddr virtual_addr, u64 size) { return ORBIS_OK; } -int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { +s32 MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* prot) { std::scoped_lock lk{mutex}; const auto it = FindVMA(addr); @@ -586,8 +609,7 @@ int MemoryManager::QueryProtection(VAddr addr, void** start, void** end, u32* pr return ORBIS_OK; } -s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, - MemoryProt prot) { +s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot) { const auto start_in_vma = addr - vma_base.base; const auto adjusted_size = vma_base.size - start_in_vma < size ? vma_base.size - start_in_vma : size; @@ -624,7 +646,7 @@ s64 MemoryManager::ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t s return adjusted_size; } -s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { +s32 MemoryManager::Protect(VAddr addr, u64 size, MemoryProt prot) { std::scoped_lock lk{mutex}; // Validate protection flags @@ -649,9 +671,8 @@ s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { auto& vma_base = it->second; ASSERT_MSG(vma_base.Contains(addr + protected_bytes, 0), "Address {:#x} is out of bounds", addr + protected_bytes); - auto result = 0; - result = ProtectBytes(aligned_addr + protected_bytes, vma_base, - aligned_size - protected_bytes, prot); + auto result = ProtectBytes(aligned_addr + protected_bytes, vma_base, + aligned_size - protected_bytes, prot); if (result < 0) { // ProtectBytes returned an error, return it return result; @@ -662,7 +683,7 @@ s32 MemoryManager::Protect(VAddr addr, size_t size, MemoryProt prot) { return ORBIS_OK; } -int MemoryManager::VirtualQuery(VAddr addr, int flags, +s32 MemoryManager::VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info) { std::scoped_lock lk{mutex}; @@ -707,7 +728,7 @@ int MemoryManager::VirtualQuery(VAddr addr, int flags, return ORBIS_OK; } -int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, +s32 MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, ::Libraries::Kernel::OrbisQueryInfo* out_info) { std::scoped_lock lk{mutex}; @@ -728,13 +749,13 @@ int MemoryManager::DirectMemoryQuery(PAddr addr, bool find_next, return ORBIS_OK; } -int MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, - PAddr* phys_addr_out, size_t* size_out) { +s32 MemoryManager::DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment, + PAddr* phys_addr_out, u64* size_out) { std::scoped_lock lk{mutex}; auto dmem_area = FindDmemArea(search_start); PAddr paddr{}; - size_t max_size{}; + u64 max_size{}; while (dmem_area != dmem_map.end()) { if (!dmem_area->second.is_free) { @@ -815,13 +836,60 @@ void MemoryManager::NameVirtualRange(VAddr virtual_addr, u64 size, std::string_v } } +s32 MemoryManager::GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, + void** directMemoryStartOut, void** directMemoryEndOut) { + std::scoped_lock lk{mutex}; + + auto dmem_area = FindDmemArea(addr); + + if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) { + LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!"); + return ORBIS_KERNEL_ERROR_ENOENT; + } + + const auto& area = dmem_area->second; + *directMemoryStartOut = reinterpret_cast(area.base); + *directMemoryEndOut = reinterpret_cast(area.GetEnd()); + *directMemoryTypeOut = area.memory_type; + return ORBIS_OK; +} + +s32 MemoryManager::IsStack(VAddr addr, void** start, void** end) { + auto vma_handle = FindVMA(addr); + if (vma_handle == vma_map.end()) { + return ORBIS_KERNEL_ERROR_EINVAL; + } + + const VirtualMemoryArea& vma = vma_handle->second; + if (!vma.Contains(addr, 0) || vma.IsFree()) { + return ORBIS_KERNEL_ERROR_EACCES; + } + + u64 stack_start = 0; + u64 stack_end = 0; + if (vma.type == VMAType::Stack) { + stack_start = vma.base; + stack_end = vma.base + vma.size; + } + + if (start != nullptr) { + *start = reinterpret_cast(stack_start); + } + + if (end != nullptr) { + *end = reinterpret_cast(stack_end); + } + + return ORBIS_OK; +} + void MemoryManager::InvalidateMemory(const VAddr addr, const u64 size) const { if (rasterizer) { rasterizer->InvalidateMemory(addr, size); } } -VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) { +VAddr MemoryManager::SearchFree(VAddr virtual_addr, u64 size, u32 alignment) { // If the requested address is below the mapped range, start search from the lowest address auto min_search_address = impl.SystemManagedVirtualBase(); if (virtual_addr < min_search_address) { @@ -864,7 +932,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) } // If there's enough space in the VMA, return the address. - const size_t remaining_size = vma.base + vma.size - virtual_addr; + const u64 remaining_size = vma.base + vma.size - virtual_addr; if (remaining_size >= size) { return virtual_addr; } @@ -877,7 +945,7 @@ VAddr MemoryManager::SearchFree(VAddr virtual_addr, size_t size, u32 alignment) return -1; } -MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size) { +MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, u64 size) { auto vma_handle = FindVMA(virtual_addr); ASSERT_MSG(vma_handle->second.Contains(virtual_addr, 0), "Virtual address not in vm_map"); @@ -906,7 +974,7 @@ MemoryManager::VMAHandle MemoryManager::CarveVMA(VAddr virtual_addr, size_t size return vma_handle; } -MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) { +MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, u64 size) { auto dmem_handle = FindDmemArea(addr); ASSERT_MSG(addr <= dmem_handle->second.GetEnd(), "Physical address not in dmem_map"); @@ -930,7 +998,7 @@ MemoryManager::DMemHandle MemoryManager::CarveDmemArea(PAddr addr, size_t size) return dmem_handle; } -MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offset_in_vma) { +MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, u64 offset_in_vma) { auto& old_vma = vma_handle->second; ASSERT(offset_in_vma < old_vma.size && offset_in_vma > 0); @@ -945,7 +1013,7 @@ MemoryManager::VMAHandle MemoryManager::Split(VMAHandle vma_handle, size_t offse return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma); } -MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t offset_in_area) { +MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, u64 offset_in_area) { auto& old_area = dmem_handle->second; ASSERT(offset_in_area < old_area.size && offset_in_area > 0); @@ -957,51 +1025,4 @@ MemoryManager::DMemHandle MemoryManager::Split(DMemHandle dmem_handle, size_t of return dmem_map.emplace_hint(std::next(dmem_handle), new_area.base, new_area); } -int MemoryManager::GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, - void** directMemoryStartOut, void** directMemoryEndOut) { - std::scoped_lock lk{mutex}; - - auto dmem_area = FindDmemArea(addr); - - if (addr > dmem_area->second.GetEnd() || dmem_area->second.is_free) { - LOG_ERROR(Core, "Unable to find allocated direct memory region to check type!"); - return ORBIS_KERNEL_ERROR_ENOENT; - } - - const auto& area = dmem_area->second; - *directMemoryStartOut = reinterpret_cast(area.base); - *directMemoryEndOut = reinterpret_cast(area.GetEnd()); - *directMemoryTypeOut = area.memory_type; - return ORBIS_OK; -} - -int MemoryManager::IsStack(VAddr addr, void** start, void** end) { - auto vma_handle = FindVMA(addr); - if (vma_handle == vma_map.end()) { - return ORBIS_KERNEL_ERROR_EINVAL; - } - - const VirtualMemoryArea& vma = vma_handle->second; - if (!vma.Contains(addr, 0) || vma.IsFree()) { - return ORBIS_KERNEL_ERROR_EACCES; - } - - auto stack_start = 0ul; - auto stack_end = 0ul; - if (vma.type == VMAType::Stack) { - stack_start = vma.base; - stack_end = vma.base + vma.size; - } - - if (start != nullptr) { - *start = reinterpret_cast(stack_start); - } - - if (end != nullptr) { - *end = reinterpret_cast(stack_end); - } - - return ORBIS_OK; -} - } // namespace Core diff --git a/src/core/memory.h b/src/core/memory.h index 6a9b29382..c800ef763 100644 --- a/src/core/memory.h +++ b/src/core/memory.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "common/enum.h" #include "common/singleton.h" @@ -62,8 +63,8 @@ enum class VMAType : u32 { struct DirectMemoryArea { PAddr base = 0; - size_t size = 0; - int memory_type = 0; + u64 size = 0; + s32 memory_type = 0; bool is_pooled = false; bool is_free = true; @@ -87,7 +88,7 @@ struct DirectMemoryArea { struct VirtualMemoryArea { VAddr base = 0; - size_t size = 0; + u64 size = 0; PAddr phys_base = 0; VMAType type = VMAType::Free; MemoryProt prot = MemoryProt::NoAccess; @@ -96,7 +97,7 @@ struct VirtualMemoryArea { uintptr_t fd = 0; bool is_exec = false; - bool Contains(VAddr addr, size_t size) const { + bool Contains(VAddr addr, u64 size) const { return addr >= base && (addr + size) <= (base + this->size); } @@ -183,14 +184,13 @@ public: void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); - PAddr PoolExpand(PAddr search_start, PAddr search_end, size_t size, u64 alignment); + PAddr PoolExpand(PAddr search_start, PAddr search_end, u64 size, u64 alignment); - PAddr Allocate(PAddr search_start, PAddr search_end, size_t size, u64 alignment, - int memory_type); + PAddr Allocate(PAddr search_start, PAddr search_end, u64 size, u64 alignment, s32 memory_type); - void Free(PAddr phys_addr, size_t size); + void Free(PAddr phys_addr, u64 size); - int PoolCommit(VAddr virtual_addr, size_t size, MemoryProt prot); + s32 PoolCommit(VAddr virtual_addr, u64 size, MemoryProt prot); s32 MapMemory(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, VMAType type, std::string_view name = "anon", @@ -199,35 +199,35 @@ public: s32 MapFile(void** out_addr, VAddr virtual_addr, u64 size, MemoryProt prot, MemoryMapFlags flags, s32 fd, s64 phys_addr); - s32 PoolDecommit(VAddr virtual_addr, size_t size); + s32 PoolDecommit(VAddr virtual_addr, u64 size); - s32 UnmapMemory(VAddr virtual_addr, size_t size); + s32 UnmapMemory(VAddr virtual_addr, u64 size); - int QueryProtection(VAddr addr, void** start, void** end, u32* prot); + s32 QueryProtection(VAddr addr, void** start, void** end, u32* prot); - s32 Protect(VAddr addr, size_t size, MemoryProt prot); + s32 Protect(VAddr addr, u64 size, MemoryProt prot); - s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, size_t size, MemoryProt prot); + s64 ProtectBytes(VAddr addr, VirtualMemoryArea vma_base, u64 size, MemoryProt prot); - int VirtualQuery(VAddr addr, int flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); + s32 VirtualQuery(VAddr addr, s32 flags, ::Libraries::Kernel::OrbisVirtualQueryInfo* info); - int DirectMemoryQuery(PAddr addr, bool find_next, + s32 DirectMemoryQuery(PAddr addr, bool find_next, ::Libraries::Kernel::OrbisQueryInfo* out_info); - int DirectQueryAvailable(PAddr search_start, PAddr search_end, size_t alignment, - PAddr* phys_addr_out, size_t* size_out); + s32 DirectQueryAvailable(PAddr search_start, PAddr search_end, u64 alignment, + PAddr* phys_addr_out, u64* size_out); - int GetDirectMemoryType(PAddr addr, int* directMemoryTypeOut, void** directMemoryStartOut, + s32 GetDirectMemoryType(PAddr addr, s32* directMemoryTypeOut, void** directMemoryStartOut, void** directMemoryEndOut); + s32 IsStack(VAddr addr, void** start, void** end); + s32 SetDirectMemoryType(s64 phys_addr, s32 memory_type); void NameVirtualRange(VAddr virtual_addr, u64 size, std::string_view name); void InvalidateMemory(VAddr addr, u64 size) const; - int IsStack(VAddr addr, void** start, void** end); - private: VMAHandle FindVMA(VAddr target) { return std::prev(vma_map.upper_bound(target)); @@ -257,15 +257,15 @@ private: return iter; } - VAddr SearchFree(VAddr virtual_addr, size_t size, u32 alignment = 0); + VAddr SearchFree(VAddr virtual_addr, u64 size, u32 alignment = 0); - VMAHandle CarveVMA(VAddr virtual_addr, size_t size); + VMAHandle CarveVMA(VAddr virtual_addr, u64 size); - DMemHandle CarveDmemArea(PAddr addr, size_t size); + DMemHandle CarveDmemArea(PAddr addr, u64 size); - VMAHandle Split(VMAHandle vma_handle, size_t offset_in_vma); + VMAHandle Split(VMAHandle vma_handle, u64 offset_in_vma); - DMemHandle Split(DMemHandle dmem_handle, size_t offset_in_area); + DMemHandle Split(DMemHandle dmem_handle, u64 offset_in_area); u64 UnmapBytesFromEntry(VAddr virtual_addr, VirtualMemoryArea vma_base, u64 size); @@ -276,10 +276,10 @@ private: DMemMap dmem_map; VMAMap vma_map; std::mutex mutex; - size_t total_direct_size{}; - size_t total_flexible_size{}; - size_t flexible_usage{}; - size_t pool_budget{}; + u64 total_direct_size{}; + u64 total_flexible_size{}; + u64 flexible_usage{}; + u64 pool_budget{}; Vulkan::Rasterizer* rasterizer{}; struct PrtArea { diff --git a/src/emulator.cpp b/src/emulator.cpp index f50147818..fbab5929b 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -58,10 +58,7 @@ Emulator::Emulator() { #endif } -Emulator::~Emulator() { - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); -} +Emulator::~Emulator() {} void Emulator::Run(std::filesystem::path file, const std::vector args) { if (std::filesystem::is_directory(file)) { @@ -135,6 +132,8 @@ 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 directMemoryAccess: {}", Config::directMemoryAccess()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv()); LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); diff --git a/src/images/favorite_icon.png b/src/images/favorite_icon.png new file mode 100644 index 000000000..743eb0fbe Binary files /dev/null and b/src/images/favorite_icon.png differ diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 3e2d66a6b..67980ed0c 100644 --- a/src/input/input_handler.cpp +++ b/src/input/input_handler.cpp @@ -66,22 +66,25 @@ auto output_array = std::array{ ControllerOutput(LEFTJOYSTICK_HALFMODE), ControllerOutput(RIGHTJOYSTICK_HALFMODE), ControllerOutput(KEY_TOGGLE), + ControllerOutput(MOUSE_GYRO_ROLL_MODE), // Button mappings - ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle - ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle - ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross - ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 - ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 - ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 - ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options - ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left - ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right + ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle + ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle + ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross + ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1 + ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1 + ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3 + ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left + ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right // Axis mappings // ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false), @@ -130,6 +133,12 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) { return OPBDO::Options; case SDL_GAMEPAD_BUTTON_TOUCHPAD: return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: + return OPBDO::TouchPad; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: + return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_BACK: return OPBDO::TouchPad; case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: @@ -176,7 +185,7 @@ InputBinding GetBindingFromString(std::string& line) { if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) { input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t)); } else if (string_to_axis_map.find(t) != string_to_axis_map.end()) { - input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis); + input = InputID(InputType::Axis, string_to_axis_map.at(t).axis); } else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) { input = InputID(InputType::Controller, string_to_cbutton_map.at(t)); } else { @@ -227,19 +236,15 @@ void ParseInputConfig(const std::string game_id = "") { line.erase(std::remove_if(line.begin(), line.end(), [](unsigned char c) { return std::isspace(c); }), line.end()); - if (line.empty()) { continue; } + // Truncate lines starting at # std::size_t comment_pos = line.find('#'); if (comment_pos != std::string::npos) { line = line.substr(0, comment_pos); } - // Remove trailing semicolon - if (!line.empty() && line[line.length() - 1] == ';') { - line = line.substr(0, line.length() - 1); - } if (line.empty()) { continue; } @@ -254,8 +259,13 @@ void ParseInputConfig(const std::string game_id = "") { std::string output_string = line.substr(0, equal_pos); std::string input_string = line.substr(equal_pos + 1); - std::size_t comma_pos = input_string.find(','); + // Remove trailing semicolon from input_string + if (!input_string.empty() && input_string[input_string.length() - 1] == ';' && + input_string != ";") { + line = line.substr(0, line.length() - 1); + } + std::size_t comma_pos = input_string.find(','); auto parseInt = [](const std::string& s) -> std::optional { try { return std::stoi(s); @@ -373,7 +383,6 @@ void ParseInputConfig(const std::string game_id = "") { BindingConnection connection(InputID(), nullptr); auto button_it = string_to_cbutton_map.find(output_string); auto axis_it = string_to_axis_map.find(output_string); - if (binding.IsEmpty()) { LOG_WARNING(Input, "Invalid format at line: {}, data: \"{}\", skipping line.", lineCount, line); @@ -411,7 +420,7 @@ void ParseInputConfig(const std::string game_id = "") { u32 GetMouseWheelEvent(const SDL_Event& event) { if (event.type != SDL_EVENT_MOUSE_WHEEL && event.type != SDL_EVENT_MOUSE_WHEEL_OFF) { LOG_WARNING(Input, "Something went wrong with wheel input parsing!"); - return (u32)-1; + return SDL_UNMAPPED; } if (event.wheel.y > 0) { return SDL_MOUSE_WHEEL_UP; @@ -422,7 +431,7 @@ u32 GetMouseWheelEvent(const SDL_Event& event) { } else if (event.wheel.x < 0) { return SDL_MOUSE_WHEEL_LEFT; } - return (u32)-1; + return SDL_UNMAPPED; } InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { @@ -432,16 +441,19 @@ InputEvent InputBinding::GetInputEventFromSDLEvent(const SDL_Event& e) { return InputEvent(InputType::KeyboardMouse, e.key.key, e.key.down, 0); case SDL_EVENT_MOUSE_BUTTON_DOWN: case SDL_EVENT_MOUSE_BUTTON_UP: - return InputEvent(InputType::KeyboardMouse, (u32)e.button.button, e.button.down, 0); + return InputEvent(InputType::KeyboardMouse, static_cast(e.button.button), + e.button.down, 0); case SDL_EVENT_MOUSE_WHEEL: case SDL_EVENT_MOUSE_WHEEL_OFF: return InputEvent(InputType::KeyboardMouse, GetMouseWheelEvent(e), e.type == SDL_EVENT_MOUSE_WHEEL, 0); case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: - return InputEvent(InputType::Controller, (u32)e.gbutton.button, e.gbutton.down, 0); + return InputEvent(InputType::Controller, static_cast(e.gbutton.button), e.gbutton.down, + 0); // clang made me do it case SDL_EVENT_GAMEPAD_AXIS_MOTION: - return InputEvent(InputType::Axis, (u32)e.gaxis.axis, true, e.gaxis.value / 256); + return InputEvent(InputType::Axis, static_cast(e.gaxis.axis), true, + e.gaxis.value / 256); // this too default: return InputEvent(); } @@ -499,14 +511,18 @@ void ControllerOutput::FinalizeUpdate() { } old_button_state = new_button_state; old_param = *new_param; - float touchpad_x = 0; if (button != SDL_GAMEPAD_BUTTON_INVALID) { switch (button) { - case SDL_GAMEPAD_BUTTON_TOUCHPAD: - touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f - : Config::getBackButtonBehavior() == "right" ? 0.75f - : 0.5f; - controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f); + case SDL_GAMEPAD_BUTTON_TOUCHPAD_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: + 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: + controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: @@ -519,6 +535,9 @@ void ControllerOutput::FinalizeUpdate() { // to do it, and it would be inconvenient to force it here, when AddUpdate does the job just // fine, and a toggle doesn't have to checked against every input that's bound to it, it's // enough that one is pressed + case MOUSE_GYRO_ROLL_MODE: + SetMouseGyroRollMode(new_button_state); + break; default: // is a normal key (hopefully) controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; @@ -570,7 +589,7 @@ void ControllerOutput::FinalizeUpdate() { bool UpdatePressedKeys(InputEvent event) { // Skip invalid inputs InputID input = event.input; - if (input.sdl_id == (u32)-1) { + if (input.sdl_id == SDL_UNMAPPED) { return false; } if (input.type == InputType::Axis) { diff --git a/src/input/input_handler.h b/src/input/input_handler.h index 0178e7937..daef22f21 100644 --- a/src/input/input_handler.h +++ b/src/input/input_handler.h @@ -23,6 +23,10 @@ #define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5 #define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT SDL_GAMEPAD_BUTTON_COUNT + 1 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER SDL_GAMEPAD_BUTTON_COUNT + 2 +#define SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT SDL_GAMEPAD_BUTTON_COUNT + 3 + // idk who already used what where so I just chose a big number #define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10 @@ -31,6 +35,9 @@ #define BACK_BUTTON 0x00040000 #define KEY_TOGGLE 0x00200000 +#define MOUSE_GYRO_ROLL_MODE 0x00400000 + +#define SDL_UNMAPPED UINT32_MAX - 1 namespace Input { using Input::Axis; @@ -49,7 +56,7 @@ class InputID { public: InputType type; u32 sdl_id; - InputID(InputType d = InputType::Count, u32 i = (u32)-1) : type(d), sdl_id(i) {} + InputID(InputType d = InputType::Count, u32 i = SDL_UNMAPPED) : type(d), sdl_id(i) {} bool operator==(const InputID& o) const { return type == o.type && sdl_id == o.sdl_id; } @@ -63,7 +70,7 @@ public: return *this != InputID(); } std::string ToString() { - return fmt::format("({}: {:x})", input_type_names[(u8)type], sdl_id); + return fmt::format("({}: {:x})", input_type_names[static_cast(type)], sdl_id); } }; @@ -96,12 +103,19 @@ const std::map string_to_cbutton_map = { {"options", SDL_GAMEPAD_BUTTON_START}, // these are outputs only (touchpad can only be bound to itself) - {"touchpad", SDL_GAMEPAD_BUTTON_TOUCHPAD}, + {"touchpad_left", SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT}, + {"touchpad_center", SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER}, + {"touchpad_right", SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT}, {"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE}, {"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE}, // this is only for input {"back", SDL_GAMEPAD_BUTTON_BACK}, + {"lpaddle_high", SDL_GAMEPAD_BUTTON_LEFT_PADDLE1}, + {"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2}, + {"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1}, + {"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2}, + {"mouse_gyro_roll_mode", MOUSE_GYRO_ROLL_MODE}, }; const std::map string_to_axis_map = { @@ -124,6 +138,7 @@ const std::map string_to_axis_map = { {"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}}, }; const std::map string_to_keyboard_key_map = { + // alphanumeric {"a", SDLK_A}, {"b", SDLK_B}, {"c", SDLK_C}, @@ -160,6 +175,73 @@ const std::map string_to_keyboard_key_map = { {"7", SDLK_7}, {"8", SDLK_8}, {"9", SDLK_9}, + + // symbols + {"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}, + {"printscreen", SDLK_PRINTSCREEN}, + {"scrolllock", SDLK_SCROLLLOCK}, + {"pausebreak", SDLK_PAUSE}, + {"backspace", SDLK_BACKSPACE}, + {"delete", SDLK_DELETE}, + {"insert", SDLK_INSERT}, + {"home", SDLK_HOME}, + {"end", SDLK_END}, + {"pgup", SDLK_PAGEUP}, + {"pgdown", SDLK_PAGEDOWN}, + {"tab", SDLK_TAB}, + {"capslock", SDLK_CAPSLOCK}, + {"enter", SDLK_RETURN}, + {"lshift", SDLK_LSHIFT}, + {"rshift", SDLK_RSHIFT}, + {"lctrl", SDLK_LCTRL}, + {"rctrl", SDLK_RCTRL}, + {"lalt", SDLK_LALT}, + {"ralt", SDLK_RALT}, + {"lmeta", SDLK_LGUI}, + {"rmeta", SDLK_RGUI}, + {"lwin", SDLK_LGUI}, + {"rwin", SDLK_RGUI}, + {"space", SDLK_SPACE}, + {"up", SDLK_UP}, + {"down", SDLK_DOWN}, + {"left", SDLK_LEFT}, + {"right", SDLK_RIGHT}, + + // keypad {"kp0", SDLK_KP_0}, {"kp1", SDLK_KP_1}, {"kp2", SDLK_KP_2}, @@ -170,43 +252,16 @@ const std::map string_to_keyboard_key_map = { {"kp7", SDLK_KP_7}, {"kp8", SDLK_KP_8}, {"kp9", SDLK_KP_9}, - {"comma", SDLK_COMMA}, - {"period", SDLK_PERIOD}, - {"question", SDLK_QUESTION}, - {"semicolon", SDLK_SEMICOLON}, - {"minus", SDLK_MINUS}, - {"underscore", SDLK_UNDERSCORE}, - {"lparenthesis", SDLK_LEFTPAREN}, - {"rparenthesis", SDLK_RIGHTPAREN}, - {"lbracket", SDLK_LEFTBRACKET}, - {"rbracket", SDLK_RIGHTBRACKET}, - {"lbrace", SDLK_LEFTBRACE}, - {"rbrace", SDLK_RIGHTBRACE}, - {"backslash", SDLK_BACKSLASH}, - {"dash", SDLK_SLASH}, - {"enter", SDLK_RETURN}, - {"space", SDLK_SPACE}, - {"tab", SDLK_TAB}, - {"backspace", SDLK_BACKSPACE}, - {"escape", SDLK_ESCAPE}, - {"left", SDLK_LEFT}, - {"right", SDLK_RIGHT}, - {"up", SDLK_UP}, - {"down", SDLK_DOWN}, - {"lctrl", SDLK_LCTRL}, - {"rctrl", SDLK_RCTRL}, - {"lshift", SDLK_LSHIFT}, - {"rshift", SDLK_RSHIFT}, - {"lalt", SDLK_LALT}, - {"ralt", SDLK_RALT}, - {"lmeta", SDLK_LGUI}, - {"rmeta", SDLK_RGUI}, - {"lwin", SDLK_LGUI}, - {"rwin", SDLK_RGUI}, - {"home", SDLK_HOME}, - {"end", SDLK_END}, - {"pgup", SDLK_PAGEUP}, - {"pgdown", SDLK_PAGEDOWN}, + {"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 {"leftbutton", SDL_BUTTON_LEFT}, {"rightbutton", SDL_BUTTON_RIGHT}, {"middlebutton", SDL_BUTTON_MIDDLE}, @@ -216,15 +271,9 @@ const std::map string_to_keyboard_key_map = { {"mousewheeldown", SDL_MOUSE_WHEEL_DOWN}, {"mousewheelleft", SDL_MOUSE_WHEEL_LEFT}, {"mousewheelright", SDL_MOUSE_WHEEL_RIGHT}, - {"kpperiod", SDLK_KP_PERIOD}, - {"kpcomma", SDLK_KP_COMMA}, - {"kpdivide", SDLK_KP_DIVIDE}, - {"kpmultiply", SDLK_KP_MULTIPLY}, - {"kpminus", SDLK_KP_MINUS}, - {"kpplus", SDLK_KP_PLUS}, - {"kpenter", SDLK_KP_ENTER}, - {"kpequals", SDLK_KP_EQUALS}, - {"capslock", SDLK_CAPSLOCK}, + + // no binding + {"unmapped", SDL_UNMAPPED}, }; void ParseInputConfig(const std::string game_id); @@ -320,6 +369,7 @@ public: // returns an InputEvent based on the event type (keyboard, mouse buttons/wheel, or controller) static InputEvent GetInputEventFromSDLEvent(const SDL_Event& e); }; + class ControllerOutput { static GameController* controller; diff --git a/src/input/input_mouse.cpp b/src/input/input_mouse.cpp index 5eb0aab3e..3c718dbd5 100644 --- a/src/input/input_mouse.cpp +++ b/src/input/input_mouse.cpp @@ -3,6 +3,7 @@ #include +#include "common/assert.h" #include "common/types.h" #include "input/controller.h" #include "input_mouse.h" @@ -13,12 +14,19 @@ namespace Input { int mouse_joystick_binding = 0; float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250; +bool mouse_gyro_roll_mode = false; Uint32 mouse_polling_id = 0; -bool mouse_enabled = false; +MouseMode mouse_mode = MouseMode::Off; -// We had to go through 3 files of indirection just to update a flag -void ToggleMouseEnabled() { - mouse_enabled = !mouse_enabled; +// Switches mouse to a set mode or turns mouse emulation off if it was already in that mode. +// Returns whether the mode is turned on. +bool ToggleMouseModeTo(MouseMode m) { + if (mouse_mode == m) { + mouse_mode = MouseMode::Off; + } else { + mouse_mode = m; + } + return mouse_mode == m; } void SetMouseToJoystick(int joystick) { @@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) { mouse_speed_offset = mso; } -Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { - auto* controller = (GameController*)param; - if (!mouse_enabled) - return interval; +void SetMouseGyroRollMode(bool mode) { + mouse_gyro_roll_mode = mode; +} + +void EmulateJoystick(GameController* controller, u32 interval) { Axis axis_x, axis_y; switch (mouse_joystick_binding) { @@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { axis_y = Axis::RightY; break; default: - return interval; // no update needed + return; // no update needed } float d_x = 0, d_y = 0; @@ -67,7 +76,35 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { controller->Axis(0, axis_x, GetAxis(-0x80, 0x7f, 0)); controller->Axis(0, axis_y, GetAxis(-0x80, 0x7f, 0)); } +} +constexpr float constant_down_accel[3] = {0.0f, 10.0f, 0.0f}; +void EmulateGyro(GameController* controller, u32 interval) { + // LOG_INFO(Input, "todo gyro"); + float d_x = 0, d_y = 0; + SDL_GetRelativeMouseState(&d_x, &d_y); + controller->Acceleration(1, constant_down_accel); + float gyro_from_mouse[3] = {-d_y / 100, -d_x / 100, 0.0f}; + if (mouse_gyro_roll_mode) { + gyro_from_mouse[1] = 0.0f; + gyro_from_mouse[2] = -d_x / 100; + } + controller->Gyro(1, gyro_from_mouse); +} + +Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) { + auto* controller = (GameController*)param; + switch (mouse_mode) { + case MouseMode::Joystick: + EmulateJoystick(controller, interval); + break; + case MouseMode::Gyro: + EmulateGyro(controller, interval); + break; + + default: + break; + } return interval; } diff --git a/src/input/input_mouse.h b/src/input/input_mouse.h index da18ee04e..a56ef2d8f 100644 --- a/src/input/input_mouse.h +++ b/src/input/input_mouse.h @@ -8,11 +8,21 @@ namespace Input { -void ToggleMouseEnabled(); +enum MouseMode { + Off = 0, + Joystick, + Gyro, +}; + +bool ToggleMouseModeTo(MouseMode m); void SetMouseToJoystick(int joystick); void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset); +void SetMouseGyroRollMode(bool mode); -// Polls the mouse for changes, and simulates joystick movement from it. +void EmulateJoystick(GameController* controller, u32 interval); +void EmulateGyro(GameController* controller, u32 interval); + +// Polls the mouse for changes Uint32 MousePolling(void* param, Uint32 id, Uint32 interval); } // namespace Input diff --git a/src/qt_gui/about_dialog.cpp b/src/qt_gui/about_dialog.cpp index 90fb14236..627a0c052 100644 --- a/src/qt_gui/about_dialog.cpp +++ b/src/qt_gui/about_dialog.cpp @@ -12,7 +12,8 @@ #include "main_window_themes.h" #include "ui_about_dialog.h" -AboutDialog::AboutDialog(QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { +AboutDialog::AboutDialog(std::shared_ptr gui_settings, QWidget* parent) + : QDialog(parent), ui(new Ui::AboutDialog), m_gui_settings(std::move(gui_settings)) { ui->setupUi(this); preloadImages(); @@ -57,7 +58,7 @@ void AboutDialog::preloadImages() { } void AboutDialog::updateImagesForCurrentTheme() { - Theme currentTheme = static_cast(Config::getMainWindowTheme()); + Theme currentTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); bool isDarkTheme = (currentTheme == Theme::Dark || currentTheme == Theme::Green || currentTheme == Theme::Blue || currentTheme == Theme::Violet); if (isDarkTheme) { @@ -188,7 +189,7 @@ void AboutDialog::removeHoverEffect(QLabel* label) { } bool AboutDialog::isDarkTheme() const { - Theme currentTheme = static_cast(Config::getMainWindowTheme()); + Theme currentTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); return currentTheme == Theme::Dark || currentTheme == Theme::Green || currentTheme == Theme::Blue || currentTheme == Theme::Violet; } diff --git a/src/qt_gui/about_dialog.h b/src/qt_gui/about_dialog.h index 42e8d557a..b74cdfd1a 100644 --- a/src/qt_gui/about_dialog.h +++ b/src/qt_gui/about_dialog.h @@ -8,6 +8,7 @@ #include #include #include +#include "gui_settings.h" namespace Ui { class AboutDialog; @@ -17,7 +18,7 @@ class AboutDialog : public QDialog { Q_OBJECT public: - explicit AboutDialog(QWidget* parent = nullptr); + explicit AboutDialog(std::shared_ptr gui_settings, QWidget* parent = nullptr); ~AboutDialog(); bool eventFilter(QObject* obj, QEvent* event); @@ -33,4 +34,5 @@ private: QPixmap originalImages[5]; QPixmap invertedImages[5]; + std::shared_ptr m_gui_settings; }; 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 e686f044d..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,15 +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", "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/elf_viewer.cpp b/src/qt_gui/elf_viewer.cpp index e80fa25c1..8d472755b 100644 --- a/src/qt_gui/elf_viewer.cpp +++ b/src/qt_gui/elf_viewer.cpp @@ -3,10 +3,12 @@ #include "elf_viewer.h" -ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) { - dir_list_std = Config::getElfViewer(); - for (const auto& str : dir_list_std) { - dir_list.append(QString::fromStdString(str)); +ElfViewer::ElfViewer(std::shared_ptr gui_settings, QWidget* parent) + : QTableWidget(parent), m_gui_settings(std::move(gui_settings)) { + + list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_elfDirs)); + for (const auto& str : list) { + dir_list.append(str); } CheckElfFolders(); @@ -55,11 +57,11 @@ void ElfViewer::OpenElfFolder() { } std::ranges::sort(m_elf_list); OpenElfFiles(); - dir_list_std.clear(); + list.clear(); for (auto dir : dir_list) { - dir_list_std.push_back(dir.toStdString()); + list.push_back(dir); } - Config::setElfViewer(dir_list_std); + m_gui_settings->SetValue(gui::gen_elfDirs, gui_settings::List2Var(list)); } else { // qDebug() << "Folder selection canceled."; } diff --git a/src/qt_gui/elf_viewer.h b/src/qt_gui/elf_viewer.h index 1a65d70de..6256abf31 100644 --- a/src/qt_gui/elf_viewer.h +++ b/src/qt_gui/elf_viewer.h @@ -11,7 +11,7 @@ class ElfViewer : public QTableWidget { Q_OBJECT public: - explicit ElfViewer(QWidget* parent = nullptr); + explicit ElfViewer(std::shared_ptr gui_settings, QWidget* parent = nullptr); QStringList m_elf_list; private: @@ -21,7 +21,8 @@ private: Core::Loader::Elf m_elf_file; QStringList dir_list; QStringList elf_headers_list; - std::vector dir_list_std; + QList list; + std::shared_ptr m_gui_settings; void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) { QTableWidgetItem* item = new QTableWidgetItem(); diff --git a/src/qt_gui/game_grid_frame.cpp b/src/qt_gui/game_grid_frame.cpp index 66679dc71..dda73fa17 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -34,7 +34,9 @@ GameGridFrame::GameGridFrame(std::shared_ptr gui_settings, connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this, &GameGridFrame::RefreshGridBackgroundImage); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, + m_gui_settings, this, false); + PopulateGameGrid(m_game_info->m_games, false); }); } @@ -88,10 +90,13 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from this->crtColumn = -1; QVector m_games_; this->clearContents(); - if (fromSearch) + if (fromSearch) { + SortByFavorite(&m_games_search); m_games_ = m_games_search; - else + } else { + SortByFavorite(&(m_game_info->m_games)); m_games_ = m_game_info->m_games; + } m_games_shared = std::make_shared>(m_games_); icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event. @@ -110,14 +115,21 @@ void GameGridFrame::PopulateGameGrid(QVector m_games_search, bool from for (int i = 0; i < m_games_.size(); i++) { QWidget* widget = new QWidget(); QVBoxLayout* layout = new QVBoxLayout(); - QLabel* image_label = new QLabel(); + + QWidget* image_container = new QWidget(); + image_container->setFixedSize(icon_size, icon_size); + + QLabel* image_label = new QLabel(image_container); QImage icon = m_games_[gameCounter].icon.scaled( QSize(icon_size, icon_size), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); image_label->setFixedSize(icon.width(), icon.height()); image_label->setPixmap(QPixmap::fromImage(icon)); + image_label->move(0, 0); + SetFavoriteIcon(image_container, m_games_, gameCounter); + QLabel* name_label = new QLabel(QString::fromStdString(m_games_[gameCounter].serial)); name_label->setAlignment(Qt::AlignHCenter); - layout->addWidget(image_label); + layout->addWidget(image_container); layout->addWidget(name_label); // Resizing of font-size. @@ -225,3 +237,43 @@ void GameGridFrame::resizeEvent(QResizeEvent* event) { bool GameGridFrame::IsValidCellSelected() { return validCellSelected; } + +void GameGridFrame::SetFavoriteIcon(QWidget* parentWidget, QVector m_games_, + int gameCounter) { + QString serialStr = QString::fromStdString(m_games_[gameCounter].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + + QLabel* label = new QLabel(parentWidget); + label->setPixmap(QPixmap(":images/favorite_icon.png") + .scaled(icon_size / 3.8, icon_size / 3.8, Qt::KeepAspectRatio, + Qt::SmoothTransformation)); + label->move(icon_size - icon_size / 4, 2); + label->raise(); + label->setVisible(isFavorite); + label->setObjectName("favoriteIcon"); +} + +void GameGridFrame::SortByFavorite(QVector* game_list) { + std::sort(game_list->begin(), game_list->end(), [this](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b); + }); +} + +bool GameGridFrame::CompareWithFavorite(GameInfo a, GameInfo b) { + std::string serial_a = a.serial; + std::string serial_b = b.serial; + QString serialStr_a = QString::fromStdString(a.serial); + QString serialStr_b = QString::fromStdString(b.serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite_a = list.contains(serialStr_a); + bool isFavorite_b = list.contains(serialStr_b); + if (isFavorite_a != isFavorite_b) { + return isFavorite_a; + } else { + std::string name_a = a.name, name_b = b.name; + std::transform(name_a.begin(), name_a.end(), name_a.begin(), ::tolower); + std::transform(name_b.begin(), name_b.end(), name_b.begin(), ::tolower); + return name_a < name_b; + } +} diff --git a/src/qt_gui/game_grid_frame.h b/src/qt_gui/game_grid_frame.h index 22d278a21..0a12deb1c 100644 --- a/src/qt_gui/game_grid_frame.h +++ b/src/qt_gui/game_grid_frame.h @@ -39,6 +39,8 @@ private: int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes std::shared_ptr m_gui_settings; + void SetFavoriteIcon(QWidget* parentWidget, QVector m_games_, int gameCounter); + bool CompareWithFavorite(GameInfo a, GameInfo b); public: explicit GameGridFrame(std::shared_ptr gui_settings, @@ -47,6 +49,7 @@ public: QWidget* parent = nullptr); void PopulateGameGrid(QVector m_games, bool fromSearch); bool IsValidCellSelected(); + void SortByFavorite(QVector* game_list); bool cellClicked = false; int icon_size; diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index dd10e0f8b..e4c40b4f9 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -16,6 +16,7 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, : QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get), m_compat_info(compat_info_get) { icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt(); + last_favorite = ""; this->setShowGrid(false); this->setEditTriggers(QAbstractItemView::NoEditTriggers); this->setSelectionBehavior(QAbstractItemView::SelectRows); @@ -30,9 +31,8 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, this->horizontalHeader()->setContextMenuPolicy(Qt::CustomContextMenu); this->horizontalHeader()->setHighlightSections(false); this->horizontalHeader()->setSortIndicatorShown(true); - this->horizontalHeader()->setStretchLastSection(true); this->setContextMenuPolicy(Qt::CustomContextMenu); - this->setColumnCount(10); + this->setColumnCount(11); this->setColumnWidth(1, 300); // Name this->setColumnWidth(2, 140); // Compatibility this->setColumnWidth(3, 120); // Serial @@ -41,14 +41,18 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, this->setColumnWidth(6, 90); // Size this->setColumnWidth(7, 90); // Version this->setColumnWidth(8, 120); // Play Time + this->setColumnWidth(10, 90); // Favorite QStringList headers; headers << tr("Icon") << tr("Name") << tr("Compatibility") << tr("Serial") << tr("Region") - << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path"); + << tr("Firmware") << tr("Size") << tr("Version") << tr("Play Time") << tr("Path") + << tr("Favorite"); this->setHorizontalHeaderLabels(headers); this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); this->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); this->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Fixed); + this->horizontalHeader()->setSectionResizeMode(9, QHeaderView::Stretch); + this->horizontalHeader()->setSectionResizeMode(10, QHeaderView::Fixed); PopulateGameList(); connect(this, &QTableWidget::currentCellChanged, this, &GameListFrame::onCurrentCellChanged); @@ -65,17 +69,24 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, SortNameDescending(columnIndex); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::DescendingOrder); ListSortedAsc = false; + sortColumn = columnIndex; } else { SortNameAscending(columnIndex); this->horizontalHeader()->setSortIndicator(columnIndex, Qt::AscendingOrder); ListSortedAsc = true; + sortColumn = columnIndex; } this->clearContents(); PopulateGameList(false); }); connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) { - m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, true); + int changedFavorite = m_gui_context_menus.RequestGameMenu( + pos, m_game_info->m_games, m_compat_info, m_gui_settings, this, true); + if (changedFavorite) { + last_favorite = m_game_info->m_games[this->currentRow()].serial; + PopulateGameList(false); + } }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { @@ -83,6 +94,19 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, auto url_issues = "https://github.com/shadps4-emu/shadps4-game-compatibility/issues/"; QDesktopServices::openUrl( QUrl(url_issues + m_game_info->m_games[row].compatibility.issue_number)); + } else if (column == 10) { + last_favorite = m_game_info->m_games[row].serial; + QString serialStr = QString::fromStdString(last_favorite); + QList list = + gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + if (isFavorite) { + list.removeOne(serialStr); + } else { + list.append(serialStr); + } + m_gui_settings->SetValue(gui::favorites_list, gui_settings::List2Var(list)); + PopulateGameList(false); } }); } @@ -117,10 +141,7 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) { this->setRowCount(m_game_info->m_games.size()); ResizeIcons(icon_size); - if (isInitialPopulation) { - SortNameAscending(1); // Column 1 = Name - ResizeIcons(icon_size); - } + ApplyLastSorting(isInitialPopulation); for (int i = 0; i < m_game_info->m_games.size(); i++) { SetTableItem(i, 1, QString::fromStdString(m_game_info->m_games[i].name)); @@ -129,6 +150,11 @@ void GameListFrame::PopulateGameList(bool isInitialPopulation) { SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 7, QString::fromStdString(m_game_info->m_games[i].version)); + SetFavoriteIcon(i, 10); + + if (m_game_info->m_games[i].serial == last_favorite && !isInitialPopulation) { + this->setCurrentCell(i, 10); + } m_game_info->m_games[i].compatibility = m_compat_info->GetCompatibilityInfo(m_game_info->m_games[i].serial); @@ -226,20 +252,50 @@ void GameListFrame::resizeEvent(QResizeEvent* event) { RefreshListBackgroundImage(); } +bool GameListFrame::CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending) { + std::string serial_a = a.serial; + std::string serial_b = b.serial; + QString serialStr_a = QString::fromStdString(a.serial); + QString serialStr_b = QString::fromStdString(b.serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite_a = list.contains(serialStr_a); + bool isFavorite_b = list.contains(serialStr_b); + if (isFavorite_a != isFavorite_b) { + return isFavorite_a; + } else if (ascending) { + return CompareStringsAscending(a, b, columnIndex); + } else { + return CompareStringsDescending(a, b, columnIndex); + } +} + void GameListFrame::SortNameAscending(int columnIndex) { std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), - [columnIndex](const GameInfo& a, const GameInfo& b) { - return CompareStringsAscending(a, b, columnIndex); + [this, columnIndex](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b, columnIndex, true); }); } void GameListFrame::SortNameDescending(int columnIndex) { std::sort(m_game_info->m_games.begin(), m_game_info->m_games.end(), - [columnIndex](const GameInfo& a, const GameInfo& b) { - return CompareStringsDescending(a, b, columnIndex); + [this, columnIndex](const GameInfo& a, const GameInfo& b) { + return this->CompareWithFavorite(a, b, columnIndex, false); }); } +void GameListFrame::ApplyLastSorting(bool isInitialPopulation) { + if (isInitialPopulation) { + SortNameAscending(1); // Column 1 = Name + ResizeIcons(icon_size); + } else if (ListSortedAsc) { + SortNameAscending(sortColumn); + ResizeIcons(icon_size); + } else { + SortNameDescending(sortColumn); + ResizeIcons(icon_size); + } +} + void GameListFrame::ResizeIcons(int iconSize) { for (int index = 0; auto& game : m_game_info->m_games) { QImage scaledPixmap = game.icon.scaled(QSize(iconSize, iconSize), Qt::KeepAspectRatio, @@ -390,6 +446,35 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) { this->setCellWidget(row, column, widget); } +void GameListFrame::SetFavoriteIcon(int row, int column) { + + QString serialStr = QString::fromStdString(m_game_info->m_games[row].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + + QTableWidgetItem* item = new QTableWidgetItem(); + QImage scaledPixmap = QImage(":images/favorite_icon.png"); + + scaledPixmap = scaledPixmap.scaledToHeight(this->columnWidth(column) / 2.5); + scaledPixmap = scaledPixmap.scaledToWidth(this->columnWidth(column) / 2.5); + QWidget* widget = new QWidget(this); + QVBoxLayout* layout = new QVBoxLayout(widget); + QLabel* label = new QLabel(widget); + label->setPixmap(QPixmap::fromImage(scaledPixmap)); + label->setObjectName("favoriteIcon"); + label->setVisible(isFavorite); + + layout->setAlignment(Qt::AlignCenter); + layout->addWidget(label); + widget->setLayout(layout); + this->setItem(row, column, item); + this->setCellWidget(row, column, widget); + + if (column > 0) { + this->horizontalHeader()->setSectionResizeMode(column - 1, QHeaderView::Stretch); + } +} + QString GameListFrame::GetPlayTime(const std::string& serial) { QString playTime; const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); @@ -422,4 +507,4 @@ QString GameListFrame::GetPlayTime(const std::string& serial) { QTableWidgetItem* GameListFrame::GetCurrentItem() { return m_current_item; -} \ No newline at end of file +} diff --git a/src/qt_gui/game_list_frame.h b/src/qt_gui/game_list_frame.h index f70d73054..d1e065864 100644 --- a/src/qt_gui/game_list_frame.h +++ b/src/qt_gui/game_list_frame.h @@ -42,11 +42,13 @@ public Q_SLOTS: private: void SetTableItem(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr); + void SetFavoriteIcon(int row, int column); void SetCompatibilityItem(int row, int column, CompatibilityEntry entry); QString GetPlayTime(const std::string& serial); QList m_columnActs; GameInfoClass* game_inf_get = nullptr; bool ListSortedAsc = true; + int sortColumn = 1; QTableWidgetItem* m_current_item = nullptr; int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation std::filesystem::path m_current_game_path; // Track current game path to detect changes @@ -55,6 +57,7 @@ private: public: void PopulateGameList(bool isInitialPopulation = true); void ResizeIcons(int iconSize); + void ApplyLastSorting(bool isInitialPopulation); QTableWidgetItem* GetCurrentItem(); QImage backgroundImage; GameListUtils m_game_list_utils; @@ -63,6 +66,7 @@ public: std::shared_ptr m_compat_info; int icon_size; + std::string last_favorite; static float parseAsFloat(const std::string& str, const int& offset) { return std::stof(str.substr(0, str.size() - offset)); @@ -130,4 +134,6 @@ public: return false; } } + + bool CompareWithFavorite(GameInfo a, GameInfo b, int columnIndex, bool ascending); }; diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 46a40c5cd..6c384c4bc 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -16,6 +16,7 @@ #include "common/scm_rev.h" #include "compatibility_info.h" #include "game_info.h" +#include "gui_settings.h" #include "trophy_viewer.h" #ifdef Q_OS_WIN @@ -30,11 +31,13 @@ class GuiContextMenus : public QObject { Q_OBJECT public: - void RequestGameMenu(const QPoint& pos, QVector& m_games, - std::shared_ptr m_compat_info, - QTableWidget* widget, bool isList) { + int RequestGameMenu(const QPoint& pos, QVector& m_games, + std::shared_ptr m_compat_info, + std::shared_ptr settings, QTableWidget* widget, bool isList) { QPoint global_pos = widget->viewport()->mapToGlobal(pos); + std::shared_ptr m_gui_settings = std::move(settings); int itemID = 0; + int changedFavorite = 0; if (isList) { itemID = widget->currentRow(); } else { @@ -43,7 +46,7 @@ public: // Do not show the menu if no item is selected if (itemID < 0 || itemID >= m_games.size()) { - return; + return changedFavorite; } // Setup menu. @@ -63,11 +66,22 @@ public: menu.addMenu(openFolderMenu); + QString serialStr = QString::fromStdString(m_games[itemID].serial); + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::favorites_list)); + bool isFavorite = list.contains(serialStr); + QAction* toggleFavorite; + + if (isFavorite) { + toggleFavorite = new QAction(tr("Remove from Favorites"), widget); + } else { + toggleFavorite = new QAction(tr("Add to Favorites"), widget); + } QAction createShortcut(tr("Create Shortcut"), widget); QAction openCheats(tr("Cheats / Patches"), widget); QAction openSfoViewer(tr("SFO Viewer"), widget); QAction openTrophyViewer(tr("Trophy Viewer"), widget); + menu.addAction(toggleFavorite); menu.addAction(&createShortcut); menu.addAction(&openCheats); menu.addAction(&openSfoViewer); @@ -128,7 +142,7 @@ public: // Show menu. auto selected = menu.exec(global_pos); if (!selected) { - return; + return changedFavorite; } if (selected == openGameFolder) { @@ -301,6 +315,16 @@ public: } } + if (selected == toggleFavorite) { + if (isFavorite) { + list.removeOne(serialStr); + } else { + list.append(serialStr); + } + m_gui_settings->SetValue(gui::favorites_list, gui_settings::List2Var(list)); + changedFavorite = 1; + } + if (selected == &openCheats) { QString gameName = QString::fromStdString(m_games[itemID].name); QString gameSerial = QString::fromStdString(m_games[itemID].serial); @@ -357,7 +381,7 @@ public: QString gameName = QString::fromStdString(m_games[itemID].name); TrophyViewer* trophyViewer = - new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); + new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames); trophyViewer->show(); connect(widget->parent(), &QWidget::destroyed, trophyViewer, [trophyViewer]() { trophyViewer->deleteLater(); }); @@ -586,6 +610,7 @@ public: QUrl(url_issues + m_games[itemID].compatibility.issue_number)); } } + return changedFavorite; } int GetRowIndex(QTreeWidget* treeWidget, QTreeWidgetItem* item) { diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h index da5542956..4c1eafc95 100644 --- a/src/qt_gui/gui_settings.h +++ b/src/qt_gui/gui_settings.h @@ -12,11 +12,18 @@ const QString general_settings = "general_settings"; const QString main_window = "main_window"; const QString game_list = "game_list"; const QString game_grid = "game_grid"; +const QString favorites = "favorites"; // general const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false); const gui_value gen_showChangeLog = gui_value(general_settings, "showChangeLog", false); const gui_value gen_updateChannel = gui_value(general_settings, "updateChannel", "Release"); +const gui_value gen_recentFiles = + gui_value(main_window, "recentFiles", QVariant::fromValue(QList())); +const gui_value gen_guiLanguage = gui_value(general_settings, "guiLanguage", "en_US"); +const gui_value gen_elfDirs = + gui_value(main_window, "elfDirs", QVariant::fromValue(QList())); +const gui_value gen_theme = gui_value(general_settings, "theme", 0); // main window settings const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray()); @@ -35,6 +42,10 @@ const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusic const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69); const gui_value gg_slider_pos = gui_value(game_grid, "slider_pos", 0); +// favorites list +const gui_value favorites_list = + gui_value(favorites, "favoritesList", QVariant::fromValue(QList())); + } // namespace gui class gui_settings : public settings { diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 596de6d30..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()); @@ -32,14 +36,34 @@ KBMSettings::KBMSettings(std::shared_ptr game_info_get, QWidget* ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial)); } - ButtonsList = { - ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, - ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, - ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton, - ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton, - ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, - ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton, - ui->LHalfButton, ui->RHalfButton}; + 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, + ui->LStickUpButton, + ui->LStickDownButton, + ui->LStickLeftButton, + ui->LStickRightButton, + ui->RStickUpButton, + ui->RStickDownButton, + ui->RStickLeftButton, + ui->RStickRightButton, + ui->LHalfButton, + ui->RHalfButton}; ButtonConnects(); SetUIValuestoMappings("default"); @@ -124,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() { @@ -144,187 +170,73 @@ void KBMSettings::EnableMappingButtons() { } } -void KBMSettings::SaveKBMConfig(bool CloseOnSave) { +void KBMSettings::SaveKBMConfig(bool close_on_save) { std::string output_string = "", input_string = ""; std::vector lines, inputs; + // Comment lines for config file lines.push_back("#Feeling lost? Check out the Help section!"); lines.push_back(""); lines.push_back("#Keyboard bindings"); lines.push_back(""); - input_string = ui->CrossButton->text().toStdString(); - output_string = "cross"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->CircleButton->text().toStdString(); - output_string = "circle"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TriangleButton->text().toStdString(); - output_string = "triangle"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->SquareButton->text().toStdString(); - output_string = "square"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->DpadUpButton->text().toStdString(); - output_string = "pad_up"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadDownButton->text().toStdString(); - output_string = "pad_down"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadLeftButton->text().toStdString(); - output_string = "pad_left"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->DpadRightButton->text().toStdString(); - output_string = "pad_right"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->L1Button->text().toStdString(); - output_string = "l1"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R1Button->text().toStdString(); - output_string = "r1"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->L2Button->text().toStdString(); - output_string = "l2"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R2Button->text().toStdString(); - output_string = "r2"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->L3Button->text().toStdString(); - output_string = "l3"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->R3Button->text().toStdString(); - output_string = "r3"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->OptionsButton->text().toStdString(); - output_string = "options"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->TouchpadButton->text().toStdString(); - output_string = "touchpad"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->LStickUpButton->text().toStdString(); - output_string = "axis_left_y_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickDownButton->text().toStdString(); - output_string = "axis_left_y_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickLeftButton->text().toStdString(); - output_string = "axis_left_x_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->LStickRightButton->text().toStdString(); - output_string = "axis_left_x_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->RStickUpButton->text().toStdString(); - output_string = "axis_right_y_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickDownButton->text().toStdString(); - output_string = "axis_right_y_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickLeftButton->text().toStdString(); - output_string = "axis_right_x_minus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - input_string = ui->RStickRightButton->text().toStdString(); - output_string = "axis_right_x_plus"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); - - lines.push_back(""); - - input_string = ui->MouseJoystickBox->currentText().toStdString(); - output_string = "mouse_to_joystick"; - if (input_string != "unmapped") + // 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; lines.push_back(output_string + " = " + input_string); + if (input_string != "unmapped") { + inputs.push_back(input_string); + } + }; - input_string = ui->LHalfButton->text().toStdString(); - output_string = "leftjoystick_halfmode"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); + add_mapping(ui->CrossButton->text(), "cross"); + add_mapping(ui->CircleButton->text(), "circle"); + add_mapping(ui->TriangleButton->text(), "triangle"); + add_mapping(ui->SquareButton->text(), "square"); - input_string = ui->RHalfButton->text().toStdString(); - output_string = "rightjoystick_halfmode"; - lines.push_back(output_string + " = " + input_string); - if (input_string != "unmapped") - inputs.push_back(input_string); + lines.push_back(""); + + 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(""); + + 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(""); + + 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(""); + + add_mapping(ui->LStickUpButton->text(), "axis_left_y_minus"); + add_mapping(ui->LStickDownButton->text(), "axis_left_y_plus"); + add_mapping(ui->LStickLeftButton->text(), "axis_left_x_minus"); + add_mapping(ui->LStickRightButton->text(), "axis_left_x_plus"); + + lines.push_back(""); + + add_mapping(ui->RStickUpButton->text(), "axis_right_y_minus"); + add_mapping(ui->RStickDownButton->text(), "axis_right_y_plus"); + add_mapping(ui->RStickLeftButton->text(), "axis_right_x_minus"); + add_mapping(ui->RStickRightButton->text(), "axis_right_x_plus"); + + lines.push_back(""); + + add_mapping(ui->MouseJoystickBox->currentText(), "mouse_to_joystick"); + add_mapping(ui->LHalfButton->text(), "leftjoystick_halfmode"); + add_mapping(ui->RHalfButton->text(), "rightjoystick_halfmode"); std::string DOString = std::format("{:.2f}", (ui->DeadzoneOffsetSlider->value() / 100.f)); std::string SMString = std::format("{:.1f}", (ui->SpeedMultiplierSlider->value() / 10.f)); @@ -363,9 +275,17 @@ void KBMSettings::SaveKBMConfig(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") { lines.push_back(line); } } @@ -374,6 +294,7 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) { // Prevent duplicate inputs for KBM as this breaks the 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; @@ -415,7 +336,12 @@ 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 (CloseOnSave) + if (GameRunning) { + Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default") + : Input::ParseInputConfig(RunningGameSerial); + } + + if (close_on_save) QWidget::close(); } @@ -432,7 +358,9 @@ void KBMSettings::SetDefault() { ui->R2Button->setText("o"); ui->R3Button->setText("m"); - ui->TouchpadButton->setText("space"); + ui->TouchpadLeftButton->setText("space"); + ui->TouchpadCenterButton->setText("unmapped"); + ui->TouchpadRightButton->setText("unmapped"); ui->OptionsButton->setText("enter"); ui->DpadUpButton->setText("up"); @@ -479,9 +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") { @@ -512,8 +447,12 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { ui->DpadRightButton->setText(QString::fromStdString(input_string)); } else if (output_string == "options") { ui->OptionsButton->setText(QString::fromStdString(input_string)); - } else if (output_string == "touchpad") { - ui->TouchpadButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_left") { + ui->TouchpadLeftButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_center") { + ui->TouchpadCenterButton->setText(QString::fromStdString(input_string)); + } else if (output_string == "touchpad_right") { + ui->TouchpadRightButton->setText(QString::fromStdString(input_string)); } else if (output_string == "axis_left_x_minus") { ui->LStickLeftButton->setText(QString::fromStdString(input_string)); } else if (output_string == "axis_left_x_plus") { @@ -541,7 +480,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { if (comma_pos != std::string::npos) { std::string DOstring = line.substr(equal_pos + 1, comma_pos); float DOffsetValue = std::stof(DOstring) * 100.0; - int DOffsetInt = int(DOffsetValue); + int DOffsetInt = static_cast(DOffsetValue); ui->DeadzoneOffsetSlider->setValue(DOffsetInt); QString LabelValue = QString::number(DOffsetInt / 100.0, 'f', 2); QString LabelString = tr("Deadzone Offset (def 0.50):") + " " + LabelValue; @@ -551,12 +490,8 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::size_t comma_pos2 = SMSOstring.find(','); if (comma_pos2 != std::string::npos) { std::string SMstring = SMSOstring.substr(0, comma_pos2); - float SpeedMultValue = std::stof(SMstring) * 10.0; - int SpeedMultInt = int(SpeedMultValue); - if (SpeedMultInt < 1) - SpeedMultInt = 1; - if (SpeedMultInt > 50) - SpeedMultInt = 50; + float SpeedMultValue = std::clamp(std::stof(SMstring) * 10.0f, 1.0f, 50.0f); + int SpeedMultInt = static_cast(SpeedMultValue); ui->SpeedMultiplierSlider->setValue(SpeedMultInt); LabelValue = QString::number(SpeedMultInt / 10.0, 'f', 1); LabelString = tr("Speed Multiplier (def 1.0):") + " " + LabelValue; @@ -564,7 +499,7 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { std::string SOstring = SMSOstring.substr(comma_pos2 + 1); float SOffsetValue = std::stof(SOstring) * 1000.0; - int SOffsetInt = int(SOffsetValue); + int SOffsetInt = static_cast(SOffsetValue); ui->SpeedOffsetSlider->setValue(SOffsetInt); LabelValue = QString::number(SOffsetInt / 1000.0, 'f', 3); LabelString = tr("Speed Offset (def 0.125):") + " " + LabelValue; @@ -610,7 +545,6 @@ void KBMSettings::StartTimer(QPushButton*& button) { MappingTimer = 3; EnableMapping = true; MappingCompleted = false; - modifier = ""; mapping = button->text(); DisableMappingButtons(); @@ -662,6 +596,16 @@ void KBMSettings::SetMapping(QString input) { MappingCompleted = true; } +// Helper lambda to get the modified button text based on the current keyboard modifiers +auto GetModifiedButton = [](Qt::KeyboardModifiers modifier, const std::string& m_button, + const std::string& n_button) -> QString { + if (QApplication::keyboardModifiers() & modifier) { + return QString::fromStdString(m_button); + } else { + return QString::fromStdString(n_button); + } +}; + bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { if (event->type() == QEvent::Close) { if (HelpWindowOpen) { @@ -682,213 +626,7 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { } switch (keyEvent->key()) { - case Qt::Key_Space: - pressedKeys.insert("space"); - break; - case Qt::Key_Comma: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpcomma"); - } else { - pressedKeys.insert("comma"); - } - break; - case Qt::Key_Period: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpperiod"); - } else { - pressedKeys.insert("period"); - } - break; - case Qt::Key_Slash: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - pressedKeys.insert("kpdivide"); - break; - case Qt::Key_Asterisk: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) - pressedKeys.insert("kpmultiply"); - break; - case Qt::Key_Question: - pressedKeys.insert("question"); - break; - case Qt::Key_Semicolon: - pressedKeys.insert("semicolon"); - break; - case Qt::Key_Minus: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpminus"); - } else { - pressedKeys.insert("minus"); - } - break; - case Qt::Key_Plus: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kpplus"); - } else { - pressedKeys.insert("plus"); - } - break; - case Qt::Key_ParenLeft: - pressedKeys.insert("lparenthesis"); - break; - case Qt::Key_ParenRight: - pressedKeys.insert("rparenthesis"); - break; - case Qt::Key_BracketLeft: - pressedKeys.insert("lbracket"); - break; - case Qt::Key_BracketRight: - pressedKeys.insert("rbracket"); - break; - case Qt::Key_BraceLeft: - pressedKeys.insert("lbrace"); - break; - case Qt::Key_BraceRight: - pressedKeys.insert("rbrace"); - break; - case Qt::Key_Backslash: - pressedKeys.insert("backslash"); - break; - case Qt::Key_Tab: - pressedKeys.insert("tab"); - break; - case Qt::Key_Backspace: - pressedKeys.insert("backspace"); - break; - case Qt::Key_Return: - pressedKeys.insert("enter"); - break; - case Qt::Key_Enter: - pressedKeys.insert("kpenter"); - break; - case Qt::Key_Home: - pressedKeys.insert("home"); - break; - case Qt::Key_End: - pressedKeys.insert("end"); - break; - case Qt::Key_PageDown: - pressedKeys.insert("pgdown"); - break; - case Qt::Key_PageUp: - pressedKeys.insert("pgup"); - break; - case Qt::Key_CapsLock: - pressedKeys.insert("capslock"); - break; - case Qt::Key_Escape: - pressedKeys.insert("unmapped"); - break; - case Qt::Key_Shift: - if (keyEvent->nativeScanCode() == rshift) { - pressedKeys.insert("rshift"); - } else { - pressedKeys.insert("lshift"); - } - break; - case Qt::Key_Alt: - if (keyEvent->nativeScanCode() == ralt) { - pressedKeys.insert("ralt"); - } else { - pressedKeys.insert("lalt"); - } - break; - case Qt::Key_Control: - if (keyEvent->nativeScanCode() == rctrl) { - pressedKeys.insert("rctrl"); - } else { - pressedKeys.insert("lctrl"); - } - break; - case Qt::Key_Meta: - activateWindow(); -#ifdef _WIN32 - pressedKeys.insert("lwin"); -#else - pressedKeys.insert("lmeta"); -#endif - case Qt::Key_1: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp1"); - } else { - pressedKeys.insert("1"); - } - break; - case Qt::Key_2: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp2"); - } else { - pressedKeys.insert("2"); - } - break; - case Qt::Key_3: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp3"); - } else { - pressedKeys.insert("3"); - } - break; - case Qt::Key_4: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp4"); - } else { - pressedKeys.insert("4"); - } - break; - case Qt::Key_5: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp5"); - } else { - pressedKeys.insert("5"); - } - break; - case Qt::Key_6: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp6"); - } else { - pressedKeys.insert("6"); - } - break; - case Qt::Key_7: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp7"); - } else { - pressedKeys.insert("7"); - } - break; - case Qt::Key_8: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp8"); - } else { - pressedKeys.insert("8"); - } - break; - case Qt::Key_9: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp9"); - } else { - pressedKeys.insert("9"); - } - break; - case Qt::Key_0: - if (Qt::KeypadModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("kp0"); - } else { - pressedKeys.insert("0"); - } - break; - case Qt::Key_Up: - activateWindow(); - pressedKeys.insert("up"); - break; - case Qt::Key_Down: - pressedKeys.insert("down"); - break; - case Qt::Key_Left: - pressedKeys.insert("left"); - break; - case Qt::Key_Right: - pressedKeys.insert("right"); - break; + // alphanumeric case Qt::Key_A: pressedKeys.insert("a"); break; @@ -962,87 +700,331 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { pressedKeys.insert("x"); break; case Qt::Key_Y: - pressedKeys.insert("Y"); + pressedKeys.insert("y"); break; case Qt::Key_Z: pressedKeys.insert("z"); break; + case Qt::Key_0: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp0", "0")); + break; + case Qt::Key_1: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp1", "1")); + break; + case Qt::Key_2: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp2", "2")); + break; + case Qt::Key_3: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp3", "3")); + break; + case Qt::Key_4: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp4", "4")); + break; + case Qt::Key_5: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp5", "5")); + break; + case Qt::Key_6: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp6", "6")); + break; + case Qt::Key_7: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp7", "7")); + break; + case Qt::Key_8: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp8", "8")); + break; + case Qt::Key_9: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp9", "9")); + break; + + // symbols + case Qt::Key_QuoteLeft: + pressedKeys.insert("grave"); + break; + case Qt::Key_AsciiTilde: + pressedKeys.insert("tilde"); + break; + case Qt::Key_Exclam: + pressedKeys.insert("exclamation"); + break; + case Qt::Key_At: + pressedKeys.insert("at"); + break; + case Qt::Key_NumberSign: + pressedKeys.insert("hash"); + break; + case Qt::Key_Dollar: + pressedKeys.insert("dollar"); + break; + case Qt::Key_Percent: + pressedKeys.insert("percent"); + break; + case Qt::Key_AsciiCircum: + pressedKeys.insert("caret"); + break; + case Qt::Key_Ampersand: + pressedKeys.insert("ampersand"); + break; + case Qt::Key_Asterisk: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpasterisk", "asterisk")); + break; + case Qt::Key_ParenLeft: + pressedKeys.insert("lparen"); + break; + case Qt::Key_ParenRight: + pressedKeys.insert("rparen"); + break; + case Qt::Key_Minus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpminus", "minus")); + break; + case Qt::Key_Underscore: + pressedKeys.insert("underscore"); + break; + case Qt::Key_Equal: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpequals", "equals")); + break; + case Qt::Key_Plus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpplus", "plus")); + break; + case Qt::Key_BracketLeft: + pressedKeys.insert("lbracket"); + break; + case Qt::Key_BracketRight: + pressedKeys.insert("rbracket"); + break; + case Qt::Key_BraceLeft: + pressedKeys.insert("lbrace"); + break; + case Qt::Key_BraceRight: + pressedKeys.insert("rbrace"); + break; + case Qt::Key_Backslash: + pressedKeys.insert("backslash"); + break; + case Qt::Key_Bar: + pressedKeys.insert("pipe"); + break; + case Qt::Key_Semicolon: + pressedKeys.insert("semicolon"); + break; + case Qt::Key_Colon: + pressedKeys.insert("colon"); + break; + case Qt::Key_Apostrophe: + pressedKeys.insert("apostrophe"); + break; + case Qt::Key_QuoteDbl: + pressedKeys.insert("quote"); + break; + case Qt::Key_Comma: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpcomma", "comma")); + break; + case Qt::Key_Less: + pressedKeys.insert("less"); + break; + case Qt::Key_Period: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpperiod", "period")); + break; + case Qt::Key_Greater: + pressedKeys.insert("greater"); + break; + case Qt::Key_Slash: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpslash", "slash")); + break; + case Qt::Key_Question: + pressedKeys.insert("question"); + break; + + // special keys + case Qt::Key_Print: + pressedKeys.insert("printscreen"); + break; + case Qt::Key_ScrollLock: + pressedKeys.insert("scrolllock"); + break; + case Qt::Key_Pause: + pressedKeys.insert("pausebreak"); + break; + case Qt::Key_Backspace: + pressedKeys.insert("backspace"); + break; + case Qt::Key_Insert: + pressedKeys.insert("insert"); + break; + case Qt::Key_Delete: + pressedKeys.insert("delete"); + break; + case Qt::Key_Home: + pressedKeys.insert("home"); + break; + case Qt::Key_End: + pressedKeys.insert("end"); + break; + case Qt::Key_PageUp: + pressedKeys.insert("pgup"); + break; + case Qt::Key_PageDown: + pressedKeys.insert("pgdown"); + break; + case Qt::Key_Tab: + pressedKeys.insert("tab"); + break; + case Qt::Key_CapsLock: + pressedKeys.insert("capslock"); + break; + case Qt::Key_Return: + pressedKeys.insert("enter"); + break; + case Qt::Key_Enter: + pressedKeys.insert(GetModifiedButton(Qt::ShiftModifier, "kpenter", "enter")); + break; + case Qt::Key_Shift: + if (keyEvent->nativeScanCode() == LSHIFT_KEY) { + pressedKeys.insert("lshift"); + } else { + pressedKeys.insert("rshift"); + } + break; + case Qt::Key_Alt: + if (keyEvent->nativeScanCode() == LALT_KEY) { + pressedKeys.insert("lalt"); + } else { + pressedKeys.insert("ralt"); + } + break; + case Qt::Key_Control: + if (keyEvent->nativeScanCode() == LCTRL_KEY) { + pressedKeys.insert("lctrl"); + } else { + pressedKeys.insert("rctrl"); + } + break; + case Qt::Key_Meta: +#ifdef _WIN32 + pressedKeys.insert("lwin"); +#else + pressedKeys.insert("lmeta"); +#endif + break; + case Qt::Key_Space: + pressedKeys.insert("space"); + break; + case Qt::Key_Up: + pressedKeys.insert("up"); + break; + case Qt::Key_Down: + pressedKeys.insert("down"); + break; + case Qt::Key_Left: + pressedKeys.insert("left"); + break; + case Qt::Key_Right: + pressedKeys.insert("right"); + break; + + // cancel mapping + case Qt::Key_Escape: + pressedKeys.insert("unmapped"); + break; + + // default case default: break; + // bottom text } 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; - default: - 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 + } + 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")); - } - } 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 - if (Qt::AltModifier & QApplication::keyboardModifiers()) { + 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 { - pressedKeys.insert("mousewheelright"); + QMessageBox::information( + this, tr("Cannot set mapping"), + tr("Mousewheel cannot be mapped to stick outputs")); } - } 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()) { - if (Qt::AltModifier & QApplication::keyboardModifiers()) { + } 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 { - pressedKeys.insert("mousewheelleft"); + 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")); } - } 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); } diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index bfeed2b01..1161fc30d 100644 --- a/src/qt_gui/kbm_gui.h +++ b/src/qt_gui/kbm_gui.h @@ -4,6 +4,18 @@ #include #include "game_info.h" +// macros > declaring constants +// also, we were only using one counterpart +#ifdef _WIN32 +#define LCTRL_KEY 29 +#define LALT_KEY 56 +#define LSHIFT_KEY 42 +#else +#define LCTRL_KEY 37 +#define LALT_KEY 64 +#define LSHIFT_KEY 50 +#endif + namespace Ui { class KBMSettings; } @@ -11,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(); @@ -25,22 +41,6 @@ private: std::unique_ptr ui; std::shared_ptr m_game_info; -#ifdef _WIN32 - const int lctrl = 29; - const int rctrl = 57373; - const int lalt = 56; - const int ralt = 57400; - const int lshift = 42; - const int rshift = 54; -#else - const int lctrl = 37; - const int rctrl = 105; - const int lalt = 64; - const int ralt = 108; - const int lshift = 50; - const int rshift = 62; -#endif - bool eventFilter(QObject* obj, QEvent* event) override; void ButtonConnects(); void SetUIValuestoMappings(std::string config_id); @@ -48,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_gui.ui b/src/qt_gui/kbm_gui.ui index 109423aa8..eb393254d 100644 --- a/src/qt_gui/kbm_gui.ui +++ b/src/qt_gui/kbm_gui.ui @@ -11,8 +11,8 @@ 0 0 - 1234 - 796 + 1235 + 842 @@ -44,8 +44,8 @@ 0 0 - 1214 - 746 + 1215 + 792 @@ -54,7 +54,7 @@ 0 0 1211 - 741 + 791 @@ -793,7 +793,7 @@ - + @@ -825,8 +825,11 @@ 0 - - 48 + + + 0 + 24 + Qt::FocusPolicy::NoFocus @@ -844,8 +847,11 @@ 0 - - 48 + + + 0 + 24 + Qt::FocusPolicy::NoFocus @@ -858,6 +864,55 @@ + + + + + 0 + 0 + + + + + 160 + 0 + + + + Options + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + + 0 + 0 + + + + Qt::FocusPolicy::NoFocus + + + unmapped + + + + + + @@ -1067,34 +1122,13 @@ - - - - 0 - 0 - - - - - 160 - 0 - - + - Touchpad Click + Touchpad Left - + - - - - 0 - 0 - - - - Qt::FocusPolicy::NoFocus - + unmapped @@ -1150,6 +1184,22 @@ + + + + Touchpad Center + + + + + + unmapped + + + + + + @@ -1204,7 +1254,7 @@ - + 0 @@ -1218,23 +1268,11 @@ - Options + Touchpad Right - - - 5 - - - 5 - - - 5 - - - 5 - + - + 0 diff --git a/src/qt_gui/kbm_help_dialog.cpp b/src/qt_gui/kbm_help_dialog.cpp index c13e18b59..b9055222c 100644 --- a/src/qt_gui/kbm_help_dialog.cpp +++ b/src/qt_gui/kbm_help_dialog.cpp @@ -78,16 +78,16 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { // Add expandable sections to container layout auto* quickstartSection = new ExpandableSection(tr("Quickstart"), quickstart()); - auto* faqSection = new ExpandableSection(tr("FAQ"), faq()); auto* syntaxSection = new ExpandableSection(tr("Syntax"), syntax()); - auto* specialSection = new ExpandableSection(tr("Special Bindings"), special()); auto* bindingsSection = new ExpandableSection(tr("Keybindings"), bindings()); + auto* specialSection = new ExpandableSection(tr("Special Bindings"), special()); + auto* faqSection = new ExpandableSection(tr("FAQ"), faq()); containerLayout->addWidget(quickstartSection); - containerLayout->addWidget(faqSection); containerLayout->addWidget(syntaxSection); - containerLayout->addWidget(specialSection); containerLayout->addWidget(bindingsSection); + containerLayout->addWidget(specialSection); + containerLayout->addWidget(faqSection); containerLayout->addStretch(1); // Scroll area wrapping the container @@ -110,3 +110,193 @@ HelpDialog::HelpDialog(bool* open_flag, QWidget* parent) : QDialog(parent) { connect(specialSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); connect(bindingsSection, &ExpandableSection::expandedChanged, this, &HelpDialog::adjustSize); } + +// Helper functions that store the text contents for each tab inb the HelpDialog menu +QString HelpDialog::quickstart() { + return R"( +The keyboard and controller remapping backend, GUI, and documentation have been written by kalaposfos. + +In this section, you will find information about the project and its features, as well as help setting up your ideal setup. +To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section. +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::syntax() { + return R"( +Below is the file format for mouse, keyboard, and controller inputs: + +Rules: +- You can bind up to 3 inputs to one button. +- Adding '#' at the beginning of a line creates a comment. +- Extra whitespace doesn't affect input. + =; is just as valid as = ; +- ';' at the end of a line is also optional. + +Syntax (aka how a line can look like): + #Comment line + = , , ; + = , ; + = ; + +Examples: + #Interact + cross = e; + #Heavy attack (in BB) + r2 = leftbutton, lshift; + #Move forward + axis_left_y_minus = w;)"; +} + +QString HelpDialog::bindings() { + return R"( +The following names should be interpreted without the '' around them. For inputs that have left and right versions, only the left one is shown, but the right version also works. + (Example: 'lshift', 'rshift') + +Keyboard: + Alphabet: + 'a', 'b', ..., 'z' + Numbers: + '0', '1', ..., '9' + Keypad: + 'kp0', 'kp1', ..., 'kp9', + 'kpperiod', 'kpcomma', 'kpslash', 'kpasterisk', 'kpminus', 'kpplus', 'kpequals', 'kpenter' + Symbols: + (See below) + Special keys: + 'escape' (text editor only), 'printscreen', 'scrolllock', 'pausebreak', + 'backspace', 'insert', 'delete', 'home', 'end', 'pgup', 'pgdown', 'tab', + 'capslock', 'enter', 'space' + Arrow keys: + 'up', 'down', 'left', 'right' + Modifier keys: + 'lctrl', 'lshift', 'lalt', 'lwin' = 'lmeta' (same input, different names, so if you are not on Windows and don't like calling this the Windows key, there is an alternative) + +Mouse: + 'leftbutton', 'rightbutton', 'middlebutton', 'sidebuttonforward', + 'sidebuttonback' + The following wheel inputs cannot be bound to axis input, only button: + 'mousewheelup', 'mousewheeldown', 'mousewheelleft', + 'mousewheelright' + +Controller: + The touchpad currently can't be rebound to anything else, but you can bind buttons to it. + If you have a controller that has different names for buttons, it will still work. Just look up the equivalent names for that controller. + The same left-right rule still applies here. + Buttons: + 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', + 'options', touchpad', 'up', 'down', 'left', 'right' + Input-only: + 'lpaddle_low', 'lpaddle_high' + Output-only: + 'touchpad_left', 'touchpad_center', 'touchpad_right' + Axes if you bind them to a button input: + 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', + 'axis_right_x_plus', ..., 'axis_right_y_minus', + 'l2' + Axes if you bind them to another axis input: + 'axis_left_x', 'axis_left_y', 'axis_right_x', 'axis_right_y', + 'l2' + +Invalid 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() { + return R"( +There are some extra bindings you can put in the config file that don't correspond to a controller input but something else. +You can find these here, with detailed comments, examples, and suggestions for most of them. + +'leftjoystick_halfmode' and 'rightjoystick_halfmode' = ; + These are a pair of input modifiers that change the way keyboard button-bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective 'joystick_halfmode' modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne. + +'mouse_to_joystick' = 'none', 'left' or 'right'; + This binds the mouse movement to either joystick. If it receives a value that is not 'left' or 'right', it defaults to 'none'. + +'mouse_movement_params' = float, float, float; + (If you don't know what a float is, it is a data type that stores decimal numbers.) + Default values: 0.5, 1, 0.125 + Let's break each parameter down: + 1st: 'mouse_deadzone_offset' should have a value between 0 and 1 (it gets clamped to that range anyway), with 0 being no offset and 1 being pushing the joystick to the max in the direction the mouse moved. + This controls the minimum distance the joystick gets moved when moving the mouse. If set to 0, it will emulate raw mouse input, which doesn't work very well due to deadzones preventing input if the movement is not large enough. + 2nd: 'mouse_speed' is just a standard multiplier to the mouse input speed. + If you input a negative number, the axis directions get reversed. Keep in mind that the offset can still push it back to positive if it's big enough. + 3rd: 'mouse_speed_offset' should be in the 0 to 1 range, with 0 being no offset and 1 being offsetting to the max possible value. + Let's set 'mouse_deadzone_offset' to 0.5, and 'mouse_speed_offset' to 0: This means that if we move the mouse very slowly, it still inputs a half-strength joystick input, and if we increase the speed, it would stay that way until we move faster than half the max speed. If we instead set this to 0.25, we now only need to move the mouse faster than the 0.5-0.25=0.25=quarter of the max speed, to get an increase in joystick speed. If we set it to 0.5, then even moving the mouse at 1 pixel per frame will result in a faster-than-minimum speed. + +'key_toggle' = , ; + This assigns a key to another key, and if pressed, toggles that key's virtual value. If it's on, then it doesn't matter if the key is pressed or not, the input handler will treat it as if it's pressed. + Let's say we want to be able to toggle 'l1' with 't'. You can then bind 'l1' to a key you won't use, like 'kpenter'. Then bind 't' to toggle that. You will end up with this: + l1 = kpenter; + key_toggle = t, kpenter; + +'analog_deadzone' = , , ; + This sets the deadzone range for various inputs. The first value is the minimum deadzone while the second is the maximum. Values go from 1 to 127 (no deadzone to max deadzone). + If you only want a minimum or maximum deadzone, set the other value to 1 or 127 respectively. + Valid devices: 'leftjoystick', 'rightjoystick', 'l2', 'r2' + +'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/kbm_help_dialog.h b/src/qt_gui/kbm_help_dialog.h index 3e39d4397..7f561397d 100644 --- a/src/qt_gui/kbm_help_dialog.h +++ b/src/qt_gui/kbm_help_dialog.h @@ -42,135 +42,9 @@ protected: private: bool* help_open_ptr; - QString quickstart() { - return - R"(The keyboard and controller remapping backend, GUI and documentation have been written by kalaposfos - -In this section, you will find information about the project, its features and help on setting up your ideal setup. -To view the config file's syntax, check out the Syntax tab, for keybind names, visit Normal Keybinds and Special Bindings, and if you are here to view emulator-wide keybinds, you can find it in the FAQ section. -This project started out because I didn't like the original unchangeable keybinds, but 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 ovbiously you can make adjustments however you like. -)"; - } - QString 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 emultor, if the debug menu is open --F8: Reparses the config file while in-game --F7: Toggles mouse capture and mouse input - -Q: How do I change between mouse and controller joystick input, and why is it even required? -A: You can switch between them with F7, and it is required, because mouse input is done with polling, which means mouse movement is checked every frame, and if it didn't move, the code manually sets the emulator's virtual controller to 0 (back 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 skip 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 this 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 syntax() { - return - R"(This is the full list of currently supported mouse, keyboard and controller inputs, and how to use them. -Emulator-reserved keys: F1 through F12 - -Syntax (aka how a line can look like): -#Comment line - = , , ; - = , ; - = ; - -Examples: -#Interact -cross = e; -#Heavy attack (in BB) -r2 = leftbutton, lshift; -#Move forward -axis_left_y_minus = w; - -You can make a comment line by putting # as the first character. -Whitespace doesn't matter, =; is just as valid as = ; -';' at the ends of lines is also optional. -)"; - } - QString bindings() { - return - R"(The following names should be interpreted without the '' around them, and for inputs that have left and right versions, only the left one is shown, but the right can be inferred from that. -Example: 'lshift', 'rshift' - -Keyboard: -Alphabet: 'a', 'b', ..., 'z' -Numbers: '0', '1', ..., '9' -Keypad: 'kp0', kp1', ..., 'kp9', 'kpperiod', 'kpcomma', - 'kpdivide', 'kpmultiply', 'kpdivide', 'kpplus', 'kpminus', 'kpenter' -Punctuation and misc: - 'space', 'comma', 'period', 'question', 'semicolon', 'minus', 'plus', 'lparenthesis', 'lbracket', 'lbrace', 'backslash', 'dash', - 'enter', 'tab', backspace', 'escape' -Arrow keys: 'up', 'down', 'left', 'right' -Modifier keys: - 'lctrl', 'lshift', 'lalt', 'lwin' = 'lmeta' (same input, different names, so if you are not on Windows and don't like calling this the Windows key, there is an alternative) - -Mouse: - 'leftbutton', 'rightbutton', 'middlebutton', 'sidebuttonforward', 'sidebuttonback' - The following wheel inputs cannot be bound to axis input, only button: - 'mousewheelup', 'mousewheeldown', 'mousewheelleft', 'mousewheelright' - -Controller: - The touchpad currently can't be rebound to anything else, but you can bind buttons to it. - If you have a controller that has different names for buttons, it will still work, just look up what are the equivalent names for that controller - The same left-right rule still applies here. - Buttons: - 'triangle', 'circle', 'cross', 'square', 'l1', 'l3', - 'options', touchpad', 'up', 'down', 'left', 'right' - Axes if you bind them to a button input: - 'axis_left_x_plus', 'axis_left_x_minus', 'axis_left_y_plus', 'axis_left_y_minus', - 'axis_right_x_plus', ..., 'axis_right_y_minus', - 'l2' - Axes if you bind them to another axis input: - 'axis_left_x' 'axis_left_y' 'axis_right_x' 'axis_right_y', - 'l2' -)"; - } - QString special() { - return - R"(There are some extra bindings you can put into the config file, that don't correspond to a controller input, but rather something else. -You can find these here, with detailed comments, examples and suggestions for most of them. - -'leftjoystick_halfmode' and 'rightjoystick_halfmode' = ; - These are a pair of input modifiers, that change the way keyboard button bound axes work. By default, those push the joystick to the max in their respective direction, but if their respective joystick_halfmode modifier value is true, they only push it... halfway. With this, you can change from run to walk in games like Bloodborne. - -'mouse_to_joystick' = 'none', 'left' or 'right'; - This binds the mouse movement to either joystick. If it recieves a value that is not 'left' or 'right', it defaults to 'none'. - -'mouse_movement_params' = float, float, float; - (If you don't know what a float is, it is a data type that stores non-whole numbers.) - Default values: 0.5, 1, 0.125 - Let's break each parameter down: - 1st: mouse_deadzone_offset: this value should have a value between 0 and 1 (It gets clamped to that range anyway), with 0 being no offset and 1 being pushing the joystick to the max in the direction the mouse moved. - This controls the minimum distance the joystick gets moved, when moving the mouse. If set to 0, it will emulate raw mouse input, which doesn't work very well due to deadzones preventing input if the movement is not large enough. - 2nd: mouse_speed: It's just a standard multiplier to the mouse input speed. - If you input a negative number, the axis directions get reversed (Keep in mind that the offset can still push it back to positive, if it's big enough) - 3rd: mouse_speed_offset: This also should be in the 0 to 1 range, with 0 being no offset and 1 being offsetting to the max possible value. - This is best explained through an example: Let's set mouse_deadzone to 0.5, and this to 0: This means that if we move the mousevery slowly, it still inputs a half-strength joystick input, and if we increase the speed, it would stay that way until we move faster than half the max speed. If we instead set this to 0.25, we now only need to move the mouse faster than the 0.5-0.25=0.25=quarter of the max speed, to get an increase in joystick speed. If we set it to 0.5, then even moving the mouse at 1 pixel per frame will result in a faster-than-minimum speed. - -'key_toggle' = , ; - This assigns a key to another key, and if pressed, toggles that key's virtual value. If it's on, then it doesn't matter if the key is pressed or not, the input handler will treat it as if it's pressed. - You can make an input toggleable with this, for example: Let's say we want to be able to toggle l1 with t. You can then bind l1 to a key you won't use, like kpenter, then bind t to toggle that, so you will end up with this: - l1 = kpenter; - key_toggle = t, kpenter; -'analog_deadzone' = , , ; - Values go from 1 to 127 (no deadzone to max deadzone), first is the inner, second is the outer deadzone - If you only want inner or outer deadzone, set the other to 1 or 127, respectively - Devices: leftjoystick, rightjoystick, l2, r2 -)"; - } + QString quickstart(); + QString faq(); + QString syntax(); + QString bindings(); + QString special(); }; \ No newline at end of file diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index c6da49182..f561bf392 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -39,8 +39,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi MainWindow::~MainWindow() { SaveWindowState(); - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); } bool MainWindow::Init() { @@ -297,7 +295,7 @@ void MainWindow::CreateDockWindows() { m_game_list_frame->setObjectName("gamelist"); m_game_grid_frame.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this)); m_game_grid_frame->setObjectName("gamegridlist"); - m_elf_viewer.reset(new ElfViewer(this)); + m_elf_viewer.reset(new ElfViewer(m_gui_settings, this)); m_elf_viewer->setObjectName("elflist"); int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt(); @@ -475,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(); }); @@ -492,7 +491,7 @@ void MainWindow::CreateConnects() { #endif connect(ui->aboutAct, &QAction::triggered, this, [this]() { - auto aboutDialog = new AboutDialog(this); + auto aboutDialog = new AboutDialog(m_gui_settings, this); aboutDialog->exec(); }); @@ -563,10 +562,8 @@ void MainWindow::CreateConnects() { m_game_grid_frame->hide(); m_elf_viewer->hide(); m_game_list_frame->show(); - if (m_game_list_frame->item(0, 0) == nullptr) { - m_game_list_frame->clearContents(); - m_game_list_frame->PopulateGameList(); - } + m_game_list_frame->clearContents(); + m_game_list_frame->PopulateGameList(); isTableList = true; m_gui_settings->SetValue(gui::gl_mode, 0); int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt(); @@ -771,14 +768,14 @@ void MainWindow::CreateConnects() { QString gameName = QString::fromStdString(firstGame.name); TrophyViewer* trophyViewer = - new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames); + new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames); trophyViewer->show(); }); // Themes connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Dark)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Dark)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -786,7 +783,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Light)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Light)); if (!isIconBlack) { SetUiIcons(true); isIconBlack = true; @@ -794,7 +791,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Green)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Green)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -802,7 +799,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Blue)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Blue)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -810,7 +807,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Violet)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Violet)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -818,7 +815,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeGruvbox, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Gruvbox, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Gruvbox)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Gruvbox)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -826,7 +823,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::TokyoNight, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::TokyoNight)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::TokyoNight)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -834,7 +831,7 @@ void MainWindow::CreateConnects() { }); connect(ui->setThemeOled, &QAction::triggered, &m_window_themes, [this]() { m_window_themes.SetWindowTheme(Theme::Oled, ui->mw_searchbar); - Config::setMainWindowTheme(static_cast(Theme::Oled)); + m_gui_settings->SetValue(gui::gen_theme, static_cast(Theme::Oled)); if (isIconBlack) { SetUiIcons(false); isIconBlack = false; @@ -850,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()) { @@ -981,7 +980,7 @@ void MainWindow::InstallDirectory() { } void MainWindow::SetLastUsedTheme() { - Theme lastTheme = static_cast(Config::getMainWindowTheme()); + Theme lastTheme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar); switch (lastTheme) { @@ -1122,33 +1121,32 @@ void MainWindow::HandleResize(QResizeEvent* event) { } void MainWindow::AddRecentFiles(QString filePath) { - std::vector vec = Config::getRecentFiles(); - if (!vec.empty()) { - if (filePath.toStdString() == vec.at(0)) { + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles)); + if (!list.empty()) { + if (filePath == list.at(0)) { return; } - auto it = std::find(vec.begin(), vec.end(), filePath.toStdString()); - if (it != vec.end()) { - vec.erase(it); + auto it = std::find(list.begin(), list.end(), filePath); + if (it != list.end()) { + list.erase(it); } } - vec.insert(vec.begin(), filePath.toStdString()); - if (vec.size() > 6) { - vec.pop_back(); + list.insert(list.begin(), filePath); + if (list.size() > 6) { + list.pop_back(); } - Config::setRecentFiles(vec); - const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); - Config::saveMainWindow(config_dir / "config.toml"); + m_gui_settings->SetValue(gui::gen_recentFiles, gui_settings::List2Var(list)); CreateRecentGameActions(); // Refresh the QActions. } void MainWindow::CreateRecentGameActions() { m_recent_files_group = new QActionGroup(this); ui->menuRecent->clear(); - std::vector vec = Config::getRecentFiles(); - for (int i = 0; i < vec.size(); i++) { + QList list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles)); + + for (int i = 0; i < list.size(); i++) { QAction* recentFileAct = new QAction(this); - recentFileAct->setText(QString::fromStdString(vec.at(i))); + recentFileAct->setText(list.at(i)); ui->menuRecent->addAction(recentFileAct); m_recent_files_group->addAction(recentFileAct); } @@ -1165,7 +1163,7 @@ void MainWindow::CreateRecentGameActions() { } void MainWindow::LoadTranslation() { - auto language = QString::fromStdString(Config::getEmulatorLanguage()); + auto language = m_gui_settings->GetValue(gui::gen_guiLanguage).toString(); const QString base_dir = QStringLiteral(":/translations"); QString base_path = QStringLiteral("%1/%2.qm").arg(base_dir).arg(language); @@ -1190,8 +1188,8 @@ void MainWindow::LoadTranslation() { } } -void MainWindow::OnLanguageChanged(const std::string& locale) { - Config::setEmulatorLanguage(locale); +void MainWindow::OnLanguageChanged(const QString& locale) { + m_gui_settings->SetValue(gui::gen_guiLanguage, locale); LoadTranslation(); } diff --git a/src/qt_gui/main_window.h b/src/qt_gui/main_window.h index 7f11f7310..5b880c15e 100644 --- a/src/qt_gui/main_window.h +++ b/src/qt_gui/main_window.h @@ -47,7 +47,7 @@ private Q_SLOTS: void ShowGameList(); void RefreshGameTable(); void HandleResize(QResizeEvent* event); - void OnLanguageChanged(const std::string& locale); + void OnLanguageChanged(const QString& locale); void toggleLabelsUnderIcons(); private: @@ -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.cpp b/src/qt_gui/settings.cpp index 44133dac5..4a9c1d375 100644 --- a/src/qt_gui/settings.cpp +++ b/src/qt_gui/settings.cpp @@ -75,3 +75,17 @@ void settings::SetValue(const QString& key, const QString& name, const QVariant& } } } +QVariant settings::List2Var(const QList& list) { + QByteArray ba; + QDataStream stream(&ba, QIODevice::WriteOnly); + stream << list; + return QVariant(ba); +} + +QList settings::Var2List(const QVariant& var) { + QList list; + QByteArray ba = var.toByteArray(); + QDataStream stream(&ba, QIODevice::ReadOnly); + stream >> list; + return list; +} \ No newline at end of file diff --git a/src/qt_gui/settings.h b/src/qt_gui/settings.h index da71fe01a..837804d00 100644 --- a/src/qt_gui/settings.h +++ b/src/qt_gui/settings.h @@ -35,6 +35,8 @@ public: QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const; QVariant GetValue(const gui_value& entry) const; + static QVariant List2Var(const QList& list); + static QList Var2List(const QVariant& var); public Q_SLOTS: /** Remove entry */ diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index da2b0dde3..c9d264587 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -123,11 +123,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, ui->hideCursorComboBox->addItem(tr("Idle")); ui->hideCursorComboBox->addItem(tr("Always")); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Left"), "left"); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Center"), "center"); - ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Right"), "right"); - ui->backButtonBehaviorComboBox->addItem(tr("None"), "none"); - InitializeEmulatorLanguages(); LoadValuesFromConfig(); @@ -366,7 +361,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr gui_settings, // Input ui->hideCursorGroupBox->installEventFilter(this); ui->idleTimeoutGroupBox->installEventFilter(this); - ui->backButtonBehaviorGroupBox->installEventFilter(this); // Graphics ui->graphicsAdapterGroupBox->installEventFilter(this); @@ -534,10 +528,6 @@ void SettingsDialog::LoadValuesFromConfig() { indexTab = 0; ui->tabWidgetSettings->setCurrentIndex(indexTab); - QString backButtonBehavior = QString::fromStdString( - toml::find_or(data, "Input", "backButtonBehavior", "left")); - int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); - ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); ui->motionControlsCheckBox->setChecked( toml::find_or(data, "Input", "isMotionControlsEnabled", true)); @@ -594,7 +584,7 @@ void SettingsDialog::OnLanguageChanged(int index) { ui->retranslateUi(this); - emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString()); + emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString()); } void SettingsDialog::OnCursorStateChanged(s16 index) { @@ -666,8 +656,6 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("Hide Cursor:\\nChoose when the cursor will disappear:\\nNever: You will always see the mouse.\\nidle: Set a time for it to disappear after being idle.\\nAlways: you will never see the mouse."); } else if (elementName == "idleTimeoutGroupBox") { text = tr("Hide Idle Cursor Timeout:\\nThe duration (seconds) after which the cursor that has been idle hides itself."); - } else if (elementName == "backButtonBehaviorGroupBox") { - text = tr("Back Button Behavior:\\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad."); } // Graphics @@ -745,8 +733,6 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) { void SettingsDialog::UpdateSettings() { - const QVector TouchPadIndex = {"left", "center", "right", "none"}; - Config::setBackButtonBehavior(TouchPadIndex[ui->backButtonBehaviorComboBox->currentIndex()]); Config::setIsFullscreen(screenModeMap.value(ui->displayModeComboBox->currentText()) != "Windowed"); Config::setFullscreenMode( @@ -886,4 +872,5 @@ void SettingsDialog::setDefaultValues() { } else { m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly"); } + m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US"); } \ No newline at end of file diff --git a/src/qt_gui/settings_dialog.h b/src/qt_gui/settings_dialog.h index db1bcf772..d9fbcb214 100644 --- a/src/qt_gui/settings_dialog.h +++ b/src/qt_gui/settings_dialog.h @@ -32,7 +32,7 @@ public: int exec() override; signals: - void LanguageChanged(const std::string& locale); + void LanguageChanged(const QString& locale); void CompatibilityChanged(); void BackgroundOpacityChanged(int opacity); diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 20e26775d..8d239b58c 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -1613,36 +1613,6 @@ 11 - - - - true - - - - 0 - 0 - - - - - 0 - 0 - - - - Back Button Behavior - - - - 11 - - - - - - - diff --git a/src/qt_gui/translations/ar_SA.ts b/src/qt_gui/translations/ar_SA.ts index e3f9781d2..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - زر لوحة اللمس - Mouse to Joystick تحويل الماوس إلى عصا التحكم @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller التحكم - - Back Button Behavior - سلوك زر العودة - Graphics الرسوميات @@ -1787,10 +1831,6 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. مدة إخفاء المؤشر عند الخمول:\nالوقت (بالثواني) الذي ينتظره المؤشر قبل أن يختفي تلقائيًا عند عدم استخدامه. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - سلوك زر الرجوع:\nيحدد وظيفة زر' الرجوع في وحدة التحكم لمحاكاة اللمس في موقع معيّن على لوحة اللمس الخاصة بـ PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. عرض بيانات التوافق:\nيعرض معلومات توافق الألعاب في عرض جدولي. فعّل ""تحديث التوافق عند بدء التشغيل"" للحصول على أحدث المعلومات. @@ -1815,22 +1855,6 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل Always دائماً - - Touchpad Left - الجانب الأيسر من لوحة اللمس - - - Touchpad Right - الجانب الأيمن من لوحة اللمس - - - Touchpad Center - مركز لوحة اللمس - - - None - لا شيء - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. جهاز الرسوميات:\nفي الأنظمة التي تحتوي على أكثر من معالج رسومي، اختر وحدة المعالجة الرسومية GPU التي سيستخدمها المحاكي من القائمة المنسدلة،\nأو اختر ""تحديد تلقائي"" ليتم اختيارها تلقائيًا. diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index 53a7dcd5d..c508a7146 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 + 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 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Click al touchpad - Mouse to Joystick Ratolí a palanca @@ -1184,10 +1220,22 @@ 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: + No es pot assignar una entrada més d'una vegada. S'han assignat de manera duplicada pels següents botons: %1 + + Touchpad Left + Touchpad esquerra + + + Touchpad Center + Touchpad centre + + + Touchpad Right + Touchpad dreta + MainWindow @@ -1546,10 +1594,6 @@ Controller Controlador - - Back Button Behavior - Comportament del botó de retrocés - Graphics Gràfics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Temps d'espera per ocultar el ratolí:\nLa duració (en segons) després de la qual el ratolí s'amaga si es troba inactiu. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportament del botó posterior:\nEstableix el botó posterior del controlador per simular el toc en una posició especificada del touchpad de PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostra les dades de compatibilitat:\nMostra informació sobre la compatibilitat a la vista de graella. Pots activar l'actualització de compatibilitat a l'inici per obtenir més informació actualitzada. @@ -1814,22 +1854,6 @@ Always Sempre - - Touchpad Left - Touchpad esquerra - - - Touchpad Right - Touchpad dret - - - Touchpad Center - Centre del Touchpad - - - None - Cap - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositiu de gràfics:\nEn sistemes amb múltiples targetes gràfiques, selecciona la targeta gràfica que farà servir l'emulador de la llista,\n o clica a "Selecció automàtica" per determinar-la automàticament. diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 120797aa9..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Tilbageknap adfærd - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Indstil en tid for, at musen skal forsvinde efter at være inaktiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Tilbageknap Adfærd:\nIndstiller controllerens tilbageknap til at efterligne tryk på den angivne position på PS4 berøringsflade. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Altid - - Touchpad Left - Berøringsplade Venstre - - - Touchpad Right - Berøringsplade Højre - - - Touchpad Center - Berøringsplade Center - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikadapter:\nPå systemer med flere GPU'er skal du vælge den GPU, emulatoren vil bruge fra en rullemenu,\neller vælge "Auto Select" for at vælge den automatisk. diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 3177be4ff..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad-Klick - Mouse to Joystick Maus zu Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Kontroller - - Back Button Behavior - Verhalten der Zurück-Taste - Graphics Grafik @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Stellen Sie eine Zeit ein, nach der die Maus nach Inaktivität verschwinden soll. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Zurück-Button Verhalten:\nStellt die Zurück-Taste des Controllers so ein, dass sie das Antippen der angegebenen Position auf dem PS4-Touchpad emuliert. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Kompatibilitätsdaten anzeigen:\nZeigt Spielkompatibilitätsinformationen in Tabellenansicht an. Aktivieren Sie „Aktualisiere Kompatibilitätsdatenbank beim Start“, um aktuelle Informationen zu erhalten. @@ -1814,22 +1854,6 @@ Always Immer - - Touchpad Left - Touchpad Links - - - Touchpad Right - Touchpad Rechts - - - Touchpad Center - Touchpad Mitte - - - None - Keine - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikkarte:\nAuf Systemen mit mehreren GPUs wählen Sie aus einem Dropdown-Menü die GPU aus, die der Emulator verwenden wird,\noder wählen Sie "Auto Select", um sie automatisch auszuwählen. diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index aed2b3b2c..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Συμπεριφορά κουμπιού επιστροφής - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Ορίστε έναν χρόνο για να εξαφανιστεί το ποντίκι μετά από αδράνεια. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Συμπεριφορά Κουμπιού Επιστροφής:\nΟρίζει το κουμπί επιστροφής του ελεγκτή να προσομοιώνει το πάτημα της καθορισμένης θέσης στην οθόνη αφής PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Πάντα - - Touchpad Left - Touchpad Αριστερά - - - Touchpad Right - Touchpad Δεξιά - - - Touchpad Center - Κέντρο Touchpad - - - None - Κανένα - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Προσαρμογέας Γραφικών:\nΣε συστήματα με πολλές GPU, επιλέξτε από το μενού την GPU που θα χρησιμοποιήσει ο εξομοιωτής,\nή επιλέξτε "Auto Select" για αυτόματη επιλογή. diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index 432c767f5..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 @@ -748,6 +774,10 @@ Last updated Last updated + + Favorite + + GameListUtils @@ -950,6 +980,14 @@ SFO Viewer for + + Remove from Favorites + + + + Add to Favorites + + HelpDialog @@ -1056,10 +1094,6 @@ L3 - - Touchpad Click - - Mouse to Joystick @@ -1186,6 +1220,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1544,10 +1590,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1784,10 +1826,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1812,22 +1850,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/es_ES.ts b/src/qt_gui/translations/es_ES.ts index f5a268da3..a31279d37 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,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 actualización + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualizador de SFO para + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Clic de pantalla táctil - Mouse to Joystick Ratón a Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controlador - - Back Button Behavior - Comportamiento del Botón de Retroceso - Graphics Gráficos @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Establezca un tiempo para que el ratón desaparezca después de estar inactivo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamiento del Botón Atrás:\nEstablece el botón atrás del controlador para emular el toque en la posición especificada en el touchpad del PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostrar Datos de Compatibilidad:\nMuestra información de compatibilidad de juegos en vista de tabla. Habilite "Actualizar Compatibilidad al Iniciar" para obtener información actualizada. @@ -1814,22 +1854,6 @@ Always Siempre - - Touchpad Left - Touchpad Izquierda - - - Touchpad Right - Touchpad Derecha - - - Touchpad Center - Centro del Touchpad - - - None - Ninguno - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositivo Gráfico:\nEn sistemas con múltiples GPU, selecciona la GPU que el emulador utilizará de la lista desplegable,\o selecciona "Auto Select" para determinarla automáticamente. diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index a21f6e0d2..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - کلیک روی تاچ‌پد - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller دسته بازی - - Back Button Behavior - رفتار دکمه بازگشت - Graphics گرافیک @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. زمانی را برای ناپدید شدن ماوس بعد از غیرفعالی تعیین کنید. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - رفتار دکمه برگشت:\nدکمه برگشت کنترلر را طوری تنظیم می کند که ضربه زدن روی موقعیت مشخص شده روی صفحه لمسی PS4 را شبیه سازی کند. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. نمایش داده‌های سازگاری:\nاطلاعات سازگاری بازی را به صورت جدول نمایش می‌دهد. برای دریافت اطلاعات به‌روز، گزینه "به‌روزرسانی سازگاری هنگام راه‌اندازی" را فعال کنید. @@ -1814,22 +1854,6 @@ Always همیشه - - Touchpad Left - صفحه لمسی سمت چپ - - - Touchpad Right - صفحه لمسی سمت راست - - - Touchpad Center - مرکز صفحه لمسی - - - None - هیچ کدام - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. دستگاه گرافیکی:\nدر سیستم‌های با چندین پردازنده گرافیکی، از فهرست کشویی، پردازنده گرافیکی که شبیه‌ساز از آن استفاده می‌کند را انتخاب کنید، یا گزینه "انتخاب خودکار" را انتخاب کنید تا به طور خودکار تعیین شود. diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index 25104fa40..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Kosketuslevyn Klikkaus - Mouse to Joystick Hiiri Joystickinä @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Ohjain - - Back Button Behavior - Takaisin-painikkeen Käyttäytyminen - Graphics Grafiikka @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Aseta aika, milloin hiiri häviää oltuaan aktiivinen. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Takaisin-napin käyttäytyminen:\nAsettaa ohjaimen takaisin-napin jäljittelemään kosketusta PS4:n kosketuslevyn määritettyyn kohtaan. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Näytä Yhteensopivuustiedot:\nNäyttää pelien yhteensopivuustiedot listanäkymässä. Ota käyttöön "Päivitä Yhteensopivuustietokanta Käynnistäessä" saadaksesi ajantasaista tietoa. @@ -1814,22 +1854,6 @@ Always Aina - - Touchpad Left - Kosketuslevyn Vasen Puoli - - - Touchpad Right - Kosketuslevyn Oikea Puoli - - - Touchpad Center - Kosketuslevyn Keskikohta - - - None - Ei mitään - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Näytönohjain:\nUseamman näytönohjaimen järjestelmissä, valitse pudotusvalikosta, mitä näytönohjainta emulaattori käyttää,\n tai valitse "Auto Select" automaattiseen määritykseen. diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index a9b434579..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Clic tactile - Mouse to Joystick Souris vers Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Manette - - Back Button Behavior - Comportement du bouton retour - Graphics Graphismes @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Définissez un temps pour que la souris disparaisse après être inactif. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportement du bouton retour:\nDéfinit le bouton de retour de la manette pour imiter le toucher de la position spécifiée sur le pavé tactile PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Afficher les données de compatibilité:\nAffiche les informations de compatibilité des jeux dans une colonne dédiée. Activez "Mettre à jour la compatibilité au démarrage" pour avoir des informations à jour. @@ -1814,22 +1854,6 @@ Always Toujours - - Touchpad Left - Pavé Tactile Gauche - - - Touchpad Right - Pavé Tactile Droit - - - Touchpad Center - Centre du Pavé Tactile - - - None - Aucun - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Adaptateur graphique:\nSélectionnez le GPU que l'émulateur utilisera dans les systèmes multi-GPU à partir de la liste déroulante,\nou choisissez "Auto Select" pour le déterminer automatiquement. diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index 834ecd71a..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Kontroller - - Back Button Behavior - Vissza gomb Viselkedése - Graphics Grafika @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Állítson be egy időt, ami után egér inaktív állapotban eltűnik. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Vissza gomb viselkedés:\nBeállítja a vezérlő vissza gombját, hogy utánozza a PS4 érintőpadján megadott pozíció megérintését. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Mindig - - Touchpad Left - Érintőpad Bal - - - Touchpad Right - Érintőpad Jobb - - - Touchpad Center - Érintőpad Közép - - - None - Semmi - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikus eszköz:\nTöbb GPU-s rendszereken válassza ki, melyik GPU-t használja az emulátor a legördülő listából,\nvagy válassza az "Auto Select" lehetőséget, hogy automatikusan kiválassza azt. diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index d4db7cf18..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Pengontrol - - Back Button Behavior - Perilaku tombol kembali - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tetapkan waktu untuk mouse menghilang setelah tidak aktif. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Perilaku Tombol Kembali:\nMengatur tombol kembali pada pengontrol untuk meniru ketukan di posisi yang ditentukan di touchpad PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Selalu - - Touchpad Left - Touchpad Kiri - - - Touchpad Right - Touchpad Kanan - - - Touchpad Center - Pusat Touchpad - - - None - Tidak Ada - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Perangkat Grafis:\nPada sistem GPU ganda, pilih GPU yang akan digunakan emulator dari daftar dropdown,\natau pilih "Auto Select" untuk menentukan secara otomatis. diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index 95fed4156..c636d48e4 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 + 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 Ultimo aggiornamento + + Favorite + Favorite + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for Visualizzatore SFO per + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Click Touchpad - Mouse to Joystick Mouse a Joystick @@ -1184,10 +1220,22 @@ 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: + Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti: %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Comportamento del pulsante Indietro - Graphics Grafica @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Imposta un tempo affinché il mouse scompaia dopo essere stato inattivo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento del pulsante Indietro:\nImposta il pulsante Indietro del controller per emulare il tocco sulla posizione specificata sul touchpad PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Mostra Dati Compatibilità:\nMostra informazioni sulla compatibilità del gioco nella visualizzazione lista. Abilita "Aggiorna Compatiblità all'Avvio" per ottenere informazioni aggiornate. @@ -1814,22 +1854,6 @@ Always Sempre - - Touchpad Left - Touchpad Sinistra - - - Touchpad Right - Touchpad Destra - - - Touchpad Center - Centro del Touchpad - - - None - Nessuno - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispositivo Grafico:\nIn sistemi con più GPU, seleziona la GPU che l'emulatore utilizzerà dall'elenco a discesa,\no seleziona "Selezione Automatica" per determinarlo automaticamente. diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 99ccc163e..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 - 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) @@ -523,15 +483,15 @@ R: - R: + R: G: - G: + G: B: - B: + B: Override Lightbar Color @@ -551,19 +511,87 @@ Save - Save + 保存 Apply - Apply + 適用 Restore Defaults - Restore Defaults + デフォルトに戻す 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 @@ -574,11 +602,11 @@ Use Per-Game configs - Use Per-Game configs + ゲームごとの設定を使用する Error - Error + エラー Could not open the file for reading @@ -590,11 +618,11 @@ Save Changes - Save Changes + 変更を保存 Do you want to save changes? - Do you want to save changes? + 変更を保存しますか? Help @@ -610,7 +638,7 @@ Reset to Default - Reset to Default + デフォルトに戻す @@ -748,6 +776,10 @@ Last updated 最終更新 + + Favorite + Favorite + GameListUtils @@ -848,7 +880,7 @@ Delete Trophy - Delete Trophy + トロフィーを削除 Compatibility... @@ -944,12 +976,20 @@ Trophy - Trophy + トロフィー SFO Viewer for SFO Viewer for + + Remove from Favorites + Remove from Favorites + + + Add to Favorites + Add to Favorites + HelpDialog @@ -959,7 +999,7 @@ FAQ - FAQ + FAQ Syntax @@ -982,11 +1022,11 @@ D-Pad - D-Pad + 十字キー Up - Up + unmapped @@ -994,15 +1034,15 @@ Left - Left + Right - Right + Down - Down + Left Analog Halfmode @@ -1014,7 +1054,7 @@ Left Stick - Left Stick + 左スティック Config Selection @@ -1030,11 +1070,11 @@ L1 - L1 + L1 L2 - L2 + L2 Text Editor @@ -1046,19 +1086,15 @@ R1 - R1 + R1 R2 - R2 + R2 L3 - L3 - - - Touchpad Click - Touchpad Click + L3 Mouse to Joystick @@ -1070,7 +1106,7 @@ R3 - R3 + R3 Options @@ -1166,19 +1202,19 @@ Save - Save + 保存 Apply - Apply + 適用 Restore Defaults - Restore Defaults + デフォルトに戻す Cancel - Cancel + キャンセル Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1409,43 +1457,43 @@ Play - Play + プレイ Pause - Pause + 一時停止 Stop - Stop + 停止 Restart - Restart + 再起動 Full Screen - Full Screen + 全画面表示 Controllers - Controllers + コントローラー Keyboard - Keyboard + キーボード Refresh List - Refresh List + リストの更新 Resume - Resume + 再開 Show Labels Under Icons - Show Labels Under Icons + アイコンの下にラベルを表示 @@ -1546,10 +1594,6 @@ Controller コントローラー - - Back Button Behavior - 戻るボタンの動作 - Graphics グラフィックス @@ -1632,7 +1676,7 @@ Copy GPU Buffers - Copy GPU Buffers + GPU バッファーをコピー Host Debug Markers @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. カーソルが非アクティブになってから隠すまでの時間を設定します。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 戻るボタンの動作:\nコントローラーの戻るボタンを、PS4のタッチパッドの指定された位置をタッチするように設定します。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 互換性に関するデータを表示:\nゲームの互換性に関する情報を表として表示します。常に最新情報を取得したい場合、"起動時に互換性データベースを更新する" を有効化してください。 @@ -1814,22 +1854,6 @@ Always 常に - - Touchpad Left - 左タッチパッド - - - Touchpad Right - 右タッチパッド - - - Touchpad Center - タッチパッド中央 - - - None - なし - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. グラフィックデバイス:\nシステムに複数のGPUが搭載されている場合、ドロップダウンリストからエミュレーターで使用するGPUを選択するか、\n「自動選択」を選択して自動的に決定します。 @@ -1960,31 +1984,31 @@ Display Mode - Display Mode + 表示モード Windowed - Windowed + ウィンドウ表示 Fullscreen - Fullscreen + 全画面表示 Fullscreen (Borderless) - Fullscreen (Borderless) + 全画面表示(ボーダレス) Window Size - Window Size + ウィンドウサイズ W: - W: + W: H: - H: + H: Separate Log Files @@ -2000,11 +2024,11 @@ Left - Left + Right - Right + Top @@ -2016,7 +2040,7 @@ Notification Duration - Notification Duration + 通知表示時間 Portable User Folder @@ -2036,7 +2060,7 @@ %1 already exists - %1 already exists + %1 は既に存在します Portable user folder created @@ -2052,7 +2076,7 @@ * Unsupported Vulkan Version - * Unsupported Vulkan Version + * サポートされていないVulkanバージョン @@ -2071,15 +2095,15 @@ Show Earned Trophies - Show Earned Trophies + 獲得したトロフィーを表示 Show Not Earned Trophies - Show Not Earned Trophies + 未獲得のトロフィーを表示 Show Hidden Trophies - Show Hidden Trophies + 隠しトロフィーを表示 diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index b38480f9f..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index 837ea81fe..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Valdiklis - - Back Button Behavior - Atgal mygtuko elgsena - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Slėpti tuščiosios eigos žymeklio skirtąjį laiką:\nTrukmė (sekundėmis), po kurios neaktyvus žymeklis pasislepia. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Atgal mygtuko elgesys:\nNustato valdiklio atgal mygtuką imituoti paspaudimą nurodytoje vietoje PS4 jutiklinėje plokštėje. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Visada - - Touchpad Left - Jutiklinis Paviršius Kairėje - - - Touchpad Right - Jutiklinis Paviršius Dešinėje - - - Touchpad Center - Jutiklinis Paviršius Centre - - - None - Nieko - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikos įrenginys:\nDaugiagrafikėse sistemose pasirinkite GPU, kurį emuliatorius naudos iš išskleidžiamojo sąrašo,\n arba pasirinkite "Auto Select", kad jis būtų nustatytas automatiškai. diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index 92e902dea..302229933 100644 --- a/src/qt_gui/translations/nb_NO.ts +++ b/src/qt_gui/translations/nb_NO.ts @@ -337,7 +337,7 @@ The update has been downloaded, press OK to install. - Oppdateringen ble lastet ned, trykk OK for å installere. + Oppdateringen er lastet ned, trykk OK for å installere. Failed to save the update file at @@ -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) @@ -565,6 +525,74 @@ Cancel Avbryt + + 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 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 @@ -990,7 +1030,7 @@ unmapped - Ikke satt opp + Ikke tildelt Left @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Berøringsplateknapp - Mouse to Joystick Mus til styrespak @@ -1082,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 @@ -1184,10 +1220,22 @@ 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: + Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster: %1 + + Touchpad Left + Berøringsplate venstre + + + Touchpad Center + Berøringsplate midten + + + Touchpad Right + Berøringsplate høyre + MainWindow @@ -1546,10 +1594,6 @@ Controller Kontroller - - Back Button Behavior - Tilbakeknapp atferd - Graphics Grafikk @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Sett en tid for når musepekeren forsvinner etter å ha vært inaktiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Atferd for tilbaketasten:\nSetter tilbaketasten på kontrolleren til å imitere et trykk på den angitte posisjonen på PS4s berøringsplate. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Vis kompatibilitets-data:\nViser informasjon om spillkompatibilitet i tabellvisning. Bruk «Oppdater database ved oppstart» for oppdatert informasjon. @@ -1814,22 +1854,6 @@ Always Alltid - - Touchpad Left - Berøringsplate venstre - - - Touchpad Right - Berøringsplate høyre - - - Touchpad Center - Berøringsplate midten - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikkenhet:\nSystemer med flere GPU-er, kan emulatoren velge hvilken enhet som skal brukes fra rullegardinlista,\neller bruk «Velg automatisk». diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index 61cd9f359..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Achterknop gedrag - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Stel een tijd in voor wanneer de muis verdwijnt na inactiviteit. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Gedrag van de terugknop:\nStelt de terugknop van de controller in om een aanraking op de opgegeven positie op de PS4-touchpad na te bootsen. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Altijd - - Touchpad Left - Touchpad Links - - - Touchpad Right - Touchpad Rechts - - - Touchpad Center - Touchpad Midden - - - None - Geen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafische adapter:\nIn systemen met meerdere GPU's, kies de GPU die de emulator uit de vervolgkeuzelijst moet gebruiken,\nof kies "Auto Select" om dit automatisch in te stellen. diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index 0da03b472..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Kliknięcie Touchpada - Mouse to Joystick Mysz na Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Kontroler - - Back Button Behavior - Zachowanie przycisku wstecz - Graphics Grafika @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Ustaw czas, po którym mysz zniknie po bezczynności. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Zachowanie przycisku Wstecz:\nUstawia przycisk Wstecz kontrolera tak, aby emulował dotknięcie określonego miejsca na panelu dotykowym PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Wyświetl dane zgodności:\nWyświetla informacje o kompatybilności gry w widoku tabeli. Włącz opcję „Aktualizuj zgodność przy uruchomieniu”, aby uzyskać aktualne informacje. @@ -1814,22 +1854,6 @@ Always Zawsze - - Touchpad Left - Touchpad Lewy - - - Touchpad Right - Touchpad Prawy - - - Touchpad Center - Touchpad Środkowy - - - None - Brak - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Urządzenie graficzne:\nW systemach z wieloma GPU, wybierz GPU, który emulator ma używać z rozwijanego menu,\n lub wybierz "Auto Select", aby ustawić go automatycznie. diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index eee4c5dd5..c2058bf09 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 + Touchpad Center + + + Touchpad Right + Touchpad Direito + + + Triangle + Triângulo + + + Square + Quadrado + + + Circle + Circle + + + Cross + Cross + + + 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 + Move analog stick + 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Clique do Touchpad - Mouse to Joystick Mouse para Analógico @@ -1184,10 +1220,22 @@ 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: + Não é possível atribuir a mesma entrada mais de uma vez. Entradas duplicadas foram atribuídas aos seguintes botões: %1 + + Touchpad Left + Touchpad Esquerdo + + + Touchpad Center + Centro do Touchpad + + + Touchpad Right + Touchpad Direito + MainWindow @@ -1546,10 +1594,6 @@ Controller Controle - - Back Button Behavior - Comportamento do Botão Voltar - Graphics Gráficos @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tempo de Inatividade para Ocultar Cursor:\nDefina um tempo em segundos para o mouse desaparecer após ficar inativo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento do Botão Voltar:\nDefine o botão voltar do controle para emular o toque na posição especificada no touchpad do PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Exibir Dados de Compatibilidade:\nExibe informações de compatibilidade dos jogos na visualização de tabela.\nAtive "Atualizar Compatibilidade ao Inicializar" para obter informações atualizadas. @@ -1814,22 +1854,6 @@ Always Sempre - - Touchpad Left - Touchpad Esquerdo - - - Touchpad Right - Touchpad Direito - - - Touchpad Center - Centro do Touchpad - - - None - Nenhum - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Placa de Vídeo:\nEm sistemas com múltiplas GPUs, escolha qual GPU o emulador utilizará da lista suspensa,\nou escolha "Seleção Automática" para escolher automaticamente o mesmo. diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index 9876ee291..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Clique do Touchpad - Mouse to Joystick Rato para Manípulo @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Comando - - Back Button Behavior - Comportamento do Botão Voltar - Graphics Gráficos @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Tempo de Espera para Ocultar o Cursor:\nDefine o tempo em segundos para o rato desaparecer após ficar inativo. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamento do Botão Voltar:\nConfigura o botão Voltar do comando para emular um toque na posição especificada no touchpad do PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Exibir Dados de Compatibilidade:\nExibe informações de compatibilidade dos jogos em visualização de tabela.\nAtivar "Atualizar Base de Dados de Compatibilidade no Arranque" para obter informações atualizadas. @@ -1814,22 +1854,6 @@ Always Sempre - - Touchpad Left - Esquerda do Touchpad - - - Touchpad Right - Direita do Touchpad - - - Touchpad Center - Centro do Touchpad - - - None - Nenhum - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Placa Gráfica:\nEm sistemas com múltiplas GPUs, escolha qual GPU da lista o emulador utilizará,\nou escolha "Seleção Automática" para escolher automaticamente a mesma. diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index 670a96372..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controler - - Back Button Behavior - Comportament buton înapoi - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Setați un timp pentru ca mouse-ul să dispară după ce a fost inactiv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Comportamentul butonului înapoi:\nSetează butonul înapoi al controlerului să imite atingerea poziției specificate pe touchpad-ul PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Întotdeauna - - Touchpad Left - Touchpad Stânga - - - Touchpad Right - Touchpad Dreapta - - - Touchpad Center - Centru Touchpad - - - None - Niciunul - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Dispozitiv grafic:\nPe sistemele cu mai multe GPU-uri, alege GPU-ul pe care emulatorul îl va folosi din lista derulantă,\nsau selectează "Auto Select" pentru a-l determina automat. diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index a637ccb23..7293a956e 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 + 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Нажатие на тачпад - Mouse to Joystick Мышь в джойстик @@ -1138,7 +1174,7 @@ This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config. - Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль это общий конфиг. + Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль - это общий конфиг. Copy values from Common Config @@ -1184,10 +1220,22 @@ 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 + + Touchpad Left + Тачпад слева + + + Touchpad Center + Тачпад центр + + + Touchpad Right + Тачпад справа + MainWindow @@ -1546,10 +1594,6 @@ Controller Контроллер - - Back Button Behavior - Поведение кнопки назад - Graphics Графика @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Время скрытия курсора при бездействии:\nВремя (в секундах), через которое курсор исчезнет при бездействии. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Поведение кнопки «Назад»:\nНастраивает кнопку «Назад» контроллера на эмуляцию нажатия на указанную область на сенсорной панели контроллера PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Показывать данные совместимости:\nПоказывает информацию о совместимости игр в таблице. Включите «Обновлять базу совместимости при запуске» для получения актуальной информации. @@ -1814,22 +1854,6 @@ Always Всегда - - Touchpad Left - Тачпад слева - - - Touchpad Right - Тачпад справа - - - Touchpad Center - Центр тачпада - - - None - Нет - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Графическое устройство:\nВ системах с несколькими GPU выберите тот, который будет использовать эмулятор.\nВыберите "Автовыбор", чтобы определить GPU автоматически. diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index 4b0536842..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index 553e5b44a..71308a99f 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 + 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 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Klikim i Panelit me Prekje - Mouse to Joystick Miu në Levë @@ -1184,10 +1220,22 @@ 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: + Nuk mund të caktohet e njëjta hyrje unike më shumë se një herë. Hyrjet e dublikuara janë caktuar në butonët e mëposhtëm: %1 + + Touchpad Left + Paneli me Prekje Majtas + + + Touchpad Center + Paneli me Prekje në Qendër + + + Touchpad Right + Paneli me Prekje Djathtas + MainWindow @@ -1546,10 +1594,6 @@ Controller Dorezë - - Back Button Behavior - Sjellja e butonit mbrapa - Graphics Grafika @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Koha për fshehjen e kursorit joaktiv:\nKohëzgjatja (në sekonda) pas së cilës kursori që nuk ka qënë në veprim fshihet. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Sjellja e butonit mbrapa:\nLejon të përcaktohet se në cilën pjesë të panelit me prekje të dorezës do të imitojë një prekje butoni mbrapa. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Shfaq të dhënat e përputhshmërisë:\nShfaq informacionin e përputhshmërisë së lojës në formë tabele. Aktivizo "Përditëso përputhshmërinë gjatë nisjes" për të marrë informacion të përditësuar. @@ -1814,22 +1854,6 @@ Always Gjithmonë - - Touchpad Left - Paneli me Prekje Majtas - - - Touchpad Right - Paneli me Prekje Djathtas - - - Touchpad Center - Paneli me Prekje në Qendër - - - None - Asnjë - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Pajisja grafike:\nNë sistemet me GPU të shumëfishta, zgjidh GPU-në që do të përdorë emulatori nga lista rënëse,\nose zgjidh "Auto Select" për ta përcaktuar automatikisht. diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts index 834976377..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Always - - Touchpad Left - Touchpad Left - - - Touchpad Right - Touchpad Right - - - Touchpad Center - Touchpad Center - - - None - None - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index 435712f35..83081bf1a 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 + 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 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Klick på styrplatta - Mouse to Joystick Mus till styrspak @@ -1184,10 +1220,22 @@ 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: + Det går inte att binda samma unika inmatning mer än en gång. Dubbla inmatningar har mappats till följande knappar: %1 + + Touchpad Left + Pekplatta vänster + + + Touchpad Center + Pekplatta mitten + + + Touchpad Right + Pekplatta höger + MainWindow @@ -1546,10 +1594,6 @@ Controller Handkontroller - - Back Button Behavior - Beteende för bakåtknapp - Graphics Grafik @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information. @@ -1814,22 +1854,6 @@ Always Alltid - - Touchpad Left - Touchpad vänster - - - Touchpad Right - Touchpad höger - - - Touchpad Center - Touchpad mitten - - - None - Ingen - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja "Auto Select" för att automatiskt bestämma det. diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index 8db7e02c4..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Dokunmatik Yüzey Tıklaması - Mouse to Joystick Mouse'dan Kontrolcü @@ -1184,10 +1220,22 @@ 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 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Kontrolcü - - Back Button Behavior - Geri Dönme Butonu Davranışı - Graphics Grafikler @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. İmleç İçin Hareketsizlik Zaman Aşımı:\nBoşta kalan imlecin kendini kaç saniye sonra gizleyeceğidir. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Geri düğmesi davranışı:\nKontrol cihazındaki geri düğmesini, PS4'ün dokunmatik panelindeki belirlenen noktaya dokunmak için ayarlar. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Uyumluluk Verilerini Göster:\nOyun uyumluluk bilgilerini tablo görünümünde görüntüler. Güncel bilgileri almak için "Başlangıçta Uyumluluk Veritabanını Güncelle"yi etkinleştirin. @@ -1814,22 +1854,6 @@ Always Her zaman - - Touchpad Left - Dokunmatik Yüzey Sol - - - Touchpad Right - Dokunmatik Yüzey Sağ - - - Touchpad Center - Dokunmatik Yüzey Orta - - - None - Yok - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Grafik Aygıtı:\nBirden fazla GPU'ya sahip sistemlerde, emülatörün kullanacağı GPU'yu açılır listeden seçin,\nor "Auto Select" seçeneğini seçerek otomatik olarak belirlenmesini sağlayın. diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 214596e7e..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 @@ -1056,10 +1096,6 @@ L3 Кнопка лівого стику - - Touchpad Click - Натискання на сенсорну панель - Mouse to Joystick Миша в джойстик @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Контролер - - Back Button Behavior - Перепризначення кнопки назад - Graphics Графіка @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Встановіть час, через який курсор зникне в разі бездіяльності. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Перепризначення кнопки «Назад»:\nНалаштовує кнопку «Назад» контролера на емуляцію натискання на зазначену область на сенсорній панелі контролера PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Відображати данні ігрової сумістністі:\nВідображає інформацію про сумісність ігор у вигляді таблиці. Увімкніть "Оновлення даних ігрової сумісності під час запуску" для отримання актуальної інформації. @@ -1814,22 +1854,6 @@ Always Завжди - - Touchpad Left - Ліва сторона тачпаду - - - Touchpad Right - Права сторона тачпаду - - - Touchpad Center - Середина тачпаду - - - None - Без змін - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Графічний пристрій:\nУ системах із кількома GPU виберіть з випадаючого списку GPU, який буде використовувати емулятор,\nабо виберіть "Автовибір", щоб визначити його автоматично. diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index e94515ea8..ebc6a8a52 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -22,7 +22,7 @@ CheatsPatches Cheats / Patches for - Cheats / Patches for + Cheats / Patches cho Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n @@ -415,35 +415,35 @@ Up - Up + Lên Left - Left + Trái Right - Right + Phải Down - Down + Xuống Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Deadzone cần bên trái (def:2 max:127) Left Deadzone - Left Deadzone + Deadzone bên trái Left Stick - Left Stick + Cần bên trái Config Selection - Config Selection + Chọn cấu hình Common Config @@ -451,59 +451,19 @@ 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 + Cấu hình riêng cho từng game L3 - L3 - - - Options / Start - Options / Start + L3 R3 - R3 + R3 Face Buttons - Face Buttons - - - Triangle / Y - Triangle / Y - - - Square / X - Square / X - - - Circle / B - Circle / B - - - Cross / A - Cross / A + Nút bấm mặt trước 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 @@ -824,107 +856,107 @@ Copy Size - Copy Size + Kích thước bản sao Copy All - Copy All + Sao chép tất cả Delete... - Delete... + Xoá... Delete Game - Delete Game + Xóa trò chơi Delete Update - Delete Update + Xóa bản cập nhật Delete DLC - Delete DLC + Xoá DLC Delete Trophy - Delete Trophy + Xóa cúp Compatibility... - Compatibility... + Khả năng tương thích... Update database - Update database + Cập nhật Cơ sở dữ liệu View report - View report + Xem báo cáo Submit a report - Submit a report + Gửi một báo cáo Shortcut creation - Shortcut creation + Tạo phím tắt Shortcut created successfully! - Shortcut created successfully! + Phím tắt đã được tạo thành công! Error - Error + Lỗi Error creating shortcut! - Error creating shortcut! + Lỗi khi tạo phím tắt! Game - Game + Trò chơi This game has no update to delete! - This game has no update to delete! + Trò chơi này không có bản cập nhật nào để xóa! Update - Update + Cập nhật This game has no DLC to delete! - This game has no DLC to delete! + Trò chơi này không có DLC nào để xóa! DLC - DLC + DLC Delete %1 - Delete %1 + Xóa %1 Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Bạn có muốn xoá thư mục %1's %2 không? Open Update Folder - Open Update Folder + Mở thư mục Cập nhật Delete Save Data - Delete Save Data + Xóa Lưu Dữ Liệu This game has no update folder to open! - This game has no update folder to open! + Trò chơi này không có thư mục Cập nhật nào để mở! No log file found for this game! - 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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - Touchpad Click - Mouse to Joystick Mouse to Joystick @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller Điều khiển - - Back Button Behavior - Hành vi nút quay lại - Graphics Đồ họa @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. Đặt thời gian để chuột biến mất sau khi không hoạt động. - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - Hành vi nút quay lại:\nĐặt nút quay lại của tay cầm để mô phỏng việc chạm vào vị trí đã chỉ định trên touchpad của PS4. - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. @@ -1814,22 +1854,6 @@ Always Luôn luôn - - Touchpad Left - Touchpad Trái - - - Touchpad Right - Touchpad Phải - - - Touchpad Center - Giữa Touchpad - - - None - Không có - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. Thiết bị đồ họa:\nTrên các hệ thống có GPU đa năng, hãy chọn GPU mà trình giả lập sẽ sử dụng từ danh sách thả xuống,\hoặc chọn "Auto Select" để tự động xác định. diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index acb140fdc..2956d9621 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 + 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 + 收藏 + GameListUtils @@ -950,6 +982,14 @@ SFO Viewer for SFO 查看器 - + + Remove from Favorites + 从收藏中移除 + + + Add to Favorites + 添加至收藏 + HelpDialog @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - 触摸板点击 - Mouse to Joystick 鼠标控制摇杆 @@ -1184,10 +1220,22 @@ 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 + + Touchpad Left + 触摸板左侧 + + + Touchpad Center + 触摸板中心 + + + Touchpad Right + 触摸板右侧 + MainWindow @@ -1546,10 +1594,6 @@ Controller 手柄 - - Back Button Behavior - 返回按钮行为 - Graphics 图像 @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. 光标隐藏闲置时长:\n光标自动隐藏之前的闲置时长。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 返回按钮行为:\n设置手柄的返回按钮模拟在 PS4 触控板上指定位置的点击。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 显示兼容性数据:\n在列表视图中显示游戏兼容性信息。启用“启动时更新兼容性数据库”以获取最新信息。 @@ -1814,22 +1854,6 @@ Always 始终 - - Touchpad Left - 触控板左侧 - - - Touchpad Right - 触控板右侧 - - - Touchpad Center - 触控板中间 - - - None - - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. 图形设备:\n在具有多个 GPU 的系统中,从下拉列表中选择要使用的 GPU,\n或者选择“自动选择”由模拟器决定。 diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index ee7974fca..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 @@ -1056,10 +1096,6 @@ L3 L3 - - Touchpad Click - 觸控板點擊 - Mouse to Joystick 滑鼠操控操縱桿 @@ -1188,6 +1224,18 @@ %1 + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1546,10 +1594,6 @@ Controller 控制器 - - Back Button Behavior - 返回按鈕行為 - Graphics 圖形 @@ -1786,10 +1830,6 @@ Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. 空閒滑鼠指標隱藏逾時:\n閒置滑鼠指標隱藏自身之前的持續顯示時間(秒)。 - - Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad. - 返回按鈕行為:\n設定控制器'的返回按鈕以模擬點擊 PS4 控制器觸控板上的指定位置。 - Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information. 顯示相容性資料:\n在表格顯視模式中顯示遊戲相容性資訊。啟用「啟動"時更新相容性」以取得"最新資訊。 @@ -1814,22 +1854,6 @@ Always 始終 - - Touchpad Left - 觸控板左側 - - - Touchpad Right - 觸控板右側 - - - Touchpad Center - 觸控板中間 - - - None - - Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select "Auto Select" to automatically determine it. 圖形裝置:\n在多GPU系統中,從下拉列表中選取模擬器將使用的GPU,\n或選取「自動選取」以自動選用適合的GPU。 diff --git a/src/qt_gui/trophy_viewer.cpp b/src/qt_gui/trophy_viewer.cpp index bed487605..675dba799 100644 --- a/src/qt_gui/trophy_viewer.cpp +++ b/src/qt_gui/trophy_viewer.cpp @@ -104,14 +104,16 @@ void TrophyViewer::updateTableFilters() { } } -TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath, QString gameName, +TrophyViewer::TrophyViewer(std::shared_ptr gui_settings, QString trophyPath, + QString gameTrpPath, QString gameName, const QVector& allTrophyGames) - : QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName) { + : QMainWindow(), allTrophyGames_(allTrophyGames), currentGameName_(gameName), + m_gui_settings(std::move(gui_settings)) { this->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_); this->setAttribute(Qt::WA_DeleteOnClose); tabWidget = new QTabWidget(this); - auto lan = Config::getEmulatorLanguage(); + auto lan = m_gui_settings->GetValue(gui::gen_guiLanguage).toString(); if (lan == "en_US" || lan == "zh_CN" || lan == "zh_TW" || lan == "ja_JP" || lan == "ko_KR" || lan == "lt_LT" || lan == "nb_NO" || lan == "nl_NL") { useEuropeanDateFormat = false; @@ -463,7 +465,7 @@ void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QStri item->setTextAlignment(Qt::AlignCenter); item->setFont(QFont("Arial", 12, QFont::Bold)); - Theme theme = static_cast(Config::getMainWindowTheme()); + Theme theme = static_cast(m_gui_settings->GetValue(gui::gen_theme).toInt()); if (theme == Theme::Light) { item->setForeground(QBrush(Qt::black)); diff --git a/src/qt_gui/trophy_viewer.h b/src/qt_gui/trophy_viewer.h index c63171774..60ffe8420 100644 --- a/src/qt_gui/trophy_viewer.h +++ b/src/qt_gui/trophy_viewer.h @@ -23,6 +23,7 @@ #include "common/types.h" #include "core/file_format/trp.h" +#include "gui_settings.h" struct TrophyGameInfo { QString name; @@ -34,7 +35,8 @@ class TrophyViewer : public QMainWindow { Q_OBJECT public: explicit TrophyViewer( - QString trophyPath, QString gameTrpPath, QString gameName = "", + std::shared_ptr gui_settings, QString trophyPath, QString gameTrpPath, + QString gameName = "", const QVector& allTrophyGames = QVector()); void updateTrophyInfo(); @@ -77,4 +79,5 @@ private: } return "Unknown"; } + std::shared_ptr m_gui_settings; }; diff --git a/src/sdl_window.cpp b/src/sdl_window.cpp index e369240c6..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; } @@ -474,11 +485,16 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) { Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial())); return; } - // Toggle mouse capture and movement input + // Toggle mouse capture and joystick input emulation else if (input_id == SDLK_F7) { - Input::ToggleMouseEnabled(); SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), - !SDL_GetWindowRelativeMouseMode(this->GetSDLWindow())); + Input::ToggleMouseModeTo(Input::MouseMode::Joystick)); + return; + } + // Toggle mouse capture and gyro input emulation + else if (input_id == SDLK_F6) { + SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(), + Input::ToggleMouseModeTo(Input::MouseMode::Gyro)); return; } // Toggle fullscreen diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index 93fb81df4..c4c310586 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -271,7 +271,8 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct if (info.has_image_query) { ctx.AddCapability(spv::Capability::ImageQuery); } - if (info.uses_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) { + if ((info.uses_image_atomic_float_min_max && profile.supports_image_fp32_atomic_min_max) || + (info.uses_buffer_atomic_float_min_max && profile.supports_buffer_fp32_atomic_min_max)) { ctx.AddExtension("SPV_EXT_shader_atomic_float_min_max"); ctx.AddCapability(spv::Capability::AtomicFloat32MinMaxEXT); } @@ -299,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"); } @@ -309,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 47290e7e8..e37acb2e4 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}; @@ -19,8 +23,7 @@ Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value, const Id shift_id{ctx.ConstU32(2U)}; 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, 4u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); @@ -32,8 +35,7 @@ Id SharedAtomicU32IncDec(EmitContext& ctx, Id offset, const Id shift_id{ctx.ConstU32(2U)}; 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, 4u)}; - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)}; + const Id pointer{ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index)}; const auto [scope, semantics]{AtomicArgs(ctx)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); @@ -45,40 +47,45 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value, 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.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + 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, value); }); } +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]; - if (Sirit::ValidId(buffer.offset)) { - address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); + const auto type = [&] { + if constexpr (is_float) { + return ctx.F32[1]; + } else { + return 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>(ctx, index, buffer.size_dwords, [&] { - return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value); + return AccessBoundsCheck<32, 1, is_float>(ctx, address, buffer.Size(PointerSize::B32), [&] { + return (ctx.*atomic_func)(type, ptr, scope, semantics, value); }); } 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); }); } @@ -87,14 +94,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); }); } @@ -102,14 +108,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); }); } @@ -195,18 +200,70 @@ 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, + &Sirit::Module::OpAtomicFMin); + } + + const auto u32_value = ctx.OpBitcast(ctx.U32[1], value); + const auto sign_bit_set = + ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); + + const auto result = ctx.OpSelect( + ctx.F32[1], sign_bit_set, + EmitBitCastF32U32(ctx, EmitBufferAtomicUMax32(ctx, inst, handle, address, u32_value)), + EmitBitCastF32U32(ctx, EmitBufferAtomicSMin32(ctx, inst, handle, address, u32_value))); + + return result; +} + Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { 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, + &Sirit::Module::OpAtomicFMax); + } + + const auto u32_value = ctx.OpBitcast(ctx.U32[1], value); + const auto sign_bit_set = + ctx.OpBitFieldUExtract(ctx.U32[1], u32_value, ctx.ConstU32(31u), ctx.ConstU32(1u)); + + const auto result = ctx.OpSelect( + ctx.F32[1], sign_bit_set, + EmitBitCastF32U32(ctx, EmitBufferAtomicUMin32(ctx, inst, handle, address, u32_value)), + EmitBitCastF32U32(ctx, EmitBufferAtomicSMax32(ctx, inst, handle, address, u32_value))); + + return result; +} + Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement); } @@ -319,7 +376,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); @@ -327,7 +384,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 daf1b973e..1ac2266bd 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -91,9 +91,15 @@ 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); Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); @@ -402,14 +408,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); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp index 1a995354d..ddc1e7574 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_integer.cpp @@ -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_shared_memory.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp index c59406499..731ccd55a 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_shared_memory.cpp @@ -14,8 +14,7 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u16, ctx.shared_memory_u16, index); return ctx.OpLoad(ctx.U16, pointer); }); } @@ -26,8 +25,7 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index); return ctx.OpLoad(ctx.U32[1], pointer); }); } @@ -38,8 +36,7 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index); return ctx.OpLoad(ctx.U64, pointer); }); } @@ -50,8 +47,7 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)}; AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u16, ctx.shared_memory_u16, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -63,8 +59,7 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)}; AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer = - ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index); + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u32, ctx.shared_memory_u32, index); ctx.OpStore(pointer, value); return Id{0}; }); @@ -76,8 +71,7 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) { const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)}; AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] { - const Id pointer{ - ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)}; + const Id pointer = ctx.EmitSharedMemoryAccess(ctx.shared_u64, ctx.shared_memory_u64, index); ctx.OpStore(pointer, value); return Id{0}; }); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 0a8f78f72..77336c9ec 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -71,7 +71,7 @@ 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); @@ -169,34 +169,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 +213,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 +229,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)); } } } @@ -779,20 +784,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 +800,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; @@ -972,7 +963,12 @@ void EmitContext::DefineImagesAndSamplers() { const Id id{AddGlobalVariable(sampler_pointer_type, spv::StorageClass::UniformConstant)}; Decorate(id, spv::Decoration::Binding, binding.unified++); Decorate(id, spv::Decoration::DescriptorSet, 0U); - Name(id, fmt::format("{}_{}{}", stage, "samp", samp_desc.sharp_idx)); + auto sharp_desc = std::holds_alternative(samp_desc.sampler) + ? fmt::format("sgpr:{}", std::get(samp_desc.sampler)) + : fmt::format("inline:{:#x}:{:#x}", + std::get(samp_desc.sampler).raw0, + std::get(samp_desc.sampler).raw1); + Name(id, fmt::format("{}_{}{}", stage, "samp", sharp_desc)); samplers.push_back(id); interfaces.push_back(id); } @@ -995,19 +991,26 @@ void EmitContext::DefineSharedMemory() { const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)}; const Id array_type{TypeArray(element_type, ConstU32(num_elements))}; - Decorate(array_type, spv::Decoration::ArrayStride, element_size); - const Id struct_type{TypeStruct(array_type)}; - MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); + const auto mem_type = [&] { + if (num_types > 1) { + const Id struct_type{TypeStruct(array_type)}; + Decorate(struct_type, spv::Decoration::Block); + MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u); + return struct_type; + } else { + return array_type; + } + }(); - const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type); + const Id pointer = TypePointer(spv::StorageClass::Workgroup, mem_type); const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type); const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup); Name(variable, name); interfaces.push_back(variable); if (num_types > 1) { - Decorate(struct_type, spv::Decoration::Block); + Decorate(array_type, spv::Decoration::ArrayStride, element_size); Decorate(variable, spv::Decoration::Aliased); } @@ -1142,7 +1145,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)}; @@ -1154,14 +1157,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 @@ -1199,17 +1202,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); } }); @@ -1227,7 +1224,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 93c4ed265..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,10 +168,28 @@ 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; } + Id EmitSharedMemoryAccess(const Id result_type, const Id shared_mem, const Id index) { + if (std::popcount(static_cast(info.shared_types)) > 1) { + return OpAccessChain(result_type, shared_mem, u32_zero_value, index); + } + // Workgroup layout struct omitted. + 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; @@ -306,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; @@ -314,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)]; } }; @@ -348,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/translate/scalar_alu.cpp b/src/shader_recompiler/frontend/translate/scalar_alu.cpp index 7beb594c3..48f977f49 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" diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index 086b325aa..b5bfec344 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 @@ -291,6 +291,7 @@ public: 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..54f1088f2 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,31 @@ 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[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) { + return SetDst(inst.dst[0], GetSrc(inst.src[1])); } + UNREACHABLE(); } } @@ -996,39 +1002,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 54e8b8ee8..91f545cfd 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: @@ -90,6 +98,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) { return BUFFER_ATOMIC(AtomicOp::Inc, inst); case Opcode::BUFFER_ATOMIC_DEC: return BUFFER_ATOMIC(AtomicOp::Dec, inst); + case Opcode::BUFFER_ATOMIC_FMIN: + return BUFFER_ATOMIC(AtomicOp::Fmin, inst); + case Opcode::BUFFER_ATOMIC_FMAX: + return BUFFER_ATOMIC(AtomicOp::Fmax, inst); // MIMG // Image load operations @@ -300,6 +312,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}; @@ -324,7 +337,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)); @@ -357,6 +380,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { return ir.BufferAtomicInc(handle, address, buffer_info); case AtomicOp::Dec: return ir.BufferAtomicDec(handle, address, buffer_info); + case AtomicOp::Fmin: + return ir.BufferAtomicFMin(handle, address, vdata_val, buffer_info); + case AtomicOp::Fmax: + return ir.BufferAtomicFMax(handle, address, vdata_val, buffer_info); default: UNREACHABLE(); } @@ -531,8 +558,10 @@ IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::Scal // Load first dword of T# and S#. We will use them as the handle that will guide resource // tracking pass where to read the sharps. This will later also get patched to the SPIRV texture // binding index. - const IR::Value handle = - ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg)); + const IR::Value handle = ir.GetScalarReg(tsharp_reg); + const IR::Value inline_sampler = + ir.CompositeConstruct(ir.GetScalarReg(sampler_reg), ir.GetScalarReg(sampler_reg + 1), + ir.GetScalarReg(sampler_reg + 2), ir.GetScalarReg(sampler_reg + 3)); // Determine how many address registers need to be passed. // The image type is unknown, so add all 4 possible base registers and resolve later. @@ -568,7 +597,8 @@ IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::Scal const IR::Value address4 = get_addr_reg(12); // Issue the placeholder IR instruction. - IR::Value texel = ir.ImageSampleRaw(handle, address1, address2, address3, address4, info); + IR::Value texel = + ir.ImageSampleRaw(handle, address1, address2, address3, address4, inline_sampler, info); if (info.is_depth && !gather) { // For non-gather depth sampling, only return a single value. texel = ir.CompositeExtract(texel, 0); diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index f25111350..72977b711 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -3,6 +3,7 @@ #pragma once #include +#include #include #include #include @@ -91,11 +92,15 @@ struct ImageResource { using ImageResourceList = boost::container::small_vector; struct SamplerResource { - u32 sharp_idx; - AmdGpu::Sampler inline_sampler{}; + std::variant sampler; u32 associated_image : 4; u32 disable_aniso : 1; + SamplerResource(u32 sharp_idx, u32 associated_image_, bool disable_aniso_) + : sampler{sharp_idx}, associated_image{associated_image_}, disable_aniso{disable_aniso_} {} + SamplerResource(AmdGpu::Sampler sampler_) + : sampler{sampler_}, associated_image{0}, disable_aniso(0) {} + constexpr AmdGpu::Sampler GetSharp(const Info& info) const noexcept; }; using SamplerResourceList = boost::container::small_vector; @@ -210,7 +215,8 @@ struct Info { bool has_image_query{}; bool has_perspective_interp{}; bool has_linear_interp{}; - bool uses_atomic_float_min_max{}; + bool uses_buffer_atomic_float_min_max{}; + bool uses_image_atomic_float_min_max{}; bool uses_lane_id{}; bool uses_group_quad{}; bool uses_group_ballot{}; @@ -219,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{}; @@ -232,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()}, @@ -302,23 +310,27 @@ constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept if (!is_r128) { image = info.ReadUdSharp(sharp_idx); } else { - AmdGpu::Buffer buf = info.ReadUdSharp(sharp_idx); + const auto buf = info.ReadUdSharp(sharp_idx); memcpy(&image, &buf, sizeof(buf)); } if (!image.Valid()) { // Fall back to null image if unbound. - return AmdGpu::Image::Null(); - } - const auto data_fmt = image.GetDataFmt(); - if (is_depth && data_fmt != AmdGpu::DataFormat::Format16 && - data_fmt != AmdGpu::DataFormat::Format32) { - return AmdGpu::Image::NullDepth(); + LOG_DEBUG(Render_Vulkan, "Encountered unbound image!"); + image = is_depth ? AmdGpu::Image::NullDepth() : AmdGpu::Image::Null(); + } else if (is_depth) { + const auto data_fmt = image.GetDataFmt(); + if (data_fmt != AmdGpu::DataFormat::Format16 && data_fmt != AmdGpu::DataFormat::Format32) { + LOG_DEBUG(Render_Vulkan, "Encountered non-depth image used with depth instruction!"); + image = AmdGpu::Image::NullDepth(); + } } return image; } constexpr AmdGpu::Sampler SamplerResource::GetSharp(const Info& info) const noexcept { - return inline_sampler ? inline_sampler : info.ReadUdSharp(sharp_idx); + return std::holds_alternative(sampler) + ? std::get(sampler) + : info.ReadUdSharp(std::get(sampler)); } constexpr AmdGpu::Image FMaskResource::GetSharp(const Info& info) const noexcept { diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 3d7cf71dc..3d64cc5da 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -500,14 +500,40 @@ 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, + BufferInstInfo info) { + return Inst(Opcode::BufferAtomicFMin32, Flags{info}, handle, address, value); } 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, + BufferInstInfo info) { + return Inst(Opcode::BufferAtomicFMax32, Flags{info}, handle, address, value); } Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) { @@ -1694,12 +1720,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) { @@ -1716,8 +1762,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) { @@ -1964,9 +2022,9 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c Value IREmitter::ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, const Value& address3, const Value& address4, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3, - address4); + const Value& inline_sampler, TextureInstInfo info) { + return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3, address4, + inline_sampler); } Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias, diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 215a35ee9..119e3752e 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -140,8 +140,12 @@ public: const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicFMin(const Value& handle, const Value& address, + const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address, const Value& value, bool is_signed, BufferInstInfo info); + [[nodiscard]] Value BufferAtomicFMax(const Value& handle, const Value& address, + const Value& value, BufferInstInfo info); [[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info); [[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address, @@ -295,10 +299,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); @@ -349,7 +353,8 @@ public: [[nodiscard]] Value ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, const Value& address3, - const Value& address4, TextureInstInfo info); + const Value& address4, const Value& inline_sampler, + TextureInstInfo info); [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, const Value& offset, diff --git a/src/shader_recompiler/ir/microinstruction.cpp b/src/shader_recompiler/ir/microinstruction.cpp index c2311afea..84bdb5739 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -70,9 +70,15 @@ 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: case Opcode::BufferAtomicAnd32: diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 1621d2acf..008f44659 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -124,9 +124,15 @@ 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, ) OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) @@ -378,14 +384,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, ) @@ -412,7 +424,7 @@ OPCODE(ConvertU8U32, U8, U32, OPCODE(ConvertU32U8, U32, U8, ) // Image operations -OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, ) +OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, Opaque, ) OPCODE(ImageSampleImplicitLod, F32x4, Opaque, F32x4, F32, Opaque, ) OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) 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/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index ba96d1034..f3972769c 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -20,9 +20,15 @@ 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: case IR::Opcode::BufferAtomicAnd32: @@ -93,6 +99,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: @@ -103,6 +113,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: @@ -168,7 +225,7 @@ public: u32 Add(const SamplerResource& desc) { const u32 index{Add(sampler_resources, desc, [this, &desc](const auto& existing) { - return desc.sharp_idx == existing.sharp_idx; + return desc.sampler == existing.sampler; })}; return index; } @@ -351,8 +408,7 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { const auto pred = [](const IR::Inst* inst) -> std::optional { const auto opcode = inst->GetOpcode(); - if (opcode == IR::Opcode::CompositeConstructU32x2 || // IMAGE_SAMPLE (image+sampler) - opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) + if (opcode == IR::Opcode::ReadConst || // IMAGE_LOAD (image only) opcode == IR::Opcode::GetUserData) { return inst; } @@ -360,26 +416,22 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& }; const auto result = IR::BreadthFirstSearch(&inst, pred); ASSERT_MSG(result, "Unable to find image sharp source"); - const IR::Inst* producer = result.value(); - const bool has_sampler = producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2; - const auto tsharp_handle = has_sampler ? producer->Arg(0).InstRecursive() : producer; + const IR::Inst* tsharp_handle = result.value(); // Read image sharp. const auto tsharp = TrackSharp(tsharp_handle, info); const auto inst_info = inst.Flags(); - auto image = info.ReadUdSharp(tsharp); - if (!image.Valid()) { - LOG_ERROR(Render_Vulkan, "Shader compiled with unbound image!"); - image = AmdGpu::Image::Null(); - } - const auto data_fmt = image.GetDataFmt(); - if (inst_info.is_depth && data_fmt != AmdGpu::DataFormat::Format16 && - data_fmt != AmdGpu::DataFormat::Format32) { - LOG_ERROR(Render_Vulkan, "Shader compiled using non-depth image with depth instruction!"); - image = AmdGpu::Image::NullDepth(); - } - ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite; + const ImageResource image_res = { + .sharp_idx = tsharp, + .is_depth = bool(inst_info.is_depth), + .is_atomic = IsImageAtomicInstruction(inst), + .is_array = bool(inst_info.is_array), + .is_written = is_written, + .is_r128 = bool(inst_info.is_r128), + }; + auto image = image_res.GetSharp(info); + ASSERT(image.GetType() != AmdGpu::ImageType::Invalid); // Patch image instruction if image is FMask. if (image.IsFmask()) { @@ -414,42 +466,38 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& } } - u32 image_binding = descriptors.Add(ImageResource{ - .sharp_idx = tsharp, - .is_depth = bool(inst_info.is_depth), - .is_atomic = IsImageAtomicInstruction(inst), - .is_array = bool(inst_info.is_array), - .is_written = is_written, - .is_r128 = bool(inst_info.is_r128), - }); + u32 image_binding = descriptors.Add(image_res); IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) { // Read sampler sharp. - const auto [sampler_binding, sampler] = [&] -> std::pair { - ASSERT(producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2); - const IR::Value& handle = producer->Arg(1); + const auto sampler_binding = [&] -> u32 { + const auto sampler = inst.Arg(5).InstRecursive(); + ASSERT(sampler && sampler->GetOpcode() == IR::Opcode::CompositeConstructU32x4); + const auto handle = sampler->Arg(0); // Inline sampler resource. if (handle.IsImmediate()) { - LOG_WARNING(Render_Vulkan, "Inline sampler detected"); - const auto inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()}; - const auto binding = descriptors.Add(SamplerResource{ - .sharp_idx = std::numeric_limits::max(), - .inline_sampler = inline_sampler, - }); - return {binding, inline_sampler}; + LOG_DEBUG(Render_Vulkan, "Inline sampler detected"); + const auto [s1, s2, s3, s4] = + std::tuple{sampler->Arg(0), sampler->Arg(1), sampler->Arg(2), sampler->Arg(3)}; + ASSERT(s1.IsImmediate() && s2.IsImmediate() && s3.IsImmediate() && + s4.IsImmediate()); + const auto inline_sampler = AmdGpu::Sampler{ + .raw0 = u64(s2.U32()) << 32 | u64(s1.U32()), + .raw1 = u64(s4.U32()) << 32 | u64(s3.U32()), + }; + const auto binding = descriptors.Add(SamplerResource{inline_sampler}); + return binding; + } else { + // Normal sampler resource. + const auto ssharp_handle = handle.InstRecursive(); + const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); + const auto ssharp = TrackSharp(ssharp_ud, info); + const auto binding = + descriptors.Add(SamplerResource{ssharp, image_binding, disable_aniso}); + return binding; } - // Normal sampler resource. - const auto ssharp_handle = handle.InstRecursive(); - const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle); - const auto ssharp = TrackSharp(ssharp_ud, info); - const auto binding = descriptors.Add(SamplerResource{ - .sharp_idx = ssharp, - .associated_image = image_binding, - .disable_aniso = disable_aniso, - }); - return {binding, info.ReadUdSharp(ssharp)}; }(); // Patch image and sampler handle. inst.SetArg(0, ir.Imm32(image_binding | sampler_binding << 16)); @@ -503,6 +551,22 @@ void PatchDataRingAccess(IR::Block& block, IR::Inst& inst, Info& info, Descripto 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); @@ -519,7 +583,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)} @@ -552,6 +616,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; } 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 4cd16d18f..a87dceb0a 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,11 @@ void Visit(Info& info, const IR::Inst& inst) { case IR::Opcode::SharedAtomicXor32: info.shared_types |= IR::Type::U32; break; + case IR::Opcode::SharedAtomicIAdd64: + 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: @@ -92,13 +95,24 @@ void Visit(Info& info, const IR::Inst& inst) { break; case IR::Opcode::ImageAtomicFMax32: case IR::Opcode::ImageAtomicFMin32: - info.uses_atomic_float_min_max = true; + info.uses_image_atomic_float_min_max = true; + break; + case IR::Opcode::BufferAtomicFMax32: + 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 @@ -112,7 +126,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; @@ -125,22 +139,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/profile.h b/src/shader_recompiler/profile.h index 7d313180f..ad36a2e13 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -28,13 +28,16 @@ struct Profile { bool supports_native_cube_calc{}; bool supports_trinary_minmax{}; 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/shadps4.qrc b/src/shadps4.qrc index 2aee394c8..707fc89b0 100644 --- a/src/shadps4.qrc +++ b/src/shadps4.qrc @@ -36,6 +36,7 @@ images/KBM.png images/fullscreen_icon.png images/refreshlist_icon.png + images/favorite_icon.png images/trophy_icon.png diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 464f02e3a..9b8c28b66 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; @@ -147,6 +151,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 +230,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; @@ -638,9 +646,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 +656,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 +710,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::span(header); while (!rewind->Valid()) { YIELD_GFX(); @@ -801,29 +812,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 +846,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 +868,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 +888,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 +898,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 +916,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 +1031,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 +1101,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..0613823ab 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 @@ -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_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp index 23f9dc0bc..d55e05d1e 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,14 @@ 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); + // 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 +132,26 @@ 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, Config::readbacks(), + [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 +162,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 +183,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 +312,11 @@ 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) { + ASSERT(memory->TryWriteBacking(std::bit_cast(address), value, num_bytes)); + if (!IsRegionRegistered(address, num_bytes)) { + return; + } } Buffer* buffer = [&] { if (is_gds) { @@ -326,25 +344,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 +452,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 +476,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 +814,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; 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, + memory_tracker->ForEachUploadRange( + device_addr, size, is_written, [&](u64 device_addr_out, u64 range_size) { + const u64 offset = staging_buffer.Copy(device_addr_out, range_size); + copies.push_back(vk::BufferCopy{ + .srcOffset = offset, + .dstOffset = device_addr_out - buffer_start, + .size = range_size, + }); }); - total_size_bytes += range_size; - }); SCOPE_EXIT { if (is_texel_buffer) { SynchronizeBufferFromImage(buffer, device_addr, size); } }; - if (total_size_bytes == 0) { + if (copies.empty()) { 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 = { @@ -794,7 +861,7 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size, .bufferMemoryBarrierCount = 1, .pBufferMemoryBarriers = &pre_barrier, }); - cmdbuf.copyBuffer(src_buffer, buffer.buffer, copies); + cmdbuf.copyBuffer(staging_buffer.Handle(), buffer.buffer, copies); cmdbuf.pipelineBarrier2(vk::DependencyInfo{ .dependencyFlags = vk::DependencyFlagBits::eByRegion, .bufferMemoryBarrierCount = 1, @@ -925,7 +992,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 d7d753213..900a27aee 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_base.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); + + /// Waits on pending downloads in the logical page range. + 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,8 @@ 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); bool SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size); @@ -193,11 +204,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 +219,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_base.h b/src/video_core/buffer_cache/memory_tracker.h similarity index 69% rename from src/video_core/buffer_cache/memory_tracker_base.h rename to src/video_core/buffer_cache/memory_tracker.h index c60aa9c80..ca87c7df0 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -9,14 +9,14 @@ #include #include "common/debug.h" #include "common/types.h" -#include "video_core/buffer_cache/word_manager.h" +#include "video_core/buffer_cache/region_manager.h" 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,26 +45,57 @@ 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, bool try_flush, auto&& on_flush) noexcept { + IteratePages( + cpu_addr, size, + [try_flush, &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 (try_flush && 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) { IteratePages(query_cpu_range, query_size, - [&func](RegionManager* manager, u64 offset, size_t size) { + [&func, is_written](RegionManager* manager, u64 offset, size_t size) { + std::scoped_lock lk{manager->lock}; manager->template ForEachModifiedRange( manager->GetCpuAddr() + offset, size, func); + if (is_written) { + manager->template ChangeRegionState( + manager->GetCpuAddr() + offset, size); + } }); } @@ -71,6 +104,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 +124,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 +157,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 new file mode 100644 index 000000000..76e7ee263 --- /dev/null +++ b/src/video_core/buffer_cache/region_definitions.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/bit_array.h" +#include "common/types.h" + +namespace VideoCore { + +constexpr u64 TRACKER_PAGE_BITS = 12; // 4K pages +constexpr u64 TRACKER_BYTES_PER_PAGE = 1ULL << TRACKER_PAGE_BITS; + +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, +}; + +using RegionBits = Common::BitArray; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/region_manager.h b/src/video_core/buffer_cache/region_manager.h new file mode 100644 index 000000000..608b16fb3 --- /dev/null +++ b/src/video_core/buffer_cache/region_manager.h @@ -0,0 +1,195 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "common/config.h" +#include "common/div_ceil.h" +#include "common/logging/log.h" + +#ifdef __linux__ +#include "common/adaptive_mutex.h" +#else +#include "common/spin_lock.h" +#endif +#include "common/debug.h" +#include "common/types.h" +#include "video_core/buffer_cache/region_definitions.h" +#include "video_core/page_manager.h" + +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 16MB virtual address region. + * Information is stored in bitsets for spacial locality and fast update of single pages. + */ +class RegionManager { +public: + explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_) + : tracker{tracker_}, cpu_addr{cpu_addr_} { + cpu.Fill(); + gpu.Clear(); + writeable.Fill(); + readable.Fill(); + } + explicit RegionManager() = default; + + void SetCpuAddress(VAddr new_cpu_addr) { + cpu_addr = new_cpu_addr; + } + + VAddr GetCpuAddr() const { + return cpu_addr; + } + + static constexpr size_t SanitizeAddress(size_t address) { + return static_cast(std::max(static_cast(address), 0LL)); + } + + template + RegionBits& GetRegionBits() noexcept { + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } + } + + template + const RegionBits& GetRegionBits() const noexcept { + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } + } + + /** + * Change the state of a range of pages + * + * @param dirty_addr Base address to mark or unmark as modified + * @param size Size in bytes to mark or unmark as modified + */ + template + 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) / 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; + } + + RegionBits& bits = GetRegionBits(); + if constexpr (enable) { + bits.SetRange(start_page, end_page); + } else { + bits.UnsetRange(start_page, end_page); + } + if constexpr (type == Type::CPU) { + UpdateProtection(); + } else if (Config::readbacks()) { + UpdateProtection(); + } + } + + /** + * Loop over each page in the given range, turn off those bits and notify the tracker if + * needed. Call the given function on each turned off range. + * + * @param query_cpu_range Base CPU address to loop over + * @param size Size in bytes of the CPU range to loop over + * @param func Function to call for each turned off region + */ + template + 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) / 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; + } + + RegionBits& bits = GetRegionBits(); + RegionBits mask(bits, start_page, end_page); + + if constexpr (clear) { + bits.UnsetRange(start_page, end_page); + if constexpr (type == Type::CPU) { + 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); + } + } + + /** + * Returns true when a region has been modified + * + * @param offset Offset in bytes from the start of the buffer + * @param size Size in bytes of the region to query for modifications + */ + template + [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) noexcept { + RENDERER_TRACE; + 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; + } + + const RegionBits& bits = GetRegionBits(); + RegionBits test(bits, start_page, end_page); + return test.Any(); + } + + LockType lock; + +private: + /** + * Notify tracker about changes in the CPU tracking state of a word in the buffer + * + * @param word_index Index to the word to notify to the tracker + * @param current_bits Current state of the word + * @param new_bits New state of the word + * + * @tparam track True when the tracker should start tracking the new pages + */ + template + void UpdateProtection() { + RENDERER_TRACE; + RegionBits mask = is_read ? (~gpu ^ readable) : (cpu ^ writeable); + if (mask.None()) { + return; + } + if constexpr (is_read) { + readable = ~gpu; + } else { + writeable = cpu; + } + tracker->UpdatePageWatchersForRegion(cpu_addr, mask); + } + + PageManager* tracker; + VAddr cpu_addr = 0; + RegionBits cpu; + RegionBits gpu; + RegionBits writeable; + RegionBits readable; +}; + +} // namespace VideoCore diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h deleted file mode 100644 index 51a912c62..000000000 --- a/src/video_core/buffer_cache/word_manager.h +++ /dev/null @@ -1,296 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include -#include -#include - -#ifdef __linux__ -#include "common/adaptive_mutex.h" -#else -#include "common/spin_lock.h" -#endif -#include "common/debug.h" -#include "common/types.h" -#include "video_core/page_manager.h" - -namespace VideoCore { - -constexpr u64 PAGES_PER_WORD = 64; -constexpr u64 BYTES_PER_PAGE = 4_KB; -constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE; - -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_WORDS = HIGHER_PAGE_SIZE / BYTES_PER_WORD; - -enum class Type { - CPU, - GPU, - Untracked, -}; - -using WordsArray = std::array; - -/** - * Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region. - * Information is stored in bitsets for spacial locality and fast update of single pages. - */ -class RegionManager { -public: - explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_) - : tracker{tracker_}, cpu_addr{cpu_addr_} { - cpu.fill(~u64{0}); - gpu.fill(0); - untracked.fill(~u64{0}); - } - explicit RegionManager() = default; - - void SetCpuAddress(VAddr new_cpu_addr) { - cpu_addr = new_cpu_addr; - } - - VAddr GetCpuAddr() const { - return cpu_addr; - } - - static constexpr u64 ExtractBits(u64 word, size_t page_start, size_t page_end) { - constexpr size_t number_bits = sizeof(u64) * 8; - const size_t limit_page_end = number_bits - std::min(page_end, number_bits); - u64 bits = (word >> page_start) << page_start; - bits = (bits << limit_page_end) >> limit_page_end; - return bits; - } - - static constexpr std::pair GetWordPage(VAddr address) { - const size_t converted_address = static_cast(address); - const size_t word_number = converted_address / BYTES_PER_WORD; - const size_t amount_pages = converted_address % BYTES_PER_WORD; - return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE); - } - - template - void IterateWords(size_t offset, size_t size, Func&& func) const { - RENDERER_TRACE; - using FuncReturn = std::invoke_result_t; - static constexpr bool BOOL_BREAK = std::is_same_v; - const size_t start = static_cast(std::max(static_cast(offset), 0LL)); - const size_t end = static_cast(std::max(static_cast(offset + size), 0LL)); - if (start >= HIGHER_PAGE_SIZE || end <= start) { - return; - } - auto [start_word, start_page] = GetWordPage(start); - auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL); - constexpr size_t num_words = NUM_REGION_WORDS; - start_word = std::min(start_word, num_words); - end_word = std::min(end_word, num_words); - const size_t diff = end_word - start_word; - end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD; - end_word = std::min(end_word, num_words); - end_page += diff * PAGES_PER_WORD; - constexpr u64 base_mask{~0ULL}; - for (size_t word_index = start_word; word_index < end_word; word_index++) { - const u64 mask = ExtractBits(base_mask, start_page, end_page); - start_page = 0; - end_page -= PAGES_PER_WORD; - if constexpr (BOOL_BREAK) { - if (func(word_index, mask)) { - return; - } - } else { - func(word_index, mask); - } - } - } - - void IteratePages(u64 mask, auto&& func) const { - RENDERER_TRACE; - size_t offset = 0; - while (mask != 0) { - const size_t empty_bits = std::countr_zero(mask); - offset += empty_bits; - mask >>= empty_bits; - - const size_t continuous_bits = std::countr_one(mask); - func(offset, continuous_bits); - mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0; - offset += continuous_bits; - } - } - - /** - * Change the state of a range of pages - * - * @param dirty_addr Base address to mark or unmark as modified - * @param size Size in bytes to mark or unmark as modified - */ - template - void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { - std::scoped_lock lk{lock}; - std::span state_words = Span(); - IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::CPU) { - UpdateProtection(index, untracked[index], mask); - } - if constexpr (enable) { - state_words[index] |= mask; - if constexpr (type == Type::CPU) { - untracked[index] |= mask; - } - } else { - state_words[index] &= ~mask; - if constexpr (type == Type::CPU) { - untracked[index] &= ~mask; - } - } - }); - } - - /** - * Loop over each page in the given range, turn off those bits and notify the tracker if - * needed. Call the given function on each turned off range. - * - * @param query_cpu_range Base CPU address to loop over - * @param size Size in bytes of the CPU range to loop over - * @param func Function to call for each turned off region - */ - template - void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { - RENDERER_TRACE; - std::scoped_lock lk{lock}; - static_assert(type != Type::Untracked); - - std::span state_words = Span(); - const size_t offset = query_cpu_range - cpu_addr; - bool pending = false; - size_t pending_offset{}; - size_t pending_pointer{}; - const auto release = [&]() { - func(cpu_addr + pending_offset * BYTES_PER_PAGE, - (pending_pointer - pending_offset) * BYTES_PER_PAGE); - }; - IterateWords(offset, size, [&](size_t index, u64 mask) { - RENDERER_TRACE; - if constexpr (type == Type::GPU) { - mask &= ~untracked[index]; - } - const u64 word = state_words[index] & mask; - if constexpr (clear) { - if constexpr (type == Type::CPU) { - UpdateProtection(index, untracked[index], mask); - untracked[index] &= ~mask; - } - state_words[index] &= ~mask; - } - const size_t base_offset = index * PAGES_PER_WORD; - IteratePages(word, [&](size_t pages_offset, size_t pages_size) { - RENDERER_TRACE; - const auto reset = [&]() { - pending_offset = base_offset + pages_offset; - pending_pointer = base_offset + pages_offset + pages_size; - }; - if (!pending) { - reset(); - pending = true; - return; - } - if (pending_pointer == base_offset + pages_offset) { - pending_pointer += pages_size; - return; - } - release(); - reset(); - }); - }); - if (pending) { - release(); - } - } - - /** - * Returns true when a region has been modified - * - * @param offset Offset in bytes from the start of the buffer - * @param size Size in bytes of the region to query for modifications - */ - template - [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { - static_assert(type != Type::Untracked); - - const std::span state_words = Span(); - bool result = false; - IterateWords(offset, size, [&](size_t index, u64 mask) { - if constexpr (type == Type::GPU) { - mask &= ~untracked[index]; - } - const u64 word = state_words[index] & mask; - if (word != 0) { - result = true; - return true; - } - return false; - }); - return result; - } - -private: - /** - * Notify tracker about changes in the CPU tracking state of a word in the buffer - * - * @param word_index Index to the word to notify to the tracker - * @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 - */ - template - void UpdateProtection(u64 word_index, u64 current_bits, u64 new_bits) const { - RENDERER_TRACE; - constexpr s32 delta = add_to_tracker ? 1 : -1; - u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits; - VAddr addr = cpu_addr + word_index * BYTES_PER_WORD; - IteratePages(changed_bits, [&](size_t offset, size_t size) { - tracker->UpdatePageWatchers(addr + offset * BYTES_PER_PAGE, - size * BYTES_PER_PAGE); - }); - } - - template - std::span Span() noexcept { - if constexpr (type == Type::CPU) { - return cpu; - } else if constexpr (type == Type::GPU) { - return gpu; - } else if constexpr (type == Type::Untracked) { - return untracked; - } - } - - template - std::span Span() const noexcept { - if constexpr (type == Type::CPU) { - return cpu; - } else if constexpr (type == Type::GPU) { - return gpu; - } else if constexpr (type == Type::Untracked) { - return untracked; - } - } - -#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP - Common::AdaptiveMutex lock; -#else - Common::SpinLock lock; -#endif - PageManager* tracker; - VAddr cpu_addr = 0; - WordsArray cpu; - WordsArray gpu; - WordsArray untracked; -}; - -} // namespace VideoCore diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index d52afe738..e88147eb5 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt @@ -11,6 +11,7 @@ set(SHADER_FILES detilers/micro_32bpp.comp detilers/micro_64bpp.comp detilers/micro_8bpp.comp + color_to_ms_depth.frag fault_buffer_process.comp fs_tri.vert fsr.comp diff --git a/src/video_core/host_shaders/color_to_ms_depth.frag b/src/video_core/host_shaders/color_to_ms_depth.frag new file mode 100644 index 000000000..e477fc942 --- /dev/null +++ b/src/video_core/host_shaders/color_to_ms_depth.frag @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#version 450 core +#extension GL_EXT_samplerless_texture_functions : require + +layout (binding = 0, set = 0) uniform texture2D color; + +layout (location = 0) in vec2 uv; + +void main() +{ + ivec2 coord = ivec2(uv * vec2(textureSize(color, 0).xy)); + gl_FragDepth = texelFetch(color, coord, 0)[gl_SampleID]; +} diff --git a/src/video_core/page_manager.cpp b/src/video_core/page_manager.cpp index 39c03e7da..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,32 +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; + 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 { - ASSERT_MSG(num_watchers > 0, "Not enough watchers"); - 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; + } } } }; - struct UpdateProtectRange { - VAddr addr; - u64 size; - Core::MemoryPermission perms; - }; - 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_) { @@ -178,86 +201,164 @@ 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; - boost::container::small_vector update_ranges; - { - std::scoped_lock lk(lock); - size_t page = addr >> PAGE_BITS; - auto perms = cached_pages[page].Perm(); - u64 range_begin = 0; - u64 range_bytes = 0; + size_t page = addr >> PAGE_BITS; + const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); - const auto release_pending = [&] { - if (range_bytes > 0) { - RENDERER_TRACE; - // Add pending (un)protect action - update_ranges.push_back({range_begin << PAGE_BITS, range_bytes, perms}); - range_bytes = 0; - } - }; + // 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); - // 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), - "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", - aligned_addr, aligned_end - aligned_addr); + auto perms = cached_pages[page].Perms(); + u64 range_begin = 0; + u64 range_bytes = 0; + u64 potential_range_bytes = 0; - for (; page != page_end; ++page) { - PageState& state = cached_pages[page]; + const auto release_pending = [&] { + if (range_bytes > 0) { + RENDERER_TRACE; + // Perform pending (un)protect action + Protect(range_begin << PAGE_BITS, range_bytes, perms); + range_bytes = 0; + potential_range_bytes = 0; + } + }; - // Apply the change to the page state - const u8 new_count = state.AddDelta(); + // Iterate requested pages + 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), + "Attempted to track non-GPU memory at address {:#x}, size {:#x}.", aligned_addr, + aligned_end - aligned_addr); + for (; page != page_end; ++page) { + PageState& state = cached_pages[page]; + + // Apply the change to the page state + const u8 new_count = state.AddDelta(); + + if (auto new_perms = state.Perms(); new_perms != perms) [[unlikely]] { // If the protection changed add pending (un)protect action - if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { - release_pending(); - perms = new_perms; - } - - // If the page must be (un)protected, add it to the pending range - if ((new_count == 0 && delta < 0) || (new_count == 1 && delta > 0)) { - if (range_bytes == 0) { - range_begin = page; - } - range_bytes += PAGE_SIZE; - } else { - release_pending(); - } + release_pending(); + perms = new_perms; + } else if (range_bytes != 0) { + // If the protection did not change, extend the potential range + potential_range_bytes += PAGE_SIZE; } - // Add pending (un)protect action - release_pending(); + // Only start a new range 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 = page; + potential_range_bytes = PAGE_SIZE; + } + // Extend current range up to potential range + range_bytes = potential_range_bytes; + } } - // Flush deferred protects - for (const auto& range : update_ranges) { - Protect(range.addr, range.size, range.perms); + // Add pending (un)protect action + release_pending(); + } + + 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) { + // 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; + return UpdatePageWatchers(start_addr, size); } + + size_t base_page = (base_addr >> PAGE_BITS); + 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) { + RENDERER_TRACE; + // Perform pending (un)protect action + Protect((range_begin << PAGE_BITS), range_bytes, perms); + range_bytes = 0; + potential_range_bytes = 0; + } + }; + + // 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, is_read>(); + + 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 potential range + potential_range_bytes += PAGE_SIZE; + } + + // If the page is not being updated, skip it + if (!update) { + continue; + } + + // 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; + } + } + + // Add pending (un)protect action + release_pending(); } 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_) @@ -273,12 +374,25 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) { impl->OnUnmap(address, size); } -template +template void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const { - impl->UpdatePageWatchers(addr, size); + impl->UpdatePageWatchers(addr, size); } -template void PageManager::UpdatePageWatchers<1>(VAddr addr, u64 size) const; -template void PageManager::UpdatePageWatchers<-1>(VAddr addr, u64 size) const; +template +void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const { + 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; } // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index 98dd099af..4ca41cb43 100644 --- a/src/video_core/page_manager.h +++ b/src/video_core/page_manager.h @@ -6,6 +6,7 @@ #include #include "common/alignment.h" #include "common/types.h" +#include "video_core/buffer_cache//region_definitions.h" namespace Vulkan { class Rasterizer; @@ -14,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); @@ -28,9 +34,13 @@ public: void OnGpuUnmap(VAddr address, size_t size); /// Updates watches in the pages touching the specified region. - template + template void UpdatePageWatchers(VAddr addr, u64 size) const; + /// 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. static constexpr VAddr GetPageAddr(VAddr addr) { return Common::AlignDown(addr, PAGE_SIZE); diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 63c0a38d6..237fa202d 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -281,6 +281,8 @@ bool Instance::CreateDevice() { if (shader_atomic_float2) { shader_atomic_float2_features = feature_chain.get(); + LOG_INFO(Render_Vulkan, "- shaderBufferFloat32AtomicMinMax: {}", + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax); LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}", shader_atomic_float2_features.shaderImageFloat32AtomicMinMax); } @@ -339,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{ @@ -355,6 +357,7 @@ bool Instance::CreateDevice() { .independentBlend = features.independentBlend, .geometryShader = features.geometryShader, .tessellationShader = features.tessellationShader, + .sampleRateShading = features.sampleRateShading, .dualSrcBlend = features.dualSrcBlend, .logicOp = features.logicOp, .multiDrawIndirect = features.multiDrawIndirect, @@ -384,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, @@ -432,6 +437,8 @@ bool Instance::CreateDevice() { .legacyVertexAttributes = true, }, vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{ + .shaderBufferFloat32AtomicMinMax = + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax, .shaderImageFloat32AtomicMinMax = shader_atomic_float2_features.shaderImageFloat32AtomicMinMax, }, diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index c687e6f67..c9e354186 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -165,12 +165,29 @@ public: return amd_shader_trinary_minmax; } + /// Returns true when the shaderBufferFloat32AtomicMinMax feature of + /// VK_EXT_shader_atomic_float2 is supported. + bool IsShaderAtomicFloatBuffer32MinMaxSupported() const { + return shader_atomic_float2 && + shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax; + } + /// Returns true when the shaderImageFloat32AtomicMinMax feature of /// VK_EXT_shader_atomic_float2 is supported. bool IsShaderAtomicFloatImage32MinMaxSupported() const { 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 && @@ -324,6 +341,9 @@ public: return driver_id != vk::DriverId::eMoltenvk; } + /// Determines if a format is supported for a set of feature flags. + [[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const; + private: /// Creates the logical device opportunistically enabling extensions bool CreateDevice(); @@ -338,9 +358,6 @@ private: /// Gets the supported feature flags for a format. [[nodiscard]] vk::FormatFeatureFlags2 GetFormatFeatureFlags(vk::Format format) const; - /// Determines if a format is supported for a set of feature flags. - [[nodiscard]] bool IsFormatSupported(vk::Format format, vk::FormatFeatureFlags2 flags) const; - private: vk::UniqueInstance instance; vk::PhysicalDevice physical_device; @@ -351,6 +368,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 2c3f4ba2f..7dd468f9a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -216,13 +216,18 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, .supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(), // TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed. .supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(), + .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. @@ -346,8 +351,15 @@ bool PipelineCache::RefreshGraphicsKey() { col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 || col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8); - key.color_formats[remapped_cb] = + const auto format = LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt()); + key.color_formats[remapped_cb] = format; + if (!instance.IsFormatSupported(format, vk::FormatFeatureFlagBits2::eColorAttachment)) { + LOG_WARNING(Render_Vulkan, + "color buffer format {} does not support COLOR_ATTACHMENT_BIT", + vk::to_string(format)); + } + key.color_buffers[remapped_cb] = Shader::PsColorBuffer{ .num_format = col_buf.GetNumberFmt(), .num_conversion = col_buf.GetNumberConversion(), diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 9dea5ceea..e4e026485 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()) { @@ -468,17 +468,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 +485,8 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) { fault_process_pending |= uses_dma; + pipeline->BindResources(set_writes, buffer_barriers, push_data); + return true; } @@ -947,6 +944,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 +960,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 +994,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..4a978746c 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); @@ -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.h b/src/video_core/renderer_vulkan/vk_scheduler.h index c30fc6e0e..8ddf00f6a 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.h +++ b/src/video_core/renderer_vulkan/vk_scheduler.h @@ -328,6 +328,7 @@ public: return render_state; } + /// Returns the current pipeline dynamic state tracking. DynamicState& GetDynamicState() { return dynamic_state; } diff --git a/src/video_core/texture_cache/blit_helper.cpp b/src/video_core/texture_cache/blit_helper.cpp new file mode 100644 index 000000000..1ad41be00 --- /dev/null +++ b/src/video_core/texture_cache/blit_helper.cpp @@ -0,0 +1,256 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "video_core/renderer_vulkan/vk_instance.h" +#include "video_core/renderer_vulkan/vk_scheduler.h" +#include "video_core/renderer_vulkan/vk_shader_util.h" +#include "video_core/texture_cache/blit_helper.h" +#include "video_core/texture_cache/image.h" + +#include "video_core/host_shaders/color_to_ms_depth_frag.h" +#include "video_core/host_shaders/fs_tri_vert.h" + +namespace VideoCore { + +static vk::SampleCountFlagBits ToSampleCount(u32 num_samples) { + switch (num_samples) { + case 1: + return vk::SampleCountFlagBits::e1; + case 2: + return vk::SampleCountFlagBits::e2; + case 4: + return vk::SampleCountFlagBits::e4; + case 8: + return vk::SampleCountFlagBits::e8; + case 16: + return vk::SampleCountFlagBits::e16; + default: + UNREACHABLE_MSG("Unknown samples count = {}", num_samples); + } +} + +BlitHelper::BlitHelper(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_) + : instance{instance_}, scheduler{scheduler_} { + CreateShaders(); + CreatePipelineLayouts(); +} + +BlitHelper::~BlitHelper() = default; + +void BlitHelper::BlitColorToMsDepth(Image& source, Image& dest) { + source.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, vk::AccessFlagBits2::eShaderRead, {}); + dest.Transit(vk::ImageLayout::eDepthAttachmentOptimal, + vk::AccessFlagBits2::eDepthStencilAttachmentWrite, {}); + + const vk::ImageViewUsageCreateInfo color_usage_ci{.usage = vk::ImageUsageFlagBits::eSampled}; + const vk::ImageViewCreateInfo color_view_ci = { + .pNext = &color_usage_ci, + .image = source.image, + .viewType = vk::ImageViewType::e2D, + .format = source.info.pixel_format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0U, + .levelCount = 1U, + .baseArrayLayer = 0U, + .layerCount = 1U, + }, + }; + const auto [color_view_result, color_view] = + instance.GetDevice().createImageView(color_view_ci); + ASSERT_MSG(color_view_result == vk::Result::eSuccess, "Failed to create image view: {}", + vk::to_string(color_view_result)); + const vk::ImageViewUsageCreateInfo depth_usage_ci{ + .usage = vk::ImageUsageFlagBits::eDepthStencilAttachment}; + const vk::ImageViewCreateInfo depth_view_ci = { + .pNext = &depth_usage_ci, + .image = dest.image, + .viewType = vk::ImageViewType::e2D, + .format = dest.info.pixel_format, + .subresourceRange{ + .aspectMask = vk::ImageAspectFlagBits::eDepth, + .baseMipLevel = 0U, + .levelCount = 1U, + .baseArrayLayer = 0U, + .layerCount = 1U, + }, + }; + const auto [depth_view_result, depth_view] = + instance.GetDevice().createImageView(depth_view_ci); + ASSERT_MSG(depth_view_result == vk::Result::eSuccess, "Failed to create image view: {}", + vk::to_string(depth_view_result)); + scheduler.DeferOperation([device = instance.GetDevice(), color_view, depth_view] { + device.destroyImageView(color_view); + device.destroyImageView(depth_view); + }); + + Vulkan::RenderState state{}; + state.has_depth = true; + state.width = dest.info.size.width; + state.height = dest.info.size.height; + state.depth_attachment = vk::RenderingAttachmentInfo{ + .imageView = depth_view, + .imageLayout = vk::ImageLayout::eDepthAttachmentOptimal, + .loadOp = vk::AttachmentLoadOp::eDontCare, + .storeOp = vk::AttachmentStoreOp::eStore, + .clearValue = vk::ClearValue{.depthStencil = {.depth = 0.f}}, + }; + scheduler.BeginRendering(state); + + const auto cmdbuf = scheduler.CommandBuffer(); + const vk::DescriptorImageInfo image_info = { + .sampler = VK_NULL_HANDLE, + .imageView = color_view, + .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal, + }; + const vk::WriteDescriptorSet texture_write = { + .dstSet = VK_NULL_HANDLE, + .dstBinding = 0U, + .dstArrayElement = 0U, + .descriptorCount = 1U, + .descriptorType = vk::DescriptorType::eSampledImage, + .pImageInfo = &image_info, + }; + cmdbuf.pushDescriptorSetKHR(vk::PipelineBindPoint::eGraphics, *single_texture_pl_layout, 0U, + texture_write); + + const DepthPipelineKey key{dest.info.num_samples, dest.info.pixel_format}; + const vk::Pipeline depth_pipeline = GetDepthToMsPipeline(key); + cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, depth_pipeline); + + const vk::Viewport viewport = { + .x = 0, + .y = 0, + .width = float(state.width), + .height = float(state.height), + .minDepth = 0.f, + .maxDepth = 1.f, + }; + cmdbuf.setViewport(0, viewport); + + const vk::Rect2D scissor = { + .offset = {0, 0}, + .extent = {state.width, state.height}, + }; + cmdbuf.setScissor(0, scissor); + + cmdbuf.draw(3, 1, 0, 0); + + scheduler.GetDynamicState().Invalidate(); +} + +vk::Pipeline BlitHelper::GetDepthToMsPipeline(const DepthPipelineKey& key) { + auto it = std::ranges::find(color_to_ms_depth_pl, key, &DepthPipeline::first); + if (it != color_to_ms_depth_pl.end()) { + return *it->second; + } + CreateColorToMSDepthPipeline(key); + return *color_to_ms_depth_pl.back().second; +} + +void BlitHelper::CreateShaders() { + fs_tri_vertex = Vulkan::Compile(HostShaders::FS_TRI_VERT, vk::ShaderStageFlagBits::eVertex, + instance.GetDevice()); + color_to_ms_depth_frag = + Vulkan::Compile(HostShaders::COLOR_TO_MS_DEPTH_FRAG, vk::ShaderStageFlagBits::eFragment, + instance.GetDevice()); +} + +void BlitHelper::CreatePipelineLayouts() { + const vk::DescriptorSetLayoutBinding texture_binding = { + .binding = 0, + .descriptorType = vk::DescriptorType::eSampledImage, + .descriptorCount = 1, + .stageFlags = vk::ShaderStageFlagBits::eFragment, + }; + const vk::DescriptorSetLayoutCreateInfo desc_layout_ci = { + .flags = vk::DescriptorSetLayoutCreateFlagBits::ePushDescriptorKHR, + .bindingCount = 1U, + .pBindings = &texture_binding, + }; + auto [desc_layout_result, desc_layout] = + instance.GetDevice().createDescriptorSetLayoutUnique(desc_layout_ci); + single_texture_descriptor_set_layout = std::move(desc_layout); + const vk::DescriptorSetLayout set_layout = *single_texture_descriptor_set_layout; + const vk::PipelineLayoutCreateInfo layout_info = { + .setLayoutCount = 1U, + .pSetLayouts = &set_layout, + .pushConstantRangeCount = 0U, + .pPushConstantRanges = nullptr, + }; + auto [layout_result, pipeline_layout] = + instance.GetDevice().createPipelineLayoutUnique(layout_info); + ASSERT_MSG(layout_result == vk::Result::eSuccess, + "Failed to create graphics pipeline layout: {}", vk::to_string(layout_result)); + Vulkan::SetObjectName(instance.GetDevice(), *pipeline_layout, "Single texture pipeline layout"); + single_texture_pl_layout = std::move(pipeline_layout); +} + +void BlitHelper::CreateColorToMSDepthPipeline(const DepthPipelineKey& key) { + const vk::PipelineInputAssemblyStateCreateInfo input_assembly = { + .topology = vk::PrimitiveTopology::eTriangleList, + }; + const vk::PipelineMultisampleStateCreateInfo multisampling = { + .rasterizationSamples = ToSampleCount(key.num_samples), + }; + const vk::PipelineDepthStencilStateCreateInfo depth_state = { + .depthTestEnable = true, + .depthWriteEnable = true, + .depthCompareOp = vk::CompareOp::eAlways, + }; + const std::array dynamic_states = {vk::DynamicState::eViewportWithCount, + vk::DynamicState::eScissorWithCount}; + const vk::PipelineDynamicStateCreateInfo dynamic_info = { + .dynamicStateCount = static_cast(dynamic_states.size()), + .pDynamicStates = dynamic_states.data(), + }; + + std::array shader_stages; + shader_stages[0] = { + .stage = vk::ShaderStageFlagBits::eVertex, + .module = fs_tri_vertex, + .pName = "main", + }; + shader_stages[1] = { + .stage = vk::ShaderStageFlagBits::eFragment, + .module = color_to_ms_depth_frag, + .pName = "main", + }; + + const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = { + .colorAttachmentCount = 0U, + .pColorAttachmentFormats = nullptr, + .depthAttachmentFormat = key.depth_format, + .stencilAttachmentFormat = vk::Format::eUndefined, + }; + + const vk::PipelineColorBlendStateCreateInfo color_blending{}; + const vk::PipelineViewportStateCreateInfo viewport_info{}; + const vk::PipelineVertexInputStateCreateInfo vertex_input_info{}; + const vk::PipelineRasterizationStateCreateInfo raster_state{.lineWidth = 1.f}; + + const vk::GraphicsPipelineCreateInfo pipeline_info = { + .pNext = &pipeline_rendering_ci, + .stageCount = static_cast(shader_stages.size()), + .pStages = shader_stages.data(), + .pVertexInputState = &vertex_input_info, + .pInputAssemblyState = &input_assembly, + .pViewportState = &viewport_info, + .pRasterizationState = &raster_state, + .pMultisampleState = &multisampling, + .pDepthStencilState = &depth_state, + .pColorBlendState = &color_blending, + .pDynamicState = &dynamic_info, + .layout = *single_texture_pl_layout, + }; + + auto [pipeline_result, pipeline] = + instance.GetDevice().createGraphicsPipelineUnique(VK_NULL_HANDLE, pipeline_info); + ASSERT_MSG(pipeline_result == vk::Result::eSuccess, "Failed to create graphics pipeline: {}", + vk::to_string(pipeline_result)); + Vulkan::SetObjectName(instance.GetDevice(), *pipeline, "Color to MS Depth {}", key.num_samples); + + color_to_ms_depth_pl.emplace_back(key, std::move(pipeline)); +} + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/blit_helper.h b/src/video_core/texture_cache/blit_helper.h new file mode 100644 index 000000000..8c506bd0b --- /dev/null +++ b/src/video_core/texture_cache/blit_helper.h @@ -0,0 +1,55 @@ +// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include + +#include "common/types.h" +#include "video_core/renderer_vulkan/vk_common.h" + +namespace Vulkan { +class Instance; +class Scheduler; +} // namespace Vulkan + +namespace VideoCore { + +class Image; +class ImageView; + +class BlitHelper { + static constexpr size_t MaxMsPipelines = 6; + +public: + explicit BlitHelper(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler); + ~BlitHelper(); + + void BlitColorToMsDepth(Image& source, Image& dest); + +private: + void CreateShaders(); + void CreatePipelineLayouts(); + + struct DepthPipelineKey { + u32 num_samples; + vk::Format depth_format; + + auto operator<=>(const DepthPipelineKey&) const noexcept = default; + }; + vk::Pipeline GetDepthToMsPipeline(const DepthPipelineKey& key); + void CreateColorToMSDepthPipeline(const DepthPipelineKey& key); + +private: + const Vulkan::Instance& instance; + Vulkan::Scheduler& scheduler; + vk::UniqueDescriptorSetLayout single_texture_descriptor_set_layout; + vk::UniquePipelineLayout single_texture_pl_layout; + vk::ShaderModule fs_tri_vertex; + vk::ShaderModule color_to_ms_depth_frag; + + using DepthPipeline = std::pair; + std::vector color_to_ms_depth_pl{}; +}; + +} // namespace VideoCore diff --git a/src/video_core/texture_cache/image.cpp b/src/video_core/texture_cache/image.cpp index ab9111e6b..7b8ff4403 100644 --- a/src/video_core/texture_cache/image.cpp +++ b/src/video_core/texture_cache/image.cpp @@ -130,11 +130,24 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, constexpr auto tiling = vk::ImageTiling::eOptimal; const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features); - const auto properties = instance->GetPhysicalDevice().getImageFormatProperties( - supported_format, info.type, tiling, usage_flags, flags); - const auto supported_samples = properties.result == vk::Result::eSuccess - ? properties.value.sampleCounts - : vk::SampleCountFlagBits::e1; + const vk::PhysicalDeviceImageFormatInfo2 format_info{ + .format = supported_format, + .type = info.type, + .tiling = tiling, + .usage = usage_flags, + .flags = flags, + }; + const auto image_format_properties = + instance->GetPhysicalDevice().getImageFormatProperties2(format_info); + if (image_format_properties.result == vk::Result::eErrorFormatNotSupported) { + LOG_ERROR(Render_Vulkan, "image format {} type {} is not supported (flags {}, usage {})", + vk::to_string(supported_format), vk::to_string(info.type), + vk::to_string(format_info.flags), vk::to_string(format_info.usage)); + } + const auto supported_samples = + image_format_properties.result == vk::Result::eSuccess + ? image_format_properties.value.imageFormatProperties.sampleCounts + : vk::SampleCountFlagBits::e1; const vk::ImageCreateInfo image_ci = { .flags = flags, diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h index 47718a095..dbd7f7cbb 100644 --- a/src/video_core/texture_cache/image_info.h +++ b/src/video_core/texture_cache/image_info.h @@ -47,6 +47,7 @@ struct ImageInfo { VAddr cmask_addr; VAddr fmask_addr; VAddr htile_addr; + u32 htile_clear_mask{u32(-1)}; } meta_info{}; struct { diff --git a/src/video_core/texture_cache/image_view.cpp b/src/video_core/texture_cache/image_view.cpp index 7befb5259..2e162ce83 100644 --- a/src/video_core/texture_cache/image_view.cpp +++ b/src/video_core/texture_cache/image_view.cpp @@ -29,6 +29,24 @@ vk::ImageViewType ConvertImageViewType(AmdGpu::ImageType type) { } } +bool IsViewTypeCompatible(vk::ImageViewType view_type, vk::ImageType image_type) { + switch (view_type) { + case vk::ImageViewType::e1D: + case vk::ImageViewType::e1DArray: + return image_type == vk::ImageType::e1D; + case vk::ImageViewType::e2D: + case vk::ImageViewType::e2DArray: + return image_type == vk::ImageType::e2D || image_type == vk::ImageType::e3D; + case vk::ImageViewType::eCube: + case vk::ImageViewType::eCubeArray: + return image_type == vk::ImageType::e2D; + case vk::ImageViewType::e3D: + return image_type == vk::ImageType::e3D; + default: + UNREACHABLE(); + } +} + ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept : is_storage{desc.is_written} { const auto dfmt = image.GetDataFmt(); @@ -106,6 +124,11 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info .layerCount = info.range.extent.layers, }, }; + if (!IsViewTypeCompatible(image_view_ci.viewType, image.info.type)) { + LOG_ERROR(Render_Vulkan, "image view type {} is incompatible with image type {}", + vk::to_string(image_view_ci.viewType), vk::to_string(image.info.type)); + } + auto [view_result, view] = instance.GetDevice().createImageViewUnique(image_view_ci); ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}", vk::to_string(view_result)); diff --git a/src/video_core/texture_cache/texture_cache.cpp b/src/video_core/texture_cache/texture_cache.cpp index a47e858ab..a50601af6 100644 --- a/src/video_core/texture_cache/texture_cache.cpp +++ b/src/video_core/texture_cache/texture_cache.cpp @@ -22,7 +22,7 @@ static constexpr u64 NumFramesBeforeRemoval = 32; TextureCache::TextureCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache& buffer_cache_, PageManager& tracker_) : instance{instance_}, scheduler{scheduler_}, buffer_cache{buffer_cache_}, tracker{tracker_}, - tile_manager{instance, scheduler} { + blit_helper{instance, scheduler}, tile_manager{instance, scheduler} { // Create basic null image at fixed image ID. const auto null_id = GetNullImage(vk::Format::eR8G8B8A8Unorm); ASSERT(null_id.index == NULL_IMAGE_ID.index); @@ -177,10 +177,20 @@ ImageId TextureCache::ResolveDepthOverlap(const ImageInfo& requested_info, Bindi auto& new_image = slot_images[new_image_id]; new_image.usage = cache_image.usage; new_image.flags &= ~ImageFlagBits::Dirty; + // When creating a depth buffer through overlap resolution don't clear it on first use. + new_image.info.meta_info.htile_clear_mask = 0; - // Perform depth<->color copy using the intermediate copy buffer. - const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal); - new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0); + if (cache_image.info.num_samples == 1 && new_info.num_samples == 1) { + // Perform depth<->color copy using the intermediate copy buffer. + const auto& copy_buffer = buffer_cache.GetUtilityBuffer(MemoryUsage::DeviceLocal); + new_image.CopyImageWithBuffer(cache_image, copy_buffer.Handle(), 0); + } else if (cache_image.info.num_samples == 1 && new_info.IsDepthStencil() && + new_info.num_samples > 1) { + // Perform a rendering pass to transfer the channels of source as samples in dest. + blit_helper.BlitColorToMsDepth(cache_image, new_image); + } else { + LOG_WARNING(Render_Vulkan, "Unimplemented depth overlap copy"); + } // Free the cache image. FreeImage(cache_image_id); @@ -202,7 +212,8 @@ std::tuple TextureCache::ResolveOverlap(const ImageInfo& imag if (image_info.guest_address == tex_cache_image.info.guest_address) { // Equal address if (image_info.BlockDim() != tex_cache_image.info.BlockDim() || - image_info.num_bits != tex_cache_image.info.num_bits) { + image_info.num_bits * image_info.num_samples != + tex_cache_image.info.num_bits * tex_cache_image.info.num_samples) { // Very likely this kind of overlap is caused by allocation from a pool. if (safe_to_delete) { FreeImage(cache_image_id); @@ -470,8 +481,10 @@ ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) { // Register meta data for this depth buffer if (!(image.flags & ImageFlagBits::MetaRegistered)) { if (desc.info.meta_info.htile_addr) { - surface_metas.emplace(desc.info.meta_info.htile_addr, - MetaDataInfo{.type = MetaDataInfo::Type::HTile}); + surface_metas.emplace( + desc.info.meta_info.htile_addr, + MetaDataInfo{.type = MetaDataInfo::Type::HTile, + .clear_mask = image.info.meta_info.htile_clear_mask}); image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr; image.flags |= ImageFlagBits::MetaRegistered; } @@ -748,7 +761,7 @@ void TextureCache::UntrackImage(ImageId image_id) { image.track_addr = 0; image.track_addr_end = 0; if (size != 0) { - tracker.UpdatePageWatchers<-1>(addr, size); + tracker.UpdatePageWatchers(addr, size); } } @@ -767,7 +780,7 @@ void TextureCache::UntrackImageHead(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePageWatchers<-1>(image_begin, size); + tracker.UpdatePageWatchers(image_begin, size); } void TextureCache::UntrackImageTail(ImageId image_id) { @@ -786,7 +799,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) { // Cehck its hash later. MarkAsMaybeDirty(image_id, image); } - tracker.UpdatePageWatchers<-1>(addr, size); + tracker.UpdatePageWatchers(addr, size); } void TextureCache::DeleteImage(ImageId image_id) { diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index ccfeb36b2..87228b84f 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -9,6 +9,7 @@ #include "common/slot_vector.h" #include "video_core/amdgpu/resource.h" #include "video_core/multi_level_page_table.h" +#include "video_core/texture_cache/blit_helper.h" #include "video_core/texture_cache/image.h" #include "video_core/texture_cache/image_view.h" #include "video_core/texture_cache/sampler.h" @@ -286,6 +287,7 @@ private: Vulkan::Scheduler& scheduler; BufferCache& buffer_cache; PageManager& tracker; + BlitHelper blit_helper; TileManager tile_manager; Common::SlotVector slot_images; Common::SlotVector slot_image_views;