Merge branch 'shadps4-emu:main' into v_cmp_u64-support

This commit is contained in:
nickci2002 2025-07-02 11:08:35 -04:00 committed by GitHub
commit c5c7546552
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
97 changed files with 5134 additions and 2609 deletions

View File

@ -35,7 +35,7 @@ body:
required: true required: true
- label: I have disabled all patches and cheats and the issue is still present. - label: I have disabled all patches and cheats and the issue is still present.
required: true required: true
- label: I have all the required [system modules](https://github.com/shadps4-emu/shadps4-game-compatibility?tab=readme-ov-file#informations) installed. - label: I have all the required [system modules](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D#4-adding-modules) installed.
required: true required: true
- type: textarea - type: textarea
id: desc id: desc

21
CMakeLinuxPresets.json Normal file
View File

@ -0,0 +1,21 @@
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"configurePresets": [
{
"name": "x64-Clang-Base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Build/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang",
"CMAKE_CXX_COMPILER": "clang++",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}"
}
}
]
}

View File

@ -683,11 +683,13 @@ set(COMMON src/common/logging/backend.cpp
src/common/path_util.h src/common/path_util.h
src/common/object_pool.h src/common/object_pool.h
src/common/polyfill_thread.h src/common/polyfill_thread.h
src/common/range_lock.h
src/common/rdtsc.cpp src/common/rdtsc.cpp
src/common/rdtsc.h src/common/rdtsc.h
src/common/recursive_lock.cpp src/common/recursive_lock.cpp
src/common/recursive_lock.h src/common/recursive_lock.h
src/common/sha1.h src/common/sha1.h
src/common/shared_first_mutex.h
src/common/signal_context.h src/common/signal_context.h
src/common/signal_context.cpp src/common/signal_context.cpp
src/common/singleton.h src/common/singleton.h
@ -1067,6 +1069,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/gui_settings.h src/qt_gui/gui_settings.h
src/qt_gui/settings.cpp src/qt_gui/settings.cpp
src/qt_gui/settings.h src/qt_gui/settings.h
src/qt_gui/sdl_event_wrapper.cpp
src/qt_gui/sdl_event_wrapper.h
${EMULATOR} ${EMULATOR}
${RESOURCE_FILES} ${RESOURCE_FILES}
${TRANSLATIONS} ${TRANSLATIONS}

View File

@ -1,27 +1,12 @@
{ {
"version": 5, "version": 9,
"cmakeMinimumRequired": { "cmakeMinimumRequired": {
"major": 3, "major": 3,
"minor": 24, "minor": 30,
"patch": 0 "patch": 0
}, },
"include": ["CMake${hostSystemName}Presets.json"],
"configurePresets": [ "configurePresets": [
{
"name": "x64-Clang-Base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Build/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}"
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
"intelliSenseMode": "windows-clang-x64"
}
}
},
{ {
"name": "x64-Clang-Debug", "name": "x64-Clang-Debug",
"displayName": "Clang x64 Debug", "displayName": "Clang x64 Debug",
@ -74,4 +59,4 @@
} }
} }
] ]
} }

26
CMakeWindowsPresets.json Normal file
View File

@ -0,0 +1,26 @@
{
"version": 9,
"cmakeMinimumRequired": {
"major": 3,
"minor": 30,
"patch": 0
},
"configurePresets": [
{
"name": "x64-Clang-Base",
"hidden": true,
"generator": "Ninja",
"binaryDir": "${sourceDir}/Build/${presetName}",
"cacheVariables": {
"CMAKE_C_COMPILER": "clang-cl",
"CMAKE_CXX_COMPILER": "clang-cl",
"CMAKE_INSTALL_PREFIX": "${sourceDir}/Build/${presetName}"
},
"vendor": {
"microsoft.com/VisualStudioSettings/CMake/1.0": {
"intelliSenseMode": "windows-clang-x64"
}
}
}
]
}

View File

@ -5,6 +5,8 @@ path = [
"REUSE.toml", "REUSE.toml",
"crowdin.yml", "crowdin.yml",
"CMakeSettings.json", "CMakeSettings.json",
"CMakeLinuxPresets.json",
"CMakeWindowsPresets.json",
"CMakePresets.json", "CMakePresets.json",
".github/FUNDING.yml", ".github/FUNDING.yml",
".github/shadps4.png", ".github/shadps4.png",

View File

@ -18,6 +18,9 @@ public:
void unlock() { void unlock() {
pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&mutex);
} }
[[nodiscard]] bool try_lock() {
return pthread_mutex_trylock(&mutex) == 0;
}
private: private:
pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP; pthread_mutex_t mutex = PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

View File

@ -361,13 +361,6 @@ public:
return *this; 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 { inline constexpr BitArray operator|(const BitArray& other) const {
BitArray result = *this; BitArray result = *this;
result |= other; result |= other;
@ -388,7 +381,9 @@ public:
inline constexpr BitArray operator~() const { inline constexpr BitArray operator~() const {
BitArray result = *this; BitArray result = *this;
result = ~result; for (size_t i = 0; i < WORD_COUNT; ++i) {
result.data[i] = ~result.data[i];
}
return result; return result;
} }
@ -408,4 +403,4 @@ private:
std::array<u64, WORD_COUNT> data{}; std::array<u64, WORD_COUNT> data{};
}; };
} // namespace Common } // namespace Common

View File

@ -51,6 +51,7 @@ static bool isShowSplash = false;
static std::string isSideTrophy = "right"; static std::string isSideTrophy = "right";
static bool isNullGpu = false; static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false; static bool shouldCopyGPUBuffers = false;
static bool readbacksEnabled = false;
static bool shouldDumpShaders = false; static bool shouldDumpShaders = false;
static bool shouldPatchShaders = true; static bool shouldPatchShaders = true;
static u32 vblankDivider = 1; static u32 vblankDivider = 1;
@ -240,6 +241,10 @@ bool copyGPUCmdBuffers() {
return shouldCopyGPUBuffers; return shouldCopyGPUBuffers;
} }
bool readbacks() {
return readbacksEnabled;
}
bool dumpShaders() { bool dumpShaders() {
return shouldDumpShaders; return shouldDumpShaders;
} }
@ -344,6 +349,10 @@ void setCopyGPUCmdBuffers(bool enable) {
shouldCopyGPUBuffers = enable; shouldCopyGPUBuffers = enable;
} }
void setReadbacks(bool enable) {
readbacksEnabled = enable;
}
void setDumpShaders(bool enable) { void setDumpShaders(bool enable) {
shouldDumpShaders = enable; shouldDumpShaders = enable;
} }
@ -586,6 +595,7 @@ void load(const std::filesystem::path& path) {
screenHeight = toml::find_or<int>(gpu, "screenHeight", screenHeight); screenHeight = toml::find_or<int>(gpu, "screenHeight", screenHeight);
isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false); isNullGpu = toml::find_or<bool>(gpu, "nullGpu", false);
shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", false); shouldCopyGPUBuffers = toml::find_or<bool>(gpu, "copyGPUBuffers", false);
readbacksEnabled = toml::find_or<bool>(gpu, "readbacks", false);
shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false); shouldDumpShaders = toml::find_or<bool>(gpu, "dumpShaders", false);
shouldPatchShaders = toml::find_or<bool>(gpu, "patchShaders", true); shouldPatchShaders = toml::find_or<bool>(gpu, "patchShaders", true);
vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1); vblankDivider = toml::find_or<int>(gpu, "vblankDivider", 1);
@ -735,6 +745,7 @@ void save(const std::filesystem::path& path) {
data["GPU"]["screenHeight"] = screenHeight; data["GPU"]["screenHeight"] = screenHeight;
data["GPU"]["nullGpu"] = isNullGpu; data["GPU"]["nullGpu"] = isNullGpu;
data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers; data["GPU"]["copyGPUBuffers"] = shouldCopyGPUBuffers;
data["GPU"]["readbacks"] = readbacksEnabled;
data["GPU"]["dumpShaders"] = shouldDumpShaders; data["GPU"]["dumpShaders"] = shouldDumpShaders;
data["GPU"]["patchShaders"] = shouldPatchShaders; data["GPU"]["patchShaders"] = shouldPatchShaders;
data["GPU"]["vblankDivider"] = vblankDivider; data["GPU"]["vblankDivider"] = vblankDivider;

View File

@ -45,6 +45,8 @@ bool nullGpu();
void setNullGpu(bool enable); void setNullGpu(bool enable);
bool copyGPUCmdBuffers(); bool copyGPUCmdBuffers();
void setCopyGPUCmdBuffers(bool enable); void setCopyGPUCmdBuffers(bool enable);
bool readbacks();
void setReadbacks(bool enable);
bool dumpShaders(); bool dumpShaders();
void setDumpShaders(bool enable); void setDumpShaders(bool enable);
u32 vblankDiv(); u32 vblankDiv();

101
src/common/range_lock.h Normal file
View File

@ -0,0 +1,101 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <iterator>
#include <mutex>
namespace Common {
// From boost thread locking
template <typename Iterator>
struct RangeLockGuard {
Iterator begin;
Iterator end;
RangeLockGuard(Iterator begin_, Iterator end_) : begin(begin_), end(end_) {
LockRange(begin, end);
}
void release() {
begin = end;
}
~RangeLockGuard() {
for (; begin != end; ++begin) {
begin->unlock();
}
}
};
template <typename Iterator>
Iterator TryLockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return end;
}
std::unique_lock<LockType> guard(*begin, std::try_to_lock);
if (!guard.owns_lock()) {
return begin;
}
Iterator failed = TryLockRange(++begin, end);
if (failed == end) {
guard.release();
}
return failed;
}
template <typename Iterator>
void LockRange(Iterator begin, Iterator end) {
using LockType = typename std::iterator_traits<Iterator>::value_type;
if (begin == end) {
return;
}
bool start_with_begin = true;
Iterator second = begin;
++second;
Iterator next = second;
while (true) {
std::unique_lock<LockType> begin_lock(*begin, std::defer_lock);
if (start_with_begin) {
begin_lock.lock();
const Iterator failed_lock = TryLockRange(next, end);
if (failed_lock == end) {
begin_lock.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
RangeLockGuard<Iterator> guard(next, end);
if (begin_lock.try_lock()) {
const Iterator failed_lock = TryLockRange(second, next);
if (failed_lock == next) {
begin_lock.release();
guard.release();
return;
}
start_with_begin = false;
next = failed_lock;
} else {
start_with_begin = true;
next = second;
}
}
}
}
} // namespace Common

View File

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <condition_variable>
#include <mutex>
namespace Common {
// Like std::shared_mutex, but reader has priority over writer.
class SharedFirstMutex {
public:
void lock() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active && readers == 0; });
writer_active = true;
}
void unlock() {
std::lock_guard<std::mutex> lock(mtx);
writer_active = false;
cv.notify_all();
}
void lock_shared() {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [this]() { return !writer_active; });
++readers;
}
void unlock_shared() {
std::lock_guard<std::mutex> lock(mtx);
if (--readers == 0) {
cv.notify_all();
}
}
private:
std::mutex mtx;
std::condition_variable cv;
int readers = 0;
bool writer_active = false;
};
} // namespace Common

View File

@ -302,14 +302,15 @@ struct AddressSpace::Impl {
new_flags = PAGE_READWRITE; new_flags = PAGE_READWRITE;
} else if (read && !write) { } else if (read && !write) {
new_flags = PAGE_READONLY; new_flags = PAGE_READONLY;
} else if (execute && !read && not write) { } else if (execute && !read && !write) {
new_flags = PAGE_EXECUTE; new_flags = PAGE_EXECUTE;
} else if (!read && !write && !execute) { } else if (!read && !write && !execute) {
new_flags = PAGE_NOACCESS; new_flags = PAGE_NOACCESS;
} else { } else {
LOG_CRITICAL(Common_Memory, LOG_CRITICAL(Common_Memory,
"Unsupported protection flag combination for address {:#x}, size {}", "Unsupported protection flag combination for address {:#x}, size {}, "
virtual_addr, size); "read={}, write={}, execute={}",
virtual_addr, size, read, write, execute);
return; return;
} }

View File

@ -11,6 +11,7 @@
namespace Core { namespace Core {
enum class MemoryPermission : u32 { enum class MemoryPermission : u32 {
None = 0,
Read = 1 << 0, Read = 1 << 0,
Write = 1 << 1, Write = 1 << 1,
ReadWrite = Read | Write, ReadWrite = Read | Write,

View File

@ -2834,7 +2834,7 @@ void RegisterlibSceGnmDriver(Core::Loader::SymbolsResolver* sym) {
} }
if (Config::copyGPUCmdBuffers()) { if (Config::copyGPUCmdBuffers()) {
liverpool->reserveCopyBufferSpace(); liverpool->ReserveCopyBufferSpace();
} }
Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock, Platform::IrqC::Instance()->Register(Platform::InterruptId::GpuIdle, ResetSubmissionLock,

View File

@ -171,19 +171,40 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
LOG_ERROR(Lib_Vdec2, "Invalid struct size"); LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
} }
if (outputInfo->pictureCount == 0 || gPictureInfos.empty()) { if (outputInfo->pictureCount == 0) {
LOG_ERROR(Lib_Vdec2, "No picture info available"); LOG_ERROR(Lib_Vdec2, "No picture info available");
return ORBIS_OK; return ORBIS_OK;
} }
if (p1stPictureInfoOut) { // If the game uses the older Videodec2 structs, we need to accomodate that.
OrbisVideodec2AvcPictureInfo* picInfo = if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut); if (gLegacyPictureInfos.empty()) {
if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) { LOG_ERROR(Lib_Vdec2, "No picture info available");
LOG_ERROR(Lib_Vdec2, "Invalid struct size"); return ORBIS_OK;
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE; }
if (p1stPictureInfoOut) {
OrbisVideodec2LegacyAvcPictureInfo* picInfo =
static_cast<OrbisVideodec2LegacyAvcPictureInfo*>(p1stPictureInfoOut);
if (picInfo->thisSize != sizeof(OrbisVideodec2LegacyAvcPictureInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
*picInfo = gLegacyPictureInfos.back();
}
} else {
if (gPictureInfos.empty()) {
LOG_ERROR(Lib_Vdec2, "No picture info available");
return ORBIS_OK;
}
if (p1stPictureInfoOut) {
OrbisVideodec2AvcPictureInfo* picInfo =
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut);
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
*picInfo = gPictureInfos.back();
} }
*picInfo = gPictureInfos.back();
} }
if (outputInfo->pictureCount > 1) { if (outputInfo->pictureCount > 1) {

View File

@ -74,4 +74,57 @@ struct OrbisVideodec2AvcPictureInfo {
}; };
static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78); static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78);
// An older version of the OrbisVideodec2AvcPictureInfo struct
// Keeping this is needed for compatiblity with older games.
struct OrbisVideodec2LegacyAvcPictureInfo {
u64 thisSize;
bool isValid;
u64 ptsData;
u64 dtsData;
u64 attachedData;
u8 idrPictureflag;
u8 profile_idc;
u8 level_idc;
u32 pic_width_in_mbs_minus1;
u32 pic_height_in_map_units_minus1;
u8 frame_mbs_only_flag;
u8 frame_cropping_flag;
u32 frameCropLeftOffset;
u32 frameCropRightOffset;
u32 frameCropTopOffset;
u32 frameCropBottomOffset;
u8 aspect_ratio_info_present_flag;
u8 aspect_ratio_idc;
u16 sar_width;
u16 sar_height;
u8 video_signal_type_present_flag;
u8 video_format;
u8 video_full_range_flag;
u8 colour_description_present_flag;
u8 colour_primaries;
u8 transfer_characteristics;
u8 matrix_coefficients;
u8 timing_info_present_flag;
u32 num_units_in_tick;
u32 time_scale;
u8 fixed_frame_rate_flag;
u8 bitstream_restriction_flag;
u8 max_dec_frame_buffering;
u8 pic_struct_present_flag;
u8 pic_struct;
u8 field_pic_flag;
u8 bottom_field_flag;
};
static_assert(sizeof(OrbisVideodec2LegacyAvcPictureInfo) == 0x68);
} // namespace Libraries::Vdec2 } // namespace Libraries::Vdec2

View File

@ -12,6 +12,7 @@
namespace Libraries::Vdec2 { namespace Libraries::Vdec2 {
std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos; std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
static inline void CopyNV12Data(u8* dst, const AVFrame& src) { static inline void CopyNV12Data(u8* dst, const AVFrame& src) {
std::memcpy(dst, src.data[0], src.width * src.height); std::memcpy(dst, src.data[0], src.width * src.height);
@ -117,27 +118,46 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
outputInfo.isErrorFrame = false; outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version. // For proper compatibility with older games, check the inputted OutputInfo struct size.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) { if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
// framePitchInBytes only exists in the newer struct.
outputInfo.framePitchInBytes = frame->linesize[0]; outputInfo.framePitchInBytes = frame->linesize[0];
} if (outputInfo.isValid) {
OrbisVideodec2AvcPictureInfo pictureInfo = {};
if (outputInfo.isValid) { pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo);
OrbisVideodec2AvcPictureInfo pictureInfo = {}; pictureInfo.isValid = true;
pictureInfo.thisSize = sizeof(OrbisVideodec2AvcPictureInfo); pictureInfo.ptsData = inputData.ptsData;
pictureInfo.isValid = true; pictureInfo.dtsData = inputData.dtsData;
pictureInfo.attachedData = inputData.attachedData;
pictureInfo.ptsData = inputData.ptsData; pictureInfo.frameCropLeftOffset = frame->crop_left;
pictureInfo.dtsData = inputData.dtsData; pictureInfo.frameCropRightOffset = frame->crop_right;
pictureInfo.attachedData = inputData.attachedData; pictureInfo.frameCropTopOffset = frame->crop_top;
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
pictureInfo.frameCropLeftOffset = frame->crop_left; gPictureInfos.push_back(pictureInfo);
pictureInfo.frameCropRightOffset = frame->crop_right; }
pictureInfo.frameCropTopOffset = frame->crop_top; } else {
pictureInfo.frameCropBottomOffset = frame->crop_bottom; if (outputInfo.isValid) {
// If the game uses the older struct versions, we need to use it too.
OrbisVideodec2LegacyAvcPictureInfo pictureInfo = {};
gPictureInfos.push_back(pictureInfo); pictureInfo.thisSize = sizeof(OrbisVideodec2LegacyAvcPictureInfo);
pictureInfo.isValid = true;
pictureInfo.ptsData = inputData.ptsData;
pictureInfo.dtsData = inputData.dtsData;
pictureInfo.attachedData = inputData.attachedData;
pictureInfo.frameCropLeftOffset = frame->crop_left;
pictureInfo.frameCropRightOffset = frame->crop_right;
pictureInfo.frameCropTopOffset = frame->crop_top;
pictureInfo.frameCropBottomOffset = frame->crop_bottom;
gLegacyPictureInfos.push_back(pictureInfo);
}
} }
} }

View File

@ -16,6 +16,7 @@ extern "C" {
namespace Libraries::Vdec2 { namespace Libraries::Vdec2 {
extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos; extern std::vector<OrbisVideodec2AvcPictureInfo> gPictureInfos;
extern std::vector<OrbisVideodec2LegacyAvcPictureInfo> gLegacyPictureInfos;
class VdecDecoder { class VdecDecoder {
public: public:

View File

@ -132,6 +132,7 @@ void Emulator::Run(std::filesystem::path file, const std::vector<std::string> ar
LOG_INFO(Config, "General LogType: {}", Config::getLogType()); LOG_INFO(Config, "General LogType: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoModeConsole());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
LOG_INFO(Config, "GPU readbacks: {}", Config::readbacks());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv()); LOG_INFO(Config, "GPU vblankDivider: {}", Config::vblankDiv());
LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId()); LOG_INFO(Config, "Vulkan gpuId: {}", Config::getGpuId());

View File

@ -514,17 +514,14 @@ void ControllerOutput::FinalizeUpdate() {
if (button != SDL_GAMEPAD_BUTTON_INVALID) { if (button != SDL_GAMEPAD_BUTTON_INVALID) {
switch (button) { switch (button) {
case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT: case SDL_GAMEPAD_BUTTON_TOUCHPAD_LEFT:
LOG_INFO(Input, "Topuchpad left");
controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f); controller->SetTouchpadState(0, new_button_state, 0.25f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER: case SDL_GAMEPAD_BUTTON_TOUCHPAD_CENTER:
LOG_INFO(Input, "Topuchpad center");
controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f); controller->SetTouchpadState(0, new_button_state, 0.50f, 0.5f);
controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state); controller->CheckButton(0, SDLGamepadToOrbisButton(button), new_button_state);
break; break;
case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT: case SDL_GAMEPAD_BUTTON_TOUCHPAD_RIGHT:
LOG_INFO(Input, "Topuchpad right");
controller->SetTouchpadState(0, new_button_state, 0.75f, 0.5f); 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;

View File

@ -177,38 +177,38 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"9", SDLK_9}, {"9", SDLK_9},
// symbols // symbols
{"`", SDLK_GRAVE}, {"grave", SDLK_GRAVE},
{"~", SDLK_TILDE}, {"tilde", SDLK_TILDE},
{"!", SDLK_EXCLAIM}, {"exclamation", SDLK_EXCLAIM},
{"@", SDLK_AT}, {"at", SDLK_AT},
{"#", SDLK_HASH}, {"hash", SDLK_HASH},
{"$", SDLK_DOLLAR}, {"dollar", SDLK_DOLLAR},
{"%", SDLK_PERCENT}, {"percent", SDLK_PERCENT},
{"^", SDLK_CARET}, {"caret", SDLK_CARET},
{"&", SDLK_AMPERSAND}, {"ampersand", SDLK_AMPERSAND},
{"*", SDLK_ASTERISK}, {"asterisk", SDLK_ASTERISK},
{"(", SDLK_LEFTPAREN}, {"lparen", SDLK_LEFTPAREN},
{")", SDLK_RIGHTPAREN}, {"rparen", SDLK_RIGHTPAREN},
{"-", SDLK_MINUS}, {"minus", SDLK_MINUS},
{"_", SDLK_UNDERSCORE}, {"underscore", SDLK_UNDERSCORE},
{"=", SDLK_EQUALS}, {"equals", SDLK_EQUALS},
{"+", SDLK_PLUS}, {"plus", SDLK_PLUS},
{"[", SDLK_LEFTBRACKET}, {"lbracket", SDLK_LEFTBRACKET},
{"]", SDLK_RIGHTBRACKET}, {"rbracket", SDLK_RIGHTBRACKET},
{"{", SDLK_LEFTBRACE}, {"lbrace", SDLK_LEFTBRACE},
{"}", SDLK_RIGHTBRACE}, {"rbrace", SDLK_RIGHTBRACE},
{"\\", SDLK_BACKSLASH}, {"backslash", SDLK_BACKSLASH},
{"|", SDLK_PIPE}, {"pipe", SDLK_PIPE},
{";", SDLK_SEMICOLON}, {"semicolon", SDLK_SEMICOLON},
{":", SDLK_COLON}, {"colon", SDLK_COLON},
{"'", SDLK_APOSTROPHE}, {"apostrophe", SDLK_APOSTROPHE},
{"\"", SDLK_DBLAPOSTROPHE}, {"quote", SDLK_DBLAPOSTROPHE},
{",", SDLK_COMMA}, {"comma", SDLK_COMMA},
{"<", SDLK_LESS}, {"less", SDLK_LESS},
{".", SDLK_PERIOD}, {"period", SDLK_PERIOD},
{">", SDLK_GREATER}, {"greater", SDLK_GREATER},
{"/", SDLK_SLASH}, {"slash", SDLK_SLASH},
{"?", SDLK_QUESTION}, {"question", SDLK_QUESTION},
// special keys // special keys
{"escape", SDLK_ESCAPE}, {"escape", SDLK_ESCAPE},
@ -252,13 +252,13 @@ const std::map<std::string, u32> string_to_keyboard_key_map = {
{"kp7", SDLK_KP_7}, {"kp7", SDLK_KP_7},
{"kp8", SDLK_KP_8}, {"kp8", SDLK_KP_8},
{"kp9", SDLK_KP_9}, {"kp9", SDLK_KP_9},
{"kp.", SDLK_KP_PERIOD}, {"kpperiod", SDLK_KP_PERIOD},
{"kp,", SDLK_KP_COMMA}, {"kpcomma", SDLK_KP_COMMA},
{"kp/", SDLK_KP_DIVIDE}, {"kpslash", SDLK_KP_DIVIDE},
{"kp*", SDLK_KP_MULTIPLY}, {"kpasterisk", SDLK_KP_MULTIPLY},
{"kp-", SDLK_KP_MINUS}, {"kpminus", SDLK_KP_MINUS},
{"kp+", SDLK_KP_PLUS}, {"kpplus", SDLK_KP_PLUS},
{"kp=", SDLK_KP_EQUALS}, {"kpequals", SDLK_KP_EQUALS},
{"kpenter", SDLK_KP_ENTER}, {"kpenter", SDLK_KP_ENTER},
// mouse // mouse

File diff suppressed because it is too large Load Diff

View File

@ -2,7 +2,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialog> #include <QDialog>
#include <SDL3/SDL.h>
#include <SDL3/SDL_gamepad.h>
#include "game_info.h" #include "game_info.h"
#include "sdl_event_wrapper.h"
namespace Ui { namespace Ui {
class ControlSettings; class ControlSettings;
@ -11,22 +14,56 @@ class ControlSettings;
class ControlSettings : public QDialog { class ControlSettings : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit ControlSettings(std::shared_ptr<GameInfoClass> game_info_get, explicit ControlSettings(std::shared_ptr<GameInfoClass> game_info_get, bool GameRunning,
QWidget* parent = nullptr); std::string GameRunningSerial, QWidget* parent = nullptr);
~ControlSettings(); ~ControlSettings();
signals:
void PushGamepadEvent();
void AxisChanged();
private Q_SLOTS: private Q_SLOTS:
void SaveControllerConfig(bool CloseOnSave); void SaveControllerConfig(bool CloseOnSave);
void SetDefault(); void SetDefault();
void UpdateLightbarColor(); void UpdateLightbarColor();
void CheckMapping(QPushButton*& button);
void StartTimer(QPushButton*& button, bool isButton);
void ConnectAxisInputs(QPushButton*& button);
private: private:
std::unique_ptr<Ui::ControlSettings> ui; std::unique_ptr<Ui::ControlSettings> ui;
std::shared_ptr<GameInfoClass> m_game_info; std::shared_ptr<GameInfoClass> m_game_info;
bool eventFilter(QObject* obj, QEvent* event) override;
void AddBoxItems(); void AddBoxItems();
void SetUIValuestoMappings(); void SetUIValuestoMappings();
void GetGameTitle(); void GetGameTitle();
void CheckGamePad();
void processSDLEvents(int Type, int Input, int Value);
void pollSDLEvents();
void SetMapping(QString input);
void DisableMappingButtons();
void EnableMappingButtons();
void Cleanup();
QList<QPushButton*> ButtonsList;
QList<QPushButton*> AxisList;
QSet<QString> pressedButtons;
std::string RunningGameSerial;
bool GameRunning;
bool L2Pressed = false;
bool R2Pressed = false;
bool EnableButtonMapping = false;
bool EnableAxisMapping = false;
bool MappingCompleted = false;
QString mapping;
int MappingTimer;
QTimer* timer;
QPushButton* MappingButton;
SDL_Gamepad* gamepad = nullptr;
SdlEventWrapper::Wrapper* RemapWrapper;
QFuture<void> Polling;
const std::vector<std::string> ControllerInputs = { const std::vector<std::string> ControllerInputs = {
"cross", "circle", "square", "triangle", "l1", "cross", "circle", "square", "triangle", "l1",
@ -39,29 +76,8 @@ 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", protected:
"circle", void closeEvent(QCloseEvent* event) override {
"square", Cleanup();
"triangle", }
"l1",
"r1",
"l2",
"r2",
"l3",
"r3",
"options",
"pad_up",
"pad_down",
"pad_left",
"pad_right",
"touchpad_left",
"touchpad_center",
"touchpad_right",
"unmapped"};
const QStringList StickOutputs = {"axis_left_x", "axis_left_y", "axis_right_x", "axis_right_y",
"unmapped"};
}; };

View File

@ -11,8 +11,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1043</width> <width>1114</width>
<height>792</height> <height>794</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -33,8 +33,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1019</width> <width>1094</width>
<height>732</height> <height>744</height>
</rect> </rect>
</property> </property>
<widget class="QWidget" name="layoutWidget"> <widget class="QWidget" name="layoutWidget">
@ -42,8 +42,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1021</width> <width>1091</width>
<height>731</height> <height>741</height>
</rect> </rect>
</property> </property>
<layout class="QHBoxLayout" name="RemapLayout"> <layout class="QHBoxLayout" name="RemapLayout">
@ -110,7 +110,7 @@
<widget class="QGroupBox" name="gb_dpad_up"> <widget class="QGroupBox" name="gb_dpad_up">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -125,12 +125,9 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_4"> <layout class="QHBoxLayout" name="horizontalLayout_4">
<item> <item>
<widget class="QComboBox" name="DpadUpBox"> <widget class="QPushButton" name="DpadUpButton">
<property name="editable"> <property name="text">
<bool>false</bool> <string>unmapped</string>
</property>
<property name="sizeAdjustPolicy">
<enum>QComboBox::SizeAdjustPolicy::AdjustToContents</enum>
</property> </property>
</widget> </widget>
</item> </item>
@ -161,7 +158,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="DpadLeftBox"/> <widget class="QPushButton" name="DpadLeftButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -185,9 +186,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="DpadRightBox"> <widget class="QPushButton" name="DpadRightButton">
<property name="editable"> <property name="text">
<bool>false</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -213,6 +214,12 @@
</property> </property>
<item> <item>
<widget class="QGroupBox" name="gb_dpad_down"> <widget class="QGroupBox" name="gb_dpad_down">
<property name="minimumSize">
<size>
<width>152</width>
<height>0</height>
</size>
</property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>124</width>
@ -224,21 +231,9 @@
</property> </property>
<layout class="QHBoxLayout" name="horizontalLayout_3"> <layout class="QHBoxLayout" name="horizontalLayout_3">
<item> <item>
<widget class="QComboBox" name="DpadDownBox"> <widget class="QPushButton" name="DpadDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</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>
</widget> </widget>
</item> </item>
@ -378,7 +373,7 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>16777215</height> <height>16777215</height>
</size> </size>
</property> </property>
@ -387,9 +382,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_10"> <layout class="QVBoxLayout" name="verticalLayout_10">
<item> <item>
<widget class="QComboBox" name="LStickUpBox"> <widget class="QPushButton" name="LStickUpButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -420,9 +415,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="LStickLeftBox"> <widget class="QPushButton" name="LStickLeftButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -454,9 +449,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="LStickRightBox"> <widget class="QPushButton" name="LStickRightButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -484,7 +479,7 @@
<widget class="QGroupBox" name="gb_left_stick_down"> <widget class="QGroupBox" name="gb_left_stick_down">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -499,15 +494,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_8"> <layout class="QVBoxLayout" name="verticalLayout_8">
<item> <item>
<widget class="QComboBox" name="LStickDownBox"> <widget class="QPushButton" name="LStickDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property>
<property name="editable">
<bool>false</bool>
</property>
<property name="frame">
<bool>false</bool>
</property> </property>
</widget> </widget>
</item> </item>
@ -617,149 +606,190 @@
<property name="spacing"> <property name="spacing">
<number>0</number> <number>0</number>
</property> </property>
<item>
<layout class="QVBoxLayout" name="layout_l1_l2">
<item>
<widget class="QGroupBox" name="gb_l1">
<property name="title">
<string>L1 / LB</string>
</property>
<layout class="QVBoxLayout" name="gb_l1_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="QComboBox" name="LBBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_l2">
<property name="title">
<string>L2 / LT</string>
</property>
<layout class="QVBoxLayout" name="gb_l2_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="QComboBox" name="LTBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item> <item>
<layout class="QVBoxLayout" name="layout_system_buttons"> <layout class="QVBoxLayout" name="layout_system_buttons">
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_4"> <layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>10</number>
</property>
<item> <item>
<widget class="QGroupBox" name="groupBox_2"> <layout class="QHBoxLayout" name="horizontalLayout_13">
<property name="title"> <item>
<string>Back</string> <widget class="QGroupBox" name="gb_l1">
</property> <property name="sizePolicy">
<layout class="QVBoxLayout" name="verticalLayout_9"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<item> <horstretch>0</horstretch>
<widget class="QComboBox" name="BackBox"/> <verstretch>0</verstretch>
</item> </sizepolicy>
</layout> </property>
</widget> <property name="title">
<string>L1</string>
</property>
<layout class="QVBoxLayout" name="gb_l1_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="L1Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>133</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="gb_r1">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>R1</string>
</property>
<layout class="QVBoxLayout" name="gb_r1_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="R1Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
<widget class="QGroupBox" name="gb_l2">
<property name="title">
<string>L2</string>
</property>
<layout class="QVBoxLayout" name="gb_l2_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="L2Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_start">
<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="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_r2">
<property name="title">
<string>R2</string>
</property>
<layout class="QVBoxLayout" name="gb_r2_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="R2Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
</layout> </layout>
</item> </item>
<item>
<layout class="QVBoxLayout" name="layout_r1_r2">
<item>
<widget class="QGroupBox" name="gb_r1">
<property name="title">
<string>R1 / RB</string>
</property>
<layout class="QVBoxLayout" name="gb_r1_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="QComboBox" name="RBBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="gb_r2">
<property name="title">
<string>R2 / RT</string>
</property>
<layout class="QVBoxLayout" name="gb_r2_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="QComboBox" name="RTBox"/>
</item>
</layout>
</widget>
</item>
</layout>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -806,7 +836,7 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="layout_middle_bottom"> <layout class="QVBoxLayout" name="verticalLayout_9">
<property name="spacing"> <property name="spacing">
<number>10</number> <number>10</number>
</property> </property>
@ -814,76 +844,144 @@
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum> <enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property> </property>
<item> <item>
<widget class="QGroupBox" name="gb_l3"> <layout class="QHBoxLayout" name="horizontalLayout">
<property name="title"> <item>
<string>L3</string> <widget class="QGroupBox" name="gb_l3">
</property> <property name="sizePolicy">
<layout class="QVBoxLayout" name="gb_l3_layout"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<property name="leftMargin"> <horstretch>0</horstretch>
<number>5</number> <verstretch>0</verstretch>
</property> </sizepolicy>
<property name="topMargin"> </property>
<number>5</number> <property name="title">
</property> <string>L3</string>
<property name="rightMargin"> </property>
<number>5</number> <layout class="QVBoxLayout" name="gb_l3_layout">
</property> <property name="leftMargin">
<property name="bottomMargin"> <number>5</number>
<number>5</number> </property>
</property> <property name="topMargin">
<item> <number>5</number>
<widget class="QComboBox" name="LClickBox"/> </property>
</item> <property name="rightMargin">
</layout> <number>5</number>
</widget> </property>
<property name="bottomMargin">
<number>5</number>
</property>
<item>
<widget class="QPushButton" name="L3Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>133</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QGroupBox" name="gb_r3">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>R3</string>
</property>
<layout class="QVBoxLayout" name="gb_r3_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="R3Button">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item> </item>
<item> <item>
<widget class="QGroupBox" name="gb_start"> <layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="title"> <item>
<string>Options / Start</string> <widget class="QGroupBox" name="gb_touchleft">
</property> <property name="title">
<layout class="QVBoxLayout" name="gb_start_layout"> <string>Touchpad Left</string>
<property name="leftMargin"> </property>
<number>5</number> <layout class="QHBoxLayout" name="horizontalLayout_5">
</property> <item>
<property name="topMargin"> <widget class="QPushButton" name="TouchpadLeftButton">
<number>5</number> <property name="text">
</property> <string>unmapped</string>
<property name="rightMargin"> </property>
<number>5</number> </widget>
</property> </item>
<property name="bottomMargin"> </layout>
<number>5</number> </widget>
</property> </item>
<item> <item>
<widget class="QComboBox" name="StartBox"/> <widget class="QGroupBox" name="gb_touchcenter">
</item> <property name="title">
</layout> <string>Touchpad Center</string>
</widget> </property>
</item> <layout class="QVBoxLayout" name="verticalLayout_11">
<item> <item>
<widget class="QGroupBox" name="gb_r3"> <widget class="QPushButton" name="TouchpadCenterButton">
<property name="title"> <property name="text">
<string>R3</string> <string>unmapped</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_r3_layout"> </widget>
<property name="leftMargin"> </item>
<number>5</number> </layout>
</property> </widget>
<property name="topMargin"> </item>
<number>5</number> <item>
</property> <widget class="QGroupBox" name="gb_touchright">
<property name="rightMargin"> <property name="title">
<number>5</number> <string>Touchpad Right</string>
</property> </property>
<property name="bottomMargin"> <layout class="QVBoxLayout" name="verticalLayout_12">
<number>5</number> <item>
</property> <widget class="QPushButton" name="TouchpadRightButton">
<item> <property name="text">
<widget class="QComboBox" name="RClickBox"/> <string>unmapped</string>
</item> </property>
</layout> </widget>
</widget> </item>
</layout>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </item>
@ -1104,7 +1202,7 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1115,19 +1213,13 @@
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>Triangle / Y</string> <string>Triangle</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_3"> <layout class="QVBoxLayout" name="verticalLayout_3">
<item> <item>
<widget class="QComboBox" name="YBox"> <widget class="QPushButton" name="TriangleButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
</widget> </widget>
</item> </item>
@ -1142,7 +1234,7 @@
<item> <item>
<widget class="QGroupBox" name="gb_square"> <widget class="QGroupBox" name="gb_square">
<property name="title"> <property name="title">
<string>Square / X</string> <string>Square</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_square_layout"> <layout class="QVBoxLayout" name="gb_square_layout">
<property name="leftMargin"> <property name="leftMargin">
@ -1158,7 +1250,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="XBox"/> <widget class="QPushButton" name="SquareButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1166,7 +1262,7 @@
<item> <item>
<widget class="QGroupBox" name="gb_circle"> <widget class="QGroupBox" name="gb_circle">
<property name="title"> <property name="title">
<string>Circle / B</string> <string>Circle</string>
</property> </property>
<layout class="QVBoxLayout" name="gb_circle_layout"> <layout class="QVBoxLayout" name="gb_circle_layout">
<property name="leftMargin"> <property name="leftMargin">
@ -1182,7 +1278,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="BBox"/> <widget class="QPushButton" name="CircleButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1208,7 +1308,7 @@
<widget class="QGroupBox" name="gb_cross"> <widget class="QGroupBox" name="gb_cross">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1219,11 +1319,15 @@
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>Cross / A</string> <string>Cross</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_5"> <layout class="QVBoxLayout" name="verticalLayout_5">
<item> <item>
<widget class="QComboBox" name="ABox"/> <widget class="QPushButton" name="CrossButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1361,7 +1465,7 @@
</property> </property>
<property name="maximumSize"> <property name="maximumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>1231321</height> <height>1231321</height>
</size> </size>
</property> </property>
@ -1370,9 +1474,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_7"> <layout class="QVBoxLayout" name="verticalLayout_7">
<item> <item>
<widget class="QComboBox" name="RStickUpBox"> <widget class="QPushButton" name="RStickUpButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1403,9 +1507,9 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="RStickLeftBox"> <widget class="QPushButton" name="RStickLeftButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -1431,7 +1535,11 @@
<number>5</number> <number>5</number>
</property> </property>
<item> <item>
<widget class="QComboBox" name="RStickRightBox"/> <widget class="QPushButton" name="RStickRightButton">
<property name="text">
<string>unmapped</string>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
@ -1457,7 +1565,7 @@
<widget class="QGroupBox" name="gb_right_stick_down"> <widget class="QGroupBox" name="gb_right_stick_down">
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>124</width> <width>152</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
@ -1472,9 +1580,9 @@
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_6"> <layout class="QVBoxLayout" name="verticalLayout_6">
<item> <item>
<widget class="QComboBox" name="RStickDownBox"> <widget class="QPushButton" name="RStickDownButton">
<property name="enabled"> <property name="text">
<bool>true</bool> <string>unmapped</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -7,16 +7,20 @@
#include <QMouseEvent> #include <QMouseEvent>
#include <QPushButton> #include <QPushButton>
#include <QWheelEvent> #include <QWheelEvent>
#include <SDL3/SDL_events.h>
#include "common/path_util.h" #include "common/path_util.h"
#include "input/input_handler.h"
#include "kbm_config_dialog.h" #include "kbm_config_dialog.h"
#include "kbm_gui.h" #include "kbm_gui.h"
#include "kbm_help_dialog.h" #include "kbm_help_dialog.h"
#include "ui_kbm_gui.h" #include "ui_kbm_gui.h"
HelpDialog* HelpWindow; HelpDialog* HelpWindow;
KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent) KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, bool isGameRunning,
: QDialog(parent), m_game_info(game_info_get), ui(new Ui::KBMSettings) { std::string GameRunningSerial, QWidget* parent)
: QDialog(parent), m_game_info(game_info_get), GameRunning(isGameRunning),
RunningGameSerial(GameRunningSerial), ui(new Ui::KBMSettings) {
ui->setupUi(this); ui->setupUi(this);
ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig()); ui->PerGameCheckBox->setChecked(!Config::GetUseUnifiedInputConfig());
@ -144,6 +148,8 @@ tr("Do you want to overwrite existing mappings with the mappings from the Common
QString SOSString = tr("Speed Offset (def 0.125):") + " " + SOSValue; QString SOSString = tr("Speed Offset (def 0.125):") + " " + SOSValue;
ui->SpeedOffsetLabel->setText(SOSString); ui->SpeedOffsetLabel->setText(SOSString);
}); });
connect(this, &KBMSettings::PushKBMEvent, this, [this]() { CheckMapping(MappingButton); });
} }
void KBMSettings::ButtonConnects() { void KBMSettings::ButtonConnects() {
@ -269,9 +275,17 @@ void KBMSettings::SaveKBMConfig(bool close_on_save) {
output_string = line.substr(0, equal_pos - 1); output_string = line.substr(0, equal_pos - 1);
input_string = line.substr(equal_pos + 2); input_string = line.substr(equal_pos + 2);
if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) != bool controllerInputdetected = false;
ControllerInputs.end() || for (std::string input : ControllerInputs) {
output_string == "analog_deadzone" || output_string == "override_controller_color") { // Needed to avoid detecting backspace while detecting back
if (input_string.contains(input) && !input_string.contains("backspace")) {
controllerInputdetected = true;
break;
}
}
if (controllerInputdetected || output_string == "analog_deadzone" ||
output_string == "override_controller_color") {
lines.push_back(line); lines.push_back(line);
} }
} }
@ -322,6 +336,11 @@ QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped
Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked()); Config::SetUseUnifiedInputConfig(!ui->PerGameCheckBox->isChecked());
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml"); Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
if (GameRunning) {
Config::GetUseUnifiedInputConfig() ? Input::ParseInputConfig("default")
: Input::ParseInputConfig(RunningGameSerial);
}
if (close_on_save) if (close_on_save)
QWidget::close(); QWidget::close();
} }
@ -388,8 +407,16 @@ void KBMSettings::SetUIValuestoMappings(std::string config_id) {
std::string output_string = line.substr(0, equal_pos - 1); std::string output_string = line.substr(0, equal_pos - 1);
std::string input_string = line.substr(equal_pos + 2); std::string input_string = line.substr(equal_pos + 2);
if (std::find(ControllerInputs.begin(), ControllerInputs.end(), input_string) == bool controllerInputdetected = false;
ControllerInputs.end()) { for (std::string input : ControllerInputs) {
// Needed to avoid detecting backspace while detecting back
if (input_string.contains(input) && !input_string.contains("backspace")) {
controllerInputdetected = true;
break;
}
}
if (!controllerInputdetected) {
if (output_string == "cross") { if (output_string == "cross") {
ui->CrossButton->setText(QString::fromStdString(input_string)); ui->CrossButton->setText(QString::fromStdString(input_string));
} else if (output_string == "circle") { } else if (output_string == "circle") {
@ -518,7 +545,6 @@ void KBMSettings::StartTimer(QPushButton*& button) {
MappingTimer = 3; MappingTimer = 3;
EnableMapping = true; EnableMapping = true;
MappingCompleted = false; MappingCompleted = false;
modifier = "";
mapping = button->text(); mapping = button->text();
DisableMappingButtons(); DisableMappingButtons();
@ -711,92 +737,98 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
break; break;
// symbols // symbols
case Qt::Key_QuoteLeft:
pressedKeys.insert("grave");
break;
case Qt::Key_AsciiTilde:
pressedKeys.insert("tilde");
break;
case Qt::Key_Exclam: case Qt::Key_Exclam:
pressedKeys.insert("!"); pressedKeys.insert("exclamation");
break; break;
case Qt::Key_At: case Qt::Key_At:
pressedKeys.insert("@"); pressedKeys.insert("at");
break; break;
case Qt::Key_NumberSign: case Qt::Key_NumberSign:
pressedKeys.insert("#"); pressedKeys.insert("hash");
break; break;
case Qt::Key_Dollar: case Qt::Key_Dollar:
pressedKeys.insert("$"); pressedKeys.insert("dollar");
break; break;
case Qt::Key_Percent: case Qt::Key_Percent:
pressedKeys.insert("%"); pressedKeys.insert("percent");
break; break;
case Qt::Key_AsciiCircum: case Qt::Key_AsciiCircum:
pressedKeys.insert("^"); pressedKeys.insert("caret");
break; break;
case Qt::Key_Ampersand: case Qt::Key_Ampersand:
pressedKeys.insert("&"); pressedKeys.insert("ampersand");
break; break;
case Qt::Key_Asterisk: case Qt::Key_Asterisk:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp*", "*")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpasterisk", "asterisk"));
break; break;
case Qt::Key_ParenLeft: case Qt::Key_ParenLeft:
pressedKeys.insert("("); pressedKeys.insert("lparen");
break; break;
case Qt::Key_ParenRight: case Qt::Key_ParenRight:
pressedKeys.insert(")"); pressedKeys.insert("rparen");
break; break;
case Qt::Key_Minus: case Qt::Key_Minus:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp-", "-")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpminus", "minus"));
break; break;
case Qt::Key_Underscore: case Qt::Key_Underscore:
pressedKeys.insert("_"); pressedKeys.insert("underscore");
break; break;
case Qt::Key_Equal: case Qt::Key_Equal:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp=", "=")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpequals", "equals"));
break; break;
case Qt::Key_Plus: case Qt::Key_Plus:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp+", "+")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpplus", "plus"));
break; break;
case Qt::Key_BracketLeft: case Qt::Key_BracketLeft:
pressedKeys.insert("["); pressedKeys.insert("lbracket");
break; break;
case Qt::Key_BracketRight: case Qt::Key_BracketRight:
pressedKeys.insert("]"); pressedKeys.insert("rbracket");
break; break;
case Qt::Key_BraceLeft: case Qt::Key_BraceLeft:
pressedKeys.insert("{"); pressedKeys.insert("lbrace");
break; break;
case Qt::Key_BraceRight: case Qt::Key_BraceRight:
pressedKeys.insert("}"); pressedKeys.insert("rbrace");
break; break;
case Qt::Key_Backslash: case Qt::Key_Backslash:
pressedKeys.insert("\\"); pressedKeys.insert("backslash");
break; break;
case Qt::Key_Bar: case Qt::Key_Bar:
pressedKeys.insert("|"); pressedKeys.insert("pipe");
break; break;
case Qt::Key_Semicolon: case Qt::Key_Semicolon:
pressedKeys.insert(";"); pressedKeys.insert("semicolon");
break; break;
case Qt::Key_Colon: case Qt::Key_Colon:
pressedKeys.insert(":"); pressedKeys.insert("colon");
break; break;
case Qt::Key_Apostrophe: case Qt::Key_Apostrophe:
pressedKeys.insert("'"); pressedKeys.insert("apostrophe");
break; break;
case Qt::Key_QuoteDbl: case Qt::Key_QuoteDbl:
pressedKeys.insert("\""); pressedKeys.insert("quote");
break; break;
case Qt::Key_Comma: case Qt::Key_Comma:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp,", ",")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpcomma", "comma"));
break; break;
case Qt::Key_Less: case Qt::Key_Less:
pressedKeys.insert("<"); pressedKeys.insert("less");
break; break;
case Qt::Key_Period: case Qt::Key_Period:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp.", ".")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpperiod", "period"));
break; break;
case Qt::Key_Greater: case Qt::Key_Greater:
pressedKeys.insert(">"); pressedKeys.insert("greater");
break; break;
case Qt::Key_Slash: case Qt::Key_Slash:
pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kp/", "/")); pressedKeys.insert(GetModifiedButton(Qt::KeypadModifier, "kpslash", "slash"));
break; break;
case Qt::Key_Question: case Qt::Key_Question:
pressedKeys.insert("question"); pressedKeys.insert("question");
@ -867,7 +899,6 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
} }
break; break;
case Qt::Key_Meta: case Qt::Key_Meta:
activateWindow();
#ifdef _WIN32 #ifdef _WIN32
pressedKeys.insert("lwin"); pressedKeys.insert("lwin");
#else #else
@ -878,7 +909,6 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
pressedKeys.insert("space"); pressedKeys.insert("space");
break; break;
case Qt::Key_Up: case Qt::Key_Up:
activateWindow();
pressedKeys.insert("up"); pressedKeys.insert("up");
break; break;
case Qt::Key_Down: case Qt::Key_Down:
@ -903,81 +933,99 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
} }
return true; return true;
} }
}
if (event->type() == QEvent::MouseButtonPress) { if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event); QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
if (pressedKeys.size() < 3) { if (pressedKeys.size() < 3) {
switch (mouseEvent->button()) { switch (mouseEvent->button()) {
case Qt::LeftButton: case Qt::LeftButton:
pressedKeys.insert("leftbutton"); pressedKeys.insert("leftbutton");
break; break;
case Qt::RightButton: case Qt::RightButton:
pressedKeys.insert("rightbutton"); pressedKeys.insert("rightbutton");
break; break;
case Qt::MiddleButton: case Qt::MiddleButton:
pressedKeys.insert("middlebutton"); pressedKeys.insert("middlebutton");
break; break;
case Qt::XButton1: case Qt::XButton1:
pressedKeys.insert("sidebuttonback"); pressedKeys.insert("sidebuttonback");
break; break;
case Qt::XButton2: case Qt::XButton2:
pressedKeys.insert("sidebuttonforward"); pressedKeys.insert("sidebuttonforward");
break; break;
// default case // default case
default: default:
break; break;
// bottom text // bottom text
}
return true;
} }
return true;
} }
}
const QList<QPushButton*> AxisList = { const QList<QPushButton*> AxisList = {
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton}; ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton};
if (event->type() == QEvent::Wheel) { if (event->type() == QEvent::Wheel) {
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event); QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
if (pressedKeys.size() < 3) { if (pressedKeys.size() < 3) {
if (wheelEvent->angleDelta().y() > 5) { if (wheelEvent->angleDelta().y() > 5) {
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) ==
pressedKeys.insert("mousewheelup"); AxisList.end()) {
} else { pressedKeys.insert("mousewheelup");
QMessageBox::information(this, tr("Cannot set mapping"), if (QApplication::keyboardModifiers() == Qt::NoModifier)
tr("Mousewheel cannot be mapped to stick outputs")); emit PushKBMEvent();
} else {
QMessageBox::information(
this, tr("Cannot set mapping"),
tr("Mousewheel cannot be mapped to stick outputs"));
}
} else if (wheelEvent->angleDelta().y() < -5) {
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) ==
AxisList.end()) {
pressedKeys.insert("mousewheeldown");
if (QApplication::keyboardModifiers() == Qt::NoModifier)
emit PushKBMEvent();
} else {
QMessageBox::information(
this, tr("Cannot set mapping"),
tr("Mousewheel cannot be mapped to stick outputs"));
}
} }
} else if (wheelEvent->angleDelta().y() < -5) { if (wheelEvent->angleDelta().x() > 5) {
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { if (std::find(AxisList.begin(), AxisList.end(), MappingButton) ==
pressedKeys.insert("mousewheeldown"); AxisList.end()) {
} else { // QT changes scrolling to horizontal for all widgets with the alt modifier
QMessageBox::information(this, tr("Cannot set mapping"), pressedKeys.insert(
tr("Mousewheel cannot be mapped to stick outputs")); GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright"));
} if (QApplication::keyboardModifiers() == Qt::NoModifier)
} emit PushKBMEvent();
if (wheelEvent->angleDelta().x() > 5) { } else {
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { QMessageBox::information(
// QT changes scrolling to horizontal for all widgets with the alt modifier this, tr("Cannot set mapping"),
pressedKeys.insert( tr("Mousewheel cannot be mapped to stick outputs"));
GetModifiedButton(Qt::AltModifier, "mousewheelup", "mousewheelright")); }
} else { } else if (wheelEvent->angleDelta().x() < -5) {
QMessageBox::information(this, tr("Cannot set mapping"), if (std::find(AxisList.begin(), AxisList.end(), MappingButton) ==
tr("Mousewheel cannot be mapped to stick outputs")); AxisList.end()) {
} pressedKeys.insert(
} else if (wheelEvent->angleDelta().x() < -5) { GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft"));
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) { if (QApplication::keyboardModifiers() == Qt::NoModifier)
pressedKeys.insert( emit PushKBMEvent();
GetModifiedButton(Qt::AltModifier, "mousewheeldown", "mousewheelleft")); } else {
} else { QMessageBox::information(
QMessageBox::information(this, tr("Cannot set mapping"), this, tr("Cannot set mapping"),
tr("Mousewheel cannot be mapped to stick outputs")); tr("Mousewheel cannot be mapped to stick outputs"));
}
} }
} }
} }
}
if (event->type() == QEvent::KeyRelease || event->type() == QEvent::MouseButtonRelease)
emit PushKBMEvent();
}
return QDialog::eventFilter(obj, event); return QDialog::eventFilter(obj, event);
} }
KBMSettings::~KBMSettings() {} KBMSettings::~KBMSettings() {}

View File

@ -23,9 +23,13 @@ class KBMSettings;
class KBMSettings : public QDialog { class KBMSettings : public QDialog {
Q_OBJECT Q_OBJECT
public: public:
explicit KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget* parent = nullptr); explicit KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, bool GameRunning,
std::string GameRunningSerial, QWidget* parent = nullptr);
~KBMSettings(); ~KBMSettings();
signals:
void PushKBMEvent();
private Q_SLOTS: private Q_SLOTS:
void SaveKBMConfig(bool CloseOnSave); void SaveKBMConfig(bool CloseOnSave);
void SetDefault(); void SetDefault();
@ -44,13 +48,15 @@ private:
void DisableMappingButtons(); void DisableMappingButtons();
void EnableMappingButtons(); void EnableMappingButtons();
void SetMapping(QString input); void SetMapping(QString input);
void Cleanup();
std::string RunningGameSerial;
QSet<QString> pressedKeys; QSet<QString> pressedKeys;
bool GameRunning;
bool EnableMapping = false; bool EnableMapping = false;
bool MappingCompleted = false; bool MappingCompleted = false;
bool HelpWindowOpen = false; bool HelpWindowOpen = false;
QString mapping; QString mapping;
QString modifier;
int MappingTimer; int MappingTimer;
QTimer* timer; QTimer* timer;
QPushButton* MappingButton; QPushButton* MappingButton;

View File

@ -121,35 +121,6 @@ To view the config file's syntax, check out the Syntax tab, for keybind names, v
This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)"; This project began because I disliked the original, unchangeable keybinds. Rather than waiting for someone else to do it, I implemented this myself. From the default keybinds, you can clearly tell this was a project built for Bloodborne, but obviously, you can make adjustments however you like.)";
} }
QString HelpDialog::faq() {
return R"(
Q: What are the emulator-wide keybinds?
A:
-F12: Triggers Renderdoc capture
-F11: Toggles fullscreen
-F10: Toggles FPS counter
-Ctrl+F10: Open the debug menu
-F9: Pauses the emulator if the debug menu is open
-F8: Reparses the config file while in-game
-F7: Toggles mouse capture and mouse input
-F6: Toggles mouse-to-gyro emulation
Q: How do I switch between mouse and controller joystick input? Why is it even required?
A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it.
Q: What happens if I accidentally make a typo in the config?
A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'.
Q: I want to bind <input> to <output>, but your code doesn't support <input>!
A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4.
Q: What does default.ini do?
A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead.
Q: What does the use Per-game Config checkbox do?
A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)";
}
QString HelpDialog::syntax() { QString HelpDialog::syntax() {
return R"( return R"(
Below is the file format for mouse, keyboard, and controller inputs: Below is the file format for mouse, keyboard, and controller inputs:
@ -187,13 +158,12 @@ Keyboard:
Numbers: Numbers:
'0', '1', ..., '9' '0', '1', ..., '9'
Keypad: Keypad:
'kp 0', 'kp 1', ..., 'kp 9', 'kp0', 'kp1', ..., 'kp9',
'kp .', 'kp ,', 'kp /', 'kp *', 'kp -', 'kp +', 'kp =', 'kp enter' 'kpperiod', 'kpcomma', 'kpslash', 'kpasterisk', 'kpminus', 'kpplus', 'kpequals', 'kpenter'
Symbols: Symbols:
'`', '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '-', '_', '=', '+', '{', '}', '[', ']', '\', '|', (See below)
';', ':', ''', '"', ',', '<', '.', '>', '/', '?'
Special keys: Special keys:
'escape (text editor only)', 'printscreen', 'scrolllock', 'pausebreak', 'escape' (text editor only), 'printscreen', 'scrolllock', 'pausebreak',
'backspace', 'insert', 'delete', 'home', 'end', 'pgup', 'pgdown', 'tab', 'backspace', 'insert', 'delete', 'home', 'end', 'pgup', 'pgdown', 'tab',
'capslock', 'enter', 'space' 'capslock', 'enter', 'space'
Arrow keys: Arrow keys:
@ -228,7 +198,38 @@ Controller:
'l2' 'l2'
Invalid Inputs: Invalid Inputs:
'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs.)"; 'F1-F12' are reserved for emulator-wide keybinds, and cannot be bound to controller inputs.
Symbols (expanded):
` 'grave'
~ 'tilde'
! 'exclamation'
@ 'at'
# 'hash'
$ 'dollar'
% 'percent'
^ 'caret'
& 'ampersand'
* 'asterisk'
( 'lparen'
- 'minus'
_ 'underscore'
= 'equals'
+ 'plus'
[ 'lbracket'
{ 'lbrace'
\ 'backslash'
| 'pipe'
; 'semicolon'
: 'colon'
' 'apostrophe'
" 'quote'
, 'comma'
< 'less'
. 'period'
> 'greater'
/ 'slash'
? 'question')";
} }
QString HelpDialog::special() { QString HelpDialog::special() {
@ -267,3 +268,35 @@ You can find these here, with detailed comments, examples, and suggestions for m
'mouse_gyro_roll_mode': 'mouse_gyro_roll_mode':
Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active.)"; Controls whether moving the mouse sideways causes a panning or a rolling motion while mouse-to-gyro emulation is active.)";
} }
QString HelpDialog::faq() {
return R"(
Q: What are the emulator-wide keybinds?
A:
-F12: Triggers Renderdoc capture
-F11: Toggles fullscreen
-F10: Toggles FPS counter
-Ctrl+F10: Open the debug menu
-F9: Pauses the emulator if the debug menu is open
-F8: Reparses the config file while in-game
-F7: Toggles mouse capture and mouse input
-F6: Toggles mouse-to-gyro emulation
Q: How do I switch between mouse and controller joystick input? Why is it even required?
A: Pressing F7 toggles between mouse and controller joystick input. It is required because the program polls the mouse input, which means it checks mouse movement every frame. If it didn't move, the code would manually set the emulator's virtual controller to 0 (to the center), even if other input devices would update it.
Q: What in the world is a 'grave' key?
A: (`). It represents one of the many symbols you can bind to a key. You can find the various symbols and their names in the Bindings tab.
Q: What happens if I accidentally make a typo in the config?
A: The code recognises the line as wrong and skips it, so the rest of the file will get parsed, but that line in question will be treated like a comment line. You can find these lines in the log if you search for 'input_handler'.
Q: I want to bind <input> to <output>, but your code doesn't support <input>!
A: Some keys are intentionally omitted, but if you read the bindings through, and you're sure it is not there and isn't one of the intentionally disabled ones, open an issue on https://github.com/shadps4-emu/shadPS4.
Q: What does default.ini do?
A: If you're using per-game configs, it's the base from which all new games generate their config file. If you use the unified config, then default.ini is used for every game directly instead.
Q: What does the use Per-game Config checkbox do?
A: It controls whether the config is loaded from CUSAXXXXX.ini for a game or from default.ini. This way, if you only want to manage one set of bindings, you can do so, but if you want to use a different setup for every game, that's possible as well.)";
}

View File

@ -473,12 +473,13 @@ void MainWindow::CreateConnects() {
}); });
connect(ui->controllerButton, &QPushButton::clicked, this, [this]() { connect(ui->controllerButton, &QPushButton::clicked, this, [this]() {
auto configWindow = new ControlSettings(m_game_info, this); ControlSettings* remapWindow =
configWindow->exec(); new ControlSettings(m_game_info, isGameRunning, runningGameSerial, this);
remapWindow->exec();
}); });
connect(ui->keyboardButton, &QPushButton::clicked, this, [this]() { connect(ui->keyboardButton, &QPushButton::clicked, this, [this]() {
auto kbmWindow = new KBMSettings(m_game_info, this); auto kbmWindow = new KBMSettings(m_game_info, isGameRunning, runningGameSerial, this);
kbmWindow->exec(); kbmWindow->exec();
}); });
@ -846,12 +847,14 @@ void MainWindow::StartGame() {
if (m_game_list_frame->currentItem()) { if (m_game_list_frame->currentItem()) {
int itemID = m_game_list_frame->currentItem()->row(); int itemID = m_game_list_frame->currentItem()->row();
Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin");
runningGameSerial = m_game_info->m_games[itemID].serial;
} }
} else if (table_mode == 1) { } else if (table_mode == 1) {
if (m_game_grid_frame->cellClicked) { if (m_game_grid_frame->cellClicked) {
int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) + int itemID = (m_game_grid_frame->crtRow * m_game_grid_frame->columnCnt) +
m_game_grid_frame->crtColumn; m_game_grid_frame->crtColumn;
Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin"); Common::FS::PathToQString(gamePath, m_game_info->m_games[itemID].path / "eboot.bin");
runningGameSerial = m_game_info->m_games[itemID].serial;
} }
} else { } else {
if (m_elf_viewer->currentItem()) { if (m_elf_viewer->currentItem()) {

View File

@ -75,11 +75,13 @@ private:
void PlayBackgroundMusic(); void PlayBackgroundMusic();
QIcon RecolorIcon(const QIcon& icon, bool isWhite); QIcon RecolorIcon(const QIcon& icon, bool isWhite);
void StartEmulator(std::filesystem::path); void StartEmulator(std::filesystem::path);
bool isIconBlack = false; bool isIconBlack = false;
bool isTableList = true; bool isTableList = true;
bool isGameRunning = false; bool isGameRunning = false;
bool isWhite = false; bool isWhite = false;
bool is_paused = false; bool is_paused = false;
std::string runningGameSerial = "";
QActionGroup* m_icon_size_act_group = nullptr; QActionGroup* m_icon_size_act_group = nullptr;
QActionGroup* m_list_mode_act_group = nullptr; QActionGroup* m_list_mode_act_group = nullptr;

View File

@ -0,0 +1,47 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "sdl_event_wrapper.h"
using namespace SdlEventWrapper;
Wrapper* Wrapper::WrapperInstance = nullptr;
bool Wrapper::wrapperActive = false;
Wrapper::Wrapper(QObject* parent) : QObject(parent) {}
Wrapper* Wrapper::GetInstance() {
if (WrapperInstance == nullptr) {
WrapperInstance = new Wrapper();
}
return WrapperInstance;
}
bool Wrapper::ProcessEvent(SDL_Event* event) {
switch (event->type) {
case SDL_EVENT_WINDOW_RESTORED:
return false;
case SDL_EVENT_WINDOW_EXPOSED:
return false;
case SDL_EVENT_GAMEPAD_ADDED:
return false;
case SDL_EVENT_GAMEPAD_REMOVED:
return false;
case SDL_EVENT_QUIT:
emit SDLEvent(SDL_EVENT_QUIT, 0, 0);
return true;
case SDL_EVENT_GAMEPAD_BUTTON_DOWN:
emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_DOWN, event->gbutton.button, 0);
return true;
case SDL_EVENT_GAMEPAD_BUTTON_UP:
emit SDLEvent(SDL_EVENT_GAMEPAD_BUTTON_UP, event->gbutton.button, 0);
return true;
case SDL_EVENT_GAMEPAD_AXIS_MOTION:
emit SDLEvent(SDL_EVENT_GAMEPAD_AXIS_MOTION, event->gaxis.axis, event->gaxis.value);
return true;
// block all other SDL events while wrapper is active
default:
return true;
}
}
Wrapper::~Wrapper() {}

View File

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QObject>
#include <SDL3/SDL_events.h>
namespace SdlEventWrapper {
class Wrapper : public QObject {
Q_OBJECT
public:
explicit Wrapper(QObject* parent = nullptr);
~Wrapper();
bool ProcessEvent(SDL_Event* event);
static Wrapper* GetInstance();
static bool wrapperActive;
static Wrapper* WrapperInstance;
signals:
void SDLEvent(int Type, int Input, int Value);
};
} // namespace SdlEventWrapper

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>استخدام إعدادات كل لُعْبَة</translation> <translation>استخدام إعدادات كل لُعْبَة</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>رجوع</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>الخيارات / البَدْء</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>الأزرار</translation> <translation>الأزرار</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>مثلث / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>مربع / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>دائرة / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>إكس / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>النقطة العمياء للعصا اليمنى (الافتراضي: 2، الحد الأقصى: 127)</translation> <translation>النقطة العمياء للعصا اليمنى (الافتراضي: 2، الحد الأقصى: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>إلغاء</translation> <translation>إلغاء</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>آخر تحديث</translation> <translation>آخر تحديث</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>عارض SFO لـ </translation> <translation>عارض SFO لـ </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Fes servir configuracions per cada joc</translation> <translation>Fes servir configuracions per cada joc</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Torna</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Opcions / Executa</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Botons d'acció</translation> <translation>Botons d'acció</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Quadrat / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Cercle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Creu / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona morta de la palanca dreta (per defecte:2 màxim:127)</translation> <translation>Zona morta de la palanca dreta (per defecte:2 màxim:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancel·la</translation> <translation>Cancel·la</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Darrera actualització</translation> <translation>Darrera actualització</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Preferit</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visualitzador SFO per </translation> <translation>Visualitzador SFO per </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Esborra dels preferits</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Afegeix a preferits</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Sidst opdateret</translation> <translation>Sidst opdateret</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Benutze Per-Game Einstellungen</translation> <translation>Benutze Per-Game Einstellungen</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Zurück</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Aktionstasten</translation> <translation>Aktionstasten</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Dreieck / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Quadrat / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Kreis / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Kreuz / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Rechter Stick tote Zone (def:2, max:127)</translation> <translation>Rechter Stick tote Zone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Abbrechen</translation> <translation>Abbrechen</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Zuletzt aktualisiert</translation> <translation>Zuletzt aktualisiert</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO-Betrachter für </translation> <translation>SFO-Betrachter für </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Τελευταία ενημέρωση</translation> <translation>Τελευταία ενημέρωση</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -565,6 +525,72 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Usar configuraciones por juego</translation> <translation>Usar configuraciones por juego</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1/LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2/LT</translation>
</message>
<message>
<source>Back</source>
<translation>Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1/RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2/RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options/Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Botones de acción</translation> <translation>Botones de acción</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triángulo/Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Cuadrado/X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Círculo/B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Cruz / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona muerta del stick derecho (defecto: 2, máx.: 127)</translation> <translation>Zona muerta del stick derecho (defecto: 2, máx.: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Última actualización</translation> <translation>Última actualización</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visualizador de SFO para </translation> <translation>Visualizador de SFO para </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>از پیکربندیهای مخصوص هر بازی استفاده کنید</translation> <translation>از پیکربندیهای مخصوص هر بازی استفاده کنید</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>مثلث / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>آخرین بهروزرسانی</translation> <translation>آخرین بهروزرسانی</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO مشاهده </translation> <translation>SFO مشاهده </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Käytä pelikohtaisia asetuksia</translation> <translation>Käytä pelikohtaisia asetuksia</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Etunäppäimet</translation> <translation>Etunäppäimet</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Kolmio / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Neliö / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Ympyrä / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Rasti / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Oikean Analogin Deadzone (oletus:2 max:127)</translation> <translation>Oikean Analogin Deadzone (oletus:2 max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Peruuta</translation> <translation>Peruuta</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Viimeksi päivitetty</translation> <translation>Viimeksi päivitetty</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Utiliser les configurations par jeu</translation> <translation>Utiliser les configurations par jeu</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Retour</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Touches d'action</translation> <translation>Touches d'action</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Carré / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Rond / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Croix / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Joystick Gauche Deadzone (def:2 max:127)</translation> <translation>Joystick Gauche Deadzone (def:2 max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Annuler</translation> <translation>Annuler</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Dernière mise à jour</translation> <translation>Dernière mise à jour</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visionneuse SFO pour </translation> <translation>Visionneuse SFO pour </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Utoljára frissítve</translation> <translation>Utoljára frissítve</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Terakhir diperbarui</translation> <translation>Terakhir diperbarui</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Usa configurazioni per gioco</translation> <translation>Usa configurazioni per gioco</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Indietro</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Opzioni / Avvio</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Pulsanti Frontali</translation> <translation>Pulsanti Frontali</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triangolo / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Quadrato / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Cerchio / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Croce / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona Morta Levetta Destra (def:2 max:127)</translation> <translation>Zona Morta Levetta Destra (def:2 max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Annulla</translation> <translation>Annulla</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Ultimo aggiornamento</translation> <translation>Ultimo aggiornamento</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visualizzatore SFO per </translation> <translation>Visualizzatore SFO per </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation></translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation> / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation> / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation> / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation> / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>既定:2, 最大:127</translation> <translation>既定:2, 最大:127</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation></translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation> / </translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation> </translation> <translation> </translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Atgal</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Atšaukti</translation> <translation>Atšaukti</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Paskutinį kartą atnaujinta</translation> <translation>Paskutinį kartą atnaujinta</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Bruk oppsett per spill</translation> <translation>Bruk oppsett per spill</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Tilbake</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Handlingsknapper</translation> <translation>Handlingsknapper</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triangel / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Firkant / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Sirkel / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Kryss / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Høyre analog dødsone (def:2, maks:127)</translation> <translation>Høyre analog dødsone (def:2, maks:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Avbryt</translation> <translation>Avbryt</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Sist oppdatert</translation> <translation>Sist oppdatert</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Favoritter</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO-viser for </translation> <translation>SFO-viser for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Fjern fra favoritter</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Legg til i favoritter</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>
@ -1078,7 +1118,7 @@
</message> </message>
<message> <message>
<source>note: click Help Button/Special Keybindings for more information</source> <source>note: click Help Button/Special Keybindings for more information</source>
<translation>Merk: Trykk hjelpeknappen for mer informasjon</translation> <translation>Merk: Trykk «Hjelp»-knappen for mer informasjon</translation>
</message> </message>
<message> <message>
<source>Face Buttons</source> <source>Face Buttons</source>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -582,7 +610,7 @@
</message> </message>
<message> <message>
<source>Could not open the file for reading</source> <source>Could not open the file for reading</source>
<translation/> <translation type="unfinished">Could not open the file for reading</translation>
</message> </message>
<message> <message>
<source>Could not open the file for writing</source> <source>Could not open the file for writing</source>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Laatst bijgewerkt</translation> <translation>Laatst bijgewerkt</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Użyj osobnej konfiguracji dla każdej gry</translation> <translation>Użyj osobnej konfiguracji dla każdej gry</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Wstecz</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Opcje / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Przyciski akcji</translation> <translation>Przyciski akcji</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Trójkąt / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Kwadrat / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Kółko / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Krzyżyk / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Martwa strefa prawego drążka (def:2 max:127)</translation> <translation>Martwa strefa prawego drążka (def:2 max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Anuluj</translation> <translation>Anuluj</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Ostatnia aktualizacja</translation> <translation>Ostatnia aktualizacja</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Menedżer plików SFO dla </translation> <translation>Menedżer plików SFO dla </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Usar configurações por jogo</translation> <translation>Usar configurações por jogo</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Voltar</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Botões de Ação</translation> <translation>Botões de Ação</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triângulo / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Quadrado / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Círculo / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Cruz / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona Morta do Analógico Direito (Pad: 2, Máx: 127)</translation> <translation>Zona Morta do Analógico Direito (Pad: 2, Máx: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation>não mapeado</translation>
</message>
<message>
<source>L1</source>
<translation>L1</translation>
</message>
<message>
<source>R1</source>
<translation>R1</translation>
</message>
<message>
<source>L2</source>
<translation>L2</translation>
</message>
<message>
<source>Options</source>
<translation>Opções</translation>
</message>
<message>
<source>R2</source>
<translation>R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation>Touchpad Esquerdo</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation>Touchpad Direito</translation>
</message>
<message>
<source>Triangle</source>
<translation>Triângulo</translation>
</message>
<message>
<source>Square</source>
<translation>Quadrado</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation>Não é possível atribuir a mesma entrada mais de uma vez. Entradas duplicadas foram atribuídas aos seguintes botões:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation>Pressione um botão</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Última atualização</translation> <translation>Última atualização</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Favorito</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visualizador de SFO para </translation> <translation>Visualizador de SFO para </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Remover dos Favoritos</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Adicionar aos Favoritos</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Utilizar configurações por jogo</translation> <translation>Utilizar configurações por jogo</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Voltar</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Opções / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Botões Frontais</translation> <translation>Botões Frontais</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triângulo / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Quadrado / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Círculo / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Cruz / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona Morta do Manípulo Direito (def: 2, max: 127)</translation> <translation>Zona Morta do Manípulo Direito (def: 2, max: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Última atualização</translation> <translation>Última atualização</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Visualizador SFO para </translation> <translation>Visualizador SFO para </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Ultima actualizare</translation> <translation>Ultima actualizare</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Использовать настройки для каждой игры</translation> <translation>Использовать настройки для каждой игры</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Назад</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Кнопки действий</translation> <translation>Кнопки действий</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Треугольник / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Квадрат / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Круг / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Крест / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Мёртвая зона правого стика (по умолч:2 макс:127)</translation> <translation>Мёртвая зона правого стика (по умолч:2 макс:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Отмена</translation> <translation>Отмена</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation>не назначено</translation>
</message>
<message>
<source>L1</source>
<translation>L1</translation>
</message>
<message>
<source>R1</source>
<translation>R1</translation>
</message>
<message>
<source>L2</source>
<translation>L2</translation>
</message>
<message>
<source>Options</source>
<translation>Options</translation>
</message>
<message>
<source>R2</source>
<translation>R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation>Тачпад слева</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation>Тачпад центр</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation>Тачпад справа</translation>
</message>
<message>
<source>Triangle</source>
<translation>Треугольник</translation>
</message>
<message>
<source>Square</source>
<translation>Квадрат</translation>
</message>
<message>
<source>Circle</source>
<translation>Круг</translation>
</message>
<message>
<source>Cross</source>
<translation>Крест</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation>Невозможно привязать уникальный ввод более одного раза. Дублированные вводы назначены на следующие кнопки:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation>Нажмите кнопку</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Последнее обновление</translation> <translation>Последнее обновление</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Избранное</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Просмотр SFO для</translation> <translation>Просмотр SFO для</translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Удалить из избранного</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Добавить в избранное</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation type="unfinished">Last updated</translation> <translation type="unfinished">Last updated</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Përdor konfigurime veçanta për secilën lojë</translation> <translation>Përdor konfigurime veçanta për secilën lojë</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Mbrapa</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Butonat kryesore</translation> <translation>Butonat kryesore</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Trekëndësh / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Katror / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Rreth / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Kryq / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Zona e vdekur e levës djathtë (def:2, max:127)</translation> <translation>Zona e vdekur e levës djathtë (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Anulo</translation> <translation>Anulo</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Përditësuar për herë fundit</translation> <translation>Përditësuar për herë fundit</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation> Preferuarat</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Shikuesi SFO për </translation> <translation>Shikuesi SFO për </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Hiq nga Preferuarat</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Shto Preferuarat</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>
@ -1186,15 +1226,15 @@
</message> </message>
<message> <message>
<source>Touchpad Left</source> <source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation> <translation>Paneli me Prekje Majtas</translation>
</message> </message>
<message> <message>
<source>Touchpad Center</source> <source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation> <translation>Paneli me Prekje Qendër</translation>
</message> </message>
<message> <message>
<source>Touchpad Right</source> <source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation> <translation>Paneli me Prekje Djathtas</translation>
</message> </message>
</context> </context>
<context> <context>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation type="unfinished">Use per-game configs</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation type="unfinished">L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation type="unfinished">L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation type="unfinished">Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation type="unfinished">R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation type="unfinished">R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation type="unfinished">L3</translation> <translation type="unfinished">L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation type="unfinished">Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation type="unfinished">R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation type="unfinished">Face Buttons</translation> <translation type="unfinished">Face Buttons</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation type="unfinished">Square / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation type="unfinished">Circle / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation type="unfinished">Cross / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation type="unfinished">Last updated</translation> <translation type="unfinished">Last updated</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Använd konfigurationer per spel</translation> <translation>Använd konfigurationer per spel</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Bakåt</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Handlingsknappar</translation> <translation>Handlingsknappar</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Triangel / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Fyrkant / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Cirkel / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Kryss / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Dödläge för höger spak (standard:2, max:127)</translation> <translation>Dödläge för höger spak (standard:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Avbryt</translation> <translation>Avbryt</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Senast uppdaterad</translation> <translation>Senast uppdaterad</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Favorit</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO-visare för </translation> <translation>SFO-visare för </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation>Ta bort från favoriter</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Lägg till i favoriter</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Oyuna özel yapılandırma kullan</translation> <translation>Oyuna özel yapılandırma kullan</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Geri</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Seçenekler / Başlat</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Eylem Düğmeleri</translation> <translation>Eylem Düğmeleri</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Üçgen / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Kare / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Daire / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Çarpı / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Sağ Analog Ö Bölgesi (varsayılan: 2, en çok: 127)</translation> <translation>Sağ Analog Ö Bölgesi (varsayılan: 2, en çok: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>İptal</translation> <translation>İptal</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation>atanmamış</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation>Seçenekler</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation>Üçgen</translation>
</message>
<message>
<source>Square</source>
<translation>Kare</translation>
</message>
<message>
<source>Circle</source>
<translation>Yuvarlak</translation>
</message>
<message>
<source>Cross</source>
<translation>Çarpı</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation>Aynı tuş birden fazla kez atanamaz. Aşağıdaki tuşlara birden fazla giriş atanmış:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation>Bir Düğmeye Bas</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Son güncelleme</translation> <translation>Son güncelleme</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation>Sık Kullanılan</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO Görüntüleyici: </translation> <translation>SFO Görüntüleyici: </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation>Favorilere Ekle</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>
@ -1180,7 +1220,7 @@
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: <source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source> %1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons: <translation>Aynı tuş birden fazla kez atanamaz. Aşağıdaki tuşlara birden fazla giriş atanmış:
%1</translation> %1</translation>
</message> </message>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Використовувати ігрові конфігурації</translation> <translation>Використовувати ігрові конфігурації</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / Лівий Бампер</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / Лівий Тригер</translation>
</message>
<message>
<source>Back</source>
<translation>Назад</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / Правий Бампер</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / Правий Тригер</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>Кнопка лівого стику</translation> <translation>Кнопка лівого стику</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Опції / Старт</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>Кнопка правого стику</translation> <translation>Кнопка правого стику</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Лицьові кнопки</translation> <translation>Лицьові кнопки</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Трикутник / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Квадрат / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Коло / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Хрест / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>Мертва зона правого стику (за замов.: 2, максимум: 127)</translation> <translation>Мертва зона правого стику (за замов.: 2, максимум: 127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Відмінити</translation> <translation>Відмінити</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Останнє оновлення</translation> <translation>Останнє оновлення</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>Перегляд SFO </translation> <translation>Перегляд SFO </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>Cấu hình riêng cho từng game</translation> <translation>Cấu hình riêng cho từng game</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Quay Lại</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Tuỳ chọn / Bắt đu</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation>Nút bấm mặt trước</translation> <translation>Nút bấm mặt trước</translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation>Tam giác / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation>Vuông / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation>Tròn / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation>Chéo / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation> <translation type="unfinished">Right Stick Deadzone (def:2, max:127)</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Hủy</translation> <translation>Hủy</translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation>Cập nhật lần cuối</translation> <translation>Cập nhật lần cuối</translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -924,7 +956,7 @@
</message> </message>
<message> <message>
<source>No log file found for this game!</source> <source>No log file found for this game!</source>
<translation>!</translation> <translation/>
</message> </message>
<message> <message>
<source>Failed to convert icon.</source> <source>Failed to convert icon.</source>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation type="unfinished">SFO Viewer for </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation> / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation> / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation> / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation> / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>2 127</translation> <translation>2 127</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Favorite</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO - </translation> <translation>SFO - </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation></translation>
</message>
<message>
<source>Add to Favorites</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -453,34 +453,10 @@
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation>使</translation> <translation>使</translation>
</message> </message>
<message>
<source>L1 / LB</source>
<translation>L1 / LB</translation>
</message>
<message>
<source>L2 / LT</source>
<translation>L2 / LT</translation>
</message>
<message>
<source>Back</source>
<translation>Back</translation>
</message>
<message>
<source>R1 / RB</source>
<translation>R1 / RB</translation>
</message>
<message>
<source>R2 / RT</source>
<translation>R2 / RT</translation>
</message>
<message> <message>
<source>L3</source> <source>L3</source>
<translation>L3</translation> <translation>L3</translation>
</message> </message>
<message>
<source>Options / Start</source>
<translation>Options / Start</translation>
</message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation>R3</translation> <translation>R3</translation>
@ -489,22 +465,6 @@
<source>Face Buttons</source> <source>Face Buttons</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Triangle / Y</source>
<translation> / Y</translation>
</message>
<message>
<source>Square / X</source>
<translation> / X</translation>
</message>
<message>
<source>Circle / B</source>
<translation> / B</translation>
</message>
<message>
<source>Cross / A</source>
<translation> / A</translation>
</message>
<message> <message>
<source>Right Stick Deadzone (def:2, max:127)</source> <source>Right Stick Deadzone (def:2, max:127)</source>
<translation>2 127</translation> <translation>2 127</translation>
@ -565,6 +525,74 @@
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>unmapped</source>
<translation type="unfinished">unmapped</translation>
</message>
<message>
<source>L1</source>
<translation type="unfinished">L1</translation>
</message>
<message>
<source>R1</source>
<translation type="unfinished">R1</translation>
</message>
<message>
<source>L2</source>
<translation type="unfinished">L2</translation>
</message>
<message>
<source>Options</source>
<translation type="unfinished">Options</translation>
</message>
<message>
<source>R2</source>
<translation type="unfinished">R2</translation>
</message>
<message>
<source>Touchpad Left</source>
<translation type="unfinished">Touchpad Left</translation>
</message>
<message>
<source>Touchpad Center</source>
<translation type="unfinished">Touchpad Center</translation>
</message>
<message>
<source>Touchpad Right</source>
<translation type="unfinished">Touchpad Right</translation>
</message>
<message>
<source>Triangle</source>
<translation type="unfinished">Triangle</translation>
</message>
<message>
<source>Square</source>
<translation type="unfinished">Square</translation>
</message>
<message>
<source>Circle</source>
<translation type="unfinished">Circle</translation>
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
<message>
<source>Press a button</source>
<translation type="unfinished">Press a button</translation>
</message>
<message>
<source>Move analog stick</source>
<translation type="unfinished">Move analog stick</translation>
</message>
</context> </context>
<context> <context>
<name>EditorDialog</name> <name>EditorDialog</name>
@ -748,6 +776,10 @@
<source>Last updated</source> <source>Last updated</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Favorite</source>
<translation type="unfinished">Favorite</translation>
</message>
</context> </context>
<context> <context>
<name>GameListUtils</name> <name>GameListUtils</name>
@ -950,6 +982,14 @@
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation>SFO </translation> <translation>SFO </translation>
</message> </message>
<message>
<source>Remove from Favorites</source>
<translation type="unfinished">Remove from Favorites</translation>
</message>
<message>
<source>Add to Favorites</source>
<translation type="unfinished">Add to Favorites</translation>
</message>
</context> </context>
<context> <context>
<name>HelpDialog</name> <name>HelpDialog</name>

View File

@ -20,6 +20,10 @@
#include "sdl_window.h" #include "sdl_window.h"
#include "video_core/renderdoc.h" #include "video_core/renderdoc.h"
#ifdef ENABLE_QT_GUI
#include "qt_gui/sdl_event_wrapper.h"
#endif
#ifdef __APPLE__ #ifdef __APPLE__
#include "SDL3/SDL_metal.h" #include "SDL3/SDL_metal.h"
#endif #endif
@ -340,6 +344,13 @@ void WindowSDL::WaitEvent() {
return; return;
} }
#ifdef ENABLE_QT_GUI
if (SdlEventWrapper::Wrapper::wrapperActive) {
if (SdlEventWrapper::Wrapper::GetInstance()->ProcessEvent(&event))
return;
}
#endif
if (ImGui::Core::ProcessEvent(&event)) { if (ImGui::Core::ProcessEvent(&event)) {
return; return;
} }

View File

@ -300,7 +300,7 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct
if (stage == LogicalStage::TessellationControl || stage == LogicalStage::TessellationEval) { if (stage == LogicalStage::TessellationControl || stage == LogicalStage::TessellationEval) {
ctx.AddCapability(spv::Capability::Tessellation); ctx.AddCapability(spv::Capability::Tessellation);
} }
if (info.dma_types != IR::Type::Void) { if (info.uses_dma) {
ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses); ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
ctx.AddExtension("SPV_KHR_physical_storage_buffer"); ctx.AddExtension("SPV_KHR_physical_storage_buffer");
} }
@ -310,6 +310,19 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct
ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR); ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR); ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
} }
if (info.uses_buffer_int64_atomics || info.uses_shared_int64_atomics) {
if (info.uses_buffer_int64_atomics) {
ASSERT_MSG(ctx.profile.supports_buffer_int64_atomics,
"Shader requires support for atomic Int64 buffer operations that your "
"Vulkan instance does not advertise");
}
if (info.uses_shared_int64_atomics) {
ASSERT_MSG(ctx.profile.supports_shared_int64_atomics,
"Shader requires support for atomic Int64 shared memory operations that "
"your Vulkan instance does not advertise");
}
ctx.AddCapability(spv::Capability::Int64Atomics);
}
} }
void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) { void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) {

View File

@ -7,7 +7,11 @@
#include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h"
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
using PointerType = EmitContext::PointerType;
using PointerSize = EmitContext::PointerSize;
std::pair<Id, Id> AtomicArgs(EmitContext& ctx) { std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
const Id scope{ctx.ConstU32(static_cast<u32>(spv::Scope::Device))}; const Id scope{ctx.ConstU32(static_cast<u32>(spv::Scope::Device))};
const Id semantics{ctx.u32_zero_value}; const Id semantics{ctx.u32_zero_value};
@ -61,14 +65,13 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id
return ctx.U32[1]; return ctx.U32[1];
} }
}(); }();
if (Sirit::ValidId(buffer.offset)) { if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address);
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, 1, is_float>(ctx, index, buffer.size_dwords, [&] { return AccessBoundsCheck<32, 1, is_float>(ctx, address, buffer.Size(PointerSize::B32), [&] {
return (ctx.*atomic_func)(type, ptr, scope, semantics, value); return (ctx.*atomic_func)(type, ptr, scope, semantics, value);
}); });
} }
@ -76,14 +79,13 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id
Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
const auto& buffer = ctx.buffers[handle]; const auto& buffer = ctx.buffers[handle];
if (Sirit::ValidId(buffer.offset)) { if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address);
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>(ctx, address, buffer.Size(PointerSize::B32), [&] {
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics); return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics);
}); });
} }
@ -92,14 +94,13 @@ Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
Id cmp_value, Id cmp_value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) {
const auto& buffer = ctx.buffers[handle]; const auto& buffer = ctx.buffers[handle];
if (Sirit::ValidId(buffer.offset)) { if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u)); const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address);
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>(ctx, address, buffer.Size(PointerSize::B32), [&] {
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value); return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value);
}); });
} }
@ -107,14 +108,13 @@ Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
Id BufferAtomicU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id BufferAtomicU64(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];
if (Sirit::ValidId(buffer.offset)) { if (const Id offset = buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); const auto [id, pointer_type] = buffer.Alias(PointerType::U64);
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U64]; const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address);
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<64>(ctx, index, buffer.size_qwords, [&] { return AccessBoundsCheck<64>(ctx, address, buffer.Size(PointerSize::B64), [&] {
return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value); return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value);
}); });
} }
@ -226,10 +226,18 @@ Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax); return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax);
} }
Id EmitBufferAtomicSMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMax);
}
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax); return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
} }
Id EmitBufferAtomicUMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
}
Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
if (ctx.profile.supports_buffer_fp32_atomic_min_max) { if (ctx.profile.supports_buffer_fp32_atomic_min_max) {
return BufferAtomicU32<true>(ctx, inst, handle, address, value, return BufferAtomicU32<true>(ctx, inst, handle, address, value,
@ -360,7 +368,7 @@ Id EmitImageAtomicExchange32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id co
Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) { Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) {
const auto& buffer = ctx.buffers[binding]; const auto& buffer = ctx.buffers[binding];
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr));
const auto [scope, semantics]{AtomicArgs(ctx)}; const auto [scope, semantics]{AtomicArgs(ctx)};
return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics); return ctx.OpAtomicIIncrement(ctx.U32[1], ptr, scope, semantics);
@ -368,7 +376,7 @@ Id EmitDataAppend(EmitContext& ctx, u32 gds_addr, u32 binding) {
Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) { Id EmitDataConsume(EmitContext& ctx, u32 gds_addr, u32 binding) {
const auto& buffer = ctx.buffers[binding]; const auto& buffer = ctx.buffers[binding];
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32]; const auto [id, pointer_type] = buffer.Alias(PointerType::U32);
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr)); const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, ctx.ConstU32(gds_addr));
const auto [scope, semantics]{AtomicArgs(ctx)}; const auto [scope, semantics]{AtomicArgs(ctx)};
return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics); return ctx.OpAtomicIDecrement(ctx.U32[1], ptr, scope, semantics);

View File

@ -3,6 +3,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h"
#include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/attribute.h"
@ -11,8 +12,6 @@
#include <magic_enum/magic_enum.hpp> #include <magic_enum/magic_enum.hpp>
#include "emit_spirv_bounds.h"
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
@ -164,6 +163,7 @@ void EmitGetGotoVariable(EmitContext&) {
} }
using PointerType = EmitContext::PointerType; using PointerType = EmitContext::PointerType;
using PointerSize = EmitContext::PointerSize;
Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) { Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) {
const u32 flatbuf_off_dw = inst->Flags<u32>(); const u32 flatbuf_off_dw = inst->Flags<u32>();
@ -179,14 +179,15 @@ Id EmitReadConst(EmitContext& ctx, IR::Inst* inst, Id addr, Id offset) {
template <PointerType type> template <PointerType type>
Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) { Id ReadConstBuffer(EmitContext& ctx, u32 handle, Id index) {
const auto& buffer = ctx.buffers[handle]; const auto& buffer = ctx.buffers[handle];
index = ctx.OpIAdd(ctx.U32[1], index, buffer.offset_dwords); if (const Id offset = buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
const auto [id, pointer_type] = buffer[type]; index = ctx.OpIAdd(ctx.U32[1], index, offset);
}
const auto [id, pointer_type] = buffer.Alias(type);
const auto value_type = type == PointerType::U32 ? ctx.U32[1] : ctx.F32[1]; const auto value_type = type == PointerType::U32 ? ctx.U32[1] : ctx.F32[1];
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
const Id result{ctx.OpLoad(value_type, ptr)}; const Id result{ctx.OpLoad(value_type, ptr)};
if (const Id size = buffer.Size(PointerSize::B32); Sirit::ValidId(size)) {
if (Sirit::ValidId(buffer.size_dwords)) { const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, size);
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer.size_dwords);
return ctx.OpSelect(value_type, in_bounds, result, ctx.u32_zero_value); return ctx.OpSelect(value_type, in_bounds, result, ctx.u32_zero_value);
} }
return result; return result;
@ -419,25 +420,24 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
template <u32 N, PointerType alias> template <u32 N, PointerType alias>
static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
constexpr bool is_float = alias == PointerType::F32;
const auto flags = inst->Flags<IR::BufferInstInfo>(); const auto flags = inst->Flags<IR::BufferInstInfo>();
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32;
const auto [id, pointer_type] = spv_buffer[alias]; const auto [id, pointer_type] = spv_buffer.Alias(alias);
boost::container::static_vector<Id, N> ids; boost::container::static_vector<Id, N> ids;
for (u32 i = 0; i < N; i++) { for (u32 i = 0; i < N; i++) {
const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); const Id index_i = i == 0 ? address : ctx.OpIAdd(ctx.U32[1], address, ctx.ConstU32(i));
const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i); const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i);
const Id result_i = ctx.OpLoad(data_types[1], ptr_i); const Id result_i = ctx.OpLoad(data_types[1], ptr_i);
if (!flags.typed) { if (!flags.typed) {
// Untyped loads have bounds checking per-component. // Untyped loads have bounds checking per-component.
ids.push_back(LoadAccessBoundsCheck < 32, 1, ids.push_back(LoadAccessBoundsCheck<32, 1, is_float>(
alias == ctx, index_i, spv_buffer.Size(PointerSize::B32), result_i));
PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i));
} else { } else {
ids.push_back(result_i); ids.push_back(result_i);
} }
@ -446,33 +446,32 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a
const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids); const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids);
if (flags.typed) { if (flags.typed) {
// Typed loads have single bounds check for the whole load. // Typed loads have single bounds check for the whole load.
return LoadAccessBoundsCheck < 32, N, return LoadAccessBoundsCheck<32, N, is_float>(ctx, address,
alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result); spv_buffer.Size(PointerSize::B32), result);
} }
return result; return result;
} }
Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B8); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U8);
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id result{ctx.OpLoad(ctx.U8, ptr)}; const Id result{ctx.OpLoad(ctx.U8, ptr)};
return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result); return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.Size(PointerSize::B8), result);
} }
Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B16); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U16);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
const Id result{ctx.OpLoad(ctx.U16, ptr)}; const Id result{ctx.OpLoad(ctx.U16, ptr)};
return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result); return LoadAccessBoundsCheck<16>(ctx, address, spv_buffer.Size(PointerSize::B16), result);
} }
Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
@ -493,14 +492,13 @@ Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address)
Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U64]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U64);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, address)};
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)};
const Id result{ctx.OpLoad(ctx.U64, ptr)}; const Id result{ctx.OpLoad(ctx.U64, ptr)};
return LoadAccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, result); return LoadAccessBoundsCheck<64>(ctx, address, spv_buffer.Size(PointerSize::B64), result);
} }
Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
@ -526,18 +524,18 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr
template <u32 N, PointerType alias> template <u32 N, PointerType alias>
static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
Id value) { Id value) {
constexpr bool is_float = alias == PointerType::F32;
const auto flags = inst->Flags<IR::BufferInstInfo>(); const auto flags = inst->Flags<IR::BufferInstInfo>();
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B32); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32; const auto& data_types = alias == PointerType::U32 ? ctx.U32 : ctx.F32;
const auto [id, pointer_type] = spv_buffer[alias]; const auto [id, pointer_type] = spv_buffer.Alias(alias);
auto store = [&] { auto store = [&] {
for (u32 i = 0; i < N; i++) { for (u32 i = 0; i < N; i++) {
const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i)); const Id index_i = i == 0 ? address : ctx.OpIAdd(ctx.U32[1], address, ctx.ConstU32(i));
const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i); const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i);
const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i); const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i);
auto store_i = [&] { auto store_i = [&] {
@ -546,8 +544,8 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I
}; };
if (!flags.typed) { if (!flags.typed) {
// Untyped stores have bounds checking per-component. // Untyped stores have bounds checking per-component.
AccessBoundsCheck<32, 1, alias == PointerType::F32>( AccessBoundsCheck<32, 1, is_float>(ctx, index_i, spv_buffer.Size(PointerSize::B32),
ctx, index_i, spv_buffer.size_dwords, store_i); store_i);
} else { } else {
store_i(); store_i();
} }
@ -557,8 +555,7 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I
if (flags.typed) { if (flags.typed) {
// Typed stores have single bounds check for the whole store. // Typed stores have single bounds check for the whole store.
AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords, AccessBoundsCheck<32, N, is_float>(ctx, address, spv_buffer.Size(PointerSize::B32), store);
store);
} else { } else {
store(); store();
} }
@ -566,12 +563,12 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I
void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B8); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U8]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U8);
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] { AccessBoundsCheck<8>(ctx, address, spv_buffer.Size(PointerSize::B8), [&] {
ctx.OpStore(ptr, value); ctx.OpStore(ptr, value);
return Id{}; return Id{};
}); });
@ -579,13 +576,12 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v
void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B16); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U16]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U16);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)}; AccessBoundsCheck<16>(ctx, address, spv_buffer.Size(PointerSize::B16), [&] {
AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] {
ctx.OpStore(ptr, value); ctx.OpStore(ptr, value);
return Id{}; return Id{};
}); });
@ -609,13 +605,12 @@ void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
const auto& spv_buffer = ctx.buffers[handle]; const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) { if (const Id offset = spv_buffer.Offset(PointerSize::B64); Sirit::ValidId(offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset); address = ctx.OpIAdd(ctx.U32[1], address, offset);
} }
const auto [id, pointer_type] = spv_buffer[PointerType::U64]; const auto [id, pointer_type] = spv_buffer.Alias(PointerType::U64);
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u)); const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, address)};
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)}; AccessBoundsCheck<64>(ctx, address, spv_buffer.Size(PointerSize::B64), [&] {
AccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, [&] {
ctx.OpStore(ptr, value); ctx.OpStore(ptr, value);
return Id{}; return Id{};
}); });

View File

@ -94,7 +94,9 @@ Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
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 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 EmitBufferAtomicSMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMax64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicFMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id 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);

View File

@ -71,7 +71,7 @@ EmitContext::EmitContext(const Profile& profile_, const RuntimeInfo& runtime_inf
Bindings& binding_) Bindings& binding_)
: Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_}, : Sirit::Module(profile_.supported_spirv), info{info_}, runtime_info{runtime_info_},
profile{profile_}, stage{info.stage}, l_stage{info.l_stage}, binding{binding_} { profile{profile_}, stage{info.stage}, l_stage{info.l_stage}, binding{binding_} {
if (info.dma_types != IR::Type::Void) { if (info.uses_dma) {
SetMemoryModel(spv::AddressingModel::PhysicalStorageBuffer64, spv::MemoryModel::GLSL450); SetMemoryModel(spv::AddressingModel::PhysicalStorageBuffer64, spv::MemoryModel::GLSL450);
} else { } else {
SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450); SetMemoryModel(spv::AddressingModel::Logical, spv::MemoryModel::GLSL450);
@ -169,34 +169,8 @@ void EmitContext::DefineArithmeticTypes() {
if (info.uses_fp64) { if (info.uses_fp64) {
frexp_result_f64 = Name(TypeStruct(F64[1], S32[1]), "frexp_result_f64"); frexp_result_f64 = Name(TypeStruct(F64[1], S32[1]), "frexp_result_f64");
} }
if (info.uses_dma) {
if (True(info.dma_types & IR::Type::F64)) { physical_pointer_type_u32 = TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]);
physical_pointer_types[PointerType::F64] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, F64[1]);
}
if (True(info.dma_types & IR::Type::U64)) {
physical_pointer_types[PointerType::U64] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, U64);
}
if (True(info.dma_types & IR::Type::F32)) {
physical_pointer_types[PointerType::F32] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, F32[1]);
}
if (True(info.dma_types & IR::Type::U32)) {
physical_pointer_types[PointerType::U32] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, U32[1]);
}
if (True(info.dma_types & IR::Type::F16)) {
physical_pointer_types[PointerType::F16] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, F16[1]);
}
if (True(info.dma_types & IR::Type::U16)) {
physical_pointer_types[PointerType::U16] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, U16);
}
if (True(info.dma_types & IR::Type::U8)) {
physical_pointer_types[PointerType::U8] =
TypePointer(spv::StorageClass::PhysicalStorageBuffer, U8);
} }
} }
@ -239,7 +213,7 @@ Id EmitContext::GetBufferSize(const u32 sharp_idx) {
// Can this be done with memory access? Like we do now with ReadConst // Can this be done with memory access? Like we do now with ReadConst
const auto& srt_flatbuf = buffers[flatbuf_index]; const auto& srt_flatbuf = buffers[flatbuf_index];
ASSERT(srt_flatbuf.buffer_type == BufferType::Flatbuf); ASSERT(srt_flatbuf.buffer_type == BufferType::Flatbuf);
const auto [id, pointer_type] = srt_flatbuf[PointerType::U32]; const auto [id, pointer_type] = srt_flatbuf.Alias(PointerType::U32);
const auto rsrc1{ const auto rsrc1{
OpLoad(U32[1], OpAccessChain(pointer_type, id, u32_zero_value, ConstU32(sharp_idx + 1)))}; OpLoad(U32[1], OpAccessChain(pointer_type, id, u32_zero_value, ConstU32(sharp_idx + 1)))};
@ -255,39 +229,70 @@ Id EmitContext::GetBufferSize(const u32 sharp_idx) {
} }
void EmitContext::DefineBufferProperties() { void EmitContext::DefineBufferProperties() {
if (!profile.needs_buffer_offsets && profile.supports_robust_buffer_access) {
return;
}
for (u32 i = 0; i < buffers.size(); i++) { for (u32 i = 0; i < buffers.size(); i++) {
BufferDefinition& buffer = buffers[i]; auto& buffer = buffers[i];
const auto& desc = info.buffers[i];
const u32 binding = buffer.binding;
if (buffer.buffer_type != BufferType::Guest) { if (buffer.buffer_type != BufferType::Guest) {
continue; continue;
} }
const u32 binding = buffer.binding;
const u32 half = PushData::BufOffsetIndex + (binding >> 4);
const u32 comp = (binding & 0xf) >> 2;
const u32 offset = (binding & 0x3) << 3;
const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]),
push_data_block, ConstU32(half), ConstU32(comp))};
const Id value{OpLoad(U32[1], ptr)};
buffer.offset = OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U));
Name(buffer.offset, fmt::format("buf{}_off", binding));
buffer.offset_dwords = OpShiftRightLogical(U32[1], buffer.offset, ConstU32(2U));
Name(buffer.offset_dwords, fmt::format("buf{}_dword_off", binding));
// Only need to load size if performing bounds checks and the buffer is both guest and not // Only load and apply buffer offsets if host GPU alignment is larger than guest.
// inline. if (profile.needs_buffer_offsets) {
if (!profile.supports_robust_buffer_access && buffer.buffer_type == BufferType::Guest) { const u32 half = PushData::BufOffsetIndex + (binding >> 4);
const BufferResource& desc = info.buffers[i]; const u32 comp = (binding & 0xf) >> 2;
if (desc.sharp_idx == std::numeric_limits<u32>::max()) { const u32 offset = (binding & 0x3) << 3;
buffer.size = ConstU32(desc.inline_cbuf.GetSize()); const Id ptr{OpAccessChain(TypePointer(spv::StorageClass::PushConstant, U32[1]),
} else { push_data_block, ConstU32(half), ConstU32(comp))};
buffer.size = GetBufferSize(desc.sharp_idx); const Id value{OpLoad(U32[1], ptr)};
const Id buf_offset{OpBitFieldUExtract(U32[1], value, ConstU32(offset), ConstU32(8U))};
Name(buf_offset, fmt::format("buf{}_off", binding));
buffer.Offset(PointerSize::B8) = buf_offset;
if (True(desc.used_types & IR::Type::U16)) {
const Id buf_word_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(1U))};
Name(buf_word_offset, fmt::format("buf{}_word_off", binding));
buffer.Offset(PointerSize::B16) = buf_word_offset;
}
if (True(desc.used_types & IR::Type::U32)) {
const Id buf_dword_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(2U))};
Name(buf_dword_offset, fmt::format("buf{}_dword_off", binding));
buffer.Offset(PointerSize::B32) = buf_dword_offset;
}
if (True(desc.used_types & IR::Type::U64)) {
const Id buf_qword_offset{OpShiftRightLogical(U32[1], buf_offset, ConstU32(3U))};
Name(buf_qword_offset, fmt::format("buf{}_qword_off", binding));
buffer.Offset(PointerSize::B64) = buf_qword_offset;
}
}
// Only load size if performing bounds checks.
if (!profile.supports_robust_buffer_access) {
const Id buf_size{desc.sharp_idx == std::numeric_limits<u32>::max()
? ConstU32(desc.inline_cbuf.GetSize())
: GetBufferSize(desc.sharp_idx)};
Name(buf_size, fmt::format("buf{}_size", binding));
buffer.Size(PointerSize::B8) = buf_size;
if (True(desc.used_types & IR::Type::U16)) {
const Id buf_word_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(1U))};
Name(buf_word_size, fmt::format("buf{}_short_size", binding));
buffer.Size(PointerSize::B16) = buf_word_size;
}
if (True(desc.used_types & IR::Type::U32)) {
const Id buf_dword_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(2U))};
Name(buf_dword_size, fmt::format("buf{}_dword_size", binding));
buffer.Size(PointerSize::B32) = buf_dword_size;
}
if (True(desc.used_types & IR::Type::U64)) {
const Id buf_qword_size{OpShiftRightLogical(U32[1], buf_size, ConstU32(3U))};
Name(buf_qword_size, fmt::format("buf{}_qword_size", binding));
buffer.Size(PointerSize::B64) = buf_qword_size;
} }
Name(buffer.size, fmt::format("buf{}_size", binding));
buffer.size_shorts = OpShiftRightLogical(U32[1], buffer.size, ConstU32(1U));
Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding));
buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U));
Name(buffer.size_dwords, fmt::format("buf{}_dword_size", binding));
buffer.size_qwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(3U));
Name(buffer.size_qwords, fmt::format("buf{}_qword_size", binding));
} }
} }
} }
@ -779,8 +784,7 @@ EmitContext::BufferSpv EmitContext::DefineBuffer(bool is_storage, bool is_writte
}; };
void EmitContext::DefineBuffers() { void EmitContext::DefineBuffers() {
if (!profile.supports_robust_buffer_access && if (!profile.supports_robust_buffer_access && !info.uses_dma) {
info.readconst_types == Info::ReadConstType::None) {
// In case Flatbuf has not already been bound by IR and is needed // In case Flatbuf has not already been bound by IR and is needed
// to query buffer sizes, bind it now. // to query buffer sizes, bind it now.
info.buffers.push_back({ info.buffers.push_back({
@ -809,23 +813,23 @@ void EmitContext::DefineBuffers() {
// Define aliases depending on the shader usage. // Define aliases depending on the shader usage.
auto& spv_buffer = buffers.emplace_back(binding.buffer++, desc.buffer_type); auto& spv_buffer = buffers.emplace_back(binding.buffer++, desc.buffer_type);
if (True(desc.used_types & IR::Type::U64)) { if (True(desc.used_types & IR::Type::U64)) {
spv_buffer[PointerType::U64] = spv_buffer.Alias(PointerType::U64) =
DefineBuffer(is_storage, desc.is_written, 3, desc.buffer_type, U64); DefineBuffer(is_storage, desc.is_written, 3, desc.buffer_type, U64);
} }
if (True(desc.used_types & IR::Type::U32)) { if (True(desc.used_types & IR::Type::U32)) {
spv_buffer[PointerType::U32] = spv_buffer.Alias(PointerType::U32) =
DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, U32[1]); DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, U32[1]);
} }
if (True(desc.used_types & IR::Type::F32)) { if (True(desc.used_types & IR::Type::F32)) {
spv_buffer[PointerType::F32] = spv_buffer.Alias(PointerType::F32) =
DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, F32[1]); DefineBuffer(is_storage, desc.is_written, 2, desc.buffer_type, F32[1]);
} }
if (True(desc.used_types & IR::Type::U16)) { if (True(desc.used_types & IR::Type::U16)) {
spv_buffer[PointerType::U16] = spv_buffer.Alias(PointerType::U16) =
DefineBuffer(is_storage, desc.is_written, 1, desc.buffer_type, U16); DefineBuffer(is_storage, desc.is_written, 1, desc.buffer_type, U16);
} }
if (True(desc.used_types & IR::Type::U8)) { if (True(desc.used_types & IR::Type::U8)) {
spv_buffer[PointerType::U8] = spv_buffer.Alias(PointerType::U8) =
DefineBuffer(is_storage, desc.is_written, 0, desc.buffer_type, U8); DefineBuffer(is_storage, desc.is_written, 0, desc.buffer_type, U8);
} }
++binding.unified; ++binding.unified;
@ -1154,7 +1158,7 @@ Id EmitContext::DefineGetBdaPointer() {
const auto page{OpShiftRightLogical(U64, address, caching_pagebits)}; const auto page{OpShiftRightLogical(U64, address, caching_pagebits)};
const auto page32{OpUConvert(U32[1], page)}; const auto page32{OpUConvert(U32[1], page)};
const auto& bda_buffer{buffers[bda_pagetable_index]}; const auto& bda_buffer{buffers[bda_pagetable_index]};
const auto [bda_buffer_id, bda_pointer_type] = bda_buffer[PointerType::U64]; const auto [bda_buffer_id, bda_pointer_type] = bda_buffer.Alias(PointerType::U64);
const auto bda_ptr{OpAccessChain(bda_pointer_type, bda_buffer_id, u32_zero_value, page32)}; const auto bda_ptr{OpAccessChain(bda_pointer_type, bda_buffer_id, u32_zero_value, page32)};
const auto bda{OpLoad(U64, bda_ptr)}; const auto bda{OpLoad(U64, bda_ptr)};
@ -1166,14 +1170,14 @@ Id EmitContext::DefineGetBdaPointer() {
// First time acces, mark as fault // First time acces, mark as fault
AddLabel(fault_label); AddLabel(fault_label);
const auto& fault_buffer{buffers[fault_buffer_index]}; const auto& fault_buffer{buffers[fault_buffer_index]};
const auto [fault_buffer_id, fault_pointer_type] = fault_buffer[PointerType::U8]; const auto [fault_buffer_id, fault_pointer_type] = fault_buffer.Alias(PointerType::U32);
const auto page_div8{OpShiftRightLogical(U32[1], page32, ConstU32(3U))}; const auto page_div32{OpShiftRightLogical(U32[1], page32, ConstU32(5U))};
const auto page_mod8{OpBitwiseAnd(U32[1], page32, ConstU32(7U))}; const auto page_mod32{OpBitwiseAnd(U32[1], page32, ConstU32(31U))};
const auto page_mask{OpShiftLeftLogical(U8, u8_one_value, page_mod8)}; const auto page_mask{OpShiftLeftLogical(U32[1], u32_one_value, page_mod32)};
const auto fault_ptr{ const auto fault_ptr{
OpAccessChain(fault_pointer_type, fault_buffer_id, u32_zero_value, page_div8)}; OpAccessChain(fault_pointer_type, fault_buffer_id, u32_zero_value, page_div32)};
const auto fault_value{OpLoad(U8, fault_ptr)}; const auto fault_value{OpLoad(U32[1], fault_ptr)};
const auto fault_value_masked{OpBitwiseOr(U8, fault_value, page_mask)}; const auto fault_value_masked{OpBitwiseOr(U32[1], fault_value, page_mask)};
OpStore(fault_ptr, fault_value_masked); OpStore(fault_ptr, fault_value_masked);
// Return null pointer // Return null pointer
@ -1211,14 +1215,15 @@ Id EmitContext::DefineReadConst(bool dynamic) {
const auto offset_bytes{OpShiftLeftLogical(U32[1], offset, ConstU32(2U))}; const auto offset_bytes{OpShiftLeftLogical(U32[1], offset, ConstU32(2U))};
const auto addr{OpIAdd(U64, base_addr, OpUConvert(U64, offset_bytes))}; const auto addr{OpIAdd(U64, base_addr, OpUConvert(U64, offset_bytes))};
const auto result = EmitMemoryRead(U32[1], addr, [&]() { const auto result = EmitDwordMemoryRead(addr, [&]() {
if (dynamic) { if (dynamic) {
return u32_zero_value; return u32_zero_value;
} else { } else {
const auto& flatbuf_buffer{buffers[flatbuf_index]}; const auto& flatbuf_buffer{buffers[flatbuf_index]};
ASSERT(flatbuf_buffer.binding >= 0 && ASSERT(flatbuf_buffer.binding >= 0 &&
flatbuf_buffer.buffer_type == BufferType::Flatbuf); flatbuf_buffer.buffer_type == BufferType::Flatbuf);
const auto [flatbuf_buffer_id, flatbuf_pointer_type] = flatbuf_buffer[PointerType::U32]; const auto [flatbuf_buffer_id, flatbuf_pointer_type] =
flatbuf_buffer.Alias(PointerType::U32);
const auto ptr{OpAccessChain(flatbuf_pointer_type, flatbuf_buffer_id, u32_zero_value, const auto ptr{OpAccessChain(flatbuf_pointer_type, flatbuf_buffer_id, u32_zero_value,
flatbuf_offset)}; flatbuf_offset)};
return OpLoad(U32[1], ptr); return OpLoad(U32[1], ptr);
@ -1239,7 +1244,7 @@ void EmitContext::DefineFunctions() {
uf11_to_f32 = DefineUfloatM5ToFloat32(6, "uf11_to_f32"); uf11_to_f32 = DefineUfloatM5ToFloat32(6, "uf11_to_f32");
uf10_to_f32 = DefineUfloatM5ToFloat32(5, "uf10_to_f32"); uf10_to_f32 = DefineUfloatM5ToFloat32(5, "uf10_to_f32");
} }
if (info.dma_types != IR::Type::Void) { if (info.uses_dma) {
get_bda_pointer = DefineGetBdaPointer(); get_bda_pointer = DefineGetBdaPointer();
} }

View File

@ -42,17 +42,6 @@ public:
Bindings& binding); Bindings& binding);
~EmitContext(); ~EmitContext();
enum class PointerType : u32 {
U8,
U16,
F16,
U32,
F32,
U64,
F64,
NumAlias,
};
Id Def(const IR::Value& value); Id Def(const IR::Value& value);
void DefineBufferProperties(); void DefineBufferProperties();
@ -155,25 +144,7 @@ public:
return last_label; return last_label;
} }
PointerType PointerTypeFromType(Id type) { Id EmitDwordMemoryRead(Id address, auto&& fallback) {
if (type.value == U8.value)
return PointerType::U8;
if (type.value == U16.value)
return PointerType::U16;
if (type.value == F16[1].value)
return PointerType::F16;
if (type.value == U32[1].value)
return PointerType::U32;
if (type.value == F32[1].value)
return PointerType::F32;
if (type.value == U64.value)
return PointerType::U64;
if (type.value == F64[1].value)
return PointerType::F64;
UNREACHABLE_MSG("Unknown type for pointer");
}
Id EmitMemoryRead(Id type, Id address, auto&& fallback) {
const Id available_label = OpLabel(); const Id available_label = OpLabel();
const Id fallback_label = OpLabel(); const Id fallback_label = OpLabel();
const Id merge_label = OpLabel(); const Id merge_label = OpLabel();
@ -185,10 +156,8 @@ public:
// Available // Available
AddLabel(available_label); AddLabel(available_label);
const auto pointer_type = PointerTypeFromType(type); const Id addr_ptr = OpConvertUToPtr(physical_pointer_type_u32, addr);
const Id pointer_type_id = physical_pointer_types[pointer_type]; const Id result = OpLoad(U32[1], addr_ptr, spv::MemoryAccessMask::Aligned, 4u);
const Id addr_ptr = OpConvertUToPtr(pointer_type_id, addr);
const Id result = OpLoad(type, addr_ptr, spv::MemoryAccessMask::Aligned, 4u);
OpBranch(merge_label); OpBranch(merge_label);
// Fallback // Fallback
@ -199,7 +168,7 @@ public:
// Merge // Merge
AddLabel(merge_label); AddLabel(merge_label);
const Id final_result = const Id final_result =
OpPhi(type, fallback_result, fallback_label, result, available_label); OpPhi(U32[1], fallback_result, fallback_label, result, available_label);
return final_result; return final_result;
} }
@ -314,6 +283,24 @@ public:
bool is_storage = false; bool is_storage = false;
}; };
enum class PointerType : u32 {
U8,
U16,
U32,
F32,
U64,
F64,
NumAlias,
};
enum class PointerSize : u32 {
B8,
B16,
B32,
B64,
NumClass,
};
struct BufferSpv { struct BufferSpv {
Id id; Id id;
Id pointer_type; Id pointer_type;
@ -322,32 +309,23 @@ public:
struct BufferDefinition { struct BufferDefinition {
u32 binding; u32 binding;
BufferType buffer_type; BufferType buffer_type;
Id offset; std::array<Id, u32(PointerSize::NumClass)> offsets;
Id offset_dwords; std::array<Id, u32(PointerSize::NumClass)> sizes;
Id size;
Id size_shorts;
Id size_dwords;
Id size_qwords;
std::array<BufferSpv, u32(PointerType::NumAlias)> aliases; std::array<BufferSpv, u32(PointerType::NumAlias)> aliases;
const BufferSpv& operator[](PointerType alias) const { template <class Self>
return aliases[u32(alias)]; auto& Alias(this Self& self, PointerType alias) {
return self.aliases[u32(alias)];
} }
BufferSpv& operator[](PointerType alias) { template <class Self>
return aliases[u32(alias)]; auto& Offset(this Self& self, PointerSize size) {
} return self.offsets[u32(size)];
};
struct PhysicalPointerTypes {
std::array<Id, u32(PointerType::NumAlias)> types;
const Id& operator[](PointerType type) const {
return types[u32(type)];
} }
Id& operator[](PointerType type) { template <class Self>
return types[u32(type)]; auto& Size(this Self& self, PointerSize size) {
return self.sizes[u32(size)];
} }
}; };
@ -356,12 +334,12 @@ public:
boost::container::small_vector<BufferDefinition, 16> buffers; boost::container::small_vector<BufferDefinition, 16> buffers;
boost::container::small_vector<TextureDefinition, 8> images; boost::container::small_vector<TextureDefinition, 8> images;
boost::container::small_vector<Id, 4> samplers; boost::container::small_vector<Id, 4> samplers;
PhysicalPointerTypes physical_pointer_types;
std::unordered_map<u32, Id> first_to_last_label_map; std::unordered_map<u32, Id> first_to_last_label_map;
size_t flatbuf_index{}; size_t flatbuf_index{};
size_t bda_pagetable_index{}; size_t bda_pagetable_index{};
size_t fault_buffer_index{}; size_t fault_buffer_index{};
Id physical_pointer_type_u32;
Id sampler_type{}; Id sampler_type{};
Id sampler_pointer_type{}; Id sampler_pointer_type{};

View File

@ -1,7 +1,6 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <bit>
#include "common/assert.h" #include "common/assert.h"
#include "shader_recompiler/frontend/translate/translate.h" #include "shader_recompiler/frontend/translate/translate.h"

View File

@ -292,6 +292,7 @@ public:
void BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst); void BUFFER_LOAD(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, const GcnInst& inst);
void BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed, void BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer_typed,
const GcnInst& inst); const GcnInst& inst);
template <typename T = IR::U32>
void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst); void BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst);
// Image Memory // Image Memory

View File

@ -78,8 +78,12 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
return BUFFER_ATOMIC(AtomicOp::Umin, inst); return BUFFER_ATOMIC(AtomicOp::Umin, inst);
case Opcode::BUFFER_ATOMIC_SMAX: case Opcode::BUFFER_ATOMIC_SMAX:
return BUFFER_ATOMIC(AtomicOp::Smax, inst); return BUFFER_ATOMIC(AtomicOp::Smax, inst);
case Opcode::BUFFER_ATOMIC_SMAX_X2:
return BUFFER_ATOMIC<IR::U64>(AtomicOp::Smax, inst);
case Opcode::BUFFER_ATOMIC_UMAX: case Opcode::BUFFER_ATOMIC_UMAX:
return BUFFER_ATOMIC(AtomicOp::Umax, inst); return BUFFER_ATOMIC(AtomicOp::Umax, inst);
case Opcode::BUFFER_ATOMIC_UMAX_X2:
return BUFFER_ATOMIC<IR::U64>(AtomicOp::Umax, inst);
case Opcode::BUFFER_ATOMIC_AND: case Opcode::BUFFER_ATOMIC_AND:
return BUFFER_ATOMIC(AtomicOp::And, inst); return BUFFER_ATOMIC(AtomicOp::And, inst);
case Opcode::BUFFER_ATOMIC_OR: case Opcode::BUFFER_ATOMIC_OR:
@ -304,6 +308,7 @@ void Translator::BUFFER_STORE(u32 num_dwords, bool is_inst_typed, bool is_buffer
} }
} }
template <typename T>
void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) { void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
const auto& mubuf = inst.control.mubuf; const auto& mubuf = inst.control.mubuf;
const IR::VectorReg vaddr{inst.src[0].code}; const IR::VectorReg vaddr{inst.src[0].code};
@ -328,7 +333,17 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
buffer_info.globally_coherent.Assign(mubuf.glc); buffer_info.globally_coherent.Assign(mubuf.glc);
buffer_info.system_coherent.Assign(mubuf.slc); buffer_info.system_coherent.Assign(mubuf.slc);
IR::Value vdata_val = ir.GetVectorReg<Shader::IR::U32>(vdata); IR::Value vdata_val = [&] {
if constexpr (std::is_same_v<T, IR::U32>) {
return ir.GetVectorReg<Shader::IR::U32>(vdata);
} else if constexpr (std::is_same_v<T, IR::U64>) {
return ir.PackUint2x32(
ir.CompositeConstruct(ir.GetVectorReg<Shader::IR::U32>(vdata),
ir.GetVectorReg<Shader::IR::U32>(vdata + 1)));
} else {
static_assert(false, "buffer_atomic: type not supported");
}
}();
const IR::Value handle = const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1), ir.CompositeConstruct(ir.GetScalarReg(srsrc), ir.GetScalarReg(srsrc + 1),
ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3)); ir.GetScalarReg(srsrc + 2), ir.GetScalarReg(srsrc + 3));

View File

@ -58,6 +58,7 @@ struct BufferResource {
BufferType buffer_type; BufferType buffer_type;
u8 instance_attrib{}; u8 instance_attrib{};
bool is_written{}; bool is_written{};
bool is_read{};
bool is_formatted{}; bool is_formatted{};
bool IsSpecial() const noexcept { bool IsSpecial() const noexcept {
@ -225,6 +226,8 @@ struct Info {
bool uses_fp64{}; bool uses_fp64{};
bool uses_pack_10_11_11{}; bool uses_pack_10_11_11{};
bool uses_unpack_10_11_11{}; bool uses_unpack_10_11_11{};
bool uses_buffer_int64_atomics{};
bool uses_shared_int64_atomics{};
bool stores_tess_level_outer{}; bool stores_tess_level_outer{};
bool stores_tess_level_inner{}; bool stores_tess_level_inner{};
bool translation_failed{}; bool translation_failed{};
@ -238,7 +241,7 @@ struct Info {
Dynamic = 1 << 1, Dynamic = 1 << 1,
}; };
ReadConstType readconst_types{}; ReadConstType readconst_types{};
IR::Type dma_types{IR::Type::Void}; bool uses_dma{false};
explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params) explicit Info(Stage stage_, LogicalStage l_stage_, ShaderParams params)
: stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()}, : stage{stage_}, l_stage{l_stage_}, pgm_hash{params.hash}, pgm_base{params.Base()},

View File

@ -511,8 +511,16 @@ Value IREmitter::BufferAtomicFMin(const Value& handle, const Value& address, con
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) switch (value.Type()) {
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value); case Type::U32:
return is_signed ? Inst(Opcode::BufferAtomicSMax32, Flags{info}, handle, address, value)
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value);
case Type::U64:
return is_signed ? Inst(Opcode::BufferAtomicSMax64, Flags{info}, handle, address, value)
: Inst(Opcode::BufferAtomicUMax64, Flags{info}, handle, address, value);
default:
ThrowInvalidType(value.Type());
}
} }
Value IREmitter::BufferAtomicFMax(const Value& handle, const Value& address, const Value& value, Value IREmitter::BufferAtomicFMax(const Value& handle, const Value& address, const Value& value,

View File

@ -73,7 +73,9 @@ bool Inst::MayHaveSideEffects() const noexcept {
case Opcode::BufferAtomicUMin32: case Opcode::BufferAtomicUMin32:
case Opcode::BufferAtomicFMin32: case Opcode::BufferAtomicFMin32:
case Opcode::BufferAtomicSMax32: case Opcode::BufferAtomicSMax32:
case Opcode::BufferAtomicSMax64:
case Opcode::BufferAtomicUMax32: case Opcode::BufferAtomicUMax32:
case Opcode::BufferAtomicUMax64:
case Opcode::BufferAtomicFMax32: case Opcode::BufferAtomicFMax32:
case Opcode::BufferAtomicInc32: case Opcode::BufferAtomicInc32:
case Opcode::BufferAtomicDec32: case Opcode::BufferAtomicDec32:

View File

@ -127,7 +127,9 @@ OPCODE(BufferAtomicSMin32, U32, Opaq
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 ) OPCODE(BufferAtomicFMin32, U32, Opaque, Opaque, F32 )
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicSMax64, U64, Opaque, Opaque, U64 )
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 ) OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
OPCODE(BufferAtomicUMax64, U64, Opaque, Opaque, U64 )
OPCODE(BufferAtomicFMax32, U32, Opaque, Opaque, F32 ) 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, )

View File

@ -23,7 +23,9 @@ bool IsBufferAtomic(const IR::Inst& inst) {
case IR::Opcode::BufferAtomicUMin32: case IR::Opcode::BufferAtomicUMin32:
case IR::Opcode::BufferAtomicFMin32: case IR::Opcode::BufferAtomicFMin32:
case IR::Opcode::BufferAtomicSMax32: case IR::Opcode::BufferAtomicSMax32:
case IR::Opcode::BufferAtomicSMax64:
case IR::Opcode::BufferAtomicUMax32: case IR::Opcode::BufferAtomicUMax32:
case IR::Opcode::BufferAtomicUMax64:
case IR::Opcode::BufferAtomicFMax32: case IR::Opcode::BufferAtomicFMax32:
case IR::Opcode::BufferAtomicInc32: case IR::Opcode::BufferAtomicInc32:
case IR::Opcode::BufferAtomicDec32: case IR::Opcode::BufferAtomicDec32:
@ -105,6 +107,49 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) {
} }
} }
u32 BufferAddressShift(const IR::Inst& inst, AmdGpu::DataFormat data_format) {
switch (inst.GetOpcode()) {
case IR::Opcode::LoadBufferU8:
case IR::Opcode::StoreBufferU8:
return 0;
case IR::Opcode::LoadBufferU16:
case IR::Opcode::StoreBufferU16:
return 1;
case IR::Opcode::LoadBufferU64:
case IR::Opcode::StoreBufferU64:
case IR::Opcode::BufferAtomicIAdd64:
return 3;
case IR::Opcode::LoadBufferFormatF32:
case IR::Opcode::StoreBufferFormatF32: {
switch (data_format) {
case AmdGpu::DataFormat::Format8:
return 0;
case AmdGpu::DataFormat::Format8_8:
case AmdGpu::DataFormat::Format16:
return 1;
case AmdGpu::DataFormat::Format8_8_8_8:
case AmdGpu::DataFormat::Format16_16:
case AmdGpu::DataFormat::Format10_11_11:
case AmdGpu::DataFormat::Format2_10_10_10:
case AmdGpu::DataFormat::Format16_16_16_16:
case AmdGpu::DataFormat::Format32:
case AmdGpu::DataFormat::Format32_32:
case AmdGpu::DataFormat::Format32_32_32:
case AmdGpu::DataFormat::Format32_32_32_32:
return 2;
default:
return 0;
}
break;
}
case IR::Opcode::ReadConstBuffer:
// Provided address is already in dwords
return 0;
default:
return 2;
}
}
bool IsImageAtomicInstruction(const IR::Inst& inst) { bool IsImageAtomicInstruction(const IR::Inst& inst) {
switch (inst.GetOpcode()) { switch (inst.GetOpcode()) {
case IR::Opcode::ImageAtomicIAdd32: case IR::Opcode::ImageAtomicIAdd32:
@ -154,6 +199,7 @@ public:
auto& buffer = buffer_resources[index]; auto& buffer = buffer_resources[index];
buffer.used_types |= desc.used_types; buffer.used_types |= desc.used_types;
buffer.is_written |= desc.is_written; buffer.is_written |= desc.is_written;
buffer.is_read |= desc.is_read;
buffer.is_formatted |= desc.is_formatted; buffer.is_formatted |= desc.is_formatted;
return index; return index;
} }
@ -324,6 +370,7 @@ s32 TryHandleInlineCbuf(IR::Inst& inst, Info& info, Descriptors& descriptors,
.used_types = BufferDataType(inst, cbuf.GetNumberFmt()), .used_types = BufferDataType(inst, cbuf.GetNumberFmt()),
.inline_cbuf = cbuf, .inline_cbuf = cbuf,
.buffer_type = BufferType::Guest, .buffer_type = BufferType::Guest,
.is_read = true,
}); });
} }
@ -335,11 +382,13 @@ void PatchBufferSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
IR::Inst* producer = handle->Arg(0).InstRecursive(); IR::Inst* producer = handle->Arg(0).InstRecursive();
SharpLocation sharp; SharpLocation sharp;
std::tie(sharp, buffer) = TrackSharp<AmdGpu::Buffer>(producer, info); std::tie(sharp, buffer) = TrackSharp<AmdGpu::Buffer>(producer, info);
const bool is_written = IsBufferStore(inst);
binding = descriptors.Add(BufferResource{ binding = descriptors.Add(BufferResource{
.sharp_idx = sharp, .sharp_idx = sharp,
.used_types = BufferDataType(inst, buffer.GetNumberFmt()), .used_types = BufferDataType(inst, buffer.GetNumberFmt()),
.buffer_type = BufferType::Guest, .buffer_type = BufferType::Guest,
.is_written = IsBufferStore(inst), .is_written = is_written,
.is_read = !is_written,
.is_formatted = inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32 || .is_formatted = inst.GetOpcode() == IR::Opcode::LoadBufferFormatF32 ||
inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32, inst.GetOpcode() == IR::Opcode::StoreBufferFormatF32,
}); });
@ -366,11 +415,12 @@ void PatchImageSharp(IR::Block& block, IR::Inst& inst, Info& info, Descriptors&
// Read image sharp. // Read image sharp.
const auto tsharp = TrackSharp(tsharp_handle, info); const auto tsharp = TrackSharp(tsharp_handle, info);
const auto inst_info = inst.Flags<IR::TextureInstInfo>(); const auto inst_info = inst.Flags<IR::TextureInstInfo>();
const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite; const bool is_atomic = IsImageAtomicInstruction(inst);
const bool is_written = inst.GetOpcode() == IR::Opcode::ImageWrite || is_atomic;
const ImageResource image_res = { const ImageResource image_res = {
.sharp_idx = tsharp, .sharp_idx = tsharp,
.is_depth = bool(inst_info.is_depth), .is_depth = bool(inst_info.is_depth),
.is_atomic = IsImageAtomicInstruction(inst), .is_atomic = is_atomic,
.is_array = bool(inst_info.is_array), .is_array = bool(inst_info.is_array),
.is_written = is_written, .is_written = is_written,
.is_r128 = bool(inst_info.is_r128), .is_r128 = bool(inst_info.is_r128),
@ -496,6 +546,22 @@ void PatchDataRingAccess(IR::Block& block, IR::Inst& inst, Info& info, Descripto
IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const Info& info, IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const Info& info,
const AmdGpu::Buffer& buffer, u32 stride) { const AmdGpu::Buffer& buffer, u32 stride) {
const auto inst_info = inst.Flags<IR::BufferInstInfo>(); const auto inst_info = inst.Flags<IR::BufferInstInfo>();
const u32 inst_offset = inst_info.inst_offset.Value();
const auto is_inst_typed = inst_info.inst_data_fmt != AmdGpu::DataFormat::FormatInvalid;
const auto data_format = is_inst_typed
? AmdGpu::RemapDataFormat(inst_info.inst_data_fmt.Value())
: buffer.GetDataFmt();
const u32 shift = BufferAddressShift(inst, data_format);
const u32 mask = (1 << shift) - 1;
// If address calculation is of the form "index * const_stride + offset" with offset constant
// and both const_stride and offset are divisible with the element size, apply shift directly.
if (inst_info.index_enable && !inst_info.offset_enable && !buffer.swizzle_enable &&
!buffer.add_tid_enable && (stride & mask) == 0 && (inst_offset & mask) == 0) {
// buffer_offset = index * (const_stride >> shift) + (inst_offset >> shift)
const IR::U32 index = IR::U32{inst.Arg(1)};
return ir.IAdd(ir.IMul(index, ir.Imm32(stride >> shift)), ir.Imm32(inst_offset >> shift));
}
// index = (inst_idxen ? vgpr_index : 0) + (const_add_tid_enable ? thread_id[5:0] : 0) // index = (inst_idxen ? vgpr_index : 0) + (const_add_tid_enable ? thread_id[5:0] : 0)
IR::U32 index = ir.Imm32(0U); IR::U32 index = ir.Imm32(0U);
@ -512,7 +578,7 @@ IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const In
index = ir.IAdd(index, thread_id); index = ir.IAdd(index, thread_id);
} }
// offset = (inst_offen ? vgpr_offset : 0) + inst_offset // offset = (inst_offen ? vgpr_offset : 0) + inst_offset
IR::U32 offset = ir.Imm32(inst_info.inst_offset.Value()); IR::U32 offset = ir.Imm32(inst_offset);
if (inst_info.offset_enable) { if (inst_info.offset_enable) {
const IR::U32 vgpr_offset = inst_info.index_enable const IR::U32 vgpr_offset = inst_info.index_enable
? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)} ? IR::U32{ir.CompositeExtract(inst.Arg(1), 1)}
@ -545,6 +611,9 @@ IR::U32 CalculateBufferAddress(IR::IREmitter& ir, const IR::Inst& inst, const In
// buffer_offset = index * const_stride + offset // buffer_offset = index * const_stride + offset
buffer_offset = ir.IAdd(ir.IMul(index, const_stride), offset); buffer_offset = ir.IAdd(ir.IMul(index, const_stride), offset);
} }
if (shift != 0) {
buffer_offset = ir.ShiftRightLogical(buffer_offset, ir.Imm32(shift));
}
return buffer_offset; return buffer_offset;
} }

View File

@ -53,9 +53,11 @@ void Visit(Info& info, const IR::Inst& inst) {
case IR::Opcode::SharedAtomicXor32: case IR::Opcode::SharedAtomicXor32:
info.shared_types |= IR::Type::U32; info.shared_types |= IR::Type::U32;
break; break;
case IR::Opcode::SharedAtomicIAdd64:
info.uses_shared_int64_atomics = true;
[[fallthrough]];
case IR::Opcode::LoadSharedU64: case IR::Opcode::LoadSharedU64:
case IR::Opcode::WriteSharedU64: case IR::Opcode::WriteSharedU64:
case IR::Opcode::SharedAtomicIAdd64:
info.shared_types |= IR::Type::U64; info.shared_types |= IR::Type::U64;
break; break;
case IR::Opcode::ConvertF16F32: case IR::Opcode::ConvertF16F32:
@ -98,11 +100,16 @@ void Visit(Info& info, const IR::Inst& inst) {
case IR::Opcode::BufferAtomicFMin32: case IR::Opcode::BufferAtomicFMin32:
info.uses_buffer_atomic_float_min_max = true; info.uses_buffer_atomic_float_min_max = true;
break; break;
case IR::Opcode::BufferAtomicIAdd64:
case IR::Opcode::BufferAtomicSMax64:
case IR::Opcode::BufferAtomicUMax64:
info.uses_buffer_int64_atomics = true;
break;
case IR::Opcode::LaneId: case IR::Opcode::LaneId:
info.uses_lane_id = true; info.uses_lane_id = true;
break; break;
case IR::Opcode::ReadConst: case IR::Opcode::ReadConst:
if (info.readconst_types == Info::ReadConstType::None) { if (!info.uses_dma) {
info.buffers.push_back({ info.buffers.push_back({
.used_types = IR::Type::U32, .used_types = IR::Type::U32,
// We can't guarantee that flatbuf will not grow past UBO // We can't guarantee that flatbuf will not grow past UBO
@ -116,7 +123,7 @@ void Visit(Info& info, const IR::Inst& inst) {
} else { } else {
info.readconst_types |= Info::ReadConstType::Dynamic; info.readconst_types |= Info::ReadConstType::Dynamic;
} }
info.dma_types |= IR::Type::U32; info.uses_dma = true;
break; break;
case IR::Opcode::PackUfloat10_11_11: case IR::Opcode::PackUfloat10_11_11:
info.uses_pack_10_11_11 = true; info.uses_pack_10_11_11 = true;
@ -130,21 +137,22 @@ void Visit(Info& info, const IR::Inst& inst) {
} }
void CollectShaderInfoPass(IR::Program& program) { void CollectShaderInfoPass(IR::Program& program) {
auto& info = program.info;
for (IR::Block* const block : program.post_order_blocks) { for (IR::Block* const block : program.post_order_blocks) {
for (IR::Inst& inst : block->Instructions()) { for (IR::Inst& inst : block->Instructions()) {
Visit(program.info, inst); Visit(info, inst);
} }
} }
if (program.info.dma_types != IR::Type::Void) { if (info.uses_dma) {
program.info.buffers.push_back({ info.buffers.push_back({
.used_types = IR::Type::U64, .used_types = IR::Type::U64,
.inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::BDA_PAGETABLE_SIZE), .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::BDA_PAGETABLE_SIZE),
.buffer_type = BufferType::BdaPagetable, .buffer_type = BufferType::BdaPagetable,
.is_written = true, .is_written = true,
}); });
program.info.buffers.push_back({ info.buffers.push_back({
.used_types = IR::Type::U8, .used_types = IR::Type::U32,
.inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::FAULT_BUFFER_SIZE), .inline_cbuf = AmdGpu::Buffer::Placeholder(VideoCore::BufferCache::FAULT_BUFFER_SIZE),
.buffer_type = BufferType::FaultBuffer, .buffer_type = BufferType::FaultBuffer,
.is_written = true, .is_written = true,

View File

@ -30,12 +30,14 @@ struct Profile {
bool supports_robust_buffer_access{}; bool supports_robust_buffer_access{};
bool supports_buffer_fp32_atomic_min_max{}; bool supports_buffer_fp32_atomic_min_max{};
bool supports_image_fp32_atomic_min_max{}; bool supports_image_fp32_atomic_min_max{};
bool supports_buffer_int64_atomics{};
bool supports_shared_int64_atomics{};
bool supports_workgroup_explicit_memory_layout{}; bool supports_workgroup_explicit_memory_layout{};
bool has_broken_spirv_clamp{}; bool has_broken_spirv_clamp{};
bool lower_left_origin_mode{}; bool lower_left_origin_mode{};
bool needs_manual_interpolation{}; bool needs_manual_interpolation{};
bool needs_lds_barriers{}; bool needs_lds_barriers{};
u64 min_ssbo_alignment{}; bool needs_buffer_offsets{};
u64 max_ubo_size{}; u64 max_ubo_size{};
u32 max_viewport_width{}; u32 max_viewport_width{};
u32 max_viewport_height{}; u32 max_viewport_height{};

View File

@ -72,8 +72,23 @@ Liverpool::~Liverpool() {
process_thread.join(); process_thread.join();
} }
void Liverpool::ProcessCommands() {
// Process incoming commands with high priority
while (num_commands) {
Common::UniqueFunction<void> callback{};
{
std::scoped_lock lk{submit_mutex};
callback = std::move(command_queue.front());
command_queue.pop();
--num_commands;
}
callback();
}
}
void Liverpool::Process(std::stop_token stoken) { void Liverpool::Process(std::stop_token stoken) {
Common::SetCurrentThreadName("shadPS4:GpuCommandProcessor"); Common::SetCurrentThreadName("shadPS4:GpuCommandProcessor");
gpu_id = std::this_thread::get_id();
while (!stoken.stop_requested()) { while (!stoken.stop_requested()) {
{ {
@ -90,18 +105,7 @@ void Liverpool::Process(std::stop_token stoken) {
curr_qid = -1; curr_qid = -1;
while (num_submits || num_commands) { while (num_submits || num_commands) {
ProcessCommands();
// Process incoming commands with high priority
while (num_commands) {
Common::UniqueFunction<void> callback{};
{
std::unique_lock lk{submit_mutex};
callback = std::move(command_queue.front());
command_queue.pop();
--num_commands;
}
callback();
}
curr_qid = (curr_qid + 1) % num_mapped_queues; curr_qid = (curr_qid + 1) % num_mapped_queues;
@ -147,6 +151,8 @@ Liverpool::Task Liverpool::ProcessCeUpdate(std::span<const u32> ccb) {
FIBER_ENTER(ccb_task_name); FIBER_ENTER(ccb_task_name);
while (!ccb.empty()) { while (!ccb.empty()) {
ProcessCommands();
const auto* header = reinterpret_cast<const PM4Header*>(ccb.data()); const auto* header = reinterpret_cast<const PM4Header*>(ccb.data());
const u32 type = header->type; const u32 type = header->type;
if (type != 3) { if (type != 3) {
@ -224,6 +230,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
const auto base_addr = reinterpret_cast<uintptr_t>(dcb.data()); const auto base_addr = reinterpret_cast<uintptr_t>(dcb.data());
while (!dcb.empty()) { while (!dcb.empty()) {
ProcessCommands();
const auto* header = reinterpret_cast<const PM4Header*>(dcb.data()); const auto* header = reinterpret_cast<const PM4Header*>(dcb.data());
const u32 type = header->type; const u32 type = header->type;
@ -638,9 +646,8 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
} else if ((dma_data->src_sel == DmaDataSrc::Memory || } else if ((dma_data->src_sel == DmaDataSrc::Memory ||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
dma_data->dst_sel == DmaDataDst::Gds) { dma_data->dst_sel == DmaDataDst::Gds) {
rasterizer->InlineData(dma_data->dst_addr_lo, rasterizer->CopyBuffer(dma_data->dst_addr_lo, dma_data->SrcAddress<VAddr>(),
dma_data->SrcAddress<const void*>(), dma_data->NumBytes(), true, false);
dma_data->NumBytes(), true);
} else if (dma_data->src_sel == DmaDataSrc::Data && } else if (dma_data->src_sel == DmaDataSrc::Data &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
@ -649,14 +656,15 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
} else if (dma_data->src_sel == DmaDataSrc::Gds && } else if (dma_data->src_sel == DmaDataSrc::Gds &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
// LOG_WARNING(Render_Vulkan, "GDS memory read"); rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(), dma_data->src_addr_lo,
dma_data->NumBytes(), false, true);
} else if ((dma_data->src_sel == DmaDataSrc::Memory || } else if ((dma_data->src_sel == DmaDataSrc::Memory ||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
rasterizer->InlineData(dma_data->DstAddress<VAddr>(), rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(),
dma_data->SrcAddress<const void*>(), dma_data->SrcAddress<VAddr>(), dma_data->NumBytes(),
dma_data->NumBytes(), false); false, false);
} else { } else {
UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}",
u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value()));
@ -702,6 +710,9 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
break; break;
} }
case PM4ItOpcode::Rewind: { case PM4ItOpcode::Rewind: {
if (!rasterizer) {
break;
}
const PM4CmdRewind* rewind = reinterpret_cast<const PM4CmdRewind*>(header); const PM4CmdRewind* rewind = reinterpret_cast<const PM4CmdRewind*>(header);
while (!rewind->Valid()) { while (!rewind->Valid()) {
YIELD_GFX(); YIELD_GFX();
@ -801,29 +812,32 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
} }
template <bool is_indirect> template <bool is_indirect>
Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid) { Liverpool::Task Liverpool::ProcessCompute(std::span<const u32> acb, u32 vqid) {
FIBER_ENTER(acb_task_name[vqid]); FIBER_ENTER(acb_task_name[vqid]);
auto& queue = asc_queues[{vqid}]; auto& queue = asc_queues[{vqid}];
auto base_addr = reinterpret_cast<VAddr>(acb); auto base_addr = reinterpret_cast<VAddr>(acb.data());
while (acb_dwords > 0) { while (!acb.empty()) {
auto* header = reinterpret_cast<const PM4Header*>(acb); ProcessCommands();
auto* header = reinterpret_cast<const PM4Header*>(acb.data());
u32 next_dw_off = header->type3.NumWords() + 1; u32 next_dw_off = header->type3.NumWords() + 1;
// If we have a buffered packet, use it. // If we have a buffered packet, use it.
if (queue.tmp_dwords > 0) [[unlikely]] { if (queue.tmp_dwords > 0) [[unlikely]] {
header = reinterpret_cast<const PM4Header*>(queue.tmp_packet.data()); header = reinterpret_cast<const PM4Header*>(queue.tmp_packet.data());
next_dw_off = header->type3.NumWords() + 1 - queue.tmp_dwords; next_dw_off = header->type3.NumWords() + 1 - queue.tmp_dwords;
std::memcpy(queue.tmp_packet.data() + queue.tmp_dwords, acb, next_dw_off * sizeof(u32)); std::memcpy(queue.tmp_packet.data() + queue.tmp_dwords, acb.data(),
next_dw_off * sizeof(u32));
queue.tmp_dwords = 0; queue.tmp_dwords = 0;
} }
// If the packet is split across ring boundary, buffer until next submission // If the packet is split across ring boundary, buffer until next submission
if (next_dw_off > acb_dwords) [[unlikely]] { if (next_dw_off > acb.size()) [[unlikely]] {
std::memcpy(queue.tmp_packet.data(), acb, acb_dwords * sizeof(u32)); std::memcpy(queue.tmp_packet.data(), acb.data(), acb.size_bytes());
queue.tmp_dwords = acb_dwords; queue.tmp_dwords = acb.size();
if constexpr (!is_indirect) { if constexpr (!is_indirect) {
*queue.read_addr += acb_dwords; *queue.read_addr += acb.size();
*queue.read_addr %= queue.ring_size_dw; *queue.read_addr %= queue.ring_size_dw;
} }
break; break;
@ -832,9 +846,7 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
if (header->type == 2) { if (header->type == 2) {
// Type-2 packet are used for padding purposes // Type-2 packet are used for padding purposes
next_dw_off = 1; next_dw_off = 1;
acb += next_dw_off; acb = NextPacket(acb, next_dw_off);
acb_dwords -= next_dw_off;
if constexpr (!is_indirect) { if constexpr (!is_indirect) {
*queue.read_addr += next_dw_off; *queue.read_addr += next_dw_off;
*queue.read_addr %= queue.ring_size_dw; *queue.read_addr %= queue.ring_size_dw;
@ -856,8 +868,8 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
} }
case PM4ItOpcode::IndirectBuffer: { case PM4ItOpcode::IndirectBuffer: {
const auto* indirect_buffer = reinterpret_cast<const PM4CmdIndirectBuffer*>(header); const auto* indirect_buffer = reinterpret_cast<const PM4CmdIndirectBuffer*>(header);
auto task = ProcessCompute<true>(indirect_buffer->Address<const u32>(), auto task = ProcessCompute<true>(
indirect_buffer->ib_size, vqid); {indirect_buffer->Address<const u32>(), indirect_buffer->ib_size}, vqid);
RESUME_ASC(task, vqid); RESUME_ASC(task, vqid);
while (!task.handle.done()) { while (!task.handle.done()) {
@ -876,8 +888,8 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
} else if ((dma_data->src_sel == DmaDataSrc::Memory || } else if ((dma_data->src_sel == DmaDataSrc::Memory ||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
dma_data->dst_sel == DmaDataDst::Gds) { dma_data->dst_sel == DmaDataDst::Gds) {
rasterizer->InlineData(dma_data->dst_addr_lo, dma_data->SrcAddress<const void*>(), rasterizer->CopyBuffer(dma_data->dst_addr_lo, dma_data->SrcAddress<VAddr>(),
dma_data->NumBytes(), true); dma_data->NumBytes(), true, false);
} else if (dma_data->src_sel == DmaDataSrc::Data && } else if (dma_data->src_sel == DmaDataSrc::Data &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
@ -886,14 +898,14 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
} else if (dma_data->src_sel == DmaDataSrc::Gds && } else if (dma_data->src_sel == DmaDataSrc::Gds &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
// LOG_WARNING(Render_Vulkan, "GDS memory read"); rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(), dma_data->src_addr_lo,
dma_data->NumBytes(), false, true);
} else if ((dma_data->src_sel == DmaDataSrc::Memory || } else if ((dma_data->src_sel == DmaDataSrc::Memory ||
dma_data->src_sel == DmaDataSrc::MemoryUsingL2) && dma_data->src_sel == DmaDataSrc::MemoryUsingL2) &&
(dma_data->dst_sel == DmaDataDst::Memory || (dma_data->dst_sel == DmaDataDst::Memory ||
dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) { dma_data->dst_sel == DmaDataDst::MemoryUsingL2)) {
rasterizer->InlineData(dma_data->DstAddress<VAddr>(), rasterizer->CopyBuffer(dma_data->DstAddress<VAddr>(), dma_data->SrcAddress<VAddr>(),
dma_data->SrcAddress<const void*>(), dma_data->NumBytes(), dma_data->NumBytes(), false, false);
false);
} else { } else {
UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}", UNREACHABLE_MSG("WriteData src_sel = {}, dst_sel = {}",
u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value())); u32(dma_data->src_sel.Value()), u32(dma_data->dst_sel.Value()));
@ -904,6 +916,9 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
break; break;
} }
case PM4ItOpcode::Rewind: { case PM4ItOpcode::Rewind: {
if (!rasterizer) {
break;
}
const PM4CmdRewind* rewind = reinterpret_cast<const PM4CmdRewind*>(header); const PM4CmdRewind* rewind = reinterpret_cast<const PM4CmdRewind*>(header);
while (!rewind->Valid()) { while (!rewind->Valid()) {
YIELD_ASC(vqid); YIELD_ASC(vqid);
@ -1016,8 +1031,7 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
static_cast<u32>(opcode), header->type3.NumWords()); static_cast<u32>(opcode), header->type3.NumWords());
} }
acb += next_dw_off; acb = NextPacket(acb, next_dw_off);
acb_dwords -= next_dw_off;
if constexpr (!is_indirect) { if constexpr (!is_indirect) {
*queue.read_addr += next_dw_off; *queue.read_addr += next_dw_off;
@ -1087,7 +1101,7 @@ void Liverpool::SubmitAsc(u32 gnm_vqid, std::span<const u32> acb) {
auto& queue = mapped_queues[gnm_vqid]; auto& queue = mapped_queues[gnm_vqid];
const auto vqid = gnm_vqid - 1; const auto vqid = gnm_vqid - 1;
const auto& task = ProcessCompute(acb.data(), acb.size(), vqid); const auto& task = ProcessCompute(acb, vqid);
{ {
std::scoped_lock lock{queue.m_access}; std::scoped_lock lock{queue.m_access};
queue.submits.emplace(task.handle); queue.submits.emplace(task.handle);

View File

@ -8,6 +8,7 @@
#include <coroutine> #include <coroutine>
#include <exception> #include <exception>
#include <mutex> #include <mutex>
#include <semaphore>
#include <span> #include <span>
#include <thread> #include <thread>
#include <vector> #include <vector>
@ -1512,14 +1513,32 @@ public:
rasterizer = rasterizer_; rasterizer = rasterizer_;
} }
void SendCommand(Common::UniqueFunction<void>&& func) { template <bool wait_done = false>
std::scoped_lock lk{submit_mutex}; void SendCommand(auto&& func) {
command_queue.emplace(std::move(func)); if (std::this_thread::get_id() == gpu_id) {
++num_commands; return func();
submit_cv.notify_one(); }
if constexpr (wait_done) {
std::binary_semaphore sem{0};
{
std::scoped_lock lk{submit_mutex};
command_queue.emplace([&sem, &func] {
func();
sem.release();
});
++num_commands;
submit_cv.notify_one();
}
sem.acquire();
} else {
std::scoped_lock lk{submit_mutex};
command_queue.emplace(std::move(func));
++num_commands;
submit_cv.notify_one();
}
} }
void reserveCopyBufferSpace() { void ReserveCopyBufferSpace() {
GpuQueue& gfx_queue = mapped_queues[GfxQueueId]; GpuQueue& gfx_queue = mapped_queues[GfxQueueId];
std::scoped_lock<std::mutex> lk(gfx_queue.m_access); std::scoped_lock<std::mutex> lk(gfx_queue.m_access);
@ -1581,8 +1600,9 @@ private:
Task ProcessGraphics(std::span<const u32> dcb, std::span<const u32> ccb); Task ProcessGraphics(std::span<const u32> dcb, std::span<const u32> ccb);
Task ProcessCeUpdate(std::span<const u32> ccb); Task ProcessCeUpdate(std::span<const u32> ccb);
template <bool is_indirect = false> template <bool is_indirect = false>
Task ProcessCompute(const u32* acb, u32 acb_dwords, u32 vqid); Task ProcessCompute(std::span<const u32> acb, u32 vqid);
void ProcessCommands();
void Process(std::stop_token stoken); void Process(std::stop_token stoken);
struct GpuQueue { struct GpuQueue {
@ -1626,6 +1646,7 @@ private:
std::mutex submit_mutex; std::mutex submit_mutex;
std::condition_variable_any submit_cv; std::condition_variable_any submit_cv;
std::queue<Common::UniqueFunction<void>> command_queue{}; std::queue<Common::UniqueFunction<void>> command_queue{};
std::thread::id gpu_id;
int curr_qid{-1}; int curr_qid{-1};
}; };

View File

@ -3,12 +3,14 @@
#include <algorithm> #include <algorithm>
#include "common/alignment.h" #include "common/alignment.h"
#include "common/config.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/scope_exit.h" #include "common/scope_exit.h"
#include "common/types.h" #include "common/types.h"
#include "core/memory.h" #include "core/memory.h"
#include "video_core/amdgpu/liverpool.h" #include "video_core/amdgpu/liverpool.h"
#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/buffer_cache/memory_tracker.h"
#include "video_core/host_shaders/fault_buffer_process_comp.h" #include "video_core/host_shaders/fault_buffer_process_comp.h"
#include "video_core/renderer_vulkan/vk_graphics_pipeline.h" #include "video_core/renderer_vulkan/vk_graphics_pipeline.h"
#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_instance.h"
@ -27,10 +29,10 @@ static constexpr size_t DeviceBufferSize = 128_MB;
static constexpr size_t MaxPageFaults = 1024; static constexpr size_t MaxPageFaults = 1024;
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_, BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_, AmdGpu::Liverpool* liverpool_, TextureCache& texture_cache_,
TextureCache& texture_cache_, PageManager& tracker_) PageManager& tracker)
: instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_}, : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_},
memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, tracker{tracker_}, memory{Core::Memory::Instance()}, texture_cache{texture_cache_},
staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize}, staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize},
stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize}, stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize},
download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize}, download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize},
@ -38,13 +40,14 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s
gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize}, gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize},
bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal, bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal,
0, AllFlags, BDA_PAGETABLE_SIZE}, 0, AllFlags, BDA_PAGETABLE_SIZE},
fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE), fault_buffer(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, FAULT_BUFFER_SIZE) {
memory_tracker{tracker} {
Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer"); Vulkan::SetObjectName(instance.GetDevice(), gds_buffer.Handle(), "GDS Buffer");
Vulkan::SetObjectName(instance.GetDevice(), bda_pagetable_buffer.Handle(), Vulkan::SetObjectName(instance.GetDevice(), bda_pagetable_buffer.Handle(),
"BDA Page Table Buffer"); "BDA Page Table Buffer");
Vulkan::SetObjectName(instance.GetDevice(), fault_buffer.Handle(), "Fault Buffer"); Vulkan::SetObjectName(instance.GetDevice(), fault_buffer.Handle(), "Fault Buffer");
memory_tracker = std::make_unique<MemoryTracker>(tracker);
// Ensure the first slot is used for the null buffer // Ensure the first slot is used for the null buffer
const auto null_id = const auto null_id =
slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16); slot_buffers.insert(instance, scheduler, MemoryUsage::DeviceLocal, 0, AllFlags, 16);
@ -129,22 +132,27 @@ BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& s
BufferCache::~BufferCache() = default; BufferCache::~BufferCache() = default;
void BufferCache::InvalidateMemory(VAddr device_addr, u64 size, bool unmap) { void BufferCache::InvalidateMemory(VAddr device_addr, u64 size) {
const bool is_tracked = IsRegionRegistered(device_addr, size); if (!IsRegionRegistered(device_addr, size)) {
if (is_tracked) { return;
// Mark the page as CPU modified to stop tracking writes.
memory_tracker.MarkRegionAsCpuModified(device_addr, size);
if (unmap) {
return;
}
} }
if (Config::readbacks() && memory_tracker->IsRegionGpuModified(device_addr, size)) {
ReadMemory(device_addr, size);
}
memory_tracker->MarkRegionAsCpuModified(device_addr, size);
}
void BufferCache::ReadMemory(VAddr device_addr, u64 size) {
liverpool->SendCommand<true>([this, device_addr, size] {
Buffer& buffer = slot_buffers[FindBuffer(device_addr, size)];
DownloadBufferMemory(buffer, device_addr, size);
});
} }
void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) { void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size) {
boost::container::small_vector<vk::BufferCopy, 1> copies; boost::container::small_vector<vk::BufferCopy, 1> copies;
u64 total_size_bytes = 0; u64 total_size_bytes = 0;
memory_tracker.ForEachDownloadRange<true>( memory_tracker->ForEachDownloadRange<false>(
device_addr, size, [&](u64 device_addr_out, u64 range_size) { device_addr, size, [&](u64 device_addr_out, u64 range_size) {
const VAddr buffer_addr = buffer.CpuAddr(); const VAddr buffer_addr = buffer.CpuAddr();
const auto add_download = [&](VAddr start, VAddr end) { const auto add_download = [&](VAddr start, VAddr end) {
@ -155,7 +163,10 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si
.dstOffset = total_size_bytes, .dstOffset = total_size_bytes,
.size = new_size, .size = new_size,
}); });
total_size_bytes += new_size; // Align up to avoid cache conflicts
constexpr u64 align = 64ULL;
constexpr u64 mask = ~(align - 1ULL);
total_size_bytes += (new_size + align - 1) & mask;
}; };
gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download); gpu_modified_ranges.ForEachInRange(device_addr_out, range_size, add_download);
gpu_modified_ranges.Subtract(device_addr_out, range_size); gpu_modified_ranges.Subtract(device_addr_out, range_size);
@ -173,11 +184,14 @@ void BufferCache::DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 si
const auto cmdbuf = scheduler.CommandBuffer(); const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.copyBuffer(buffer.buffer, download_buffer.Handle(), copies); cmdbuf.copyBuffer(buffer.buffer, download_buffer.Handle(), copies);
scheduler.Finish(); scheduler.Finish();
auto* memory = Core::Memory::Instance();
for (const auto& copy : copies) { for (const auto& copy : copies) {
const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset; const VAddr copy_device_addr = buffer.CpuAddr() + copy.srcOffset;
const u64 dst_offset = copy.dstOffset - offset; const u64 dst_offset = copy.dstOffset - offset;
std::memcpy(std::bit_cast<u8*>(copy_device_addr), download + dst_offset, copy.size); memory->TryWriteBacking(std::bit_cast<u8*>(copy_device_addr), download + dst_offset,
copy.size);
} }
memory_tracker->UnmarkRegionAsGpuModified(device_addr, size);
} }
void BufferCache::BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline) { void BufferCache::BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline) {
@ -296,9 +310,11 @@ void BufferCache::BindIndexBuffer(u32 index_offset) {
void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) { void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {
ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned"); ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned");
if (!is_gds && !IsRegionGpuModified(address, num_bytes)) { if (!is_gds) {
memcpy(std::bit_cast<void*>(address), value, num_bytes); ASSERT(memory->TryWriteBacking(std::bit_cast<void*>(address), value, num_bytes));
return; if (!IsRegionRegistered(address, num_bytes)) {
return;
}
} }
Buffer* buffer = [&] { Buffer* buffer = [&] {
if (is_gds) { if (is_gds) {
@ -326,25 +342,108 @@ void BufferCache::WriteData(VAddr address, const void* value, u32 num_bytes, boo
WriteDataBuffer(*buffer, address, value, num_bytes); WriteDataBuffer(*buffer, address, value, num_bytes);
} }
void BufferCache::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) {
if (!dst_gds && !IsRegionGpuModified(dst, num_bytes)) {
if (!src_gds && !IsRegionGpuModified(src, num_bytes)) {
// Both buffers were not transferred to GPU yet. Can safely copy in host memory.
memcpy(std::bit_cast<void*>(dst), std::bit_cast<void*>(src), num_bytes);
return;
}
// Without a readback there's nothing we can do with this
// Fallback to creating dst buffer on GPU to at least have this data there
}
auto& src_buffer = [&] -> const Buffer& {
if (src_gds) {
return gds_buffer;
}
// Avoid using ObtainBuffer here as that might give us the stream buffer.
const BufferId buffer_id = FindBuffer(src, num_bytes);
auto& buffer = slot_buffers[buffer_id];
SynchronizeBuffer(buffer, src, num_bytes, false);
return buffer;
}();
auto& dst_buffer = [&] -> const Buffer& {
if (dst_gds) {
return gds_buffer;
}
// Prefer using ObtainBuffer here as that will auto-mark the region as GPU modified.
const auto [buffer, offset] = ObtainBuffer(dst, num_bytes, true);
return *buffer;
}();
vk::BufferCopy region{
.srcOffset = src_buffer.Offset(src),
.dstOffset = dst_buffer.Offset(dst),
.size = num_bytes,
};
const vk::BufferMemoryBarrier2 buf_barriers_before[2] = {
{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eMemoryRead,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eTransferWrite,
.buffer = dst_buffer.Handle(),
.offset = dst_buffer.Offset(dst),
.size = num_bytes,
},
{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eMemoryWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eTransferRead,
.buffer = src_buffer.Handle(),
.offset = src_buffer.Offset(src),
.size = num_bytes,
},
};
scheduler.EndRendering();
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 2,
.pBufferMemoryBarriers = buf_barriers_before,
});
cmdbuf.copyBuffer(src_buffer.Handle(), dst_buffer.Handle(), region);
const vk::BufferMemoryBarrier2 buf_barriers_after[2] = {
{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eTransferWrite,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryRead,
.buffer = dst_buffer.Handle(),
.offset = dst_buffer.Offset(dst),
.size = num_bytes,
},
{
.srcStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.srcAccessMask = vk::AccessFlagBits2::eTransferRead,
.dstStageMask = vk::PipelineStageFlagBits2::eAllCommands,
.dstAccessMask = vk::AccessFlagBits2::eMemoryWrite,
.buffer = src_buffer.Handle(),
.offset = src_buffer.Offset(src),
.size = num_bytes,
},
};
cmdbuf.pipelineBarrier2(vk::DependencyInfo{
.dependencyFlags = vk::DependencyFlagBits::eByRegion,
.bufferMemoryBarrierCount = 2,
.pBufferMemoryBarriers = buf_barriers_after,
});
}
std::pair<Buffer*, u32> BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written, std::pair<Buffer*, u32> BufferCache::ObtainBuffer(VAddr device_addr, u32 size, bool is_written,
bool is_texel_buffer, BufferId buffer_id) { bool is_texel_buffer, BufferId buffer_id) {
// For small uniform buffers that have not been modified by gpu // For read-only buffers use device local stream buffer to reduce renderpass breaks.
// use device local stream buffer to reduce renderpass breaks. if (!is_written && size <= CACHING_PAGESIZE && !IsRegionGpuModified(device_addr, size)) {
// Maybe we want to modify the threshold now that the page size is 16KB?
static constexpr u64 StreamThreshold = CACHING_PAGESIZE;
const bool is_gpu_dirty = memory_tracker.IsRegionGpuModified(device_addr, size);
if (!is_written && size <= StreamThreshold && !is_gpu_dirty) {
const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment()); const u64 offset = stream_buffer.Copy(device_addr, size, instance.UniformMinAlignment());
return {&stream_buffer, offset}; return {&stream_buffer, offset};
} }
if (IsBufferInvalid(buffer_id)) {
if (!buffer_id || slot_buffers[buffer_id].is_deleted) {
buffer_id = FindBuffer(device_addr, size); buffer_id = FindBuffer(device_addr, size);
} }
Buffer& buffer = slot_buffers[buffer_id]; Buffer& buffer = slot_buffers[buffer_id];
SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer); SynchronizeBuffer(buffer, device_addr, size, is_texel_buffer);
if (is_written) { if (is_written) {
memory_tracker.MarkRegionAsGpuModified(device_addr, size); memory_tracker->MarkRegionAsGpuModified(device_addr, size);
gpu_modified_ranges.Add(device_addr, size); gpu_modified_ranges.Add(device_addr, size);
} }
return {&buffer, buffer.Offset(device_addr)}; return {&buffer, buffer.Offset(device_addr)};
@ -352,21 +451,17 @@ std::pair<Buffer*, u32> BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b
std::pair<Buffer*, u32> BufferCache::ObtainBufferForImage(VAddr gpu_addr, u32 size) { std::pair<Buffer*, u32> BufferCache::ObtainBufferForImage(VAddr gpu_addr, u32 size) {
// Check if any buffer contains the full requested range. // Check if any buffer contains the full requested range.
const u64 page = gpu_addr >> CACHING_PAGEBITS; const BufferId buffer_id = page_table[gpu_addr >> CACHING_PAGEBITS].buffer_id;
const BufferId buffer_id = page_table[page].buffer_id;
if (buffer_id) { if (buffer_id) {
Buffer& buffer = slot_buffers[buffer_id]; if (Buffer& buffer = slot_buffers[buffer_id]; buffer.IsInBounds(gpu_addr, size)) {
if (buffer.IsInBounds(gpu_addr, size)) {
SynchronizeBuffer(buffer, gpu_addr, size, false); SynchronizeBuffer(buffer, gpu_addr, size, false);
return {&buffer, buffer.Offset(gpu_addr)}; return {&buffer, buffer.Offset(gpu_addr)};
} }
} }
// If no buffer contains the full requested range but some buffer within was GPU-modified, // If some buffer within was GPU modified create a full buffer to avoid losing GPU data.
// fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications. if (IsRegionGpuModified(gpu_addr, size)) {
if (memory_tracker.IsRegionGpuModified(gpu_addr, size)) {
return ObtainBuffer(gpu_addr, size, false, false); return ObtainBuffer(gpu_addr, size, false, false);
} }
// In all other cases, just do a CPU copy to the staging buffer. // In all other cases, just do a CPU copy to the staging buffer.
const auto [data, offset] = staging_buffer.Map(size, 16); const auto [data, offset] = staging_buffer.Map(size, 16);
memory->CopySparseMemory(gpu_addr, data, size); memory->CopySparseMemory(gpu_addr, data, size);
@ -380,11 +475,11 @@ bool BufferCache::IsRegionRegistered(VAddr addr, size_t size) {
} }
bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) { bool BufferCache::IsRegionCpuModified(VAddr addr, size_t size) {
return memory_tracker.IsRegionCpuModified(addr, size); return memory_tracker->IsRegionCpuModified(addr, size);
} }
bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) { bool BufferCache::IsRegionGpuModified(VAddr addr, size_t size) {
return memory_tracker.IsRegionGpuModified(addr, size); return memory_tracker->IsRegionGpuModified(addr, size);
} }
BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) { BufferId BufferCache::FindBuffer(VAddr device_addr, u32 size) {
@ -723,7 +818,7 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
boost::container::small_vector<vk::BufferCopy, 4> copies; boost::container::small_vector<vk::BufferCopy, 4> copies;
u64 total_size_bytes = 0; u64 total_size_bytes = 0;
VAddr buffer_start = buffer.CpuAddr(); VAddr buffer_start = buffer.CpuAddr();
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) { memory_tracker->ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
copies.push_back(vk::BufferCopy{ copies.push_back(vk::BufferCopy{
.srcOffset = total_size_bytes, .srcOffset = total_size_bytes,
.dstOffset = device_addr_out - buffer_start, .dstOffset = device_addr_out - buffer_start,

View File

@ -9,7 +9,6 @@
#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.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"
@ -21,13 +20,6 @@ namespace Core {
class MemoryManager; class MemoryManager;
} }
namespace Shader {
namespace Gcn {
struct FetchShaderData;
}
struct Info;
} // namespace Shader
namespace Vulkan { namespace Vulkan {
class GraphicsPipeline; class GraphicsPipeline;
} }
@ -39,6 +31,8 @@ using BufferId = Common::SlotId;
static constexpr BufferId NULL_BUFFER_ID{0}; static constexpr BufferId NULL_BUFFER_ID{0};
class TextureCache; class TextureCache;
class MemoryTracker;
class PageManager;
class BufferCache { class BufferCache {
public: public:
@ -69,10 +63,16 @@ public:
bool has_stream_leap = false; bool has_stream_leap = false;
}; };
using IntervalSet =
boost::icl::interval_set<VAddr, std::less,
ICL_INTERVAL_INSTANCE(ICL_INTERVAL_DEFAULT, VAddr, std::less),
RangeSetsAllocator>;
using IntervalType = typename IntervalSet::interval_type;
public: public:
explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler, explicit BufferCache(const Vulkan::Instance& instance, Vulkan::Scheduler& scheduler,
Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool, AmdGpu::Liverpool* liverpool, TextureCache& texture_cache,
TextureCache& texture_cache, PageManager& tracker); PageManager& tracker);
~BufferCache(); ~BufferCache();
/// Returns a pointer to GDS device local buffer. /// Returns a pointer to GDS device local buffer.
@ -110,7 +110,10 @@ public:
} }
/// Invalidates any buffer in the logical page range. /// Invalidates any buffer in the logical page range.
void InvalidateMemory(VAddr device_addr, u64 size, bool unmap); void InvalidateMemory(VAddr device_addr, u64 size);
/// Waits on pending downloads in the logical page range.
void ReadMemory(VAddr device_addr, u64 size);
/// Binds host vertex buffers for the current draw. /// Binds host vertex buffers for the current draw.
void BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline); void BindVertexBuffers(const Vulkan::GraphicsPipeline& pipeline);
@ -124,6 +127,9 @@ public:
/// Writes a value to GPU buffer. (uses staging buffer to temporarily store the data) /// Writes a value to GPU buffer. (uses staging buffer to temporarily store the data)
void WriteData(VAddr address, const void* value, u32 num_bytes, bool is_gds); void WriteData(VAddr address, const void* value, u32 num_bytes, bool is_gds);
/// Performs buffer to buffer data copy on the GPU.
void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds);
/// Obtains a buffer for the specified region. /// Obtains a buffer for the specified region.
[[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written, [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(VAddr gpu_addr, u32 size, bool is_written,
bool is_texel_buffer = false, bool is_texel_buffer = false,
@ -166,6 +172,10 @@ private:
}); });
} }
inline bool IsBufferInvalid(BufferId buffer_id) const {
return !buffer_id || slot_buffers[buffer_id].is_deleted;
}
void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size); void DownloadBufferMemory(Buffer& buffer, VAddr device_addr, u64 size);
[[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size); [[nodiscard]] OverlapResult ResolveOverlaps(VAddr device_addr, u32 wanted_size);
@ -193,11 +203,10 @@ private:
const Vulkan::Instance& instance; const Vulkan::Instance& instance;
Vulkan::Scheduler& scheduler; Vulkan::Scheduler& scheduler;
Vulkan::Rasterizer& rasterizer;
AmdGpu::Liverpool* liverpool; AmdGpu::Liverpool* liverpool;
Core::MemoryManager* memory; Core::MemoryManager* memory;
TextureCache& texture_cache; TextureCache& texture_cache;
PageManager& tracker; std::unique_ptr<MemoryTracker> memory_tracker;
StreamBuffer staging_buffer; StreamBuffer staging_buffer;
StreamBuffer stream_buffer; StreamBuffer stream_buffer;
StreamBuffer download_buffer; StreamBuffer download_buffer;
@ -209,7 +218,6 @@ private:
Common::SlotVector<Buffer> slot_buffers; Common::SlotVector<Buffer> slot_buffers;
RangeSet gpu_modified_ranges; RangeSet gpu_modified_ranges;
SplitRangeMap<BufferId> buffer_ranges; SplitRangeMap<BufferId> buffer_ranges;
MemoryTracker memory_tracker;
PageTable page_table; PageTable page_table;
vk::UniqueDescriptorSetLayout fault_process_desc_layout; vk::UniqueDescriptorSetLayout fault_process_desc_layout;
vk::UniquePipeline fault_process_pipeline; vk::UniquePipeline fault_process_pipeline;

View File

@ -16,7 +16,7 @@ namespace VideoCore {
class MemoryTracker { class MemoryTracker {
public: public:
static constexpr size_t MAX_CPU_PAGE_BITS = 40; static constexpr size_t MAX_CPU_PAGE_BITS = 40;
static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS); static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - TRACKER_HIGHER_PAGE_BITS);
static constexpr size_t MANAGER_POOL_SIZE = 32; static constexpr size_t MANAGER_POOL_SIZE = 32;
public: public:
@ -57,6 +57,14 @@ public:
}); });
} }
void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
IteratePages<false>(dirty_cpu_addr, query_size,
[](RegionManager* manager, u64 offset, size_t size) {
manager->template ChangeRegionState<Type::GPU, false>(
manager->GetCpuAddr() + offset, size);
});
}
/// Call 'func' for each CPU modified range and unmark those pages as CPU modified /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, auto&& func) { void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, auto&& func) {
IteratePages<true>(query_cpu_range, query_size, IteratePages<true>(query_cpu_range, query_size,
@ -90,11 +98,11 @@ private:
using FuncReturn = typename std::invoke_result<Func, RegionManager*, u64, size_t>::type; using FuncReturn = typename std::invoke_result<Func, RegionManager*, u64, size_t>::type;
static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>; static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
std::size_t remaining_size{size}; std::size_t remaining_size{size};
std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS}; std::size_t page_index{cpu_address >> TRACKER_HIGHER_PAGE_BITS};
u64 page_offset{cpu_address & HIGHER_PAGE_MASK}; u64 page_offset{cpu_address & TRACKER_HIGHER_PAGE_MASK};
while (remaining_size > 0) { while (remaining_size > 0) {
const std::size_t copy_amount{ const std::size_t copy_amount{
std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)}; std::min<std::size_t>(TRACKER_HIGHER_PAGE_SIZE - page_offset, remaining_size)};
auto* manager{top_tier[page_index]}; auto* manager{top_tier[page_index]};
if (manager) { if (manager) {
if constexpr (BOOL_BREAK) { if constexpr (BOOL_BREAK) {
@ -123,7 +131,7 @@ private:
} }
void CreateRegion(std::size_t page_index) { void CreateRegion(std::size_t page_index) {
const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS; const VAddr base_cpu_addr = page_index << TRACKER_HIGHER_PAGE_BITS;
if (free_managers.empty()) { if (free_managers.empty()) {
manager_pool.emplace_back(); manager_pool.emplace_back();
auto& last_pool = manager_pool.back(); auto& last_pool = manager_pool.back();

View File

@ -3,26 +3,24 @@
#pragma once #pragma once
#include <array>
#include "common/bit_array.h" #include "common/bit_array.h"
#include "common/types.h" #include "common/types.h"
namespace VideoCore { namespace VideoCore {
constexpr u64 PAGES_PER_WORD = 64; constexpr u64 TRACKER_PAGE_BITS = 12; // 4K pages
constexpr u64 BYTES_PER_PAGE = 4_KB; constexpr u64 TRACKER_BYTES_PER_PAGE = 1ULL << TRACKER_PAGE_BITS;
constexpr u64 HIGHER_PAGE_BITS = 22; constexpr u64 TRACKER_HIGHER_PAGE_BITS = 24; // each region is 16MB
constexpr u64 HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS; constexpr u64 TRACKER_HIGHER_PAGE_SIZE = 1ULL << TRACKER_HIGHER_PAGE_BITS;
constexpr u64 HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL; constexpr u64 TRACKER_HIGHER_PAGE_MASK = TRACKER_HIGHER_PAGE_SIZE - 1ULL;
constexpr u64 NUM_REGION_PAGES = HIGHER_PAGE_SIZE / BYTES_PER_PAGE; constexpr u64 NUM_PAGES_PER_REGION = TRACKER_HIGHER_PAGE_SIZE / TRACKER_BYTES_PER_PAGE;
enum class Type { enum class Type {
CPU, CPU,
GPU, GPU,
Writeable,
}; };
using RegionBits = Common::BitArray<NUM_REGION_PAGES>; using RegionBits = Common::BitArray<NUM_PAGES_PER_REGION>;
} // namespace VideoCore } // namespace VideoCore

View File

@ -4,7 +4,7 @@
#pragma once #pragma once
#include <mutex> #include <mutex>
#include <utility> #include "common/config.h"
#include "common/div_ceil.h" #include "common/div_ceil.h"
#ifdef __linux__ #ifdef __linux__
@ -20,7 +20,7 @@
namespace VideoCore { namespace VideoCore {
/** /**
* Allows tracking CPU and GPU modification of pages in a contigious 4MB virtual address region. * Allows tracking CPU and GPU modification of pages in a contigious 16MB virtual address region.
* Information is stored in bitsets for spacial locality and fast update of single pages. * Information is stored in bitsets for spacial locality and fast update of single pages.
*/ */
class RegionManager { class RegionManager {
@ -30,6 +30,7 @@ public:
cpu.Fill(); cpu.Fill();
gpu.Clear(); gpu.Clear();
writeable.Fill(); writeable.Fill();
readable.Fill();
} }
explicit RegionManager() = default; explicit RegionManager() = default;
@ -47,29 +48,19 @@ public:
template <Type type> template <Type type>
RegionBits& GetRegionBits() noexcept { RegionBits& GetRegionBits() noexcept {
static_assert(type != Type::Writeable);
if constexpr (type == Type::CPU) { if constexpr (type == Type::CPU) {
return cpu; return cpu;
} else if constexpr (type == Type::GPU) { } else if constexpr (type == Type::GPU) {
return gpu; return gpu;
} else if constexpr (type == Type::Writeable) {
return writeable;
} else {
static_assert(false, "Invalid type");
} }
} }
template <Type type> template <Type type>
const RegionBits& GetRegionBits() const noexcept { const RegionBits& GetRegionBits() const noexcept {
static_assert(type != Type::Writeable);
if constexpr (type == Type::CPU) { if constexpr (type == Type::CPU) {
return cpu; return cpu;
} else if constexpr (type == Type::GPU) { } else if constexpr (type == Type::GPU) {
return gpu; return gpu;
} else if constexpr (type == Type::Writeable) {
return writeable;
} else {
static_assert(false, "Invalid type");
} }
} }
@ -83,13 +74,13 @@ public:
void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) { void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
RENDERER_TRACE; RENDERER_TRACE;
const size_t offset = dirty_addr - cpu_addr; const size_t offset = dirty_addr - cpu_addr;
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE;
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); const size_t end_page =
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE);
if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) {
return; return;
} }
std::scoped_lock lk{lock}; std::scoped_lock lk{lock};
static_assert(type != Type::Writeable);
RegionBits& bits = GetRegionBits<type>(); RegionBits& bits = GetRegionBits<type>();
if constexpr (enable) { if constexpr (enable) {
@ -98,7 +89,9 @@ public:
bits.UnsetRange(start_page, end_page); bits.UnsetRange(start_page, end_page);
} }
if constexpr (type == Type::CPU) { if constexpr (type == Type::CPU) {
UpdateProtection<!enable>(); UpdateProtection<!enable, false>();
} else if (Config::readbacks()) {
UpdateProtection<enable, true>();
} }
} }
@ -114,30 +107,27 @@ public:
void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) { void ForEachModifiedRange(VAddr query_cpu_range, s64 size, auto&& func) {
RENDERER_TRACE; RENDERER_TRACE;
const size_t offset = query_cpu_range - cpu_addr; const size_t offset = query_cpu_range - cpu_addr;
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE;
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); const size_t end_page =
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE);
if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) {
return; return;
} }
std::scoped_lock lk{lock}; std::scoped_lock lk{lock};
static_assert(type != Type::Writeable);
RegionBits& bits = GetRegionBits<type>(); RegionBits& bits = GetRegionBits<type>();
RegionBits mask(bits, start_page, end_page); 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) { for (const auto& [start, end] : mask) {
func(cpu_addr + start * BYTES_PER_PAGE, (end - start) * BYTES_PER_PAGE); func(cpu_addr + start * TRACKER_BYTES_PER_PAGE, (end - start) * TRACKER_BYTES_PER_PAGE);
} }
if constexpr (clear) { if constexpr (clear) {
bits.UnsetRange(start_page, end_page); bits.UnsetRange(start_page, end_page);
if constexpr (type == Type::CPU) { if constexpr (type == Type::CPU) {
UpdateProtection<true>(); UpdateProtection<true, false>();
} else if (Config::readbacks()) {
UpdateProtection<false, true>();
} }
} }
} }
@ -149,24 +139,18 @@ public:
* @param size Size in bytes of the region to query for modifications * @param size Size in bytes of the region to query for modifications
*/ */
template <Type type> template <Type type>
[[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept { [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) noexcept {
RENDERER_TRACE; RENDERER_TRACE;
const size_t start_page = SanitizeAddress(offset) / BYTES_PER_PAGE; const size_t start_page = SanitizeAddress(offset) / TRACKER_BYTES_PER_PAGE;
const size_t end_page = Common::DivCeil(SanitizeAddress(offset + size), BYTES_PER_PAGE); const size_t end_page =
if (start_page >= NUM_REGION_PAGES || end_page <= start_page) { Common::DivCeil(SanitizeAddress(offset + size), TRACKER_BYTES_PER_PAGE);
if (start_page >= NUM_PAGES_PER_REGION || end_page <= start_page) {
return false; return false;
} }
// std::scoped_lock lk{lock}; // Is this needed? std::scoped_lock lk{lock};
static_assert(type != Type::Writeable);
const RegionBits& bits = GetRegionBits<type>(); const RegionBits& bits = GetRegionBits<type>();
RegionBits test(bits, start_page, end_page); 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(); return test.Any();
} }
@ -178,19 +162,21 @@ private:
* @param current_bits Current state of the word * @param current_bits Current state of the word
* @param new_bits New 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 * @tparam track True when the tracker should start tracking the new pages
*/ */
template <bool add_to_tracker> template <bool track, bool is_read>
void UpdateProtection() { void UpdateProtection() {
RENDERER_TRACE; RENDERER_TRACE;
RegionBits mask = cpu ^ writeable; RegionBits mask = is_read ? (~gpu ^ readable) : (cpu ^ writeable);
if (mask.None()) { if (mask.None()) {
return; // No changes to the CPU tracking state return;
} }
if constexpr (is_read) {
writeable = cpu; readable = ~gpu;
tracker->UpdatePageWatchersForRegion<add_to_tracker>(cpu_addr, mask); } else {
writeable = cpu;
}
tracker->UpdatePageWatchersForRegion<track, is_read>(cpu_addr, mask);
} }
#ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP #ifdef PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP
@ -203,6 +189,7 @@ private:
RegionBits cpu; RegionBits cpu;
RegionBits gpu; RegionBits gpu;
RegionBits writeable; RegionBits writeable;
RegionBits readable;
}; };
} // namespace VideoCore } // namespace VideoCore

View File

@ -4,6 +4,7 @@
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include "common/assert.h" #include "common/assert.h"
#include "common/debug.h" #include "common/debug.h"
#include "common/range_lock.h"
#include "common/signal_context.h" #include "common/signal_context.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/signals.h" #include "core/signals.h"
@ -12,6 +13,7 @@
#ifndef _WIN64 #ifndef _WIN64
#include <sys/mman.h> #include <sys/mman.h>
#include "common/adaptive_mutex.h"
#ifdef ENABLE_USERFAULTFD #ifdef ENABLE_USERFAULTFD
#include <thread> #include <thread>
#include <fcntl.h> #include <fcntl.h>
@ -22,6 +24,7 @@
#endif #endif
#else #else
#include <windows.h> #include <windows.h>
#include "common/spin_lock.h"
#endif #endif
#ifdef __linux__ #ifdef __linux__
@ -37,28 +40,52 @@ constexpr size_t PAGE_BITS = 12;
struct PageManager::Impl { struct PageManager::Impl {
struct PageState { struct PageState {
u8 num_watchers{}; u8 num_write_watchers : 7;
// At the moment only buffer cache can request read watchers.
// And buffers cannot overlap, thus only 1 can exist per page.
u8 num_read_watchers : 1;
Core::MemoryPermission Perm() const noexcept { Core::MemoryPermission WritePerm() const noexcept {
return num_watchers == 0 ? Core::MemoryPermission::ReadWrite return num_write_watchers == 0 ? Core::MemoryPermission::Write
: Core::MemoryPermission::Read; : Core::MemoryPermission::None;
} }
template <s32 delta> Core::MemoryPermission ReadPerm() const noexcept {
return num_read_watchers == 0 ? Core::MemoryPermission::Read
: Core::MemoryPermission::None;
}
Core::MemoryPermission Perms() const noexcept {
return ReadPerm() | WritePerm();
}
template <s32 delta, bool is_read>
u8 AddDelta() { u8 AddDelta() {
if constexpr (delta == 1) { if constexpr (is_read) {
return ++num_watchers; if constexpr (delta == 1) {
} else if constexpr (delta == -1) { return ++num_read_watchers;
ASSERT_MSG(num_watchers > 0, "Not enough watchers"); } else if (delta == -1) {
return --num_watchers; ASSERT_MSG(num_read_watchers > 0, "Not enough watchers");
return --num_read_watchers;
} else {
return num_read_watchers;
}
} else { } else {
return num_watchers; if constexpr (delta == 1) {
return ++num_write_watchers;
} else if (delta == -1) {
ASSERT_MSG(num_write_watchers > 0, "Not enough watchers");
return --num_write_watchers;
} else {
return num_write_watchers;
}
} }
} }
}; };
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);
static constexpr size_t NUM_ADDRESS_LOCKS = NUM_ADDRESS_PAGES / PAGES_PER_LOCK;
inline static Vulkan::Rasterizer* rasterizer; inline static Vulkan::Rasterizer* rasterizer;
#ifdef ENABLE_USERFAULTFD #ifdef ENABLE_USERFAULTFD
Impl(Vulkan::Rasterizer* rasterizer_) { Impl(Vulkan::Rasterizer* rasterizer_) {
@ -174,6 +201,7 @@ struct PageManager::Impl {
RENDERER_TRACE; RENDERER_TRACE;
auto* memory = Core::Memory::Instance(); auto* memory = Core::Memory::Instance();
auto& impl = memory->GetAddressSpace(); auto& impl = memory->GetAddressSpace();
// ASSERT(perms != Core::MemoryPermission::Write);
impl.Protect(address, size, perms); impl.Protect(address, size, perms);
} }
@ -181,19 +209,29 @@ struct PageManager::Impl {
const auto addr = reinterpret_cast<VAddr>(fault_address); const auto addr = reinterpret_cast<VAddr>(fault_address);
if (Common::IsWriteError(context)) { if (Common::IsWriteError(context)) {
return rasterizer->InvalidateMemory(addr, 1); return rasterizer->InvalidateMemory(addr, 1);
} else {
return rasterizer->ReadMemory(addr, 1);
} }
return false; return false;
} }
#endif #endif
template <bool track>
template <bool track, bool is_read>
void UpdatePageWatchers(VAddr addr, u64 size) { void UpdatePageWatchers(VAddr addr, u64 size) {
RENDERER_TRACE; RENDERER_TRACE;
size_t page = addr >> PAGE_BITS; size_t page = addr >> PAGE_BITS;
auto perms = cached_pages[page].Perm(); const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
// Acquire locks for the range of pages
const auto lock_start = locks.begin() + (page / PAGES_PER_LOCK);
const auto lock_end = locks.begin() + Common::DivCeil(page_end, PAGES_PER_LOCK);
Common::RangeLockGuard lk(lock_start, lock_end);
auto perms = cached_pages[page].Perms();
u64 range_begin = 0; u64 range_begin = 0;
u64 range_bytes = 0; u64 range_bytes = 0;
u64 potential_range_bytes = 0;
const auto release_pending = [&] { const auto release_pending = [&] {
if (range_bytes > 0) { if (range_bytes > 0) {
@ -201,13 +239,11 @@ struct PageManager::Impl {
// Perform pending (un)protect action // Perform pending (un)protect action
Protect(range_begin << PAGE_BITS, range_bytes, perms); Protect(range_begin << PAGE_BITS, range_bytes, perms);
range_bytes = 0; range_bytes = 0;
potential_range_bytes = 0;
} }
}; };
std::scoped_lock lk(lock);
// Iterate requested pages // Iterate requested pages
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
const u64 aligned_addr = page << PAGE_BITS; const u64 aligned_addr = page << PAGE_BITS;
const u64 aligned_end = page_end << PAGE_BITS; const u64 aligned_end = page_end << PAGE_BITS;
ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr), ASSERT_MSG(rasterizer->IsMapped(aligned_addr, aligned_end - aligned_addr),
@ -218,21 +254,26 @@ struct PageManager::Impl {
PageState& state = cached_pages[page]; PageState& state = cached_pages[page];
// Apply the change to the page state // Apply the change to the page state
const u8 new_count = state.AddDelta<track ? 1 : -1>(); const u8 new_count = state.AddDelta<track ? 1 : -1, is_read>();
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { if (auto new_perms = state.Perms(); new_perms != perms) [[unlikely]] {
// If the protection changed add pending (un)protect action // If the protection changed add pending (un)protect action
release_pending(); release_pending();
perms = new_perms; perms = new_perms;
} else if (range_bytes != 0) { } else if (range_bytes != 0) {
// If the protection did not change, extend the current range // If the protection did not change, extend the potential range
range_bytes += PAGE_SIZE; potential_range_bytes += PAGE_SIZE;
} }
// Only start a new range if the page must be (un)protected // Only start a new range if the page must be (un)protected
if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { if ((new_count == 0 && !track) || (new_count == 1 && track)) {
range_begin = page; if (range_bytes == 0) {
range_bytes = PAGE_SIZE; // Start a new potential range
range_begin = page;
potential_range_bytes = PAGE_SIZE;
}
// Extend current range up to potential range
range_bytes = potential_range_bytes;
} }
} }
@ -240,25 +281,26 @@ struct PageManager::Impl {
release_pending(); release_pending();
} }
template <bool track> template <bool track, bool is_read>
void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) { void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) {
RENDERER_TRACE; RENDERER_TRACE;
auto start_range = mask.FirstRange(); auto start_range = mask.FirstRange();
auto end_range = mask.LastRange(); auto end_range = mask.LastRange();
if (start_range.second == end_range.second) { if (start_range.second == end_range.second) {
// Optimization: if all pages are contiguous, use the regular UpdatePageWatchers // if all pages are contiguous, use the regular UpdatePageWatchers
const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS); const VAddr start_addr = base_addr + (start_range.first << PAGE_BITS);
const u64 size = (start_range.second - start_range.first) << PAGE_BITS; const u64 size = (start_range.second - start_range.first) << PAGE_BITS;
return UpdatePageWatchers<track, is_read>(start_addr, size);
UpdatePageWatchers<track>(start_addr, size);
return;
} }
size_t base_page = (base_addr >> PAGE_BITS); size_t base_page = (base_addr >> PAGE_BITS);
auto perms = cached_pages[base_page + start_range.first].Perm(); ASSERT(base_page % PAGES_PER_LOCK == 0);
std::scoped_lock lk(locks[base_page / PAGES_PER_LOCK]);
auto perms = cached_pages[base_page + start_range.first].Perms();
u64 range_begin = 0; u64 range_begin = 0;
u64 range_bytes = 0; u64 range_bytes = 0;
u64 potential_range_bytes = 0;
const auto release_pending = [&] { const auto release_pending = [&] {
if (range_bytes > 0) { if (range_bytes > 0) {
@ -266,26 +308,26 @@ struct PageManager::Impl {
// Perform pending (un)protect action // Perform pending (un)protect action
Protect((range_begin << PAGE_BITS), range_bytes, perms); Protect((range_begin << PAGE_BITS), range_bytes, perms);
range_bytes = 0; range_bytes = 0;
potential_range_bytes = 0;
} }
}; };
std::scoped_lock lk(lock);
// Iterate pages // Iterate pages
for (size_t page = start_range.first; page < end_range.second; ++page) { for (size_t page = start_range.first; page < end_range.second; ++page) {
PageState& state = cached_pages[base_page + page]; PageState& state = cached_pages[base_page + page];
const bool update = mask.Get(page); const bool update = mask.Get(page);
// Apply the change to the page state // Apply the change to the page state
const u8 new_count = update ? state.AddDelta<track ? 1 : -1>() : state.AddDelta<0>(); const u8 new_count =
update ? state.AddDelta<track ? 1 : -1, is_read>() : state.AddDelta<0, is_read>();
if (auto new_perms = state.Perm(); new_perms != perms) [[unlikely]] { if (auto new_perms = state.Perms(); new_perms != perms) [[unlikely]] {
// If the protection changed add pending (un)protect action // If the protection changed add pending (un)protect action
release_pending(); release_pending();
perms = new_perms; perms = new_perms;
} else if (range_bytes != 0) { } else if (range_bytes != 0) {
// If the protection did not change, extend the current range // If the protection did not change, extend the potential range
range_bytes += PAGE_SIZE; potential_range_bytes += PAGE_SIZE;
} }
// If the page is not being updated, skip it // If the page is not being updated, skip it
@ -293,10 +335,15 @@ struct PageManager::Impl {
continue; continue;
} }
// Only start a new range if the page must be (un)protected // If the page must be (un)protected
if (range_bytes == 0 && ((new_count == 0 && !track) || (new_count == 1 && track))) { if ((new_count == 0 && !track) || (new_count == 1 && track)) {
range_begin = base_page + page; if (range_bytes == 0) {
range_bytes = PAGE_SIZE; // Start a new potential range
range_begin = base_page + page;
potential_range_bytes = PAGE_SIZE;
}
// Extend current rango up to potential range
range_bytes = potential_range_bytes;
} }
} }
@ -306,10 +353,11 @@ struct PageManager::Impl {
std::array<PageState, NUM_ADDRESS_PAGES> cached_pages{}; std::array<PageState, NUM_ADDRESS_PAGES> cached_pages{};
#ifdef __linux__ #ifdef __linux__
Common::AdaptiveMutex lock; using LockType = Common::AdaptiveMutex;
#else #else
Common::SpinLock lock; using LockType = Common::SpinLock;
#endif #endif
std::array<LockType, NUM_ADDRESS_LOCKS> locks{};
}; };
PageManager::PageManager(Vulkan::Rasterizer* rasterizer_) PageManager::PageManager(Vulkan::Rasterizer* rasterizer_)
@ -327,19 +375,23 @@ void PageManager::OnGpuUnmap(VAddr address, size_t size) {
template <bool track> template <bool track>
void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const { void PageManager::UpdatePageWatchers(VAddr addr, u64 size) const {
impl->UpdatePageWatchers<track>(addr, size); impl->UpdatePageWatchers<track, false>(addr, size);
} }
template <bool track> template <bool track, bool is_read>
void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const { void PageManager::UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const {
impl->UpdatePageWatchersForRegion<track>(base_addr, mask); impl->UpdatePageWatchersForRegion<track, is_read>(base_addr, mask);
} }
template void PageManager::UpdatePageWatchers<true>(VAddr addr, u64 size) const; template void PageManager::UpdatePageWatchers<true>(VAddr addr, u64 size) const;
template void PageManager::UpdatePageWatchers<false>(VAddr addr, u64 size) const; template void PageManager::UpdatePageWatchers<false>(VAddr addr, u64 size) const;
template void PageManager::UpdatePageWatchersForRegion<true>(VAddr base_addr, template void PageManager::UpdatePageWatchersForRegion<true, true>(VAddr base_addr,
RegionBits& mask) const; RegionBits& mask) const;
template void PageManager::UpdatePageWatchersForRegion<false>(VAddr base_addr, template void PageManager::UpdatePageWatchersForRegion<true, false>(VAddr base_addr,
RegionBits& mask) const; RegionBits& mask) const;
template void PageManager::UpdatePageWatchersForRegion<false, true>(VAddr base_addr,
RegionBits& mask) const;
template void PageManager::UpdatePageWatchersForRegion<false, false>(VAddr base_addr,
RegionBits& mask) const;
} // namespace VideoCore } // namespace VideoCore

View File

@ -15,8 +15,13 @@ class Rasterizer;
namespace VideoCore { namespace VideoCore {
class PageManager { class PageManager {
static constexpr size_t PAGE_BITS = 12; // Use the same page size as the tracker.
static constexpr size_t PAGE_SIZE = 1ULL << PAGE_BITS; static constexpr size_t PAGE_BITS = TRACKER_PAGE_BITS;
static constexpr size_t PAGE_SIZE = TRACKER_BYTES_PER_PAGE;
// Keep the lock granularity the same as region granularity. (since each regions has
// itself a lock)
static constexpr size_t PAGES_PER_LOCK = NUM_PAGES_PER_REGION;
public: public:
explicit PageManager(Vulkan::Rasterizer* rasterizer); explicit PageManager(Vulkan::Rasterizer* rasterizer);
@ -32,9 +37,8 @@ public:
template <bool track> 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 /// Updates watches in the pages touching the specified region using a mask.
/// using a mask. template <bool track, bool is_read = false>
template <bool track>
void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const; void UpdatePageWatchersForRegion(VAddr base_addr, RegionBits& mask) const;
/// Returns page aligned address. /// Returns page aligned address.

View File

@ -341,7 +341,7 @@ bool Instance::CreateDevice() {
const auto topology_list_restart_features = const auto topology_list_restart_features =
feature_chain.get<vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>(); feature_chain.get<vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>();
const auto vk11_features = feature_chain.get<vk::PhysicalDeviceVulkan11Features>(); const auto vk11_features = feature_chain.get<vk::PhysicalDeviceVulkan11Features>();
const auto vk12_features = feature_chain.get<vk::PhysicalDeviceVulkan12Features>(); vk12_features = feature_chain.get<vk::PhysicalDeviceVulkan12Features>();
const auto vk13_features = feature_chain.get<vk::PhysicalDeviceVulkan13Features>(); const auto vk13_features = feature_chain.get<vk::PhysicalDeviceVulkan13Features>();
vk::StructureChain device_chain = { vk::StructureChain device_chain = {
vk::DeviceCreateInfo{ vk::DeviceCreateInfo{
@ -387,6 +387,8 @@ bool Instance::CreateDevice() {
.drawIndirectCount = vk12_features.drawIndirectCount, .drawIndirectCount = vk12_features.drawIndirectCount,
.storageBuffer8BitAccess = vk12_features.storageBuffer8BitAccess, .storageBuffer8BitAccess = vk12_features.storageBuffer8BitAccess,
.uniformAndStorageBuffer8BitAccess = vk12_features.uniformAndStorageBuffer8BitAccess, .uniformAndStorageBuffer8BitAccess = vk12_features.uniformAndStorageBuffer8BitAccess,
.shaderBufferInt64Atomics = vk12_features.shaderBufferInt64Atomics,
.shaderSharedInt64Atomics = vk12_features.shaderSharedInt64Atomics,
.shaderFloat16 = vk12_features.shaderFloat16, .shaderFloat16 = vk12_features.shaderFloat16,
.shaderInt8 = vk12_features.shaderInt8, .shaderInt8 = vk12_features.shaderInt8,
.scalarBlockLayout = vk12_features.scalarBlockLayout, .scalarBlockLayout = vk12_features.scalarBlockLayout,

View File

@ -178,6 +178,16 @@ public:
return shader_atomic_float2 && shader_atomic_float2_features.shaderImageFloat32AtomicMinMax; return shader_atomic_float2 && shader_atomic_float2_features.shaderImageFloat32AtomicMinMax;
} }
/// Returns true if 64-bit integer atomic operations can be used on buffers
bool IsBufferInt64AtomicsSupported() const {
return vk12_features.shaderBufferInt64Atomics;
}
/// Returns true if 64-bit integer atomic operations can be used on shared memory
bool IsSharedInt64AtomicsSupported() const {
return vk12_features.shaderSharedInt64Atomics;
}
/// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported. /// Returns true when VK_KHR_workgroup_memory_explicit_layout is supported.
bool IsWorkgroupMemoryExplicitLayoutSupported() const { bool IsWorkgroupMemoryExplicitLayoutSupported() const {
return workgroup_memory_explicit_layout && return workgroup_memory_explicit_layout &&
@ -358,6 +368,7 @@ private:
vk::PhysicalDeviceVulkan12Properties vk12_props; vk::PhysicalDeviceVulkan12Properties vk12_props;
vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props;
vk::PhysicalDeviceFeatures features; vk::PhysicalDeviceFeatures features;
vk::PhysicalDeviceVulkan12Features vk12_features;
vk::PhysicalDevicePortabilitySubsetFeaturesKHR portability_features; vk::PhysicalDevicePortabilitySubsetFeaturesKHR portability_features;
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features; vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT dynamic_state_3_features;
vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features; vk::PhysicalDeviceRobustness2FeaturesEXT robustness2_features;

View File

@ -219,12 +219,15 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
.supports_buffer_fp32_atomic_min_max = .supports_buffer_fp32_atomic_min_max =
instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(), instance_.IsShaderAtomicFloatBuffer32MinMaxSupported(),
.supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(), .supports_image_fp32_atomic_min_max = instance_.IsShaderAtomicFloatImage32MinMaxSupported(),
.supports_buffer_int64_atomics = instance_.IsBufferInt64AtomicsSupported(),
.supports_shared_int64_atomics = instance_.IsSharedInt64AtomicsSupported(),
.supports_workgroup_explicit_memory_layout = .supports_workgroup_explicit_memory_layout =
instance_.IsWorkgroupMemoryExplicitLayoutSupported(), instance_.IsWorkgroupMemoryExplicitLayoutSupported(),
.needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() && .needs_manual_interpolation = instance.IsFragmentShaderBarycentricSupported() &&
instance.GetDriverID() == vk::DriverId::eNvidiaProprietary, instance.GetDriverID() == vk::DriverId::eNvidiaProprietary,
.needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary || .needs_lds_barriers = instance.GetDriverID() == vk::DriverId::eNvidiaProprietary ||
instance.GetDriverID() == vk::DriverId::eMoltenvk, instance.GetDriverID() == vk::DriverId::eMoltenvk,
.needs_buffer_offsets = instance.StorageMinAlignment() > 4,
// When binding a UBO, we calculate its size considering the offset in the larger buffer // When binding a UBO, we calculate its size considering the offset in the larger buffer
// cache underlying resource. In some cases, it may produce sizes exceeding the system // cache underlying resource. In some cases, it may produce sizes exceeding the system
// maximum allowed UBO range, so we need to reduce the threshold to prevent issues. // maximum allowed UBO range, so we need to reduce the threshold to prevent issues.

View File

@ -36,7 +36,7 @@ static Shader::PushData MakeUserData(const AmdGpu::Liverpool::Regs& regs) {
Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_, Rasterizer::Rasterizer(const Instance& instance_, Scheduler& scheduler_,
AmdGpu::Liverpool* liverpool_) AmdGpu::Liverpool* liverpool_)
: instance{instance_}, scheduler{scheduler_}, page_manager{this}, : instance{instance_}, scheduler{scheduler_}, page_manager{this},
buffer_cache{instance, scheduler, *this, liverpool_, texture_cache, page_manager}, buffer_cache{instance, scheduler, liverpool_, texture_cache, page_manager},
texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_}, texture_cache{instance, scheduler, buffer_cache, page_manager}, liverpool{liverpool_},
memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} { memory{Core::Memory::Instance()}, pipeline_cache{instance, scheduler, liverpool} {
if (!Config::nullGpu()) { if (!Config::nullGpu()) {
@ -468,17 +468,12 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) {
stage->PushUd(binding, push_data); stage->PushUd(binding, push_data);
BindBuffers(*stage, binding, push_data); BindBuffers(*stage, binding, push_data);
BindTextures(*stage, binding); BindTextures(*stage, binding);
uses_dma |= stage->uses_dma;
uses_dma |= stage->dma_types != Shader::IR::Type::Void;
} }
pipeline->BindResources(set_writes, buffer_barriers, push_data);
if (uses_dma && !fault_process_pending) { if (uses_dma && !fault_process_pending) {
// We only use fault buffer for DMA right now. // We only use fault buffer for DMA right now.
{ {
// TODO: GPU might have written to memory (for example with EVENT_WRITE_EOP)
// we need to account for that and synchronize.
Common::RecursiveSharedLock lock{mapped_ranges_mutex}; Common::RecursiveSharedLock lock{mapped_ranges_mutex};
for (auto& range : mapped_ranges) { for (auto& range : mapped_ranges) {
buffer_cache.SynchronizeBuffersInRange(range.lower(), buffer_cache.SynchronizeBuffersInRange(range.lower(),
@ -490,6 +485,8 @@ bool Rasterizer::BindResources(const Pipeline* pipeline) {
fault_process_pending |= uses_dma; fault_process_pending |= uses_dma;
pipeline->BindResources(set_writes, buffer_barriers, push_data);
return true; return true;
} }
@ -514,7 +511,8 @@ bool Rasterizer::IsComputeMetaClear(const Pipeline* pipeline) {
// will need its full emulation anyways. // will need its full emulation anyways.
for (const auto& desc : info.buffers) { for (const auto& desc : info.buffers) {
const VAddr address = desc.GetSharp(info).base_address; const VAddr address = desc.GetSharp(info).base_address;
if (!desc.IsSpecial() && desc.is_written && texture_cache.ClearMeta(address)) { if (!desc.IsSpecial() && desc.is_written && !desc.is_read &&
texture_cache.ClearMeta(address)) {
// Assume all slices were updates // Assume all slices were updates
LOG_TRACE(Render_Vulkan, "Metadata update skipped"); LOG_TRACE(Render_Vulkan, "Metadata update skipped");
return true; return true;
@ -947,6 +945,10 @@ void Rasterizer::InlineData(VAddr address, const void* value, u32 num_bytes, boo
buffer_cache.InlineData(address, value, num_bytes, is_gds); buffer_cache.InlineData(address, value, num_bytes, is_gds);
} }
void Rasterizer::CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds) {
buffer_cache.CopyBuffer(dst, src, num_bytes, dst_gds, src_gds);
}
u32 Rasterizer::ReadDataFromGds(u32 gds_offset) { u32 Rasterizer::ReadDataFromGds(u32 gds_offset) {
auto* gds_buf = buffer_cache.GetGdsBuffer(); auto* gds_buf = buffer_cache.GetGdsBuffer();
u32 value; u32 value;
@ -959,11 +961,20 @@ bool Rasterizer::InvalidateMemory(VAddr addr, u64 size) {
// Not GPU mapped memory, can skip invalidation logic entirely. // Not GPU mapped memory, can skip invalidation logic entirely.
return false; return false;
} }
buffer_cache.InvalidateMemory(addr, size, false); buffer_cache.InvalidateMemory(addr, size);
texture_cache.InvalidateMemory(addr, size); texture_cache.InvalidateMemory(addr, size);
return true; return true;
} }
bool Rasterizer::ReadMemory(VAddr addr, u64 size) {
if (!IsMapped(addr, size)) {
// Not GPU mapped memory, can skip invalidation logic entirely.
return false;
}
buffer_cache.ReadMemory(addr, size);
return true;
}
bool Rasterizer::IsMapped(VAddr addr, u64 size) { bool Rasterizer::IsMapped(VAddr addr, u64 size) {
if (size == 0) { if (size == 0) {
// There is no memory, so not mapped. // There is no memory, so not mapped.
@ -984,7 +995,7 @@ void Rasterizer::MapMemory(VAddr addr, u64 size) {
} }
void Rasterizer::UnmapMemory(VAddr addr, u64 size) { void Rasterizer::UnmapMemory(VAddr addr, u64 size) {
buffer_cache.InvalidateMemory(addr, size, true); buffer_cache.InvalidateMemory(addr, size);
texture_cache.UnmapMemory(addr, size); texture_cache.UnmapMemory(addr, size);
page_manager.OnGpuUnmap(addr, size); page_manager.OnGpuUnmap(addr, size);
{ {

View File

@ -5,6 +5,7 @@
#include <shared_mutex> #include <shared_mutex>
#include "common/recursive_lock.h" #include "common/recursive_lock.h"
#include "common/shared_first_mutex.h"
#include "video_core/buffer_cache/buffer_cache.h" #include "video_core/buffer_cache/buffer_cache.h"
#include "video_core/page_manager.h" #include "video_core/page_manager.h"
#include "video_core/renderer_vulkan/vk_pipeline_cache.h" #include "video_core/renderer_vulkan/vk_pipeline_cache.h"
@ -56,8 +57,10 @@ public:
bool from_guest = false); bool from_guest = false);
void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds); void InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds);
void CopyBuffer(VAddr dst, VAddr src, u32 num_bytes, bool dst_gds, bool src_gds);
u32 ReadDataFromGds(u32 gsd_offset); u32 ReadDataFromGds(u32 gsd_offset);
bool InvalidateMemory(VAddr addr, u64 size); bool InvalidateMemory(VAddr addr, u64 size);
bool ReadMemory(VAddr addr, u64 size);
bool IsMapped(VAddr addr, u64 size); bool IsMapped(VAddr addr, u64 size);
void MapMemory(VAddr addr, u64 size); void MapMemory(VAddr addr, u64 size);
void UnmapMemory(VAddr addr, u64 size); void UnmapMemory(VAddr addr, u64 size);
@ -120,7 +123,7 @@ private:
AmdGpu::Liverpool* liverpool; AmdGpu::Liverpool* liverpool;
Core::MemoryManager* memory; Core::MemoryManager* memory;
boost::icl::interval_set<VAddr> mapped_ranges; boost::icl::interval_set<VAddr> mapped_ranges;
std::shared_mutex mapped_ranges_mutex; Common::SharedFirstMutex mapped_ranges_mutex;
PipelineCache pipeline_cache; PipelineCache pipeline_cache;
boost::container::static_vector< boost::container::static_vector<

View File

@ -27,10 +27,9 @@ enum ImageFlagBits : u32 {
CpuDirty = 1 << 1, ///< Contents have been modified from the CPU CpuDirty = 1 << 1, ///< Contents have been modified from the CPU
GpuDirty = 1 << 2, ///< Contents have been modified from the GPU (valid data in buffer cache) GpuDirty = 1 << 2, ///< Contents have been modified from the GPU (valid data in buffer cache)
Dirty = MaybeCpuDirty | CpuDirty | GpuDirty, Dirty = MaybeCpuDirty | CpuDirty | GpuDirty,
GpuModified = 1 << 3, ///< Contents have been modified from the GPU GpuModified = 1 << 3, ///< Contents have been modified from the GPU
Registered = 1 << 6, ///< True when the image is registered Registered = 1 << 6, ///< True when the image is registered
Picked = 1 << 7, ///< Temporary flag to mark the image as picked Picked = 1 << 7, ///< Temporary flag to mark the image as picked
MetaRegistered = 1 << 8, ///< True when metadata for this surface is known and registered
}; };
DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits) DECLARE_ENUM_FLAG_OPERATORS(ImageFlagBits)

View File

@ -451,20 +451,16 @@ ImageView& TextureCache::FindRenderTarget(BaseDesc& desc) {
UpdateImage(image_id); UpdateImage(image_id);
// Register meta data for this color buffer // Register meta data for this color buffer
if (!(image.flags & ImageFlagBits::MetaRegistered)) { if (desc.info.meta_info.cmask_addr) {
if (desc.info.meta_info.cmask_addr) { surface_metas.emplace(desc.info.meta_info.cmask_addr,
surface_metas.emplace(desc.info.meta_info.cmask_addr, MetaDataInfo{.type = MetaDataInfo::Type::CMask});
MetaDataInfo{.type = MetaDataInfo::Type::CMask}); image.info.meta_info.cmask_addr = desc.info.meta_info.cmask_addr;
image.info.meta_info.cmask_addr = desc.info.meta_info.cmask_addr; }
image.flags |= ImageFlagBits::MetaRegistered;
}
if (desc.info.meta_info.fmask_addr) { if (desc.info.meta_info.fmask_addr) {
surface_metas.emplace(desc.info.meta_info.fmask_addr, surface_metas.emplace(desc.info.meta_info.fmask_addr,
MetaDataInfo{.type = MetaDataInfo::Type::FMask}); MetaDataInfo{.type = MetaDataInfo::Type::FMask});
image.info.meta_info.fmask_addr = desc.info.meta_info.fmask_addr; image.info.meta_info.fmask_addr = desc.info.meta_info.fmask_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}
} }
return RegisterImageView(image_id, desc.view_info); return RegisterImageView(image_id, desc.view_info);
@ -479,15 +475,11 @@ ImageView& TextureCache::FindDepthTarget(BaseDesc& desc) {
UpdateImage(image_id); UpdateImage(image_id);
// Register meta data for this depth buffer // Register meta data for this depth buffer
if (!(image.flags & ImageFlagBits::MetaRegistered)) { if (desc.info.meta_info.htile_addr) {
if (desc.info.meta_info.htile_addr) { surface_metas.emplace(desc.info.meta_info.htile_addr,
surface_metas.emplace( MetaDataInfo{.type = MetaDataInfo::Type::HTile,
desc.info.meta_info.htile_addr, .clear_mask = image.info.meta_info.htile_clear_mask});
MetaDataInfo{.type = MetaDataInfo::Type::HTile, image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr;
.clear_mask = image.info.meta_info.htile_clear_mask});
image.info.meta_info.htile_addr = desc.info.meta_info.htile_addr;
image.flags |= ImageFlagBits::MetaRegistered;
}
} }
// If there is a stencil attachment, link depth and stencil. // If there is a stencil attachment, link depth and stencil.