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/CMakeLists.txt b/CMakeLists.txt index 243c84b2e..a729d2f00 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -654,6 +654,7 @@ set(COMMON src/common/logging/backend.cpp src/common/assert.cpp src/common/assert.h src/common/binary_helper.h + src/common/bit_array.h src/common/bit_field.h src/common/bounded_threadsafe_queue.h src/common/concepts.h @@ -914,9 +915,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/shader_cache.cpp @@ -955,6 +957,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 @@ -968,8 +974,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 diff --git a/src/common/bit_array.h b/src/common/bit_array.h new file mode 100644 index 000000000..f211bbf95 --- /dev/null +++ b/src/common/bit_array.h @@ -0,0 +1,411 @@ +// 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~() { + for (size_t i = 0; i < WORD_COUNT; ++i) { + data[i] = ~data[i]; + } + return *this; + } + + inline constexpr BitArray operator|(const BitArray& other) const { + BitArray result = *this; + result |= other; + 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; + result = ~result; + 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 \ No newline at end of file diff --git a/src/common/config.cpp b/src/common/config.cpp index d8f46a17d..9c316949a 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; @@ -81,10 +80,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 +204,6 @@ std::string getChooseHomeTab() { return chooseHomeTab; } -std::string getBackButtonBehavior() { - return backButtonBehavior; -} - bool getUseSpecialPad() { return useSpecialPad; } @@ -428,10 +419,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 +471,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 +512,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 +573,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); @@ -668,7 +620,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 +644,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 +656,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 +727,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; @@ -855,7 +789,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 +797,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 +817,6 @@ void setDefaultValues() { cursorState = HideCursorState::Idle; cursorHideTimeout = 5; trophyNotificationDuration = 6.0; - backButtonBehavior = "left"; useSpecialPad = false; specialPadClass = 1; isDebugDump = false; @@ -937,7 +833,6 @@ void setDefaultValues() { vkHostMarkers = false; vkGuestMarkers = false; rdocEnable = false; - emulator_language = "en_US"; m_language = 1; gpuId = -1; compatibilityData = false; @@ -967,7 +862,7 @@ l3 = x r3 = m options = enter -touchpad = space +touchpad_center = space pad_up = up pad_down = down @@ -999,7 +894,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..38114983f 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -18,77 +18,96 @@ 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 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 +115,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/io_file.h b/src/common/io_file.h index 45787a092..cb01e154a 100644 --- a/src/common/io_file.h +++ b/src/common/io_file.h @@ -186,7 +186,9 @@ public: template size_t WriteRaw(const void* data, size_t size) const { - return std::fwrite(data, sizeof(T), size, file); + auto bytes = std::fwrite(data, sizeof(T), size, file); + std::fflush(file); + return bytes; } template diff --git a/src/core/libraries/kernel/file_system.cpp b/src/core/libraries/kernel/file_system.cpp index fecc606fd..76d1a3339 100644 --- a/src/core/libraries/kernel/file_system.cpp +++ b/src/core/libraries/kernel/file_system.cpp @@ -293,6 +293,7 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) { } return result; } + return file->f.WriteRaw(buf, nbytes); } @@ -750,7 +751,24 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) { *__Error() = POSIX_ENOTEMPTY; return -1; } + + // On Windows, std::filesystem::rename will error if the file has been opened before. std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing); + auto* h = Common::Singleton::Instance(); + auto file = h->GetFile(src_path); + if (file) { + // We need to force ReadWrite if the file had Write access before + // Otherwise f.Open will clear the file contents. + auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write + ? Common::FS::FileAccessMode::ReadWrite + : file->f.GetAccessMode(); + file->f.Close(); + std::filesystem::remove(src_path); + file->f.Open(dst_path, access_mode); + } else { + std::filesystem::remove(src_path); + } + return ORBIS_OK; } 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/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/memory.h b/src/core/memory.h index 6a9b29382..d0a2a09b4 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" diff --git a/src/emulator.cpp b/src/emulator.cpp index bb0318070..283cc9ae2 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)) { diff --git a/src/input/input_handler.cpp b/src/input/input_handler.cpp index 3e2d66a6b..7c4e19103 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,21 @@ 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: + LOG_INFO(Input, "Topuchpad left"); + controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); + controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + break; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: + LOG_INFO(Input, "Topuchpad center"); + controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); + controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); + break; + case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: + LOG_INFO(Input, "Topuchpad right"); + controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); break; case LEFTJOYSTICK_HALFMODE: @@ -519,6 +538,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 +592,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..745906620 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 + {"`", SDLK_GRAVE}, + {"~", SDLK_TILDE}, + {"!", SDLK_EXCLAIM}, + {"@", SDLK_AT}, + {"#", SDLK_HASH}, + {"$", SDLK_DOLLAR}, + {"%", SDLK_PERCENT}, + {"^", SDLK_CARET}, + {"&", SDLK_AMPERSAND}, + {"*", SDLK_ASTERISK}, + {"(", SDLK_LEFTPAREN}, + {")", SDLK_RIGHTPAREN}, + {"-", SDLK_MINUS}, + {"_", SDLK_UNDERSCORE}, + {"=", SDLK_EQUALS}, + {"+", SDLK_PLUS}, + {"[", SDLK_LEFTBRACKET}, + {"]", SDLK_RIGHTBRACKET}, + {"{", SDLK_LEFTBRACE}, + {"}", SDLK_RIGHTBRACE}, + {"\\", SDLK_BACKSLASH}, + {"|", SDLK_PIPE}, + {";", SDLK_SEMICOLON}, + {":", SDLK_COLON}, + {"'", SDLK_APOSTROPHE}, + {"\"", SDLK_DBLAPOSTROPHE}, + {",", SDLK_COMMA}, + {"<", SDLK_LESS}, + {".", SDLK_PERIOD}, + {">", SDLK_GREATER}, + {"/", SDLK_SLASH}, + {"?", SDLK_QUESTION}, + + // 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}, + {"kp.", SDLK_KP_PERIOD}, + {"kp,", SDLK_KP_COMMA}, + {"kp/", SDLK_KP_DIVIDE}, + {"kp*", SDLK_KP_MULTIPLY}, + {"kp-", SDLK_KP_MINUS}, + {"kp+", SDLK_KP_PLUS}, + {"kp=", SDLK_KP_EQUALS}, + {"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.h b/src/qt_gui/control_settings.h index e686f044d..b1fff1dad 100644 --- a/src/qt_gui/control_settings.h +++ b/src/qt_gui/control_settings.h @@ -39,14 +39,28 @@ 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", + const QStringList ButtonOutputs = {"cross", + "circle", + "square", + "triangle", + "l1", + "r1", + "l2", + "r2", + "l3", - "r3", "options", "pad_up", + "r3", + "options", + "pad_up", "pad_down", - "pad_left", "pad_right", "touchpad", "unmapped"}; + "pad_left", + "pad_right", + "touchpad_left", + "touchpad_center", + "touchpad_right", + "unmapped"}; const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y", "unmapped"}; 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..8a5219da1 100644 --- a/src/qt_gui/game_grid_frame.cpp +++ b/src/qt_gui/game_grid_frame.cpp @@ -34,7 +34,8 @@ 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); }); } diff --git a/src/qt_gui/game_list_frame.cpp b/src/qt_gui/game_list_frame.cpp index dd10e0f8b..45a9a4810 100644 --- a/src/qt_gui/game_list_frame.cpp +++ b/src/qt_gui/game_list_frame.cpp @@ -75,7 +75,8 @@ GameListFrame::GameListFrame(std::shared_ptr gui_settings, }); 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); + m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, + m_gui_settings, this, true); }); connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) { diff --git a/src/qt_gui/gui_context_menus.h b/src/qt_gui/gui_context_menus.h index 46a40c5cd..ba82da261 100644 --- a/src/qt_gui/gui_context_menus.h +++ b/src/qt_gui/gui_context_menus.h @@ -32,8 +32,10 @@ class GuiContextMenus : public QObject { public: void RequestGameMenu(const QPoint& pos, QVector& m_games, std::shared_ptr m_compat_info, - QTableWidget* widget, bool isList) { + 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; if (isList) { itemID = widget->currentRow(); @@ -357,7 +359,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(); }); diff --git a/src/qt_gui/gui_settings.h b/src/qt_gui/gui_settings.h index da5542956..0fa807d70 100644 --- a/src/qt_gui/gui_settings.h +++ b/src/qt_gui/gui_settings.h @@ -17,6 +17,12 @@ const QString game_grid = "game_grid"; 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()); diff --git a/src/qt_gui/kbm_gui.cpp b/src/qt_gui/kbm_gui.cpp index 596de6d30..1f7743412 100644 --- a/src/qt_gui/kbm_gui.cpp +++ b/src/qt_gui/kbm_gui.cpp @@ -32,14 +32,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"); @@ -144,187 +164,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)); @@ -374,6 +280,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 +322,7 @@ 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 (close_on_save) QWidget::close(); } @@ -432,7 +339,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"); @@ -481,7 +390,6 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) { if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == ControllerInputs.end()) { - if (output_string == "cross") { ui->CrossButton->setText(QString::fromStdString(input_string)); } else if (output_string == "circle") { @@ -512,8 +420,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 +453,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 +463,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 +472,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; @@ -662,6 +570,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 +600,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,13 +674,232 @@ 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_Exclam: + pressedKeys.insert("!"); + break; + case Qt::Key_At: + pressedKeys.insert("@"); + break; + case Qt::Key_NumberSign: + pressedKeys.insert("#"); + break; + case Qt::Key_Dollar: + pressedKeys.insert("$"); + break; + case Qt::Key_Percent: + pressedKeys.insert("%"); + break; + case Qt::Key_AsciiCircum: + pressedKeys.insert("^"); + break; + case Qt::Key_Ampersand: + pressedKeys.insert("&"); + break; + case Qt::Key_Asterisk: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp*", "*")); + break; + case Qt::Key_ParenLeft: + pressedKeys.insert("("); + break; + case Qt::Key_ParenRight: + pressedKeys.insert(")"); + break; + case Qt::Key_Minus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp-", "-")); + break; + case Qt::Key_Underscore: + pressedKeys.insert("_"); + break; + case Qt::Key_Equal: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp=", "=")); + break; + case Qt::Key_Plus: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp+", "+")); + break; + case Qt::Key_BracketLeft: + pressedKeys.insert("["); + break; + case Qt::Key_BracketRight: + pressedKeys.insert("]"); + break; + case Qt::Key_BraceLeft: + pressedKeys.insert("{"); + break; + case Qt::Key_BraceRight: + pressedKeys.insert("}"); + break; + case Qt::Key_Backslash: + pressedKeys.insert("\\"); + break; + case Qt::Key_Bar: + pressedKeys.insert("|"); + break; + case Qt::Key_Semicolon: + pressedKeys.insert(";"); + break; + case Qt::Key_Colon: + pressedKeys.insert(":"); + break; + case Qt::Key_Apostrophe: + pressedKeys.insert("'"); + break; + case Qt::Key_QuoteDbl: + pressedKeys.insert("\""); + break; + case Qt::Key_Comma: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp,", ",")); + break; + case Qt::Key_Less: + pressedKeys.insert("<"); + break; + case Qt::Key_Period: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp.", ".")); + break; + case Qt::Key_Greater: + pressedKeys.insert(">"); + break; + case Qt::Key_Slash: + pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp/", "/")); + 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: + activateWindow(); +#ifdef _WIN32 + pressedKeys.insert("lwin"); +#else + pressedKeys.insert("lmeta"); +#endif + break; + case Qt::Key_Space: + pressedKeys.insert("space"); + 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; + + // cancel mapping + case Qt::Key_Escape: + pressedKeys.insert("unmapped"); + break; + + // default case default: break; + // bottom text } return true; } @@ -987,8 +918,17 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { 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; } @@ -1019,22 +959,16 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { 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()) { - pressedKeys.insert("mousewheelup"); - } else { - pressedKeys.insert("mousewheelright"); - } + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright")); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); } } else if (wheelEvent->angleDelta().x() < -5) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { - if (Qt::AltModifier & QApplication::keyboardModifiers()) { - pressedKeys.insert("mousewheeldown"); - } else { - pressedKeys.insert("mousewheelleft"); - } + pressedKeys.insert( + GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft")); } else { QMessageBox::information(this, tr("Cannot set mapping"), tr("Mousewheel cannot be mapped to stick outputs")); @@ -1046,4 +980,4 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) { return QDialog::eventFilter(obj, event); } -KBMSettings::~KBMSettings() {} +KBMSettings::~KBMSettings() {} \ No newline at end of file diff --git a/src/qt_gui/kbm_gui.h b/src/qt_gui/kbm_gui.h index bfeed2b01..09a9166b9 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; } @@ -25,22 +37,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); 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..1c40c6c4d 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,160 @@ 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::faq() { + return R"( +Q: What are the emulator-wide keybinds? +A: +-F12: Triggers Renderdoc capture +-F11: Toggles fullscreen +-F10: Toggles FPS counter +-Ctrl+F10: Open the debug menu +-F9: Pauses the emulator if the debug menu is open +-F8: Reparses the config file while in-game +-F7: Toggles mouse capture and mouse input +-F6: Toggles mouse-to-gyro emulation + +Q: How do I switch between mouse and controller joystick input? Why is it even required? +A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it. + +Q: What happens if I accidentally make a typo in the config? +A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'. + +Q: I want to bind to , but your code doesn't support ! +A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4. + +Q: What does default.ini do? +A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead. + +Q: What does the use Per-game Config checkbox do? +A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)"; +} + +QString HelpDialog::syntax() { + return R"( +Below is the file format for mouse, keyboard, and controller inputs: + +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: + 'kp 0', 'kp 1', ..., 'kp 9', + 'kp .', 'kp ,', 'kp /', 'kp *', 'kp -', 'kp +', 'kp =', 'kp enter' + Symbols: + '`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '{', '}', '[', ']', '\', '|', + ';', ':', ''', '"', ',', '<', '.', '>', '/', '?' + 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.)"; +} + +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.)"; +} 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..9379519c2 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(); @@ -492,7 +490,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(); }); @@ -771,14 +769,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 +784,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 +792,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 +800,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 +808,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 +816,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 +824,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 +832,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; @@ -981,7 +979,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 +1120,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 +1162,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 +1187,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..eec1a65de 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: 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 7d0c15e6b..e3f9781d2 100644 --- a/src/qt_gui/translations/ar_SA.ts +++ b/src/qt_gui/translations/ar_SA.ts @@ -1152,10 +1152,6 @@ Unable to Save تعذّر الحفظ - - Cannot bind any unique input more than once - لا يمكن تعيين نفس الزر لأكثر من وظيفة - Press a key اضغط زرًا @@ -1184,6 +1180,14 @@ Cancel إلغاء + + 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 + MainWindow diff --git a/src/qt_gui/translations/ca_ES.ts b/src/qt_gui/translations/ca_ES.ts index 412cefa2c..e41eec14d 100644 --- a/src/qt_gui/translations/ca_ES.ts +++ b/src/qt_gui/translations/ca_ES.ts @@ -11,11 +11,11 @@ shadPS4 is an experimental open-source emulator for the PlayStation 4. - shadPS4 is an experimental open-source emulator for the PlayStation 4. + shadPS4 és un emulador experimental de codi obert de la PlayStation 4. This software should not be used to play games you have not legally obtained. - This software should not be used to play games you have not legally obtained. + Aquest programari no s'hauria de fer servir per jugar jocs que no has obtingut legalment. @@ -26,7 +26,7 @@ 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 - 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 + Els trucs i les correccions són experimentals.\nFes-les servir amb precaució.\n\nDescarrega trucs individualment seleccionant el repositori i clicant al botó de baixada.\nA la pestanya de correccions, et pots descarregar totes les correccions de cop, escull quines vols fer servir i desa la selecció.\n\nCom que no desenvolupem ni els trucs ni les correccions,\nhas d'informar de qualsevol problema als seus autors corresponents.\n\nHas creat un nou truc? Visita:\n No Image Available @@ -66,11 +66,11 @@ You can delete the cheats you don't want after downloading them. - You can delete the cheats you don't want after downloading them. + Pots suprimir trucs que no vols abans de descarregar-los. Do you want to delete the selected file?\n%1 - Do you want to delete the selected file?\n%1 + Estàs segur que vols esborrar el fitxer seleccionat?\n%1 Select Patch File: @@ -102,19 +102,19 @@ Unable to open files.json for reading. - Unable to open files.json for reading. + Error en obrir fitxers .json per modificar-los. No patch file found for the current serial. - No patch file found for the current serial. + No s'ha trobat fitxer de correccions pel número de sèrie actual. Unable to open the file for reading. - Unable to open the file for reading. + No es pot obrir el fitxer per llegir-lo. Unable to open the file for writing. - Unable to open the file for writing. + No es pot obrir el fitxer per modificar-lo. Failed to parse XML: @@ -126,7 +126,7 @@ Options saved successfully. - Options saved successfully. + Opcions desades correctament. Invalid Source @@ -134,7 +134,7 @@ The selected source is invalid. - The selected source is invalid. + La font seleccionada no és vàlida. File Exists @@ -142,7 +142,7 @@ File already exists. Do you want to replace it? - File already exists. Do you want to replace it? + Aquest fitxer ja existeix. Estàs segur que el vols reemplaçar? Failed to save file: @@ -150,7 +150,7 @@ Failed to download file: - Failed to download file: + Error en descarregar el fitxer: Cheats Not Found @@ -158,15 +158,15 @@ No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. + No s'han trobat trucs per aquest joc en aquesta versió del repositori seleccionat, prova-ho amb un altre repositori o amb una versió diferent del joc. Cheats Downloaded Successfully - Cheats Downloaded Successfully + S'han descarregat els trucs correctament You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. + Has descarregat correctament els trucs per aquesta versió del joc des d'aquest repositori. Pots provar de descarregar des d'un altre repositori; si està disponible, també pots seleccionar el fitxer des d'una llista. Failed to save: @@ -182,27 +182,27 @@ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. + Has descarregat les correccions correctament! Totes les correccions disponibles per tots els jocs s'han descarregat, no cal descarregar-les individualment com passa amb els trucs. Si una correcció no apareix, pot ser que no existeixi per aquesta versió o per aquest número de sèrie del joc. Failed to parse JSON data from HTML. - Failed to parse JSON data from HTML. + Error en analitzar les dades JSON del HTML. Failed to retrieve HTML page. - Failed to retrieve HTML page. + Error en recuperar la pàgina HTML. The game is in version: %1 - The game is in version: %1 + La versió del joc és: %1 The downloaded patch only works on version: %1 - The downloaded patch only works on version: %1 + La correcció descarregada només funció amb la versió: %1 You may need to update your game. - You may need to update your game. + Cal actualitzar el joc. Incompatibility Notice @@ -218,7 +218,7 @@ Failed to open files.json for writing - Failed to open files.json for writing + Error en obrir fitxers .json per modificar-los Author: @@ -226,11 +226,11 @@ Directory does not exist: - Directory does not exist: + Aquesta carpeta no existeix: Failed to open files.json for reading. - Failed to open files.json for reading. + Error en obrir fitxers .json per llegir-los. Name: @@ -238,7 +238,7 @@ Can't apply cheats before the game is started - Can't apply cheats before the game is started + No es poden aplicar trucs abans que s'iniciï el joc Close @@ -261,11 +261,11 @@ The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. - The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. + L'actualització automàtica permet fer 60 comprovacions cada hora.\nTu has arribat a aquest límit. Torna a intentar-ho més tard. Failed to parse update information. - Failed to parse update information. + Error en analitzar la informació de l'actualització. No pre-releases found. @@ -277,11 +277,11 @@ No download URL found for the specified asset. - No download URL found for the specified asset. + No s'ha trobat la direcció web de descàrrega. Your version is already up to date! - Your version is already up to date! + La teva versió ja és la més actualitzada! Update Available @@ -309,7 +309,7 @@ Check for Updates at Startup - Check for Updates at Startup + Comprova les actualitzacions a l'inici Update @@ -329,7 +329,7 @@ Network error occurred while trying to access the URL - Network error occurred while trying to access the URL + S'ha produït un error de xarxa en intentar connectar a l'adreça web Download Complete @@ -337,11 +337,11 @@ The update has been downloaded, press OK to install. - The update has been downloaded, press OK to install. + S'ha baixat l'actualització, clica OK per instal·lar-la. Failed to save the update file at - Failed to save the update file at + Error en desar el fitxer d'actualització a Starting Update... @@ -349,14 +349,14 @@ Failed to create the update script file - Failed to create the update script file + Error en crear el fitxer de script d'actualització CompatibilityInfoClass Fetching compatibility data, please wait - Fetching compatibility data, please wait + Obtenint dades de compatibilitat, espera si us plau Cancel @@ -372,11 +372,11 @@ Unable to update compatibility data! Try again later. - Unable to update compatibility data! Try again later. + No s'ha pogut actualitzar les dades de compatibilitat! Prova-ho més tard. Unable to open compatibility_data.json for writing. - Unable to open compatibility_data.json for writing. + No es pot obrir el fitxer compatibility_data.json per modificar-lo. Unknown @@ -431,7 +431,7 @@ Left Stick Deadzone (def:2 max:127) - Left Stick Deadzone (def:2 max:127) + Zona morta de la palanca esquerra (per defecte:2 màxim:127) Left Deadzone @@ -507,7 +507,7 @@ Right Stick Deadzone (def:2, max:127) - Right Stick Deadzone (def:2, max:127) + Zona morta de la palanca dreta (per defecte:2 màxim:127) Right Deadzone @@ -535,7 +535,7 @@ Override Lightbar Color - Override Lightbar Color + Canvia el color de la barra de llum Override Color @@ -547,7 +547,7 @@ Cannot bind axis values more than once - Cannot bind axis values more than once + No es pot vincular els valors de l'eix més d'una vegada Save @@ -570,7 +570,7 @@ EditorDialog Edit Keyboard + Mouse and Controller input bindings - Edit Keyboard + Mouse and Controller input bindings + Edita les assignacions del teclat, ratolí i controlador Use Per-Game configs @@ -582,11 +582,11 @@ Could not open the file for reading - Could not open the file for reading + No es pot obrir el fitxer per llegir-lo Could not open the file for writing - Could not open the file for writing + No es pot obrir el fitxer per modificar-lo Save Changes @@ -594,7 +594,7 @@ Do you want to save changes? - Do you want to save changes? + Voleu desar els canvis? Help @@ -602,11 +602,11 @@ Do you want to reset your custom default config to the original default config? - Do you want to reset your custom default config to the original default config? + Vols reiniciar la configuració personalitzada a la configuració original per defecte? Do you want to reset this config to your custom default config? - Do you want to reset this config to your custom default config? + Vols reiniciar aquesta configuració a la configuració personalitzada per defecte? Reset to Default @@ -624,7 +624,7 @@ GameInfoClass Loading game list, please wait :3 - Loading game list, please wait :3 + Carregant la llista de jocs, espera si us plau :3 Cancel @@ -639,11 +639,11 @@ GameInstallDialog shadPS4 - Choose directory - shadPS4 - Choose directory + shadPS4 - Selecciona carpeta Directory to install games - Directory to install games + Carpeta per instal·lar jocs Browse @@ -655,7 +655,7 @@ Directory to install DLC - Directory to install DLC + Carpeta per instal·lar DLC @@ -718,31 +718,31 @@ Compatibility is untested - Compatibility is untested + No s'ha provat la compatibilitat Game does not initialize properly / crashes the emulator - Game does not initialize properly / crashes the emulator + El joc no s'inicialitza correctament / l'emulador fa fallida Game boots, but only displays a blank screen - Game boots, but only displays a blank screen + El joc s'inicia, però es queda en blanc Game displays an image but does not go past the menu - Game displays an image but does not go past the menu + El joc mostra imatges, però no es pot anar més enllà dels menús Game has game-breaking glitches or unplayable performance - Game has game-breaking glitches or unplayable performance + El joc té errors greus o un rendiment que no el fa jugable Game can be completed with playable performance and no major glitches - Game can be completed with playable performance and no major glitches + El joc pot ser completat amb un rendiment jugable i amb pocs errors greus Click to see details on github - Click to see details on github + Clica per veure els detalls a github Last updated @@ -872,7 +872,7 @@ Shortcut created successfully! - Shortcut created successfully! + S'ha creat la drecera correctament! Error @@ -880,7 +880,7 @@ Error creating shortcut! - Error creating shortcut! + Error en crear la drecera! Game @@ -888,7 +888,7 @@ This game has no update to delete! - This game has no update to delete! + Aquest joc no té una actualització per esborrar! Update @@ -896,7 +896,7 @@ This game has no DLC to delete! - This game has no DLC to delete! + Aquest joc no té un DLC per esborrar! DLC @@ -908,7 +908,7 @@ Are you sure you want to delete %1's %2 directory? - Are you sure you want to delete %1's %2 directory? + Estàs segur que vols eliminar la carpeta %1 de %2? Open Update Folder @@ -920,23 +920,23 @@ This game has no update folder to open! - This game has no update folder to open! + Aquest joc no té carpeta d'actualització per obrir! No log file found for this game! - No log file found for this game! + No s'ha trobat fitxer de registre per aquest joc! Failed to convert icon. - Failed to convert icon. + Error en convertir la icona. This game has no save data to delete! - This game has no save data to delete! + Aquest joc no té dades desades per esborrar! This game has no saved trophies to delete! - This game has no saved trophies to delete! + Aquest joc no té trofeus desats per esborrar! Save Data @@ -1010,7 +1010,7 @@ hold to move left stick at half-speed - hold to move left stick at half-speed + manté per moure la palanca esquerra a mitja velocitat Left Stick @@ -1066,7 +1066,7 @@ *press F7 ingame to activate - *press F7 ingame to activate + Pressiona F7 durant el joc per activar R3 @@ -1078,11 +1078,11 @@ Mouse Movement Parameters - Mouse Movement Parameters + Paràmetres de moviment del ratolí note: click Help Button/Special Keybindings for more information - note: click Help Button/Special Keybindings for more information + nota: clica al botó Ajuda/Assignació de tecles especials per més informació Face Buttons @@ -1110,7 +1110,7 @@ hold to move right stick at half-speed - hold to move right stick at half-speed + manté per moure la palanca dreta a mitja velocitat Right Stick @@ -1118,19 +1118,19 @@ Speed Offset (def 0.125): - Speed Offset (def 0.125): + Compensació de velocitat (0,125 per defecte): Copy from Common Config - Copy from Common Config + Copia de la configuració estàndard Deadzone Offset (def 0.50): - Deadzone Offset (def 0.50): + Desplaçament de zona morta (0,50 per defecte): Speed Multiplier (def 1.0): - Speed Multiplier (def 1.0): + Multiplicador de velocitat (1,0 per defecte): Common Config Selected @@ -1138,24 +1138,20 @@ 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. - 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. + Aquest botó copia les assignacions de la configuració comuna al perfil seleccionat, i no es pot utilitzar si has seleccionat el perfil de la configuració comuna. Copy values from Common Config - Copy values from Common Config + Copia els valors de la configuració estàndard Do you want to overwrite existing mappings with the mappings from the Common Config? - Do you want to overwrite existing mappings with the mappings from the Common Config? + Vols sobreescriure l'assignació actual amb l'assignació de la configuració comuna? Unable to Save No s'ha pogut desar - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Premeu una tecla @@ -1166,7 +1162,7 @@ Mousewheel cannot be mapped to stick outputs - Mousewheel cannot be mapped to stick outputs + La roda del ratolí no es pot assignar a les palanques Save @@ -1184,6 +1180,14 @@ Cancel Cancel·la + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + No es pot assignar una entrada més d'una vegada. S'han assignat de manera duplicada pels següents botons: + +%1 + MainWindow @@ -1269,7 +1273,7 @@ Download Cheats/Patches - Download Cheats/Patches + Descarrega Trucs/Correccions Dump Game List @@ -1281,7 +1285,7 @@ No games found. Please add your games to your library first. - No games found. Please add your games to your library first. + No s'han trobat jocs. Primer afegeix els teus jocs a la llibreria, si us plau. Search... @@ -1349,11 +1353,11 @@ Download Cheats For All Installed Games - Download Cheats For All Installed Games + Descarrega els trucs per tots els jocs instal·lats Download Patches For All Games - Download Patches For All Games + Descarrega les correccions per tots els jocs Download Complete @@ -1361,15 +1365,15 @@ You have downloaded cheats for all the games you have installed. - You have downloaded cheats for all the games you have installed. + Has descarregat trucs per tots els jocs que tens instal·lats. Patches Downloaded Successfully! - Patches Downloaded Successfully! + S'han descarregat les correccions correctament! All Patches available for all games have been downloaded. - All Patches available for all games have been downloaded. + S'han descarregat totes les correccions disponibles de tots els jocs. Games: @@ -1377,7 +1381,7 @@ ELF files (*.bin *.elf *.oelf) - ELF files (*.bin *.elf *.oelf) + Fitxers ELF (*.bin *.elf *.oelf) Game Boot @@ -1385,7 +1389,7 @@ Only one file can be selected! - Only one file can be selected! + Només es pot seleccionar un fitxer! Run Game @@ -1393,11 +1397,11 @@ Eboot.bin file not found - Eboot.bin file not found + No s'ha trobat el fitxer Eboot.bin Game is already running! - Game is already running! + El joc ja està en funcionament! shadPS4 @@ -1441,7 +1445,7 @@ Show Labels Under Icons - Show Labels Under Icons + Mostra les etiquetes sota les icones @@ -1472,7 +1476,7 @@ Default tab when opening settings - Default tab when opening settings + Pestanya predeterminada en obrir la configuració Show Game Size In List @@ -1484,7 +1488,7 @@ Enable Discord Rich Presence - Enable Discord Rich Presence + Activa la Discord Rich Presence Username @@ -1500,7 +1504,7 @@ Open the custom trophy images/sounds folder - Open the custom trophy images/sounds folder + Obre la carpeta de trofeus/sons personalitzats Logger @@ -1532,7 +1536,7 @@ Hide Cursor Idle Timeout - Hide Cursor Idle Timeout + Temps d'espera per ocultar el ratolí s @@ -1608,19 +1612,19 @@ Enable Vulkan Validation Layers - Enable Vulkan Validation Layers + Activa les capes de validació de Vulkan Enable Vulkan Synchronization Validation - Enable Vulkan Synchronization Validation + Activa la validació de sincronització de Vulkan Enable RenderDoc Debugging - Enable RenderDoc Debugging + Habilita la depuració de RenderDoc Enable Crash Diagnostics - Enable Crash Diagnostics + Habilita el diagnòstic d'errors Collect Shaders @@ -1644,7 +1648,7 @@ Check for Updates at Startup - Check for Updates at Startup + Comprova les actualitzacions a l'inici Always Show Changelog @@ -1668,7 +1672,7 @@ Disable Trophy Notification - Disable Trophy Notification + Desactiva les notificacions de trofeus Background Image @@ -1688,7 +1692,7 @@ Update Compatibility Database On Startup - Update Compatibility Database On Startup + Actualitza la base de dades de compatibilitat a l'inici Game Compatibility @@ -1696,11 +1700,11 @@ Display Compatibility Data - Display Compatibility Data + Mostra les dades de compatibilitat Update Compatibility Database - Update Compatibility Database + S'ha actualitzat la base de dades de compatibilitat Volume @@ -1724,79 +1728,79 @@ Point your mouse at an option to display its description. - Point your mouse at an option to display its description. + Mou el ratolí sobre una opció per mostrar una descripció. Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. + Idioma de la consola:\nEstableix l'idioma que la consola PS4 fa servir.\nEs recomana seleccionar un idioma que el joc suporti, que pot variar en funció de la regió. Emulator Language:\nSets the language of the emulator's user interface. - Emulator Language:\nSets the language of the emulator's user interface. + Idioma de l'emulador:\nSelecciona l'idioma de l'interfície d'usuari de l'emulador. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. + Mostra la pantalla d'inici:\nMostra la pantalla d'inici del joc (una imatge especial) mentre el joc s'està iniciant. Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. - Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. + Activa Discord Rich Presence:\nMostra la icona de l'emulador i informació rellevant en el teu perfil de Discord. Username:\nSets the PS4's account username, which may be displayed by some games. - Username:\nSets the PS4's account username, which may be displayed by some games. + Nom d'usuari:\nProveeix el nom d'usuari del compte de PS4, que es farà servir i es veurà en alguns jocs. Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. + Clau de trofeus:\nClau que es fa servir per desxifrar trofeus. Cal obtenir-la de la teva consola desbloquejada.\nAcostuma a tenir caràcters hexadecimals. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. + Tipus de registre:\nEstableix si sincronitza la sortida de la finestra de registre per millorar el rendiment. Pot tenir reaccions adverses en el rendiment. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. + Filtre del registre:\nFiltra el registre per mostrar només la informació especificada.\nExemples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivells: Trace, Debug, Info, Warning, Error, Critical - en aquest ordre, un nivell específic silencia tots els nivells precedents de la llista i mostra només els nivells posteriors. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. + Actualització:\nEstables: Versions oficials llançades cada mes que poden estar desactualitzades, però que són més resistents i estan més provades.\nDiàries: Versions de desenvolupament que tenen totes les últimes funcions i correccions, però poden contenir errors i son menys estables. Background Image:\nControl the opacity of the game background image. - Background Image:\nControl the opacity of the game background image. + Imatge de fons:\nControla la opacitat de la imatge de fons del joc. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + Reprodueix música del títol:\nSi el joc la conté, reprodueix la música del títol si es selecciona el joc a l'interfície gràfica. Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). - Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). + Desactiva les notificacions de trofeus:\nDesactiva les notificacions de trofeus en el joc. El progrés en els trofeus es pot seguir en el visualitzador de trofeus (clicant botó dret del ratolí a la finestra principal). 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. - 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. + Oculta el ratolí:\nSelecciona quan s'ocultarà el ratolí.\nMai: El ratolí serà sempre visible.\nInactiu: Estableix un temps perquè s'oculti el ratolí si està inactiu.\nSempre: El ratolí estarà sempre ocult. 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. + 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. - 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. - 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. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. + Actualitza la compatibilitat a l'inici:\nActualitza automàticament la base de dades de compatibilitat quan s'inicia shadPS4. Update Compatibility Database:\nImmediately update the compatibility database. - Update Compatibility Database:\nImmediately update the compatibility database. + Base de dades de compatibilitat:\nActualitza immediatament la base de dades de compatibilitat. Never @@ -1828,83 +1832,83 @@ 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. + 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. Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. + Amplada/Alçada:\nEstableix les mides de la finestra de l'emulador quan s'inicia, durant el joc es pot canviar la mida.\nAixò és diferent que la resolució del joc. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! - Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! + Divisor Vblank:\nLa taxa de fotogrames a la qual s'actualitza l'emulador és multiplicada per aquest valor. Canviar aquest valor pot tenir reaccions adverses, com incrementar la velocitat del joc, o trencar alguna funcionalitat crítica del joc que no s'espera aquest canvi. Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. + Activa els abocats de shaders:\nPer la millora de la depuració tècnica, desa els shaders del joc en una carpeta mentre es renderitzen. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + Habilita GPU Nul·la:\nPer la millora de la depuració tècnica, desactiva el renderitzat del joc si no hi ha una targeta gràfica. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. - Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. + Activa HDR:\nActiva HDR els jocs que ho suporten.\nEl teu monitor ha de donar suport a l'espai de color BT2020 PQ i al format de cadena d'intercanvi RGB10A2. Game Folders:\nThe list of folders to check for installed games. - Game Folders:\nThe list of folders to check for installed games. + Carpetes de joc:\nLa llista de carpetes on comprovar si hi ha jocs instal·lats. Add:\nAdd a folder to the list. - Add:\nAdd a folder to the list. + Afegeix:\nAfegeix una carpeta a la llista. Remove:\nRemove a folder from the list. - Remove:\nRemove a folder from the list. + Elimina:\nElimina la carpeta de la llista. Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. + Activa l'abocat de depuració:\nDesa els símbols d'importació i exportació i la informació de l'encapçalament del fitxer de programa de PS4 que s'està executant ara mateix. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. + Activa les capes de validació de Vulkan:\nActiva un sistema que valida l'estat del renderitzador de Vulkan i registra informació sobre el seu ús intern.\nAixò redueix el rendiment i pot canviar el comportament de l'emulador. Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. + Activa la validació de sincronització de Vulkan:\nActiva un sistema que valida la sincronització de Vulkan en les tasques de renderització.\nAixò pot reduir el rendiment i pot canviar el comportament de l'emulació. Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. - Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. + Activa la depuració RenderDoc:\nSi aquesta opció està activada, l'emulador tindrà compatibilitat amb RenderDoc per permetre capturar i analitzar els fotogrames renderitzats. Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). - Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). + Recopilar shaders:\nHas de tenir activada aquesta opció per editar els shaders amb el menú de depurador (Ctrl + F10). Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. - Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. + Diagnòstiques de fallides:\nCrea un fitxer .yaml amb informació sobre l'estat de Vulkan en el moment de fer fallida.\nÉs útil per depurar errors del tipus 'Dispositiu perdut'. Si aquesta opció està activada, caldria activar els marcadors de depuració de convidat.\nNo funciona en les targetes gràfiques de Intel.\nCal activar la validació de capes de Vulkan i el SDK de Vulkan perquè funcioni. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. + Copia la memòria intermèdia de la GPU:\nEvita les condicions de carrera que impliquen més enviaments a la targeta gràfica.\nPot ajudar o no amb fer fallida del tipus 0 de PM4. Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Marcadors de depuració de convidat:\nInsereix informació des de l'emulador com marcadors específics de les targetes gràfiques AMB sobre les comandes Vulkan, així com proporcionar noms de depuració.\nSi aquesta opció està activada, caldria activar el diagnòstic d'errors.\nÉs útil per aplicacions com RenderDoc. Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. + Marcadors de depuració de convidat:\nInsereix qualsevol marcador que el joc ha afegit a la memòria intermèdia de les comandes.\nSi està activada aquesta opció, caldria activar el diagnòstic d'errors.\nÉs útil per aplicacions com RenderDoc. Save Data Path:\nThe folder where game save data will be saved. - Save Data Path:\nThe folder where game save data will be saved. + Carpeta de dades desades:\nLa carpeta on es desaran les dades el joc. Browse:\nBrowse for a folder to set as the save data path. - Browse:\nBrowse for a folder to set as the save data path. + Navegador:\nCerca una carpeta que serà assignada com la carpeta de dades desades. Release @@ -1916,7 +1920,7 @@ Set the volume of the background music. - Set the volume of the background music. + Selecciona el volum de la música de fons. Enable Motion Controls @@ -1944,11 +1948,11 @@ Directory to install games - Directory to install games + Carpeta per instal·lar jocs Directory to save data - Directory to save data + Carpeta de les dades desades Video @@ -1968,7 +1972,7 @@ Fullscreen (Borderless) - Fullscreen (Borderless) + Pantalla completa (Sense vores) Window Size @@ -1988,11 +1992,11 @@ Separate Log Files:\nWrites a separate logfile for each game. - Separate Log Files:\nWrites a separate logfile for each game. + Fitxers de registre independents:\nFes servir un fitxer de registre separat per cada joc. Trophy Notification Position - Trophy Notification Position + Posició de les notificacions de trofeus Left @@ -2020,15 +2024,15 @@ Create Portable User Folder from Common User Folder - Create Portable User Folder from Common User Folder + Crea la carpeta d'usuari portàtil a partir de la carpeta d'usuari estàndard Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. - Portable user folder:\nStores shadPS4 settings and data that will be applied only to the shadPS4 build located in the current folder. Restart the app after creating the portable user folder to begin using it. + Carpeta d'usuari portàtil:\nDesa la configuració i les dades de shadPS4 en la carpeta actual. Reinicia shadPS4 després de crear la carpeta d'usuari portàtil per començar a fer-la servir. Cannot create portable user folder - Cannot create portable user folder + No s'ha pogut crear la carpeta d'usuari portàtil %1 already exists @@ -2036,19 +2040,19 @@ Portable user folder created - Portable user folder created + S'ha creat la carpeta d'usuari portàtil %1 successfully created. - %1 successfully created. + %1 s'ha creat correctament. Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. - Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions. + Obre la carpeta de trofeus/sons personalitzats.\nPots afegir imatges i sons personalitzats als trofeus.\nAfeigeix els fitxers com custom_trophy amb els següents noms:\ntrophy.wav o trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNota: El so només funcionarà en les versions QT. * Unsupported Vulkan Version - * Unsupported Vulkan Version + Versió de Vulkan no suportada @@ -2071,7 +2075,7 @@ Show Not Earned Trophies - Show Not Earned Trophies + Mostra els trofeus no aconseguits Show Hidden Trophies diff --git a/src/qt_gui/translations/da_DK.ts b/src/qt_gui/translations/da_DK.ts index 1023c584b..120797aa9 100644 --- a/src/qt_gui/translations/da_DK.ts +++ b/src/qt_gui/translations/da_DK.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/de_DE.ts b/src/qt_gui/translations/de_DE.ts index 1d44eb717..3177be4ff 100644 --- a/src/qt_gui/translations/de_DE.ts +++ b/src/qt_gui/translations/de_DE.ts @@ -1152,10 +1152,6 @@ Unable to Save Speichern nicht möglich - - Cannot bind any unique input more than once - Kann keine eindeutige Eingabe mehr als einmal zuordnen - Press a key Drücken Sie eine Taste @@ -1184,6 +1180,14 @@ Cancel Abbrechen + + 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 + MainWindow diff --git a/src/qt_gui/translations/el_GR.ts b/src/qt_gui/translations/el_GR.ts index 765185c9e..aed2b3b2c 100644 --- a/src/qt_gui/translations/el_GR.ts +++ b/src/qt_gui/translations/el_GR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/en_US.ts b/src/qt_gui/translations/en_US.ts index cc854120f..acd8bc965 100644 --- a/src/qt_gui/translations/en_US.ts +++ b/src/qt_gui/translations/en_US.ts @@ -1056,10 +1056,6 @@ L3 - - Touchpad Click - - Mouse to Joystick @@ -1152,10 +1148,6 @@ Unable to Save - - Cannot bind any unique input more than once - - Press a key @@ -1184,6 +1176,24 @@ Cancel Cancel + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + + + + Touchpad Left + Touchpad Left + + + Touchpad Center + Touchpad Center + + + Touchpad Right + Touchpad Right + MainWindow @@ -1542,10 +1552,6 @@ Controller Controller - - Back Button Behavior - Back Button Behavior - Graphics Graphics @@ -1782,10 +1788,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. @@ -1810,22 +1812,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 9568388cc..f5a268da3 100644 --- a/src/qt_gui/translations/es_ES.ts +++ b/src/qt_gui/translations/es_ES.ts @@ -1152,10 +1152,6 @@ Unable to Save No se Pudo Guardar - - Cannot bind any unique input more than once - No se puede vincular ninguna entrada única más de una vez - Press a key Pulsa una tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + 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 + MainWindow diff --git a/src/qt_gui/translations/fa_IR.ts b/src/qt_gui/translations/fa_IR.ts index 115632444..a21f6e0d2 100644 --- a/src/qt_gui/translations/fa_IR.ts +++ b/src/qt_gui/translations/fa_IR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/fi_FI.ts b/src/qt_gui/translations/fi_FI.ts index c77c63b3e..25104fa40 100644 --- a/src/qt_gui/translations/fi_FI.ts +++ b/src/qt_gui/translations/fi_FI.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/fr_FR.ts b/src/qt_gui/translations/fr_FR.ts index 75e424ad0..a9b434579 100644 --- a/src/qt_gui/translations/fr_FR.ts +++ b/src/qt_gui/translations/fr_FR.ts @@ -1152,10 +1152,6 @@ Unable to Save Impossible de sauvegarder - - Cannot bind any unique input more than once - Impossible de lier une entrée unique plus d'une fois - Press a key Appuyez sur un bouton @@ -1184,6 +1180,14 @@ Cancel Annuler + + 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 + MainWindow diff --git a/src/qt_gui/translations/hu_HU.ts b/src/qt_gui/translations/hu_HU.ts index e396cc4f5..834ecd71a 100644 --- a/src/qt_gui/translations/hu_HU.ts +++ b/src/qt_gui/translations/hu_HU.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/id_ID.ts b/src/qt_gui/translations/id_ID.ts index b4fe48637..d4db7cf18 100644 --- a/src/qt_gui/translations/id_ID.ts +++ b/src/qt_gui/translations/id_ID.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/it_IT.ts b/src/qt_gui/translations/it_IT.ts index a1aa0bb3f..4a2add015 100644 --- a/src/qt_gui/translations/it_IT.ts +++ b/src/qt_gui/translations/it_IT.ts @@ -1152,10 +1152,6 @@ Unable to Save Impossibile Salvare - - Cannot bind any unique input more than once - Non è possibile associare qualsiasi input univoco più di una volta - Press a key Premi un tasto @@ -1184,6 +1180,14 @@ Cancel Annulla + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti: + +%1 + MainWindow diff --git a/src/qt_gui/translations/ja_JP.ts b/src/qt_gui/translations/ja_JP.ts index 7bd7fed8a..99ccc163e 100644 --- a/src/qt_gui/translations/ja_JP.ts +++ b/src/qt_gui/translations/ja_JP.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/ko_KR.ts b/src/qt_gui/translations/ko_KR.ts index 6e5b232a0..b38480f9f 100644 --- a/src/qt_gui/translations/ko_KR.ts +++ b/src/qt_gui/translations/ko_KR.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/lt_LT.ts b/src/qt_gui/translations/lt_LT.ts index a0b047dbb..837ea81fe 100644 --- a/src/qt_gui/translations/lt_LT.ts +++ b/src/qt_gui/translations/lt_LT.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/nb_NO.ts b/src/qt_gui/translations/nb_NO.ts index cb209cfb1..f6bc61ee1 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 @@ -990,7 +990,7 @@ unmapped - Ikke satt opp + Ikke tildelt Left @@ -1152,10 +1152,6 @@ Unable to Save Klarte ikke lagre - - Cannot bind any unique input more than once - Kan ikke tildele unike oppsett mer enn en gang - Press a key Trykk på en tast @@ -1184,6 +1180,14 @@ Cancel Avbryt + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster: + +%1 + MainWindow diff --git a/src/qt_gui/translations/nl_NL.ts b/src/qt_gui/translations/nl_NL.ts index ec676d360..61cd9f359 100644 --- a/src/qt_gui/translations/nl_NL.ts +++ b/src/qt_gui/translations/nl_NL.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/pl_PL.ts b/src/qt_gui/translations/pl_PL.ts index c1343cd2e..0da03b472 100644 --- a/src/qt_gui/translations/pl_PL.ts +++ b/src/qt_gui/translations/pl_PL.ts @@ -1152,10 +1152,6 @@ Unable to Save Zapisywanie nie powiodło się - - Cannot bind any unique input more than once - Nie można powiązać żadnych unikalnych danych wejściowych więcej niż raz - Press a key Naciśnij klawisz @@ -1184,6 +1180,14 @@ Cancel Anuluj + + 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 + MainWindow diff --git a/src/qt_gui/translations/pt_BR.ts b/src/qt_gui/translations/pt_BR.ts index 9f254e272..0afb0a297 100644 --- a/src/qt_gui/translations/pt_BR.ts +++ b/src/qt_gui/translations/pt_BR.ts @@ -1152,10 +1152,6 @@ Unable to Save Não foi possível salvar - - Cannot bind any unique input more than once - Não é possível vincular qualquer entrada única mais de uma vez - Press a key Aperte uma tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + 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 + MainWindow diff --git a/src/qt_gui/translations/pt_PT.ts b/src/qt_gui/translations/pt_PT.ts index a543d0ec3..9876ee291 100644 --- a/src/qt_gui/translations/pt_PT.ts +++ b/src/qt_gui/translations/pt_PT.ts @@ -1152,10 +1152,6 @@ Unable to Save Não foi possível guardar - - Cannot bind any unique input more than once - Não é possível vincular qualquer entrada única mais de uma vez - Press a key Pressione uma tecla @@ -1184,6 +1180,14 @@ Cancel Cancelar + + 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 + MainWindow diff --git a/src/qt_gui/translations/ro_RO.ts b/src/qt_gui/translations/ro_RO.ts index a05bb06a8..670a96372 100644 --- a/src/qt_gui/translations/ro_RO.ts +++ b/src/qt_gui/translations/ro_RO.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/ru_RU.ts b/src/qt_gui/translations/ru_RU.ts index a56127ece..145dba4c9 100644 --- a/src/qt_gui/translations/ru_RU.ts +++ b/src/qt_gui/translations/ru_RU.ts @@ -1138,7 +1138,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 @@ -1152,10 +1152,6 @@ Unable to Save Не удаётся сохранить - - Cannot bind any unique input more than once - Невозможно привязать уникальный ввод более одного раза - Press a key Нажмите кнопку @@ -1184,6 +1180,14 @@ Cancel Отмена + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sl_SI.ts b/src/qt_gui/translations/sl_SI.ts index 47c5f5534..4b0536842 100644 --- a/src/qt_gui/translations/sl_SI.ts +++ b/src/qt_gui/translations/sl_SI.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/sq_AL.ts b/src/qt_gui/translations/sq_AL.ts index c554a283a..033f2aed6 100644 --- a/src/qt_gui/translations/sq_AL.ts +++ b/src/qt_gui/translations/sq_AL.ts @@ -1152,10 +1152,6 @@ Unable to Save Ruajtja Dështoi - - Cannot bind any unique input more than once - Asnjë hyrje unike nuk mund të caktohet më shumë se një herë - Press a key Shtyp një tast @@ -1184,6 +1180,14 @@ Cancel Anulo + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Nuk mund të caktohet e njëjta hyrje unike më shumë se një herë. Hyrjet e dublikuara janë caktuar në butonët e mëposhtëm: + +%1 + MainWindow diff --git a/src/qt_gui/translations/sr_CS.ts b/src/qt_gui/translations/sr_CS.ts index 4277728e6..834976377 100644 --- a/src/qt_gui/translations/sr_CS.ts +++ b/src/qt_gui/translations/sr_CS.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Cancel + + 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 + MainWindow diff --git a/src/qt_gui/translations/sv_SE.ts b/src/qt_gui/translations/sv_SE.ts index b8fab701c..31d6baef8 100644 --- a/src/qt_gui/translations/sv_SE.ts +++ b/src/qt_gui/translations/sv_SE.ts @@ -158,7 +158,7 @@ No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game. - Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet + Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet. Cheats Downloaded Successfully @@ -166,7 +166,7 @@ You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list. - Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga så kan det vara möjligt att använda det genom att välja det genom att välja filen från listan + Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga så kan det vara möjligt att använda det genom att välja det genom att välja filen från listan. Failed to save: @@ -182,7 +182,7 @@ Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game. - Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero på att den inte finns för det specifika serienumret och versionen av spelet + Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero på att den inte finns för det specifika serienumret och versionen av spelet. Failed to parse JSON data from HTML. @@ -261,7 +261,7 @@ The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later. - Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare + Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare. Failed to parse update information. @@ -1152,10 +1152,6 @@ Unable to Save Kunde inte spara - - Cannot bind any unique input more than once - Kan inte binda någon unik inmatning fler än en gång - Press a key Tryck på en tangent @@ -1184,6 +1180,14 @@ Cancel Avbryt + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + Det går inte att binda samma unika inmatning mer än en gång. Dubbla inmatningar har mappats till följande knappar: + +%1 + MainWindow @@ -1728,47 +1732,47 @@ Console Language:\nSets the language that the PS4 game uses.\nIt's recommended to set this to a language the game supports, which will vary by region. - Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner + Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner. Emulator Language:\nSets the language of the emulator's user interface. - Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt + Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt. Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting. - Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas + Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas. Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile. - Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil + Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil. Username:\nSets the PS4's account username, which may be displayed by some games. - Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel + Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel. Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters. - Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken + Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken. Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation. - Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen + Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen. Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it. - Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den + Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den. Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable. - Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila + Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila. Background Image:\nControl the opacity of the game background image. - Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild + Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild. Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. - Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet + Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet. Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window). @@ -1776,27 +1780,27 @@ 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. - Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren + Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren. 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 + 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 + 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 + Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information. Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts. - Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar + Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar. Update Compatibility Database:\nImmediately update the compatibility database. - Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt + Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt. Never @@ -1828,23 +1832,23 @@ 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 + 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. Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution. - Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen + Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen. Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change! - Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan på saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring + Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan på saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring! Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render. - Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas + Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas. Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. - Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort + Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort. Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format. @@ -1852,31 +1856,31 @@ Game Folders:\nThe list of folders to check for installed games. - Spelmappar:\nListan över mappar att leta i efter installerade spel + Spelmappar:\nListan över mappar att leta i efter installerade spel. Add:\nAdd a folder to the list. - Aktivera separat uppdateringsmapp:\nAktiverar installation av speluppdateringar till en separat mapp för enkel hantering.\nDetta kan manuellt skapas genom att lägga till den uppackade uppdateringen till spelmappen med namnet "CUSA00000-UPDATE" där CUSA ID matchar spelets id + Lägg till:\Lägg till en mapp till listan. Remove:\nRemove a folder from the list. - Ta bort:\nTa bort en mapp från listan + Ta bort:\nTa bort en mapp från listan. Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory. - Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog + Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog. Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation. - Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen + Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen. Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation. - Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen + Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen. Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame. - Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta + Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta. Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10). @@ -1884,27 +1888,27 @@ Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging 'Device lost' errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work. - Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av 'Device lost'-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte på Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera + Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av 'Device lost'-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte på Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera. Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes. - Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar + Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar. Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, så väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, så väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc. Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc. - Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc + Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc. Save Data Path:\nThe folder where game save data will be saved. - Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas + Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas. Browse:\nBrowse for a folder to set as the save data path. - Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data + Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data. Release diff --git a/src/qt_gui/translations/tr_TR.ts b/src/qt_gui/translations/tr_TR.ts index c6d641470..8db7e02c4 100644 --- a/src/qt_gui/translations/tr_TR.ts +++ b/src/qt_gui/translations/tr_TR.ts @@ -1152,10 +1152,6 @@ Unable to Save Kaydedilemedi - - Cannot bind any unique input more than once - Herhangi bir benzersiz girdi birden fazla kez bağlanamaz - Press a key Bir tuşa basın @@ -1184,6 +1180,14 @@ Cancel İptal + + 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 + MainWindow diff --git a/src/qt_gui/translations/uk_UA.ts b/src/qt_gui/translations/uk_UA.ts index 8b83ae62f..214596e7e 100644 --- a/src/qt_gui/translations/uk_UA.ts +++ b/src/qt_gui/translations/uk_UA.ts @@ -1152,10 +1152,6 @@ Unable to Save Не вдалося зберегти - - Cannot bind any unique input more than once - Не можна прив'язати кнопку вводу більш ніж один раз - Press a key Натисніть клавішу @@ -1184,6 +1180,14 @@ Cancel Відмінити + + 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 + MainWindow diff --git a/src/qt_gui/translations/vi_VN.ts b/src/qt_gui/translations/vi_VN.ts index 1e26f626c..e94515ea8 100644 --- a/src/qt_gui/translations/vi_VN.ts +++ b/src/qt_gui/translations/vi_VN.ts @@ -1152,10 +1152,6 @@ Unable to Save Unable to Save - - Cannot bind any unique input more than once - Cannot bind any unique input more than once - Press a key Press a key @@ -1184,6 +1180,14 @@ Cancel Hủy + + 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 + MainWindow diff --git a/src/qt_gui/translations/zh_CN.ts b/src/qt_gui/translations/zh_CN.ts index 2bc635c41..a8c2c619a 100644 --- a/src/qt_gui/translations/zh_CN.ts +++ b/src/qt_gui/translations/zh_CN.ts @@ -1152,10 +1152,6 @@ Unable to Save 无法保存 - - Cannot bind any unique input more than once - 不能绑定重复的按键 - Press a key 按下按键 @@ -1184,6 +1180,14 @@ Cancel 取消 + + Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: + +%1 + 不能多次绑定任何同一输入。请重新映射以下按键的输入: + +%1 + MainWindow diff --git a/src/qt_gui/translations/zh_TW.ts b/src/qt_gui/translations/zh_TW.ts index 320f73c83..ee7974fca 100644 --- a/src/qt_gui/translations/zh_TW.ts +++ b/src/qt_gui/translations/zh_TW.ts @@ -1152,10 +1152,6 @@ Unable to Save 無法儲存 - - Cannot bind any unique input more than once - 任何唯一的鍵位都不能重複連結 - Press a key 按下按鍵 @@ -1184,6 +1180,14 @@ Cancel 取消 + + 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 + MainWindow 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..735f14639 100644 --- a/src/sdl_window.cpp +++ b/src/sdl_window.cpp @@ -474,11 +474,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..02f290140 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); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp index 47290e7e8..97e455ff8 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_atomic.cpp @@ -19,8 +19,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 +31,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,17 +43,24 @@ 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]; + const auto type = [&] { + if constexpr (is_float) { + return ctx.F32[1]; + } else { + return ctx.U32[1]; + } + }(); if (Sirit::ValidId(buffer.offset)) { address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); } @@ -63,8 +68,8 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); 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, index, buffer.size_dwords, [&] { + return (ctx.*atomic_func)(type, ptr, scope, semantics, value); }); } @@ -199,6 +204,24 @@ Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(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); } @@ -207,6 +230,24 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre return BufferAtomicU32(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); } diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index daf1b973e..12d4fa671 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -92,8 +92,10 @@ Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre 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 EmitBufferAtomicUMin32(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 EmitBufferAtomicUMax32(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); 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..567c059ae 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -972,7 +972,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 +1000,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); } diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.h b/src/shader_recompiler/backend/spirv/spirv_emit_context.h index 93c4ed265..1eb7d05c6 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.h +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.h @@ -203,6 +203,14 @@ public: 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); + } + Info& info; const RuntimeInfo& runtime_info; const Profile& profile; diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index 54e8b8ee8..a102ebf99 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -90,6 +90,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 @@ -357,6 +361,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 +539,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 +578,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..6c931da31 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{}; @@ -318,7 +324,9 @@ constexpr AmdGpu::Image ImageResource::GetSharp(const Info& info) const noexcept } 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..ab6535af2 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -504,12 +504,22 @@ Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, con : Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value); } +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); } +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) { return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address); } @@ -1964,9 +1974,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..9e2f79978 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, @@ -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..1ea5c0967 100644 --- a/src/shader_recompiler/ir/microinstruction.cpp +++ b/src/shader_recompiler/ir/microinstruction.cpp @@ -71,8 +71,10 @@ bool Inst::MayHaveSideEffects() const noexcept { case Opcode::BufferAtomicISub32: case Opcode::BufferAtomicSMin32: case Opcode::BufferAtomicUMin32: + case Opcode::BufferAtomicFMin32: case Opcode::BufferAtomicSMax32: case Opcode::BufferAtomicUMax32: + 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..179a01945 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -125,8 +125,10 @@ OPCODE(BufferAtomicIAdd64, U64, Opaq OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) +OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, ) OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, ) @@ -412,7 +414,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/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index ba96d1034..e278d10f8 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -21,8 +21,10 @@ bool IsBufferAtomic(const IR::Inst& inst) { case IR::Opcode::BufferAtomicISub32: case IR::Opcode::BufferAtomicSMin32: case IR::Opcode::BufferAtomicUMin32: + case IR::Opcode::BufferAtomicFMin32: case IR::Opcode::BufferAtomicSMax32: case IR::Opcode::BufferAtomicUMax32: + case IR::Opcode::BufferAtomicFMax32: case IR::Opcode::BufferAtomicInc32: case IR::Opcode::BufferAtomicDec32: case IR::Opcode::BufferAtomicAnd32: @@ -168,7 +170,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 +353,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,9 +361,7 @@ 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); @@ -427,29 +426,32 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& 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)); 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..b3b4ac36a 100644 --- a/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp +++ b/src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp @@ -92,7 +92,11 @@ 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::LaneId: info.uses_lane_id = true; diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index 7d313180f..bcdf86962 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -28,6 +28,7 @@ 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_workgroup_explicit_memory_layout{}; bool has_broken_spirv_clamp{}; diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index d7d753213..651ba84dc 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -9,7 +9,7 @@ #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/memory_tracker.h" #include "video_core/buffer_cache/range_set.h" #include "video_core/multi_level_page_table.h" diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker.h similarity index 99% rename from src/video_core/buffer_cache/memory_tracker_base.h rename to src/video_core/buffer_cache/memory_tracker.h index c60aa9c80..37fafa2d6 100644 --- a/src/video_core/buffer_cache/memory_tracker_base.h +++ b/src/video_core/buffer_cache/memory_tracker.h @@ -9,7 +9,7 @@ #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 { 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..80c6afdc6 --- /dev/null +++ b/src/video_core/buffer_cache/region_definitions.h @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "common/bit_array.h" +#include "common/types.h" + +namespace VideoCore { + +constexpr u64 PAGES_PER_WORD = 64; +constexpr u64 BYTES_PER_PAGE = 4_KB; + +constexpr u64 HIGHER_PAGE_BITS = 22; +constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; +constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; +constexpr u64 NUM_REGION_PAGES = HIGHER_PAGE_SIZE / BYTES_PER_PAGE; + +enum class Type { + CPU, + GPU, + Writeable, +}; + +using RegionBits = Common::BitArray; + +} // namespace VideoCore \ No newline at end of file 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..07ffee36b --- /dev/null +++ b/src/video_core/buffer_cache/region_manager.h @@ -0,0 +1,208 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include "common/div_ceil.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 { + +/** + * 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(); + gpu.Clear(); + writeable.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 { + static_assert(type != Type::Writeable); + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } else if constexpr (type == Type::Writeable) { + return writeable; + } else { + static_assert(false, "Invalid type"); + } + } + + template + const RegionBits& GetRegionBits() const noexcept { + static_assert(type != Type::Writeable); + if constexpr (type == Type::CPU) { + return cpu; + } else if constexpr (type == Type::GPU) { + return gpu; + } else if constexpr (type == Type::Writeable) { + return writeable; + } else { + static_assert(false, "Invalid type"); + } + } + + /** + * 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) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return; + } + std::scoped_lock lk{lock}; + static_assert(type != Type::Writeable); + + 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(); + } + } + + /** + * 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) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return; + } + std::scoped_lock lk{lock}; + static_assert(type != Type::Writeable); + + RegionBits& bits = GetRegionBits(); + RegionBits mask(bits, start_page, end_page); + + // TODO: this will not be needed once we handle readbacks + if constexpr (type == Type::GPU) { + mask &= ~writeable; + } + + for (const auto& [start, end] : mask) { + func(cpu_addr + start * BYTES_PER_PAGE, (end - start) * BYTES_PER_PAGE); + } + + if constexpr (clear) { + bits.UnsetRange(start_page, end_page); + if constexpr (type == Type::CPU) { + UpdateProtection(); + } + } + } + + /** + * 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 { + RENDERER_TRACE; + const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; + const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); + if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { + return false; + } + // std::scoped_lock lk{lock}; // Is this needed? + static_assert(type != Type::Writeable); + + const RegionBits& bits = GetRegionBits(); + RegionBits test(bits, start_page, end_page); + + // TODO: this will not be needed once we handle readbacks + if constexpr (type == Type::GPU) { + test &= ~writeable; + } + + return test.Any(); + } + +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() { + RENDERER_TRACE; + RegionBits mask = cpu ^ writeable; + + if (mask.None()) { + return; // No changes to the CPU tracking state + } + + writeable = cpu; + tracker->UpdatePageWatchersForRegion(cpu_addr, mask); + } + +#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP + Common::AdaptiveMutex lock; +#else + Common::SpinLock lock; +#endif + PageManager* tracker; + VAddr cpu_addr = 0; + RegionBits cpu; + RegionBits gpu; + RegionBits writeable; +}; + +} // 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..145779070 100644 --- a/src/video_core/page_manager.cpp +++ b/src/video_core/page_manager.cpp @@ -48,19 +48,15 @@ struct PageManager::Impl { u8 AddDelta() { if constexpr (delta == 1) { return ++num_watchers; - } else { + } else if constexpr (delta == -1) { ASSERT_MSG(num_watchers > 0, "Not enough watchers"); return --num_watchers; + } else { + return num_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); inline static Vulkan::Rasterizer* rasterizer; @@ -190,66 +186,122 @@ struct PageManager::Impl { } #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; + auto perms = cached_pages[page].Perm(); + u64 range_begin = 0; + u64 range_bytes = 0; - 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; - } - }; + 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; + } + }; - // 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); + std::scoped_lock lk(lock); - for (; page != page_end; ++page) { - PageState& state = cached_pages[page]; + // 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); - // Apply the change to the page state - const u8 new_count = state.AddDelta(); + 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.Perm(); 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 current range + range_bytes += PAGE_SIZE; } - // Add pending (un)protect action - release_pending(); + // Only start a new range if the page must be (un)protected + if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { + range_begin = page; + range_bytes = PAGE_SIZE; + } } - // 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) { + // Optimization: if all pages are contiguous, use the regular UpdatePageWatchers + const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS); + const u64 size = (start_range.second - start_range.first) << PAGE_BITS; + + UpdatePageWatchers(start_addr, size); + return; } + + size_t base_page = (base_addr >> PAGE_BITS); + auto perms = cached_pages[base_page + start_range.first].Perm(); + u64 range_begin = 0; + u64 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; + } + }; + + std::scoped_lock lk(lock); + + // Iterate pages + for (size_t page = start_range.first; page < end_range.second; ++page) { + PageState& state = cached_pages[base_page + page]; + const bool update = mask.Get(page); + + // Apply the change to the page state + const u8 new_count = update ? state.AddDelta() : state.AddDelta<0>(); + + if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { + // If the protection changed add pending (un)protect action + release_pending(); + perms = new_perms; + } else if (range_bytes != 0) { + // If the protection did not change, extend the current range + range_bytes += PAGE_SIZE; + } + + // If the page is not being updated, skip it + if (!update) { + continue; + } + + // Only start a new range if the page must be (un)protected + if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { + range_begin = base_page + page; + range_bytes = PAGE_SIZE; + } + } + + // Add pending (un)protect action + release_pending(); } std::array cached_pages{}; @@ -273,12 +325,21 @@ 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; } // namespace VideoCore diff --git a/src/video_core/page_manager.h b/src/video_core/page_manager.h index 98dd099af..157b34984 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; @@ -28,9 +29,14 @@ 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..61ddd3f05 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); } @@ -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, @@ -432,6 +435,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..991bfb031 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -165,6 +165,13 @@ 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 { @@ -324,6 +331,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 +348,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; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 843544a0a..c7ad5b331 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -217,6 +217,8 @@ 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_workgroup_explicit_memory_layout = instance_.IsWorkgroupMemoryExplicitLayoutSupported(), @@ -347,8 +349,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_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;