Merge branch 'main' into user_management

This commit is contained in:
georgemoralis 2025-06-21 10:07:12 +03:00 committed by GitHub
commit cc8ce30e32
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
75 changed files with 2099 additions and 1531 deletions

View file

@ -224,7 +224,10 @@ jobs:
arch: clang_64
archives: qtbase qttools
modules: qtmultimedia
- name: Workaround Qt <=6.9.1 issue
run: sed -i '' '/target_link_libraries(WrapOpenGL::WrapOpenGL INTERFACE ${__opengl_agl_fw_path})/d' ${{env.QT_ROOT_DIR}}/lib/cmake/Qt6/FindWrapOpenGL.cmake
- name: Cache CMake Configuration
uses: actions/cache@v4
env:

View file

@ -653,6 +653,7 @@ set(COMMON src/common/logging/backend.cpp
src/common/arch.h
src/common/assert.cpp
src/common/assert.h
src/common/bit_array.h
src/common/bit_field.h
src/common/bounded_threadsafe_queue.h
src/common/concepts.h
@ -915,9 +916,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/buffer_cache/buffer.h
src/video_core/buffer_cache/buffer_cache.cpp
src/video_core/buffer_cache/buffer_cache.h
src/video_core/buffer_cache/memory_tracker_base.h
src/video_core/buffer_cache/memory_tracker.h
src/video_core/buffer_cache/range_set.h
src/video_core/buffer_cache/word_manager.h
src/video_core/buffer_cache/region_definitions.h
src/video_core/buffer_cache/region_manager.h
src/video_core/renderer_vulkan/liverpool_to_vk.cpp
src/video_core/renderer_vulkan/liverpool_to_vk.h
src/video_core/renderer_vulkan/vk_common.cpp

411
src/common/bit_array.h Normal file
View 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 &range;
}
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

View file

@ -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;
@ -86,10 +85,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;
@ -214,10 +209,6 @@ std::string getChooseHomeTab() {
return chooseHomeTab;
}
std::string getBackButtonBehavior() {
return backButtonBehavior;
}
bool getUseSpecialPad() {
return useSpecialPad;
}
@ -433,10 +424,6 @@ void setChooseHomeTab(const std::string& type) {
chooseHomeTab = type;
}
void setBackButtonBehavior(const std::string& type) {
backButtonBehavior = type;
}
void setUseSpecialPad(bool use) {
useSpecialPad = use;
}
@ -489,24 +476,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) {
@ -548,22 +517,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;
}
@ -625,7 +578,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);
@ -673,7 +625,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", {});
@ -698,9 +649,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")) {
@ -713,19 +661,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) {
@ -797,7 +732,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;
@ -860,7 +794,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
@ -869,42 +802,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() {
@ -925,7 +822,6 @@ void setDefaultValues() {
cursorState = HideCursorState::Idle;
cursorHideTimeout = 5;
trophyNotificationDuration = 6.0;
backButtonBehavior = "left";
useSpecialPad = false;
specialPadClass = 1;
isDebugDump = false;
@ -942,7 +838,6 @@ void setDefaultValues() {
vkHostMarkers = false;
vkGuestMarkers = false;
rdocEnable = false;
emulator_language = "en_US";
m_language = 1;
gpuId = -1;
compatibilityData = false;
@ -972,7 +867,7 @@ l3 = x
r3 = m
options = enter
touchpad = space
touchpad_center = space
pad_up = up
pad_down = down
@ -1004,7 +899,7 @@ r2 = r2
r3 = r3
options = options
touchpad = back
touchpad_center = back
pad_up = pad_up
pad_down = pad_down

View file

@ -18,78 +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 getDefaultUserId();
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);
@ -97,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

View file

@ -201,6 +201,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);

View file

@ -450,21 +450,18 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
// Only do this on handle 1 for now
if (engine && handle == 1) {
const auto gyro_poll_rate = engine->GetAccelPollRate();
if (gyro_poll_rate != 0.0f) {
auto now = std::chrono::steady_clock::now();
float deltaTime = std::chrono::duration_cast<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);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
}
auto now = std::chrono::steady_clock::now();
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);
pData->orientation = outputOrientation;
controller->SetLastOrientation(outputOrientation);
}
pData->touchData.touchNum =
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);

View file

@ -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__)

View file

@ -5,6 +5,7 @@
#include <map>
#include <mutex>
#include <string>
#include <string_view>
#include "common/enum.h"
#include "common/singleton.h"

View file

@ -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)) {

View file

@ -66,22 +66,25 @@ auto output_array = std::array{
ControllerOutput(LEFTJOYSTICK_HALFMODE),
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
ControllerOutput(KEY_TOGGLE),
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
// Button mappings
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
// Axis mappings
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
@ -130,6 +133,12 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
return OPBDO::Options;
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_BACK:
return OPBDO::TouchPad;
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
@ -176,7 +185,7 @@ InputBinding GetBindingFromString(std::string& line) {
if (string_to_keyboard_key_map.find(t) != string_to_keyboard_key_map.end()) {
input = InputID(InputType::KeyboardMouse, string_to_keyboard_key_map.at(t));
} else if (string_to_axis_map.find(t) != string_to_axis_map.end()) {
input = InputID(InputType::Axis, (u32)string_to_axis_map.at(t).axis);
input = InputID(InputType::Axis, string_to_axis_map.at(t).axis);
} else if (string_to_cbutton_map.find(t) != string_to_cbutton_map.end()) {
input = InputID(InputType::Controller, string_to_cbutton_map.at(t));
} else {
@ -227,19 +236,15 @@ void ParseInputConfig(const std::string game_id = "") {
line.erase(std::remove_if(line.begin(), line.end(),
[](unsigned char c) { return std::isspace(c); }),
line.end());
if (line.empty()) {
continue;
}
// Truncate lines starting at #
std::size_t comment_pos = line.find('#');
if (comment_pos != std::string::npos) {
line = line.substr(0, comment_pos);
}
// Remove trailing semicolon
if (!line.empty() && line[line.length() - 1] == ';') {
line = line.substr(0, line.length() - 1);
}
if (line.empty()) {
continue;
}
@ -254,8 +259,13 @@ void ParseInputConfig(const std::string game_id = "") {
std::string output_string = line.substr(0, equal_pos);
std::string input_string = line.substr(equal_pos + 1);
std::size_t comma_pos = input_string.find(',');
// Remove trailing semicolon from input_string
if (!input_string.empty() && input_string[input_string.length() - 1] == ';' &&
input_string != ";") {
line = line.substr(0, line.length() - 1);
}
std::size_t comma_pos = input_string.find(',');
auto parseInt = [](const std::string& s) -> std::optional<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) {

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
};

View file

@ -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"};

View file

@ -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.";
}

View file

@ -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();

View file

@ -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);
});
}

View file

@ -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) {

View file

@ -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(); });

View file

@ -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());

View file

@ -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";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->CircleButton->text().toStdString();
output_string = "circle";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->TriangleButton->text().toStdString();
output_string = "triangle";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->SquareButton->text().toStdString();
output_string = "square";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->DpadUpButton->text().toStdString();
output_string = "pad_up";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->DpadDownButton->text().toStdString();
output_string = "pad_down";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->DpadLeftButton->text().toStdString();
output_string = "pad_left";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->DpadRightButton->text().toStdString();
output_string = "pad_right";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->L1Button->text().toStdString();
output_string = "l1";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->R1Button->text().toStdString();
output_string = "r1";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->L2Button->text().toStdString();
output_string = "l2";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->R2Button->text().toStdString();
output_string = "r2";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->L3Button->text().toStdString();
output_string = "l3";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->R3Button->text().toStdString();
output_string = "r3";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->OptionsButton->text().toStdString();
output_string = "options";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->TouchpadButton->text().toStdString();
output_string = "touchpad";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->LStickUpButton->text().toStdString();
output_string = "axis_left_y_minus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->LStickDownButton->text().toStdString();
output_string = "axis_left_y_plus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->LStickLeftButton->text().toStdString();
output_string = "axis_left_x_minus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->LStickRightButton->text().toStdString();
output_string = "axis_left_x_plus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->RStickUpButton->text().toStdString();
output_string = "axis_right_y_minus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->RStickDownButton->text().toStdString();
output_string = "axis_right_y_plus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->RStickLeftButton->text().toStdString();
output_string = "axis_right_x_minus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
input_string = ui->RStickRightButton->text().toStdString();
output_string = "axis_right_x_plus";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
input_string = ui->MouseJoystickBox->currentText().toStdString();
output_string = "mouse_to_joystick";
if (input_string != "unmapped")
// Lambda to reduce repetitive code for mapping buttons to config lines
auto add_mapping = [&](const QString& buttonText, const std::string& output_name) {
input_string = buttonText.toStdString();
output_string = output_name;
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped") {
inputs.push_back(input_string);
}
};
input_string = ui->LHalfButton->text().toStdString();
output_string = "leftjoystick_halfmode";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
add_mapping(ui->CrossButton->text(), "cross");
add_mapping(ui->CircleButton->text(), "circle");
add_mapping(ui->TriangleButton->text(), "triangle");
add_mapping(ui->SquareButton->text(), "square");
input_string = ui->RHalfButton->text().toStdString();
output_string = "rightjoystick_halfmode";
lines.push_back(output_string + " = " + input_string);
if (input_string != "unmapped")
inputs.push_back(input_string);
lines.push_back("");
add_mapping(ui->DpadUpButton->text(), "pad_up");
add_mapping(ui->DpadDownButton->text(), "pad_down");
add_mapping(ui->DpadLeftButton->text(), "pad_left");
add_mapping(ui->DpadRightButton->text(), "pad_right");
lines.push_back("");
add_mapping(ui->L1Button->text(), "l1");
add_mapping(ui->R1Button->text(), "r1");
add_mapping(ui->L2Button->text(), "l2");
add_mapping(ui->R2Button->text(), "r2");
add_mapping(ui->L3Button->text(), "l3");
add_mapping(ui->R3Button->text(), "r3");
lines.push_back("");
add_mapping(ui->TouchpadLeftButton->text(), "touchpad_left");
add_mapping(ui->TouchpadCenterButton->text(), "touchpad_center");
add_mapping(ui->TouchpadRightButton->text(), "touchpad_right");
add_mapping(ui->OptionsButton->text(), "options");
lines.push_back("");
add_mapping(ui->LStickUpButton->text(), "axis_left_y_minus");
add_mapping(ui->LStickDownButton->text(), "axis_left_y_plus");
add_mapping(ui->LStickLeftButton->text(), "axis_left_x_minus");
add_mapping(ui->LStickRightButton->text(), "axis_left_x_plus");
lines.push_back("");
add_mapping(ui->RStickUpButton->text(), "axis_right_y_minus");
add_mapping(ui->RStickDownButton->text(), "axis_right_y_plus");
add_mapping(ui->RStickLeftButton->text(), "axis_right_x_minus");
add_mapping(ui->RStickRightButton->text(), "axis_right_x_plus");
lines.push_back("");
add_mapping(ui->MouseJoystickBox->currentText(), "mouse_to_joystick");
add_mapping(ui->LHalfButton->text(), "leftjoystick_halfmode");
add_mapping(ui->RHalfButton->text(), "rightjoystick_halfmode");
std::string DOString = std::format("{:.2f}", (ui->DeadzoneOffsetSlider->value() / 100.f));
std::string SMString = std::format("{:.1f}", (ui->SpeedMultiplierSlider->value() / 10.f));
@ -374,6 +280,7 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) {
// Prevent duplicate inputs for KBM as this breaks the engine
bool duplicateFound = false;
QSet<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"));
@ -1046,4 +980,4 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
return QDialog::eventFilter(obj, event);
}
KBMSettings::~KBMSettings() {}
KBMSettings::~KBMSettings() {}

View file

@ -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);

View file

@ -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>
<string>Touchpad Right</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>
<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>

View file

@ -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.)";
}

View file

@ -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();
};

View file

@ -40,8 +40,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() {
@ -298,7 +296,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();
@ -498,7 +496,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();
});
@ -777,14 +775,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;
@ -792,7 +790,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;
@ -800,7 +798,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;
@ -808,7 +806,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;
@ -816,7 +814,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;
@ -824,7 +822,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;
@ -832,7 +830,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;
@ -840,7 +838,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;
@ -987,7 +985,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) {
@ -1129,33 +1127,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);
}
@ -1172,7 +1169,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);
@ -1197,8 +1194,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();
}

View file

@ -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:

View file

@ -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;
}

View file

@ -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 */

View file

@ -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");
}

View file

@ -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);

View file

@ -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">

View file

@ -1184,7 +1184,7 @@
<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:
<translation>No es pot assignar una entrada més d'una vegada. S'han assignat de manera duplicada pels següents botons:
%1</translation>
</message>

View file

@ -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>
@ -1186,6 +1182,18 @@
%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>
@ -1544,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>
@ -1784,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&apos;s back button to emulate tapping the specified position on the PS4 touchpad.</source>
<translation>Back Button Behavior:\nSets the controller&apos;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 &quot;Update Compatibility On Startup&quot; to get up-to-date information.</source>
<translation>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable &quot;Update Compatibility On Startup&quot; to get up-to-date information.</translation>
@ -1812,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 &quot;Auto Select&quot; 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 &quot;Auto Select&quot; to automatically determine it.</translation>

View file

@ -1184,7 +1184,7 @@
<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:
<translation>Non è possibile associare più di una volta qualsiasi input univoco. Sono presenti input duplicati mappati ai seguenti pulsanti:
%1</translation>
</message>

View file

@ -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>
@ -1184,7 +1184,7 @@
<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:
<translation>Kan ikke tildele samme inndata mer enn én gang. Dupliserte inndata tildeles følgende taster:
%1</translation>
</message>

View file

@ -1184,7 +1184,7 @@
<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:
<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>

View file

@ -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>
@ -1184,7 +1184,7 @@
<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:
<translation>Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки:
%1</translation>
</message>

View file

@ -1184,7 +1184,7 @@
<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:
<translation>Nuk mund caktohet e njëjta hyrje unike shumë se një herë. Hyrjet e dublikuara janë caktuar butonët e mëposhtëm:
%1</translation>
</message>

View file

@ -1184,7 +1184,7 @@
<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:
<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>

View file

@ -1184,7 +1184,7 @@
<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:
<translation>
%1</translation>
</message>

View file

@ -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));

View file

@ -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;
};

View file

@ -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

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);

View file

@ -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};
});

View file

@ -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 Id struct_type{TypeStruct(array_type)};
MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u);
const auto mem_type = [&] {
if (num_types > 1) {
const Id struct_type{TypeStruct(array_type)};
Decorate(struct_type, spv::Decoration::Block);
MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u);
return struct_type;
} else {
return array_type;
}
}();
const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type);
const Id pointer = TypePointer(spv::StorageClass::Workgroup, mem_type);
const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type);
const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup);
Name(variable, name);
interfaces.push_back(variable);
if (num_types > 1) {
Decorate(struct_type, spv::Decoration::Block);
Decorate(array_type, spv::Decoration::ArrayStride, element_size);
Decorate(variable, spv::Decoration::Aliased);
}

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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:

View file

@ -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, )

View file

@ -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{ssharp, image_binding, disable_aniso});
return binding;
}
// Normal sampler resource.
const auto ssharp_handle = handle.InstRecursive();
const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle);
const auto ssharp = TrackSharp(ssharp_ud, info);
const auto binding = descriptors.Add(SamplerResource{
.sharp_idx = ssharp,
.associated_image = image_binding,
.disable_aniso = disable_aniso,
});
return {binding, info.ReadUdSharp<AmdGpu::Sampler>(ssharp)};
}();
// Patch image and sampler handle.
inst.SetArg(0, ir.Imm32(image_binding | sampler_binding << 16));

View file

@ -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;

View file

@ -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{};

View file

@ -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"

View file

@ -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 {

View 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

View 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

View file

@ -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

View file

@ -48,19 +48,15 @@ struct PageManager::Impl {
u8 AddDelta() {
if constexpr (delta == 1) {
return ++num_watchers;
} else {
} else if constexpr (delta == -1) {
ASSERT_MSG(num_watchers > 0, "Not enough watchers");
return --num_watchers;
} else {
return num_watchers;
}
}
};
struct UpdateProtectRange {
VAddr addr;
u64 size;
Core::MemoryPermission perms;
};
static constexpr size_t ADDRESS_BITS = 40;
static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS);
inline static Vulkan::Rasterizer* rasterizer;
@ -190,66 +186,122 @@ struct PageManager::Impl {
}
#endif
template <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();
u64 range_begin = 0;
u64 range_bytes = 0;
size_t page = addr >> PAGE_BITS;
auto perms = cached_pages[page].Perm();
u64 range_begin = 0;
u64 range_bytes = 0;
const auto release_pending = [&] {
if (range_bytes > 0) {
RENDERER_TRACE;
// Add pending (un)protect action
update_ranges.push_back({range_begin << PAGE_BITS, range_bytes, perms});
range_bytes = 0;
}
};
const auto release_pending = [&] {
if (range_bytes > 0) {
RENDERER_TRACE;
// Perform pending (un)protect action
Protect(range_begin << PAGE_BITS, range_bytes, perms);
range_bytes = 0;
}
};
// Iterate requested pages
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
const u64 aligned_addr = page << PAGE_BITS;
const u64 aligned_end = page_end << PAGE_BITS;
ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr),
"Attempted to track non-GPU memory at address {:#x}, size {:#x}.",
aligned_addr, aligned_end - aligned_addr);
std::scoped_lock lk(lock);
for (; page != page_end; ++page) {
PageState& state = cached_pages[page];
// Iterate requested pages
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
const u64 aligned_addr = page << PAGE_BITS;
const u64 aligned_end = page_end << PAGE_BITS;
ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr),
"Attempted to track non-GPU memory at address {:#x}, size {:#x}.", aligned_addr,
aligned_end - aligned_addr);
// Apply the change to the page state
const u8 new_count = state.AddDelta<delta>();
for (; page != page_end; ++page) {
PageState& state = cached_pages[page];
// Apply the change to the page state
const u8 new_count = state.AddDelta<track ? 1 : -1>();
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] {
// If the protection changed add pending (un)protect action
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] {
release_pending();
perms = new_perms;
}
// If the page must be (un)protected, add it to the pending range
if ((new_count == 0 && delta < 0) || (new_count == 1 && delta > 0)) {
if (range_bytes == 0) {
range_begin = page;
}
range_bytes += PAGE_SIZE;
} else {
release_pending();
}
release_pending();
perms = new_perms;
} else if (range_bytes != 0) {
// If the protection did not change, extend the current range
range_bytes += PAGE_SIZE;
}
// Add pending (un)protect action
release_pending();
// Only start a new range if the page must be (un)protected
if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) {
range_begin = page;
range_bytes = PAGE_SIZE;
}
}
// Flush deferred protects
for (const auto& range : update_ranges) {
Protect(range.addr, range.size, range.perms);
// Add pending (un)protect action
release_pending();
}
template <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

View file

@ -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);

View file

@ -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);
}
@ -433,6 +435,8 @@ bool Instance::CreateDevice() {
.legacyVertexAttributes = true,
},
vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{
.shaderBufferFloat32AtomicMinMax =
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax,
.shaderImageFloat32AtomicMinMax =
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax,
},

View file

@ -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;

View file

@ -216,6 +216,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
.supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(),
// TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed.
.supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(),
.supports_buffer_fp32_atomic_min_max =
instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(),
.supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(),
.supports_workgroup_explicit_memory_layout =
instance_.IsWorkgroupMemoryExplicitLayoutSupported(),
@ -346,8 +348,15 @@ bool PipelineCache::RefreshGraphicsKey() {
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 ||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_8);
key.color_formats[remapped_cb] =
const auto format =
LiverpoolToVK::SurfaceFormat(col_buf.GetDataFmt(), col_buf.GetNumberFmt());
key.color_formats[remapped_cb] = format;
if (!instance.IsFormatSupported(format, vk::FormatFeatureFlagBits2::eColorAttachment)) {
LOG_WARNING(Render_Vulkan,
"color buffer format {} does not support COLOR_ATTACHMENT_BIT",
vk::to_string(format));
}
key.color_buffers[remapped_cb] = Shader::PsColorBuffer{
.num_format = col_buf.GetNumberFmt(),
.num_conversion = col_buf.GetNumberConversion(),

View file

@ -130,11 +130,24 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
constexpr auto tiling = vk::ImageTiling::eOptimal;
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
const auto properties = instance->GetPhysicalDevice().getImageFormatProperties(
supported_format, info.type, tiling, usage_flags, flags);
const auto supported_samples = properties.result == vk::Result::eSuccess
? properties.value.sampleCounts
: vk::SampleCountFlagBits::e1;
const vk::PhysicalDeviceImageFormatInfo2 format_info{
.format = supported_format,
.type = info.type,
.tiling = tiling,
.usage = usage_flags,
.flags = flags,
};
const auto image_format_properties =
instance->GetPhysicalDevice().getImageFormatProperties2(format_info);
if (image_format_properties.result == vk::Result::eErrorFormatNotSupported) {
LOG_ERROR(Render_Vulkan, "image format {} type {} is not supported (flags {}, usage {})",
vk::to_string(supported_format), vk::to_string(info.type),
vk::to_string(format_info.flags), vk::to_string(format_info.usage));
}
const auto supported_samples =
image_format_properties.result == vk::Result::eSuccess
? image_format_properties.value.imageFormatProperties.sampleCounts
: vk::SampleCountFlagBits::e1;
const vk::ImageCreateInfo image_ci = {
.flags = flags,

View file

@ -29,6 +29,24 @@ vk::ImageViewType ConvertImageViewType(AmdGpu::ImageType type) {
}
}
bool IsViewTypeCompatible(vk::ImageViewType view_type, vk::ImageType image_type) {
switch (view_type) {
case vk::ImageViewType::e1D:
case vk::ImageViewType::e1DArray:
return image_type == vk::ImageType::e1D;
case vk::ImageViewType::e2D:
case vk::ImageViewType::e2DArray:
return image_type == vk::ImageType::e2D || image_type == vk::ImageType::e3D;
case vk::ImageViewType::eCube:
case vk::ImageViewType::eCubeArray:
return image_type == vk::ImageType::e2D;
case vk::ImageViewType::e3D:
return image_type == vk::ImageType::e3D;
default:
UNREACHABLE();
}
}
ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept
: is_storage{desc.is_written} {
const auto dfmt = image.GetDataFmt();
@ -106,6 +124,11 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info
.layerCount = info.range.extent.layers,
},
};
if (!IsViewTypeCompatible(image_view_ci.viewType, image.info.type)) {
LOG_ERROR(Render_Vulkan, "image view type {} is incompatible with image type {}",
vk::to_string(image_view_ci.viewType), vk::to_string(image.info.type));
}
auto [view_result, view] = instance.GetDevice().createImageViewUnique(image_view_ci);
ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}",
vk::to_string(view_result));

View file

@ -761,7 +761,7 @@ void TextureCache::UntrackImage(ImageId image_id) {
image.track_addr = 0;
image.track_addr_end = 0;
if (size != 0) {
tracker.UpdatePageWatchers<-1>(addr, size);
tracker.UpdatePageWatchers<false>(addr, size);
}
}
@ -780,7 +780,7 @@ void TextureCache::UntrackImageHead(ImageId image_id) {
// Cehck its hash later.
MarkAsMaybeDirty(image_id, image);
}
tracker.UpdatePageWatchers<-1>(image_begin, size);
tracker.UpdatePageWatchers<false>(image_begin, size);
}
void TextureCache::UntrackImageTail(ImageId image_id) {
@ -799,7 +799,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) {
// Cehck its hash later.
MarkAsMaybeDirty(image_id, image);
}
tracker.UpdatePageWatchers<-1>(addr, size);
tracker.UpdatePageWatchers<false>(addr, size);
}
void TextureCache::DeleteImage(ImageId image_id) {