mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-12 12:45:56 +00:00
Merge branch 'main' into shader_cache
This commit is contained in:
commit
f9f75ef085
109 changed files with 2961 additions and 1883 deletions
3
.github/workflows/build.yml
vendored
3
.github/workflows/build.yml
vendored
|
@ -225,6 +225,9 @@ jobs:
|
|||
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:
|
||||
|
|
|
@ -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
|
||||
|
|
411
src/common/bit_array.h
Normal file
411
src/common/bit_array.h
Normal file
|
@ -0,0 +1,411 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include "common/types.h"
|
||||
|
||||
#ifdef __AVX2__
|
||||
#define BIT_ARRAY_USE_AVX
|
||||
#include <immintrin.h>
|
||||
#endif
|
||||
|
||||
namespace Common {
|
||||
|
||||
template <size_t N>
|
||||
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<size_t, size_t>;
|
||||
|
||||
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<const __m256i*>(&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<const __m256i*>(&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<const __m256i*>(&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<const __m256i*>(&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<const __m256i*>(&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<u64, WORD_COUNT> data{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
|
@ -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<GameInstallDir> settings_install_dirs = {};
|
|||
std::vector<bool> install_dirs_enabled = {};
|
||||
std::filesystem::path settings_addon_install_dir = {};
|
||||
std::filesystem::path save_data_path = {};
|
||||
u32 mw_themes = 0;
|
||||
std::vector<std::string> m_elf_viewer;
|
||||
std::vector<std::string> 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<std::string>& elfList) {
|
||||
m_elf_viewer.resize(elfList.size());
|
||||
m_elf_viewer = elfList;
|
||||
}
|
||||
|
||||
void setRecentFiles(const std::vector<std::string>& recentFiles) {
|
||||
m_recent_files.resize(recentFiles.size());
|
||||
m_recent_files = recentFiles;
|
||||
}
|
||||
|
||||
void setEmulatorLanguage(std::string language) {
|
||||
emulator_language = language;
|
||||
}
|
||||
|
||||
void setGameInstallDirs(const std::vector<std::filesystem::path>& 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<std::string> getElfViewer() {
|
||||
return m_elf_viewer;
|
||||
}
|
||||
|
||||
std::vector<std::string> 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<int>(input, "cursorState", HideCursorState::Idle);
|
||||
cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", 5);
|
||||
backButtonBehavior = toml::find_or<std::string>(input, "backButtonBehavior", "left");
|
||||
useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", false);
|
||||
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1);
|
||||
isMotionControlsEnabled = toml::find_or<bool>(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<bool>(gui, "loadGameSizeEnabled", true);
|
||||
mw_themes = toml::find_or<int>(gui, "theme", 0);
|
||||
|
||||
const auto install_dir_array =
|
||||
toml::find_or<std::vector<std::u8string>>(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<std::vector<std::string>>(gui, "elfDirs", {});
|
||||
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
|
||||
emulator_language = toml::find_or<std::string>(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<std::string>(keys, "TrophyKey", "");
|
||||
}
|
||||
|
||||
// Check if the loaded language is in the allowed list
|
||||
const std::vector<std::string> 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<toml::ordered_type_config>(
|
||||
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
|
||||
|
|
|
@ -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<std::filesystem::path>& dirs_config);
|
||||
|
@ -96,57 +115,19 @@ void setAllGameInstallDirs(const std::vector<GameInstallDir>& 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<std::string>& elfList);
|
||||
void setRecentFiles(const std::vector<std::string>& recentFiles);
|
||||
void setEmulatorLanguage(std::string language);
|
||||
|
||||
const std::vector<std::filesystem::path> getGameInstallDirs();
|
||||
const std::vector<bool> getGameInstallDirsEnabled();
|
||||
std::filesystem::path getAddonInstallDir();
|
||||
u32 getMainWindowTheme();
|
||||
std::vector<std::string> getElfViewer();
|
||||
std::vector<std::string> 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
|
||||
|
|
|
@ -186,7 +186,9 @@ public:
|
|||
|
||||
template <typename T>
|
||||
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 <typename T>
|
||||
|
|
|
@ -293,6 +293,7 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) {
|
|||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return file->f.WriteRaw<u8>(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<Core::FileSys::HandleTable>::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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Core::FileSys::HandleTable>::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<Libraries::Net::NetInternal>::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
|
||||
|
|
|
@ -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 <class F, F f>
|
||||
struct OrbisWrapperImpl;
|
||||
|
@ -33,7 +33,7 @@ struct OrbisWrapperImpl<PS4_SYSV_ABI R (*)(Args...), f> {
|
|||
|
||||
#define ORBIS(func) (Libraries::Kernel::OrbisWrapperImpl<decltype(&(func)), func>::wrap)
|
||||
|
||||
int* PS4_SYSV_ABI __Error();
|
||||
s32* PS4_SYSV_ABI __Error();
|
||||
|
||||
void RegisterKernel(Core::Loader::SymbolsResolver* sym);
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -447,22 +447,19 @@ 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<std::chrono::microseconds>(
|
||||
now - controller->GetLastUpdate())
|
||||
float deltaTime =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(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);
|
||||
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);
|
||||
|
||||
|
|
|
@ -22,25 +22,25 @@ static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoi
|
|||
namespace fs = std::filesystem;
|
||||
|
||||
// clang-format off
|
||||
static const std::unordered_map<std::string, std::string> 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<int, std::string> 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__)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <map>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include "common/enum.h"
|
||||
#include "common/singleton.h"
|
||||
|
|
|
@ -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<std::string> args) {
|
||||
if (std::filesystem::is_directory(file)) {
|
||||
|
|
|
@ -66,6 +66,7 @@ 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
|
||||
|
@ -77,7 +78,9 @@ auto output_array = std::array{
|
|||
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_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
|
||||
|
@ -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<int> {
|
||||
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<u32>(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<u32>(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<u32>(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) {
|
||||
|
|
|
@ -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<u8>(type)], sdl_id);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -96,12 +103,19 @@ const std::map<std::string, u32> 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<std::string, AxisMapping> string_to_axis_map = {
|
||||
|
@ -124,6 +138,7 @@ const std::map<std::string, AxisMapping> string_to_axis_map = {
|
|||
{"axis_right_y", {SDL_GAMEPAD_AXIS_RIGHTY, 127}},
|
||||
};
|
||||
const std::map<std::string, u32> string_to_keyboard_key_map = {
|
||||
// alphanumeric
|
||||
{"a", SDLK_A},
|
||||
{"b", SDLK_B},
|
||||
{"c", SDLK_C},
|
||||
|
@ -160,6 +175,73 @@ const std::map<std::string, u32> 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<std::string, u32> 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<std::string, u32> 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;
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <cmath>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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> 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<Theme>(Config::getMainWindowTheme());
|
||||
Theme currentTheme = static_cast<Theme>(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<Theme>(Config::getMainWindowTheme());
|
||||
Theme currentTheme = static_cast<Theme>(m_gui_settings->GetValue(gui::gen_theme).toInt());
|
||||
return currentTheme == Theme::Dark || currentTheme == Theme::Green ||
|
||||
currentTheme == Theme::Blue || currentTheme == Theme::Violet;
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <QLabel>
|
||||
#include <QPixmap>
|
||||
#include <QUrl>
|
||||
#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> 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<gui_settings> m_gui_settings;
|
||||
};
|
||||
|
|
|
@ -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"};
|
||||
|
|
|
@ -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> 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.";
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
class ElfViewer : public QTableWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ElfViewer(QWidget* parent = nullptr);
|
||||
explicit ElfViewer(std::shared_ptr<gui_settings> 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<std::string> dir_list_std;
|
||||
QList<QString> list;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
|
||||
void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) {
|
||||
QTableWidgetItem* item = new QTableWidgetItem();
|
||||
|
|
|
@ -34,7 +34,8 @@ GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> 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);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -75,7 +75,8 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> 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) {
|
||||
|
|
|
@ -32,8 +32,10 @@ class GuiContextMenus : public QObject {
|
|||
public:
|
||||
void RequestGameMenu(const QPoint& pos, QVector<GameInfo>& m_games,
|
||||
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
||||
QTableWidget* widget, bool isList) {
|
||||
std::shared_ptr<gui_settings> settings, QTableWidget* widget,
|
||||
bool isList) {
|
||||
QPoint global_pos = widget->viewport()->mapToGlobal(pos);
|
||||
std::shared_ptr<gui_settings> 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(); });
|
||||
|
|
|
@ -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<QString>()));
|
||||
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<QString>()));
|
||||
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());
|
||||
|
|
|
@ -32,14 +32,34 @@ KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> 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<std::string> 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";
|
||||
// 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")
|
||||
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);
|
||||
add_mapping(ui->CrossButton->text(), "cross");
|
||||
add_mapping(ui->CircleButton->text(), "circle");
|
||||
add_mapping(ui->TriangleButton->text(), "triangle");
|
||||
add_mapping(ui->SquareButton->text(), "square");
|
||||
|
||||
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);
|
||||
add_mapping(ui->DpadUpButton->text(), "pad_up");
|
||||
add_mapping(ui->DpadDownButton->text(), "pad_down");
|
||||
add_mapping(ui->DpadLeftButton->text(), "pad_left");
|
||||
add_mapping(ui->DpadRightButton->text(), "pad_right");
|
||||
|
||||
lines.push_back("");
|
||||
|
||||
input_string = 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);
|
||||
add_mapping(ui->L1Button->text(), "l1");
|
||||
add_mapping(ui->R1Button->text(), "r1");
|
||||
add_mapping(ui->L2Button->text(), "l2");
|
||||
add_mapping(ui->R2Button->text(), "r2");
|
||||
add_mapping(ui->L3Button->text(), "l3");
|
||||
add_mapping(ui->R3Button->text(), "r3");
|
||||
|
||||
lines.push_back("");
|
||||
|
||||
input_string = 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);
|
||||
add_mapping(ui->TouchpadLeftButton->text(), "touchpad_left");
|
||||
add_mapping(ui->TouchpadCenterButton->text(), "touchpad_center");
|
||||
add_mapping(ui->TouchpadRightButton->text(), "touchpad_right");
|
||||
add_mapping(ui->OptionsButton->text(), "options");
|
||||
|
||||
lines.push_back("");
|
||||
|
||||
input_string = 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);
|
||||
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("");
|
||||
|
||||
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);
|
||||
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("");
|
||||
|
||||
input_string = ui->MouseJoystickBox->currentText().toStdString();
|
||||
output_string = "mouse_to_joystick";
|
||||
if (input_string != "unmapped")
|
||||
lines.push_back(output_string + " = " + 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);
|
||||
|
||||
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);
|
||||
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<QString> 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<int>(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<int>(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<int>(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"));
|
||||
|
|
|
@ -4,6 +4,18 @@
|
|||
#include <QDialog>
|
||||
#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::KBMSettings> ui;
|
||||
std::shared_ptr<GameInfoClass> 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);
|
||||
|
|
|
@ -11,8 +11,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1234</width>
|
||||
<height>796</height>
|
||||
<width>1235</width>
|
||||
<height>842</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
|
@ -44,8 +44,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1214</width>
|
||||
<height>746</height>
|
||||
<width>1215</width>
|
||||
<height>792</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
|
@ -54,7 +54,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1211</width>
|
||||
<height>741</height>
|
||||
<height>791</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="RemapLayout">
|
||||
|
@ -793,7 +793,7 @@
|
|||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="layout_system_buttons" stretch="0">
|
||||
<layout class="QVBoxLayout" name="layout_system_buttons" stretch="0,0">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBox_4">
|
||||
<property name="sizePolicy">
|
||||
|
@ -825,8 +825,11 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumHeight">
|
||||
<number>48</number>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||
|
@ -844,8 +847,11 @@
|
|||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumHeight">
|
||||
<number>48</number>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>24</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||
|
@ -858,6 +864,55 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_options">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_start_layout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QPushButton" name="OptionsButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>unmapped</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -1067,34 +1122,13 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_touchpad">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>160</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<widget class="QGroupBox" name="gb_touchpadleft">
|
||||
<property name="title">
|
||||
<string>Touchpad Click</string>
|
||||
<string>Touchpad Left</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||
<item>
|
||||
<widget class="QPushButton" name="TouchpadButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="focusPolicy">
|
||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||
</property>
|
||||
<widget class="QPushButton" name="TouchpadLeftButton">
|
||||
<property name="text">
|
||||
<string>unmapped</string>
|
||||
</property>
|
||||
|
@ -1150,6 +1184,22 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_touchpadcenter">
|
||||
<property name="title">
|
||||
<string>Touchpad Center</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_11">
|
||||
<item>
|
||||
<widget class="QPushButton" name="TouchpadCenterButton">
|
||||
<property name="text">
|
||||
<string>unmapped</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
|
@ -1204,7 +1254,7 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_options">
|
||||
<widget class="QGroupBox" name="gb_touchpadright">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
|
@ -1218,23 +1268,11 @@
|
|||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Options</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="gb_start_layout">
|
||||
<property name="leftMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>5</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>5</number>
|
||||
<string>Touchpad Right</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||
<item>
|
||||
<widget class="QPushButton" name="OptionsButton">
|
||||
<widget class="QPushButton" name="TouchpadRightButton">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
|
|
|
@ -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 <input> to <output>, but your code doesn't support <input>!
|
||||
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.
|
||||
<output>=<input>; is just as valid as <output> = <input>;
|
||||
- ';' at the end of a line is also optional.
|
||||
|
||||
Syntax (aka how a line can look like):
|
||||
#Comment line
|
||||
<controller_button> = <input>, <input>, <input>;
|
||||
<controller_button> = <input>, <input>;
|
||||
<controller_button> = <input>;
|
||||
|
||||
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' = <key>;
|
||||
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' = <key>, <key_to_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' = <device>, <value>, <value>;
|
||||
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.)";
|
||||
}
|
||||
|
|
|
@ -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 <input> to <output>, but your code doesn't support <input>!
|
||||
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
|
||||
<controller_button> = <input>, <input>, <input>;
|
||||
<controller_button> = <input>, <input>;
|
||||
<controller_button> = <input>;
|
||||
|
||||
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, <output>=<input>; is just as valid as <output> = <input>;
|
||||
';' 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' = <key>;
|
||||
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' = <key>, <key_to_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' = <device>, <value>, <value>;
|
||||
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();
|
||||
};
|
|
@ -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<int>(Theme::Dark));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Light));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Green));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Blue));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Violet));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Gruvbox));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::TokyoNight));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(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<int>(Theme::Oled));
|
||||
m_gui_settings->SetValue(gui::gen_theme, static_cast<int>(Theme::Oled));
|
||||
if (isIconBlack) {
|
||||
SetUiIcons(false);
|
||||
isIconBlack = false;
|
||||
|
@ -981,7 +979,7 @@ void MainWindow::InstallDirectory() {
|
|||
}
|
||||
|
||||
void MainWindow::SetLastUsedTheme() {
|
||||
Theme lastTheme = static_cast<Theme>(Config::getMainWindowTheme());
|
||||
Theme lastTheme = static_cast<Theme>(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<std::string> vec = Config::getRecentFiles();
|
||||
if (!vec.empty()) {
|
||||
if (filePath.toStdString() == vec.at(0)) {
|
||||
QList<QString> 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<std::string> vec = Config::getRecentFiles();
|
||||
for (int i = 0; i < vec.size(); i++) {
|
||||
QList<QString> 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();
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ private Q_SLOTS:
|
|||
void ShowGameList();
|
||||
void RefreshGameTable();
|
||||
void HandleResize(QResizeEvent* event);
|
||||
void OnLanguageChanged(const std::string& locale);
|
||||
void OnLanguageChanged(const QString& locale);
|
||||
void toggleLabelsUnderIcons();
|
||||
|
||||
private:
|
||||
|
|
|
@ -75,3 +75,17 @@ void settings::SetValue(const QString& key, const QString& name, const QVariant&
|
|||
}
|
||||
}
|
||||
}
|
||||
QVariant settings::List2Var(const QList<QString>& list) {
|
||||
QByteArray ba;
|
||||
QDataStream stream(&ba, QIODevice::WriteOnly);
|
||||
stream << list;
|
||||
return QVariant(ba);
|
||||
}
|
||||
|
||||
QList<QString> settings::Var2List(const QVariant& var) {
|
||||
QList<QString> list;
|
||||
QByteArray ba = var.toByteArray();
|
||||
QDataStream stream(&ba, QIODevice::ReadOnly);
|
||||
stream >> list;
|
||||
return list;
|
||||
}
|
|
@ -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<QString>& list);
|
||||
static QList<QString> Var2List(const QVariant& var);
|
||||
|
||||
public Q_SLOTS:
|
||||
/** Remove entry */
|
||||
|
|
|
@ -123,11 +123,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> 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> 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<std::string>(data, "Input", "backButtonBehavior", "left"));
|
||||
int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior);
|
||||
ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0);
|
||||
ui->motionControlsCheckBox->setChecked(
|
||||
toml::find_or<bool>(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<std::string> 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");
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -1613,36 +1613,6 @@
|
|||
<property name="bottomMargin">
|
||||
<number>11</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="backButtonBehaviorGroupBox">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>Back Button Behavior</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="BackButtonLayout">
|
||||
<property name="leftMargin">
|
||||
<number>11</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="backButtonBehaviorComboBox"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="motionControlsCheckBox">
|
||||
<property name="text">
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>تعذّر الحفظ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>لا يمكن تعيين نفس الزر لأكثر من وظيفة</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>اضغط زرًا</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>إلغاء</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Speichern nicht möglich</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Kann keine eindeutige Eingabe mehr als einmal zuordnen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Drücken Sie eine Taste</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Abbrechen</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1056,10 +1056,6 @@
|
|||
<source>L3</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Click</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mouse to Joystick</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -1152,10 +1148,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished"></translation>
|
||||
|
@ -1184,6 +1176,24 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Left</source>
|
||||
<translation type="unfinished">Touchpad Left</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Center</source>
|
||||
<translation type="unfinished">Touchpad Center</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Right</source>
|
||||
<translation type="unfinished">Touchpad Right</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -1542,10 +1552,6 @@
|
|||
<source>Controller</source>
|
||||
<translation>Controller</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Back Button Behavior</source>
|
||||
<translation>Back Button Behavior</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Graphics</source>
|
||||
<translation>Graphics</translation>
|
||||
|
@ -1782,10 +1788,6 @@
|
|||
<source>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</source>
|
||||
<translation>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad.</source>
|
||||
<translation>Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information.</source>
|
||||
<translation>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information.</translation>
|
||||
|
@ -1810,22 +1812,6 @@
|
|||
<source>Always</source>
|
||||
<translation>Always</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Left</source>
|
||||
<translation>Touchpad Left</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Right</source>
|
||||
<translation>Touchpad Right</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Touchpad Center</source>
|
||||
<translation>Touchpad Center</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>None</source>
|
||||
<translation>None</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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.</translation>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>No se Pudo Guardar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>No se puede vincular ninguna entrada única más de una vez</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Pulsa una tecla</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Impossible de sauvegarder</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Impossible de lier une entrée unique plus d'une fois</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Appuyez sur un bouton</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Annuler</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Impossibile Salvare</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Non è possibile associare qualsiasi input univoco più di una volta</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Premi un tasto</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Annulla</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -337,7 +337,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>The update has been downloaded, press OK to install.</source>
|
||||
<translation>Oppdateringen ble lastet ned, trykk OK for å installere.</translation>
|
||||
<translation>Oppdateringen er lastet ned, trykk OK for å installere.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to save the update file at</source>
|
||||
|
@ -990,7 +990,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>unmapped</source>
|
||||
<translation>Ikke satt opp</translation>
|
||||
<translation>Ikke tildelt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Left</source>
|
||||
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Klarte ikke lagre</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Kan ikke tildele unike oppsett mer enn en gang</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Trykk på en tast</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Avbryt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Zapisywanie nie powiodło się</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Nie można powiązać żadnych unikalnych danych wejściowych więcej niż raz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Naciśnij klawisz</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Anuluj</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Não foi possível salvar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Não é possível vincular qualquer entrada única mais de uma vez</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Aperte uma tecla</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>Não é possível atribuir a mesma entrada mais de uma vez. Entradas duplicadas foram atribuídas aos seguintes botões:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Não foi possível guardar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Não é possível vincular qualquer entrada única mais de uma vez</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Pressione uma tecla</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Cancelar</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1138,7 +1138,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль это общий конфиг.</translation>
|
||||
<translation>Эта кнопка копирует настройки из общего конфига в текущий выбранный профиль, и не может быть использован, когда выбранный профиль - это общий конфиг.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy values from Common Config</source>
|
||||
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Не удаётся сохранить</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Невозможно привязать уникальный ввод более одного раза</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Нажмите кнопку</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Отмена</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Ruajtja Dështoi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Asnjë hyrje unike nuk mund të caktohet më shumë se një herë</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Shtyp një tast</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Anulo</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>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</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation type="unfinished">Cancel</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -158,7 +158,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cheats Downloaded Successfully</source>
|
||||
|
@ -166,7 +166,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to save:</source>
|
||||
|
@ -182,7 +182,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to parse JSON data from HTML.</source>
|
||||
|
@ -261,7 +261,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later.</source>
|
||||
<translation>Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare</translation>
|
||||
<translation>Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to parse update information.</source>
|
||||
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Kunde inte spara</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Kan inte binda någon unik inmatning fler än en gång</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Tryck på en tangent</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Avbryt</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>Det går inte att binda samma unika inmatning mer än en gång. Dubbla inmatningar har mappats till följande knappar:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
@ -1728,47 +1732,47 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Emulator Language:\nSets the language of the emulator's user interface.</source>
|
||||
<translation>Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt</translation>
|
||||
<translation>Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Show Splash Screen:\nShows the game's splash screen (a special image) while the game is starting.</source>
|
||||
<translation>Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas</translation>
|
||||
<translation>Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile.</source>
|
||||
<translation>Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil</translation>
|
||||
<translation>Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information på din Discord-profil.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Username:\nSets the PS4's account username, which may be displayed by some games.</source>
|
||||
<translation>Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel</translation>
|
||||
<translation>Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source>
|
||||
<translation>Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen</translation>
|
||||
<translation>Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan på emulationen.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Background Image:\nControl the opacity of the game background image.</source>
|
||||
<translation>Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild</translation>
|
||||
<translation>Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source>
|
||||
<translation>Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet</translation>
|
||||
<translation>Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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).</source>
|
||||
|
@ -1776,27 +1780,27 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</source>
|
||||
<translation>Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv</translation>
|
||||
<translation>Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Back Button Behavior:\nSets the controller's back button to emulate tapping the specified position on the PS4 touchpad.</source>
|
||||
<translation>Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad</translation>
|
||||
<translation>Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck på angivna positionen på PS4ns touchpad.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable "Update Compatibility On Startup" to get up-to-date information.</source>
|
||||
<translation>Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information</translation>
|
||||
<translation>Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera "Uppdatera kompatibilitet vid uppstart" för att få uppdaterad information.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts.</source>
|
||||
<translation>Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar</translation>
|
||||
<translation>Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Update Compatibility Database:\nImmediately update the compatibility database.</source>
|
||||
<translation>Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt</translation>
|
||||
<translation>Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Never</source>
|
||||
|
@ -1828,23 +1832,23 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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!</source>
|
||||
<translation>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</translation>
|
||||
<translation>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!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render.</source>
|
||||
<translation>Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas</translation>
|
||||
<translation>Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source>
|
||||
<translation>Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort</translation>
|
||||
<translation>Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
|
@ -1852,31 +1856,31 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Game Folders:\nThe list of folders to check for installed games.</source>
|
||||
<translation>Spelmappar:\nListan över mappar att leta i efter installerade spel</translation>
|
||||
<translation>Spelmappar:\nListan över mappar att leta i efter installerade spel.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Add:\nAdd a folder to the list.</source>
|
||||
<translation>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</translation>
|
||||
<translation>Lägg till:\Lägg till en mapp till listan.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Remove:\nRemove a folder from the list.</source>
|
||||
<translation>Ta bort:\nTa bort en mapp från listan</translation>
|
||||
<translation>Ta bort:\nTa bort en mapp från listan.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</source>
|
||||
<translation>Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog</translation>
|
||||
<translation>Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source>
|
||||
|
@ -1884,27 +1888,27 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>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.</source>
|
||||
<translation>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</translation>
|
||||
<translation>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.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Save Data Path:\nThe folder where game save data will be saved.</source>
|
||||
<translation>Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas</translation>
|
||||
<translation>Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Browse:\nBrowse for a folder to set as the save data path.</source>
|
||||
<translation>Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data</translation>
|
||||
<translation>Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Release</source>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Kaydedilemedi</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Herhangi bir benzersiz girdi birden fazla kez bağlanamaz</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Bir tuşa basın</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>İptal</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>Не вдалося зберегти</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>Не можна прив'язати кнопку вводу більш ніж один раз</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>Натисніть клавішу</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Відмінити</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation type="unfinished">Press a key</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>Hủy</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>无法保存</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>不能绑定重复的按键</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>按下按键</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation>不能多次绑定任何同一输入。请重新映射以下按键的输入:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -1152,10 +1152,6 @@
|
|||
<source>Unable to Save</source>
|
||||
<translation>無法儲存</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once</source>
|
||||
<translation>任何唯一的鍵位都不能重複連結</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Press a key</source>
|
||||
<translation>按下按鍵</translation>
|
||||
|
@ -1184,6 +1180,14 @@
|
|||
<source>Cancel</source>
|
||||
<translation>取消</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</source>
|
||||
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
|
||||
|
||||
%1</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>MainWindow</name>
|
||||
|
|
|
@ -104,14 +104,16 @@ void TrophyViewer::updateTableFilters() {
|
|||
}
|
||||
}
|
||||
|
||||
TrophyViewer::TrophyViewer(QString trophyPath, QString gameTrpPath, QString gameName,
|
||||
TrophyViewer::TrophyViewer(std::shared_ptr<gui_settings> gui_settings, QString trophyPath,
|
||||
QString gameTrpPath, QString gameName,
|
||||
const QVector<TrophyGameInfo>& 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<Theme>(Config::getMainWindowTheme());
|
||||
Theme theme = static_cast<Theme>(m_gui_settings->GetValue(gui::gen_theme).toInt());
|
||||
|
||||
if (theme == Theme::Light) {
|
||||
item->setForeground(QBrush(Qt::black));
|
||||
|
|
|
@ -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> gui_settings, QString trophyPath, QString gameTrpPath,
|
||||
QString gameName = "",
|
||||
const QVector<TrophyGameInfo>& allTrophyGames = QVector<TrophyGameInfo>());
|
||||
|
||||
void updateTrophyInfo();
|
||||
|
@ -77,4 +79,5 @@ private:
|
|||
}
|
||||
return "Unknown";
|
||||
}
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 <bool is_float = false>
|
||||
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<true>(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<true>(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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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};
|
||||
});
|
||||
|
|
|
@ -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<u32>(samp_desc.sampler)
|
||||
? fmt::format("sgpr:{}", std::get<u32>(samp_desc.sampler))
|
||||
: fmt::format("inline:{:#x}:{:#x}",
|
||||
std::get<AmdGpu::Sampler>(samp_desc.sampler).raw0,
|
||||
std::get<AmdGpu::Sampler>(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 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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<u32>(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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
@ -91,11 +92,15 @@ struct ImageResource {
|
|||
using ImageResourceList = boost::container::small_vector<ImageResource, NumImages>;
|
||||
|
||||
struct SamplerResource {
|
||||
u32 sharp_idx;
|
||||
AmdGpu::Sampler inline_sampler{};
|
||||
std::variant<u32, AmdGpu::Sampler> 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<SamplerResource, NumSamplers>;
|
||||
|
@ -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<AmdGpu::Sampler>(sharp_idx);
|
||||
return std::holds_alternative<AmdGpu::Sampler>(sampler)
|
||||
? std::get<AmdGpu::Sampler>(sampler)
|
||||
: info.ReadUdSharp<AmdGpu::Sampler>(std::get<u32>(sampler));
|
||||
}
|
||||
|
||||
constexpr AmdGpu::Image FMaskResource::GetSharp(const Info& info) const noexcept {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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, )
|
||||
|
|
|
@ -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 IR::Inst*> {
|
||||
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<u32, AmdGpu::Sampler> {
|
||||
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<u32>::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{
|
||||
.sharp_idx = ssharp,
|
||||
.associated_image = image_binding,
|
||||
.disable_aniso = disable_aniso,
|
||||
});
|
||||
return {binding, info.ReadUdSharp<AmdGpu::Sampler>(ssharp)};
|
||||
const auto binding =
|
||||
descriptors.Add(SamplerResource{ssharp, image_binding, disable_aniso});
|
||||
return binding;
|
||||
}
|
||||
}();
|
||||
// Patch image and sampler handle.
|
||||
inst.SetArg(0, ir.Imm32(image_binding | sampler_binding << 16));
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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{};
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
#include <vector>
|
||||
#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 {
|
||||
|
28
src/video_core/buffer_cache/region_definitions.h
Normal file
28
src/video_core/buffer_cache/region_definitions.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#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<NUM_REGION_PAGES>;
|
||||
|
||||
} // namespace VideoCore
|
208
src/video_core/buffer_cache/region_manager.h
Normal file
208
src/video_core/buffer_cache/region_manager.h
Normal file
|
@ -0,0 +1,208 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <utility>
|
||||
#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<size_t>(std::max<s64>(static_cast<s64>(address), 0LL));
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
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 <Type type>
|
||||
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 <Type type, bool enable>
|
||||
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<type>();
|
||||
if constexpr (enable) {
|
||||
bits.SetRange(start_page, end_page);
|
||||
} else {
|
||||
bits.UnsetRange(start_page, end_page);
|
||||
}
|
||||
if constexpr (type == Type::CPU) {
|
||||
UpdateProtection<!enable>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <Type type, bool clear>
|
||||
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<type>();
|
||||
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<true>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 <Type type>
|
||||
[[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<type>();
|
||||
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 <bool add_to_tracker>
|
||||
void UpdateProtection() {
|
||||
RENDERER_TRACE;
|
||||
RegionBits mask = cpu ^ writeable;
|
||||
|
||||
if (mask.None()) {
|
||||
return; // No changes to the CPU tracking state
|
||||
}
|
||||
|
||||
writeable = cpu;
|
||||
tracker->UpdatePageWatchersForRegion<add_to_tracker>(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
|
|
@ -1,296 +0,0 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
|
||||
#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<u64, NUM_REGION_WORDS>;
|
||||
|
||||
/**
|
||||
* 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<size_t, size_t> GetWordPage(VAddr address) {
|
||||
const size_t converted_address = static_cast<size_t>(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 <typename Func>
|
||||
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
||||
RENDERER_TRACE;
|
||||
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
|
||||
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
|
||||
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
|
||||
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(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 <Type type, bool enable>
|
||||
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
||||
std::scoped_lock lk{lock};
|
||||
std::span<u64> state_words = Span<type>();
|
||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
||||
if constexpr (type == Type::CPU) {
|
||||
UpdateProtection<!enable>(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 <Type type, bool clear>
|
||||
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) {
|
||||
RENDERER_TRACE;
|
||||
std::scoped_lock lk{lock};
|
||||
static_assert(type != Type::Untracked);
|
||||
|
||||
std::span<u64> state_words = Span<type>();
|
||||
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<true>(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 <Type type>
|
||||
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
||||
static_assert(type != Type::Untracked);
|
||||
|
||||
const std::span<const u64> state_words = Span<type>();
|
||||
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 <bool add_to_tracker>
|
||||
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<delta>(addr + offset * BYTES_PER_PAGE,
|
||||
size * BYTES_PER_PAGE);
|
||||
});
|
||||
}
|
||||
|
||||
template <Type type>
|
||||
std::span<u64> 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 <Type type>
|
||||
std::span<const u64> 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
|
|
@ -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
|
||||
|
|
15
src/video_core/host_shaders/color_to_ms_depth.frag
Normal file
15
src/video_core/host_shaders/color_to_ms_depth.frag
Normal file
|
@ -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];
|
||||
}
|
|
@ -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,12 +186,9 @@ struct PageManager::Impl {
|
|||
}
|
||||
|
||||
#endif
|
||||
template <s32 delta>
|
||||
template <bool track>
|
||||
void UpdatePageWatchers(VAddr addr, u64 size) {
|
||||
RENDERER_TRACE;
|
||||
boost::container::small_vector<UpdateProtectRange, 16> update_ranges;
|
||||
{
|
||||
std::scoped_lock lk(lock);
|
||||
|
||||
size_t page = addr >> PAGE_BITS;
|
||||
auto perms = cached_pages[page].Perm();
|
||||
|
@ -205,40 +198,41 @@ struct PageManager::Impl {
|
|||
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});
|
||||
// Perform pending (un)protect action
|
||||
Protect(range_begin << PAGE_BITS, range_bytes, perms);
|
||||
range_bytes = 0;
|
||||
}
|
||||
};
|
||||
|
||||
std::scoped_lock lk(lock);
|
||||
|
||||
// Iterate requested pages
|
||||
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
|
||||
const u64 aligned_addr = page << PAGE_BITS;
|
||||
const u64 aligned_end = page_end << PAGE_BITS;
|
||||
ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr),
|
||||
"Attempted to track non-GPU memory at address {:#x}, size {:#x}.",
|
||||
aligned_addr, aligned_end - aligned_addr);
|
||||
"Attempted to track non-GPU memory at address {:#x}, size {:#x}.", aligned_addr,
|
||||
aligned_end - aligned_addr);
|
||||
|
||||
for (; page != page_end; ++page) {
|
||||
PageState& state = cached_pages[page];
|
||||
|
||||
// Apply the change to the page state
|
||||
const u8 new_count = state.AddDelta<delta>();
|
||||
const u8 new_count = state.AddDelta<track ? 1 : -1>();
|
||||
|
||||
// If the protection changed add pending (un)protect action
|
||||
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 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) {
|
||||
// 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;
|
||||
} else {
|
||||
release_pending();
|
||||
range_bytes = PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,10 +240,68 @@ struct PageManager::Impl {
|
|||
release_pending();
|
||||
}
|
||||
|
||||
// Flush deferred protects
|
||||
for (const auto& range : update_ranges) {
|
||||
Protect(range.addr, range.size, range.perms);
|
||||
template <bool track>
|
||||
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<track>(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<track ? 1 : -1>() : 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<PageState, NUM_ADDRESS_PAGES> cached_pages{};
|
||||
|
@ -273,12 +325,21 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) {
|
|||
impl->OnUnmap(address, size);
|
||||
}
|
||||
|
||||
template <s32 delta>
|
||||
template <bool track>
|
||||
void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const {
|
||||
impl->UpdatePageWatchers<delta>(addr, size);
|
||||
impl->UpdatePageWatchers<track>(addr, size);
|
||||
}
|
||||
|
||||
template void PageManager::UpdatePageWatchers<1>(VAddr addr, u64 size) const;
|
||||
template void PageManager::UpdatePageWatchers<-1>(VAddr addr, u64 size) const;
|
||||
template <bool track>
|
||||
void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const {
|
||||
impl->UpdatePageWatchersForRegion<track>(base_addr, mask);
|
||||
}
|
||||
|
||||
template void PageManager::UpdatePageWatchers<true>(VAddr addr, u64 size) const;
|
||||
template void PageManager::UpdatePageWatchers<false>(VAddr addr, u64 size) const;
|
||||
template void PageManager::UpdatePageWatchersForRegion<true>(VAddr base_addr,
|
||||
RegionBits& mask) const;
|
||||
template void PageManager::UpdatePageWatchersForRegion<false>(VAddr base_addr,
|
||||
RegionBits& mask) const;
|
||||
|
||||
} // namespace VideoCore
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <memory>
|
||||
#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 <s32 delta>
|
||||
template <bool track>
|
||||
void UpdatePageWatchers(VAddr addr, u64 size) const;
|
||||
|
||||
/// Updates watches in the pages touching the specified region
|
||||
/// using a mask.
|
||||
template <bool track>
|
||||
void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const;
|
||||
|
||||
/// Returns page aligned address.
|
||||
static constexpr VAddr GetPageAddr(VAddr addr) {
|
||||
return Common::AlignDown(addr, PAGE_SIZE);
|
||||
|
|
|
@ -281,6 +281,8 @@ bool Instance::CreateDevice() {
|
|||
if (shader_atomic_float2) {
|
||||
shader_atomic_float2_features =
|
||||
feature_chain.get<vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT>();
|
||||
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,
|
||||
},
|
||||
|
|
|
@ -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;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue