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 }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with: with:
append-timestamp: false append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} 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 - 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 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 - name: Setup Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: 6.9.0 version: 6.9.1
host: windows host: windows
target: desktop target: desktop
arch: win64_msvc2022_64 arch: win64_msvc2022_64
@ -130,18 +125,13 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build cache-name: ${{ runner.os }}-qt-cache-cmake-build
with: with:
append-timestamp: false append-timestamp: false
key: ${{ env.cache-name }}-${{ hashFiles('**/CMakeLists.txt', 'cmake/**') }} 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 - 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 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 }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{runner.os}}-sdl-cache-cmake-build cache-name: ${{runner.os}}-sdl-cache-cmake-build
with: with:
@ -228,7 +218,7 @@ jobs:
- name: Setup Qt - name: Setup Qt
uses: jurplel/install-qt-action@v4 uses: jurplel/install-qt-action@v4
with: with:
version: 6.9.0 version: 6.9.1
host: mac host: mac
target: desktop target: desktop
arch: clang_64 arch: clang_64
@ -247,7 +237,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{runner.os}}-qt-cache-cmake-build cache-name: ${{runner.os}}-qt-cache-cmake-build
with: with:
@ -301,7 +291,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-cache-cmake-build cache-name: ${{ runner.os }}-sdl-cache-cmake-build
with: with:
@ -362,7 +352,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-cache-cmake-build cache-name: ${{ runner.os }}-qt-cache-cmake-build
with: with:
@ -409,7 +399,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build cache-name: ${{ runner.os }}-sdl-gcc-cache-cmake-build
with: with:
@ -445,7 +435,7 @@ jobs:
${{ env.cache-name }}- ${{ env.cache-name }}-
- name: Cache CMake Build - name: Cache CMake Build
uses: hendrikmuhs/ccache-action@v1.2.17 uses: hendrikmuhs/ccache-action@v1.2.18
env: env:
cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build cache-name: ${{ runner.os }}-qt-gcc-cache-cmake-build
with: with:
@ -494,7 +484,7 @@ jobs:
with: with:
token: ${{ secrets.SHADPS4_TOKEN_REPO }} token: ${{ secrets.SHADPS4_TOKEN_REPO }}
name: "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}" 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 draft: false
prerelease: true 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 }})" 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 # Check if release already exists and get ID
release_id=$(curl -s -H "Authorization: token $GITHUB_TOKEN" \ 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 if [[ "$release_id" == "null" ]]; then
echo "Creating release in $REPO for $filename" echo "Creating release in $REPO for $filename"
release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \ release_id=$(curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github.v3+json" \ -H "Accept: application/vnd.github.v3+json" \
-d '{ -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 }}", "name": "Pre-release-shadPS4-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}",
"draft": false, "draft": false,
"prerelease": true, "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/ring_access_elimination.cpp
src/shader_recompiler/ir/passes/shader_info_collection_pass.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_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/shared_memory_to_storage_pass.cpp
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
src/shader_recompiler/ir/abstract_syntax_list.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.cpp
src/video_core/texture_cache/tile_manager.h src/video_core/texture_cache/tile_manager.h
src/video_core/texture_cache/types.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/texture_cache/host_compatibility.h
src/video_core/page_manager.cpp src/video_core/page_manager.cpp
src/video_core/page_manager.h 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.h
src/qt_gui/settings_dialog.ui src/qt_gui/settings_dialog.ui
src/qt_gui/main.cpp 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} ${EMULATOR}
${RESOURCE_FILES} ${RESOURCE_FILES}
${TRANSLATIONS} ${TRANSLATIONS}
@ -1124,6 +1130,10 @@ if (APPLE)
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d") set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}") 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}) 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() else()
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path") set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}) 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_SRC ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK/MoltenVK/icd/MoltenVK_icd.json)
set(MVK_ICD_DST ${MVK_DST}/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( add_custom_command(
OUTPUT ${MVK_ICD_DST} OUTPUT ${MVK_ICD_DST}
DEPENDS ${MVK_ICD_SRC} ${MVK_DST} DEPENDS ${MVK_ICD_SRC} ${MVK_DST}
@ -1151,17 +1158,13 @@ if (APPLE)
if (ARCHITECTURE STREQUAL "x86_64") if (ARCHITECTURE STREQUAL "x86_64")
# Reserve system-managed memory space. # 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() endif()
# Replacement for std::chrono::time_zone # Replacement for std::chrono::time_zone
target_link_libraries(shadps4 PRIVATE date::date-tz) target_link_libraries(shadps4 PRIVATE date::date-tz)
endif() endif()
if (NOT ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE SDL3::SDL3)
endif()
if (ENABLE_QT_GUI) if (ENABLE_QT_GUI)
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia) target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
add_definitions(-DENABLE_QT_GUI) 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++. **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 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 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/).\ 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 # Firmware files
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\ 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. The following firmware modules are supported and must be placed in shadPS4's `sys_modules` folder.
<div align="center"> <div align="center">
@ -138,8 +138,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
</div> </div>
> [!Caution] > [!Caution]
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\ > 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**.
@ -148,7 +147,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
- [**georgemoralis**](https://github.com/georgemoralis) - [**georgemoralis**](https://github.com/georgemoralis)
- [**psucien**](https://github.com/psucien) - [**psucien**](https://github.com/psucien)
- [**viniciuslrangel**](https://github.com/viniciuslrangel) - [**viniciuslrangel**](https://github.com/viniciuslrangel)
- [**roamic**](https://github.com/vladmikhalin) - [**roamic**](https://github.com/roamic)
- [**squidbus**](https://github.com/squidbus) - [**squidbus**](https://github.com/squidbus)
- [**frodo**](https://github.com/baggins183) - [**frodo**](https://github.com/baggins183)
- [**Stephen Miller**](https://github.com/StevenMiller123) - [**Stephen Miller**](https://github.com/StevenMiller123)
@ -158,7 +157,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
# Contributing # 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 :) Open a PR and we'll check it :)
# Translations # Translations

View file

@ -25,7 +25,7 @@ sudo apt install build-essential clang git cmake libasound2-dev \
```bash ```bash
sudo dnf install clang git cmake libatomic alsa-lib-devel \ 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 \ openssl-devel libevdev-devel libudev-devel libXext-devel \
qt6-qtbase-devel qt6-qtbase-private-devel \ qt6-qtbase-devel qt6-qtbase-private-devel \
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-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 isNeo = false;
static bool isDevKit = false; static bool isDevKit = false;
static bool playBGM = false;
static bool isTrophyPopupDisabled = false; static bool isTrophyPopupDisabled = false;
static int BGMvolume = 50;
static bool enableDiscordRPC = false; static bool enableDiscordRPC = false;
static u32 screenWidth = 1280; static u32 screenWidth = 1280;
static u32 screenHeight = 720; 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 logFilter;
static std::string logType = "sync"; static std::string logType = "sync";
static std::string userName = "shadPS4"; static std::string userName = "shadPS4";
static std::string updateChannel;
static std::string chooseHomeTab; static std::string chooseHomeTab;
static std::string backButtonBehavior = "left"; static std::string backButtonBehavior = "left";
static bool useSpecialPad = false; static bool useSpecialPad = false;
@ -52,8 +49,6 @@ static bool isMotionControlsEnabled = true;
static bool isDebugDump = false; static bool isDebugDump = false;
static bool isShaderDebug = false; static bool isShaderDebug = false;
static bool isShowSplash = false; static bool isShowSplash = false;
static bool isAutoUpdate = false;
static bool isAlwaysShowChangelog = false;
static std::string isSideTrophy = "right"; static std::string isSideTrophy = "right";
static bool isNullGpu = false; static bool isNullGpu = false;
static bool shouldCopyGPUBuffers = false; static bool shouldCopyGPUBuffers = false;
@ -86,27 +81,13 @@ static std::vector<GameInstallDir> settings_install_dirs = {};
std::vector<bool> install_dirs_enabled = {}; std::vector<bool> install_dirs_enabled = {};
std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path settings_addon_install_dir = {};
std::filesystem::path save_data_path = {}; std::filesystem::path save_data_path = {};
u32 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 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_elf_viewer;
std::vector<std::string> m_recent_files; std::vector<std::string> m_recent_files;
std::string emulator_language = "en_US"; std::string emulator_language = "en_US";
static int backgroundImageOpacity = 50;
static bool showBackgroundImage = true;
static bool isFullscreen = false; static bool isFullscreen = false;
static std::string fullscreenMode = "Windowed"; static std::string fullscreenMode = "Windowed";
static bool isHDRAllowed = false; static bool isHDRAllowed = false;
static bool showLabelsUnderIcons = true;
// Language // Language
u32 m_language = 1; // english u32 m_language = 1; // english
@ -176,14 +157,6 @@ bool getIsFullscreen() {
return isFullscreen; return isFullscreen;
} }
bool getShowLabelsUnderIcons() {
return showLabelsUnderIcons;
}
bool setShowLabelsUnderIcons() {
return false;
}
std::string getFullscreenMode() { std::string getFullscreenMode() {
return fullscreenMode; return fullscreenMode;
} }
@ -192,14 +165,6 @@ bool getisTrophyPopupDisabled() {
return isTrophyPopupDisabled; return isTrophyPopupDisabled;
} }
bool getPlayBGM() {
return playBGM;
}
int getBGMvolume() {
return BGMvolume;
}
bool getEnableDiscordRPC() { bool getEnableDiscordRPC() {
return enableDiscordRPC; return enableDiscordRPC;
} }
@ -240,10 +205,6 @@ std::string getUserName() {
return userName; return userName;
} }
std::string getUpdateChannel() {
return updateChannel;
}
std::string getChooseHomeTab() { std::string getChooseHomeTab() {
return chooseHomeTab; return chooseHomeTab;
} }
@ -276,14 +237,6 @@ bool showSplash() {
return isShowSplash; return isShowSplash;
} }
bool autoUpdate() {
return isAutoUpdate;
}
bool alwaysShowChangelog() {
return isAlwaysShowChangelog;
}
std::string sideTrophy() { std::string sideTrophy() {
return isSideTrophy; return isSideTrophy;
} }
@ -384,14 +337,6 @@ void setShowSplash(bool enable) {
isShowSplash = enable; isShowSplash = enable;
} }
void setAutoUpdate(bool enable) {
isAutoUpdate = enable;
}
void setAlwaysShowChangelog(bool enable) {
isAlwaysShowChangelog = enable;
}
void setSideTrophy(std::string side) { void setSideTrophy(std::string side) {
isSideTrophy = side; isSideTrophy = side;
} }
@ -431,9 +376,6 @@ void setVblankDiv(u32 value) {
void setIsFullscreen(bool enable) { void setIsFullscreen(bool enable) {
isFullscreen = enable; isFullscreen = enable;
} }
static void setShowLabelsUnderIcons(bool enable) {
showLabelsUnderIcons = enable;
}
void setFullscreenMode(std::string mode) { void setFullscreenMode(std::string mode) {
fullscreenMode = mode; fullscreenMode = mode;
@ -443,14 +385,6 @@ void setisTrophyPopupDisabled(bool disable) {
isTrophyPopupDisabled = disable; isTrophyPopupDisabled = disable;
} }
void setPlayBGM(bool enable) {
playBGM = enable;
}
void setBGMvolume(int volume) {
BGMvolume = volume;
}
void setEnableDiscordRPC(bool enable) { void setEnableDiscordRPC(bool enable) {
enableDiscordRPC = enable; enableDiscordRPC = enable;
} }
@ -490,9 +424,6 @@ void setUserName(const std::string& type) {
userName = type; userName = type;
} }
void setUpdateChannel(const std::string& type) {
updateChannel = type;
}
void setChooseHomeTab(const std::string& type) { void setChooseHomeTab(const std::string& type) {
chooseHomeTab = type; chooseHomeTab = type;
} }
@ -521,13 +452,6 @@ void setCheckCompatibilityOnStartup(bool use) {
checkCompatibilityOnStartup = 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) { bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
for (const auto& install_dir : settings_install_dirs) { for (const auto& install_dir : settings_install_dirs) {
if (install_dir.path == dir) { if (install_dir.path == dir) {
@ -564,34 +488,6 @@ void setMainWindowTheme(u32 theme) {
mw_themes = 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) { void setElfViewer(const std::vector<std::string>& elfList) {
m_elf_viewer.resize(elfList.size()); m_elf_viewer.resize(elfList.size());
m_elf_viewer = elfList; m_elf_viewer = elfList;
@ -621,22 +517,6 @@ void setSaveDataPath(const std::filesystem::path& path) {
save_data_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() { const std::vector<std::filesystem::path> getGameInstallDirs() {
std::vector<std::filesystem::path> enabled_dirs; std::vector<std::filesystem::path> enabled_dirs;
for (const auto& dir : settings_install_dirs) { for (const auto& dir : settings_install_dirs) {
@ -667,34 +547,6 @@ u32 getMainWindowTheme() {
return mw_themes; 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() { std::vector<std::string> getElfViewer() {
return m_elf_viewer; return m_elf_viewer;
} }
@ -715,22 +567,6 @@ bool getSeparateLogFilesEnabled() {
return isSeparateLogFilesEnabled; 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() { bool getPSNSignedIn() {
return isPSNSignedIn; return isPSNSignedIn;
} }
@ -764,23 +600,14 @@ void load(const std::filesystem::path& path) {
isNeo = toml::find_or<bool>(general, "isPS4Pro", false); isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
isDevKit = toml::find_or<bool>(general, "isDevKit", false); isDevKit = toml::find_or<bool>(general, "isDevKit", false);
isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", 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); isTrophyPopupDisabled = toml::find_or<bool>(general, "isTrophyPopupDisabled", false);
trophyNotificationDuration = trophyNotificationDuration =
toml::find_or<double>(general, "trophyNotificationDuration", 5.0); toml::find_or<double>(general, "trophyNotificationDuration", 5.0);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true); enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
logFilter = toml::find_or<std::string>(general, "logFilter", ""); logFilter = toml::find_or<std::string>(general, "logFilter", "");
logType = toml::find_or<std::string>(general, "logType", "sync"); logType = toml::find_or<std::string>(general, "logType", "sync");
userName = toml::find_or<std::string>(general, "userName", "shadPS4"); 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); 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"); isSideTrophy = toml::find_or<std::string>(general, "sideTrophy", "right");
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false); compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
checkCompatibilityOnStartup = checkCompatibilityOnStartup =
@ -841,13 +668,7 @@ void load(const std::filesystem::path& path) {
const toml::value& gui = data.at("GUI"); const toml::value& gui = data.at("GUI");
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true); load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true);
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); 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 = const auto install_dir_array =
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {}); 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", {}); save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {});
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
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_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {});
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {}); 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"); 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")) { 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 // Check if the loaded language is in the allowed list
const std::vector<std::string> allowed_languages = { 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", "ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI",
"id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", "nl_NL", "pl_PL", "pt_BR", "pt_PT", "fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO",
"ro_RO", "ru_RU", "sq_AL", "sv_SE", "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW"}; "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) == if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) ==
allowed_languages.end()) { allowed_languages.end()) {
@ -966,17 +781,12 @@ void save(const std::filesystem::path& path) {
data["General"]["isPSNSignedIn"] = isPSNSignedIn; data["General"]["isPSNSignedIn"] = isPSNSignedIn;
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled; data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
data["General"]["trophyNotificationDuration"] = trophyNotificationDuration; data["General"]["trophyNotificationDuration"] = trophyNotificationDuration;
data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume;
data["General"]["enableDiscordRPC"] = enableDiscordRPC; data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["logFilter"] = logFilter; data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType; data["General"]["logType"] = logType;
data["General"]["userName"] = userName; data["General"]["userName"] = userName;
data["General"]["updateChannel"] = updateChannel;
data["General"]["chooseHomeTab"] = chooseHomeTab; data["General"]["chooseHomeTab"] = chooseHomeTab;
data["General"]["showSplash"] = isShowSplash; data["General"]["showSplash"] = isShowSplash;
data["General"]["autoUpdate"] = isAutoUpdate;
data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog;
data["General"]["sideTrophy"] = isSideTrophy; data["General"]["sideTrophy"] = isSideTrophy;
data["General"]["compatibilityEnabled"] = compatibilityData; data["General"]["compatibilityEnabled"] = compatibilityData;
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup; data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
@ -1046,8 +856,6 @@ void save(const std::filesystem::path& path) {
data["GUI"]["addonInstallDir"] = data["GUI"]["addonInstallDir"] =
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["emulatorLanguage"] = emulator_language; data["GUI"]["emulatorLanguage"] = emulator_language;
data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity;
data["GUI"]["showBackgroundImage"] = showBackgroundImage;
data["Settings"]["consoleLanguage"] = m_language; data["Settings"]["consoleLanguage"] = m_language;
// Sorting of TOML sections // 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())); 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"]["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"]["elfDirs"] = m_elf_viewer;
data["GUI"]["recentFiles"] = m_recent_files; data["GUI"]["recentFiles"] = m_recent_files;
@ -1112,19 +909,13 @@ void setDefaultValues() {
isPSNSignedIn = false; isPSNSignedIn = false;
isFullscreen = false; isFullscreen = false;
isTrophyPopupDisabled = false; isTrophyPopupDisabled = false;
playBGM = false;
BGMvolume = 50;
enableDiscordRPC = true; enableDiscordRPC = true;
screenWidth = 1280; screenWidth = 1280;
screenHeight = 720; screenHeight = 720;
logFilter = ""; logFilter = "";
logType = "sync"; logType = "sync";
userName = "shadPS4"; userName = "shadPS4";
if (Common::g_is_release) {
updateChannel = "Release";
} else {
updateChannel = "Nightly";
}
chooseHomeTab = "General"; chooseHomeTab = "General";
cursorState = HideCursorState::Idle; cursorState = HideCursorState::Idle;
cursorHideTimeout = 5; cursorHideTimeout = 5;
@ -1135,8 +926,6 @@ void setDefaultValues() {
isDebugDump = false; isDebugDump = false;
isShaderDebug = false; isShaderDebug = false;
isShowSplash = false; isShowSplash = false;
isAutoUpdate = false;
isAlwaysShowChangelog = false;
isSideTrophy = "right"; isSideTrophy = "right";
isNullGpu = false; isNullGpu = false;
shouldDumpShaders = false; shouldDumpShaders = false;
@ -1153,8 +942,6 @@ void setDefaultValues() {
gpuId = -1; gpuId = -1;
compatibilityData = false; compatibilityData = false;
checkCompatibilityOnStartup = false; checkCompatibilityOnStartup = false;
backgroundImageOpacity = 50;
showBackgroundImage = true;
} }
constexpr std::string_view GetDefaultKeyboardConfig() { constexpr std::string_view GetDefaultKeyboardConfig() {

View file

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

View file

@ -186,7 +186,9 @@ public:
template <typename T> template <typename T>
size_t WriteRaw(const void* data, size_t size) const { 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> template <typename T>

View file

@ -88,7 +88,8 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) {
dst_op.reg.value <= ZYDIS_REGISTER_R15; 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]); const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
#if defined(_WIN32) #if defined(_WIN32)
@ -126,7 +127,8 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
return !cpu.has(Cpu::tSSE4a); 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 && bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[2].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 && bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
operands[3].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 PatchFilter = bool (*)(const ZydisDecodedOperand*);
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&); using InstructionGenerator = void (*)(void*, const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
struct PatchInfo { struct PatchInfo {
/// Filter for more granular patch conditions past just the instruction mnemonic. /// Filter for more granular patch conditions past just the instruction mnemonic.
PatchFilter filter; PatchFilter filter;
@ -400,6 +439,8 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
// SSE4a // SSE4a
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}}, {ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}}, {ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
#if defined(_WIN32) #if defined(_WIN32)
// Windows needs a trampoline. // Windows needs a trampoline.
@ -477,7 +518,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
auto& trampoline_gen = module->trampoline_gen; auto& trampoline_gen = module->trampoline_gen;
const auto trampoline_ptr = trampoline_gen.getCurr(); 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. // Return to the following instruction at the end of the trampoline.
trampoline_gen.jmp(code + instruction.length); 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. // Replace instruction with near jump to the trampoline.
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR); patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
} else { } else {
patch_info.generator(operands, patch_gen); patch_info.generator(code, operands, patch_gen);
} }
const auto patch_size = patch_gen.getCurr() - code; const auto patch_size = patch_gen.getCurr() - code;

View file

@ -10,6 +10,8 @@
namespace Core::FileSys { namespace Core::FileSys {
bool MntPoints::ignore_game_patches = false;
std::string RemoveTrailingSlashes(const std::string& path) { std::string RemoveTrailingSlashes(const std::string& path) {
// Remove trailing slashes to make comparisons simpler. // Remove trailing slashes to make comparisons simpler.
std::string path_sanitized = path; 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; patch_path /= rel_path;
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) && 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; 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); 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)) { if (const auto path = search(patch_path)) {
return *path; return *path;
} }

View file

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

View file

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

View file

@ -9,6 +9,7 @@
#include <vector> #include <vector>
#include <boost/asio/steady_timer.hpp> #include <boost/asio/steady_timer.hpp>
#include <unordered_map>
#include "common/rdtsc.h" #include "common/rdtsc.h"
#include "common/types.h" #include "common/types.h"
@ -135,6 +136,12 @@ private:
}; };
class EqueueInternal { class EqueueInternal {
struct SmallTimer {
SceKernelEvent event;
std::chrono::steady_clock::time_point added;
std::chrono::microseconds interval;
};
public: public:
explicit EqueueInternal(std::string_view name) : m_name(name) {} explicit EqueueInternal(std::string_view name) : m_name(name) {}
@ -151,13 +158,14 @@ public:
int GetTriggeredEvents(SceKernelEvent* ev, int num); int GetTriggeredEvents(SceKernelEvent* ev, int num);
bool AddSmallTimer(EqueueEvent& event); bool AddSmallTimer(EqueueEvent& event);
bool HasSmallTimer() const { bool HasSmallTimer() {
return small_timer_event.event.data != 0; std::scoped_lock lock{m_mutex};
return !m_small_timers.empty();
} }
bool RemoveSmallTimer(u64 id) { bool RemoveSmallTimer(u64 id) {
if (HasSmallTimer() && small_timer_event.event.ident == id) { if (HasSmallTimer()) {
small_timer_event = {}; std::scoped_lock lock{m_mutex};
return true; return m_small_timers.erase(id) > 0;
} }
return false; return false;
} }
@ -170,8 +178,8 @@ private:
std::string m_name; std::string m_name;
std::mutex m_mutex; std::mutex m_mutex;
std::vector<EqueueEvent> m_events; std::vector<EqueueEvent> m_events;
EqueueEvent small_timer_event{};
std::condition_variable m_cond; std::condition_variable m_cond;
std::unordered_map<u64, SmallTimer> m_small_timers;
}; };
u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev); 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 result;
} }
return file->f.WriteRaw<u8>(buf, nbytes); 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; *__Error() = POSIX_ENOTEMPTY;
return -1; 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); 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; return ORBIS_OK;
} }
@ -1050,6 +1068,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite); LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite);
LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv); LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv);
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev); 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", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 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); 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_ ... Libraries::Net::sceNetInetNtop); // TODO fix it to sys_ ...
LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1, LIB_FUNCTION("4n51s0zEf0c", "libScePosix", 1, "libkernel", 1, 1,
Libraries::Net::sceNetInetPton); // TODO fix it to sys_ ... 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 } // namespace Libraries::Kernel

View file

@ -99,8 +99,8 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) {
s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd, s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd,
size_t alignment, u64* physAddrOut, size_t alignment, u64* physAddrOut,
size_t* sizeOut) { 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); searchStart, searchEnd, alignment);
if (physAddrOut == nullptr || sizeOut == nullptr) { if (physAddrOut == nullptr || sizeOut == nullptr) {
return ORBIS_KERNEL_ERROR_EINVAL; return ORBIS_KERNEL_ERROR_EINVAL;
@ -222,9 +222,10 @@ s32 PS4_SYSV_ABI sceKernelMapDirectMemory2(void** addr, u64 len, s32 type, s32 p
return ret; return ret;
} }
s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
int flags, const char* name) { 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)) { if (len == 0 || !Common::Is16KBAligned(len)) {
LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple"); LOG_ERROR(Kernel_Vmm, "len is 0 or not 16kb multiple");
return ORBIS_KERNEL_ERROR_EINVAL; 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 VAddr in_addr = reinterpret_cast<VAddr>(*addr_in_out);
const auto mem_prot = static_cast<Core::MemoryProt>(prot); const auto mem_prot = static_cast<Core::MemoryProt>(prot);
const auto map_flags = static_cast<Core::MemoryMapFlags>(flags); 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(); 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); 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, s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags) {
int flags) {
return sceKernelMapNamedFlexibleMemory(addr_in_out, len, prot, flags, "anon"); 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, int PS4_SYSV_ABI sceKernelDirectMemoryQuery(u64 offset, int flags, OrbisQueryInfo* query_info,
size_t infoSize) { 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(); auto* memory = Core::Memory::Instance();
return memory->DirectMemoryQuery(offset, flags == 1, query_info); 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, "PRT aperture id = {}, address = {:#x}, size = {:#x} is set but not used", id,
address, size); address, size);
auto* memory = Core::Memory::Instance();
memory->SetPrtArea(id, address, size);
PrtApertures[id] = {address, size}; PrtApertures[id] = {address, size};
return ORBIS_OK; 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, s32 PS4_SYSV_ABI sceKernelVirtualQuery(const void* addr, int flags, OrbisVirtualQueryInfo* info,
size_t infoSize); size_t infoSize);
s32 PS4_SYSV_ABI sceKernelReserveVirtualRange(void** addr, u64 len, int flags, u64 alignment); 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, s32 PS4_SYSV_ABI sceKernelMapNamedFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags,
int flags, const char* name); const char* name);
s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, std::size_t len, int prot, s32 PS4_SYSV_ABI sceKernelMapFlexibleMemory(void** addr_in_out, u64 len, s32 prot, s32 flags);
int flags);
int PS4_SYSV_ABI sceKernelQueryMemoryProtection(void* addr, void** start, void** end, u32* prot); 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); 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 // Posix
LIB_FUNCTION("ttHNfU+qDBU", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_init); 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("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("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("ltCfaGr2JGE", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutex_destroy);
LIB_FUNCTION("dQHWEsJtoE4", "libScePosix", 1, "libkernel", 1, 1, posix_pthread_mutexattr_init); 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); 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, *context = ctx_id.index + 1;
user_id, service_label); contexts_internal[key].context_id = *context;
*context = ctx_id.index; LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
service_label);
return ORBIS_OK; return ORBIS_OK;
} }
@ -179,21 +181,23 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
if (trophy_handles.size() >= MaxTrophyHandles) { if (trophy_handles.size() >= MaxTrophyHandles) {
return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX; 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; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) { int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", 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; return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
}
Common::SlotId contextId; Common::SlotId contextId;
contextId.index = context; contextId.index = context - 1;
ContextKey contextkey = trophy_contexts[contextId]; ContextKey contextkey = trophy_contexts[contextId];
trophy_contexts.erase(contextId); trophy_contexts.erase(contextId);
@ -206,15 +210,17 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE) if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
return ORBIS_NP_TROPHY_ERROR_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); LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle);
return ORBIS_NP_TROPHY_ERROR_INVALID_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; 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); LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
return ORBIS_OK; return ORBIS_OK;
} }

View file

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

View file

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

View file

@ -55,6 +55,23 @@ struct OrbisVideodec2AvcPictureInfo {
u8 pic_struct; u8 pic_struct;
u8 field_pic_flag; u8 field_pic_flag;
u8 bottom_field_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 } // namespace Libraries::Vdec2

View file

@ -44,11 +44,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
OrbisVideodec2FrameBuffer& frameBuffer, OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) { OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false; frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false; outputInfo.isValid = false;
outputInfo.isErrorFrame = true; outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0; 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) { if (!inputData.auData) {
return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER; return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER;
} }
@ -113,6 +117,11 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
outputInfo.isErrorFrame = false; outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.framePitchInBytes = frame->linesize[0];
}
if (outputInfo.isValid) { if (outputInfo.isValid) {
OrbisVideodec2AvcPictureInfo pictureInfo = {}; OrbisVideodec2AvcPictureInfo pictureInfo = {};
@ -140,11 +149,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer, s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
OrbisVideodec2OutputInfo& outputInfo) { OrbisVideodec2OutputInfo& outputInfo) {
frameBuffer.isAccepted = false; frameBuffer.isAccepted = false;
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
outputInfo.isValid = false; outputInfo.isValid = false;
outputInfo.isErrorFrame = true; outputInfo.isErrorFrame = true;
outputInfo.pictureCount = 0; 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(); AVFrame* frame = av_frame_alloc();
if (!frame) { if (!frame) {
LOG_ERROR(Lib_Vdec2, "Failed to allocate frame"); LOG_ERROR(Lib_Vdec2, "Failed to allocate frame");
@ -182,6 +195,11 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
outputInfo.isErrorFrame = false; outputInfo.isErrorFrame = false;
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
// Only set framePitchInBytes if the game uses the newer struct version.
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
outputInfo.framePitchInBytes = frame->linesize[0];
}
// FIXME: Should we add picture info here too? // 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) { s32 PS4_SYSV_ABI sceVideoOutGetResolutionStatus(s32 handle, SceVideoOutResolutionStatus* status) {
LOG_INFO(Lib_VideoOut, "called"); 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; 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(); })) { if (!task_queue_cv.wait(lock, stop, [&] { return !task_queue.empty(); })) {
break; break;
} }
task = task_queue.back(); task = task_queue.front();
task_queue.pop(); task_queue.pop();
} }
@ -136,7 +136,7 @@ s32 PS4_SYSV_ABI sceZlibWaitForDone(u64* request_id, const u32* timeout) {
} else { } else {
done_queue_cv.wait(lock, pred); done_queue_cv.wait(lock, pred);
} }
*request_id = done_queue.back(); *request_id = done_queue.front();
done_queue.pop(); done_queue.pop();
} }
return ORBIS_OK; return ORBIS_OK;

View file

@ -332,21 +332,22 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
sr.type = sym_type; sr.type = sym_type;
const auto* record = m_hle_symbols.FindSymbol(sr); const auto* record = m_hle_symbols.FindSymbol(sr);
if (!record) {
// 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) { if (record) {
*return_info = *record; *return_info = *record;
Core::Devtools::Widget::ModuleList::AddModule(sr.library); Core::Devtools::Widget::ModuleList::AddModule(sr.library);
return true; 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;
return true;
}
}
const auto aeronid = AeroLib::FindByNid(sr.name.c_str()); const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
if (aeronid) { if (aeronid) {
return_info->name = aeronid->name; return_info->name = aeronid->name;

View file

@ -95,6 +95,46 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
return clamped_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) { bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) {
const VAddr virtual_addr = std::bit_cast<VAddr>(address); const VAddr virtual_addr = std::bit_cast<VAddr>(address);
const auto& vma = FindVMA(virtual_addr)->second; const auto& vma = FindVMA(virtual_addr)->second;

View file

@ -75,6 +75,9 @@ struct DirectMemoryArea {
if (base + size != next.base) { if (base + size != next.base) {
return false; return false;
} }
if (memory_type != next.memory_type) {
return false;
}
if (is_free != next.is_free) { if (is_free != next.is_free) {
return false; return false;
} }
@ -172,6 +175,10 @@ public:
u64 ClampRangeSize(VAddr virtual_addr, u64 size); 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); bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2); void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
@ -275,6 +282,18 @@ private:
size_t pool_budget{}; size_t pool_budget{};
Vulkan::Rasterizer* rasterizer{}; 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; friend class ::Core::Devtools::Widget::MemoryMapViewer;
}; };

View file

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

View file

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

View file

@ -51,7 +51,7 @@ Tcb* GetTcbBase() {
// Apple x86_64 // Apple x86_64
// Reserve space in the 32-bit address range for allocating TCB pages. // 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 { struct LdtPage {
void* tcb; 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")) { game_folder_name.ends_with("-UPDATE") || game_folder_name.ends_with("-patch")) {
// If an executable was launched from a separate update directory, // If an executable was launched from a separate update directory,
// use the base game directory as the game folder. // 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; const auto base_path = game_folder.parent_path() / base_name;
if (std::filesystem::is_directory(base_path)) { if (std::filesystem::is_directory(base_path)) {
game_folder = base_path; game_folder = base_path;

View file

@ -35,17 +35,20 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::function<void(int&)>> arg_map = { std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
{"-h", {"-h",
[&](int&) { [&](int&) {
std::cout << "Usage: shadps4 [options] <elf or eboot.bin path>\n" std::cout
"Options:\n" << "Usage: shadps4 [options] <elf or eboot.bin path>\n"
" -g, --game <path|ID> Specify game path to launch\n" "Options:\n"
" -- ... Parameters passed to the game ELF. " " -g, --game <path|ID> Specify game path to launch\n"
"Needs to be at the end of the line, and everything after \"--\" is a " " -- ... Parameters passed to the game ELF. "
"game argument.\n" "Needs to be at the end of the line, and everything after \"--\" is a "
" -p, --patch <patch_file> Apply specified patch file\n" "game argument.\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen " " -p, --patch <patch_file> Apply specified patch file\n"
"state. Does not overwrite the config file.\n" " -i, --ignore-game-patch Disable automatic loading of game patch\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n" " -f, --fullscreen <true|false> Specify window initial fullscreen "
" -h, --help Display this help message\n"; "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); exit(0);
}}, }},
{"--help", [&](int& i) { arg_map["-h"](i); }}, {"--help", [&](int& i) { arg_map["-h"](i); }},
@ -72,6 +75,8 @@ int main(int argc, char* argv[]) {
} }
}}, }},
{"--patch", [&](int& i) { arg_map["-p"](i); }}, {"--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", {"-f",
[&](int& i) { [&](int& i) {
if (++i >= argc) { if (++i >= argc) {
@ -112,7 +117,24 @@ int main(int argc, char* argv[]) {
std::cout << "Game folder successfully saved.\n"; std::cout << "Game folder successfully saved.\n";
exit(0); 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) { if (argc == 1) {
int dummy = 0; // one does not simply pass 0 directly int dummy = 0; // one does not simply pass 0 directly

View file

@ -28,8 +28,10 @@
using namespace Common::FS; using namespace Common::FS;
CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent) CheckUpdate::CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
: QDialog(parent), networkManager(new QNetworkAccessManager(this)) { QWidget* parent)
: QDialog(parent), m_gui_settings(std::move(gui_settings)),
networkManager(new QNetworkAccessManager(this)) {
setWindowTitle(tr("Auto Updater")); setWindowTitle(tr("Auto Updater"));
setFixedSize(0, 0); setFixedSize(0, 0);
CheckForUpdates(showMessage); CheckForUpdates(showMessage);
@ -43,7 +45,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
bool checkName = true; bool checkName = true;
while (checkName) { while (checkName) {
updateChannel = QString::fromStdString(Config::getUpdateChannel()); updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
if (updateChannel == "Nightly") { if (updateChannel == "Nightly") {
url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases"); url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases");
checkName = false; checkName = false;
@ -52,12 +54,10 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
checkName = false; checkName = false;
} else { } else {
if (Common::g_is_release) { if (Common::g_is_release) {
Config::setUpdateChannel("Release"); m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
} else { } 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(); latestDate = jsonObj["published_at"].toString();
QJsonArray assets = jsonObj["assets"].toArray(); 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); QDateTime dateTime = QDateTime::fromString(latestDate, Qt::ISODate);
latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date"; latestDate = dateTime.isValid() ? dateTime.toString("yyyy-MM-dd HH:mm:ss") : "Unknown date";
if (latestRev == currentRev.left(7)) { if (latestRev == currentRev) {
if (showMessage) { if (showMessage) {
QMessageBox::information(this, tr("Auto Updater"), QMessageBox::information(this, tr("Auto Updater"),
tr("Your version is already up to date!")); tr("Your version is already up to date!"));
@ -198,7 +198,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
titleLayout->addWidget(titleLabel); titleLayout->addWidget(titleLabel);
layout->addLayout(titleLayout); 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 + QString updateText = QString("<p><b>" + tr("Update Channel") + ": </b>" + updateChannel +
"<br>" "<br>"
@ -215,7 +215,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
"<td>%3</td>" "<td>%3</td>"
"<td>(%4)</td>" "<td>(%4)</td>"
"</tr></table></p>") "</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); QLabel* updateLabel = new QLabel(updateText, this);
layout->addWidget(updateLabel); 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); requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate);
textField->setVisible(true); textField->setVisible(true);
toggleButton->setText(tr("Hide Changelog")); 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(); }); 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)) #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 #else
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) { connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) {
#endif #endif
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); 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"); Config::save(user_dir / "config.toml");
}); });

View file

@ -8,12 +8,14 @@
#include <QDialog> #include <QDialog>
#include <QNetworkAccessManager> #include <QNetworkAccessManager>
#include <QPushButton> #include <QPushButton>
#include "gui_settings.h"
class CheckUpdate : public QDialog { class CheckUpdate : public QDialog {
Q_OBJECT Q_OBJECT
public: 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(); ~CheckUpdate();
private slots: private slots:
@ -35,6 +37,7 @@ private:
QString updateDownloadUrl; QString updateDownloadUrl;
QNetworkAccessManager* networkManager; QNetworkAccessManager* networkManager;
std::shared_ptr<gui_settings> m_gui_settings;
}; };
#endif // CHECKUPDATE_H #endif // CHECKUPDATE_H

View file

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

View file

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

View file

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

View file

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

@ -33,13 +33,13 @@ KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget*
} }
ButtonsList = { ButtonsList = {
ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton, ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton,
ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button, ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button,
ui->L3Button, ui->R3Button, ui->TouchpadButton, ui->OptionsButton, ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton,
ui->TouchpadButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton,
ui->DpadRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton,
ui->RStickRightButton, ui->LHalfButton, ui->RHalfButton}; ui->LHalfButton, ui->RHalfButton};
ButtonConnects(); ButtonConnects();
SetUIValuestoMappings("default"); SetUIValuestoMappings("default");
@ -372,14 +372,31 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) {
file.close(); file.close();
// Prevent duplicate inputs for KBM as this breaks the engine // 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) { for (auto it = inputs.begin(); it != inputs.end(); ++it) {
if (std::find(it + 1, inputs.end(), *it) != inputs.end()) { if (std::find(it + 1, inputs.end(), *it) != inputs.end()) {
QMessageBox::information(this, tr("Unable to Save"), duplicateFound = true;
tr("Cannot bind any unique input more than once")); duplicateMappings.insert(QString::fromStdString(*it));
return;
} }
} }
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; std::vector<std::string> save;
bool CurrentLineEmpty = false, LastLineEmpty = false; bool CurrentLineEmpty = false, LastLineEmpty = false;
for (auto const& line : lines) { for (auto const& line : lines) {

View file

@ -41,20 +41,22 @@ int main(int argc, char* argv[]) {
std::unordered_map<std::string, std::function<void(int&)>> arg_map = { std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
{"-h", {"-h",
[&](int&) { [&](int&) {
std::cout << "Usage: shadps4 [options]\n" std::cout
"Options:\n" << "Usage: shadps4 [options]\n"
" No arguments: Opens the GUI.\n" "Options:\n"
" -g, --game <path|ID> Specify <eboot.bin or elf path> or " " No arguments: Opens the GUI.\n"
"<game ID (CUSAXXXXX)> to launch\n" " -g, --game <path|ID> Specify <eboot.bin or elf path> or "
" -- ... Parameters passed to the game ELF. " "<game ID (CUSAXXXXX)> to launch\n"
"Needs to be at the end of the line, and everything after \"--\" is a " " -- ... Parameters passed to the game ELF. "
"game argument.\n" "Needs to be at the end of the line, and everything after \"--\" is a "
" -p, --patch <patch_file> Apply specified patch file\n" "game argument.\n"
" -s, --show-gui Show the GUI\n" " -p, --patch <patch_file> Apply specified patch file\n"
" -f, --fullscreen <true|false> Specify window initial fullscreen " " -i, --ignore-game-patch Disable automatic loading of game patch\n"
"state. Does not overwrite the config file.\n" " -s, --show-gui Show the GUI\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n" " -f, --fullscreen <true|false> Specify window initial fullscreen "
" -h, --help Display this help message\n"; "state. Does not overwrite the config file.\n"
" --add-game-folder <folder> Adds a new game folder to the config.\n"
" -h, --help Display this help message\n";
exit(0); exit(0);
}}, }},
{"--help", [&](int& i) { arg_map["-h"](i); }}, // Redirect --help to -h {"--help", [&](int& i) { arg_map["-h"](i); }}, // Redirect --help to -h
@ -84,6 +86,8 @@ int main(int argc, char* argv[]) {
} }
}}, }},
{"--patch", [&](int& i) { arg_map["-p"](i); }}, {"--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", {"-f",
[&](int& i) { [&](int& i) {
if (++i >= argc) { if (++i >= argc) {

View file

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

View file

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

View file

@ -107,7 +107,6 @@ public:
toggleLabelsAct = new QAction(MainWindow); toggleLabelsAct = new QAction(MainWindow);
toggleLabelsAct->setObjectName("toggleLabelsAct"); toggleLabelsAct->setObjectName("toggleLabelsAct");
toggleLabelsAct->setCheckable(true); toggleLabelsAct->setCheckable(true);
toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons());
setIconSizeTinyAct = new QAction(MainWindow); setIconSizeTinyAct = new QAction(MainWindow);
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct"); 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; 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) 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->setupUi(this);
ui->tabWidgetSettings->setUsesScrollButtons(false); ui->tabWidgetSettings->setUsesScrollButtons(false);
@ -147,6 +148,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
Config::save(config_dir / "config.toml"); Config::save(config_dir / "config.toml");
} else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) { } else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
Config::setDefaultValues(); Config::setDefaultValues();
setDefaultValues();
Config::save(config_dir / "config.toml"); Config::save(config_dir / "config.toml");
LoadValuesFromConfig(); LoadValuesFromConfig();
} else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) { } else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) {
@ -175,28 +177,34 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
{ {
#ifdef ENABLE_UPDATER #ifdef ENABLE_UPDATER
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) #if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
[](int state) { Config::setAutoUpdate(state == Qt::Checked); }); m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked);
});
connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
[](int state) { Config::setAlwaysShowChangelog(state == Qt::Checked); }); m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked);
});
#else #else
connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this, 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, 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 #endif
connect(ui->updateComboBox, &QComboBox::currentTextChanged, this, connect(ui->updateComboBox, &QComboBox::currentTextChanged, this,
[this](const QString& channel) { [this](const QString& channel) {
if (channelMap.contains(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, []() { connect(ui->checkUpdateButton, &QPushButton::clicked, this, [this]() {
auto checkUpdate = new CheckUpdate(true); auto checkUpdate = new CheckUpdate(m_gui_settings, true);
checkUpdate->exec(); checkUpdate->exec();
}); });
#else #else
@ -235,12 +243,12 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
[](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); }); [](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); });
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0)) #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 #else
connect(ui->showBackgroundImageCheckBox, &QCheckBox::checkStateChanged, this, connect(ui->showBackgroundImageCheckBox, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state) { [this](Qt::CheckState state) {
#endif #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->dumpShadersCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "dumpShaders", false));
ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false)); ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false));
ui->enableHDRCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "allowHDR", 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( ui->disableTrophycheckBox->setChecked(
toml::find_or<bool>(data, "General", "isTrophyPopupDisabled", false)); toml::find_or<bool>(data, "General", "isTrophyPopupDisabled", false));
ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration()); ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration());
@ -460,7 +468,7 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->radioButton_Top->setChecked(side == "top"); ui->radioButton_Top->setChecked(side == "top");
ui->radioButton_Bottom->setChecked(side == "bottom"); 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( ui->discordRPCCheckbox->setChecked(
toml::find_or<bool>(data, "General", "enableDiscordRPC", true)); toml::find_or<bool>(data, "General", "enableDiscordRPC", true));
QString translatedText_FullscreenMode = QString translatedText_FullscreenMode =
@ -501,11 +509,10 @@ void SettingsDialog::LoadValuesFromConfig() {
toml::find_or<bool>(data, "General", "checkCompatibilityOnStartup", false)); toml::find_or<bool>(data, "General", "checkCompatibilityOnStartup", false));
#ifdef ENABLE_UPDATER #ifdef ENABLE_UPDATER
ui->updateCheckBox->setChecked(toml::find_or<bool>(data, "General", "autoUpdate", false)); ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
ui->changelogCheckBox->setChecked( ui->changelogCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_showChangeLog).toBool());
toml::find_or<bool>(data, "General", "alwaysShowChangelog", false));
QString updateChannel = QString::fromStdString(Config::getUpdateChannel()); QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
ui->updateComboBox->setCurrentText( ui->updateComboBox->setCurrentText(
channelMap.key(updateChannel != "Release" && updateChannel != "Nightly" channelMap.key(updateChannel != "Release" && updateChannel != "Nightly"
? (Common::g_is_release ? "Release" : "Nightly") ? (Common::g_is_release ? "Release" : "Nightly")
@ -536,11 +543,14 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty());
ResetInstallFolders(); ResetInstallFolders();
ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity()); ui->backgroundImageOpacitySlider->setValue(
ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage()); m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt());
ui->showBackgroundImageCheckBox->setChecked(
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool());
backgroundImageOpacitySlider_backup = Config::getBackgroundImageOpacity(); backgroundImageOpacitySlider_backup =
bgm_volume_backup = Config::getBGMvolume(); m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt();
} }
void SettingsDialog::InitializeEmulatorLanguages() { void SettingsDialog::InitializeEmulatorLanguages() {
@ -754,8 +764,7 @@ void SettingsDialog::UpdateSettings() {
} else if (ui->radioButton_Bottom->isChecked()) { } else if (ui->radioButton_Bottom->isChecked()) {
Config::setSideTrophy("bottom"); Config::setSideTrophy("bottom");
} }
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked());
Config::setPlayBGM(ui->playBGMCheckBox->isChecked());
Config::setAllowHDR(ui->enableHDRCheckBox->isChecked()); Config::setAllowHDR(ui->enableHDRCheckBox->isChecked());
Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString()); Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString());
Config::setLogFilter(ui->logFilterLineEdit->text().toStdString()); Config::setLogFilter(ui->logFilterLineEdit->text().toStdString());
@ -764,7 +773,7 @@ void SettingsDialog::UpdateSettings() {
Config::setCursorState(ui->hideCursorComboBox->currentIndex()); Config::setCursorState(ui->hideCursorComboBox->currentIndex());
Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value()); Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value());
Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1); 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::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]);
Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked()); Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked());
Config::setScreenWidth(ui->widthSpinBox->value()); Config::setScreenWidth(ui->widthSpinBox->value());
@ -784,16 +793,19 @@ void SettingsDialog::UpdateSettings() {
Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked()); Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked());
Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked()); Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked());
Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked()); Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked());
Config::setAutoUpdate(ui->updateCheckBox->isChecked()); m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked());
Config::setAlwaysShowChangelog(ui->changelogCheckBox->isChecked()); m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked());
Config::setUpdateChannel(channelMap.value(ui->updateComboBox->currentText()).toStdString()); m_gui_settings->SetValue(gui::gen_updateChannel,
channelMap.value(ui->updateComboBox->currentText()));
Config::setChooseHomeTab( Config::setChooseHomeTab(
chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString()); chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString());
Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked()); Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked());
Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->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()); 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; std::vector<Config::GameInstallDir> dirs_with_states;
for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) { for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) {
@ -862,3 +874,16 @@ void SettingsDialog::ResetInstallFolders() {
Config::setAllGameInstallDirs(settings_install_dirs_config); 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/config.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "gui_settings.h"
#include "qt_gui/compatibility_info.h" #include "qt_gui/compatibility_info.h"
namespace Ui { namespace Ui {
@ -20,7 +21,8 @@ class SettingsDialog;
class SettingsDialog : public QDialog { class SettingsDialog : public QDialog {
Q_OBJECT Q_OBJECT
public: 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); QWidget* parent = nullptr);
~SettingsDialog(); ~SettingsDialog();
@ -42,6 +44,7 @@ private:
void OnLanguageChanged(int index); void OnLanguageChanged(int index);
void OnCursorStateChanged(s16 index); void OnCursorStateChanged(s16 index);
void closeEvent(QCloseEvent* event) override; void closeEvent(QCloseEvent* event) override;
void setDefaultValues();
std::unique_ptr<Ui::SettingsDialog> ui; std::unique_ptr<Ui::SettingsDialog> ui;
@ -52,4 +55,5 @@ private:
int initialHeight; int initialHeight;
bool is_saving = false; bool is_saving = false;
std::shared_ptr<gui_settings> m_gui_settings;
}; };

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Speichern nicht möglich</translation> <translation>Speichern nicht möglich</translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Kann keine eindeutige Eingabe mehr als einmal zuordnen</translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Drücken Sie eine Taste</translation> <translation>Drücken Sie eine Taste</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Abbrechen</translation> <translation>Abbrechen</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation type="unfinished"></translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
@ -1184,6 +1180,12 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>No se Pudo Guardar</translation> <translation>No se Pudo Guardar</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Pulsa una tecla</translation> <translation>Pulsa una tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -26,7 +26,7 @@
</message> </message>
<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> <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>
<message> <message>
<source>No Image Available</source> <source>No Image Available</source>
@ -214,7 +214,7 @@
</message> </message>
<message> <message>
<source>XML ERROR:</source> <source>XML ERROR:</source>
<translation type="unfinished">XML ERROR:</translation> <translation>XML خطای :</translation>
</message> </message>
<message> <message>
<source>Failed to open files.json for writing</source> <source>Failed to open files.json for writing</source>
@ -407,43 +407,43 @@
<name>ControlSettings</name> <name>ControlSettings</name>
<message> <message>
<source>Configure Controls</source> <source>Configure Controls</source>
<translation type="unfinished">Configure Controls</translation> <translation>پیکربندی دسته ها</translation>
</message> </message>
<message> <message>
<source>D-Pad</source> <source>D-Pad</source>
<translation type="unfinished">D-Pad</translation> <translation>D-Pad</translation>
</message> </message>
<message> <message>
<source>Up</source> <source>Up</source>
<translation type="unfinished">Up</translation> <translation>بالا</translation>
</message> </message>
<message> <message>
<source>Left</source> <source>Left</source>
<translation type="unfinished">Left</translation> <translation>چپ</translation>
</message> </message>
<message> <message>
<source>Right</source> <source>Right</source>
<translation type="unfinished">Right</translation> <translation>راست</translation>
</message> </message>
<message> <message>
<source>Down</source> <source>Down</source>
<translation type="unfinished">Down</translation> <translation>پایین</translation>
</message> </message>
<message> <message>
<source>Left Stick Deadzone (def:2 max:127)</source> <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>
<message> <message>
<source>Left Deadzone</source> <source>Left Deadzone</source>
<translation type="unfinished">Left Deadzone</translation> <translation>منطقه مرده چپ</translation>
</message> </message>
<message> <message>
<source>Left Stick</source> <source>Left Stick</source>
<translation type="unfinished">Left Stick</translation> <translation>جواستیک چپ</translation>
</message> </message>
<message> <message>
<source>Config Selection</source> <source>Config Selection</source>
<translation type="unfinished">Config Selection</translation> <translation>انتخاب پیکربندی</translation>
</message> </message>
<message> <message>
<source>Common Config</source> <source>Common Config</source>
@ -451,7 +451,7 @@
</message> </message>
<message> <message>
<source>Use per-game configs</source> <source>Use per-game configs</source>
<translation type="unfinished">Use per-game configs</translation> <translation>از پیکربندیهای مخصوص هر بازی استفاده کنید</translation>
</message> </message>
<message> <message>
<source>L1 / LB</source> <source>L1 / LB</source>
@ -483,7 +483,7 @@
</message> </message>
<message> <message>
<source>R3</source> <source>R3</source>
<translation type="unfinished">R3</translation> <translation>R3</translation>
</message> </message>
<message> <message>
<source>Face Buttons</source> <source>Face Buttons</source>
@ -491,7 +491,7 @@
</message> </message>
<message> <message>
<source>Triangle / Y</source> <source>Triangle / Y</source>
<translation type="unfinished">Triangle / Y</translation> <translation>مثلث / Y</translation>
</message> </message>
<message> <message>
<source>Square / X</source> <source>Square / X</source>
@ -531,7 +531,7 @@
</message> </message>
<message> <message>
<source>B:</source> <source>B:</source>
<translation type="unfinished">B:</translation> <translation>B:</translation>
</message> </message>
<message> <message>
<source>Override Lightbar Color</source> <source>Override Lightbar Color</source>
@ -543,7 +543,7 @@
</message> </message>
<message> <message>
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation>ذخیره امکان پذیر نیست</translation>
</message> </message>
<message> <message>
<source>Cannot bind axis values more than once</source> <source>Cannot bind axis values more than once</source>
@ -570,7 +570,7 @@
<name>EditorDialog</name> <name>EditorDialog</name>
<message> <message>
<source>Edit Keyboard + Mouse and Controller input bindings</source> <source>Edit Keyboard + Mouse and Controller input bindings</source>
<translation type="unfinished">Edit Keyboard + Mouse and Controller input bindings</translation> <translation>تغییر دکمه های کیبرد + ماوس و دسته</translation>
</message> </message>
<message> <message>
<source>Use Per-Game configs</source> <source>Use Per-Game configs</source>
@ -582,7 +582,7 @@
</message> </message>
<message> <message>
<source>Could not open the file for reading</source> <source>Could not open the file for reading</source>
<translation type="unfinished">Could not open the file for reading</translation> <translation>نمی تواند فایل را برای خواندن باز کند</translation>
</message> </message>
<message> <message>
<source>Could not open the file for writing</source> <source>Could not open the file for writing</source>
@ -602,7 +602,7 @@
</message> </message>
<message> <message>
<source>Do you want to reset your custom default config to the original default config?</source> <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>
<message> <message>
<source>Do you want to reset this config to your custom default config?</source> <source>Do you want to reset this config to your custom default config?</source>
@ -860,7 +860,7 @@
</message> </message>
<message> <message>
<source>View report</source> <source>View report</source>
<translation type="unfinished">View report</translation> <translation>مشاهده گزارش</translation>
</message> </message>
<message> <message>
<source>Submit a report</source> <source>Submit a report</source>
@ -916,11 +916,11 @@
</message> </message>
<message> <message>
<source>Delete Save Data</source> <source>Delete Save Data</source>
<translation type="unfinished">Delete Save Data</translation> <translation>پاک کردن داده های ذخیره شده</translation>
</message> </message>
<message> <message>
<source>This game has no update folder to open!</source> <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>
<message> <message>
<source>No log file found for this game!</source> <source>No log file found for this game!</source>
@ -948,7 +948,7 @@
</message> </message>
<message> <message>
<source>SFO Viewer for </source> <source>SFO Viewer for </source>
<translation type="unfinished">SFO Viewer for </translation> <translation>SFO مشاهده </translation>
</message> </message>
</context> </context>
<context> <context>
@ -986,7 +986,7 @@
</message> </message>
<message> <message>
<source>Up</source> <source>Up</source>
<translation type="unfinished">Up</translation> <translation/>
</message> </message>
<message> <message>
<source>unmapped</source> <source>unmapped</source>
@ -1058,7 +1058,7 @@
</message> </message>
<message> <message>
<source>Touchpad Click</source> <source>Touchpad Click</source>
<translation type="unfinished">Touchpad Click</translation> <translation>کلیک روی تاچپد</translation>
</message> </message>
<message> <message>
<source>Mouse to Joystick</source> <source>Mouse to Joystick</source>
@ -1078,7 +1078,7 @@
</message> </message>
<message> <message>
<source>Mouse Movement Parameters</source> <source>Mouse Movement Parameters</source>
<translation type="unfinished">Mouse Movement Parameters</translation> <translation/>
</message> </message>
<message> <message>
<source>note: click Help Button/Special Keybindings for more information</source> <source>note: click Help Button/Special Keybindings for more information</source>
@ -1102,7 +1102,7 @@
</message> </message>
<message> <message>
<source>Cross</source> <source>Cross</source>
<translation type="unfinished">Cross</translation> <translation>ضربدر</translation>
</message> </message>
<message> <message>
<source>Right Analog Halfmode</source> <source>Right Analog Halfmode</source>
@ -1122,7 +1122,7 @@
</message> </message>
<message> <message>
<source>Copy from Common Config</source> <source>Copy from Common Config</source>
<translation type="unfinished">Copy from Common Config</translation> <translation>کپی از پیکربندی مشترک</translation>
</message> </message>
<message> <message>
<source>Deadzone Offset (def 0.50):</source> <source>Deadzone Offset (def 0.50):</source>
@ -1130,32 +1130,28 @@
</message> </message>
<message> <message>
<source>Speed Multiplier (def 1.0):</source> <source>Speed Multiplier (def 1.0):</source>
<translation type="unfinished">Speed Multiplier (def 1.0):</translation> <translation>ضریب سرعت (def 1.0):</translation>
</message> </message>
<message> <message>
<source>Common Config Selected</source> <source>Common Config Selected</source>
<translation type="unfinished">Common Config Selected</translation> <translation>پیکربندی مشترک انتخاب شده</translation>
</message> </message>
<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> <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>
<message> <message>
<source>Copy values from Common Config</source> <source>Copy values from Common Config</source>
<translation type="unfinished">Copy values from Common Config</translation> <translation>کپی کردن مقادیر از پیکربندی مشترک</translation>
</message> </message>
<message> <message>
<source>Do you want to overwrite existing mappings with the mappings from the Common Config?</source> <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>
<message> <message>
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1170,7 +1166,7 @@
</message> </message>
<message> <message>
<source>Save</source> <source>Save</source>
<translation type="unfinished">Save</translation> <translation>ذخیرهسازی</translation>
</message> </message>
<message> <message>
<source>Apply</source> <source>Apply</source>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@ -1213,7 +1217,7 @@
</message> </message>
<message> <message>
<source>Open shadPS4 Folder</source> <source>Open shadPS4 Folder</source>
<translation type="unfinished">Open shadPS4 Folder</translation> <translation>پوشه shadPS4 را باز کنید</translation>
</message> </message>
<message> <message>
<source>Exit</source> <source>Exit</source>
@ -1624,7 +1628,7 @@
</message> </message>
<message> <message>
<source>Collect Shaders</source> <source>Collect Shaders</source>
<translation type="unfinished">Collect Shaders</translation> <translation>جمع آوری شیدرها</translation>
</message> </message>
<message> <message>
<source>Copy GPU Buffers</source> <source>Copy GPU Buffers</source>
@ -1664,7 +1668,7 @@
</message> </message>
<message> <message>
<source>Title Music</source> <source>Title Music</source>
<translation type="unfinished">Title Music</translation> <translation/>
</message> </message>
<message> <message>
<source>Disable Trophy Notification</source> <source>Disable Trophy Notification</source>
@ -1728,7 +1732,7 @@
</message> </message>
<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> <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>
<message> <message>
<source>Emulator Language:\nSets the language of the emulator&apos;s user interface.</source> <source>Emulator Language:\nSets the language of the emulator&apos;s user interface.</source>
@ -1748,7 +1752,7 @@
</message> </message>
<message> <message>
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source> <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>
<message> <message>
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Background Image:\nControl the opacity of the game background image.</source> <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>
<message> <message>
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source> <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>
<message> <message>
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source> <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>
<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> <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>
<message> <message>
<source>Game Folders:\nThe list of folders to check for installed games.</source> <source>Game Folders:\nThe list of folders to check for installed games.</source>
@ -1860,7 +1864,7 @@
</message> </message>
<message> <message>
<source>Remove:\nRemove a folder from the list.</source> <source>Remove:\nRemove a folder from the list.</source>
<translation>حذف:\nیک پوشه را از لیست حذف کنید.</translation> <translation>حذف:\n یک پوشه را از لیست حذف کنید.</translation>
</message> </message>
<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> <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>
<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> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source> <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>
<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> <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>
<message> <message>
<source>Nightly</source> <source>Nightly</source>
<translation type="unfinished">Nightly</translation> <translation>اخرین نسخه شبانه</translation>
</message> </message>
<message> <message>
<source>Set the volume of the background music.</source> <source>Set the volume of the background music.</source>
@ -1936,7 +1940,7 @@
</message> </message>
<message> <message>
<source>sync</source> <source>sync</source>
<translation type="unfinished">sync</translation> <translation>همزمان</translation>
</message> </message>
<message> <message>
<source>Auto Select</source> <source>Auto Select</source>
@ -2000,7 +2004,7 @@
</message> </message>
<message> <message>
<source>Right</source> <source>Right</source>
<translation type="unfinished">Right</translation> <translation>راست</translation>
</message> </message>
<message> <message>
<source>Top</source> <source>Top</source>
@ -2032,7 +2036,7 @@
</message> </message>
<message> <message>
<source>%1 already exists</source> <source>%1 already exists</source>
<translation type="unfinished">%1 already exists</translation> <translation>%1 از قبل وجود دارد</translation>
</message> </message>
<message> <message>
<source>Portable user folder created</source> <source>Portable user folder created</source>
@ -2044,7 +2048,7 @@
</message> </message>
<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> <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>
<message> <message>
<source> * Unsupported Vulkan Version</source> <source> * Unsupported Vulkan Version</source>
@ -2075,7 +2079,7 @@
</message> </message>
<message> <message>
<source>Show Hidden Trophies</source> <source>Show Hidden Trophies</source>
<translation type="unfinished">Show Hidden Trophies</translation> <translation>نمایش جوایز مخفی</translation>
</message> </message>
</context> </context>
</TS> </TS>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Impossible de sauvegarder</translation> <translation>Impossible de sauvegarder</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Appuyez sur un bouton</translation> <translation>Appuyez sur un bouton</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Annuler</translation> <translation>Annuler</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Impossibile Salvare</translation> <translation>Impossibile Salvare</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Premi un tasto</translation> <translation>Premi un tasto</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Annulla</translation> <translation>Annulla</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Klarte ikke lagre</translation> <translation>Klarte ikke lagre</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Trykk en tast</translation> <translation>Trykk en tast</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Avbryt</translation> <translation>Avbryt</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Zapisywanie nie powiodło się</translation> <translation>Zapisywanie nie powiodło się</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Naciśnij klawisz</translation> <translation>Naciśnij klawisz</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Anuluj</translation> <translation>Anuluj</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Não foi possível salvar</translation> <translation>Não foi possível salvar</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Aperte uma tecla</translation> <translation>Aperte uma tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@ -2048,7 +2052,7 @@
</message> </message>
<message> <message>
<source> * Unsupported Vulkan Version</source> <source> * Unsupported Vulkan Version</source>
<translation type="unfinished"> * Unsupported Vulkan Version</translation> <translation> * Versão do Vulkan não suportada</translation>
</message> </message>
</context> </context>
<context> <context>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Não foi possível guardar</translation> <translation>Não foi possível guardar</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Pressione uma tecla</translation> <translation>Pressione uma tecla</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Cancelar</translation> <translation>Cancelar</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Не удаётся сохранить</translation> <translation>Не удаётся сохранить</translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Невозможно привязать уникальный ввод более одного раза</translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Нажмите кнопку</translation> <translation>Нажмите кнопку</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Отмена</translation> <translation>Отмена</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation type="unfinished">Cancel</translation> <translation type="unfinished">Cancel</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Ruajtja Dështoi</translation> <translation>Ruajtja Dështoi</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Shtyp një tast</translation> <translation>Shtyp një tast</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Anulo</translation> <translation>Anulo</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

File diff suppressed because it is too large Load diff

View file

@ -158,7 +158,7 @@
</message> </message>
<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> <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>
<message> <message>
<source>Cheats Downloaded Successfully</source> <source>Cheats Downloaded Successfully</source>
@ -166,7 +166,7 @@
</message> </message>
<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> <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>
<message> <message>
<source>Failed to save:</source> <source>Failed to save:</source>
@ -182,7 +182,7 @@
</message> </message>
<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> <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>
<message> <message>
<source>Failed to parse JSON data from HTML.</source> <source>Failed to parse JSON data from HTML.</source>
@ -261,7 +261,7 @@
</message> </message>
<message> <message>
<source>The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later.</source> <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>
<message> <message>
<source>Failed to parse update information.</source> <source>Failed to parse update information.</source>
@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Kunde inte spara</translation> <translation>Kunde inte spara</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Tryck en tangent</translation> <translation>Tryck en tangent</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Avbryt</translation> <translation>Avbryt</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@ -1728,47 +1732,47 @@
</message> </message>
<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> <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>
<message> <message>
<source>Emulator Language:\nSets the language of the emulator&apos;s user interface.</source> <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>
<message> <message>
<source>Show Splash Screen:\nShows the game&apos;s splash screen (a special image) while the game is starting.</source> <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>
<message> <message>
<source>Enable Discord Rich Presence:\nDisplays the emulator icon and relevant information on your Discord profile.</source> <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>
<message> <message>
<source>Username:\nSets the PS4&apos;s account username, which may be displayed by some games.</source> <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>
<message> <message>
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source> <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>
<message> <message>
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Background Image:\nControl the opacity of the game background image.</source> <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>
<message> <message>
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</source> <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>
<message> <message>
<source>Back Button Behavior:\nSets the controller&apos;s back button to emulate tapping the specified position on the PS4 touchpad.</source> <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>
<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> <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>
<message> <message>
<source>Update Compatibility On Startup:\nAutomatically update the compatibility database when shadPS4 starts.</source> <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>
<message> <message>
<source>Update Compatibility Database:\nImmediately update the compatibility database.</source> <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>
<message> <message>
<source>Never</source> <source>Never</source>
@ -1828,23 +1832,23 @@
</message> </message>
<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> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Enable Shaders Dumping:\nFor the sake of technical debugging, saves the games shaders to a folder as they render.</source> <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>
<message> <message>
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source> <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>
<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> <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>
<message> <message>
<source>Game Folders:\nThe list of folders to check for installed games.</source> <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>
<message> <message>
<source>Add:\nAdd a folder to the list.</source> <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>
<message> <message>
<source>Remove:\nRemove a folder from the list.</source> <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>
<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> <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>
<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> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source> <source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source>
@ -1884,27 +1888,27 @@
</message> </message>
<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> <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>
<message> <message>
<source>Copy GPU Buffers:\nGets around race conditions involving GPU submits.\nMay or may not help with PM4 type 0 crashes.</source> <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>
<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> <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>
<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> <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>
<message> <message>
<source>Save Data Path:\nThe folder where game save data will be saved.</source> <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>
<message> <message>
<source>Browse:\nBrowse for a folder to set as the save data path.</source> <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>
<message> <message>
<source>Release</source> <source>Release</source>

View file

@ -138,7 +138,7 @@
</message> </message>
<message> <message>
<source>File Exists</source> <source>File Exists</source>
<translation>Dosya mevcut</translation> <translation>Dosya Mevcut</translation>
</message> </message>
<message> <message>
<source>File already exists. Do you want to replace it?</source> <source>File already exists. Do you want to replace it?</source>
@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Kaydedilemedi</translation> <translation>Kaydedilemedi</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Bir tuşa basın</translation> <translation>Bir tuşa basın</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>İptal</translation> <translation>İptal</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@ -1221,7 +1225,7 @@
</message> </message>
<message> <message>
<source>Exit shadPS4</source> <source>Exit shadPS4</source>
<translation>shadPS4&apos;ten Çık</translation> <translation>shadPS4 Çıkış</translation>
</message> </message>
<message> <message>
<source>Exit the application.</source> <source>Exit the application.</source>
@ -1381,7 +1385,7 @@
</message> </message>
<message> <message>
<source>Game Boot</source> <source>Game Boot</source>
<translation>Oyun Başlatma</translation> <translation>Oyun Başlat</translation>
</message> </message>
<message> <message>
<source>Only one file can be selected!</source> <source>Only one file can be selected!</source>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation>Не вдалося зберегти</translation> <translation>Не вдалося зберегти</translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation>Не можна прив'язати кнопку вводу більш ніж один раз</translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation>Натисніть клавішу</translation> <translation>Натисніть клавішу</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Відмінити</translation> <translation>Відмінити</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation type="unfinished">Unable to Save</translation> <translation type="unfinished">Unable to Save</translation>
</message> </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> <message>
<source>Press a key</source> <source>Press a key</source>
<translation type="unfinished">Press a key</translation> <translation type="unfinished">Press a key</translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation>Hủy</translation> <translation>Hủy</translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation></translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation></translation> <translation></translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>

View file

@ -1152,10 +1152,6 @@
<source>Unable to Save</source> <source>Unable to Save</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<source>Cannot bind any unique input more than once</source>
<translation></translation>
</message>
<message> <message>
<source>Press a key</source> <source>Press a key</source>
<translation></translation> <translation></translation>
@ -1184,6 +1180,14 @@
<source>Cancel</source> <source>Cancel</source>
<translation></translation> <translation></translation>
</message> </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>
<context> <context>
<name>MainWindow</name> <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.AddCapability(spv::Capability::PhysicalStorageBufferAddresses);
ctx.AddExtension("SPV_KHR_physical_storage_buffer"); 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) { void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) {

View file

@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // 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/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.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 SharedAtomicU32(EmitContext& ctx, Id offset, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const Id shift_id{ctx.ConstU32(2U)}; const Id shift_id{ctx.ConstU32(2U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; 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)}; const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value); 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)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
const Id shift_id{ctx.ConstU32(2U)}; const Id shift_id{ctx.ConstU32(2U)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)}; const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)}; 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)}; const auto [scope, semantics]{AtomicArgs(ctx)};
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics); 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) { Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value,
if (Sirit::ValidId(buffer_size)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
// Bounds checking enabled, wrap in a conditional branch to make sure that const Id shift_id{ctx.ConstU32(3U)};
// the atomic is not mistakenly executed when the index is out of bounds. const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size); const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
const Id ib_label = ctx.OpLabel(); const Id pointer{
const Id oob_label = ctx.OpLabel(); ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
const Id end_label = ctx.OpLabel(); const auto [scope, semantics]{AtomicArgs(ctx)};
ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone); return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
ctx.OpBranchConditional(in_bounds, ib_label, oob_label); return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics, value);
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 BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value, Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
@ -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 auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index); const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
const auto [scope, semantics]{AtomicArgs(ctx)}; const auto [scope, semantics]{AtomicArgs(ctx)};
return 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); 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 ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value,
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) { Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
const auto& texture = ctx.images[handle & 0xFFFF]; 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); 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) { Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value) {
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMax); 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); return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub);
} }
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset) { Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset) {
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement); return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement);
} }
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset) { Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset) {
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement); return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement);
} }
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd); 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) { Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin); 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); return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
} }
Id EmitBufferAtomicInc32(EmitContext&, IR::Inst*, u32, Id, Id) { Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
// TODO return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicInc32);
} }
Id EmitBufferAtomicDec32(EmitContext&, IR::Inst*, u32, Id, Id) { Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
// TODO return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIDecrement);
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicDec32);
} }
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
@ -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); 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) { Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value) {
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicIAdd); 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 <magic_enum/magic_enum.hpp>
#include "emit_spirv_bounds.h"
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
namespace { namespace {
@ -239,8 +241,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) {
} }
if (IR::IsParam(attr)) { if (IR::IsParam(attr)) {
const u32 index{u32(attr) - u32(IR::Attribute::Param0)}; const u32 param_index{u32(attr) - u32(IR::Attribute::Param0)};
const auto& param{ctx.input_params.at(index)}; const auto& param{ctx.input_params.at(param_index)};
if (param.buffer_handle >= 0) { if (param.buffer_handle >= 0) {
const auto step_rate = EmitReadStepRate(ctx, param.id.value); const auto step_rate = EmitReadStepRate(ctx, param.id.value);
const auto offset = ctx.OpIAdd( const auto offset = ctx.OpIAdd(
@ -415,27 +417,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
ctx.OpStore(pointer, 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> template <u32 N, PointerType alias>
static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
const auto flags = inst->Flags<IR::BufferInstInfo>(); 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); const Id result_i = ctx.OpLoad(data_types[1], ptr_i);
if (!flags.typed) { if (!flags.typed) {
// Untyped loads have bounds checking per-component. // Untyped loads have bounds checking per-component.
ids.push_back(EmitLoadBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, ids.push_back(LoadAccessBoundsCheck < 32, 1,
result_i, alias == PointerType::F32)); alias ==
PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i));
} else { } else {
ids.push_back(result_i); 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); const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids);
if (flags.typed) { if (flags.typed) {
// Typed loads have single bounds check for the whole load. // Typed loads have single bounds check for the whole load.
return EmitLoadBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, result, return LoadAccessBoundsCheck < 32, N,
alias == PointerType::F32); alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result);
} }
return 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 auto [id, pointer_type] = spv_buffer[PointerType::U8];
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, ptr))}; const Id result{ctx.OpLoad(ctx.U8, ptr)};
return EmitLoadBufferBoundsCheck<1>(ctx, address, spv_buffer.size, result, false); return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result);
} }
Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { 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 auto [id, pointer_type] = spv_buffer[PointerType::U16];
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); 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 ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, ptr))}; const Id result{ctx.OpLoad(ctx.U16, ptr)};
return EmitLoadBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, result, false); return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result);
} }
Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) { 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); 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) { Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
return EmitLoadBufferB32xN<1, PointerType::F32>(ctx, inst, handle, 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"); 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> template <u32 N, PointerType alias>
static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
Id value) { Id value) {
@ -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 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 ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i);
const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i); const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i);
auto store_i = [&]() { ctx.OpStore(ptr_i, value_i); }; auto store_i = [&] {
ctx.OpStore(ptr_i, value_i);
return Id{};
};
if (!flags.typed) { if (!flags.typed) {
// Untyped stores have bounds checking per-component. // 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 { } else {
store_i(); store_i();
} }
} }
return Id{};
}; };
if (flags.typed) { if (flags.typed) {
// Typed stores have single bounds check for the whole store. // Typed stores have single bounds check for the whole store.
EmitStoreBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, store); AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords,
store);
} else { } else {
store(); 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 auto [id, pointer_type] = spv_buffer[PointerType::U8];
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)}; const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
const Id result{ctx.OpUConvert(ctx.U8, value)}; AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] {
EmitStoreBufferBoundsCheck<1>(ctx, address, spv_buffer.size, [&] { ctx.OpStore(ptr, result); }); ctx.OpStore(ptr, value);
return Id{};
});
} }
void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) { 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 auto [id, pointer_type] = spv_buffer[PointerType::U16];
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u)); 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 ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
const Id result{ctx.OpUConvert(ctx.U16, value)}; AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] {
EmitStoreBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, ctx.OpStore(ptr, value);
[&] { ctx.OpStore(ptr, result); }); return Id{};
});
} }
void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) { 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); 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) { void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
EmitStoreBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address, 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); 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 } // 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 EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferU32x3(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 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 EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitLoadBufferF32x2(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); 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 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 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 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 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 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 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 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); 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 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 EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicInc32(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 value); Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value); Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
Id EmitBufferAtomicOr32(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 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 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 EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index);
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp); Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp);
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, 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 EmitUndefU16(EmitContext& ctx);
Id EmitUndefU32(EmitContext& ctx); Id EmitUndefU32(EmitContext& ctx);
Id EmitUndefU64(EmitContext& ctx); Id EmitUndefU64(EmitContext& ctx);
Id EmitLoadSharedU16(EmitContext& ctx, Id offset);
Id EmitLoadSharedU32(EmitContext& ctx, Id offset); Id EmitLoadSharedU32(EmitContext& ctx, Id offset);
Id EmitLoadSharedU64(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 EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value);
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value); void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicIAdd32(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 EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicUMin32(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 EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value);
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset); Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset); Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset);
Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value); Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value);
Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2); 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 EmitBitwiseNot32(EmitContext& ctx, Id value);
Id EmitFindSMsb32(EmitContext& ctx, Id value); Id EmitFindSMsb32(EmitContext& ctx, Id value);
Id EmitFindUMsb32(EmitContext& ctx, Id value); Id EmitFindUMsb32(EmitContext& ctx, Id value);
Id EmitFindUMsb64(EmitContext& ctx, Id value);
Id EmitFindILsb32(EmitContext& ctx, Id value); Id EmitFindILsb32(EmitContext& ctx, Id value);
Id EmitFindILsb64(EmitContext& ctx, Id value); Id EmitFindILsb64(EmitContext& ctx, Id value);
Id EmitSMin32(EmitContext& ctx, Id a, Id b); 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 EmitConvertF64U64(EmitContext& ctx, Id value);
Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value);
Id EmitConvertU32U16(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 EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
Id address3, Id address4); Id address3, Id address4);

View file

@ -229,6 +229,20 @@ Id EmitFindUMsb32(EmitContext& ctx, Id value) {
return ctx.OpFindUMsb(ctx.U32[1], 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) { Id EmitFindILsb32(EmitContext& ctx, Id value) {
return ctx.OpFindILsb(ctx.U32[1], value); return ctx.OpFindILsb(ctx.U32[1], value);
} }

View file

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

View file

@ -146,6 +146,7 @@ void EmitContext::DefineArithmeticTypes() {
false_value = ConstantFalse(U1[1]); false_value = ConstantFalse(U1[1]);
u8_one_value = Constant(U8, 1U); u8_one_value = Constant(U8, 1U);
u8_zero_value = Constant(U8, 0U); u8_zero_value = Constant(U8, 0U);
u16_zero_value = Constant(U16, 0U);
u32_one_value = ConstU32(1U); u32_one_value = ConstU32(1U);
u32_zero_value = ConstU32(0U); u32_zero_value = ConstU32(0U);
f32_zero_value = ConstF32(0.0f); f32_zero_value = ConstF32(0.0f);
@ -285,6 +286,8 @@ void EmitContext::DefineBufferProperties() {
Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding)); Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding));
buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U)); buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U));
Name(buffer.size_dwords, fmt::format("buf{}_dword_size", binding)); 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. // Iterate all input attributes, load them and manually interpolate.
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) { for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
const auto& input = runtime_info.fs_info.inputs[i]; const auto& input = runtime_info.fs_info.inputs[i];
const u32 semantic = input.param_index; auto& params = input_params[i];
auto& params = input_params[semantic];
if (input.is_flat || params.is_loaded) { if (input.is_flat || params.is_loaded) {
continue; continue;
} }
@ -307,13 +309,15 @@ void EmitContext::DefineInterpolatedAttribs() {
const Id p2{OpCompositeExtract(F32[4], p_array, 2U)}; const Id p2{OpCompositeExtract(F32[4], p_array, 2U)};
const Id p10{OpFSub(F32[4], p1, p0)}; const Id p10{OpFSub(F32[4], p1, p0)};
const Id p20{OpFSub(F32[4], p2, 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_y{OpCompositeExtract(F32[1], bary_coord, 1)};
const Id bary_coord_z{OpCompositeExtract(F32[1], bary_coord, 2)}; const Id bary_coord_z{OpCompositeExtract(F32[1], bary_coord, 2)};
const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)}; const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)};
const Id p20_z{OpVectorTimesScalar(F32[4], p20, bary_coord_z)}; const Id p20_z{OpVectorTimesScalar(F32[4], p20, bary_coord_z)};
params.id = OpFAdd(F32[4], p0, OpFAdd(F32[4], p10_y, p20_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; params.is_loaded = true;
} }
} }
@ -411,35 +415,47 @@ void EmitContext::DefineInputs() {
DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input);
} }
if (profile.needs_manual_interpolation) { if (profile.needs_manual_interpolation) {
gl_bary_coord_id = if (info.has_perspective_interp) {
DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input); 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++) { for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
const auto& input = runtime_info.fs_info.inputs[i]; const auto& input = runtime_info.fs_info.inputs[i];
const u32 semantic = input.param_index;
ASSERT(semantic < IR::NumParams);
if (input.IsDefault()) { if (input.IsDefault()) {
input_params[semantic] = { input_params[i] = {
MakeDefaultValue(*this, input.default_value), input_f32, F32[1], 4, false, true, .id = MakeDefaultValue(*this, input.default_value),
.pointer_type = input_f32,
.component_type = F32[1],
.num_components = 4,
.is_integer = false,
.is_loaded = true,
}; };
continue; 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 u32 num_components = info.loads.NumComponents(param);
const Id type{F32[num_components]}; const Id type{F32[num_components]};
Id attr_id{}; Id attr_id{};
if (profile.needs_manual_interpolation && !input.is_flat) { 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); 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 { } else {
attr_id = DefineInput(type, semantic); attr_id = DefineInput(type, input.param_index);
Name(attr_id, fmt::format("fs_in_attr{}", semantic)); 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);
}
} }
if (input.is_flat) { input_params[i] =
Decorate(attr_id, spv::Decoration::Flat);
}
input_params[semantic] =
GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false); GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false);
} }
break; break;
@ -634,7 +650,8 @@ void EmitContext::DefineOutputs() {
} }
break; break;
} }
case LogicalStage::Fragment: case LogicalStage::Fragment: {
u32 num_render_targets = 0;
for (u32 i = 0; i < IR::NumRenderTargets; i++) { for (u32 i = 0; i < IR::NumRenderTargets; i++) {
const IR::Attribute mrt{IR::Attribute::RenderTarget0 + i}; const IR::Attribute mrt{IR::Attribute::RenderTarget0 + i};
if (!info.stores.GetAny(mrt)) { if (!info.stores.GetAny(mrt)) {
@ -643,11 +660,21 @@ void EmitContext::DefineOutputs() {
const u32 num_components = info.stores.NumComponents(mrt); const u32 num_components = info.stores.NumComponents(mrt);
const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format}; 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 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)); Name(id, fmt::format("frag_color{}", i));
frag_outputs[i] = GetAttributeInfo(num_format, id, num_components, true); 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; break;
}
case LogicalStage::Geometry: { case LogicalStage::Geometry: {
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output); output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
@ -952,18 +979,46 @@ void EmitContext::DefineImagesAndSamplers() {
} }
void EmitContext::DefineSharedMemory() { void EmitContext::DefineSharedMemory() {
if (!info.uses_shared) { const auto num_types = std::popcount(static_cast<u32>(info.shared_types));
if (num_types == 0) {
return; return;
} }
ASSERT(info.stage == Stage::Compute); ASSERT(info.stage == Stage::Compute);
const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size; 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))}; const auto make_type = [&](IR::Type type, Id element_type, u32 element_size,
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type); std::string_view name) {
shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]); if (False(info.shared_types & type)) {
shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup); // Skip unused shared memory types.
Name(shared_memory_u32, "shared_mem"); return std::make_tuple(Id{}, Id{}, Id{});
interfaces.push_back(shared_memory_u32); }
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) { Id EmitContext::DefineFloat32ToUfloatM5(u32 mantissa_bits, const std::string_view name) {

View file

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

View file

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

View file

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

View file

@ -13,6 +13,8 @@ void Translator::EmitDataShare(const GcnInst& inst) {
// DS // DS
case Opcode::DS_ADD_U32: case Opcode::DS_ADD_U32:
return DS_ADD_U32(inst, false); return DS_ADD_U32(inst, false);
case Opcode::DS_ADD_U64:
return DS_ADD_U64(inst, false);
case Opcode::DS_SUB_U32: case Opcode::DS_SUB_U32:
return DS_SUB_U32(inst, false); return DS_SUB_U32(inst, false);
case Opcode::DS_INC_U32: case Opcode::DS_INC_U32:
@ -61,10 +63,14 @@ void Translator::EmitDataShare(const GcnInst& inst) {
return DS_READ(32, false, true, false, inst); return DS_READ(32, false, true, false, inst);
case Opcode::DS_READ2ST64_B32: case Opcode::DS_READ2ST64_B32:
return DS_READ(32, false, true, true, inst); 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: case Opcode::DS_CONSUME:
return DS_CONSUME(inst); return DS_CONSUME(inst);
case Opcode::DS_APPEND: case Opcode::DS_APPEND:
return DS_APPEND(inst); return DS_APPEND(inst);
case Opcode::DS_WRITE_B16:
return DS_WRITE(16, false, false, false, inst);
case Opcode::DS_WRITE_B64: case Opcode::DS_WRITE_B64:
return DS_WRITE(64, false, false, false, inst); return DS_WRITE(64, false, false, false, inst);
case Opcode::DS_WRITE2_B64: 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) { void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) {
const IR::U32 addr{GetSrc(inst.src[0])}; const IR::U32 addr{GetSrc(inst.src[0])};
const IR::U32 data{GetSrc(inst.src[1])}; 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) { if (is_pair) {
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1); 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::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
if (bit_size == 32) { 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); ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
} else { } else if (bit_size == 16) {
ir.WriteShared( ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
addr0);
} }
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
if (bit_size == 32) { 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); ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
} else { } else if (bit_size == 16) {
ir.WriteShared( ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1);
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
addr1);
} }
} else if (bit_size == 64) {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
const IR::Value data =
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
ir.WriteShared(bit_size, data, addr0);
} else { } else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0); if (bit_size == 64) {
const IR::Value data =
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
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 = const IR::U32 offset =
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset); 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) { if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val}); 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 = const IR::U32 offset =
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0)); ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
const IR::U32 addr_offset = ir.IAdd(addr, offset); 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) { if (rtn) {
SetDst(inst.dst[0], IR::U32{original_val}); 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 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::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0); 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}); ir.SetVectorReg(dst_reg++, IR::U32{data0});
} else { } else if (bit_size == 16) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)}); ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)});
} }
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj))); 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); 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}); ir.SetVectorReg(dst_reg++, IR::U32{data1});
} else { } else if (bit_size == 16) {
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)}); ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})});
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)});
} }
} else if (bit_size == 64) {
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 { } else {
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset)); const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)}; const IR::Value data = 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) { 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); 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 color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
AmdGpu::NumberFormat num_format; 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) { 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); 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 color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
const auto swizzled_comp = SwizzleMrtComponent(color_buffer, comp); 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); return S_FF1_I32_B64(inst);
case Opcode::S_FLBIT_I32_B32: case Opcode::S_FLBIT_I32_B32:
return S_FLBIT_I32_B32(inst); return S_FLBIT_I32_B32(inst);
case Opcode::S_FLBIT_I32_B64:
return S_FLBIT_I32_B64(inst);
case Opcode::S_BITSET0_B32: case Opcode::S_BITSET0_B32:
return S_BITSET_B32(inst, 0); return S_BITSET_B32(inst, 0);
case Opcode::S_BITSET1_B32: 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))}); 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) { void Translator::S_BITSET_B32(const GcnInst& inst, u32 bit_value) {
const IR::U32 old_value{GetSrc(inst.dst[0])}; 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))}; 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 { namespace Shader::Gcn {
static u32 next_vgpr_num; Translator::Translator(Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
static std::unordered_map<u32, IR::VectorReg> vgpr_map; : info{info_}, runtime_info{runtime_info_}, profile{profile_},
next_vgpr_num{runtime_info.num_allocated_vgprs} {
Translator::Translator(IR::Block* block_, Info& info_, const RuntimeInfo& runtime_info_, if (info.l_stage == LogicalStage::Fragment) {
const Profile& profile_) dst_frag_vreg = GatherInterpQualifiers();
: 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;
} }
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.Prologue();
ir.SetExec(ir.Imm1(true)); ir.SetExec(ir.Imm1(true));
@ -60,39 +104,7 @@ void Translator::EmitPrologue() {
} }
break; break;
case LogicalStage::Fragment: case LogicalStage::Fragment:
dst_vreg = IR::VectorReg::V0; dst_vreg = dst_frag_vreg;
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;
}
if (runtime_info.fs_info.addr_flags.pos_x_float_ena) { if (runtime_info.fs_info.addr_flags.pos_x_float_ena) {
if (runtime_info.fs_info.en_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)); ir.SetVectorReg(dst_vreg++, ir.GetAttribute(IR::Attribute::FragCoord, 0));
@ -543,6 +555,26 @@ void Translator::LogMissingOpcode(const GcnInst& inst) {
info.translation_failed = true; 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) { void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) {
// Emit instructions for each category. // Emit instructions for each category.
switch (inst.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 } // namespace Shader::Gcn

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