Merge branch 'main' into fontlib

This commit is contained in:
georgemoralis 2025-06-16 13:22:00 +03:00 committed by GitHub
commit 479bca9c59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
154 changed files with 7597 additions and 2130 deletions

View file

@ -76,18 +76,13 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@ -111,7 +106,7 @@ jobs:
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
version: 6.9.0
version: 6.9.1
host: windows
target: desktop
arch: win64_msvc2022_64
@ -130,18 +125,13 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }}
- name: Setup VS Environment
uses: ilammy/msvc-dev-cmd@v1.13.0
with:
arch: amd64
- name: Configure CMake
run: cmake --fresh -G Ninja -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DENABLE_QT_GUI=ON -DENABLE_UPDATER=ON -DCMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE=ON -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache
@ -186,7 +176,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{runner.os}}-sdl-cache-cmake-build
with:
@ -228,7 +218,7 @@ jobs:
- name: Setup Qt
uses: jurplel/install-qt-action@v4
with:
version: 6.9.0
version: 6.9.1
host: mac
target: desktop
arch: clang_64
@ -247,7 +237,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{runner.os}}-qt-cache-cmake-build
with:
@ -301,7 +291,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with:
@ -362,7 +352,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build
with:
@ -409,7 +399,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
with:
@ -445,7 +435,7 @@ jobs:
${{ env.cache-name }}-
- name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17
uses: hendrikmuhs/ccache-action@v1.2.18
env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
with:
@ -494,7 +484,7 @@ jobs:
with:
token: ${{ secrets.SHADPS4_TOKEN_REPO }}
name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}"
tag: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}"
draft: false
prerelease: true
body: "Full Changelog: [${{ env.last_release_tag }}...${{ needs.get-info.outputs.shorthash }}](https://github.com/shadps4-emu/shadPS4/compare/${{ env.last_release_tag }}...${{ needs.get-info.outputs.fullhash }})"
@ -530,14 +520,14 @@ jobs:
# Check if release already exists and get ID
release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \
"https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" | jq -r '.id')
"https://api.github.com/repos/$REPO/releases/tags/Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}" | jq -r '.id')
if [[ "$release_id" == "null" ]]; then
echo "Creating release in $REPO for $filename"
release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \
-d '{
"tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
"tag_name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.fullhash }}",
"name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
"draft": false,
"prerelease": true,

View file

@ -876,6 +876,7 @@ set(SHADER_RECOMPILER src/shader_recompiler/exception.h
src/shader_recompiler/ir/passes/ring_access_elimination.cpp
src/shader_recompiler/ir/passes/shader_info_collection_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp
src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
src/shader_recompiler/ir/abstract_syntax_list.cpp
@ -970,6 +971,7 @@ set(VIDEO_CORE src/video_core/amdgpu/liverpool.cpp
src/video_core/texture_cache/tile_manager.cpp
src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.h
src/video_core/texture_cache/host_compatibility.cpp
src/video_core/texture_cache/host_compatibility.h
src/video_core/page_manager.cpp
src/video_core/page_manager.h
@ -1059,6 +1061,10 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/settings_dialog.h
src/qt_gui/settings_dialog.ui
src/qt_gui/main.cpp
src/qt_gui/gui_settings.cpp
src/qt_gui/gui_settings.h
src/qt_gui/settings.cpp
src/qt_gui/settings.h
${EMULATOR}
${RESOURCE_FILES}
${TRANSLATIONS}
@ -1124,6 +1130,10 @@ if (APPLE)
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH})
add_custom_command(
OUTPUT ${MVK_DST}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
else()
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
@ -1134,9 +1144,6 @@ if (APPLE)
set(MVK_ICD_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json)
set(MVK_ICD_DST ${MVK_DST}/MoltenVK_icd.json)
add_custom_command(
OUTPUT ${MVK_DST}
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
add_custom_command(
OUTPUT ${MVK_ICD_DST}
DEPENDS ${MVK_ICD_SRC} ${MVK_DST}
@ -1151,17 +1158,13 @@ if (APPLE)
if (ARCHITECTURE STREQUAL "x86_64")
# Reserve system-managed memory space.
target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
target_link_options(shadps4 PRIVATE -Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
endif()
# Replacement for std::chrono::time_zone
target_link_libraries(shadps4 PRIVATE date::date-tz)
endif()
if (NOT ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE SDL3::SDL3)
endif()
if (ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
add_definitions(-DENABLE_QT_GUI)

View file

@ -36,7 +36,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
**shadPS4** is an early **PlayStation 4** emulator for **Windows**, **Linux** and **macOS** written in C++.
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\
@ -124,8 +124,8 @@ Keyboard and mouse inputs can be customized in the settings menu by clicking the
# Firmware files
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\
The following firmware modules are supported and must be placed in shadPS4's `user/sys_modules` folder.
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.
The following firmware modules are supported and must be placed in shadPS4's `sys_modules` folder.
<div align="center">
@ -138,8 +138,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
</div>
> [!Caution]
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\
> **We do not provide any information or support on how to do this**.
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.
@ -148,7 +147,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
- [**georgemoralis**](https://github.com/georgemoralis)
- [**psucien**](https://github.com/psucien)
- [**viniciuslrangel**](https://github.com/viniciuslrangel)
- [**roamic**](https://github.com/vladmikhalin)
- [**roamic**](https://github.com/roamic)
- [**squidbus**](https://github.com/squidbus)
- [**frodo**](https://github.com/baggins183)
- [**Stephen Miller**](https://github.com/StevenMiller123)
@ -158,7 +157,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
# Contributing
If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
Open a PR and we'll check it :)
# Translations

View file

@ -25,7 +25,7 @@ sudo apt install build-essential clang git cmake libasound2-dev \
```bash
sudo dnf install clang git cmake libatomic alsa-lib-devel \
pipewire-jack-audio-connection-kit-devel openal-devel \
pipewire-jack-audio-connection-kit-devel openal-soft-devel \
openssl-devel libevdev-devel libudev-devel libXext-devel \
qt6-qtbase-devel qt6-qtbase-private-devel \
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \

@ -1 +1 @@
Subproject commit 3a0b07a24a4a681ffe70b461b1f4333b2729e2ef
Subproject commit 00abd384ce01cbd439045905d2fa6cf799dfa2f6

@ -1 +1 @@
Subproject commit 969e75f7cc0718774231d029f9d52fa87d4ae1b2
Subproject commit 1a69a919fa302e92b337594bd0a8aaea61037d91

View file

@ -33,9 +33,7 @@ namespace Config {
static bool isNeo = false;
static bool isDevKit = false;
static bool playBGM = false;
static bool isTrophyPopupDisabled = false;
static int BGMvolume = 50;
static bool enableDiscordRPC = false;
static u32 screenWidth = 1280;
static u32 screenHeight = 720;
@ -43,7 +41,6 @@ static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto
static std::string logFilter;
static std::string logType = "sync";
static std::string userName = "shadPS4";
static std::string updateChannel;
static std::string chooseHomeTab;
static std::string backButtonBehavior = "left";
static bool useSpecialPad = false;
@ -52,8 +49,6 @@ static bool isMotionControlsEnabled = true;
static bool isDebugDump = false;
static bool isShaderDebug = false;
static bool isShowSplash = false;
static bool isAutoUpdate = false;
static bool isAlwaysShowChangelog = false;
static std::string isSideTrophy = "right";
static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false;
@ -86,27 +81,13 @@ static std::vector<GameInstallDir> settings_install_dirs = {};
std::vector<bool> install_dirs_enabled = {};
std::filesystem::path settings_addon_install_dir = {};
std::filesystem::path save_data_path = {};
u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400;
u32 main_window_geometry_w = 1280;
u32 main_window_geometry_h = 720;
u32 mw_themes = 0;
u32 m_icon_size = 36;
u32 m_icon_size_grid = 69;
u32 m_slider_pos = 0;
u32 m_slider_pos_grid = 0;
u32 m_table_mode = 0;
u32 m_window_size_W = 1280;
u32 m_window_size_H = 720;
std::vector<std::string> m_elf_viewer;
std::vector<std::string> m_recent_files;
std::string emulator_language = "en_US";
static int backgroundImageOpacity = 50;
static bool showBackgroundImage = true;
static bool isFullscreen = false;
static std::string fullscreenMode = "Windowed";
static bool isHDRAllowed = false;
static bool showLabelsUnderIcons = true;
// Language
u32 m_language = 1; // english
@ -176,14 +157,6 @@ bool getIsFullscreen() {
return isFullscreen;
}
bool getShowLabelsUnderIcons() {
return showLabelsUnderIcons;
}
bool setShowLabelsUnderIcons() {
return false;
}
std::string getFullscreenMode() {
return fullscreenMode;
}
@ -192,14 +165,6 @@ bool getisTrophyPopupDisabled() {
return isTrophyPopupDisabled;
}
bool getPlayBGM() {
return playBGM;
}
int getBGMvolume() {
return BGMvolume;
}
bool getEnableDiscordRPC() {
return enableDiscordRPC;
}
@ -240,10 +205,6 @@ std::string getUserName() {
return userName;
}
std::string getUpdateChannel() {
return updateChannel;
}
std::string getChooseHomeTab() {
return chooseHomeTab;
}
@ -276,14 +237,6 @@ bool showSplash() {
return isShowSplash;
}
bool autoUpdate() {
return isAutoUpdate;
}
bool alwaysShowChangelog() {
return isAlwaysShowChangelog;
}
std::string sideTrophy() {
return isSideTrophy;
}
@ -384,14 +337,6 @@ void setShowSplash(bool enable) {
isShowSplash = enable;
}
void setAutoUpdate(bool enable) {
isAutoUpdate = enable;
}
void setAlwaysShowChangelog(bool enable) {
isAlwaysShowChangelog = enable;
}
void setSideTrophy(std::string side) {
isSideTrophy = side;
}
@ -431,9 +376,6 @@ void setVblankDiv(u32 value) {
void setIsFullscreen(bool enable) {
isFullscreen = enable;
}
static void setShowLabelsUnderIcons(bool enable) {
showLabelsUnderIcons = enable;
}
void setFullscreenMode(std::string mode) {
fullscreenMode = mode;
@ -443,14 +385,6 @@ void setisTrophyPopupDisabled(bool disable) {
isTrophyPopupDisabled = disable;
}
void setPlayBGM(bool enable) {
playBGM = enable;
}
void setBGMvolume(int volume) {
BGMvolume = volume;
}
void setEnableDiscordRPC(bool enable) {
enableDiscordRPC = enable;
}
@ -490,9 +424,6 @@ void setUserName(const std::string& type) {
userName = type;
}
void setUpdateChannel(const std::string& type) {
updateChannel = type;
}
void setChooseHomeTab(const std::string& type) {
chooseHomeTab = type;
}
@ -521,13 +452,6 @@ void setCheckCompatibilityOnStartup(bool use) {
checkCompatibilityOnStartup = use;
}
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
main_window_geometry_x = x;
main_window_geometry_y = y;
main_window_geometry_w = w;
main_window_geometry_h = h;
}
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& install_dir : settings_install_dirs) {
if (install_dir.path == dir) {
@ -564,34 +488,6 @@ void setMainWindowTheme(u32 theme) {
mw_themes = theme;
}
void setIconSize(u32 size) {
m_icon_size = size;
}
void setIconSizeGrid(u32 size) {
m_icon_size_grid = size;
}
void setSliderPosition(u32 pos) {
m_slider_pos = pos;
}
void setSliderPositionGrid(u32 pos) {
m_slider_pos_grid = pos;
}
void setTableMode(u32 mode) {
m_table_mode = mode;
}
void setMainWindowWidth(u32 width) {
m_window_size_W = width;
}
void setMainWindowHeight(u32 height) {
m_window_size_H = height;
}
void setElfViewer(const std::vector<std::string>& elfList) {
m_elf_viewer.resize(elfList.size());
m_elf_viewer = elfList;
@ -621,22 +517,6 @@ void setSaveDataPath(const std::filesystem::path& path) {
save_data_path = path;
}
u32 getMainWindowGeometryX() {
return main_window_geometry_x;
}
u32 getMainWindowGeometryY() {
return main_window_geometry_y;
}
u32 getMainWindowGeometryW() {
return main_window_geometry_w;
}
u32 getMainWindowGeometryH() {
return main_window_geometry_h;
}
const std::vector<std::filesystem::path> getGameInstallDirs() {
std::vector<std::filesystem::path> enabled_dirs;
for (const auto& dir : settings_install_dirs) {
@ -667,34 +547,6 @@ u32 getMainWindowTheme() {
return mw_themes;
}
u32 getIconSize() {
return m_icon_size;
}
u32 getIconSizeGrid() {
return m_icon_size_grid;
}
u32 getSliderPosition() {
return m_slider_pos;
}
u32 getSliderPositionGrid() {
return m_slider_pos_grid;
}
u32 getTableMode() {
return m_table_mode;
}
u32 getMainWindowWidth() {
return m_window_size_W;
}
u32 getMainWindowHeight() {
return m_window_size_H;
}
std::vector<std::string> getElfViewer() {
return m_elf_viewer;
}
@ -715,22 +567,6 @@ bool getSeparateLogFilesEnabled() {
return isSeparateLogFilesEnabled;
}
int getBackgroundImageOpacity() {
return backgroundImageOpacity;
}
void setBackgroundImageOpacity(int opacity) {
backgroundImageOpacity = std::clamp(opacity, 0, 100);
}
bool getShowBackgroundImage() {
return showBackgroundImage;
}
void setShowBackgroundImage(bool show) {
showBackgroundImage = show;
}
bool getPSNSignedIn() {
return isPSNSignedIn;
}
@ -764,23 +600,14 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
isDevKit = toml::find_or<bool>(general, "isDevKit", false);
isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", false);
playBGM = toml::find_or<bool>(general, "playBGM", false);
isTrophyPopupDisabled = toml::find_or<bool>(general, "isTrophyPopupDisabled", false);
trophyNotificationDuration =
toml::find_or<double>(general, "trophyNotificationDuration", 5.0);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
logFilter = toml::find_or<std::string>(general, "logFilter", "");
logType = toml::find_or<std::string>(general, "logType", "sync");
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
if (Common::g_is_release) {
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release");
} else {
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Nightly");
}
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
isAlwaysShowChangelog = toml::find_or<bool>(general, "alwaysShowChangelog", false);
isSideTrophy = toml::find_or<std::string>(general, "sideTrophy", "right");
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
checkCompatibilityOnStartup =
@ -841,13 +668,7 @@ void load(const std::filesystem::path& path) {
const toml::value& gui = data.at("GUI");
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true);
m_icon_size = toml::find_or<int>(gui, "iconSize", 0);
m_icon_size_grid = toml::find_or<int>(gui, "iconSizeGrid", 0);
m_slider_pos = toml::find_or<int>(gui, "sliderPos", 0);
m_slider_pos_grid = toml::find_or<int>(gui, "sliderPosGrid", 0);
mw_themes = toml::find_or<int>(gui, "theme", 0);
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
const auto install_dir_array =
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
@ -872,16 +693,9 @@ void load(const std::filesystem::path& path) {
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {});
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
main_window_geometry_h = toml::find_or<int>(gui, "geometry_h", 0);
m_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {});
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
m_table_mode = toml::find_or<int>(gui, "gameTableMode", 0);
emulator_language = toml::find_or<std::string>(gui, "emulatorLanguage", "en_US");
backgroundImageOpacity = toml::find_or<int>(gui, "backgroundImageOpacity", 50);
showBackgroundImage = toml::find_or<bool>(gui, "showBackgroundImage", true);
}
if (data.contains("Settings")) {
@ -897,9 +711,10 @@ void load(const std::filesystem::path& path) {
// Check if the loaded language is in the allowed list
const std::vector<std::string> allowed_languages = {
"ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", "fr_FR", "hu_HU",
"id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", "nl_NL", "pl_PL", "pt_BR", "pt_PT",
"ro_RO", "ru_RU", "sq_AL", "sv_SE", "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW"};
"ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI",
"fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO",
"nl_NL", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sq_AL", "sv_SE",
"tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW", "ca_ES", "sr_CS"};
if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) ==
allowed_languages.end()) {
@ -966,17 +781,12 @@ void save(const std::filesystem::path& path) {
data["General"]["isPSNSignedIn"] = isPSNSignedIn;
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
data["General"]["trophyNotificationDuration"] = trophyNotificationDuration;
data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume;
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType;
data["General"]["userName"] = userName;
data["General"]["updateChannel"] = updateChannel;
data["General"]["chooseHomeTab"] = chooseHomeTab;
data["General"]["showSplash"] = isShowSplash;
data["General"]["autoUpdate"] = isAutoUpdate;
data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog;
data["General"]["sideTrophy"] = isSideTrophy;
data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
@ -1046,8 +856,6 @@ void save(const std::filesystem::path& path) {
data["GUI"]["addonInstallDir"] =
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["emulatorLanguage"] = emulator_language;
data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity;
data["GUI"]["showBackgroundImage"] = showBackgroundImage;
data["Settings"]["consoleLanguage"] = m_language;
// Sorting of TOML sections
@ -1082,18 +890,7 @@ void saveMainWindow(const std::filesystem::path& path) {
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
}
data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["theme"] = mw_themes;
data["GUI"]["iconSize"] = m_icon_size;
data["GUI"]["sliderPos"] = m_slider_pos;
data["GUI"]["iconSizeGrid"] = m_icon_size_grid;
data["GUI"]["sliderPosGrid"] = m_slider_pos_grid;
data["GUI"]["gameTableMode"] = m_table_mode;
data["GUI"]["geometry_x"] = main_window_geometry_x;
data["GUI"]["geometry_y"] = main_window_geometry_y;
data["GUI"]["geometry_w"] = main_window_geometry_w;
data["GUI"]["geometry_h"] = main_window_geometry_h;
data["GUI"]["elfDirs"] = m_elf_viewer;
data["GUI"]["recentFiles"] = m_recent_files;
@ -1112,19 +909,13 @@ void setDefaultValues() {
isPSNSignedIn = false;
isFullscreen = false;
isTrophyPopupDisabled = false;
playBGM = false;
BGMvolume = 50;
enableDiscordRPC = true;
screenWidth = 1280;
screenHeight = 720;
logFilter = "";
logType = "sync";
userName = "shadPS4";
if (Common::g_is_release) {
updateChannel = "Release";
} else {
updateChannel = "Nightly";
}
chooseHomeTab = "General";
cursorState = HideCursorState::Idle;
cursorHideTimeout = 5;
@ -1135,8 +926,6 @@ void setDefaultValues() {
isDebugDump = false;
isShaderDebug = false;
isShowSplash = false;
isAutoUpdate = false;
isAlwaysShowChangelog = false;
isSideTrophy = "right";
isNullGpu = false;
shouldDumpShaders = false;
@ -1153,8 +942,6 @@ void setDefaultValues() {
gpuId = -1;
compatibilityData = false;
checkCompatibilityOnStartup = false;
backgroundImageOpacity = 50;
showBackgroundImage = true;
}
constexpr std::string_view GetDefaultKeyboardConfig() {

View file

@ -26,25 +26,18 @@ bool GetLoadGameSizeEnabled();
std::filesystem::path GetSaveDataPath();
void setLoadGameSizeEnabled(bool enable);
bool getIsFullscreen();
bool getShowLabelsUnderIcons();
bool setShowLabelsUnderIcons();
std::string getFullscreenMode();
bool isNeoModeConsole();
bool isDevKitConsole();
bool getPlayBGM();
int getBGMvolume();
bool getisTrophyPopupDisabled();
bool getEnableDiscordRPC();
bool getCompatibilityEnabled();
bool getCheckCompatibilityOnStartup();
int getBackgroundImageOpacity();
bool getShowBackgroundImage();
bool getPSNSignedIn();
std::string getLogFilter();
std::string getLogType();
std::string getUserName();
std::string getUpdateChannel();
std::string getChooseHomeTab();
s16 getCursorState();
@ -69,8 +62,6 @@ bool allowHDR();
bool debugDump();
bool collectShadersForDebug();
bool showSplash();
bool autoUpdate();
bool alwaysShowChangelog();
std::string sideTrophy();
bool nullGpu();
bool copyGPUCmdBuffers();
@ -83,8 +74,6 @@ u32 vblankDiv();
void setDebugDump(bool enable);
void setCollectShaderForDebug(bool enable);
void setShowSplash(bool enable);
void setAutoUpdate(bool enable);
void setAlwaysShowChangelog(bool enable);
void setSideTrophy(std::string side);
void setNullGpu(bool enable);
void setAllowHDR(bool enable);
@ -97,21 +86,16 @@ void setScreenHeight(u32 height);
void setIsFullscreen(bool enable);
void setFullscreenMode(std::string mode);
void setisTrophyPopupDisabled(bool disable);
void setPlayBGM(bool enable);
void setBGMvolume(int volume);
void setEnableDiscordRPC(bool enable);
void setLanguage(u32 language);
void setNeoMode(bool enable);
void setUserName(const std::string& type);
void setUpdateChannel(const std::string& type);
void setChooseHomeTab(const std::string& type);
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
void setSaveDataPath(const std::filesystem::path& path);
void setCompatibilityEnabled(bool use);
void setCheckCompatibilityOnStartup(bool use);
void setBackgroundImageOpacity(int opacity);
void setShowBackgroundImage(bool show);
void setPSNSignedIn(bool sign);
void setCursorState(s16 cursorState);
@ -141,38 +125,19 @@ void setVkHostMarkersEnabled(bool enable);
void setVkGuestMarkersEnabled(bool enable);
// Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
void removeGameInstallDir(const std::filesystem::path& dir);
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme);
void setIconSize(u32 size);
void setIconSizeGrid(u32 size);
void setSliderPosition(u32 pos);
void setSliderPositionGrid(u32 pos);
void setTableMode(u32 mode);
void setMainWindowWidth(u32 width);
void setMainWindowHeight(u32 height);
void setElfViewer(const std::vector<std::string>& elfList);
void setRecentFiles(const std::vector<std::string>& recentFiles);
void setEmulatorLanguage(std::string language);
u32 getMainWindowGeometryX();
u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH();
const std::vector<std::filesystem::path> getGameInstallDirs();
const std::vector<bool> getGameInstallDirsEnabled();
std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme();
u32 getIconSize();
u32 getIconSizeGrid();
u32 getSliderPosition();
u32 getSliderPositionGrid();
u32 getTableMode();
u32 getMainWindowWidth();
u32 getMainWindowHeight();
std::vector<std::string> getElfViewer();
std::vector<std::string> getRecentFiles();
std::string getEmulatorLanguage();

View file

@ -186,7 +186,9 @@ public:
template <typename T>
size_t WriteRaw(const void* data, size_t size) const {
return std::fwrite(data, sizeof(T), size, file);
auto bytes = std::fwrite(data, sizeof(T), size, file);
std::fflush(file);
return bytes;
}
template <typename T>

View file

@ -88,7 +88,8 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) {
dst_op.reg.value <= ZYDIS_REGISTER_R15;
}
static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
#if defined(_WIN32)
@ -126,7 +127,8 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
return !cpu.has(Cpu::tSSE4a);
}
static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
@ -245,7 +247,8 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
}
}
static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* operands,
Xbyak::CodeGenerator& c) {
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
@ -383,8 +386,44 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
}
}
static void ReplaceMOVNT(void* address, u8 rep_prefix) {
// Find the opcode byte
// There can be any amount of prefixes but the instruction can't be more than 15 bytes
// And we know for sure this is a MOVNTSS/MOVNTSD
bool found = false;
bool rep_prefix_found = false;
int index = 0;
u8* ptr = reinterpret_cast<u8*>(address);
for (int i = 0; i < 15; i++) {
if (ptr[i] == rep_prefix) {
rep_prefix_found = true;
} else if (ptr[i] == 0x2B) {
index = i;
found = true;
break;
}
}
// Some sanity checks
ASSERT(found);
ASSERT(index >= 2);
ASSERT(ptr[index - 1] == 0x0F);
ASSERT(rep_prefix_found);
// This turns the MOVNTSS/MOVNTSD to a MOVSS/MOVSD m, xmm
ptr[index] = 0x11;
}
static void ReplaceMOVNTSS(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
ReplaceMOVNT(address, 0xF3);
}
static void ReplaceMOVNTSD(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
ReplaceMOVNT(address, 0xF2);
}
using PatchFilter = bool (*)(const ZydisDecodedOperand*);
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
using InstructionGenerator = void (*)(void*, const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
struct PatchInfo {
/// Filter for more granular patch conditions past just the instruction mnemonic.
PatchFilter filter;
@ -400,6 +439,8 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
// SSE4a
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
#if defined(_WIN32)
// Windows needs a trampoline.
@ -477,7 +518,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
auto& trampoline_gen = module->trampoline_gen;
const auto trampoline_ptr = trampoline_gen.getCurr();
patch_info.generator(operands, trampoline_gen);
patch_info.generator(code, operands, trampoline_gen);
// Return to the following instruction at the end of the trampoline.
trampoline_gen.jmp(code + instruction.length);
@ -485,7 +526,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
// Replace instruction with near jump to the trampoline.
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
} else {
patch_info.generator(operands, patch_gen);
patch_info.generator(code, operands, patch_gen);
}
const auto patch_size = patch_gen.getCurr() - code;

View file

@ -10,6 +10,8 @@
namespace Core::FileSys {
bool MntPoints::ignore_game_patches = false;
std::string RemoveTrailingSlashes(const std::string& path) {
// Remove trailing slashes to make comparisons simpler.
std::string path_sanitized = path;
@ -77,7 +79,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
patch_path /= rel_path;
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
!force_base_path && std::filesystem::exists(patch_path)) {
!force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) {
return patch_path;
}
@ -137,7 +139,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
return std::optional<std::filesystem::path>(current_path);
};
if (!force_base_path) {
if (!force_base_path && !ignore_game_patches) {
if (const auto path = search(patch_path)) {
return *path;
}

View file

@ -21,6 +21,7 @@ class MntPoints {
static constexpr bool NeedsCaseInsensitiveSearch = true;
#endif
public:
static bool ignore_game_patches;
struct MntPair {
std::filesystem::path host_path;
std::string mount; // e.g /app0

View file

@ -125,7 +125,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) {
.count();
count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited)));
}
small_timer_event.event.data = 0;
}
if (ev->flags & SceKernelEvent::Flags::OneShot) {
@ -179,39 +178,46 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) {
}
bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) {
// We assume that only one timer event (with the same ident across calls)
// can be posted to the queue, based on observations so far. In the opposite case,
// the small timer storage and wait logic should be reworked.
ASSERT(!HasSmallTimer() || small_timer_event.event.ident == ev.event.ident);
ev.time_added = std::chrono::steady_clock::now();
small_timer_event = std::move(ev);
SmallTimer st;
st.event = ev.event;
st.added = std::chrono::steady_clock::now();
st.interval = std::chrono::microseconds{ev.event.data};
{
std::scoped_lock lock{m_mutex};
m_small_timers[st.event.ident] = std::move(st);
}
return true;
}
int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) {
int count{};
ASSERT(num == 1);
ASSERT(num >= 1);
auto curr_clock = std::chrono::steady_clock::now();
const auto wait_end_us = (micros == 0) ? std::chrono::steady_clock::time_point::max()
: curr_clock + std::chrono::microseconds{micros};
int count = 0;
do {
curr_clock = std::chrono::steady_clock::now();
{
std::scoped_lock lock{m_mutex};
if ((curr_clock - small_timer_event.time_added) >
std::chrono::microseconds{small_timer_event.event.data}) {
ev[count++] = small_timer_event.event;
small_timer_event.event.data = 0;
break;
for (auto it = m_small_timers.begin(); it != m_small_timers.end() && count < num;) {
const SmallTimer& st = it->second;
if (curr_clock - st.added >= st.interval) {
ev[count++] = st.event;
it = m_small_timers.erase(it);
} else {
++it;
}
}
if (count > 0)
return count;
}
std::this_thread::yield();
} while (curr_clock < wait_end_us);
return count;
return 0;
}
bool EqueueInternal::EventExists(u64 id, s16 filter) {
@ -326,6 +332,11 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec*
// `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is
// large. Even for large delays, we truncate a small portion to complete the wait
// using the spinlock, prioritizing precision.
if (eq->EventExists(event.event.ident, event.event.filter)) {
eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer);
}
if (total_us < HrTimerSpinlockThresholdUs) {
return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM;
}

View file

@ -9,6 +9,7 @@
#include <vector>
#include <boost/asio/steady_timer.hpp>
#include <unordered_map>
#include "common/rdtsc.h"
#include "common/types.h"
@ -135,6 +136,12 @@ private:
};
class EqueueInternal {
struct SmallTimer {
SceKernelEvent event;
std::chrono::steady_clock::time_point added;
std::chrono::microseconds interval;
};
public:
explicit EqueueInternal(std::string_view name) : m_name(name) {}
@ -151,13 +158,14 @@ public:
int GetTriggeredEvents(SceKernelEvent* ev, int num);
bool AddSmallTimer(EqueueEvent& event);
bool HasSmallTimer() const {
return small_timer_event.event.data != 0;
bool HasSmallTimer() {
std::scoped_lock lock{m_mutex};
return !m_small_timers.empty();
}
bool RemoveSmallTimer(u64 id) {
if (HasSmallTimer() && small_timer_event.event.ident == id) {
small_timer_event = {};
return true;
if (HasSmallTimer()) {
std::scoped_lock lock{m_mutex};
return m_small_timers.erase(id) > 0;
}
return false;
}
@ -170,8 +178,8 @@ private:
std::string m_name;
std::mutex m_mutex;
std::vector<EqueueEvent> m_events;
EqueueEvent small_timer_event{};
std::condition_variable m_cond;
std::unordered_map<u64, SmallTimer> m_small_timers;
};
u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev);

View file

@ -293,6 +293,7 @@ s64 PS4_SYSV_ABI write(s32 fd, const void* buf, size_t nbytes) {
}
return result;
}
return file->f.WriteRaw<u8>(buf, nbytes);
}
@ -750,7 +751,24 @@ s32 PS4_SYSV_ABI posix_rename(const char* from, const char* to) {
*__Error() = POSIX_ENOTEMPTY;
return -1;
}
// On Windows, std::filesystem::rename will error if the file has been opened before.
std::filesystem::copy(src_path, dst_path, std::filesystem::copy_options::overwrite_existing);
auto* h = Common::Singleton<Core::FileSys::HandleTable>::Instance();
auto file = h->GetFile(src_path);
if (file) {
// We need to force ReadWrite if the file had Write access before
// Otherwise f.Open will clear the file contents.
auto access_mode = file->f.GetAccessMode() == Common::FS::FileAccessMode::Write
? Common::FS::FileAccessMode::ReadWrite
: file->f.GetAccessMode();
file->f.Close();
std::filesystem::remove(src_path);
file->f.Open(dst_path, access_mode);
} else {
std::filesystem::remove(src_path);
}
return ORBIS_OK;
}
@ -1050,6 +1068,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite);
LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv);
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev);
LIB_FUNCTION("kAt6VDbHmro", "libkernel", 1, "libkernel", 1, 1, sceKernelWritev);
LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek);

View file

@ -273,6 +273,10 @@ void RegisterKernel(Core::Loader::SymbolsResolver* sym) {
Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ...
LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1,
Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ...
LIB_FUNCTION("XVL8So3QJUk", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_connect);
LIB_FUNCTION("3e+4Iv7IJ8U", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_accept);
LIB_FUNCTION("aNeavPDNKzA", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_sendmsg);
LIB_FUNCTION("pxnCmagrtao", "libScePosix", 1, "libkernel", 1, 1, Libraries::Net::sys_listen);
}
} // namespace Libraries::Kernel

View file

@ -99,7 +99,7 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) {
s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd,
size_t alignment, u64* physAddrOut,
size_t* sizeOut) {
LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
searchStart, searchEnd, alignment);
if (physAddrOut == nullptr || sizeOut == nullptr) {
@ -222,9 +222,10 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
return ret;
}
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
int flags, const char* name) {
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
const char* name) {
LOG_INFO(Kernel_Vmm, "in_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}, name = '{}'",
fmt::ptr(*addr_in_out), len, prot, flags, name);
if (len == 0 || !Common::Is16KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple");
return ORBIS_KERNEL_ERROR_EINVAL;
@ -243,18 +244,14 @@ s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t
const VAddr in_addr = reinterpret_cast<VAddr>(*addr_in_out);
const auto mem_prot = static_cast<Core::MemoryProt>(prot);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags);
SCOPE_EXIT {
LOG_INFO(Kernel_Vmm,
"in_addr = {:#x}, out_addr = {}, len = {:#x}, prot = {:#x}, flags = {:#x}",
in_addr, fmt::ptr(*addr_in_out), len, prot, flags);
};
auto* memory = Core::Memory::Instance();
return memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags,
const auto ret = memory->MapMemory(addr_in_out, in_addr, len, mem_prot, map_flags,
Core::VMAType::Flexible, name);
LOG_INFO(Kernel_Vmm, "out_addr = {}", fmt::ptr(*addr_in_out));
return ret;
}
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
int flags) {
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags) {
return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon");
}
@ -290,7 +287,7 @@ s32 PS4_SYSV_ABI sceKernelMtypeprotect(const void* addr, u64 size, s32 mtype, s3
int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
size_t infoSize) {
LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
auto* memory = Core::Memory::Instance();
return memory->DirectMemoryQuery(offset, flags == 1, query_info);
}
@ -663,6 +660,9 @@ int PS4_SYSV_ABI sceKernelSetPrtAperture(int id, VAddr address, size_t size) {
"PRT aperture id = {}, address = {:#x}, size = {:#x} is set but not used", id,
address, size);
auto* memory = Core::Memory::Instance();
memory->SetPrtArea(id, address, size);
PrtApertures[id] = {address, size};
return ORBIS_OK;
}

View file

@ -141,10 +141,9 @@ s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchE
s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
size_t infoSize);
s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment);
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addrInOut, std::size_t len, int prot,
int flags, const char* name);
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot,
int flags);
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
const char* name);
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags);
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot);
s32 PS4_SYSV_ABI sceKernelMprotect(const void* addr, u64 size, s32 prot);

View file

@ -426,6 +426,7 @@ void RegisterMutex(Core::Loader::SymbolsResolver* sym) {
// Posix
LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init);
LIB_FUNCTION("7H0iTOciTLo", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_lock);
LIB_FUNCTION("Io9+nTKXZtA", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_timedlock);
LIB_FUNCTION("2Z+PpY6CaJg", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_unlock);
LIB_FUNCTION("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init);

View file

@ -164,10 +164,12 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateContext(OrbisNpTrophyContext* context, int32_t
}
const auto ctx_id = trophy_contexts.insert(user_id, service_label);
contexts_internal[key].context_id = ctx_id.index;
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", ctx_id.index,
user_id, service_label);
*context = ctx_id.index;
*context = ctx_id.index + 1;
contexts_internal[key].context_id = *context;
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
service_label);
return ORBIS_OK;
}
@ -179,21 +181,23 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
if (trophy_handles.size() >= MaxTrophyHandles) {
return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX;
}
const auto handle_id = trophy_handles.insert();
LOG_INFO(Lib_NpTrophy, "New handle = {}", handle_id.index);
*handle = handle_id.index;
const auto handle_id = trophy_handles.insert();
*handle = handle_id.index + 1;
LOG_INFO(Lib_NpTrophy, "New handle = {}", *handle);
return ORBIS_OK;
}
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context);
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) {
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
Common::SlotId contextId;
contextId.index = context;
contextId.index = context - 1;
ContextKey contextkey = trophy_contexts[contextId];
trophy_contexts.erase(contextId);
@ -206,15 +210,17 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
if (handle >= trophy_handles.size()) {
s32 handle_index = handle - 1;
if (handle_index >= trophy_handles.size()) {
LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle);
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
if (!trophy_handles.is_allocated({static_cast<u32>(handle)})) {
if (!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
}
trophy_handles.erase({static_cast<u32>(handle)});
trophy_handles.erase({static_cast<u32>(handle_index)});
LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
return ORBIS_OK;
}

View file

@ -140,7 +140,7 @@ s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder,
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
}
if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) ||
outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
(outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
@ -167,7 +167,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
LOG_ERROR(Lib_Vdec2, "Invalid arguments");
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
}
if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
if ((outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}
@ -179,7 +179,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
if (p1stPictureInfoOut) {
OrbisVideodec2AvcPictureInfo* picInfo =
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut);
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) {
if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) {
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
}

View file

@ -73,8 +73,10 @@ struct OrbisVideodec2OutputInfo {
u32 frameHeight;
void* frameBuffer;
u64 frameBufferSize;
u32 frameFormat;
u32 framePitchInBytes;
};
static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30);
static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x38);
struct OrbisVideodec2FrameBuffer {
u64 thisSize;

View file

@ -55,6 +55,23 @@ struct OrbisVideodec2AvcPictureInfo {
u8 pic_struct;
u8 field_pic_flag;
u8 bottom_field_flag;
u8 sequenceParameterSetPresentFlag;
u8 pictureParameterSetPresentFlag;
u8 auDelimiterPresentFlag;
u8 endOfSequencePresentFlag;
u8 endOfStreamPresentFlag;
u8 fillerDataPresentFlag;
u8 pictureTimingSeiPresentFlag;
u8 bufferingPeriodSeiPresentFlag;
u8 constraint_set0_flag;
u8 constraint_set1_flag;
u8 constraint_set2_flag;
u8 constraint_set3_flag;
u8 constraint_set4_flag;
u8 constraint_set5_flag;
};
static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78);
} // namespace Libraries::Vdec2

View file

@ -44,11 +44,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false;
outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0;
// Only set frameFormat if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.frameFormat = 0;
}
if (!inputData.auData) {
return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER;
}
@ -113,6 +117,11 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.framePitchInBytes = frame->linesize[0];
}
if (outputInfo.isValid) {
OrbisVideodec2AvcPictureInfo pictureInfo = {};
@ -140,11 +149,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false;
outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0;
// Only set frameFormat if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.frameFormat = 0;
}
AVFrame* frame = av_frame_alloc();
if (!frame) {
LOG_ERROR(Lib_Vdec2, "Failed to allocate frame");
@ -182,6 +195,11 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.framePitchInBytes = frame->linesize[0];
}
// FIXME: Should we add picture info here too?
}

View file

@ -282,7 +282,12 @@ s32 PS4_SYSV_ABI sceVideoOutGetVblankStatus(int handle, SceVideoOutVblankStatus*
s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called");
*status = driver->GetPort(handle)->resolution;
auto* port = driver->GetPort(handle);
if (!port || !port->is_open) {
return ORBIS_VIDEO_OUT_ERROR_INVALID_HANDLE;
}
*status = port->resolution;
return ORBIS_OK;
}

View file

@ -51,7 +51,7 @@ void ZlibTaskThread(const std::stop_token& stop) {
if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) {
break;
}
task = task_queue.back();
task = task_queue.front();
task_queue.pop();
}
@ -136,7 +136,7 @@ s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) {
} else {
done_queue_cv.wait(lock, pred);
}
*request_id = done_queue.back();
*request_id = done_queue.front();
done_queue.pop();
}
return ORBIS_OK;

View file

@ -332,20 +332,21 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
sr.type = sym_type;
const auto* record = m_hle_symbols.FindSymbol(sr);
if (!record) {
if (record) {
*return_info = *record;
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
return true;
}
// Check if it an export function
const auto* p = FindExportedModule(*module, *library);
if (p && p->export_sym.GetSize() > 0) {
record = p->export_sym.FindSymbol(sr);
}
}
if (record) {
*return_info = *record;
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
return true;
}
}
const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
if (aeronid) {

View file

@ -95,6 +95,46 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
return clamped_size;
}
void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
PrtArea& area = prt_areas[id];
if (area.mapped) {
rasterizer->UnmapMemory(area.start, area.end - area.start);
}
area.start = address;
area.end = address + size;
area.mapped = true;
// Pretend the entire PRT area is mapped to avoid GPU tracking errors.
// The caches will use CopySparseMemory to fetch data which avoids unmapped areas.
rasterizer->MapMemory(address, size);
}
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
const bool is_sparse = std::ranges::any_of(
prt_areas, [&](const PrtArea& area) { return area.Overlaps(virtual_addr, size); });
if (!is_sparse) {
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), size);
return;
}
auto vma = FindVMA(virtual_addr);
ASSERT_MSG(vma->second.Contains(virtual_addr, 0),
"Attempted to access invalid GPU address {:#x}", virtual_addr);
while (size) {
u64 copy_size = std::min<u64>(vma->second.size - (virtual_addr - vma->first), size);
if (vma->second.IsFree()) {
std::memset(dest, 0, copy_size);
} else {
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), copy_size);
}
size -= copy_size;
virtual_addr += copy_size;
dest += copy_size;
++vma;
}
}
bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) {
const VAddr virtual_addr = std::bit_cast<VAddr>(address);
const auto& vma = FindVMA(virtual_addr)->second;

View file

@ -75,6 +75,9 @@ struct DirectMemoryArea {
if (base + size != next.base) {
return false;
}
if (memory_type != next.memory_type) {
return false;
}
if (is_free != next.is_free) {
return false;
}
@ -172,6 +175,10 @@ public:
u64 ClampRangeSize(VAddr virtual_addr, u64 size);
void SetPrtArea(u32 id, VAddr address, u64 size);
void CopySparseMemory(VAddr source, u8* dest, u64 size);
bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
@ -275,6 +282,18 @@ private:
size_t pool_budget{};
Vulkan::Rasterizer* rasterizer{};
struct PrtArea {
VAddr start;
VAddr end;
bool mapped;
bool Overlaps(VAddr test_address, u64 test_size) const {
const VAddr overlap_end = test_address + test_size;
return start < overlap_end && test_address < end;
}
};
std::array<PrtArea, 3> prt_areas{};
friend class ::Core::Devtools::Widget::MemoryMapViewer;
};

View file

@ -11,6 +11,7 @@
#include <windows.h>
#else
#include <csignal>
#include <pthread.h>
#ifdef ARCH_X86_64
#include <Zydis/Formatter.h>
#endif

View file

@ -5,6 +5,7 @@
#include <set>
#include "common/singleton.h"
#include "common/types.h"
namespace Core {

View file

@ -51,7 +51,7 @@ Tcb* GetTcbBase() {
// Apple x86_64
// Reserve space in the 32-bit address range for allocating TCB pages.
asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000");
asm(".zerofill TCB_SPACE,TCB_SPACE,__tcb_space,0x3FC000");
struct LdtPage {
void* tcb;

View file

@ -77,7 +77,7 @@ void Emulator::Run(std::filesystem::path file, const std::vector<std::string> ar
game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) {
// If an executable was launched from a separate update directory,
// use the base game directory as the game folder.
const auto base_name = game_folder_name.substr(0, game_folder_name.size() - 7);
const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-'));
const auto base_path = game_folder.parent_path() / base_name;
if (std::filesystem::is_directory(base_path)) {
game_folder = base_path;

View file

@ -35,16 +35,19 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
{"-h",
[&](int&) {
std::cout << "Usage: shadps4 [options] <elf or eboot.bin path>\n"
std::cout
<< "Usage: shadps4 [options] <elf or eboot.bin path>\n"
"Options:\n"
" -g, --game <path|ID> Specify game path to launch\n"
" -- ... Parameters passed to the game ELF. "
"Needs to be at the end of the line, and everything after \"--\" is a "
"game argument.\n"
" -p, --patch <patch_file> Apply specified patch file\n"
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen "
"state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
" -h, --help Display this help message\n";
exit(0);
}},
@ -72,6 +75,8 @@ int main(int argc, char* argv[]) {
}
}},
{"--patch", [&](int& i) { arg_map["-p"](i); }},
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
{"-f",
[&](int& i) {
if (++i >= argc) {
@ -112,7 +117,24 @@ int main(int argc, char* argv[]) {
std::cout << "Game folder successfully saved.\n";
exit(0);
}},
};
{"--set-addon-folder", [&](int& i) {
if (++i >= argc) {
std::cerr << "Error: Missing argument for --add-addon-folder\n";
exit(1);
}
std::string config_dir(argv[i]);
std::filesystem::path config_path = std::filesystem::path(config_dir);
std::error_code discard;
if (!std::filesystem::exists(config_path, discard)) {
std::cerr << "Error: File does not exist: " << config_path << "\n";
exit(1);
}
Config::setAddonInstallDir(config_path);
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
std::cout << "Addon folder successfully saved.\n";
exit(0);
}}};
if (argc == 1) {
int dummy = 0; // one does not simply pass 0 directly

View file

@ -28,8 +28,10 @@
using namespace Common::FS;
CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent)
: QDialog(parent), networkManager(new QNetworkAccessManager(this)) {
CheckUpdate::CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
QWidget* parent)
: QDialog(parent), m_gui_settings(std::move(gui_settings)),
networkManager(new QNetworkAccessManager(this)) {
setWindowTitle(tr("Auto Updater"));
setFixedSize(0, 0);
CheckForUpdates(showMessage);
@ -43,7 +45,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
bool checkName = true;
while (checkName) {
updateChannel = QString::fromStdString(Config::getUpdateChannel());
updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
if (updateChannel == "Nightly") {
url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases");
checkName = false;
@ -52,12 +54,10 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
checkName = false;
} else {
if (Common::g_is_release) {
Config::setUpdateChannel("Release");
m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
} else {
Config::setUpdateChannel("Nightly");
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
}
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml");
}
}
@ -137,7 +137,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
}
}
latestRev = latestVersion.right(7);
latestRev = latestVersion.right(40);
latestDate = jsonObj["published_at"].toString();
QJsonArray assets = jsonObj["assets"].toArray();
@ -167,7 +167,7 @@ tr("The Auto Updater allows up to 60 update checks per hour.\\nYou have reached
QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate);
latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date";
if (latestRev == currentRev.left(7)) {
if (latestRev == currentRev) {
if (showMessage) {
QMessageBox::information(this, tr("Auto Updater"),
tr("Your version is already up to date!"));
@ -198,7 +198,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
titleLayout->addWidget(titleLabel);
layout->addLayout(titleLayout);
QString updateChannel = QString::fromStdString(Config::getUpdateChannel());
QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
QString updateText = QString("<p><b>" + tr("Update Channel") + ": </b>" + updateChannel +
"<br>"
@ -215,7 +215,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
"<td>%3</td>"
"<td>(%4)</td>"
"</tr></table></p>")
.arg(currentRev.left(7), currentDate, latestRev, latestDate);
.arg(currentRev.left(7), currentDate, latestRev.left(7), latestDate);
QLabel* updateLabel = new QLabel(updateText, this);
layout->addWidget(updateLabel);
@ -273,7 +273,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
}
});
if (Config::alwaysShowChangelog()) {
if (m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()) {
requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate);
textField->setVisible(true);
toggleButton->setText(tr("Hide Changelog"));
@ -290,14 +290,14 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
connect(noButton, &QPushButton::clicked, this, [this]() { close(); });
autoUpdateCheckBox->setChecked(Config::autoUpdate());
autoUpdateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) {
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
#else
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) {
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) {
#endif
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::setAutoUpdate(state == Qt::Checked);
m_gui_settings->SetValue(gui::gen_checkForUpdates, (state == Qt::Checked));
Config::save(user_dir / "config.toml");
});

View file

@ -8,12 +8,14 @@
#include <QDialog>
#include <QNetworkAccessManager>
#include <QPushButton>
#include "gui_settings.h"
class CheckUpdate : public QDialog {
Q_OBJECT
public:
explicit CheckUpdate(const bool showMessage, QWidget* parent = nullptr);
explicit CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
QWidget* parent = nullptr);
~CheckUpdate();
private slots:
@ -35,6 +37,7 @@ private:
QString updateDownloadUrl;
QNetworkAccessManager* networkManager;
std::shared_ptr<gui_settings> m_gui_settings;
};
#endif // CHECKUPDATE_H

View file

@ -5,11 +5,13 @@
#include "game_grid_frame.h"
#include "qt_gui/compatibility_info.h"
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) {
icon_size = Config::getIconSizeGrid();
: QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
m_compat_info(compat_info_get) {
icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt();
windowWidth = parent->width();
this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
@ -74,7 +76,7 @@ void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
}
void GameGridFrame::PlayBackgroundMusic(QString path) {
if (path.isEmpty() || !Config::getPlayBGM()) {
if (path.isEmpty() || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
@ -91,7 +93,8 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
else
m_games_ = m_game_info->m_games;
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
icon_size = Config::getIconSizeGrid(); // update icon size for resize event.
icon_size =
m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event.
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
int row = 0;
@ -118,7 +121,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
layout->addWidget(name_label);
// Resizing of font-size.
float fontSize = (Config::getIconSizeGrid() / 5.5f);
float fontSize = (m_gui_settings->GetValue(gui::gg_icon_size).toInt() / 5.5f);
QString styleSheet =
QString("color: white; font-weight: bold; font-size: %1px;").arg(fontSize);
name_label->setStyleSheet(styleSheet);
@ -168,7 +171,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
}
// If background images are hidden, clear the background image
if (!Config::getShowBackgroundImage()) {
if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
backgroundImage = QImage();
m_last_opacity = -1; // Reset opacity tracking when disabled
m_current_game_path.clear(); // Reset current game path
@ -177,7 +180,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
}
const auto& game = (*m_games_shared)[itemID];
const int opacity = Config::getBackgroundImageOpacity();
const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
// Recompute if opacity changed or we switched to a different game
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
@ -195,7 +198,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
void GameGridFrame::RefreshGridBackgroundImage() {
QPalette palette;
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
if (!backgroundImage.isNull() &&
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
QSize widgetSize = size();
QPixmap scaledPixmap =
QPixmap::fromImage(backgroundImage)

View file

@ -11,6 +11,7 @@
#include "game_info.h"
#include "game_list_utils.h"
#include "gui_context_menus.h"
#include "gui_settings.h"
#include "qt_gui/compatibility_info.h"
class GameGridFrame : public QTableWidget {
@ -37,9 +38,11 @@ private:
bool validCellSelected = false;
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes
std::shared_ptr<gui_settings> m_gui_settings;
public:
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
explicit GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent = nullptr);
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);

View file

@ -9,11 +9,13 @@
#include "game_list_frame.h"
#include "game_list_utils.h"
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent)
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) {
icon_size = Config::getIconSize();
: QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
m_compat_info(compat_info_get) {
icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt();
this->setShowGrid(false);
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
this->setSelectionBehavior(QAbstractItemView::SelectRows);
@ -97,7 +99,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
}
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
if (!item || !Config::getPlayBGM()) {
if (!item || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
BackgroundMusicPlayer::getInstance().stopMusic();
return;
}
@ -172,7 +174,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
}
// If background images are hidden, clear the background image
if (!Config::getShowBackgroundImage()) {
if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
backgroundImage = QImage();
m_last_opacity = -1; // Reset opacity tracking when disabled
m_current_game_path.clear(); // Reset current game path
@ -181,7 +183,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
}
const auto& game = m_game_info->m_games[item->row()];
const int opacity = Config::getBackgroundImageOpacity();
const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
// Recompute if opacity changed or we switched to a different game
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
@ -200,7 +202,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
void GameListFrame::RefreshListBackgroundImage() {
QPalette palette;
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
if (!backgroundImage.isNull() &&
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
QSize widgetSize = size();
QPixmap scaledPixmap =
QPixmap::fromImage(backgroundImage)

View file

@ -17,11 +17,13 @@
#include "game_info.h"
#include "game_list_utils.h"
#include "gui_context_menus.h"
#include "gui_settings.h"
class GameListFrame : public QTableWidget {
Q_OBJECT
public:
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
explicit GameListFrame(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<GameInfoClass> game_info_get,
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
QWidget* parent = nullptr);
Q_SIGNALS:
@ -48,6 +50,7 @@ private:
QTableWidgetItem* m_current_item = nullptr;
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
std::filesystem::path m_current_game_path; // Track current game path to detect changes
std::shared_ptr<gui_settings> m_gui_settings;
public:
void PopulateGameList(bool isInitialPopulation = true);

View file

@ -0,0 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "gui_settings.h"
gui_settings::gui_settings(QObject* parent) : settings(parent) {
m_settings = std::make_unique<QSettings>(ComputeSettingsDir() + "qt_ui.ini",
QSettings::Format::IniFormat, parent);
}

46
src/qt_gui/gui_settings.h Normal file
View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QWindow>
#include "settings.h"
namespace gui {
// categories
const QString general_settings = "general_settings";
const QString main_window = "main_window";
const QString game_list = "game_list";
const QString game_grid = "game_grid";
// general
const gui_value gen_checkForUpdates = gui_value(general_settings, "checkForUpdates", false);
const gui_value gen_showChangeLog = gui_value(general_settings, "showChangeLog", false);
const gui_value gen_updateChannel = gui_value(general_settings, "updateChannel", "Release");
// main window settings
const gui_value mw_geometry = gui_value(main_window, "geometry", QByteArray());
const gui_value mw_showLabelsUnderIcons = gui_value(main_window, "showLabelsUnderIcons", true);
// game list settings
const gui_value gl_mode = gui_value(game_list, "tableMode", 0);
const gui_value gl_icon_size = gui_value(game_list, "icon_size", 36);
const gui_value gl_slider_pos = gui_value(game_list, "slider_pos", 0);
const gui_value gl_showBackgroundImage = gui_value(game_list, "showBackgroundImage", true);
const gui_value gl_backgroundImageOpacity = gui_value(game_list, "backgroundImageOpacity", 50);
const gui_value gl_playBackgroundMusic = gui_value(game_list, "playBackgroundMusic", true);
const gui_value gl_backgroundMusicVolume = gui_value(game_list, "backgroundMusicVolume", 50);
// game grid settings
const gui_value gg_icon_size = gui_value(game_grid, "icon_size", 69);
const gui_value gg_slider_pos = gui_value(game_grid, "slider_pos", 0);
} // namespace gui
class gui_settings : public settings {
Q_OBJECT
public:
explicit gui_settings(QObject* parent = nullptr);
~gui_settings() override = default;
};

View file

@ -35,11 +35,11 @@ KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget*
ButtonsList = {
ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton,
ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button,
ui->L3Button, ui->R3Button, ui->TouchpadButton, ui->OptionsButton,
ui->TouchpadButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton,
ui->DpadRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton,
ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton,
ui->RStickRightButton, ui->LHalfButton, ui->RHalfButton};
ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton,
ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton,
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton,
ui->LHalfButton, ui->RHalfButton};
ButtonConnects();
SetUIValuestoMappings("default");
@ -372,14 +372,31 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) {
file.close();
// Prevent duplicate inputs for KBM as this breaks the engine
bool duplicateFound = false;
QSet<QString> duplicateMappings;
for (auto it = inputs.begin(); it != inputs.end(); ++it) {
if (std::find(it + 1, inputs.end(), *it) != inputs.end()) {
QMessageBox::information(this, tr("Unable to Save"),
tr("Cannot bind any unique input more than once"));
return;
duplicateFound = true;
duplicateMappings.insert(QString::fromStdString(*it));
}
}
if (duplicateFound) {
QStringList duplicatesList;
for (const QString mapping : duplicateMappings) {
for (const auto& button : ButtonsList) {
if (button->text() == mapping)
duplicatesList.append(button->objectName() + " - " + mapping);
}
}
QMessageBox::information(
this, tr("Unable to Save"),
// clang-format off
QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n"))));
// clang-format on
return;
}
std::vector<std::string> save;
bool CurrentLineEmpty = false, LastLineEmpty = false;
for (auto const& line : lines) {

View file

@ -41,7 +41,8 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
{"-h",
[&](int&) {
std::cout << "Usage: shadps4 [options]\n"
std::cout
<< "Usage: shadps4 [options]\n"
"Options:\n"
" No arguments: Opens the GUI.\n"
" -g, --game <path|ID> Specify <eboot.bin or elf path> or "
@ -50,6 +51,7 @@ int main(int argc, char* argv[]) {
"Needs to be at the end of the line, and everything after \"--\" is a "
"game argument.\n"
" -p, --patch <patch_file> Apply specified patch file\n"
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
" -s, --show-gui Show the GUI\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen "
"state. Does not overwrite the config file.\n"
@ -84,6 +86,8 @@ int main(int argc, char* argv[]) {
}
}},
{"--patch", [&](int& i) { arg_map["-p"](i); }},
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
{"-f",
[&](int& i) {
if (++i >= argc) {

View file

@ -32,6 +32,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
ui->setupUi(this);
installEventFilter(this);
setAttribute(Qt::WA_DeleteOnClose);
m_gui_settings = std::make_shared<gui_settings>();
ui->toggleLabelsAct->setChecked(
m_gui_settings->GetValue(gui::mw_showLabelsUnderIcons).toBool());
}
MainWindow::~MainWindow() {
@ -139,7 +142,7 @@ void MainWindow::PauseGame() {
void MainWindow::toggleLabelsUnderIcons() {
bool showLabels = ui->toggleLabelsAct->isChecked();
Config::setShowLabelsUnderIcons();
m_gui_settings->SetValue(gui::mw_showLabelsUnderIcons, showLabels);
UpdateToolbarLabels();
if (isGameRunning) {
UpdateToolbarButtons();
@ -290,21 +293,21 @@ void MainWindow::CreateDockWindows() {
setCentralWidget(phCentralWidget);
m_dock_widget.reset(new QDockWidget(tr("Game List"), this));
m_game_list_frame.reset(new GameListFrame(m_game_info, m_compat_info, this));
m_game_list_frame.reset(new GameListFrame(m_gui_settings, m_game_info, m_compat_info, this));
m_game_list_frame->setObjectName("gamelist");
m_game_grid_frame.reset(new GameGridFrame(m_game_info, m_compat_info, this));
m_game_grid_frame.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this));
m_game_grid_frame->setObjectName("gamegridlist");
m_elf_viewer.reset(new ElfViewer(this));
m_elf_viewer->setObjectName("elflist");
int table_mode = Config::getTableMode();
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
int slider_pos = 0;
if (table_mode == 0) { // List
m_game_grid_frame->hide();
m_elf_viewer->hide();
m_game_list_frame->show();
m_dock_widget->setWidget(m_game_list_frame.data());
slider_pos = Config::getSliderPosition();
slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt();
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
isTableList = true;
} else if (table_mode == 1) { // Grid
@ -312,7 +315,7 @@ void MainWindow::CreateDockWindows() {
m_elf_viewer->hide();
m_game_grid_frame->show();
m_dock_widget->setWidget(m_game_grid_frame.data());
slider_pos = Config::getSliderPositionGrid();
slider_pos = m_gui_settings->GetValue(gui::gg_slider_pos).toInt();
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
isTableList = false;
} else {
@ -356,11 +359,11 @@ void MainWindow::LoadGameLists() {
#ifdef ENABLE_UPDATER
void MainWindow::CheckUpdateMain(bool checkSave) {
if (checkSave) {
if (!Config::autoUpdate()) {
if (!m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()) {
return;
}
}
auto checkUpdate = new CheckUpdate(false);
auto checkUpdate = new CheckUpdate(m_gui_settings, false);
checkUpdate->exec();
}
#endif
@ -380,13 +383,13 @@ void MainWindow::CreateConnects() {
m_game_list_frame->icon_size =
48 + value; // 48 is the minimum icon size to use due to text disappearing.
m_game_list_frame->ResizeIcons(48 + value);
Config::setIconSize(48 + value);
Config::setSliderPosition(value);
m_gui_settings->SetValue(gui::gl_icon_size, 48 + value);
m_gui_settings->SetValue(gui::gl_slider_pos, value);
} else {
m_game_grid_frame->icon_size = 69 + value;
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
Config::setIconSizeGrid(69 + value);
Config::setSliderPositionGrid(value);
m_gui_settings->SetValue(gui::gg_icon_size, 69 + value);
m_gui_settings->SetValue(gui::gg_slider_pos, value);
}
});
@ -404,7 +407,7 @@ void MainWindow::CreateConnects() {
&MainWindow::StartGame);
connect(ui->configureAct, &QAction::triggered, this, [this]() {
auto settingsDialog = new SettingsDialog(m_compat_info, this);
auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this);
connect(settingsDialog, &SettingsDialog::LanguageChanged, this,
&MainWindow::OnLanguageChanged);
@ -418,7 +421,8 @@ void MainWindow::CreateConnects() {
connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this,
[this](int opacity) {
Config::setBackgroundImageOpacity(opacity);
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
std::clamp(opacity, 0, 100));
if (m_game_list_frame) {
QTableWidgetItem* current = m_game_list_frame->GetCurrentItem();
if (current) {
@ -437,7 +441,7 @@ void MainWindow::CreateConnects() {
});
connect(ui->settingsButton, &QPushButton::clicked, this, [this]() {
auto settingsDialog = new SettingsDialog(m_compat_info, this);
auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this);
connect(settingsDialog, &SettingsDialog::LanguageChanged, this,
&MainWindow::OnLanguageChanged);
@ -451,7 +455,8 @@ void MainWindow::CreateConnects() {
connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this,
[this](int opacity) {
Config::setBackgroundImageOpacity(opacity);
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
std::clamp(opacity, 0, 100));
if (m_game_list_frame) {
QTableWidgetItem* current = m_game_list_frame->GetCurrentItem();
if (current) {
@ -481,7 +486,7 @@ void MainWindow::CreateConnects() {
#ifdef ENABLE_UPDATER
connect(ui->updaterAct, &QAction::triggered, this, [this]() {
auto checkUpdate = new CheckUpdate(true);
auto checkUpdate = new CheckUpdate(m_gui_settings, true);
checkUpdate->exec();
});
#endif
@ -496,13 +501,13 @@ void MainWindow::CreateConnects() {
m_game_list_frame->icon_size =
36; // 36 is the minimum icon size to use due to text disappearing.
ui->sizeSlider->setValue(0); // icone_size - 36
Config::setIconSize(36);
Config::setSliderPosition(0);
m_gui_settings->SetValue(gui::gl_icon_size, 36);
m_gui_settings->SetValue(gui::gl_slider_pos, 0);
} else {
m_game_grid_frame->icon_size = 69;
ui->sizeSlider->setValue(0); // icone_size - 36
Config::setIconSizeGrid(69);
Config::setSliderPositionGrid(0);
m_gui_settings->SetValue(gui::gg_icon_size, 69);
m_gui_settings->SetValue(gui::gg_slider_pos, 9);
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
});
@ -511,13 +516,13 @@ void MainWindow::CreateConnects() {
if (isTableList) {
m_game_list_frame->icon_size = 64;
ui->sizeSlider->setValue(28);
Config::setIconSize(64);
Config::setSliderPosition(28);
m_gui_settings->SetValue(gui::gl_icon_size, 64);
m_gui_settings->SetValue(gui::gl_slider_pos, 28);
} else {
m_game_grid_frame->icon_size = 97;
ui->sizeSlider->setValue(28);
Config::setIconSizeGrid(97);
Config::setSliderPositionGrid(28);
m_gui_settings->SetValue(gui::gg_icon_size, 97);
m_gui_settings->SetValue(gui::gg_slider_pos, 28);
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
});
@ -526,13 +531,13 @@ void MainWindow::CreateConnects() {
if (isTableList) {
m_game_list_frame->icon_size = 128;
ui->sizeSlider->setValue(92);
Config::setIconSize(128);
Config::setSliderPosition(92);
m_gui_settings->SetValue(gui::gl_icon_size, 128);
m_gui_settings->SetValue(gui::gl_slider_pos, 92);
} else {
m_game_grid_frame->icon_size = 161;
ui->sizeSlider->setValue(92);
Config::setIconSizeGrid(161);
Config::setSliderPositionGrid(92);
m_gui_settings->SetValue(gui::gg_icon_size, 161);
m_gui_settings->SetValue(gui::gg_slider_pos, 92);
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
});
@ -541,13 +546,13 @@ void MainWindow::CreateConnects() {
if (isTableList) {
m_game_list_frame->icon_size = 256;
ui->sizeSlider->setValue(220);
Config::setIconSize(256);
Config::setSliderPosition(220);
m_gui_settings->SetValue(gui::gl_icon_size, 256);
m_gui_settings->SetValue(gui::gl_slider_pos, 220);
} else {
m_game_grid_frame->icon_size = 256;
ui->sizeSlider->setValue(220);
Config::setIconSizeGrid(256);
Config::setSliderPositionGrid(220);
m_gui_settings->SetValue(gui::gg_icon_size, 256);
m_gui_settings->SetValue(gui::gg_slider_pos, 220);
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
});
@ -563,8 +568,8 @@ void MainWindow::CreateConnects() {
m_game_list_frame->PopulateGameList();
}
isTableList = true;
Config::setTableMode(0);
int slider_pos = Config::getSliderPosition();
m_gui_settings->SetValue(gui::gl_mode, 0);
int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt();
ui->sizeSlider->setEnabled(true);
ui->sizeSlider->setSliderPosition(slider_pos);
ui->mw_searchbar->setText("");
@ -582,8 +587,8 @@ void MainWindow::CreateConnects() {
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
}
isTableList = false;
Config::setTableMode(1);
int slider_pos_grid = Config::getSliderPositionGrid();
m_gui_settings->SetValue(gui::gl_mode, 1);
int slider_pos_grid = m_gui_settings->GetValue(gui::gg_slider_pos).toInt();
ui->sizeSlider->setEnabled(true);
ui->sizeSlider->setSliderPosition(slider_pos_grid);
ui->mw_searchbar->setText("");
@ -598,7 +603,7 @@ void MainWindow::CreateConnects() {
m_elf_viewer->show();
isTableList = false;
ui->sizeSlider->setDisabled(true);
Config::setTableMode(2);
m_gui_settings->SetValue(gui::gl_mode, 2);
SetLastIconSizeBullet();
});
@ -840,7 +845,7 @@ void MainWindow::CreateConnects() {
void MainWindow::StartGame() {
BackgroundMusicPlayer::getInstance().stopMusic();
QString gamePath = "";
int table_mode = Config::getTableMode();
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
if (table_mode == 0) {
if (m_game_list_frame->currentItem()) {
int itemID = m_game_list_frame->currentItem()->row();
@ -925,25 +930,25 @@ void MainWindow::RefreshGameTable() {
}
void MainWindow::ConfigureGuiFromSettings() {
setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(),
Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH());
if (!restoreGeometry(m_gui_settings->GetValue(gui::mw_geometry).toByteArray())) {
// By default, set the window to 70% of the screen
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
}
ui->showGameListAct->setChecked(true);
if (Config::getTableMode() == 0) {
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
if (table_mode == 0) {
ui->setlistModeListAct->setChecked(true);
} else if (Config::getTableMode() == 1) {
} else if (table_mode == 1) {
ui->setlistModeGridAct->setChecked(true);
} else if (Config::getTableMode() == 2) {
} else if (table_mode == 2) {
ui->setlistElfAct->setChecked(true);
}
BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume());
BackgroundMusicPlayer::getInstance().setVolume(
m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt());
}
void MainWindow::SaveWindowState() const {
Config::setMainWindowWidth(this->width());
Config::setMainWindowHeight(this->height());
Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(),
this->geometry().width(), this->geometry().height());
void MainWindow::SaveWindowState() {
m_gui_settings->SetValue(gui::mw_geometry, saveGeometry(), false);
}
void MainWindow::BootGame() {
@ -1024,8 +1029,8 @@ void MainWindow::SetLastUsedTheme() {
void MainWindow::SetLastIconSizeBullet() {
// set QAction bullet point if applicable
int lastSize = Config::getIconSize();
int lastSizeGrid = Config::getIconSizeGrid();
int lastSize = m_gui_settings->GetValue(gui::gl_icon_size).toInt();
int lastSizeGrid = m_gui_settings->GetValue(gui::gg_icon_size).toInt();
if (isTableList) {
switch (lastSize) {
case 36:
@ -1195,7 +1200,7 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
auto tblMode = Config::getTableMode();
auto tblMode = m_gui_settings->GetValue(gui::gl_mode).toInt();
if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) {
StartGame();
return true;

View file

@ -20,6 +20,7 @@
#include "game_info.h"
#include "game_list_frame.h"
#include "game_list_utils.h"
#include "gui_settings.h"
#include "main_window_themes.h"
#include "main_window_ui.h"
@ -41,7 +42,7 @@ public:
private Q_SLOTS:
void ConfigureGuiFromSettings();
void SaveWindowState() const;
void SaveWindowState();
void SearchGameTable(const QString& text);
void ShowGameList();
void RefreshGameTable();
@ -102,6 +103,7 @@ private:
std::make_shared<CompatibilityInfoClass>();
QTranslator* translator;
std::shared_ptr<gui_settings> m_gui_settings;
protected:
bool eventFilter(QObject* obj, QEvent* event) override;

View file

@ -107,7 +107,6 @@ public:
toggleLabelsAct = new QAction(MainWindow);
toggleLabelsAct->setObjectName("toggleLabelsAct");
toggleLabelsAct->setCheckable(true);
toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons());
setIconSizeTinyAct = new QAction(MainWindow);
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");

77
src/qt_gui/settings.cpp Normal file
View file

@ -0,0 +1,77 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <common/path_util.h>
#include "settings.h"
settings::settings(QObject* parent) : QObject(parent), m_settings_dir(ComputeSettingsDir()) {}
settings::~settings() {
sync();
}
void settings::sync() {
if (m_settings) {
m_settings->sync();
}
}
QString settings::GetSettingsDir() const {
return m_settings_dir.absolutePath();
}
QString settings::ComputeSettingsDir() {
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
return QString::fromStdString(config_dir.string() + "/");
}
void settings::RemoveValue(const QString& key, const QString& name, bool sync) const {
if (m_settings) {
m_settings->beginGroup(key);
m_settings->remove(name);
m_settings->endGroup();
if (sync) {
m_settings->sync();
}
}
}
void settings::RemoveValue(const gui_value& entry, bool sync) const {
RemoveValue(entry.key, entry.name, sync);
}
QVariant settings::GetValue(const QString& key, const QString& name, const QVariant& def) const {
return m_settings ? m_settings->value(key + "/" + name, def) : def;
}
QVariant settings::GetValue(const gui_value& entry) const {
return GetValue(entry.key, entry.name, entry.def);
}
void settings::SetValue(const gui_value& entry, const QVariant& value, bool sync) const {
SetValue(entry.key, entry.name, value, sync);
}
void settings::SetValue(const QString& key, const QVariant& value, bool sync) const {
if (m_settings) {
m_settings->setValue(key, value);
if (sync) {
m_settings->sync();
}
}
}
void settings::SetValue(const QString& key, const QString& name, const QVariant& value,
bool sync) const {
if (m_settings) {
m_settings->beginGroup(key);
m_settings->setValue(name, value);
m_settings->endGroup();
if (sync) {
m_settings->sync();
}
}
}

55
src/qt_gui/settings.h Normal file
View file

@ -0,0 +1,55 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDir>
#include <QSettings>
#include <QString>
#include <QVariant>
struct gui_value {
QString key;
QString name;
QVariant def;
gui_value() {}
gui_value(const QString& k, const QString& n, const QVariant& d) : key(k), name(n), def(d) {}
bool operator==(const gui_value& rhs) const noexcept {
return key == rhs.key && name == rhs.name && def == rhs.def;
}
};
class settings : public QObject {
Q_OBJECT
public:
explicit settings(QObject* parent = nullptr);
~settings();
void sync();
QString GetSettingsDir() const;
QVariant GetValue(const QString& key, const QString& name, const QVariant& def) const;
QVariant GetValue(const gui_value& entry) const;
public Q_SLOTS:
/** Remove entry */
void RemoveValue(const QString& key, const QString& name, bool sync = true) const;
void RemoveValue(const gui_value& entry, bool sync = true) const;
/** Write value to entry */
void SetValue(const gui_value& entry, const QVariant& value, bool sync = true) const;
void SetValue(const QString& key, const QVariant& value, bool sync = true) const;
void SetValue(const QString& key, const QString& name, const QVariant& value,
bool sync = true) const;
protected:
static QString ComputeSettingsDir();
std::unique_ptr<QSettings> m_settings;
QDir m_settings_dir;
};

View file

@ -71,9 +71,10 @@ int bgm_volume_backup;
static std::vector<QString> m_physical_devices;
SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_info,
SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
QWidget* parent)
: QDialog(parent), ui(new Ui::SettingsDialog) {
: QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)) {
ui->setupUi(this);
ui->tabWidgetSettings->setUsesScrollButtons(false);
@ -147,6 +148,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
Config::save(config_dir / "config.toml");
} else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
Config::setDefaultValues();
setDefaultValues();
Config::save(config_dir / "config.toml");
LoadValuesFromConfig();
} else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) {
@ -175,28 +177,34 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
{
#ifdef ENABLE_UPDATER
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this,
[](int state) { Config::setAutoUpdate(state == Qt::Checked); });
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked);
});
connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this,
[](int state) { Config::setAlwaysShowChangelog(state == Qt::Checked); });
connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked);
});
#else
connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state) { Config::setAutoUpdate(state == Qt::Checked); });
[this](Qt::CheckState state) {
m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked);
});
connect(ui->changelogCheckBox, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state) { Config::setAlwaysShowChangelog(state == Qt::Checked); });
[this](Qt::CheckState state) {
m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked);
});
#endif
connect(ui->updateComboBox, &QComboBox::currentTextChanged, this,
[this](const QString& channel) {
if (channelMap.contains(channel)) {
Config::setUpdateChannel(channelMap.value(channel).toStdString());
m_gui_settings->SetValue(gui::gen_updateChannel, channelMap.value(channel));
}
});
connect(ui->checkUpdateButton, &QPushButton::clicked, this, []() {
auto checkUpdate = new CheckUpdate(true);
connect(ui->checkUpdateButton, &QPushButton::clicked, this, [this]() {
auto checkUpdate = new CheckUpdate(m_gui_settings, true);
checkUpdate->exec();
});
#else
@ -235,12 +243,12 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
[](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); });
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [](int state) {
connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
#else
connect(ui->showBackgroundImageCheckBox, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state) {
[this](Qt::CheckState state) {
#endif
Config::setShowBackgroundImage(state == Qt::Checked);
m_gui_settings->SetValue(gui::gl_showBackgroundImage, state == Qt::Checked);
});
}
@ -448,7 +456,7 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->dumpShadersCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "dumpShaders", false));
ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false));
ui->enableHDRCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "allowHDR", false));
ui->playBGMCheckBox->setChecked(toml::find_or<bool>(data, "General", "playBGM", false));
ui->playBGMCheckBox->setChecked(m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool());
ui->disableTrophycheckBox->setChecked(
toml::find_or<bool>(data, "General", "isTrophyPopupDisabled", false));
ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration());
@ -460,7 +468,7 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->radioButton_Top->setChecked(side == "top");
ui->radioButton_Bottom->setChecked(side == "bottom");
ui->BGMVolumeSlider->setValue(toml::find_or<int>(data, "General", "BGMvolume", 50));
ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt());
ui->discordRPCCheckbox->setChecked(
toml::find_or<bool>(data, "General", "enableDiscordRPC", true));
QString translatedText_FullscreenMode =
@ -501,11 +509,10 @@ void SettingsDialog::LoadValuesFromConfig() {
toml::find_or<bool>(data, "General", "checkCompatibilityOnStartup", false));
#ifdef ENABLE_UPDATER
ui->updateCheckBox->setChecked(toml::find_or<bool>(data, "General", "autoUpdate", false));
ui->changelogCheckBox->setChecked(
toml::find_or<bool>(data, "General", "alwaysShowChangelog", false));
ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
ui->changelogCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_showChangeLog).toBool());
QString updateChannel = QString::fromStdString(Config::getUpdateChannel());
QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
ui->updateComboBox->setCurrentText(
channelMap.key(updateChannel != "Release" && updateChannel != "Nightly"
? (Common::g_is_release ? "Release" : "Nightly")
@ -536,11 +543,14 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty());
ResetInstallFolders();
ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity());
ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage());
ui->backgroundImageOpacitySlider->setValue(
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt());
ui->showBackgroundImageCheckBox->setChecked(
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool());
backgroundImageOpacitySlider_backup = Config::getBackgroundImageOpacity();
bgm_volume_backup = Config::getBGMvolume();
backgroundImageOpacitySlider_backup =
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt();
}
void SettingsDialog::InitializeEmulatorLanguages() {
@ -754,8 +764,7 @@ void SettingsDialog::UpdateSettings() {
} else if (ui->radioButton_Bottom->isChecked()) {
Config::setSideTrophy("bottom");
}
Config::setPlayBGM(ui->playBGMCheckBox->isChecked());
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked());
Config::setAllowHDR(ui->enableHDRCheckBox->isChecked());
Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString());
Config::setLogFilter(ui->logFilterLineEdit->text().toStdString());
@ -764,7 +773,7 @@ void SettingsDialog::UpdateSettings() {
Config::setCursorState(ui->hideCursorComboBox->currentIndex());
Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value());
Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1);
Config::setBGMvolume(ui->BGMVolumeSlider->value());
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value());
Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]);
Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked());
Config::setScreenWidth(ui->widthSpinBox->value());
@ -784,16 +793,19 @@ void SettingsDialog::UpdateSettings() {
Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked());
Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked());
Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked());
Config::setAutoUpdate(ui->updateCheckBox->isChecked());
Config::setAlwaysShowChangelog(ui->changelogCheckBox->isChecked());
Config::setUpdateChannel(channelMap.value(ui->updateComboBox->currentText()).toStdString());
m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked());
m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked());
m_gui_settings->SetValue(gui::gen_updateChannel,
channelMap.value(ui->updateComboBox->currentText()));
Config::setChooseHomeTab(
chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString());
Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked());
Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked());
Config::setBackgroundImageOpacity(ui->backgroundImageOpacitySlider->value());
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
std::clamp(ui->backgroundImageOpacitySlider->value(), 0, 100));
emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value());
Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked());
m_gui_settings->SetValue(gui::gl_showBackgroundImage,
ui->showBackgroundImageCheckBox->isChecked());
std::vector<Config::GameInstallDir> dirs_with_states;
for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) {
@ -862,3 +874,16 @@ void SettingsDialog::ResetInstallFolders() {
Config::setAllGameInstallDirs(settings_install_dirs_config);
}
}
void SettingsDialog::setDefaultValues() {
m_gui_settings->SetValue(gui::gl_showBackgroundImage, true);
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50);
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false);
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50);
m_gui_settings->SetValue(gui::gen_checkForUpdates, false);
m_gui_settings->SetValue(gui::gen_showChangeLog, false);
if (Common::g_is_release) {
m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
} else {
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
}
}

View file

@ -11,6 +11,7 @@
#include "common/config.h"
#include "common/path_util.h"
#include "gui_settings.h"
#include "qt_gui/compatibility_info.h"
namespace Ui {
@ -20,7 +21,8 @@ class SettingsDialog;
class SettingsDialog : public QDialog {
Q_OBJECT
public:
explicit SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_info,
explicit SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
QWidget* parent = nullptr);
~SettingsDialog();
@ -42,6 +44,7 @@ private:
void OnLanguageChanged(int index);
void OnCursorStateChanged(s16 index);
void closeEvent(QCloseEvent* event) override;
void setDefaultValues();
std::unique_ptr<Ui::SettingsDialog> ui;
@ -52,4 +55,5 @@ private:
int initialHeight;
bool is_saving = false;
std::shared_ptr<gui_settings> m_gui_settings;
};

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>تعذّر الحفظ</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>لا يمكن تعيين نفس الزر لأكثر من وظيفة</translation>
</message>
<message>
<source>Press a key</source>
<translation>اضغط زرًا</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>إلغاء</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -2049,7 +2053,7 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل
</message>
<message>
<source> * Unsupported Vulkan Version</source>
<translation type="unfinished"> * Unsupported Vulkan Version</translation>
<translation>نسخ Vulkan غير مدعومة</translation>
</message>
</context>
<context>

File diff suppressed because it is too large Load diff

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Speichern nicht möglich</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Kann keine eindeutige Eingabe mehr als einmal zuordnen</translation>
</message>
<message>
<source>Press a key</source>
<translation>Drücken Sie eine Taste</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Abbrechen</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished"></translation>
@ -1184,6 +1180,12 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>No se Pudo Guardar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>No se puede vincular ninguna entrada única más de una vez</translation>
</message>
<message>
<source>Press a key</source>
<translation>Pulsa una tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -26,7 +26,7 @@
</message>
<message>
<source>Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n</source>
<translation type="unfinished">Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n</translation>
<translation>تقلبها/پچها آزمایشی هستند.\n با احتیاط استفاده کنید.\n\n با انتخاب مخزن و کلیک روی دکمه دانلود، تقلبها را بهصورت جداگانه دانلود کنید.\n در تب پچها، میتوانید همه پچها را بهطور همزمان دانلود کنید، انتخاب کنید که میخواهید از کدام استفاده کنید و انتخاب خود را ذخیره کنید.\n\n از آنجایی که ما تقلبها/پچها را توسعه نمیدهیم،\n لطفاً مشکلات را به نویسنده تقلب گزارش دهید.\n\n تقلب جدیدی ایجاد کردهاید؟ به این صفحه مراجعه کنید: \n</translation>
</message>
<message>
<source>No Image Available</source>
@ -214,7 +214,7 @@
</message>
<message>
<source>XML ERROR:</source>
<translation type="unfinished">XML ERROR:</translation>
<translation>XML خطای :</translation>
</message>
<message>
<source>Failed to open files.json for writing</source>
@ -407,43 +407,43 @@
<name>ControlSettings</name>
<message>
<source>Configure Controls</source>
<translation type="unfinished">Configure Controls</translation>
<translation>پیکربندی دسته ها</translation>
</message>
<message>
<source>D-Pad</source>
<translation type="unfinished">D-Pad</translation>
<translation>D-Pad</translation>
</message>
<message>
<source>Up</source>
<translation type="unfinished">Up</translation>
<translation>بالا</translation>
</message>
<message>
<source>Left</source>
<translation type="unfinished">Left</translation>
<translation>چپ</translation>
</message>
<message>
<source>Right</source>
<translation type="unfinished">Right</translation>
<translation>راست</translation>
</message>
<message>
<source>Down</source>
<translation type="unfinished">Down</translation>
<translation>پایین</translation>
</message>
<message>
<source>Left Stick Deadzone (def:2 max:127)</source>
<translation type="unfinished">Left Stick Deadzone (def:2 max:127)</translation>
<translation>منطقهی حساس به حرکت چپ (def:2 max:127)</translation>
</message>
<message>
<source>Left Deadzone</source>
<translation type="unfinished">Left Deadzone</translation>
<translation>منطقه مرده چپ</translation>
</message>
<message>
<source>Left Stick</source>
<translation type="unfinished">Left Stick</translation>
<translation>جواستیک چپ</translation>
</message>
<message>
<source>Config Selection</source>
<translation type="unfinished">Config Selection</translation>
<translation>انتخاب پیکربندی</translation>
</message>
<message>
<source>Common Config</source>
@ -451,7 +451,7 @@
</message>
<message>
<source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation>
<translation>از پیکربندیهای مخصوص هر بازی استفاده کنید</translation>
</message>
<message>
<source>L1 / LB</source>
@ -483,7 +483,7 @@
</message>
<message>
<source>R3</source>
<translation type="unfinished">R3</translation>
<translation>R3</translation>
</message>
<message>
<source>Face Buttons</source>
@ -491,7 +491,7 @@
</message>
<message>
<source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation>
<translation>مثلث / Y</translation>
</message>
<message>
<source>Square / X</source>
@ -531,7 +531,7 @@
</message>
<message>
<source>B:</source>
<translation type="unfinished">B:</translation>
<translation>B:</translation>
</message>
<message>
<source>Override Lightbar Color</source>
@ -543,7 +543,7 @@
</message>
<message>
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
<translation>ذخیره امکان پذیر نیست</translation>
</message>
<message>
<source>Cannot bind axis values more than once</source>
@ -570,7 +570,7 @@
<name>EditorDialog</name>
<message>
<source>Edit Keyboard + Mouse and Controller input bindings</source>
<translation type="unfinished">Edit Keyboard + Mouse and Controller input bindings</translation>
<translation>تغییر دکمه های کیبرد + ماوس و دسته</translation>
</message>
<message>
<source>Use Per-Game configs</source>
@ -582,7 +582,7 @@
</message>
<message>
<source>Could not open the file for reading</source>
<translation type="unfinished">Could not open the file for reading</translation>
<translation>نمی تواند فایل را برای خواندن باز کند</translation>
</message>
<message>
<source>Could not open the file for writing</source>
@ -602,7 +602,7 @@
</message>
<message>
<source>Do you want to reset your custom default config to the original default config?</source>
<translation type="unfinished">Do you want to reset your custom default config to the original default config?</translation>
<translation>آیا میخواهید پیکربندی سفارشی خود را به پیکربندی پیشفرض اصلی بازگردانید ؟</translation>
</message>
<message>
<source>Do you want to reset this config to your custom default config?</source>
@ -860,7 +860,7 @@
</message>
<message>
<source>View report</source>
<translation type="unfinished">View report</translation>
<translation>مشاهده گزارش</translation>
</message>
<message>
<source>Submit a report</source>
@ -916,11 +916,11 @@
</message>
<message>
<source>Delete Save Data</source>
<translation type="unfinished">Delete Save Data</translation>
<translation>پاک کردن داده های ذخیره شده</translation>
</message>
<message>
<source>This game has no update folder to open!</source>
<translation type="unfinished">This game has no update folder to open!</translation>
<translation>این بازی هیچ پوشهی بهروزرسانی برای باز کردن ندارد!</translation>
</message>
<message>
<source>No log file found for this game!</source>
@ -948,7 +948,7 @@
</message>
<message>
<source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation>
<translation>SFO مشاهده </translation>
</message>
</context>
<context>
@ -986,7 +986,7 @@
</message>
<message>
<source>Up</source>
<translation type="unfinished">Up</translation>
<translation/>
</message>
<message>
<source>unmapped</source>
@ -1058,7 +1058,7 @@
</message>
<message>
<source>Touchpad Click</source>
<translation type="unfinished">Touchpad Click</translation>
<translation>کلیک روی تاچپد</translation>
</message>
<message>
<source>Mouse to Joystick</source>
@ -1078,7 +1078,7 @@
</message>
<message>
<source>Mouse Movement Parameters</source>
<translation type="unfinished">Mouse Movement Parameters</translation>
<translation/>
</message>
<message>
<source>note: click Help Button/Special Keybindings for more information</source>
@ -1102,7 +1102,7 @@
</message>
<message>
<source>Cross</source>
<translation type="unfinished">Cross</translation>
<translation>ضربدر</translation>
</message>
<message>
<source>Right Analog Halfmode</source>
@ -1122,7 +1122,7 @@
</message>
<message>
<source>Copy from Common Config</source>
<translation type="unfinished">Copy from Common Config</translation>
<translation>کپی از پیکربندی مشترک</translation>
</message>
<message>
<source>Deadzone Offset (def 0.50):</source>
@ -1130,32 +1130,28 @@
</message>
<message>
<source>Speed Multiplier (def 1.0):</source>
<translation type="unfinished">Speed Multiplier (def 1.0):</translation>
<translation>ضریب سرعت (def 1.0):</translation>
</message>
<message>
<source>Common Config Selected</source>
<translation type="unfinished">Common Config Selected</translation>
<translation>پیکربندی مشترک انتخاب شده</translation>
</message>
<message>
<source>This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config.</source>
<translation type="unfinished">This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config.</translation>
<translation>این دکمه نگاشتها را از پیکربندی مشترک به پروفایل انتخابشدهی فعلی کپی میکند و وقتی پروفایل انتخابشدهی فعلی پیکربندی مشترک باشد، نمیتوان از آن استفاده کرد.</translation>
</message>
<message>
<source>Copy values from Common Config</source>
<translation type="unfinished">Copy values from Common Config</translation>
<translation>کپی کردن مقادیر از پیکربندی مشترک</translation>
</message>
<message>
<source>Do you want to overwrite existing mappings with the mappings from the Common Config?</source>
<translation type="unfinished">Do you want to overwrite existing mappings with the mappings from the Common Config?</translation>
<translation>آیا میخواهید نگاشتهای موجود را با نگاشتهای پیکربندی مشترک جایگزین کنید؟</translation>
</message>
<message>
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1170,7 +1166,7 @@
</message>
<message>
<source>Save</source>
<translation type="unfinished">Save</translation>
<translation>ذخیرهسازی</translation>
</message>
<message>
<source>Apply</source>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -1213,7 +1217,7 @@
</message>
<message>
<source>Open shadPS4 Folder</source>
<translation type="unfinished">Open shadPS4 Folder</translation>
<translation>پوشه shadPS4 را باز کنید</translation>
</message>
<message>
<source>Exit</source>
@ -1624,7 +1628,7 @@
</message>
<message>
<source>Collect Shaders</source>
<translation type="unfinished">Collect Shaders</translation>
<translation>جمع آوری شیدرها</translation>
</message>
<message>
<source>Copy GPU Buffers</source>
@ -1664,7 +1668,7 @@
</message>
<message>
<source>Title Music</source>
<translation type="unfinished">Title Music</translation>
<translation/>
</message>
<message>
<source>Disable Trophy Notification</source>
@ -1728,7 +1732,7 @@
</message>
<message>
<source>Console Language:\nSets the language that the PS4 game uses.\nIt&apos;s recommended to set this to a language the game supports, which will vary by region.</source>
<translation type="unfinished">Console Language:\nSets the language that the PS4 game uses.\nIt&apos;s recommended to set this to a language the game supports, which will vary by region.</translation>
<translation>زبان کنسول:\nزبانی را که بازی PS4 استفاده میکند تنظیم میکند.\nتوصیه میشود این را روی زبانی که بازی پشتیبانی میکند تنظیم کنید، که بسته به منطقه متفاوت خواهد بود.</translation>
</message>
<message>
<source>Emulator Language:\nSets the language of the emulator&apos;s user interface.</source>
@ -1748,7 +1752,7 @@
</message>
<message>
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source>
<translation type="unfinished">Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</translation>
<translation>کلید تروفی:\و کلیدی که برای رمزگشایی تروفیها استفاده میشود. باید از کنسول جیلبریک شده شما دریافت شود.\باید فقط شامل کاراکترهای هگز باشد.</translation>
</message>
<message>
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source>
@ -1756,7 +1760,7 @@
</message>
<message>
<source>Log Filter:\nFilters the log to only print specific information.\nExamples: &quot;Core:Trace&quot; &quot;Lib.Pad:Debug Common.Filesystem:Error&quot; &quot;*:Critical&quot;\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it.</source>
<translation type="unfinished">Log Filter:\nFilters the log to only print specific information.\nExamples: &quot;Core:Trace&quot; &quot;Lib.Pad:Debug Common.Filesystem:Error&quot; &quot;*:Critical&quot;\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it.</translation>
<translation>فیلتر گزارش:\nگزارش را فیلتر میکند تا فقط اطلاعات خاصی چاپ شود.\nمثالها: &quot;هسته:ردیابی&quot; &quot;Lib.Pad:اشکال‌زدایی Common.Filesystem:خطا&quot; &quot;*:بحرانی&quot;\nسطوح: ردیابی، اشکالزدایی، اطلاعات، هشدار، خطا، بحرانی - به این ترتیب، یک سطح خاص تمام سطوح قبل از خود را در لیست بیصدا میکند و هر سطح بعد از خود را ثبت میکند.</translation>
</message>
<message>
<source>Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</source>
@ -1764,7 +1768,7 @@
</message>
<message>
<source>Background Image:\nControl the opacity of the game background image.</source>
<translation type="unfinished">Background Image:\nControl the opacity of the game background image.</translation>
<translation>تصویر پس‌زمینه: میزان شفافیت تصویر پسزمینه بازی را کنترل کنید.</translation>
</message>
<message>
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source>
@ -1844,11 +1848,11 @@
</message>
<message>
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source>
<translation type="unfinished">Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation>
<translation>فعال کردن پردازنده گرافیکی خالی:\برای رفع اشکال فنی، رندر بازی را طوری غیرفعال کنید که انگار هیچ کارت گرافیکی وجود ندارد.</translation>
</message>
<message>
<source>Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format.</source>
<translation type="unfinished">Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format.</translation>
<translation>فعال کردن HDR و :\n این گزینه HDR را در بازیهایی که از آن پشتیبانی میکنند فعال میکند.\n مانیتور شما باید از فضای رنگی BT2020 PQ و فرمت swapchain RGB10A2 پشتیبانی کند.</translation>
</message>
<message>
<source>Game Folders:\nThe list of folders to check for installed games.</source>
@ -1860,7 +1864,7 @@
</message>
<message>
<source>Remove:\nRemove a folder from the list.</source>
<translation>حذف:\nیک پوشه را از لیست حذف کنید.</translation>
<translation>حذف:\n یک پوشه را از لیست حذف کنید.</translation>
</message>
<message>
<source>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</source>
@ -1868,11 +1872,11 @@
</message>
<message>
<source>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</source>
<translation type="unfinished">Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</translation>
<translation>فعال کردن لایههای اعتبارسنجی Vulkan: \nسیستمی را فعال میکند که وضعیت رندرکننده Vulkan را اعتبارسنجی کرده و اطلاعات مربوط به وضعیت داخلی آن را ثبت میکند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیهسازی میشود.</translation>
</message>
<message>
<source>Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</source>
<translation type="unfinished">Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</translation>
<translation>فعال کردن اعتبارسنجی همگامسازی Vulkan: \nسیستمی را فعال میکند که زمانبندی وظایف رندر Vulkan را اعتبارسنجی میکند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیهسازی میشود.</translation>
</message>
<message>
<source>Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame.</source>
@ -1880,7 +1884,7 @@
</message>
<message>
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source>
<translation type="unfinished">Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</translation>
<translation>جمعآوری سایهزنها:\n برای ویرایش سایهزنها با منوی اشکالزدایی (Ctrl + F10) باید این گزینه فعال باشد.</translation>
</message>
<message>
<source>Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging &apos;Device lost&apos; errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work.</source>
@ -1912,7 +1916,7 @@
</message>
<message>
<source>Nightly</source>
<translation type="unfinished">Nightly</translation>
<translation>اخرین نسخه شبانه</translation>
</message>
<message>
<source>Set the volume of the background music.</source>
@ -1936,7 +1940,7 @@
</message>
<message>
<source>sync</source>
<translation type="unfinished">sync</translation>
<translation>همزمان</translation>
</message>
<message>
<source>Auto Select</source>
@ -2000,7 +2004,7 @@
</message>
<message>
<source>Right</source>
<translation type="unfinished">Right</translation>
<translation>راست</translation>
</message>
<message>
<source>Top</source>
@ -2032,7 +2036,7 @@
</message>
<message>
<source>%1 already exists</source>
<translation type="unfinished">%1 already exists</translation>
<translation>%1 از قبل وجود دارد</translation>
</message>
<message>
<source>Portable user folder created</source>
@ -2044,7 +2048,7 @@
</message>
<message>
<source>Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions.</source>
<translation type="unfinished">Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions.</translation>
<translation>پوشه تصاویر/صداهای تروفی سفارشی را باز کنید:\n شما میتوانید تصاویر و صدای سفارشی به تروفیها اضافه کنید.\n فایلها را با نامهای زیر به custom_trophy اضافه کنید:\ntrophy.wav یا trophy.mp3، bronze.png، gold.png، platinum.png، silver.png \nتوجه: صدا فقط در نسخههای QT کار میکند.</translation>
</message>
<message>
<source> * Unsupported Vulkan Version</source>
@ -2075,7 +2079,7 @@
</message>
<message>
<source>Show Hidden Trophies</source>
<translation type="unfinished">Show Hidden Trophies</translation>
<translation>نمایش جوایز مخفی</translation>
</message>
</context>
</TS>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Impossible de sauvegarder</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Impossible de lier une entrée unique plus d'une fois</translation>
</message>
<message>
<source>Press a key</source>
<translation>Appuyez sur un bouton</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Annuler</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Impossibile Salvare</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Non è possibile associare qualsiasi input univoco più di una volta</translation>
</message>
<message>
<source>Press a key</source>
<translation>Premi un tasto</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Annulla</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Klarte ikke lagre</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Kan ikke tildele unike oppsett mer enn en gang</translation>
</message>
<message>
<source>Press a key</source>
<translation>Trykk en tast</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Avbryt</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Zapisywanie nie powiodło się</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Nie można powiązać żadnych unikalnych danych wejściowych więcej niż raz</translation>
</message>
<message>
<source>Press a key</source>
<translation>Naciśnij klawisz</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Anuluj</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Não foi possível salvar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Não é possível vincular qualquer entrada única mais de uma vez</translation>
</message>
<message>
<source>Press a key</source>
<translation>Aperte uma tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -2048,7 +2052,7 @@
</message>
<message>
<source> * Unsupported Vulkan Version</source>
<translation type="unfinished"> * Unsupported Vulkan Version</translation>
<translation> * Versão do Vulkan não suportada</translation>
</message>
</context>
<context>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Não foi possível guardar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Não é possível vincular qualquer entrada única mais de uma vez</translation>
</message>
<message>
<source>Press a key</source>
<translation>Pressione uma tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Cancelar</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Не удаётся сохранить</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Невозможно привязать уникальный ввод более одного раза</translation>
</message>
<message>
<source>Press a key</source>
<translation>Нажмите кнопку</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Отмена</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation type="unfinished">Cancel</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Ruajtja Dështoi</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Asnjë hyrje unike nuk mund caktohet shumë se një herë</translation>
</message>
<message>
<source>Press a key</source>
<translation>Shtyp një tast</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Anulo</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

File diff suppressed because it is too large Load diff

View file

@ -158,7 +158,7 @@
</message>
<message>
<source>No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game.</source>
<translation>Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet</translation>
<translation>Inga fusk hittades för detta spel i denna version av det valda förrådet. Prova ett annat förråd eller en annan version av spelet.</translation>
</message>
<message>
<source>Cheats Downloaded Successfully</source>
@ -166,7 +166,7 @@
</message>
<message>
<source>You have successfully downloaded the cheats for this version of the game from the selected repository. You can try downloading from another repository, if it is available it will also be possible to use it by selecting the file from the list.</source>
<translation>Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga kan det vara möjligt att använda det genom att välja det genom att välja filen från listan</translation>
<translation>Du har hämtat ner fusken för denna version av spelet från valt förråd. Du kan försöka att hämta från andra förråd, om de är tillgängliga kan det vara möjligt att använda det genom att välja det genom att välja filen från listan.</translation>
</message>
<message>
<source>Failed to save:</source>
@ -182,7 +182,7 @@
</message>
<message>
<source>Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game.</source>
<translation>Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero att den inte finns för det specifika serienumret och versionen av spelet</translation>
<translation>Patchhämtningen är färdig! Alla patchar tillgängliga för alla spel har hämtats och de behövs inte hämtas individuellt för varje spel som med fusk. Om patchen inte dyker upp kan det bero att den inte finns för det specifika serienumret och versionen av spelet.</translation>
</message>
<message>
<source>Failed to parse JSON data from HTML.</source>
@ -261,7 +261,7 @@
</message>
<message>
<source>The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later.</source>
<translation>Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare</translation>
<translation>Den automatiska uppdateraren tillåter upp till 60 uppdateringskontroller per timme.\nDu har uppnått denna gräns. Försök igen senare.</translation>
</message>
<message>
<source>Failed to parse update information.</source>
@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Kunde inte spara</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Kan inte binda någon unik inmatning fler än en gång</translation>
</message>
<message>
<source>Press a key</source>
<translation>Tryck en tangent</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Avbryt</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -1728,47 +1732,47 @@
</message>
<message>
<source>Console Language:\nSets the language that the PS4 game uses.\nIt&apos;s recommended to set this to a language the game supports, which will vary by region.</source>
<translation>Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner</translation>
<translation>Konsollspråk:\nStäller in språket som PS4-spelet använder.\nDet rekommenderas att ställa in detta till ett språk som spelet har stöd för, vilket kan skilja sig mellan regioner.</translation>
</message>
<message>
<source>Emulator Language:\nSets the language of the emulator&apos;s user interface.</source>
<translation>Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt</translation>
<translation>Emulatorspråk:\nStäller in språket för emulatorns användargränssnitt.</translation>
</message>
<message>
<source>Show Splash Screen:\nShows the game&apos;s splash screen (a special image) while the game is starting.</source>
<translation>Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas</translation>
<translation>Visa startskärm:\nVisar spelets startskärm (en speciell bild) när spelet startas.</translation>
</message>
<message>
<source>Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile.</source>
<translation>Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information din Discord-profil</translation>
<translation>Aktivera Discord Rich Presence:\nVisar emulatorikonen och relevant information din Discord-profil.</translation>
</message>
<message>
<source>Username:\nSets the PS4&apos;s account username, which may be displayed by some games.</source>
<translation>Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel</translation>
<translation>Användarnamn:\nStäller in PS4ans användarkonto, som kan visas av vissa spel.</translation>
</message>
<message>
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source>
<translation>Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken</translation>
<translation>Trofényckel:\nNyckel som används för att avkryptera troféer. Måste hämtas från din konsoll (jailbroken).\nMåste innehålla endast hex-tecken.</translation>
</message>
<message>
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source>
<translation>Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan emulationen</translation>
<translation>Loggtyp:\nStäller in huruvida synkronisering av utdata för loggfönstret för prestanda. Kan ha inverkan emulationen.</translation>
</message>
<message>
<source>Log Filter:\nFilters the log to only print specific information.\nExamples: &quot;Core:Trace&quot; &quot;Lib.Pad:Debug Common.Filesystem:Error&quot; &quot;*:Critical&quot;\nLevels: Trace, Debug, Info, Warning, Error, Critical - in this order, a specific level silences all levels preceding it in the list and logs every level after it.</source>
<translation>Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: &quot;Core:Trace&quot; &quot;Lib.Pad:Debug Common.Filesystem:Error&quot; &quot;*:Critical&quot;\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den</translation>
<translation>Loggfilter:\nFiltrera loggen till att endast skriva ut specifik information.\nExempel: &quot;Core:Trace&quot; &quot;Lib.Pad:Debug Common.Filesystem:Error&quot; &quot;*:Critical&quot;\nNivåer: Trace, Debug, Info, Warning, Error, Critical - i den ordningen, en specifik nivå som tystar alla nivåer före den i listan och loggar allting efter den.</translation>
</message>
<message>
<source>Update:\nRelease: Official versions released every month that may be very outdated, but are more reliable and tested.\nNightly: Development versions that have all the latest features and fixes, but may contain bugs and are less stable.</source>
<translation>Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila</translation>
<translation>Uppdatering:\nRelease: Officiella versioner som släpps varje månad som kan vara mycket utdaterade, men är mer pålitliga och testade.\nNightly: Utvecklingsversioner som har de senaste funktionerna och fixarna, men kan innehålla fel och är mindre stabila.</translation>
</message>
<message>
<source>Background Image:\nControl the opacity of the game background image.</source>
<translation>Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild</translation>
<translation>Bakgrundsbild:\nKontrollerar opaciteten för spelets bakgrundsbild.</translation>
</message>
<message>
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source>
<translation>Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet</translation>
<translation>Spela upp titelmusik:\nOm ett spel har stöd för det kan speciell musik spelas upp från spelet i gränssnittet.</translation>
</message>
<message>
<source>Disable Trophy Pop-ups:\nDisable in-game trophy notifications. Trophy progress can still be tracked using the Trophy Viewer (right-click the game in the main window).</source>
@ -1776,27 +1780,27 @@
</message>
<message>
<source>Hide Cursor:\nChoose when the cursor will disappear:\nNever: You will always see the mouse.\nidle: Set a time for it to disappear after being idle.\nAlways: you will never see the mouse.</source>
<translation>Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren</translation>
<translation>Dölj pekare:\nVälj när muspekaren ska försvinna:\nAldrig: Du kommer alltid se muspekaren.\nOverksam: Ställ in en tid för när den ska försvinna efter den inte använts.\nAlltid: du kommer aldrig se muspekaren.</translation>
</message>
<message>
<source>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</source>
<translation>Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv</translation>
<translation>Dölj pekare vid overksam:\nLängden (sekunder) efter vilken som muspekaren som har varit overksam döljer sig själv.</translation>
</message>
<message>
<source>Back Button Behavior:\nSets the controller&apos;s back button to emulate tapping the specified position on the PS4 touchpad.</source>
<translation>Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck angivna positionen PS4ns touchpad</translation>
<translation>Beteende för bakåtknapp:\nStäller in handkontrollerns bakåtknapp för att emulera ett tryck angivna positionen PS4ns touchpad.</translation>
</message>
<message>
<source>Display Compatibility Data:\nDisplays game compatibility information in table view. Enable &quot;Update Compatibility On Startup&quot; to get up-to-date information.</source>
<translation>Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera &quot;Uppdatera kompatibilitet vid uppstart&quot; för att uppdaterad information</translation>
<translation>Visa kompatibilitetsdata:\nVisar information om spelkompatibilitet i tabellvyn. Aktivera &quot;Uppdatera kompatibilitet vid uppstart&quot; för att uppdaterad information.</translation>
</message>
<message>
<source>Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts.</source>
<translation>Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar</translation>
<translation>Uppdatera kompatibilitet vid uppstart:\nUppdatera automatiskt kompatibilitetsdatabasen när shadPS4 startar.</translation>
</message>
<message>
<source>Update Compatibility Database:\nImmediately update the compatibility database.</source>
<translation>Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt</translation>
<translation>Uppdatera kompatibilitetsdatabasen:\nUppdaterar kompatibilitetsdatabasen direkt.</translation>
</message>
<message>
<source>Never</source>
@ -1828,23 +1832,23 @@
</message>
<message>
<source>Graphics Device:\nOn multiple GPU systems, select the GPU the emulator will use from the drop down list,\nor select &quot;Auto Select&quot; to automatically determine it.</source>
<translation>Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja &quot;Auto Select&quot; för att automatiskt bestämma det</translation>
<translation>Grafikenhet:\nFör system med flera GPUer kan du välja den GPU som emulatorn ska använda från rullgardinsmenyn,\neller välja &quot;Auto Select&quot; för att automatiskt bestämma det.</translation>
</message>
<message>
<source>Width/Height:\nSets the size of the emulator window at launch, which can be resized during gameplay.\nThis is different from the in-game resolution.</source>
<translation>Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen</translation>
<translation>Bredd/Höjd:\nStäller in storleken för emulatorfönstret vid uppstart, som kan storleksändras under spelning.\nDetta är inte det samma som spelupplösningen.</translation>
</message>
<message>
<source>Vblank Divider:\nThe frame rate at which the emulator refreshes at is multiplied by this number. Changing this may have adverse effects, such as increasing the game speed, or breaking critical game functionality that does not expect this to change!</source>
<translation>Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring</translation>
<translation>Vblank Divider:\nBildfrekvensen som emulatorn uppdaterar vid multipliceras med detta tal. Ändra detta kan ha inverkan saker, såsom ökad spelhastighet eller göra sönder kritisk spelfunktionalitet, som inte förväntar sig denna ändring!</translation>
</message>
<message>
<source>Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render.</source>
<translation>Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas</translation>
<translation>Aktivera Shaders Dumping:\nFör teknisk felsökning, sparar spelets shaders till en mapp när de renderas.</translation>
</message>
<message>
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source>
<translation>Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort</translation>
<translation>Aktivera Null GPU:\nFör teknisk felsökning, inaktiverar spelrenderingen som om det inte fanns något grafikkort.</translation>
</message>
<message>
<source>Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format.</source>
@ -1852,31 +1856,31 @@
</message>
<message>
<source>Game Folders:\nThe list of folders to check for installed games.</source>
<translation>Spelmappar:\nListan över mappar att leta i efter installerade spel</translation>
<translation>Spelmappar:\nListan över mappar att leta i efter installerade spel.</translation>
</message>
<message>
<source>Add:\nAdd a folder to the list.</source>
<translation>Aktivera separat uppdateringsmapp:\nAktiverar installation av speluppdateringar till en separat mapp för enkel hantering.\nDetta kan manuellt skapas genom att lägga till den uppackade uppdateringen till spelmappen med namnet &quot;CUSA00000-UPDATE&quot; där CUSA ID matchar spelets id</translation>
<translation>Lägg till:\Lägg till en mapp till listan.</translation>
</message>
<message>
<source>Remove:\nRemove a folder from the list.</source>
<translation>Ta bort:\nTa bort en mapp från listan</translation>
<translation>Ta bort:\nTa bort en mapp från listan.</translation>
</message>
<message>
<source>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</source>
<translation>Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog</translation>
<translation>Aktivera felsökningsdumpning:\nSparar import och export av symboler och fil-header-information för aktuellt körande PS4-program till en katalog.</translation>
</message>
<message>
<source>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</source>
<translation>Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen</translation>
<translation>Aktivera Vulkan Validation Layers:\nAktiverar ett system som validerar tillståndet för Vulkan renderer och loggar information om dess interna tillstånd.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen.</translation>
</message>
<message>
<source>Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</source>
<translation>Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen</translation>
<translation>Aktivera Vulkan Synchronization Validation:\nAktiverar ett system som validerar timing för Vulkan rendering tasks.\nDetta kommer minska prestandan och antagligen ändra beteendet för emuleringen.</translation>
</message>
<message>
<source>Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame.</source>
<translation>Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta</translation>
<translation>Aktivera RenderDoc-felsökning:\nOm aktiverad kommer emulatorn att tillhandahålla kompatibilitet med Renderdoc för att tillåta fångst och analys för aktuell renderad bildruta.</translation>
</message>
<message>
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source>
@ -1884,27 +1888,27 @@
</message>
<message>
<source>Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging &apos;Device lost&apos; errors. If you have this enabled, you should enable Host AND Guest Debug Markers.\nDoes not work on Intel GPUs.\nYou need Vulkan Validation Layers enabled and the Vulkan SDK for this to work.</source>
<translation>Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av &apos;Device lost&apos;-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera</translation>
<translation>Krashdiagnostik:\nSkapar en .yaml-fil med information om Vulkan-tillståndet vid tid för kraschen.\nAnvändbart för felsökning av &apos;Device lost&apos;-fel. Om du har aktiverat detta bör du aktivera felsökningsmarkörer för Värd OCH Gäst.\nFungerar inte Intel GPUer.\nDu behöver aktivera Vulkan Validation Layers och Vulkan SDK för att detta ska fungera.</translation>
</message>
<message>
<source>Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes.</source>
<translation>Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar</translation>
<translation>Kopiera GPU-buffertar:\nGör att man kan komma runt race conditions som involverar GPU submits.\nKan eller kan inte hjälpa med PM4 type 0-kraschar.</translation>
</message>
<message>
<source>Host Debug Markers:\nInserts emulator-side information like markers for specific AMDGPU commands around Vulkan commands, as well as giving resources debug names.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc.</source>
<translation>Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc</translation>
<translation>Felsökningsmarkörer för värd:\nInfogar informationsliknande markörer i emulatorn för specifika AMDGPU-kommandon runt Vulkan-kommandon, väl som ger resurser felsökningsnamn.\nOm du har detta aktiverat bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc.</translation>
</message>
<message>
<source>Guest Debug Markers:\nInserts any debug markers the game itself has added to the command buffer.\nIf you have this enabled, you should enable Crash Diagnostics.\nUseful for programs like RenderDoc.</source>
<translation>Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc</translation>
<translation>Felsökningsmarkörer för gäst:\nInfogar felsökningsmarkörer som själva spelet har lagt till i kommandobufferten.\nOm du har aktiverat detta bör du aktivera Kraschdiagnostik.\nAnvändbart för program som RenderDoc.</translation>
</message>
<message>
<source>Save Data Path:\nThe folder where game save data will be saved.</source>
<translation>Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas</translation>
<translation>Sökväg för sparat data:\nSökvägen där spelets sparade data kommer att sparas.</translation>
</message>
<message>
<source>Browse:\nBrowse for a folder to set as the save data path.</source>
<translation>Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data</translation>
<translation>Bläddra:\nBläddra efter en mapp att ställa in som sökväg för sparat data.</translation>
</message>
<message>
<source>Release</source>

View file

@ -138,7 +138,7 @@
</message>
<message>
<source>File Exists</source>
<translation>Dosya mevcut</translation>
<translation>Dosya Mevcut</translation>
</message>
<message>
<source>File already exists. Do you want to replace it?</source>
@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Kaydedilemedi</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Herhangi bir benzersiz girdi birden fazla kez bağlanamaz</translation>
</message>
<message>
<source>Press a key</source>
<translation>Bir tuşa basın</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>İptal</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>
@ -1221,7 +1225,7 @@
</message>
<message>
<source>Exit shadPS4</source>
<translation>shadPS4&apos;ten Çık</translation>
<translation>shadPS4 Çıkış</translation>
</message>
<message>
<source>Exit the application.</source>
@ -1381,7 +1385,7 @@
</message>
<message>
<source>Game Boot</source>
<translation>Oyun Başlatma</translation>
<translation>Oyun Başlat</translation>
</message>
<message>
<source>Only one file can be selected!</source>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation>Не вдалося зберегти</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Не можна прив'язати кнопку вводу більш ніж один раз</translation>
</message>
<message>
<source>Press a key</source>
<translation>Натисніть клавішу</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Відмінити</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished">Cannot bind any unique input more than once</translation>
</message>
<message>
<source>Press a key</source>
<translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation>Hủy</translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation></translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation></translation>
</message>
<message>
<source>Press a key</source>
<translation></translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source>
<translation></translation>
</message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation></translation>
</message>
<message>
<source>Press a key</source>
<translation></translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source>
<translation></translation>
</message>
<message>
<source>Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</source>
<translation type="unfinished">Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:
%1</translation>
</message>
</context>
<context>
<name>MainWindow</name>

View file

@ -303,6 +303,12 @@ void SetupCapabilities(const Info& info, const Profile& profile, EmitContext& ct
ctx.AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
ctx.AddExtension("SPV_KHR_physical_storage_buffer");
}
const auto shared_type_count = std::popcount(static_cast<u32>(info.shared_types));
if (shared_type_count > 1 && profile.supports_workgroup_explicit_memory_layout) {
ctx.AddExtension("SPV_KHR_workgroup_memory_explicit_layout");
ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayoutKHR);
ctx.AddCapability(spv::Capability::WorkgroupMemoryExplicitLayout16BitAccessKHR);
}
}
void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) {

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/div_ceil.h"
#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
@ -15,42 +17,40 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const Id shift_id{ctx.ConstU32(2U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
const Id pointer{
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
});
}
Id SharedAtomicU32_IncDec(EmitContext& ctx, Id offset,
Id SharedAtomicU32IncDec(EmitContext& ctx, Id offset,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
const Id shift_id{ctx.ConstU32(2U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
const Id pointer{
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics);
});
}
Id BufferAtomicU32BoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) {
if (Sirit::ValidId(buffer_size)) {
// Bounds checking enabled, wrap in a conditional branch to make sure that
// the atomic is not mistakenly executed when the index is out of bounds.
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size);
const Id ib_label = ctx.OpLabel();
const Id oob_label = ctx.OpLabel();
const Id end_label = ctx.OpLabel();
ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
ctx.OpBranchConditional(in_bounds, ib_label, oob_label);
ctx.AddLabel(ib_label);
const Id ib_result = emit_func();
ctx.OpBranch(end_label);
ctx.AddLabel(oob_label);
const Id oob_result = ctx.u32_zero_value;
ctx.OpBranch(end_label);
ctx.AddLabel(end_label);
return ctx.OpPhi(ctx.U32[1], ib_result, ib_label, oob_result, oob_label);
}
// Bounds checking not enabled, just perform the atomic operation.
return emit_func();
Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const Id shift_id{ctx.ConstU32(3U)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics, value);
});
}
Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
@ -63,11 +63,57 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
const auto [scope, semantics]{AtomicArgs(ctx)};
return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] {
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value);
});
}
Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
const auto& buffer = ctx.buffers[handle];
if (Sirit::ValidId(buffer.offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
}
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics);
});
}
Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
Id cmp_value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) {
const auto& buffer = ctx.buffers[handle];
if (Sirit::ValidId(buffer.offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
}
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value);
});
}
Id BufferAtomicU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const auto& buffer = ctx.buffers[handle];
if (Sirit::ValidId(buffer.offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
}
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u));
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U64];
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
const auto [scope, semantics]{AtomicArgs(ctx)};
return AccessBoundsCheck<64>(ctx, index, buffer.size_qwords, [&] {
return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value);
});
}
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const auto& texture = ctx.images[handle & 0xFFFF];
@ -89,6 +135,10 @@ Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value) {
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicIAdd);
}
Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value) {
return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicIAdd);
}
Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value) {
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMax);
}
@ -121,18 +171,26 @@ Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value) {
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub);
}
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset) {
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement);
Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset) {
return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement);
}
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset) {
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement);
Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset) {
return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement);
}
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd);
}
Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd);
}
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicISub);
}
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin);
}
@ -149,14 +207,12 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
}
Id EmitBufferAtomicInc32(EmitContext&, IR::Inst*, u32, Id, Id) {
// TODO
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicInc32);
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
}
Id EmitBufferAtomicDec32(EmitContext&, IR::Inst*, u32, Id, Id) {
// TODO
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicDec32);
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIDecrement);
}
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
@ -175,6 +231,12 @@ Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicExchange);
}
Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
Id cmp_value) {
return BufferAtomicU32CmpSwap(ctx, inst, handle, address, value, cmp_value,
&Sirit::Module::OpAtomicCompareExchange);
}
Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value) {
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicIAdd);
}

View file

@ -0,0 +1,90 @@
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
namespace Shader::Backend::SPIRV {
template <u32 bit_size, u32 num_components = 1, bool is_float = false>
std::tuple<Id, Id> ResolveTypeAndZero(EmitContext& ctx) {
Id result_type{};
Id zero_value{};
if constexpr (bit_size == 64 && num_components == 1 && !is_float) {
result_type = ctx.U64;
zero_value = ctx.u64_zero_value;
} else if constexpr (bit_size == 32) {
if (is_float) {
result_type = ctx.F32[num_components];
zero_value = ctx.f32_zero_value;
} else {
result_type = ctx.U32[num_components];
zero_value = ctx.u32_zero_value;
}
} else if constexpr (bit_size == 16 && num_components == 1 && !is_float) {
result_type = ctx.U16;
zero_value = ctx.u16_zero_value;
} else if constexpr (bit_size == 8 && num_components == 1 && !is_float) {
result_type = ctx.U8;
zero_value = ctx.u8_zero_value;
} else {
static_assert(false, "Type not supported.");
}
if (num_components > 1) {
std::array<Id, num_components> zero_ids;
zero_ids.fill(zero_value);
zero_value = ctx.ConstantComposite(result_type, zero_ids);
}
return {result_type, zero_value};
}
template <u32 bit_size, u32 num_components = 1, bool is_float = false>
auto AccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) {
if (Sirit::ValidId(buffer_size)) {
// Bounds checking enabled, wrap in a conditional branch to make sure that
// the atomic is not mistakenly executed when the index is out of bounds.
auto compare_index = index;
if (num_components > 1) {
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(num_components - 1));
}
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
const Id ib_label = ctx.OpLabel();
const Id end_label = ctx.OpLabel();
ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
ctx.OpBranchConditional(in_bounds, ib_label, end_label);
const auto last_label = ctx.last_label;
ctx.AddLabel(ib_label);
const auto ib_result = emit_func();
ctx.OpBranch(end_label);
ctx.AddLabel(end_label);
if (Sirit::ValidId(ib_result)) {
const auto [result_type, zero_value] =
ResolveTypeAndZero<bit_size, num_components, is_float>(ctx);
return ctx.OpPhi(result_type, ib_result, ib_label, zero_value, last_label);
} else {
return Id{0};
}
}
// Bounds checking not enabled, just perform the atomic operation.
return emit_func();
}
template <u32 bit_size, u32 num_components = 1, bool is_float = false>
static Id LoadAccessBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, Id result) {
if (Sirit::ValidId(buffer_size)) {
// Bounds checking enabled, wrap in a select.
auto compare_index = index;
if (num_components > 1) {
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(num_components - 1));
}
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
const auto [result_type, zero_value] =
ResolveTypeAndZero<bit_size, num_components, is_float>(ctx);
return ctx.OpSelect(result_type, in_bounds, result, zero_value);
}
// Bounds checking not enabled, just return the plain value.
return result;
}
} // namespace Shader::Backend::SPIRV

View file

@ -11,6 +11,8 @@
#include <magic_enum/magic_enum.hpp>
#include "emit_spirv_bounds.h"
namespace Shader::Backend::SPIRV {
namespace {
@ -239,8 +241,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) {
}
if (IR::IsParam(attr)) {
const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
const auto& param{ctx.input_params.at(index)};
const u32 param_index{u32(attr) - u32(IR::Attribute::Param0)};
const auto& param{ctx.input_params.at(param_index)};
if (param.buffer_handle >= 0) {
const auto step_rate = EmitReadStepRate(ctx, param.id.value);
const auto offset = ctx.OpIAdd(
@ -415,27 +417,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
ctx.OpStore(pointer, value);
}
template <u32 N>
static Id EmitLoadBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, Id result,
bool is_float) {
if (Sirit::ValidId(buffer_size)) {
// Bounds checking enabled, wrap in a select.
const auto result_type = is_float ? ctx.F32[N] : ctx.U32[N];
auto compare_index = index;
auto zero_value = is_float ? ctx.f32_zero_value : ctx.u32_zero_value;
if (N > 1) {
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1));
std::array<Id, N> zero_ids;
zero_ids.fill(zero_value);
zero_value = ctx.ConstantComposite(result_type, zero_ids);
}
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
return ctx.OpSelect(result_type, in_bounds, result, zero_value);
}
// Bounds checking not enabled, just return the plain value.
return result;
}
template <u32 N, PointerType alias>
static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto flags = inst->Flags<IR::BufferInstInfo>();
@ -454,8 +435,9 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a
const Id result_i = ctx.OpLoad(data_types[1], ptr_i);
if (!flags.typed) {
// Untyped loads have bounds checking per-component.
ids.push_back(EmitLoadBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords,
result_i, alias == PointerType::F32));
ids.push_back(LoadAccessBoundsCheck < 32, 1,
alias ==
PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i));
} else {
ids.push_back(result_i);
}
@ -464,8 +446,8 @@ 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);
if (flags.typed) {
// Typed loads have single bounds check for the whole load.
return EmitLoadBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, result,
alias == PointerType::F32);
return LoadAccessBoundsCheck < 32, N,
alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result);
}
return result;
}
@ -477,8 +459,8 @@ Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
}
const auto [id, pointer_type] = spv_buffer[PointerType::U8];
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, ptr))};
return EmitLoadBufferBoundsCheck<1>(ctx, address, spv_buffer.size, result, false);
const Id result{ctx.OpLoad(ctx.U8, ptr)};
return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result);
}
Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
@ -489,8 +471,8 @@ Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto [id, pointer_type] = spv_buffer[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, index)};
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, ptr))};
return EmitLoadBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, result, false);
const Id result{ctx.OpLoad(ctx.U16, ptr)};
return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result);
}
Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
@ -509,6 +491,18 @@ Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address)
return EmitLoadBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address);
}
Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset);
}
const auto [id, pointer_type] = spv_buffer[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, index)};
const Id result{ctx.OpLoad(ctx.U64, ptr)};
return LoadAccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, result);
}
Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
return EmitLoadBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address);
}
@ -529,29 +523,6 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr
UNREACHABLE_MSG("SPIR-V instruction");
}
template <u32 N>
void EmitStoreBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) {
if (Sirit::ValidId(buffer_size)) {
// Bounds checking enabled, wrap in a conditional branch.
auto compare_index = index;
if (N > 1) {
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1));
}
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
const Id in_bounds_label = ctx.OpLabel();
const Id merge_label = ctx.OpLabel();
ctx.OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone);
ctx.OpBranchConditional(in_bounds, in_bounds_label, merge_label);
ctx.AddLabel(in_bounds_label);
emit_func();
ctx.OpBranch(merge_label);
ctx.AddLabel(merge_label);
return;
}
// Bounds checking not enabled, just perform the store.
emit_func();
}
template <u32 N, PointerType alias>
static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
Id value) {
@ -569,19 +540,25 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I
const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(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);
auto store_i = [&]() { ctx.OpStore(ptr_i, value_i); };
auto store_i = [&] {
ctx.OpStore(ptr_i, value_i);
return Id{};
};
if (!flags.typed) {
// Untyped stores have bounds checking per-component.
EmitStoreBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, store_i);
AccessBoundsCheck<32, 1, alias == PointerType::F32>(
ctx, index_i, spv_buffer.size_dwords, store_i);
} else {
store_i();
}
}
return Id{};
};
if (flags.typed) {
// Typed stores have single bounds check for the whole store.
EmitStoreBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, store);
AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords,
store);
} else {
store();
}
@ -594,8 +571,10 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v
}
const auto [id, pointer_type] = spv_buffer[PointerType::U8];
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id result{ctx.OpUConvert(ctx.U8, value)};
EmitStoreBufferBoundsCheck<1>(ctx, address, spv_buffer.size, [&] { ctx.OpStore(ptr, result); });
AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] {
ctx.OpStore(ptr, value);
return Id{};
});
}
void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
@ -606,9 +585,10 @@ void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id
const auto [id, pointer_type] = spv_buffer[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, index)};
const Id result{ctx.OpUConvert(ctx.U16, value)};
EmitStoreBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts,
[&] { ctx.OpStore(ptr, result); });
AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] {
ctx.OpStore(ptr, value);
return Id{};
});
}
void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
@ -627,6 +607,20 @@ void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
EmitStoreBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address, value);
}
void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
const auto& spv_buffer = ctx.buffers[handle];
if (Sirit::ValidId(spv_buffer.offset)) {
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset);
}
const auto [id, pointer_type] = spv_buffer[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, index)};
AccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, [&] {
ctx.OpStore(ptr, value);
return Id{};
});
}
void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
EmitStoreBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address, value);
}

View file

@ -263,4 +263,12 @@ Id EmitConvertU32U16(EmitContext& ctx, Id value) {
return ctx.OpUConvert(ctx.U32[1], value);
}
Id EmitConvertU8U32(EmitContext& ctx, Id value) {
return ctx.OpUConvert(ctx.U8, value);
}
Id EmitConvertU32U8(EmitContext& ctx, Id value) {
return ctx.OpUConvert(ctx.U32[1], value);
}
} // namespace Shader::Backend::SPIRV

View file

@ -69,6 +69,7 @@ Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
@ -80,22 +81,27 @@ void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address
void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
Id cmp_value);
Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index);
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp);
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 comp);
@ -118,11 +124,14 @@ Id EmitUndefU8(EmitContext& ctx);
Id EmitUndefU16(EmitContext& ctx);
Id EmitUndefU32(EmitContext& ctx);
Id EmitUndefU64(EmitContext& ctx);
Id EmitLoadSharedU16(EmitContext& ctx, Id offset);
Id EmitLoadSharedU32(EmitContext& ctx, Id offset);
Id EmitLoadSharedU64(EmitContext& ctx, Id offset);
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value);
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value);
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicUMin32(EmitContext& ctx, Id offset, Id value);
@ -130,8 +139,8 @@ Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value);
Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2);
@ -372,6 +381,7 @@ Id EmitBitCount64(EmitContext& ctx, Id value);
Id EmitBitwiseNot32(EmitContext& ctx, Id value);
Id EmitFindSMsb32(EmitContext& ctx, Id value);
Id EmitFindUMsb32(EmitContext& ctx, Id value);
Id EmitFindUMsb64(EmitContext& ctx, Id value);
Id EmitFindILsb32(EmitContext& ctx, Id value);
Id EmitFindILsb64(EmitContext& ctx, Id value);
Id EmitSMin32(EmitContext& ctx, Id a, Id b);
@ -454,6 +464,8 @@ Id EmitConvertF64U32(EmitContext& ctx, Id value);
Id EmitConvertF64U64(EmitContext& ctx, Id value);
Id EmitConvertU16U32(EmitContext& ctx, Id value);
Id EmitConvertU32U16(EmitContext& ctx, Id value);
Id EmitConvertU8U32(EmitContext& ctx, Id value);
Id EmitConvertU32U8(EmitContext& ctx, Id value);
Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
Id address3, Id address4);

View file

@ -229,6 +229,20 @@ Id EmitFindUMsb32(EmitContext& ctx, Id value) {
return ctx.OpFindUMsb(ctx.U32[1], value);
}
Id EmitFindUMsb64(EmitContext& ctx, Id value) {
// Vulkan restricts some bitwise operations to 32-bit only, so decompose into
// two 32-bit values and select the correct result.
const Id unpacked{ctx.OpBitcast(ctx.U32[2], value)};
const Id hi{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 1U)};
const Id lo{ctx.OpCompositeExtract(ctx.U32[1], unpacked, 0U)};
const Id hi_msb{ctx.OpFindUMsb(ctx.U32[1], hi)};
const Id lo_msb{ctx.OpFindUMsb(ctx.U32[1], lo)};
const Id found_hi{ctx.OpINotEqual(ctx.U1[1], hi_msb, ctx.ConstU32(u32(-1)))};
const Id shifted_hi{ctx.OpIAdd(ctx.U32[1], hi_msb, ctx.ConstU32(32u))};
// value == 0 case is checked in IREmitter
return ctx.OpSelect(ctx.U32[1], found_hi, shifted_hi, lo_msb);
}
Id EmitFindILsb32(EmitContext& ctx, Id value) {
return ctx.OpFindILsb(ctx.U32[1], value);
}

View file

@ -1,43 +1,86 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/div_ceil.h"
#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h"
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
namespace Shader::Backend::SPIRV {
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
const Id shift_id{ctx.ConstU32(1U)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)};
return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer =
ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index);
return ctx.OpLoad(ctx.U16, pointer);
});
}
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
const Id shift_id{ctx.ConstU32(2U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index);
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer =
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index);
return ctx.OpLoad(ctx.U32[1], pointer);
});
}
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
const Id shift_id{ctx.ConstU32(2U)};
const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id next_index{ctx.OpIAdd(ctx.U32[1], base_index, ctx.ConstU32(1U))};
const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, base_index)};
const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_index)};
return ctx.OpCompositeConstruct(ctx.U32[2], ctx.OpLoad(ctx.U32[1], lhs_pointer),
ctx.OpLoad(ctx.U32[1], rhs_pointer));
const Id shift_id{ctx.ConstU32(3U)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
return ctx.OpLoad(ctx.U64, pointer);
});
}
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
const Id shift{ctx.ConstU32(1U)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)};
AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer =
ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index);
ctx.OpStore(pointer, value);
return Id{0};
});
}
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
const Id shift{ctx.ConstU32(2U)};
const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)};
const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset);
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer =
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index);
ctx.OpStore(pointer, value);
return Id{0};
});
}
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
const Id shift{ctx.ConstU32(2U)};
const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)};
const Id next_offset{ctx.OpIAdd(ctx.U32[1], word_offset, ctx.ConstU32(1U))};
const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset)};
const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_offset)};
ctx.OpStore(lhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 0U));
ctx.OpStore(rhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 1U));
const Id shift{ctx.ConstU32(3U)};
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
ctx.OpStore(pointer, value);
return Id{0};
});
}
} // namespace Shader::Backend::SPIRV

View file

@ -146,6 +146,7 @@ void EmitContext::DefineArithmeticTypes() {
false_value = ConstantFalse(U1[1]);
u8_one_value = Constant(U8, 1U);
u8_zero_value = Constant(U8, 0U);
u16_zero_value = Constant(U16, 0U);
u32_one_value = ConstU32(1U);
u32_zero_value = ConstU32(0U);
f32_zero_value = ConstF32(0.0f);
@ -285,6 +286,8 @@ void EmitContext::DefineBufferProperties() {
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));
}
}
}
@ -296,8 +299,7 @@ void EmitContext::DefineInterpolatedAttribs() {
// Iterate all input attributes, load them and manually interpolate.
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
const auto& input = runtime_info.fs_info.inputs[i];
const u32 semantic = input.param_index;
auto& params = input_params[semantic];
auto& params = input_params[i];
if (input.is_flat || params.is_loaded) {
continue;
}
@ -307,13 +309,15 @@ void EmitContext::DefineInterpolatedAttribs() {
const Id p2{OpCompositeExtract(F32[4], p_array, 2U)};
const Id p10{OpFSub(F32[4], p1, p0)};
const Id p20{OpFSub(F32[4], p2, p0)};
const Id bary_coord{OpLoad(F32[3], gl_bary_coord_id)};
const Id bary_coord{OpLoad(F32[3], IsLinear(info.interp_qualifiers[i])
? bary_coord_linear_id
: bary_coord_persp_id)};
const Id bary_coord_y{OpCompositeExtract(F32[1], bary_coord, 1)};
const Id bary_coord_z{OpCompositeExtract(F32[1], bary_coord, 2)};
const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)};
const Id p20_z{OpVectorTimesScalar(F32[4], p20, bary_coord_z)};
params.id = OpFAdd(F32[4], p0, OpFAdd(F32[4], p10_y, p20_z));
Name(params.id, fmt::format("fs_in_attr{}", semantic));
Name(params.id, fmt::format("fs_in_attr{}", i));
params.is_loaded = true;
}
}
@ -411,35 +415,47 @@ void EmitContext::DefineInputs() {
DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input);
}
if (profile.needs_manual_interpolation) {
gl_bary_coord_id =
if (info.has_perspective_interp) {
bary_coord_persp_id =
DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input);
}
if (info.has_linear_interp) {
bary_coord_linear_id = DefineVariable(F32[3], spv::BuiltIn::BaryCoordNoPerspKHR,
spv::StorageClass::Input);
}
}
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
const auto& input = runtime_info.fs_info.inputs[i];
const u32 semantic = input.param_index;
ASSERT(semantic < IR::NumParams);
if (input.IsDefault()) {
input_params[semantic] = {
MakeDefaultValue(*this, input.default_value), input_f32, F32[1], 4, false, true,
input_params[i] = {
.id = MakeDefaultValue(*this, input.default_value),
.pointer_type = input_f32,
.component_type = F32[1],
.num_components = 4,
.is_integer = false,
.is_loaded = true,
};
continue;
}
const IR::Attribute param{IR::Attribute::Param0 + input.param_index};
const IR::Attribute param{IR::Attribute::Param0 + i};
const u32 num_components = info.loads.NumComponents(param);
const Id type{F32[num_components]};
Id attr_id{};
if (profile.needs_manual_interpolation && !input.is_flat) {
attr_id = DefineInput(TypeArray(type, ConstU32(3U)), semantic);
attr_id = DefineInput(TypeArray(type, ConstU32(3U)), input.param_index);
Decorate(attr_id, spv::Decoration::PerVertexKHR);
Name(attr_id, fmt::format("fs_in_attr{}_p", semantic));
Name(attr_id, fmt::format("fs_in_attr{}_p", i));
} else {
attr_id = DefineInput(type, semantic);
Name(attr_id, fmt::format("fs_in_attr{}", semantic));
}
attr_id = DefineInput(type, input.param_index);
Name(attr_id, fmt::format("fs_in_attr{}", i));
if (input.is_flat) {
Decorate(attr_id, spv::Decoration::Flat);
} else if (IsLinear(info.interp_qualifiers[i])) {
Decorate(attr_id, spv::Decoration::NoPerspective);
}
input_params[semantic] =
}
input_params[i] =
GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false);
}
break;
@ -634,7 +650,8 @@ void EmitContext::DefineOutputs() {
}
break;
}
case LogicalStage::Fragment:
case LogicalStage::Fragment: {
u32 num_render_targets = 0;
for (u32 i = 0; i < IR::NumRenderTargets; i++) {
const IR::Attribute mrt{IR::Attribute::RenderTarget0 + i};
if (!info.stores.GetAny(mrt)) {
@ -643,11 +660,21 @@ void EmitContext::DefineOutputs() {
const u32 num_components = info.stores.NumComponents(mrt);
const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format};
const Id type{GetAttributeType(*this, num_format)[num_components]};
const Id id{DefineOutput(type, i)};
Id id;
if (runtime_info.fs_info.dual_source_blending) {
id = DefineOutput(type, 0);
Decorate(id, spv::Decoration::Index, i);
} else {
id = DefineOutput(type, i);
}
Name(id, fmt::format("frag_color{}", i));
frag_outputs[i] = GetAttributeInfo(num_format, id, num_components, true);
++num_render_targets;
}
ASSERT_MSG(!runtime_info.fs_info.dual_source_blending || num_render_targets == 2,
"Dual source blending enabled, there must be exactly two MRT exports");
break;
}
case LogicalStage::Geometry: {
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
@ -952,18 +979,46 @@ void EmitContext::DefineImagesAndSamplers() {
}
void EmitContext::DefineSharedMemory() {
if (!info.uses_shared) {
const auto num_types = std::popcount(static_cast<u32>(info.shared_types));
if (num_types == 0) {
return;
}
ASSERT(info.stage == Stage::Compute);
const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size;
const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)};
const Id type{TypeArray(U32[1], ConstU32(num_elements))};
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type);
shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]);
shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup);
Name(shared_memory_u32, "shared_mem");
interfaces.push_back(shared_memory_u32);
const auto make_type = [&](IR::Type type, Id element_type, u32 element_size,
std::string_view name) {
if (False(info.shared_types & type)) {
// Skip unused shared memory types.
return std::make_tuple(Id{}, Id{}, Id{});
}
const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)};
const Id array_type{TypeArray(element_type, ConstU32(num_elements))};
Decorate(array_type, spv::Decoration::ArrayStride, element_size);
const Id struct_type{TypeStruct(array_type)};
MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u);
const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type);
const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type);
const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup);
Name(variable, name);
interfaces.push_back(variable);
if (num_types > 1) {
Decorate(struct_type, spv::Decoration::Block);
Decorate(variable, spv::Decoration::Aliased);
}
return std::make_tuple(variable, element_pointer, pointer);
};
std::tie(shared_memory_u16, shared_u16, shared_memory_u16_type) =
make_type(IR::Type::U16, U16, 2u, "shared_mem_u16");
std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) =
make_type(IR::Type::U32, U32[1], 4u, "shared_mem_u32");
std::tie(shared_memory_u64, shared_u64, shared_memory_u64_type) =
make_type(IR::Type::U64, U64, 8u, "shared_mem_u64");
}
Id EmitContext::DefineFloat32ToUfloatM5(u32 mantissa_bits, const std::string_view name) {

View file

@ -235,17 +235,16 @@ public:
Id false_value{};
Id u8_one_value{};
Id u8_zero_value{};
Id u16_zero_value{};
Id u32_one_value{};
Id u32_zero_value{};
Id f32_zero_value{};
Id u64_one_value{};
Id u64_zero_value{};
Id shared_u8{};
Id shared_u16{};
Id shared_u32{};
Id shared_u32x2{};
Id shared_u32x4{};
Id shared_u64{};
Id input_u32{};
Id input_f32{};
@ -285,16 +284,16 @@ public:
Id image_u32{};
Id image_f32{};
Id shared_memory_u8{};
Id shared_memory_u16{};
Id shared_memory_u32{};
Id shared_memory_u32x2{};
Id shared_memory_u32x4{};
Id shared_memory_u64{};
Id shared_memory_u16_type{};
Id shared_memory_u32_type{};
Id shared_memory_u64_type{};
Id interpolate_func{};
Id gl_bary_coord_id{};
Id bary_coord_persp_id{};
Id bary_coord_linear_id{};
struct TextureDefinition {
const VectorIds* data_types;
@ -320,6 +319,7 @@ public:
Id size;
Id size_shorts;
Id size_dwords;
Id size_qwords;
std::array<BufferSpv, u32(PointerType::NumAlias)> aliases;
const BufferSpv& operator[](PointerType alias) const {

View file

@ -67,6 +67,9 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
if (last_attr != IR::Attribute::Position0) {
data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1;
const auto it = data.attr_map.begin();
const u32 comp_stride = std::next(it)->first - it->first;
data.output_vertices = comp_stride / 64;
}
return data;

View file

@ -3,8 +3,8 @@
#pragma once
#include <map>
#include <span>
#include <unordered_map>
#include "common/types.h"
#include "shader_recompiler/ir/attribute.h"
@ -12,8 +12,9 @@
namespace Shader {
struct CopyShaderData {
std::unordered_map<u32, std::pair<Shader::IR::Attribute, u32>> attr_map;
std::map<u32, std::pair<Shader::IR::Attribute, u32>> attr_map;
u32 num_attrs{0};
u32 output_vertices{0};
};
CopyShaderData ParseCopyShader(std::span<const u32> code);

View file

@ -605,11 +605,12 @@ public:
Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_},
runtime_info{runtime_info_}, profile{profile_} {
runtime_info{runtime_info_}, profile{profile_},
translator{info_, runtime_info_, profile_} {
Visit(root_stmt, nullptr, nullptr);
IR::Block& first_block{*syntax_list.front().data.block};
Translator{&first_block, info, runtime_info, profile}.EmitPrologue();
IR::Block* first_block = syntax_list.front().data.block;
translator.EmitPrologue(first_block);
}
private:
@ -637,8 +638,8 @@ private:
current_block->has_multiple_predecessors = stmt.block->num_predecessors > 1;
const u32 start = stmt.block->begin_index;
const u32 size = stmt.block->end_index - start + 1;
Translate(current_block, stmt.block->begin, inst_list.subspan(start, size),
info, runtime_info, profile);
translator.Translate(current_block, stmt.block->begin,
inst_list.subspan(start, size));
}
break;
}
@ -820,6 +821,7 @@ private:
Info& info;
const RuntimeInfo& runtime_info;
const Profile& profile;
Translator translator;
};
} // Anonymous namespace

View file

@ -13,6 +13,8 @@ void Translator::EmitDataShare(const GcnInst& inst) {
// DS
case Opcode::DS_ADD_U32:
return DS_ADD_U32(inst, false);
case Opcode::DS_ADD_U64:
return DS_ADD_U64(inst, false);
case Opcode::DS_SUB_U32:
return DS_SUB_U32(inst, false);
case Opcode::DS_INC_U32:
@ -61,10 +63,14 @@ void Translator::EmitDataShare(const GcnInst& inst) {
return DS_READ(32, false, true, false, inst);
case Opcode::DS_READ2ST64_B32:
return DS_READ(32, false, true, true, inst);
case Opcode::DS_READ_U16:
return DS_READ(16, false, false, false, inst);
case Opcode::DS_CONSUME:
return DS_CONSUME(inst);
case Opcode::DS_APPEND:
return DS_APPEND(inst);
case Opcode::DS_WRITE_B16:
return DS_WRITE(16, false, false, false, inst);
case Opcode::DS_WRITE_B64:
return DS_WRITE(64, false, false, false, inst);
case Opcode::DS_WRITE2_B64:
@ -123,6 +129,18 @@ void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) {
}
}
void Translator::DS_ADD_U64(const GcnInst& inst, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U64 data{GetSrc64(inst.src[1])};
const IR::U32 offset =
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data);
if (rtn) {
SetDst64(inst.dst[0], IR::U64{original_val});
}
}
void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])};
@ -198,29 +216,38 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid
if (is_pair) {
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
if (bit_size == 64) {
ir.WriteShared(64,
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0),
ir.GetVectorReg(data0 + 1))),
addr0);
} else if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
} else if (bit_size == 16) {
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
}
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
} else {
ir.WriteShared(
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
if (bit_size == 64) {
ir.WriteShared(64,
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1),
ir.GetVectorReg(data1 + 1))),
addr1);
} else if (bit_size == 32) {
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
} else if (bit_size == 16) {
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1);
}
} else if (bit_size == 64) {
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
if (bit_size == 64) {
const IR::Value data =
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
ir.WriteShared(bit_size, data, addr0);
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0);
} else if (bit_size == 32) {
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
} else if (bit_size == 16) {
ir.WriteShared(bit_size, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
}
}
}
@ -241,7 +268,7 @@ void Translator::DS_INC_U32(const GcnInst& inst, bool rtn) {
const IR::U32 offset =
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIIncrement(addr_offset);
const IR::Value original_val = ir.SharedAtomicInc(addr_offset);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
@ -252,7 +279,7 @@ void Translator::DS_DEC_U32(const GcnInst& inst, bool rtn) {
const IR::U32 offset =
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset);
const IR::Value original_val = ir.SharedAtomicIDecrement(addr_offset);
const IR::Value original_val = ir.SharedAtomicDec(addr_offset);
if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val});
}
@ -286,29 +313,38 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0);
if (bit_size == 32) {
if (bit_size == 64) {
const auto vector = ir.UnpackUint2x32(IR::U64{data0});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)});
} else if (bit_size == 32) {
ir.SetVectorReg(dst_reg++, IR::U32{data0});
} else {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)});
} else if (bit_size == 16) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})});
}
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1);
if (bit_size == 32) {
if (bit_size == 64) {
const auto vector = ir.UnpackUint2x32(IR::U64{data1});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)});
} else if (bit_size == 32) {
ir.SetVectorReg(dst_reg++, IR::U32{data1});
} else {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)});
} else if (bit_size == 16) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})});
}
} else if (bit_size == 64) {
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0);
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)});
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)});
} else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)};
ir.SetVectorReg(dst_reg, data);
if (bit_size == 64) {
const auto vector = ir.UnpackUint2x32(IR::U64{data});
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)});
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)});
} else if (bit_size == 32) {
ir.SetVectorReg(dst_reg, IR::U32{data});
} else if (bit_size == 16) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})});
}
}
}

View file

@ -26,8 +26,11 @@ void Translator::ExportMrtValue(IR::Attribute attribute, u32 comp, const IR::F32
}
void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR::U32& value) {
const u32 color_buffer_idx =
u32 color_buffer_idx =
static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::RenderTarget0);
if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) {
color_buffer_idx = 0;
}
const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
AmdGpu::NumberFormat num_format;
@ -68,8 +71,11 @@ void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR:
}
void Translator::ExportMrtUncompressed(IR::Attribute attribute, u32 comp, const IR::F32& value) {
const u32 color_buffer_idx =
u32 color_buffer_idx =
static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::RenderTarget0);
if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) {
color_buffer_idx = 0;
}
const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
const auto swizzled_comp = SwizzleMrtComponent(color_buffer, comp);

View file

@ -114,6 +114,8 @@ void Translator::EmitScalarAlu(const GcnInst& inst) {
return S_FF1_I32_B64(inst);
case Opcode::S_FLBIT_I32_B32:
return S_FLBIT_I32_B32(inst);
case Opcode::S_FLBIT_I32_B64:
return S_FLBIT_I32_B64(inst);
case Opcode::S_BITSET0_B32:
return S_BITSET_B32(inst, 0);
case Opcode::S_BITSET1_B32:
@ -686,6 +688,17 @@ void Translator::S_FLBIT_I32_B32(const GcnInst& inst) {
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))});
}
void Translator::S_FLBIT_I32_B64(const GcnInst& inst) {
const IR::U64 src0{GetSrc64(inst.src[0])};
// Gcn wants the MSB position counting from the left, but SPIR-V counts from the rightmost (LSB)
// position
const IR::U32 msb_pos = ir.FindUMsb(src0);
const IR::U32 pos_from_left = ir.ISub(ir.Imm32(63), msb_pos);
// Select 0xFFFFFFFF if src0 was 0
const IR::U1 cond = ir.INotEqual(src0, ir.Imm64(u64(0u)));
SetDst(inst.dst[0], IR::U32{ir.Select(cond, pos_from_left, ir.Imm32(~0U))});
}
void Translator::S_BITSET_B32(const GcnInst& inst, u32 bit_value) {
const IR::U32 old_value{GetSrc(inst.dst[0])};
const IR::U32 offset{ir.BitFieldExtract(GetSrc(inst.src[0]), ir.Imm32(0U), ir.Imm32(5U))};

View file

@ -21,16 +21,60 @@
namespace Shader::Gcn {
static u32 next_vgpr_num;
static std::unordered_map<u32, IR::VectorReg> vgpr_map;
Translator::Translator(IR::Block* block_, Info& info_, const RuntimeInfo& runtime_info_,
const Profile& profile_)
: ir{*block_, block_->begin()}, info{info_}, runtime_info{runtime_info_}, profile{profile_} {
next_vgpr_num = vgpr_map.empty() ? runtime_info.num_allocated_vgprs : next_vgpr_num;
Translator::Translator(Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
: info{info_}, runtime_info{runtime_info_}, profile{profile_},
next_vgpr_num{runtime_info.num_allocated_vgprs} {
if (info.l_stage == LogicalStage::Fragment) {
dst_frag_vreg = GatherInterpQualifiers();
}
}
void Translator::EmitPrologue() {
IR::VectorReg Translator::GatherInterpQualifiers() {
u32 dst_vreg{};
if (runtime_info.fs_info.addr_flags.persp_sample_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // J
info.has_perspective_interp = true;
}
if (runtime_info.fs_info.addr_flags.persp_center_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // J
info.has_perspective_interp = true;
}
if (runtime_info.fs_info.addr_flags.persp_centroid_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // J
info.has_perspective_interp = true;
}
if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) {
++dst_vreg; // I/W
++dst_vreg; // J/W
++dst_vreg; // 1/W
}
if (runtime_info.fs_info.addr_flags.linear_sample_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // J
info.has_linear_interp = true;
}
if (runtime_info.fs_info.addr_flags.linear_center_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // J
info.has_linear_interp = true;
}
if (runtime_info.fs_info.addr_flags.linear_centroid_ena) {
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // I
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // J
info.has_linear_interp = true;
}
if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) {
++dst_vreg;
}
return IR::VectorReg(dst_vreg);
}
void Translator::EmitPrologue(IR::Block* first_block) {
ir = IR::IREmitter(*first_block, first_block->begin());
ir.Prologue();
ir.SetExec(ir.Imm1(true));
@ -60,39 +104,7 @@ void Translator::EmitPrologue() {
}
break;
case LogicalStage::Fragment:
dst_vreg = IR::VectorReg::V0;
if (runtime_info.fs_info.addr_flags.persp_sample_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.persp_center_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.persp_centroid_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) {
++dst_vreg; // I/W
++dst_vreg; // J/W
++dst_vreg; // 1/W
}
if (runtime_info.fs_info.addr_flags.linear_sample_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.linear_center_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.linear_centroid_ena) {
++dst_vreg; // I
++dst_vreg; // J
}
if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) {
++dst_vreg;
}
dst_vreg = dst_frag_vreg;
if (runtime_info.fs_info.addr_flags.pos_x_float_ena) {
if (runtime_info.fs_info.en_flags.pos_x_float_ena) {
ir.SetVectorReg(dst_vreg++, ir.GetAttribute(IR::Attribute::FragCoord, 0));
@ -543,6 +555,26 @@ void Translator::LogMissingOpcode(const GcnInst& inst) {
info.translation_failed = true;
}
void Translator::Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list) {
if (inst_list.empty()) {
return;
}
ir = IR::IREmitter{*block, block->begin()};
for (const auto& inst : inst_list) {
pc += inst.length;
// Special case for emitting fetch shader.
if (inst.opcode == Opcode::S_SWAPPC_B64) {
ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export ||
info.stage == Stage::Local);
EmitFetch(inst);
continue;
}
TranslateInstruction(inst, pc);
}
}
void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) {
// Emit instructions for each category.
switch (inst.category) {
@ -577,25 +609,4 @@ void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) {
}
}
void Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list, Info& info,
const RuntimeInfo& runtime_info, const Profile& profile) {
if (inst_list.empty()) {
return;
}
Translator translator{block, info, runtime_info, profile};
for (const auto& inst : inst_list) {
pc += inst.length;
// Special case for emitting fetch shader.
if (inst.opcode == Opcode::S_SWAPPC_B64) {
ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export ||
info.stage == Stage::Local);
translator.EmitFetch(inst);
continue;
}
translator.TranslateInstruction(inst, pc);
}
}
} // namespace Shader::Gcn

Some files were not shown because too many files have changed in this diff Show more