mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-23 18:45:36 +00:00
Merge branch 'main' into input-fix-real
This commit is contained in:
commit
b2be86efc0
@ -653,6 +653,7 @@ set(COMMON src/common/logging/backend.cpp
|
|||||||
src/common/arch.h
|
src/common/arch.h
|
||||||
src/common/assert.cpp
|
src/common/assert.cpp
|
||||||
src/common/assert.h
|
src/common/assert.h
|
||||||
|
src/common/bit_array.h
|
||||||
src/common/bit_field.h
|
src/common/bit_field.h
|
||||||
src/common/bounded_threadsafe_queue.h
|
src/common/bounded_threadsafe_queue.h
|
||||||
src/common/concepts.h
|
src/common/concepts.h
|
||||||
@ -913,9 +914,10 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
|
|||||||
src/video_core/buffer_cache/buffer.h
|
src/video_core/buffer_cache/buffer.h
|
||||||
src/video_core/buffer_cache/buffer_cache.cpp
|
src/video_core/buffer_cache/buffer_cache.cpp
|
||||||
src/video_core/buffer_cache/buffer_cache.h
|
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/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.cpp
|
||||||
src/video_core/renderer_vulkan/liverpool_to_vk.h
|
src/video_core/renderer_vulkan/liverpool_to_vk.h
|
||||||
src/video_core/renderer_vulkan/vk_common.cpp
|
src/video_core/renderer_vulkan/vk_common.cpp
|
||||||
|
411
src/common/bit_array.h
Normal file
411
src/common/bit_array.h
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
#ifdef __AVX2__
|
||||||
|
#define BIT_ARRAY_USE_AVX
|
||||||
|
#include <immintrin.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
template <size_t N>
|
||||||
|
class BitArray {
|
||||||
|
static_assert(N % 64 == 0, "BitArray size must be a multiple of 64 bits.");
|
||||||
|
|
||||||
|
static constexpr size_t BITS_PER_WORD = 64;
|
||||||
|
static constexpr size_t WORD_COUNT = N / BITS_PER_WORD;
|
||||||
|
static constexpr size_t WORDS_PER_AVX = 4;
|
||||||
|
static constexpr size_t AVX_WORD_COUNT = WORD_COUNT / WORDS_PER_AVX;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using Range = std::pair<size_t, size_t>;
|
||||||
|
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
explicit Iterator(const BitArray& bit_array_, u64 start) : bit_array(bit_array_) {
|
||||||
|
range = bit_array.FirstRangeFrom(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator& operator++() {
|
||||||
|
range = bit_array.FirstRangeFrom(range.second);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Iterator& other) const {
|
||||||
|
return range == other.range;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Iterator& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
const Range& operator*() const {
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Range* operator->() const {
|
||||||
|
return ⦥
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const BitArray& bit_array;
|
||||||
|
Range range;
|
||||||
|
};
|
||||||
|
|
||||||
|
using const_iterator = Iterator;
|
||||||
|
using iterator_category = std::forward_iterator_tag;
|
||||||
|
using value_type = Range;
|
||||||
|
using difference_type = std::ptrdiff_t;
|
||||||
|
using pointer = const Range*;
|
||||||
|
using reference = const Range&;
|
||||||
|
|
||||||
|
BitArray() = default;
|
||||||
|
BitArray(const BitArray& other) = default;
|
||||||
|
BitArray& operator=(const BitArray& other) = default;
|
||||||
|
BitArray(BitArray&& other) noexcept = default;
|
||||||
|
BitArray& operator=(BitArray&& other) noexcept = default;
|
||||||
|
~BitArray() = default;
|
||||||
|
|
||||||
|
BitArray(const BitArray& other, size_t start, size_t end) {
|
||||||
|
if (start >= end || end > N) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const size_t first_word = start / BITS_PER_WORD;
|
||||||
|
const size_t last_word = (end - 1) / BITS_PER_WORD;
|
||||||
|
const size_t start_bit = start % BITS_PER_WORD;
|
||||||
|
const size_t end_bit = (end - 1) % BITS_PER_WORD;
|
||||||
|
const u64 start_mask = ~((1ULL << start_bit) - 1);
|
||||||
|
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
|
||||||
|
if (first_word == last_word) {
|
||||||
|
data[first_word] = other.data[first_word] & (start_mask & end_mask);
|
||||||
|
} else {
|
||||||
|
data[first_word] = other.data[first_word] & start_mask;
|
||||||
|
size_t i = first_word + 1;
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
|
||||||
|
const __m256i current =
|
||||||
|
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&other.data[i]));
|
||||||
|
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), current);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; i < last_word; ++i) {
|
||||||
|
data[i] = other.data[i];
|
||||||
|
}
|
||||||
|
data[last_word] = other.data[last_word] & end_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BitArray(const BitArray& other, const Range& range)
|
||||||
|
: BitArray(other, range.first, range.second) {}
|
||||||
|
|
||||||
|
const_iterator begin() const {
|
||||||
|
return Iterator(*this, 0);
|
||||||
|
}
|
||||||
|
const_iterator end() const {
|
||||||
|
return Iterator(*this, N);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void Set(size_t idx) {
|
||||||
|
data[idx / BITS_PER_WORD] |= (1ULL << (idx % BITS_PER_WORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void Unset(size_t idx) {
|
||||||
|
data[idx / BITS_PER_WORD] &= ~(1ULL << (idx % BITS_PER_WORD));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool Get(size_t idx) const {
|
||||||
|
return (data[idx / BITS_PER_WORD] & (1ULL << (idx % BITS_PER_WORD))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void SetRange(size_t start, size_t end) {
|
||||||
|
if (start >= end || end > N) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const size_t first_word = start / BITS_PER_WORD;
|
||||||
|
const size_t last_word = (end - 1) / BITS_PER_WORD;
|
||||||
|
const size_t start_bit = start % BITS_PER_WORD;
|
||||||
|
const size_t end_bit = (end - 1) % BITS_PER_WORD;
|
||||||
|
const u64 start_mask = ~((1ULL << start_bit) - 1);
|
||||||
|
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? ~0ULL : (1ULL << (end_bit + 1)) - 1;
|
||||||
|
if (first_word == last_word) {
|
||||||
|
data[first_word] |= start_mask & end_mask;
|
||||||
|
} else {
|
||||||
|
data[first_word] |= start_mask;
|
||||||
|
size_t i = first_word + 1;
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
const __m256i value = _mm256_set1_epi64x(-1);
|
||||||
|
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
|
||||||
|
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; i < last_word; ++i) {
|
||||||
|
data[i] = ~0ULL;
|
||||||
|
}
|
||||||
|
data[last_word] |= end_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void UnsetRange(size_t start, size_t end) {
|
||||||
|
if (start >= end || end > N) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t first_word = start / BITS_PER_WORD;
|
||||||
|
const size_t last_word = (end - 1) / BITS_PER_WORD;
|
||||||
|
const size_t start_bit = start % BITS_PER_WORD;
|
||||||
|
const size_t end_bit = (end - 1) % BITS_PER_WORD;
|
||||||
|
const u64 start_mask = (1ULL << start_bit) - 1;
|
||||||
|
const u64 end_mask = end_bit == BITS_PER_WORD - 1 ? 0ULL : ~((1ULL << (end_bit + 1)) - 1);
|
||||||
|
if (first_word == last_word) {
|
||||||
|
data[first_word] &= start_mask | end_mask;
|
||||||
|
} else {
|
||||||
|
data[first_word] &= start_mask;
|
||||||
|
size_t i = first_word + 1;
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
const __m256i value = _mm256_setzero_si256();
|
||||||
|
for (; i + WORDS_PER_AVX <= last_word; i += WORDS_PER_AVX) {
|
||||||
|
_mm256_storeu_si256(reinterpret_cast<__m256i*>(&data[i]), value);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; i < last_word; ++i) {
|
||||||
|
data[i] = 0ULL;
|
||||||
|
}
|
||||||
|
data[last_word] &= end_mask;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void SetRange(const Range& range) {
|
||||||
|
SetRange(range.first, range.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void UnsetRange(const Range& range) {
|
||||||
|
UnsetRange(range.first, range.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void Clear() {
|
||||||
|
data.fill(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr void Fill() {
|
||||||
|
data.fill(~0ULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool None() const {
|
||||||
|
u64 result = 0;
|
||||||
|
for (const auto& word : data) {
|
||||||
|
result |= word;
|
||||||
|
}
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool Any() const {
|
||||||
|
return !None();
|
||||||
|
}
|
||||||
|
|
||||||
|
Range FirstRangeFrom(size_t start) const {
|
||||||
|
if (start >= N) {
|
||||||
|
return {N, N};
|
||||||
|
}
|
||||||
|
const auto find_end_bit = [&](size_t word) {
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
const __m256i all_one = _mm256_set1_epi64x(-1);
|
||||||
|
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
|
||||||
|
const __m256i current =
|
||||||
|
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
|
||||||
|
const __m256i cmp = _mm256_cmpeq_epi64(current, all_one);
|
||||||
|
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; word < WORD_COUNT; ++word) {
|
||||||
|
if (data[word] != ~0ULL) {
|
||||||
|
return (word * BITS_PER_WORD) + std::countr_one(data[word]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return N;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto word_bits = [&](size_t index, u64 word) {
|
||||||
|
const int empty_bits = std::countr_zero(word);
|
||||||
|
const int ones_count = std::countr_one(word >> empty_bits);
|
||||||
|
const size_t start_bit = index * BITS_PER_WORD + empty_bits;
|
||||||
|
if (ones_count + empty_bits < BITS_PER_WORD) {
|
||||||
|
return Range{start_bit, start_bit + ones_count};
|
||||||
|
}
|
||||||
|
return Range{start_bit, find_end_bit(index + 1)};
|
||||||
|
};
|
||||||
|
|
||||||
|
const size_t start_word = start / BITS_PER_WORD;
|
||||||
|
const size_t start_bit = start % BITS_PER_WORD;
|
||||||
|
const u64 masked_first = data[start_word] & (~((1ULL << start_bit) - 1));
|
||||||
|
if (masked_first) {
|
||||||
|
return word_bits(start_word, masked_first);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t word = start_word + 1;
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
for (; word + WORDS_PER_AVX <= WORD_COUNT; word += WORDS_PER_AVX) {
|
||||||
|
const __m256i current =
|
||||||
|
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word]));
|
||||||
|
if (!_mm256_testz_si256(current, current)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; word < WORD_COUNT; ++word) {
|
||||||
|
if (data[word] != 0) {
|
||||||
|
return word_bits(word, data[word]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {N, N};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr Range FirstRange() const {
|
||||||
|
return FirstRangeFrom(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Range LastRangeFrom(size_t end) const {
|
||||||
|
if (end == 0) {
|
||||||
|
return {0, 0};
|
||||||
|
}
|
||||||
|
if (end > N) {
|
||||||
|
end = N;
|
||||||
|
}
|
||||||
|
const auto find_start_bit = [&](size_t word) {
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
const __m256i all_zero = _mm256_setzero_si256();
|
||||||
|
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
|
||||||
|
const __m256i current = _mm256_loadu_si256(
|
||||||
|
reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
|
||||||
|
const __m256i cmp = _mm256_cmpeq_epi64(current, all_zero);
|
||||||
|
if (_mm256_movemask_epi8(cmp) != 0xFFFFFFFF) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; word > 0; --word) {
|
||||||
|
if (data[word - 1] != ~0ULL) {
|
||||||
|
return word * BITS_PER_WORD - std::countl_one(data[word - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return size_t(0);
|
||||||
|
};
|
||||||
|
const auto word_bits = [&](size_t index, u64 word) {
|
||||||
|
const int empty_bits = std::countl_zero(word);
|
||||||
|
const int ones_count = std::countl_one(word << empty_bits);
|
||||||
|
const size_t end_bit = index * BITS_PER_WORD - empty_bits;
|
||||||
|
if (empty_bits + ones_count < BITS_PER_WORD) {
|
||||||
|
return Range{end_bit - ones_count, end_bit};
|
||||||
|
}
|
||||||
|
return Range{find_start_bit(index - 1), end_bit};
|
||||||
|
};
|
||||||
|
const size_t end_word = ((end - 1) / BITS_PER_WORD) + 1;
|
||||||
|
const size_t end_bit = (end - 1) % BITS_PER_WORD;
|
||||||
|
u64 masked_last = data[end_word - 1];
|
||||||
|
if (end_bit < BITS_PER_WORD - 1) {
|
||||||
|
masked_last &= (1ULL << (end_bit + 1)) - 1;
|
||||||
|
}
|
||||||
|
if (masked_last) {
|
||||||
|
return word_bits(end_word, masked_last);
|
||||||
|
}
|
||||||
|
size_t word = end_word - 1;
|
||||||
|
#ifdef BIT_ARRAY_USE_AVX
|
||||||
|
for (; word >= WORDS_PER_AVX; word -= WORDS_PER_AVX) {
|
||||||
|
const __m256i current =
|
||||||
|
_mm256_loadu_si256(reinterpret_cast<const __m256i*>(&data[word - WORDS_PER_AVX]));
|
||||||
|
if (!_mm256_testz_si256(current, current)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
for (; word > 0; --word) {
|
||||||
|
if (data[word - 1] != 0) {
|
||||||
|
return word_bits(word, data[word - 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {0, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr Range LastRange() const {
|
||||||
|
return LastRangeFrom(N);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr size_t Size() const {
|
||||||
|
return N;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray& operator|=(const BitArray& other) {
|
||||||
|
for (size_t i = 0; i < WORD_COUNT; ++i) {
|
||||||
|
data[i] |= other.data[i];
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray& operator&=(const BitArray& other) {
|
||||||
|
for (size_t i = 0; i < WORD_COUNT; ++i) {
|
||||||
|
data[i] &= other.data[i];
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray& operator^=(const BitArray& other) {
|
||||||
|
for (size_t i = 0; i < WORD_COUNT; ++i) {
|
||||||
|
data[i] ^= other.data[i];
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray& operator~() {
|
||||||
|
for (size_t i = 0; i < WORD_COUNT; ++i) {
|
||||||
|
data[i] = ~data[i];
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray operator|(const BitArray& other) const {
|
||||||
|
BitArray result = *this;
|
||||||
|
result |= other;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray operator&(const BitArray& other) const {
|
||||||
|
BitArray result = *this;
|
||||||
|
result &= other;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray operator^(const BitArray& other) const {
|
||||||
|
BitArray result = *this;
|
||||||
|
result ^= other;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr BitArray operator~() const {
|
||||||
|
BitArray result = *this;
|
||||||
|
result = ~result;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool operator==(const BitArray& other) const {
|
||||||
|
u64 result = 0;
|
||||||
|
for (size_t i = 0; i < WORD_COUNT; ++i) {
|
||||||
|
result |= data[i] ^ other.data[i];
|
||||||
|
}
|
||||||
|
return result == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline constexpr bool operator!=(const BitArray& other) const {
|
||||||
|
return !(*this == other);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<u64, WORD_COUNT> data{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -42,7 +42,6 @@ static std::string logFilter;
|
|||||||
static std::string logType = "sync";
|
static std::string logType = "sync";
|
||||||
static std::string userName = "shadPS4";
|
static std::string userName = "shadPS4";
|
||||||
static std::string chooseHomeTab;
|
static std::string chooseHomeTab;
|
||||||
static std::string backButtonBehavior = "left";
|
|
||||||
static bool useSpecialPad = false;
|
static bool useSpecialPad = false;
|
||||||
static int specialPadClass = 1;
|
static int specialPadClass = 1;
|
||||||
static bool isMotionControlsEnabled = true;
|
static bool isMotionControlsEnabled = true;
|
||||||
@ -81,10 +80,6 @@ static std::vector<GameInstallDir> settings_install_dirs = {};
|
|||||||
std::vector<bool> install_dirs_enabled = {};
|
std::vector<bool> install_dirs_enabled = {};
|
||||||
std::filesystem::path settings_addon_install_dir = {};
|
std::filesystem::path settings_addon_install_dir = {};
|
||||||
std::filesystem::path save_data_path = {};
|
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 bool isFullscreen = false;
|
||||||
static std::string fullscreenMode = "Windowed";
|
static std::string fullscreenMode = "Windowed";
|
||||||
static bool isHDRAllowed = false;
|
static bool isHDRAllowed = false;
|
||||||
@ -209,10 +204,6 @@ std::string getChooseHomeTab() {
|
|||||||
return chooseHomeTab;
|
return chooseHomeTab;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string getBackButtonBehavior() {
|
|
||||||
return backButtonBehavior;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool getUseSpecialPad() {
|
bool getUseSpecialPad() {
|
||||||
return useSpecialPad;
|
return useSpecialPad;
|
||||||
}
|
}
|
||||||
@ -428,10 +419,6 @@ void setChooseHomeTab(const std::string& type) {
|
|||||||
chooseHomeTab = type;
|
chooseHomeTab = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setBackButtonBehavior(const std::string& type) {
|
|
||||||
backButtonBehavior = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUseSpecialPad(bool use) {
|
void setUseSpecialPad(bool use) {
|
||||||
useSpecialPad = use;
|
useSpecialPad = use;
|
||||||
}
|
}
|
||||||
@ -484,24 +471,6 @@ void setAddonInstallDir(const std::filesystem::path& dir) {
|
|||||||
settings_addon_install_dir = 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) {
|
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config) {
|
||||||
settings_install_dirs.clear();
|
settings_install_dirs.clear();
|
||||||
for (const auto& dir : dirs_config) {
|
for (const auto& dir : dirs_config) {
|
||||||
@ -543,22 +512,6 @@ std::filesystem::path getAddonInstallDir() {
|
|||||||
return settings_addon_install_dir;
|
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() {
|
u32 GetLanguage() {
|
||||||
return m_language;
|
return m_language;
|
||||||
}
|
}
|
||||||
@ -620,7 +573,6 @@ void load(const std::filesystem::path& path) {
|
|||||||
|
|
||||||
cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle);
|
cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle);
|
||||||
cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", 5);
|
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);
|
useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", false);
|
||||||
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1);
|
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1);
|
||||||
isMotionControlsEnabled = toml::find_or<bool>(input, "isMotionControlsEnabled", true);
|
isMotionControlsEnabled = toml::find_or<bool>(input, "isMotionControlsEnabled", true);
|
||||||
@ -668,7 +620,6 @@ void load(const std::filesystem::path& path) {
|
|||||||
const toml::value& gui = data.at("GUI");
|
const toml::value& gui = data.at("GUI");
|
||||||
|
|
||||||
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true);
|
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true);
|
||||||
mw_themes = toml::find_or<int>(gui, "theme", 0);
|
|
||||||
|
|
||||||
const auto install_dir_array =
|
const auto install_dir_array =
|
||||||
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
|
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
|
||||||
@ -693,9 +644,6 @@ void load(const std::filesystem::path& path) {
|
|||||||
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {});
|
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {});
|
||||||
|
|
||||||
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
|
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")) {
|
if (data.contains("Settings")) {
|
||||||
@ -708,19 +656,6 @@ void load(const std::filesystem::path& path) {
|
|||||||
const toml::value& keys = data.at("Keys");
|
const toml::value& keys = data.at("Keys");
|
||||||
trophyKey = toml::find_or<std::string>(keys, "TrophyKey", "");
|
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) {
|
void sortTomlSections(toml::ordered_value& data) {
|
||||||
@ -792,7 +727,6 @@ void save(const std::filesystem::path& path) {
|
|||||||
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
|
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
|
||||||
data["Input"]["cursorState"] = cursorState;
|
data["Input"]["cursorState"] = cursorState;
|
||||||
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
|
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
|
||||||
data["Input"]["backButtonBehavior"] = backButtonBehavior;
|
|
||||||
data["Input"]["useSpecialPad"] = useSpecialPad;
|
data["Input"]["useSpecialPad"] = useSpecialPad;
|
||||||
data["Input"]["specialPadClass"] = specialPadClass;
|
data["Input"]["specialPadClass"] = specialPadClass;
|
||||||
data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled;
|
data["Input"]["isMotionControlsEnabled"] = isMotionControlsEnabled;
|
||||||
@ -855,7 +789,6 @@ void save(const std::filesystem::path& path) {
|
|||||||
|
|
||||||
data["GUI"]["addonInstallDir"] =
|
data["GUI"]["addonInstallDir"] =
|
||||||
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
|
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
|
||||||
data["GUI"]["emulatorLanguage"] = emulator_language;
|
|
||||||
data["Settings"]["consoleLanguage"] = m_language;
|
data["Settings"]["consoleLanguage"] = m_language;
|
||||||
|
|
||||||
// Sorting of TOML sections
|
// Sorting of TOML sections
|
||||||
@ -864,42 +797,6 @@ void save(const std::filesystem::path& path) {
|
|||||||
std::ofstream file(path, std::ios::binary);
|
std::ofstream file(path, std::ios::binary);
|
||||||
file << data;
|
file << data;
|
||||||
file.close();
|
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() {
|
void setDefaultValues() {
|
||||||
@ -920,7 +817,6 @@ void setDefaultValues() {
|
|||||||
cursorState = HideCursorState::Idle;
|
cursorState = HideCursorState::Idle;
|
||||||
cursorHideTimeout = 5;
|
cursorHideTimeout = 5;
|
||||||
trophyNotificationDuration = 6.0;
|
trophyNotificationDuration = 6.0;
|
||||||
backButtonBehavior = "left";
|
|
||||||
useSpecialPad = false;
|
useSpecialPad = false;
|
||||||
specialPadClass = 1;
|
specialPadClass = 1;
|
||||||
isDebugDump = false;
|
isDebugDump = false;
|
||||||
@ -937,7 +833,6 @@ void setDefaultValues() {
|
|||||||
vkHostMarkers = false;
|
vkHostMarkers = false;
|
||||||
vkGuestMarkers = false;
|
vkGuestMarkers = false;
|
||||||
rdocEnable = false;
|
rdocEnable = false;
|
||||||
emulator_language = "en_US";
|
|
||||||
m_language = 1;
|
m_language = 1;
|
||||||
gpuId = -1;
|
gpuId = -1;
|
||||||
compatibilityData = false;
|
compatibilityData = false;
|
||||||
@ -967,7 +862,7 @@ l3 = x
|
|||||||
r3 = m
|
r3 = m
|
||||||
|
|
||||||
options = enter
|
options = enter
|
||||||
touchpad = space
|
touchpad_center = space
|
||||||
|
|
||||||
pad_up = up
|
pad_up = up
|
||||||
pad_down = down
|
pad_down = down
|
||||||
@ -999,7 +894,7 @@ r2 = r2
|
|||||||
r3 = r3
|
r3 = r3
|
||||||
|
|
||||||
options = options
|
options = options
|
||||||
touchpad = back
|
touchpad_center = back
|
||||||
|
|
||||||
pad_up = pad_up
|
pad_up = pad_up
|
||||||
pad_down = pad_down
|
pad_down = pad_down
|
||||||
|
@ -18,77 +18,96 @@ enum HideCursorState : int { Never, Idle, Always };
|
|||||||
|
|
||||||
void load(const std::filesystem::path& path);
|
void load(const std::filesystem::path& path);
|
||||||
void save(const std::filesystem::path& path);
|
void save(const std::filesystem::path& path);
|
||||||
void saveMainWindow(const std::filesystem::path& path);
|
|
||||||
|
|
||||||
std::string getTrophyKey();
|
std::string getTrophyKey();
|
||||||
void setTrophyKey(std::string key);
|
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();
|
bool GetLoadGameSizeEnabled();
|
||||||
std::filesystem::path GetSaveDataPath();
|
std::filesystem::path GetSaveDataPath();
|
||||||
void setLoadGameSizeEnabled(bool enable);
|
void setLoadGameSizeEnabled(bool enable);
|
||||||
bool getIsFullscreen();
|
|
||||||
std::string getFullscreenMode();
|
|
||||||
bool isNeoModeConsole();
|
|
||||||
bool isDevKitConsole();
|
|
||||||
bool getisTrophyPopupDisabled();
|
|
||||||
bool getEnableDiscordRPC();
|
|
||||||
bool getCompatibilityEnabled();
|
bool getCompatibilityEnabled();
|
||||||
bool getCheckCompatibilityOnStartup();
|
bool getCheckCompatibilityOnStartup();
|
||||||
bool getPSNSignedIn();
|
|
||||||
|
|
||||||
std::string getLogFilter();
|
|
||||||
std::string getLogType();
|
|
||||||
std::string getUserName();
|
std::string getUserName();
|
||||||
std::string getChooseHomeTab();
|
std::string getChooseHomeTab();
|
||||||
|
|
||||||
s16 getCursorState();
|
|
||||||
int getCursorHideTimeout();
|
|
||||||
double getTrophyNotificationDuration();
|
|
||||||
std::string getBackButtonBehavior();
|
|
||||||
bool getUseSpecialPad();
|
|
||||||
int getSpecialPadClass();
|
|
||||||
bool getIsMotionControlsEnabled();
|
|
||||||
bool GetUseUnifiedInputConfig();
|
bool GetUseUnifiedInputConfig();
|
||||||
void SetUseUnifiedInputConfig(bool use);
|
void SetUseUnifiedInputConfig(bool use);
|
||||||
bool GetOverrideControllerColor();
|
bool GetOverrideControllerColor();
|
||||||
void SetOverrideControllerColor(bool enable);
|
void SetOverrideControllerColor(bool enable);
|
||||||
int* GetControllerCustomColor();
|
int* GetControllerCustomColor();
|
||||||
void SetControllerCustomColor(int r, int b, int g);
|
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 setUserName(const std::string& type);
|
||||||
void setChooseHomeTab(const std::string& type);
|
void setChooseHomeTab(const std::string& type);
|
||||||
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
|
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
|
||||||
@ -96,57 +115,19 @@ void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
|
|||||||
void setSaveDataPath(const std::filesystem::path& path);
|
void setSaveDataPath(const std::filesystem::path& path);
|
||||||
void setCompatibilityEnabled(bool use);
|
void setCompatibilityEnabled(bool use);
|
||||||
void setCheckCompatibilityOnStartup(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
|
// Gui
|
||||||
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||||
void removeGameInstallDir(const std::filesystem::path& dir);
|
void removeGameInstallDir(const std::filesystem::path& dir);
|
||||||
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
|
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
|
||||||
void setAddonInstallDir(const std::filesystem::path& dir);
|
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<std::filesystem::path> getGameInstallDirs();
|
||||||
const std::vector<bool> getGameInstallDirsEnabled();
|
const std::vector<bool> getGameInstallDirsEnabled();
|
||||||
std::filesystem::path getAddonInstallDir();
|
std::filesystem::path getAddonInstallDir();
|
||||||
u32 getMainWindowTheme();
|
|
||||||
std::vector<std::string> getElfViewer();
|
|
||||||
std::vector<std::string> getRecentFiles();
|
|
||||||
std::string getEmulatorLanguage();
|
|
||||||
|
|
||||||
void setDefaultValues();
|
void setDefaultValues();
|
||||||
|
|
||||||
// todo: name and function location pending
|
// todo: name and function location pending
|
||||||
std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = "");
|
std::filesystem::path GetFoolproofKbmConfigFile(const std::string& game_id = "");
|
||||||
|
|
||||||
// settings
|
|
||||||
u32 GetLanguage();
|
|
||||||
}; // namespace Config
|
}; // namespace Config
|
||||||
|
@ -447,21 +447,18 @@ int PS4_SYSV_ABI scePadReadState(s32 handle, OrbisPadData* pData) {
|
|||||||
|
|
||||||
// Only do this on handle 1 for now
|
// Only do this on handle 1 for now
|
||||||
if (engine && handle == 1) {
|
if (engine && handle == 1) {
|
||||||
const auto gyro_poll_rate = engine->GetAccelPollRate();
|
auto now = std::chrono::steady_clock::now();
|
||||||
if (gyro_poll_rate != 0.0f) {
|
float deltaTime =
|
||||||
auto now = std::chrono::steady_clock::now();
|
std::chrono::duration_cast<std::chrono::microseconds>(now - controller->GetLastUpdate())
|
||||||
float deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(
|
.count() /
|
||||||
now - controller->GetLastUpdate())
|
1000000.0f;
|
||||||
.count() /
|
controller->SetLastUpdate(now);
|
||||||
1000000.0f;
|
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
||||||
controller->SetLastUpdate(now);
|
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||||
Libraries::Pad::OrbisFQuaternion lastOrientation = controller->GetLastOrientation();
|
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity, deltaTime,
|
||||||
Libraries::Pad::OrbisFQuaternion outputOrientation = {0.0f, 0.0f, 0.0f, 1.0f};
|
lastOrientation, outputOrientation);
|
||||||
GameController::CalculateOrientation(pData->acceleration, pData->angularVelocity,
|
pData->orientation = outputOrientation;
|
||||||
deltaTime, lastOrientation, outputOrientation);
|
controller->SetLastOrientation(outputOrientation);
|
||||||
pData->orientation = outputOrientation;
|
|
||||||
controller->SetLastOrientation(outputOrientation);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
pData->touchData.touchNum =
|
pData->touchData.touchNum =
|
||||||
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
|
(state.touchpad[0].state ? 1 : 0) + (state.touchpad[1].state ? 1 : 0);
|
||||||
|
@ -22,25 +22,25 @@ static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoi
|
|||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
// clang-format off
|
// clang-format off
|
||||||
static const std::unordered_map<std::string, std::string> default_title = {
|
static const std::unordered_map<int, std::string> default_title = {
|
||||||
{"ja_JP", "セーブデータ"},
|
{0/*"ja_JP"*/, "セーブデータ"},
|
||||||
{"en_US", "Saved Data"},
|
{1/*"en_US"*/, "Saved Data"},
|
||||||
{"fr_FR", "Données sauvegardées"},
|
{2/*"fr_FR"*/, "Données sauvegardées"},
|
||||||
{"es_ES", "Datos guardados"},
|
{3/*"es_ES"*/, "Datos guardados"},
|
||||||
{"de_DE", "Gespeicherte Daten"},
|
{4/*"de_DE"*/, "Gespeicherte Daten"},
|
||||||
{"it_IT", "Dati salvati"},
|
{5/*"it_IT"*/, "Dati salvati"},
|
||||||
{"nl_NL", "Opgeslagen data"},
|
{6/*"nl_NL"*/, "Opgeslagen data"},
|
||||||
{"pt_PT", "Dados guardados"},
|
{7/*"pt_PT"*/, "Dados guardados"},
|
||||||
{"ru_RU", "Сохраненные данные"},
|
{8/*"ru_RU"*/, "Сохраненные данные"},
|
||||||
{"ko_KR", "저장 데이터"},
|
{9/*"ko_KR"*/, "저장 데이터"},
|
||||||
{"zh_CN", "保存数据"},
|
{10/*"zh_CN"*/, "保存数据"},
|
||||||
{"fi_FI", "Tallennetut tiedot"},
|
{12/*"fi_FI"*/, "Tallennetut tiedot"},
|
||||||
{"sv_SE", "Sparade data"},
|
{13/*"sv_SE"*/, "Sparade data"},
|
||||||
{"da_DK", "Gemte data"},
|
{14/*"da_DK"*/, "Gemte data"},
|
||||||
{"no_NO", "Lagrede data"},
|
{15/*"no_NO"*/, "Lagrede data"},
|
||||||
{"pl_PL", "Zapisane dane"},
|
{16/*"pl_PL"*/, "Zapisane dane"},
|
||||||
{"pt_BR", "Dados salvos"},
|
{17/*"pt_BR"*/, "Dados salvos"},
|
||||||
{"tr_TR", "Kayıtlı Veriler"},
|
{19/*"tr_TR"*/, "Kayıtlı Veriler"},
|
||||||
};
|
};
|
||||||
// clang-format on
|
// 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,
|
void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
|
||||||
std::string game_serial) {
|
std::string game_serial) {
|
||||||
std::string locale = Config::getEmulatorLanguage();
|
int locale = Config::GetLanguage();
|
||||||
if (!default_title.contains(locale)) {
|
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__)
|
#define P(type, key, ...) param_sfo.Add##type(std::string{key}, __VA_ARGS__)
|
||||||
|
@ -58,10 +58,7 @@ Emulator::Emulator() {
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
Emulator::~Emulator() {
|
Emulator::~Emulator() {}
|
||||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
|
||||||
Config::saveMainWindow(config_dir / "config.toml");
|
|
||||||
}
|
|
||||||
|
|
||||||
void Emulator::Run(std::filesystem::path file, const std::vector<std::string> args) {
|
void Emulator::Run(std::filesystem::path file, const std::vector<std::string> args) {
|
||||||
if (std::filesystem::is_directory(file)) {
|
if (std::filesystem::is_directory(file)) {
|
||||||
|
@ -66,22 +66,25 @@ auto output_array = std::array{
|
|||||||
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
ControllerOutput(LEFTJOYSTICK_HALFMODE),
|
||||||
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
ControllerOutput(RIGHTJOYSTICK_HALFMODE),
|
||||||
ControllerOutput(KEY_TOGGLE),
|
ControllerOutput(KEY_TOGGLE),
|
||||||
|
ControllerOutput(MOUSE_GYRO_ROLL_MODE),
|
||||||
|
|
||||||
// Button mappings
|
// Button mappings
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
ControllerOutput(SDL_GAMEPAD_BUTTON_NORTH), // Triangle
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
ControllerOutput(SDL_GAMEPAD_BUTTON_EAST), // Circle
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
ControllerOutput(SDL_GAMEPAD_BUTTON_SOUTH), // Cross
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
ControllerOutput(SDL_GAMEPAD_BUTTON_WEST), // Square
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_SHOULDER), // L1
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
ControllerOutput(SDL_GAMEPAD_BUTTON_LEFT_STICK), // L3
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER), // R1
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
ControllerOutput(SDL_GAMEPAD_BUTTON_RIGHT_STICK), // R3
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
ControllerOutput(SDL_GAMEPAD_BUTTON_START), // Options
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD), // TouchPad
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT), // TouchPad
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER), // TouchPad
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
ControllerOutput(SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT), // TouchPad
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_UP), // Up
|
||||||
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_DOWN), // Down
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_LEFT), // Left
|
||||||
|
ControllerOutput(SDL_GAMEPAD_BUTTON_DPAD_RIGHT), // Right
|
||||||
|
|
||||||
// Axis mappings
|
// Axis mappings
|
||||||
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
// ControllerOutput(SDL_GAMEPAD_BUTTON_INVALID, SDL_GAMEPAD_AXIS_LEFTX, false),
|
||||||
@ -130,6 +133,12 @@ static OrbisPadButtonDataOffset SDLGamepadToOrbisButton(u8 button) {
|
|||||||
return OPBDO::Options;
|
return OPBDO::Options;
|
||||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
||||||
return OPBDO::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:
|
case SDL_GAMEPAD_BUTTON_BACK:
|
||||||
return OPBDO::TouchPad;
|
return OPBDO::TouchPad;
|
||||||
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER:
|
||||||
@ -502,14 +511,21 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
}
|
}
|
||||||
old_button_state = new_button_state;
|
old_button_state = new_button_state;
|
||||||
old_param = *new_param;
|
old_param = *new_param;
|
||||||
float touchpad_x = 0;
|
|
||||||
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
if (button != SDL_GAMEPAD_BUTTON_INVALID) {
|
||||||
switch (button) {
|
switch (button) {
|
||||||
case SDL_GAMEPAD_BUTTON_TOUCHPAD:
|
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
|
||||||
touchpad_x = Config::getBackButtonBehavior() == "left" ? 0.25f
|
LOG_INFO(Input, "Topuchpad left");
|
||||||
: Config::getBackButtonBehavior() == "right" ? 0.75f
|
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
|
||||||
: 0.5f;
|
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
controller->SetTouchpadState(0, new_button_state, touchpad_x, 0.5f);
|
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);
|
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
break;
|
break;
|
||||||
case LEFTJOYSTICK_HALFMODE:
|
case LEFTJOYSTICK_HALFMODE:
|
||||||
@ -522,6 +538,9 @@ void ControllerOutput::FinalizeUpdate() {
|
|||||||
// to do it, and it would be inconvenient to force it here, when AddUpdate does the job just
|
// 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
|
// fine, and a toggle doesn't have to checked against every input that's bound to it, it's
|
||||||
// enough that one is pressed
|
// enough that one is pressed
|
||||||
|
case MOUSE_GYRO_ROLL_MODE:
|
||||||
|
SetMouseGyroRollMode(new_button_state);
|
||||||
|
break;
|
||||||
default: // is a normal key (hopefully)
|
default: // is a normal key (hopefully)
|
||||||
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
|
||||||
break;
|
break;
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
#define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5
|
#define SDL_MOUSE_WHEEL_LEFT SDL_EVENT_MOUSE_WHEEL + 5
|
||||||
#define SDL_MOUSE_WHEEL_RIGHT SDL_EVENT_MOUSE_WHEEL + 7
|
#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
|
// idk who already used what where so I just chose a big number
|
||||||
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
#define SDL_EVENT_MOUSE_WHEEL_OFF SDL_EVENT_USER + 10
|
||||||
|
|
||||||
@ -31,6 +35,7 @@
|
|||||||
#define BACK_BUTTON 0x00040000
|
#define BACK_BUTTON 0x00040000
|
||||||
|
|
||||||
#define KEY_TOGGLE 0x00200000
|
#define KEY_TOGGLE 0x00200000
|
||||||
|
#define MOUSE_GYRO_ROLL_MODE 0x00400000
|
||||||
|
|
||||||
#define SDL_UNMAPPED UINT32_MAX - 1
|
#define SDL_UNMAPPED UINT32_MAX - 1
|
||||||
|
|
||||||
@ -98,7 +103,9 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
|||||||
{"options", SDL_GAMEPAD_BUTTON_START},
|
{"options", SDL_GAMEPAD_BUTTON_START},
|
||||||
|
|
||||||
// these are outputs only (touchpad can only be bound to itself)
|
// 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},
|
{"leftjoystick_halfmode", LEFTJOYSTICK_HALFMODE},
|
||||||
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
|
{"rightjoystick_halfmode", RIGHTJOYSTICK_HALFMODE},
|
||||||
|
|
||||||
@ -108,6 +115,7 @@ const std::map<std::string, u32> string_to_cbutton_map = {
|
|||||||
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
|
{"lpaddle_low", SDL_GAMEPAD_BUTTON_LEFT_PADDLE2},
|
||||||
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
|
{"rpaddle_high", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1},
|
||||||
{"rpaddle_low", SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2},
|
{"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 = {
|
const std::map<std::string, AxisMapping> string_to_axis_map = {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "input/controller.h"
|
#include "input/controller.h"
|
||||||
#include "input_mouse.h"
|
#include "input_mouse.h"
|
||||||
@ -13,12 +14,19 @@ namespace Input {
|
|||||||
|
|
||||||
int mouse_joystick_binding = 0;
|
int mouse_joystick_binding = 0;
|
||||||
float mouse_deadzone_offset = 0.5, mouse_speed = 1, mouse_speed_offset = 0.1250;
|
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;
|
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
|
// Switches mouse to a set mode or turns mouse emulation off if it was already in that mode.
|
||||||
void ToggleMouseEnabled() {
|
// Returns whether the mode is turned on.
|
||||||
mouse_enabled = !mouse_enabled;
|
bool ToggleMouseModeTo(MouseMode m) {
|
||||||
|
if (mouse_mode == m) {
|
||||||
|
mouse_mode = MouseMode::Off;
|
||||||
|
} else {
|
||||||
|
mouse_mode = m;
|
||||||
|
}
|
||||||
|
return mouse_mode == m;
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetMouseToJoystick(int joystick) {
|
void SetMouseToJoystick(int joystick) {
|
||||||
@ -31,10 +39,11 @@ void SetMouseParams(float mdo, float ms, float mso) {
|
|||||||
mouse_speed_offset = mso;
|
mouse_speed_offset = mso;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
|
void SetMouseGyroRollMode(bool mode) {
|
||||||
auto* controller = (GameController*)param;
|
mouse_gyro_roll_mode = mode;
|
||||||
if (!mouse_enabled)
|
}
|
||||||
return interval;
|
|
||||||
|
void EmulateJoystick(GameController* controller, u32 interval) {
|
||||||
|
|
||||||
Axis axis_x, axis_y;
|
Axis axis_x, axis_y;
|
||||||
switch (mouse_joystick_binding) {
|
switch (mouse_joystick_binding) {
|
||||||
@ -47,7 +56,7 @@ Uint32 MousePolling(void* param, Uint32 id, Uint32 interval) {
|
|||||||
axis_y = Axis::RightY;
|
axis_y = Axis::RightY;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return interval; // no update needed
|
return; // no update needed
|
||||||
}
|
}
|
||||||
|
|
||||||
float d_x = 0, d_y = 0;
|
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_x, GetAxis(-0x80, 0x7f, 0));
|
||||||
controller->Axis(0, axis_y, 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;
|
return interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,11 +8,21 @@
|
|||||||
|
|
||||||
namespace Input {
|
namespace Input {
|
||||||
|
|
||||||
void ToggleMouseEnabled();
|
enum MouseMode {
|
||||||
|
Off = 0,
|
||||||
|
Joystick,
|
||||||
|
Gyro,
|
||||||
|
};
|
||||||
|
|
||||||
|
bool ToggleMouseModeTo(MouseMode m);
|
||||||
void SetMouseToJoystick(int joystick);
|
void SetMouseToJoystick(int joystick);
|
||||||
void SetMouseParams(float mouse_deadzone_offset, float mouse_speed, float mouse_speed_offset);
|
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);
|
Uint32 MousePolling(void* param, Uint32 id, Uint32 interval);
|
||||||
|
|
||||||
} // namespace Input
|
} // namespace Input
|
||||||
|
@ -12,7 +12,8 @@
|
|||||||
#include "main_window_themes.h"
|
#include "main_window_themes.h"
|
||||||
#include "ui_about_dialog.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);
|
ui->setupUi(this);
|
||||||
preloadImages();
|
preloadImages();
|
||||||
|
|
||||||
@ -57,7 +58,7 @@ void AboutDialog::preloadImages() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void AboutDialog::updateImagesForCurrentTheme() {
|
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 ||
|
bool isDarkTheme = (currentTheme == Theme::Dark || currentTheme == Theme::Green ||
|
||||||
currentTheme == Theme::Blue || currentTheme == Theme::Violet);
|
currentTheme == Theme::Blue || currentTheme == Theme::Violet);
|
||||||
if (isDarkTheme) {
|
if (isDarkTheme) {
|
||||||
@ -188,7 +189,7 @@ void AboutDialog::removeHoverEffect(QLabel* label) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool AboutDialog::isDarkTheme() const {
|
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 ||
|
return currentTheme == Theme::Dark || currentTheme == Theme::Green ||
|
||||||
currentTheme == Theme::Blue || currentTheme == Theme::Violet;
|
currentTheme == Theme::Blue || currentTheme == Theme::Violet;
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
#include <QLabel>
|
#include <QLabel>
|
||||||
#include <QPixmap>
|
#include <QPixmap>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class AboutDialog;
|
class AboutDialog;
|
||||||
@ -17,7 +18,7 @@ class AboutDialog : public QDialog {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit AboutDialog(QWidget* parent = nullptr);
|
explicit AboutDialog(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
|
||||||
~AboutDialog();
|
~AboutDialog();
|
||||||
bool eventFilter(QObject* obj, QEvent* event);
|
bool eventFilter(QObject* obj, QEvent* event);
|
||||||
|
|
||||||
@ -33,4 +34,5 @@ private:
|
|||||||
|
|
||||||
QPixmap originalImages[5];
|
QPixmap originalImages[5];
|
||||||
QPixmap invertedImages[5];
|
QPixmap invertedImages[5];
|
||||||
|
std::shared_ptr<gui_settings> m_gui_settings;
|
||||||
};
|
};
|
||||||
|
@ -39,14 +39,28 @@ private:
|
|||||||
"pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x",
|
"pad_left", "pad_right", "axis_left_x", "axis_left_y", "axis_right_x",
|
||||||
"axis_right_y", "back"};
|
"axis_right_y", "back"};
|
||||||
|
|
||||||
const QStringList ButtonOutputs = {"cross", "circle", "square", "triangle", "l1",
|
const QStringList ButtonOutputs = {"cross",
|
||||||
"r1", "l2", "r2", "l3",
|
"circle",
|
||||||
|
"square",
|
||||||
|
"triangle",
|
||||||
|
"l1",
|
||||||
|
"r1",
|
||||||
|
"l2",
|
||||||
|
"r2",
|
||||||
|
"l3",
|
||||||
|
|
||||||
"r3", "options", "pad_up",
|
"r3",
|
||||||
|
"options",
|
||||||
|
"pad_up",
|
||||||
|
|
||||||
"pad_down",
|
"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",
|
const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y",
|
||||||
"unmapped"};
|
"unmapped"};
|
||||||
|
@ -3,10 +3,12 @@
|
|||||||
|
|
||||||
#include "elf_viewer.h"
|
#include "elf_viewer.h"
|
||||||
|
|
||||||
ElfViewer::ElfViewer(QWidget* parent) : QTableWidget(parent) {
|
ElfViewer::ElfViewer(std::shared_ptr<gui_settings> gui_settings, QWidget* parent)
|
||||||
dir_list_std = Config::getElfViewer();
|
: QTableWidget(parent), m_gui_settings(std::move(gui_settings)) {
|
||||||
for (const auto& str : dir_list_std) {
|
|
||||||
dir_list.append(QString::fromStdString(str));
|
list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_elfDirs));
|
||||||
|
for (const auto& str : list) {
|
||||||
|
dir_list.append(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckElfFolders();
|
CheckElfFolders();
|
||||||
@ -55,11 +57,11 @@ void ElfViewer::OpenElfFolder() {
|
|||||||
}
|
}
|
||||||
std::ranges::sort(m_elf_list);
|
std::ranges::sort(m_elf_list);
|
||||||
OpenElfFiles();
|
OpenElfFiles();
|
||||||
dir_list_std.clear();
|
list.clear();
|
||||||
for (auto dir : dir_list) {
|
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 {
|
} else {
|
||||||
// qDebug() << "Folder selection canceled.";
|
// qDebug() << "Folder selection canceled.";
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
class ElfViewer : public QTableWidget {
|
class ElfViewer : public QTableWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit ElfViewer(QWidget* parent = nullptr);
|
explicit ElfViewer(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
|
||||||
QStringList m_elf_list;
|
QStringList m_elf_list;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@ -21,7 +21,8 @@ private:
|
|||||||
Core::Loader::Elf m_elf_file;
|
Core::Loader::Elf m_elf_file;
|
||||||
QStringList dir_list;
|
QStringList dir_list;
|
||||||
QStringList elf_headers_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) {
|
void SetTableItem(QTableWidget* game_list, int row, int column, QString itemStr) {
|
||||||
QTableWidgetItem* item = new QTableWidgetItem();
|
QTableWidgetItem* item = new QTableWidgetItem();
|
||||||
|
@ -34,7 +34,8 @@ GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
|
|||||||
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
|
connect(this->horizontalScrollBar(), &QScrollBar::valueChanged, this,
|
||||||
&GameGridFrame::RefreshGridBackgroundImage);
|
&GameGridFrame::RefreshGridBackgroundImage);
|
||||||
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
|
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
|
||||||
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info, this, false);
|
m_gui_context_menus.RequestGameMenu(pos, m_game_info->m_games, m_compat_info,
|
||||||
|
m_gui_settings, this, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +75,8 @@ GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
|
|||||||
});
|
});
|
||||||
|
|
||||||
connect(this, &QTableWidget::customContextMenuRequested, this, [=, this](const QPoint& pos) {
|
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) {
|
connect(this, &QTableWidget::cellClicked, this, [=, this](int row, int column) {
|
||||||
|
@ -32,8 +32,10 @@ class GuiContextMenus : public QObject {
|
|||||||
public:
|
public:
|
||||||
void RequestGameMenu(const QPoint& pos, QVector<GameInfo>& m_games,
|
void RequestGameMenu(const QPoint& pos, QVector<GameInfo>& m_games,
|
||||||
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
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);
|
QPoint global_pos = widget->viewport()->mapToGlobal(pos);
|
||||||
|
std::shared_ptr<gui_settings> m_gui_settings = std::move(settings);
|
||||||
int itemID = 0;
|
int itemID = 0;
|
||||||
if (isList) {
|
if (isList) {
|
||||||
itemID = widget->currentRow();
|
itemID = widget->currentRow();
|
||||||
@ -357,7 +359,7 @@ public:
|
|||||||
|
|
||||||
QString gameName = QString::fromStdString(m_games[itemID].name);
|
QString gameName = QString::fromStdString(m_games[itemID].name);
|
||||||
TrophyViewer* trophyViewer =
|
TrophyViewer* trophyViewer =
|
||||||
new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames);
|
new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames);
|
||||||
trophyViewer->show();
|
trophyViewer->show();
|
||||||
connect(widget->parent(), &QWidget::destroyed, trophyViewer,
|
connect(widget->parent(), &QWidget::destroyed, trophyViewer,
|
||||||
[trophyViewer]() { trophyViewer->deleteLater(); });
|
[trophyViewer]() { trophyViewer->deleteLater(); });
|
||||||
|
@ -17,6 +17,12 @@ const QString game_grid = "game_grid";
|
|||||||
const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false);
|
const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false);
|
||||||
const gui_value gen_showChangeLog = gui_value(general_settings, "showChangeLog", 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_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
|
// main window settings
|
||||||
const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray());
|
const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray());
|
||||||
|
@ -32,14 +32,34 @@ KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget*
|
|||||||
ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial));
|
ui->ProfileComboBox->addItem(QString::fromStdString(m_game_info->m_games[i].serial));
|
||||||
}
|
}
|
||||||
|
|
||||||
ButtonsList = {
|
ButtonsList = {ui->CrossButton,
|
||||||
ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton,
|
ui->CircleButton,
|
||||||
ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button,
|
ui->TriangleButton,
|
||||||
ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton,
|
ui->SquareButton,
|
||||||
ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton,
|
ui->L1Button,
|
||||||
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
|
ui->R1Button,
|
||||||
ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton,
|
ui->L2Button,
|
||||||
ui->LHalfButton, ui->RHalfButton};
|
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();
|
ButtonConnects();
|
||||||
SetUIValuestoMappings("default");
|
SetUIValuestoMappings("default");
|
||||||
@ -187,8 +207,10 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) {
|
|||||||
|
|
||||||
lines.push_back("");
|
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");
|
add_mapping(ui->OptionsButton->text(), "options");
|
||||||
add_mapping(ui->TouchpadButton->text(), "touchpad");
|
|
||||||
|
|
||||||
lines.push_back("");
|
lines.push_back("");
|
||||||
|
|
||||||
@ -317,7 +339,9 @@ void KBMSettings::SetDefault() {
|
|||||||
ui->R2Button->setText("o");
|
ui->R2Button->setText("o");
|
||||||
ui->R3Button->setText("m");
|
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->OptionsButton->setText("enter");
|
||||||
|
|
||||||
ui->DpadUpButton->setText("up");
|
ui->DpadUpButton->setText("up");
|
||||||
@ -396,8 +420,12 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) {
|
|||||||
ui->DpadRightButton->setText(QString::fromStdString(input_string));
|
ui->DpadRightButton->setText(QString::fromStdString(input_string));
|
||||||
} else if (output_string == "options") {
|
} else if (output_string == "options") {
|
||||||
ui->OptionsButton->setText(QString::fromStdString(input_string));
|
ui->OptionsButton->setText(QString::fromStdString(input_string));
|
||||||
} else if (output_string == "touchpad") {
|
} else if (output_string == "touchpad_left") {
|
||||||
ui->TouchpadButton->setText(QString::fromStdString(input_string));
|
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") {
|
} else if (output_string == "axis_left_x_minus") {
|
||||||
ui->LStickLeftButton->setText(QString::fromStdString(input_string));
|
ui->LStickLeftButton->setText(QString::fromStdString(input_string));
|
||||||
} else if (output_string == "axis_left_x_plus") {
|
} else if (output_string == "axis_left_x_plus") {
|
||||||
|
@ -11,8 +11,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1234</width>
|
<width>1235</width>
|
||||||
<height>796</height>
|
<height>842</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -44,8 +44,8 @@
|
|||||||
<rect>
|
<rect>
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1214</width>
|
<width>1215</width>
|
||||||
<height>746</height>
|
<height>792</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QWidget" name="layoutWidget">
|
<widget class="QWidget" name="layoutWidget">
|
||||||
@ -54,7 +54,7 @@
|
|||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>1211</width>
|
<width>1211</width>
|
||||||
<height>741</height>
|
<height>791</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QHBoxLayout" name="RemapLayout">
|
<layout class="QHBoxLayout" name="RemapLayout">
|
||||||
@ -793,7 +793,7 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QVBoxLayout" name="layout_system_buttons" stretch="0">
|
<layout class="QVBoxLayout" name="layout_system_buttons" stretch="0,0">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="groupBox_4">
|
<widget class="QGroupBox" name="groupBox_4">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
@ -825,8 +825,11 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumHeight">
|
<property name="minimumSize">
|
||||||
<number>48</number>
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>24</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||||
@ -844,8 +847,11 @@
|
|||||||
<verstretch>0</verstretch>
|
<verstretch>0</verstretch>
|
||||||
</sizepolicy>
|
</sizepolicy>
|
||||||
</property>
|
</property>
|
||||||
<property name="minimumHeight">
|
<property name="minimumSize">
|
||||||
<number>48</number>
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>24</height>
|
||||||
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="focusPolicy">
|
<property name="focusPolicy">
|
||||||
<enum>Qt::FocusPolicy::NoFocus</enum>
|
<enum>Qt::FocusPolicy::NoFocus</enum>
|
||||||
@ -858,6 +864,55 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -1067,34 +1122,13 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="gb_touchpad">
|
<widget class="QGroupBox" name="gb_touchpadleft">
|
||||||
<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">
|
<property name="title">
|
||||||
<string>Touchpad Click</string>
|
<string>Touchpad Left</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_9">
|
<layout class="QVBoxLayout" name="verticalLayout_17">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="TouchpadButton">
|
<widget class="QPushButton" name="TouchpadLeftButton">
|
||||||
<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">
|
<property name="text">
|
||||||
<string>unmapped</string>
|
<string>unmapped</string>
|
||||||
</property>
|
</property>
|
||||||
@ -1150,6 +1184,22 @@
|
|||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</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>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
@ -1204,7 +1254,7 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="gb_options">
|
<widget class="QGroupBox" name="gb_touchpadright">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
@ -1218,23 +1268,11 @@
|
|||||||
</size>
|
</size>
|
||||||
</property>
|
</property>
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Options</string>
|
<string>Touchpad Right</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="gb_start_layout">
|
<layout class="QVBoxLayout" name="verticalLayout_9">
|
||||||
<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>
|
<item>
|
||||||
<widget class="QPushButton" name="OptionsButton">
|
<widget class="QPushButton" name="TouchpadRightButton">
|
||||||
<property name="sizePolicy">
|
<property name="sizePolicy">
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
|
||||||
<horstretch>0</horstretch>
|
<horstretch>0</horstretch>
|
||||||
|
@ -39,8 +39,6 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||||||
|
|
||||||
MainWindow::~MainWindow() {
|
MainWindow::~MainWindow() {
|
||||||
SaveWindowState();
|
SaveWindowState();
|
||||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
|
||||||
Config::saveMainWindow(config_dir / "config.toml");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MainWindow::Init() {
|
bool MainWindow::Init() {
|
||||||
@ -297,7 +295,7 @@ void MainWindow::CreateDockWindows() {
|
|||||||
m_game_list_frame->setObjectName("gamelist");
|
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.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this));
|
||||||
m_game_grid_frame->setObjectName("gamegridlist");
|
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");
|
m_elf_viewer->setObjectName("elflist");
|
||||||
|
|
||||||
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
||||||
@ -492,7 +490,7 @@ void MainWindow::CreateConnects() {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
connect(ui->aboutAct, &QAction::triggered, this, [this]() {
|
connect(ui->aboutAct, &QAction::triggered, this, [this]() {
|
||||||
auto aboutDialog = new AboutDialog(this);
|
auto aboutDialog = new AboutDialog(m_gui_settings, this);
|
||||||
aboutDialog->exec();
|
aboutDialog->exec();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -771,14 +769,14 @@ void MainWindow::CreateConnects() {
|
|||||||
|
|
||||||
QString gameName = QString::fromStdString(firstGame.name);
|
QString gameName = QString::fromStdString(firstGame.name);
|
||||||
TrophyViewer* trophyViewer =
|
TrophyViewer* trophyViewer =
|
||||||
new TrophyViewer(trophyPath, gameTrpPath, gameName, allTrophyGames);
|
new TrophyViewer(m_gui_settings, trophyPath, gameTrpPath, gameName, allTrophyGames);
|
||||||
trophyViewer->show();
|
trophyViewer->show();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Themes
|
// Themes
|
||||||
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeDark, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Dark, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -786,7 +784,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeLight, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Light, ui->mw_searchbar);
|
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) {
|
if (!isIconBlack) {
|
||||||
SetUiIcons(true);
|
SetUiIcons(true);
|
||||||
isIconBlack = true;
|
isIconBlack = true;
|
||||||
@ -794,7 +792,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeGreen, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Green, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -802,7 +800,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeBlue, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Blue, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -810,7 +808,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeViolet, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Violet, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -818,7 +816,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeGruvbox, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeGruvbox, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Gruvbox, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -826,7 +824,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeTokyoNight, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::TokyoNight, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -834,7 +832,7 @@ void MainWindow::CreateConnects() {
|
|||||||
});
|
});
|
||||||
connect(ui->setThemeOled, &QAction::triggered, &m_window_themes, [this]() {
|
connect(ui->setThemeOled, &QAction::triggered, &m_window_themes, [this]() {
|
||||||
m_window_themes.SetWindowTheme(Theme::Oled, ui->mw_searchbar);
|
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) {
|
if (isIconBlack) {
|
||||||
SetUiIcons(false);
|
SetUiIcons(false);
|
||||||
isIconBlack = false;
|
isIconBlack = false;
|
||||||
@ -981,7 +979,7 @@ void MainWindow::InstallDirectory() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::SetLastUsedTheme() {
|
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);
|
m_window_themes.SetWindowTheme(lastTheme, ui->mw_searchbar);
|
||||||
|
|
||||||
switch (lastTheme) {
|
switch (lastTheme) {
|
||||||
@ -1122,33 +1120,32 @@ void MainWindow::HandleResize(QResizeEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::AddRecentFiles(QString filePath) {
|
void MainWindow::AddRecentFiles(QString filePath) {
|
||||||
std::vector<std::string> vec = Config::getRecentFiles();
|
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles));
|
||||||
if (!vec.empty()) {
|
if (!list.empty()) {
|
||||||
if (filePath.toStdString() == vec.at(0)) {
|
if (filePath == list.at(0)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto it = std::find(vec.begin(), vec.end(), filePath.toStdString());
|
auto it = std::find(list.begin(), list.end(), filePath);
|
||||||
if (it != vec.end()) {
|
if (it != list.end()) {
|
||||||
vec.erase(it);
|
list.erase(it);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
vec.insert(vec.begin(), filePath.toStdString());
|
list.insert(list.begin(), filePath);
|
||||||
if (vec.size() > 6) {
|
if (list.size() > 6) {
|
||||||
vec.pop_back();
|
list.pop_back();
|
||||||
}
|
}
|
||||||
Config::setRecentFiles(vec);
|
m_gui_settings->SetValue(gui::gen_recentFiles, gui_settings::List2Var(list));
|
||||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
|
||||||
Config::saveMainWindow(config_dir / "config.toml");
|
|
||||||
CreateRecentGameActions(); // Refresh the QActions.
|
CreateRecentGameActions(); // Refresh the QActions.
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::CreateRecentGameActions() {
|
void MainWindow::CreateRecentGameActions() {
|
||||||
m_recent_files_group = new QActionGroup(this);
|
m_recent_files_group = new QActionGroup(this);
|
||||||
ui->menuRecent->clear();
|
ui->menuRecent->clear();
|
||||||
std::vector<std::string> vec = Config::getRecentFiles();
|
QList<QString> list = gui_settings::Var2List(m_gui_settings->GetValue(gui::gen_recentFiles));
|
||||||
for (int i = 0; i < vec.size(); i++) {
|
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
QAction* recentFileAct = new QAction(this);
|
QAction* recentFileAct = new QAction(this);
|
||||||
recentFileAct->setText(QString::fromStdString(vec.at(i)));
|
recentFileAct->setText(list.at(i));
|
||||||
ui->menuRecent->addAction(recentFileAct);
|
ui->menuRecent->addAction(recentFileAct);
|
||||||
m_recent_files_group->addAction(recentFileAct);
|
m_recent_files_group->addAction(recentFileAct);
|
||||||
}
|
}
|
||||||
@ -1165,7 +1162,7 @@ void MainWindow::CreateRecentGameActions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::LoadTranslation() {
|
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");
|
const QString base_dir = QStringLiteral(":/translations");
|
||||||
QString base_path = QStringLiteral("%1/%2.qm").arg(base_dir).arg(language);
|
QString base_path = QStringLiteral("%1/%2.qm").arg(base_dir).arg(language);
|
||||||
@ -1190,8 +1187,8 @@ void MainWindow::LoadTranslation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::OnLanguageChanged(const std::string& locale) {
|
void MainWindow::OnLanguageChanged(const QString& locale) {
|
||||||
Config::setEmulatorLanguage(locale);
|
m_gui_settings->SetValue(gui::gen_guiLanguage, locale);
|
||||||
|
|
||||||
LoadTranslation();
|
LoadTranslation();
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ private Q_SLOTS:
|
|||||||
void ShowGameList();
|
void ShowGameList();
|
||||||
void RefreshGameTable();
|
void RefreshGameTable();
|
||||||
void HandleResize(QResizeEvent* event);
|
void HandleResize(QResizeEvent* event);
|
||||||
void OnLanguageChanged(const std::string& locale);
|
void OnLanguageChanged(const QString& locale);
|
||||||
void toggleLabelsUnderIcons();
|
void toggleLabelsUnderIcons();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -75,3 +75,17 @@ void settings::SetValue(const QString& key, const QString& name, const QVariant&
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
QVariant settings::List2Var(const QList<QString>& list) {
|
||||||
|
QByteArray ba;
|
||||||
|
QDataStream stream(&ba, QIODevice::WriteOnly);
|
||||||
|
stream << list;
|
||||||
|
return QVariant(ba);
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<QString> settings::Var2List(const QVariant& var) {
|
||||||
|
QList<QString> list;
|
||||||
|
QByteArray ba = var.toByteArray();
|
||||||
|
QDataStream stream(&ba, QIODevice::ReadOnly);
|
||||||
|
stream >> list;
|
||||||
|
return list;
|
||||||
|
}
|
@ -35,6 +35,8 @@ public:
|
|||||||
|
|
||||||
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
|
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
|
||||||
QVariant GetValue(const gui_value& entry) 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:
|
public Q_SLOTS:
|
||||||
/** Remove entry */
|
/** Remove entry */
|
||||||
|
@ -123,11 +123,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
|
|||||||
ui->hideCursorComboBox->addItem(tr("Idle"));
|
ui->hideCursorComboBox->addItem(tr("Idle"));
|
||||||
ui->hideCursorComboBox->addItem(tr("Always"));
|
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();
|
InitializeEmulatorLanguages();
|
||||||
LoadValuesFromConfig();
|
LoadValuesFromConfig();
|
||||||
|
|
||||||
@ -366,7 +361,6 @@ SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
|
|||||||
// Input
|
// Input
|
||||||
ui->hideCursorGroupBox->installEventFilter(this);
|
ui->hideCursorGroupBox->installEventFilter(this);
|
||||||
ui->idleTimeoutGroupBox->installEventFilter(this);
|
ui->idleTimeoutGroupBox->installEventFilter(this);
|
||||||
ui->backButtonBehaviorGroupBox->installEventFilter(this);
|
|
||||||
|
|
||||||
// Graphics
|
// Graphics
|
||||||
ui->graphicsAdapterGroupBox->installEventFilter(this);
|
ui->graphicsAdapterGroupBox->installEventFilter(this);
|
||||||
@ -534,10 +528,6 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||||||
indexTab = 0;
|
indexTab = 0;
|
||||||
ui->tabWidgetSettings->setCurrentIndex(indexTab);
|
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(
|
ui->motionControlsCheckBox->setChecked(
|
||||||
toml::find_or<bool>(data, "Input", "isMotionControlsEnabled", true));
|
toml::find_or<bool>(data, "Input", "isMotionControlsEnabled", true));
|
||||||
|
|
||||||
@ -594,7 +584,7 @@ void SettingsDialog::OnLanguageChanged(int index) {
|
|||||||
|
|
||||||
ui->retranslateUi(this);
|
ui->retranslateUi(this);
|
||||||
|
|
||||||
emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString());
|
emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void SettingsDialog::OnCursorStateChanged(s16 index) {
|
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.");
|
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") {
|
} else if (elementName == "idleTimeoutGroupBox") {
|
||||||
text = tr("Hide Idle Cursor Timeout:\\nThe duration (seconds) after which the cursor that has been idle hides itself.");
|
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
|
// Graphics
|
||||||
@ -745,8 +733,6 @@ bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
|
|
||||||
void SettingsDialog::UpdateSettings() {
|
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()) !=
|
Config::setIsFullscreen(screenModeMap.value(ui->displayModeComboBox->currentText()) !=
|
||||||
"Windowed");
|
"Windowed");
|
||||||
Config::setFullscreenMode(
|
Config::setFullscreenMode(
|
||||||
@ -886,4 +872,5 @@ void SettingsDialog::setDefaultValues() {
|
|||||||
} else {
|
} else {
|
||||||
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
|
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
|
||||||
}
|
}
|
||||||
|
m_gui_settings->SetValue(gui::gen_guiLanguage, "en_US");
|
||||||
}
|
}
|
@ -32,7 +32,7 @@ public:
|
|||||||
int exec() override;
|
int exec() override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void LanguageChanged(const std::string& locale);
|
void LanguageChanged(const QString& locale);
|
||||||
void CompatibilityChanged();
|
void CompatibilityChanged();
|
||||||
void BackgroundOpacityChanged(int opacity);
|
void BackgroundOpacityChanged(int opacity);
|
||||||
|
|
||||||
|
@ -1613,36 +1613,6 @@
|
|||||||
<property name="bottomMargin">
|
<property name="bottomMargin">
|
||||||
<number>11</number>
|
<number>11</number>
|
||||||
</property>
|
</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>
|
<item>
|
||||||
<widget class="QCheckBox" name="motionControlsCheckBox">
|
<widget class="QCheckBox" name="motionControlsCheckBox">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -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)
|
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->setWindowTitle(tr("Trophy Viewer") + " - " + currentGameName_);
|
||||||
this->setAttribute(Qt::WA_DeleteOnClose);
|
this->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
tabWidget = new QTabWidget(this);
|
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" ||
|
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") {
|
lan == "lt_LT" || lan == "nb_NO" || lan == "nl_NL") {
|
||||||
useEuropeanDateFormat = false;
|
useEuropeanDateFormat = false;
|
||||||
@ -463,7 +465,7 @@ void TrophyViewer::SetTableItem(QTableWidget* parent, int row, int column, QStri
|
|||||||
item->setTextAlignment(Qt::AlignCenter);
|
item->setTextAlignment(Qt::AlignCenter);
|
||||||
item->setFont(QFont("Arial", 12, QFont::Bold));
|
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) {
|
if (theme == Theme::Light) {
|
||||||
item->setForeground(QBrush(Qt::black));
|
item->setForeground(QBrush(Qt::black));
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "core/file_format/trp.h"
|
#include "core/file_format/trp.h"
|
||||||
|
#include "gui_settings.h"
|
||||||
|
|
||||||
struct TrophyGameInfo {
|
struct TrophyGameInfo {
|
||||||
QString name;
|
QString name;
|
||||||
@ -34,7 +35,8 @@ class TrophyViewer : public QMainWindow {
|
|||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
explicit TrophyViewer(
|
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>());
|
const QVector<TrophyGameInfo>& allTrophyGames = QVector<TrophyGameInfo>());
|
||||||
|
|
||||||
void updateTrophyInfo();
|
void updateTrophyInfo();
|
||||||
@ -77,4 +79,5 @@ private:
|
|||||||
}
|
}
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
std::shared_ptr<gui_settings> m_gui_settings;
|
||||||
};
|
};
|
||||||
|
@ -474,11 +474,16 @@ void WindowSDL::OnKeyboardMouseInput(const SDL_Event* event) {
|
|||||||
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
Input::ParseInputConfig(std::string(Common::ElfInfo::Instance().GameSerial()));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Toggle mouse capture and movement input
|
// Toggle mouse capture and joystick input emulation
|
||||||
else if (input_id == SDLK_F7) {
|
else if (input_id == SDLK_F7) {
|
||||||
Input::ToggleMouseEnabled();
|
|
||||||
SDL_SetWindowRelativeMouseMode(this->GetSDLWindow(),
|
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;
|
return;
|
||||||
}
|
}
|
||||||
// Toggle fullscreen
|
// Toggle fullscreen
|
||||||
|
@ -271,7 +271,8 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct
|
|||||||
if (info.has_image_query) {
|
if (info.has_image_query) {
|
||||||
ctx.AddCapability(spv::Capability::ImageQuery);
|
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.AddExtension("SPV_EXT_shader_atomic_float_min_max");
|
||||||
ctx.AddCapability(spv::Capability::AtomicFloat32MinMaxEXT);
|
ctx.AddCapability(spv::Capability::AtomicFloat32MinMaxEXT);
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,17 @@ Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <bool is_float = false>
|
||||||
Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||||
const auto& buffer = ctx.buffers[handle];
|
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)) {
|
if (Sirit::ValidId(buffer.offset)) {
|
||||||
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
||||||
}
|
}
|
||||||
@ -60,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 auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
|
||||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||||
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
|
return AccessBoundsCheck<32, 1, is_float>(ctx, index, buffer.size_dwords, [&] {
|
||||||
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value);
|
return (ctx.*atomic_func)(type, ptr, scope, semantics, value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,6 +204,24 @@ Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMin);
|
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) {
|
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax);
|
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax);
|
||||||
}
|
}
|
||||||
@ -204,6 +230,24 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
|
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) {
|
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||||
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
|
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
|
||||||
}
|
}
|
||||||
|
@ -92,8 +92,10 @@ Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||||||
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
Id 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 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 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 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 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 EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||||
Id EmitBufferAtomicDec32(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);
|
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||||
|
@ -90,6 +90,10 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
|||||||
return BUFFER_ATOMIC(AtomicOp::Inc, inst);
|
return BUFFER_ATOMIC(AtomicOp::Inc, inst);
|
||||||
case Opcode::BUFFER_ATOMIC_DEC:
|
case Opcode::BUFFER_ATOMIC_DEC:
|
||||||
return BUFFER_ATOMIC(AtomicOp::Dec, inst);
|
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
|
// MIMG
|
||||||
// Image load operations
|
// Image load operations
|
||||||
@ -357,6 +361,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
|||||||
return ir.BufferAtomicInc(handle, address, buffer_info);
|
return ir.BufferAtomicInc(handle, address, buffer_info);
|
||||||
case AtomicOp::Dec:
|
case AtomicOp::Dec:
|
||||||
return ir.BufferAtomicDec(handle, address, buffer_info);
|
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:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,8 @@ struct Info {
|
|||||||
bool has_image_query{};
|
bool has_image_query{};
|
||||||
bool has_perspective_interp{};
|
bool has_perspective_interp{};
|
||||||
bool has_linear_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_lane_id{};
|
||||||
bool uses_group_quad{};
|
bool uses_group_quad{};
|
||||||
bool uses_group_ballot{};
|
bool uses_group_ballot{};
|
||||||
|
@ -504,12 +504,22 @@ Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, con
|
|||||||
: Inst(Opcode::BufferAtomicUMin32, Flags{info}, handle, address, value);
|
: 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,
|
Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, const Value& value,
|
||||||
bool is_signed, BufferInstInfo info) {
|
bool is_signed, BufferInstInfo info) {
|
||||||
return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value)
|
return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value)
|
||||||
: Inst(Opcode::BufferAtomicUMax32, 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) {
|
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address);
|
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address);
|
||||||
}
|
}
|
||||||
|
@ -140,8 +140,12 @@ public:
|
|||||||
const Value& value, BufferInstInfo info);
|
const Value& value, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address,
|
||||||
const Value& value, bool is_signed, BufferInstInfo info);
|
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,
|
[[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address,
|
||||||
const Value& value, bool is_signed, BufferInstInfo info);
|
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,
|
[[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address,
|
||||||
|
@ -71,8 +71,10 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
|||||||
case Opcode::BufferAtomicISub32:
|
case Opcode::BufferAtomicISub32:
|
||||||
case Opcode::BufferAtomicSMin32:
|
case Opcode::BufferAtomicSMin32:
|
||||||
case Opcode::BufferAtomicUMin32:
|
case Opcode::BufferAtomicUMin32:
|
||||||
|
case Opcode::BufferAtomicFMin32:
|
||||||
case Opcode::BufferAtomicSMax32:
|
case Opcode::BufferAtomicSMax32:
|
||||||
case Opcode::BufferAtomicUMax32:
|
case Opcode::BufferAtomicUMax32:
|
||||||
|
case Opcode::BufferAtomicFMax32:
|
||||||
case Opcode::BufferAtomicInc32:
|
case Opcode::BufferAtomicInc32:
|
||||||
case Opcode::BufferAtomicDec32:
|
case Opcode::BufferAtomicDec32:
|
||||||
case Opcode::BufferAtomicAnd32:
|
case Opcode::BufferAtomicAnd32:
|
||||||
|
@ -125,8 +125,10 @@ OPCODE(BufferAtomicIAdd64, U64, Opaq
|
|||||||
OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
||||||
|
OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 )
|
||||||
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
||||||
|
OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 )
|
||||||
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, )
|
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, )
|
||||||
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, )
|
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, )
|
||||||
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
||||||
|
@ -21,8 +21,10 @@ bool IsBufferAtomic(const IR::Inst& inst) {
|
|||||||
case IR::Opcode::BufferAtomicISub32:
|
case IR::Opcode::BufferAtomicISub32:
|
||||||
case IR::Opcode::BufferAtomicSMin32:
|
case IR::Opcode::BufferAtomicSMin32:
|
||||||
case IR::Opcode::BufferAtomicUMin32:
|
case IR::Opcode::BufferAtomicUMin32:
|
||||||
|
case IR::Opcode::BufferAtomicFMin32:
|
||||||
case IR::Opcode::BufferAtomicSMax32:
|
case IR::Opcode::BufferAtomicSMax32:
|
||||||
case IR::Opcode::BufferAtomicUMax32:
|
case IR::Opcode::BufferAtomicUMax32:
|
||||||
|
case IR::Opcode::BufferAtomicFMax32:
|
||||||
case IR::Opcode::BufferAtomicInc32:
|
case IR::Opcode::BufferAtomicInc32:
|
||||||
case IR::Opcode::BufferAtomicDec32:
|
case IR::Opcode::BufferAtomicDec32:
|
||||||
case IR::Opcode::BufferAtomicAnd32:
|
case IR::Opcode::BufferAtomicAnd32:
|
||||||
|
@ -92,7 +92,11 @@ void Visit(Info& info, const IR::Inst& inst) {
|
|||||||
break;
|
break;
|
||||||
case IR::Opcode::ImageAtomicFMax32:
|
case IR::Opcode::ImageAtomicFMax32:
|
||||||
case IR::Opcode::ImageAtomicFMin32:
|
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;
|
break;
|
||||||
case IR::Opcode::LaneId:
|
case IR::Opcode::LaneId:
|
||||||
info.uses_lane_id = true;
|
info.uses_lane_id = true;
|
||||||
|
@ -28,6 +28,7 @@ struct Profile {
|
|||||||
bool supports_native_cube_calc{};
|
bool supports_native_cube_calc{};
|
||||||
bool supports_trinary_minmax{};
|
bool supports_trinary_minmax{};
|
||||||
bool supports_robust_buffer_access{};
|
bool supports_robust_buffer_access{};
|
||||||
|
bool supports_buffer_fp32_atomic_min_max{};
|
||||||
bool supports_image_fp32_atomic_min_max{};
|
bool supports_image_fp32_atomic_min_max{};
|
||||||
bool supports_workgroup_explicit_memory_layout{};
|
bool supports_workgroup_explicit_memory_layout{};
|
||||||
bool has_broken_spirv_clamp{};
|
bool has_broken_spirv_clamp{};
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include "common/slot_vector.h"
|
#include "common/slot_vector.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "video_core/buffer_cache/buffer.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/buffer_cache/range_set.h"
|
||||||
#include "video_core/multi_level_page_table.h"
|
#include "video_core/multi_level_page_table.h"
|
||||||
|
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "video_core/buffer_cache/word_manager.h"
|
#include "video_core/buffer_cache/region_manager.h"
|
||||||
|
|
||||||
namespace VideoCore {
|
namespace VideoCore {
|
||||||
|
|
28
src/video_core/buffer_cache/region_definitions.h
Normal file
28
src/video_core/buffer_cache/region_definitions.h
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include "common/bit_array.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
constexpr u64 PAGES_PER_WORD = 64;
|
||||||
|
constexpr u64 BYTES_PER_PAGE = 4_KB;
|
||||||
|
|
||||||
|
constexpr u64 HIGHER_PAGE_BITS = 22;
|
||||||
|
constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS;
|
||||||
|
constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL;
|
||||||
|
constexpr u64 NUM_REGION_PAGES = HIGHER_PAGE_SIZE / BYTES_PER_PAGE;
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
CPU,
|
||||||
|
GPU,
|
||||||
|
Writeable,
|
||||||
|
};
|
||||||
|
|
||||||
|
using RegionBits = Common::BitArray<NUM_REGION_PAGES>;
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
208
src/video_core/buffer_cache/region_manager.h
Normal file
208
src/video_core/buffer_cache/region_manager.h
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <utility>
|
||||||
|
#include "common/div_ceil.h"
|
||||||
|
|
||||||
|
#ifdef __linux__
|
||||||
|
#include "common/adaptive_mutex.h"
|
||||||
|
#else
|
||||||
|
#include "common/spin_lock.h"
|
||||||
|
#endif
|
||||||
|
#include "common/debug.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
#include "video_core/buffer_cache/region_definitions.h"
|
||||||
|
#include "video_core/page_manager.h"
|
||||||
|
|
||||||
|
namespace VideoCore {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region.
|
||||||
|
* Information is stored in bitsets for spacial locality and fast update of single pages.
|
||||||
|
*/
|
||||||
|
class RegionManager {
|
||||||
|
public:
|
||||||
|
explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_)
|
||||||
|
: tracker{tracker_}, cpu_addr{cpu_addr_} {
|
||||||
|
cpu.Fill();
|
||||||
|
gpu.Clear();
|
||||||
|
writeable.Fill();
|
||||||
|
}
|
||||||
|
explicit RegionManager() = default;
|
||||||
|
|
||||||
|
void SetCpuAddress(VAddr new_cpu_addr) {
|
||||||
|
cpu_addr = new_cpu_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr GetCpuAddr() const {
|
||||||
|
return cpu_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr size_t SanitizeAddress(size_t address) {
|
||||||
|
return static_cast<size_t>(std::max<s64>(static_cast<s64>(address), 0LL));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Type type>
|
||||||
|
RegionBits& GetRegionBits() noexcept {
|
||||||
|
static_assert(type != Type::Writeable);
|
||||||
|
if constexpr (type == Type::CPU) {
|
||||||
|
return cpu;
|
||||||
|
} else if constexpr (type == Type::GPU) {
|
||||||
|
return gpu;
|
||||||
|
} else if constexpr (type == Type::Writeable) {
|
||||||
|
return writeable;
|
||||||
|
} else {
|
||||||
|
static_assert(false, "Invalid type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <Type type>
|
||||||
|
const RegionBits& GetRegionBits() const noexcept {
|
||||||
|
static_assert(type != Type::Writeable);
|
||||||
|
if constexpr (type == Type::CPU) {
|
||||||
|
return cpu;
|
||||||
|
} else if constexpr (type == Type::GPU) {
|
||||||
|
return gpu;
|
||||||
|
} else if constexpr (type == Type::Writeable) {
|
||||||
|
return writeable;
|
||||||
|
} else {
|
||||||
|
static_assert(false, "Invalid type");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the state of a range of pages
|
||||||
|
*
|
||||||
|
* @param dirty_addr Base address to mark or unmark as modified
|
||||||
|
* @param size Size in bytes to mark or unmark as modified
|
||||||
|
*/
|
||||||
|
template <Type type, bool enable>
|
||||||
|
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
const size_t offset = dirty_addr - cpu_addr;
|
||||||
|
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE;
|
||||||
|
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE);
|
||||||
|
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::scoped_lock lk{lock};
|
||||||
|
static_assert(type != Type::Writeable);
|
||||||
|
|
||||||
|
RegionBits& bits = GetRegionBits<type>();
|
||||||
|
if constexpr (enable) {
|
||||||
|
bits.SetRange(start_page, end_page);
|
||||||
|
} else {
|
||||||
|
bits.UnsetRange(start_page, end_page);
|
||||||
|
}
|
||||||
|
if constexpr (type == Type::CPU) {
|
||||||
|
UpdateProtection<!enable>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loop over each page in the given range, turn off those bits and notify the tracker if
|
||||||
|
* needed. Call the given function on each turned off range.
|
||||||
|
*
|
||||||
|
* @param query_cpu_range Base CPU address to loop over
|
||||||
|
* @param size Size in bytes of the CPU range to loop over
|
||||||
|
* @param func Function to call for each turned off region
|
||||||
|
*/
|
||||||
|
template <Type type, bool clear>
|
||||||
|
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
const size_t offset = query_cpu_range - cpu_addr;
|
||||||
|
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE;
|
||||||
|
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE);
|
||||||
|
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::scoped_lock lk{lock};
|
||||||
|
static_assert(type != Type::Writeable);
|
||||||
|
|
||||||
|
RegionBits& bits = GetRegionBits<type>();
|
||||||
|
RegionBits mask(bits, start_page, end_page);
|
||||||
|
|
||||||
|
// TODO: this will not be needed once we handle readbacks
|
||||||
|
if constexpr (type == Type::GPU) {
|
||||||
|
mask &= ~writeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [start, end] : mask) {
|
||||||
|
func(cpu_addr + start * BYTES_PER_PAGE, (end - start) * BYTES_PER_PAGE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (clear) {
|
||||||
|
bits.UnsetRange(start_page, end_page);
|
||||||
|
if constexpr (type == Type::CPU) {
|
||||||
|
UpdateProtection<true>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true when a region has been modified
|
||||||
|
*
|
||||||
|
* @param offset Offset in bytes from the start of the buffer
|
||||||
|
* @param size Size in bytes of the region to query for modifications
|
||||||
|
*/
|
||||||
|
template <Type type>
|
||||||
|
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE;
|
||||||
|
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE);
|
||||||
|
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// std::scoped_lock lk{lock}; // Is this needed?
|
||||||
|
static_assert(type != Type::Writeable);
|
||||||
|
|
||||||
|
const RegionBits& bits = GetRegionBits<type>();
|
||||||
|
RegionBits test(bits, start_page, end_page);
|
||||||
|
|
||||||
|
// TODO: this will not be needed once we handle readbacks
|
||||||
|
if constexpr (type == Type::GPU) {
|
||||||
|
test &= ~writeable;
|
||||||
|
}
|
||||||
|
|
||||||
|
return test.Any();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* Notify tracker about changes in the CPU tracking state of a word in the buffer
|
||||||
|
*
|
||||||
|
* @param word_index Index to the word to notify to the tracker
|
||||||
|
* @param current_bits Current state of the word
|
||||||
|
* @param new_bits New state of the word
|
||||||
|
*
|
||||||
|
* @tparam add_to_tracker True when the tracker should start tracking the new pages
|
||||||
|
*/
|
||||||
|
template <bool add_to_tracker>
|
||||||
|
void UpdateProtection() {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
RegionBits mask = cpu ^ writeable;
|
||||||
|
|
||||||
|
if (mask.None()) {
|
||||||
|
return; // No changes to the CPU tracking state
|
||||||
|
}
|
||||||
|
|
||||||
|
writeable = cpu;
|
||||||
|
tracker->UpdatePageWatchersForRegion<add_to_tracker>(cpu_addr, mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
|
||||||
|
Common::AdaptiveMutex lock;
|
||||||
|
#else
|
||||||
|
Common::SpinLock lock;
|
||||||
|
#endif
|
||||||
|
PageManager* tracker;
|
||||||
|
VAddr cpu_addr = 0;
|
||||||
|
RegionBits cpu;
|
||||||
|
RegionBits gpu;
|
||||||
|
RegionBits writeable;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace VideoCore
|
@ -1,296 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <mutex>
|
|
||||||
#include <span>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
#ifdef __linux__
|
|
||||||
#include "common/adaptive_mutex.h"
|
|
||||||
#else
|
|
||||||
#include "common/spin_lock.h"
|
|
||||||
#endif
|
|
||||||
#include "common/debug.h"
|
|
||||||
#include "common/types.h"
|
|
||||||
#include "video_core/page_manager.h"
|
|
||||||
|
|
||||||
namespace VideoCore {
|
|
||||||
|
|
||||||
constexpr u64 PAGES_PER_WORD = 64;
|
|
||||||
constexpr u64 BYTES_PER_PAGE = 4_KB;
|
|
||||||
constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
|
|
||||||
|
|
||||||
constexpr u64 HIGHER_PAGE_BITS = 22;
|
|
||||||
constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS;
|
|
||||||
constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL;
|
|
||||||
constexpr u64 NUM_REGION_WORDS = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
|
|
||||||
|
|
||||||
enum class Type {
|
|
||||||
CPU,
|
|
||||||
GPU,
|
|
||||||
Untracked,
|
|
||||||
};
|
|
||||||
|
|
||||||
using WordsArray = std::array<u64, NUM_REGION_WORDS>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region.
|
|
||||||
* Information is stored in bitsets for spacial locality and fast update of single pages.
|
|
||||||
*/
|
|
||||||
class RegionManager {
|
|
||||||
public:
|
|
||||||
explicit RegionManager(PageManager* tracker_, VAddr cpu_addr_)
|
|
||||||
: tracker{tracker_}, cpu_addr{cpu_addr_} {
|
|
||||||
cpu.fill(~u64{0});
|
|
||||||
gpu.fill(0);
|
|
||||||
untracked.fill(~u64{0});
|
|
||||||
}
|
|
||||||
explicit RegionManager() = default;
|
|
||||||
|
|
||||||
void SetCpuAddress(VAddr new_cpu_addr) {
|
|
||||||
cpu_addr = new_cpu_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
VAddr GetCpuAddr() const {
|
|
||||||
return cpu_addr;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
|
|
||||||
constexpr size_t number_bits = sizeof(u64) * 8;
|
|
||||||
const size_t limit_page_end = number_bits - std::min(page_end, number_bits);
|
|
||||||
u64 bits = (word >> page_start) << page_start;
|
|
||||||
bits = (bits << limit_page_end) >> limit_page_end;
|
|
||||||
return bits;
|
|
||||||
}
|
|
||||||
|
|
||||||
static constexpr std::pair<size_t, size_t> GetWordPage(VAddr address) {
|
|
||||||
const size_t converted_address = static_cast<size_t>(address);
|
|
||||||
const size_t word_number = converted_address / BYTES_PER_WORD;
|
|
||||||
const size_t amount_pages = converted_address % BYTES_PER_WORD;
|
|
||||||
return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename Func>
|
|
||||||
void IterateWords(size_t offset, size_t size, Func&& func) const {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
|
|
||||||
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
|
|
||||||
const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
|
|
||||||
const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
|
|
||||||
if (start >= HIGHER_PAGE_SIZE || end <= start) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto [start_word, start_page] = GetWordPage(start);
|
|
||||||
auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
|
|
||||||
constexpr size_t num_words = NUM_REGION_WORDS;
|
|
||||||
start_word = std::min(start_word, num_words);
|
|
||||||
end_word = std::min(end_word, num_words);
|
|
||||||
const size_t diff = end_word - start_word;
|
|
||||||
end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
|
|
||||||
end_word = std::min(end_word, num_words);
|
|
||||||
end_page += diff * PAGES_PER_WORD;
|
|
||||||
constexpr u64 base_mask{~0ULL};
|
|
||||||
for (size_t word_index = start_word; word_index < end_word; word_index++) {
|
|
||||||
const u64 mask = ExtractBits(base_mask, start_page, end_page);
|
|
||||||
start_page = 0;
|
|
||||||
end_page -= PAGES_PER_WORD;
|
|
||||||
if constexpr (BOOL_BREAK) {
|
|
||||||
if (func(word_index, mask)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
func(word_index, mask);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void IteratePages(u64 mask, auto&& func) const {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
size_t offset = 0;
|
|
||||||
while (mask != 0) {
|
|
||||||
const size_t empty_bits = std::countr_zero(mask);
|
|
||||||
offset += empty_bits;
|
|
||||||
mask >>= empty_bits;
|
|
||||||
|
|
||||||
const size_t continuous_bits = std::countr_one(mask);
|
|
||||||
func(offset, continuous_bits);
|
|
||||||
mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0;
|
|
||||||
offset += continuous_bits;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the state of a range of pages
|
|
||||||
*
|
|
||||||
* @param dirty_addr Base address to mark or unmark as modified
|
|
||||||
* @param size Size in bytes to mark or unmark as modified
|
|
||||||
*/
|
|
||||||
template <Type type, bool enable>
|
|
||||||
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
|
|
||||||
std::scoped_lock lk{lock};
|
|
||||||
std::span<u64> state_words = Span<type>();
|
|
||||||
IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
UpdateProtection<!enable>(index, untracked[index], mask);
|
|
||||||
}
|
|
||||||
if constexpr (enable) {
|
|
||||||
state_words[index] |= mask;
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
untracked[index] |= mask;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
state_words[index] &= ~mask;
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
untracked[index] &= ~mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loop over each page in the given range, turn off those bits and notify the tracker if
|
|
||||||
* needed. Call the given function on each turned off range.
|
|
||||||
*
|
|
||||||
* @param query_cpu_range Base CPU address to loop over
|
|
||||||
* @param size Size in bytes of the CPU range to loop over
|
|
||||||
* @param func Function to call for each turned off region
|
|
||||||
*/
|
|
||||||
template <Type type, bool clear>
|
|
||||||
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
std::scoped_lock lk{lock};
|
|
||||||
static_assert(type != Type::Untracked);
|
|
||||||
|
|
||||||
std::span<u64> state_words = Span<type>();
|
|
||||||
const size_t offset = query_cpu_range - cpu_addr;
|
|
||||||
bool pending = false;
|
|
||||||
size_t pending_offset{};
|
|
||||||
size_t pending_pointer{};
|
|
||||||
const auto release = [&]() {
|
|
||||||
func(cpu_addr + pending_offset * BYTES_PER_PAGE,
|
|
||||||
(pending_pointer - pending_offset) * BYTES_PER_PAGE);
|
|
||||||
};
|
|
||||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
if constexpr (type == Type::GPU) {
|
|
||||||
mask &= ~untracked[index];
|
|
||||||
}
|
|
||||||
const u64 word = state_words[index] & mask;
|
|
||||||
if constexpr (clear) {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
UpdateProtection<true>(index, untracked[index], mask);
|
|
||||||
untracked[index] &= ~mask;
|
|
||||||
}
|
|
||||||
state_words[index] &= ~mask;
|
|
||||||
}
|
|
||||||
const size_t base_offset = index * PAGES_PER_WORD;
|
|
||||||
IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
const auto reset = [&]() {
|
|
||||||
pending_offset = base_offset + pages_offset;
|
|
||||||
pending_pointer = base_offset + pages_offset + pages_size;
|
|
||||||
};
|
|
||||||
if (!pending) {
|
|
||||||
reset();
|
|
||||||
pending = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (pending_pointer == base_offset + pages_offset) {
|
|
||||||
pending_pointer += pages_size;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
release();
|
|
||||||
reset();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (pending) {
|
|
||||||
release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when a region has been modified
|
|
||||||
*
|
|
||||||
* @param offset Offset in bytes from the start of the buffer
|
|
||||||
* @param size Size in bytes of the region to query for modifications
|
|
||||||
*/
|
|
||||||
template <Type type>
|
|
||||||
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
|
|
||||||
static_assert(type != Type::Untracked);
|
|
||||||
|
|
||||||
const std::span<const u64> state_words = Span<type>();
|
|
||||||
bool result = false;
|
|
||||||
IterateWords(offset, size, [&](size_t index, u64 mask) {
|
|
||||||
if constexpr (type == Type::GPU) {
|
|
||||||
mask &= ~untracked[index];
|
|
||||||
}
|
|
||||||
const u64 word = state_words[index] & mask;
|
|
||||||
if (word != 0) {
|
|
||||||
result = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
/**
|
|
||||||
* Notify tracker about changes in the CPU tracking state of a word in the buffer
|
|
||||||
*
|
|
||||||
* @param word_index Index to the word to notify to the tracker
|
|
||||||
* @param current_bits Current state of the word
|
|
||||||
* @param new_bits New state of the word
|
|
||||||
*
|
|
||||||
* @tparam add_to_tracker True when the tracker should start tracking the new pages
|
|
||||||
*/
|
|
||||||
template <bool add_to_tracker>
|
|
||||||
void UpdateProtection(u64 word_index, u64 current_bits, u64 new_bits) const {
|
|
||||||
RENDERER_TRACE;
|
|
||||||
constexpr s32 delta = add_to_tracker ? 1 : -1;
|
|
||||||
u64 changed_bits = (add_to_tracker ? current_bits : ~current_bits) & new_bits;
|
|
||||||
VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
|
|
||||||
IteratePages(changed_bits, [&](size_t offset, size_t size) {
|
|
||||||
tracker->UpdatePageWatchers<delta>(addr + offset * BYTES_PER_PAGE,
|
|
||||||
size * BYTES_PER_PAGE);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Type type>
|
|
||||||
std::span<u64> Span() noexcept {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
return cpu;
|
|
||||||
} else if constexpr (type == Type::GPU) {
|
|
||||||
return gpu;
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return untracked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template <Type type>
|
|
||||||
std::span<const u64> Span() const noexcept {
|
|
||||||
if constexpr (type == Type::CPU) {
|
|
||||||
return cpu;
|
|
||||||
} else if constexpr (type == Type::GPU) {
|
|
||||||
return gpu;
|
|
||||||
} else if constexpr (type == Type::Untracked) {
|
|
||||||
return untracked;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
|
|
||||||
Common::AdaptiveMutex lock;
|
|
||||||
#else
|
|
||||||
Common::SpinLock lock;
|
|
||||||
#endif
|
|
||||||
PageManager* tracker;
|
|
||||||
VAddr cpu_addr = 0;
|
|
||||||
WordsArray cpu;
|
|
||||||
WordsArray gpu;
|
|
||||||
WordsArray untracked;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace VideoCore
|
|
@ -48,19 +48,15 @@ struct PageManager::Impl {
|
|||||||
u8 AddDelta() {
|
u8 AddDelta() {
|
||||||
if constexpr (delta == 1) {
|
if constexpr (delta == 1) {
|
||||||
return ++num_watchers;
|
return ++num_watchers;
|
||||||
} else {
|
} else if constexpr (delta == -1) {
|
||||||
ASSERT_MSG(num_watchers > 0, "Not enough watchers");
|
ASSERT_MSG(num_watchers > 0, "Not enough watchers");
|
||||||
return --num_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 ADDRESS_BITS = 40;
|
||||||
static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS);
|
static constexpr size_t NUM_ADDRESS_PAGES = 1ULL << (40 - PAGE_BITS);
|
||||||
inline static Vulkan::Rasterizer* rasterizer;
|
inline static Vulkan::Rasterizer* rasterizer;
|
||||||
@ -190,66 +186,122 @@ struct PageManager::Impl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
template <s32 delta>
|
template <bool track>
|
||||||
void UpdatePageWatchers(VAddr addr, u64 size) {
|
void UpdatePageWatchers(VAddr addr, u64 size) {
|
||||||
RENDERER_TRACE;
|
RENDERER_TRACE;
|
||||||
boost::container::small_vector<UpdateProtectRange, 16> update_ranges;
|
|
||||||
{
|
|
||||||
std::scoped_lock lk(lock);
|
|
||||||
|
|
||||||
size_t page = addr >> PAGE_BITS;
|
size_t page = addr >> PAGE_BITS;
|
||||||
auto perms = cached_pages[page].Perm();
|
auto perms = cached_pages[page].Perm();
|
||||||
u64 range_begin = 0;
|
u64 range_begin = 0;
|
||||||
u64 range_bytes = 0;
|
u64 range_bytes = 0;
|
||||||
|
|
||||||
const auto release_pending = [&] {
|
const auto release_pending = [&] {
|
||||||
if (range_bytes > 0) {
|
if (range_bytes > 0) {
|
||||||
RENDERER_TRACE;
|
RENDERER_TRACE;
|
||||||
// Add pending (un)protect action
|
// Perform pending (un)protect action
|
||||||
update_ranges.push_back({range_begin << PAGE_BITS, range_bytes, perms});
|
Protect(range_begin << PAGE_BITS, range_bytes, perms);
|
||||||
range_bytes = 0;
|
range_bytes = 0;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Iterate requested pages
|
std::scoped_lock lk(lock);
|
||||||
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);
|
|
||||||
|
|
||||||
for (; page != page_end; ++page) {
|
// Iterate requested pages
|
||||||
PageState& state = cached_pages[page];
|
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
|
for (; page != page_end; ++page) {
|
||||||
const u8 new_count = state.AddDelta<delta>();
|
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 the protection changed add pending (un)protect action
|
||||||
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] {
|
release_pending();
|
||||||
release_pending();
|
perms = new_perms;
|
||||||
perms = new_perms;
|
} else if (range_bytes != 0) {
|
||||||
}
|
// If the protection did not change, extend the current range
|
||||||
|
range_bytes += PAGE_SIZE;
|
||||||
// If the page must be (un)protected, add it to the pending range
|
|
||||||
if ((new_count == 0 && delta < 0) || (new_count == 1 && delta > 0)) {
|
|
||||||
if (range_bytes == 0) {
|
|
||||||
range_begin = page;
|
|
||||||
}
|
|
||||||
range_bytes += PAGE_SIZE;
|
|
||||||
} else {
|
|
||||||
release_pending();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add pending (un)protect action
|
// Only start a new range if the page must be (un)protected
|
||||||
release_pending();
|
if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) {
|
||||||
|
range_begin = page;
|
||||||
|
range_bytes = PAGE_SIZE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush deferred protects
|
// Add pending (un)protect action
|
||||||
for (const auto& range : update_ranges) {
|
release_pending();
|
||||||
Protect(range.addr, range.size, range.perms);
|
}
|
||||||
|
|
||||||
|
template <bool track>
|
||||||
|
void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
auto start_range = mask.FirstRange();
|
||||||
|
auto end_range = mask.LastRange();
|
||||||
|
|
||||||
|
if (start_range.second == end_range.second) {
|
||||||
|
// Optimization: if all pages are contiguous, use the regular UpdatePageWatchers
|
||||||
|
const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS);
|
||||||
|
const u64 size = (start_range.second - start_range.first) << PAGE_BITS;
|
||||||
|
|
||||||
|
UpdatePageWatchers<track>(start_addr, size);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t base_page = (base_addr >> PAGE_BITS);
|
||||||
|
auto perms = cached_pages[base_page + start_range.first].Perm();
|
||||||
|
u64 range_begin = 0;
|
||||||
|
u64 range_bytes = 0;
|
||||||
|
|
||||||
|
const auto release_pending = [&] {
|
||||||
|
if (range_bytes > 0) {
|
||||||
|
RENDERER_TRACE;
|
||||||
|
// Perform pending (un)protect action
|
||||||
|
Protect((range_begin << PAGE_BITS), range_bytes, perms);
|
||||||
|
range_bytes = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::scoped_lock lk(lock);
|
||||||
|
|
||||||
|
// Iterate pages
|
||||||
|
for (size_t page = start_range.first; page < end_range.second; ++page) {
|
||||||
|
PageState& state = cached_pages[base_page + page];
|
||||||
|
const bool update = mask.Get(page);
|
||||||
|
|
||||||
|
// Apply the change to the page state
|
||||||
|
const u8 new_count = update ? state.AddDelta<track ? 1 : -1>() : state.AddDelta<0>();
|
||||||
|
|
||||||
|
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] {
|
||||||
|
// If the protection changed add pending (un)protect action
|
||||||
|
release_pending();
|
||||||
|
perms = new_perms;
|
||||||
|
} else if (range_bytes != 0) {
|
||||||
|
// If the protection did not change, extend the current range
|
||||||
|
range_bytes += PAGE_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the page is not being updated, skip it
|
||||||
|
if (!update) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only start a new range if the page must be (un)protected
|
||||||
|
if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) {
|
||||||
|
range_begin = base_page + page;
|
||||||
|
range_bytes = PAGE_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add pending (un)protect action
|
||||||
|
release_pending();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::array<PageState, NUM_ADDRESS_PAGES> cached_pages{};
|
std::array<PageState, NUM_ADDRESS_PAGES> cached_pages{};
|
||||||
@ -273,12 +325,21 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) {
|
|||||||
impl->OnUnmap(address, size);
|
impl->OnUnmap(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <s32 delta>
|
template <bool track>
|
||||||
void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const {
|
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 <bool track>
|
||||||
template void PageManager::UpdatePageWatchers<-1>(VAddr addr, u64 size) const;
|
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
|
} // namespace VideoCore
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "video_core/buffer_cache//region_definitions.h"
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
class Rasterizer;
|
class Rasterizer;
|
||||||
@ -28,9 +29,14 @@ public:
|
|||||||
void OnGpuUnmap(VAddr address, size_t size);
|
void OnGpuUnmap(VAddr address, size_t size);
|
||||||
|
|
||||||
/// Updates watches in the pages touching the specified region.
|
/// Updates watches in the pages touching the specified region.
|
||||||
template <s32 delta>
|
template <bool track>
|
||||||
void UpdatePageWatchers(VAddr addr, u64 size) const;
|
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.
|
/// Returns page aligned address.
|
||||||
static constexpr VAddr GetPageAddr(VAddr addr) {
|
static constexpr VAddr GetPageAddr(VAddr addr) {
|
||||||
return Common::AlignDown(addr, PAGE_SIZE);
|
return Common::AlignDown(addr, PAGE_SIZE);
|
||||||
|
@ -281,6 +281,8 @@ bool Instance::CreateDevice() {
|
|||||||
if (shader_atomic_float2) {
|
if (shader_atomic_float2) {
|
||||||
shader_atomic_float2_features =
|
shader_atomic_float2_features =
|
||||||
feature_chain.get<vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT>();
|
feature_chain.get<vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT>();
|
||||||
|
LOG_INFO(Render_Vulkan, "- shaderBufferFloat32AtomicMinMax: {}",
|
||||||
|
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax);
|
||||||
LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}",
|
LOG_INFO(Render_Vulkan, "- shaderImageFloat32AtomicMinMax: {}",
|
||||||
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax);
|
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax);
|
||||||
}
|
}
|
||||||
@ -433,6 +435,8 @@ bool Instance::CreateDevice() {
|
|||||||
.legacyVertexAttributes = true,
|
.legacyVertexAttributes = true,
|
||||||
},
|
},
|
||||||
vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{
|
vk::PhysicalDeviceShaderAtomicFloat2FeaturesEXT{
|
||||||
|
.shaderBufferFloat32AtomicMinMax =
|
||||||
|
shader_atomic_float2_features.shaderBufferFloat32AtomicMinMax,
|
||||||
.shaderImageFloat32AtomicMinMax =
|
.shaderImageFloat32AtomicMinMax =
|
||||||
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax,
|
shader_atomic_float2_features.shaderImageFloat32AtomicMinMax,
|
||||||
},
|
},
|
||||||
|
@ -165,6 +165,13 @@ public:
|
|||||||
return amd_shader_trinary_minmax;
|
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
|
/// Returns true when the shaderImageFloat32AtomicMinMax feature of
|
||||||
/// VK_EXT_shader_atomic_float2 is supported.
|
/// VK_EXT_shader_atomic_float2 is supported.
|
||||||
bool IsShaderAtomicFloatImage32MinMaxSupported() const {
|
bool IsShaderAtomicFloatImage32MinMaxSupported() const {
|
||||||
@ -324,6 +331,9 @@ public:
|
|||||||
return driver_id != vk::DriverId::eMoltenvk;
|
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:
|
private:
|
||||||
/// Creates the logical device opportunistically enabling extensions
|
/// Creates the logical device opportunistically enabling extensions
|
||||||
bool CreateDevice();
|
bool CreateDevice();
|
||||||
@ -338,9 +348,6 @@ private:
|
|||||||
/// Gets the supported feature flags for a format.
|
/// Gets the supported feature flags for a format.
|
||||||
[[nodiscard]] vk::FormatFeatureFlags2 GetFormatFeatureFlags(vk::Format format) const;
|
[[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:
|
private:
|
||||||
vk::UniqueInstance instance;
|
vk::UniqueInstance instance;
|
||||||
vk::PhysicalDevice physical_device;
|
vk::PhysicalDevice physical_device;
|
||||||
|
@ -216,6 +216,8 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
|||||||
.supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(),
|
.supports_trinary_minmax = instance_.IsAmdShaderTrinaryMinMaxSupported(),
|
||||||
// TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed.
|
// TODO: Emitted bounds checks cause problems with phi control flow; needs to be fixed.
|
||||||
.supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(),
|
.supports_robust_buffer_access = true, // instance_.IsRobustBufferAccess2Supported(),
|
||||||
|
.supports_buffer_fp32_atomic_min_max =
|
||||||
|
instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(),
|
||||||
.supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(),
|
.supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(),
|
||||||
.supports_workgroup_explicit_memory_layout =
|
.supports_workgroup_explicit_memory_layout =
|
||||||
instance_.IsWorkgroupMemoryExplicitLayoutSupported(),
|
instance_.IsWorkgroupMemoryExplicitLayoutSupported(),
|
||||||
@ -346,8 +348,15 @@ bool PipelineCache::RefreshGraphicsKey() {
|
|||||||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 ||
|
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8 ||
|
||||||
col_buf.GetDataFmt() == AmdGpu::DataFormat::Format8_8_8_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());
|
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{
|
key.color_buffers[remapped_cb] = Shader::PsColorBuffer{
|
||||||
.num_format = col_buf.GetNumberFmt(),
|
.num_format = col_buf.GetNumberFmt(),
|
||||||
.num_conversion = col_buf.GetNumberConversion(),
|
.num_conversion = col_buf.GetNumberConversion(),
|
||||||
|
@ -130,11 +130,24 @@ Image::Image(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
|||||||
|
|
||||||
constexpr auto tiling = vk::ImageTiling::eOptimal;
|
constexpr auto tiling = vk::ImageTiling::eOptimal;
|
||||||
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
|
const auto supported_format = instance->GetSupportedFormat(info.pixel_format, format_features);
|
||||||
const auto properties = instance->GetPhysicalDevice().getImageFormatProperties(
|
const vk::PhysicalDeviceImageFormatInfo2 format_info{
|
||||||
supported_format, info.type, tiling, usage_flags, flags);
|
.format = supported_format,
|
||||||
const auto supported_samples = properties.result == vk::Result::eSuccess
|
.type = info.type,
|
||||||
? properties.value.sampleCounts
|
.tiling = tiling,
|
||||||
: vk::SampleCountFlagBits::e1;
|
.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 = {
|
const vk::ImageCreateInfo image_ci = {
|
||||||
.flags = flags,
|
.flags = flags,
|
||||||
|
@ -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
|
ImageViewInfo::ImageViewInfo(const AmdGpu::Image& image, const Shader::ImageResource& desc) noexcept
|
||||||
: is_storage{desc.is_written} {
|
: is_storage{desc.is_written} {
|
||||||
const auto dfmt = image.GetDataFmt();
|
const auto dfmt = image.GetDataFmt();
|
||||||
@ -106,6 +124,11 @@ ImageView::ImageView(const Vulkan::Instance& instance, const ImageViewInfo& info
|
|||||||
.layerCount = info.range.extent.layers,
|
.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);
|
auto [view_result, view] = instance.GetDevice().createImageViewUnique(image_view_ci);
|
||||||
ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}",
|
ASSERT_MSG(view_result == vk::Result::eSuccess, "Failed to create image view: {}",
|
||||||
vk::to_string(view_result));
|
vk::to_string(view_result));
|
||||||
|
@ -761,7 +761,7 @@ void TextureCache::UntrackImage(ImageId image_id) {
|
|||||||
image.track_addr = 0;
|
image.track_addr = 0;
|
||||||
image.track_addr_end = 0;
|
image.track_addr_end = 0;
|
||||||
if (size != 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.
|
// Cehck its hash later.
|
||||||
MarkAsMaybeDirty(image_id, image);
|
MarkAsMaybeDirty(image_id, image);
|
||||||
}
|
}
|
||||||
tracker.UpdatePageWatchers<-1>(image_begin, size);
|
tracker.UpdatePageWatchers<false>(image_begin, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::UntrackImageTail(ImageId image_id) {
|
void TextureCache::UntrackImageTail(ImageId image_id) {
|
||||||
@ -799,7 +799,7 @@ void TextureCache::UntrackImageTail(ImageId image_id) {
|
|||||||
// Cehck its hash later.
|
// Cehck its hash later.
|
||||||
MarkAsMaybeDirty(image_id, image);
|
MarkAsMaybeDirty(image_id, image);
|
||||||
}
|
}
|
||||||
tracker.UpdatePageWatchers<-1>(addr, size);
|
tracker.UpdatePageWatchers<false>(addr, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextureCache::DeleteImage(ImageId image_id) {
|
void TextureCache::DeleteImage(ImageId image_id) {
|
||||||
|
Loading…
Reference in New Issue
Block a user