mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-13 21:25:59 +00:00
Merge branch 'shadps4-emu:main' into shader_cache
This commit is contained in:
commit
cc911b390a
115 changed files with 7088 additions and 1915 deletions
|
@ -871,6 +871,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
|
||||||
|
@ -967,6 +968,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
|
||||||
|
@ -1056,6 +1058,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}
|
||||||
|
@ -1121,6 +1127,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})
|
||||||
|
@ -1131,9 +1141,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}
|
||||||
|
@ -1148,17 +1155,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)
|
||||||
|
|
13
README.md
13
README.md
|
@ -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
|
||||||
|
|
|
@ -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 \
|
||||||
|
|
2
externals/MoltenVK/MoltenVK
vendored
2
externals/MoltenVK/MoltenVK
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 3a0b07a24a4a681ffe70b461b1f4333b2729e2ef
|
Subproject commit 00abd384ce01cbd439045905d2fa6cf799dfa2f6
|
2
externals/MoltenVK/SPIRV-Cross
vendored
2
externals/MoltenVK/SPIRV-Cross
vendored
|
@ -1 +1 @@
|
||||||
Subproject commit 969e75f7cc0718774231d029f9d52fa87d4ae1b2
|
Subproject commit 1a69a919fa302e92b337594bd0a8aaea61037d91
|
|
@ -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() {
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -1050,6 +1050,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);
|
||||||
|
|
|
@ -99,7 +99,7 @@ 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) {
|
||||||
|
@ -287,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);
|
||||||
}
|
}
|
||||||
|
@ -660,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
|
@ -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?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -332,20 +332,21 @@ 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) {
|
if (record) {
|
||||||
|
*return_info = *record;
|
||||||
|
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Check if it an export function
|
// Check if it an export function
|
||||||
const auto* p = FindExportedModule(*module, *library);
|
const auto* p = FindExportedModule(*module, *library);
|
||||||
if (p && p->export_sym.GetSize() > 0) {
|
if (p && p->export_sym.GetSize() > 0) {
|
||||||
record = p->export_sym.FindSymbol(sr);
|
record = p->export_sym.FindSymbol(sr);
|
||||||
}
|
|
||||||
}
|
|
||||||
if (record) {
|
if (record) {
|
||||||
*return_info = *record;
|
*return_info = *record;
|
||||||
|
|
||||||
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
|
const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
|
||||||
if (aeronid) {
|
if (aeronid) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
#include <set>
|
#include <set>
|
||||||
#include "common/singleton.h"
|
#include "common/singleton.h"
|
||||||
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -75,7 +75,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;
|
||||||
|
|
26
src/main.cpp
26
src/main.cpp
|
@ -35,16 +35,19 @@ 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
|
||||||
|
<< "Usage: shadps4 [options] <elf or eboot.bin path>\n"
|
||||||
"Options:\n"
|
"Options:\n"
|
||||||
" -g, --game <path|ID> Specify game path to launch\n"
|
" -g, --game <path|ID> Specify game path to launch\n"
|
||||||
" -- ... Parameters passed to the game ELF. "
|
" -- ... Parameters passed to the game ELF. "
|
||||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||||
"game argument.\n"
|
"game argument.\n"
|
||||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||||
|
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||||
"state. Does not overwrite the config file.\n"
|
"state. Does not overwrite the config file.\n"
|
||||||
" --add-game-folder <folder> Adds a new game folder to the config.\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";
|
" -h, --help Display this help message\n";
|
||||||
exit(0);
|
exit(0);
|
||||||
}},
|
}},
|
||||||
|
@ -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
|
||||||
|
|
|
@ -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");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>"
|
||||||
|
@ -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");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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);
|
||||||
|
|
9
src/qt_gui/gui_settings.cpp
Normal file
9
src/qt_gui/gui_settings.cpp
Normal 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
46
src/qt_gui/gui_settings.h
Normal 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;
|
||||||
|
};
|
|
@ -35,11 +35,11 @@ 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) {
|
||||||
|
|
|
@ -41,7 +41,8 @@ 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
|
||||||
|
<< "Usage: shadps4 [options]\n"
|
||||||
"Options:\n"
|
"Options:\n"
|
||||||
" No arguments: Opens the GUI.\n"
|
" No arguments: Opens the GUI.\n"
|
||||||
" -g, --game <path|ID> Specify <eboot.bin or elf path> or "
|
" -g, --game <path|ID> Specify <eboot.bin or elf path> or "
|
||||||
|
@ -50,6 +51,7 @@ int main(int argc, char* argv[]) {
|
||||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||||
"game argument.\n"
|
"game argument.\n"
|
||||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||||
|
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||||
" -s, --show-gui Show the GUI\n"
|
" -s, --show-gui Show the GUI\n"
|
||||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||||
"state. Does not overwrite the config file.\n"
|
"state. Does not overwrite the config file.\n"
|
||||||
|
@ -84,6 +86,8 @@ int main(int argc, char* argv[]) {
|
||||||
}
|
}
|
||||||
}},
|
}},
|
||||||
{"--patch", [&](int& i) { arg_map["-p"](i); }},
|
{"--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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
77
src/qt_gui/settings.cpp
Normal 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
55
src/qt_gui/settings.h
Normal 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;
|
||||||
|
};
|
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2049,7 +2049,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>
|
||||||
|
|
2081
src/qt_gui/translations/ca_ES.ts
Normal file
2081
src/qt_gui/translations/ca_ES.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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,23 +1130,23 @@
|
||||||
</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>
|
||||||
|
@ -1170,7 +1170,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>
|
||||||
|
@ -1213,7 +1213,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 +1624,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 +1664,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 +1728,7 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Console Language:\nSets the language that the PS4 game uses.\nIt'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'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'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's user interface.</source>
|
<source>Emulator Language:\nSets the language of the emulator's user interface.</source>
|
||||||
|
@ -1748,7 +1748,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 +1756,7 @@
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Log Filter:\nFilters the log to only print specific information.\nExamples: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\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: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\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: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical"\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مثالها: "هسته:ردیابی" "Lib.Pad:اشکالزدایی Common.Filesystem:خطا" "*:بحرانی"\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 +1764,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 +1844,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>
|
||||||
|
@ -1868,11 +1868,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 +1880,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 'Device lost' 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 'Device lost' 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 +1912,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 +1936,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 +2000,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 +2032,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 +2044,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 +2075,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>
|
||||||
|
|
2081
src/qt_gui/translations/sr_CS.ts
Normal file
2081
src/qt_gui/translations/sr_CS.ts
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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) {
|
||||||
|
|
|
@ -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 AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
|
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 AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics);
|
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);
|
||||||
}
|
}
|
||||||
|
|
90
src/shader_recompiler/backend/spirv/emit_spirv_bounds.h
Normal file
90
src/shader_recompiler/backend/spirv/emit_spirv_bounds.h
Normal 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
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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);
|
||||||
|
@ -455,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);
|
||||||
|
|
|
@ -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 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);
|
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)};
|
||||||
|
|
||||||
|
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);
|
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
|
||||||
|
|
|
@ -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) {
|
||||||
|
bary_coord_persp_id =
|
||||||
DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input);
|
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) {
|
if (input.is_flat) {
|
||||||
Decorate(attr_id, spv::Decoration::Flat);
|
Decorate(attr_id, spv::Decoration::Flat);
|
||||||
|
} else if (IsLinear(info.interp_qualifiers[i])) {
|
||||||
|
Decorate(attr_id, spv::Decoration::NoPerspective);
|
||||||
}
|
}
|
||||||
input_params[semantic] =
|
}
|
||||||
|
input_params[i] =
|
||||||
GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false);
|
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) {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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(32, ir.GetVectorReg(data0), addr0);
|
ir.WriteShared(64,
|
||||||
} else {
|
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0),
|
||||||
ir.WriteShared(
|
ir.GetVectorReg(data0 + 1))),
|
||||||
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
|
|
||||||
addr0);
|
addr0);
|
||||||
|
} else if (bit_size == 32) {
|
||||||
|
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
|
||||||
|
} else if (bit_size == 16) {
|
||||||
|
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
|
||||||
}
|
}
|
||||||
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
|
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(32, ir.GetVectorReg(data1), addr1);
|
ir.WriteShared(64,
|
||||||
} else {
|
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1),
|
||||||
ir.WriteShared(
|
ir.GetVectorReg(data1 + 1))),
|
||||||
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
|
|
||||||
addr1);
|
addr1);
|
||||||
|
} else if (bit_size == 32) {
|
||||||
|
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
|
||||||
|
} else if (bit_size == 16) {
|
||||||
|
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1);
|
||||||
}
|
}
|
||||||
} else if (bit_size == 64) {
|
} else {
|
||||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||||
|
if (bit_size == 64) {
|
||||||
const IR::Value data =
|
const IR::Value data =
|
||||||
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
|
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
|
||||||
ir.WriteShared(bit_size, data, addr0);
|
ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0);
|
||||||
} else {
|
} else if (bit_size == 32) {
|
||||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
|
||||||
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
|
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) {
|
} else {
|
||||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||||
const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0);
|
const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0);
|
||||||
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)});
|
if (bit_size == 64) {
|
||||||
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)});
|
const auto vector = ir.UnpackUint2x32(IR::U64{data});
|
||||||
} else {
|
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)});
|
||||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)});
|
||||||
const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)};
|
} else if (bit_size == 32) {
|
||||||
ir.SetVectorReg(dst_reg, data);
|
ir.SetVectorReg(dst_reg, IR::U32{data});
|
||||||
|
} else if (bit_size == 16) {
|
||||||
|
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <span>
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
#include "shader_recompiler/frontend/instruction.h"
|
#include "shader_recompiler/frontend/instruction.h"
|
||||||
#include "shader_recompiler/info.h"
|
#include "shader_recompiler/info.h"
|
||||||
#include "shader_recompiler/ir/basic_block.h"
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
|
@ -53,15 +54,17 @@ enum class NegateMode : u32 {
|
||||||
Result,
|
Result,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static constexpr size_t MaxInterpVgpr = 16;
|
||||||
|
|
||||||
class Translator {
|
class Translator {
|
||||||
public:
|
public:
|
||||||
explicit Translator(IR::Block* block_, Info& info, const RuntimeInfo& runtime_info,
|
explicit Translator(Info& info, const RuntimeInfo& runtime_info, const Profile& profile);
|
||||||
const Profile& profile);
|
|
||||||
|
|
||||||
|
void Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list);
|
||||||
void TranslateInstruction(const GcnInst& inst, u32 pc);
|
void TranslateInstruction(const GcnInst& inst, u32 pc);
|
||||||
|
|
||||||
// Instruction categories
|
// Instruction categories
|
||||||
void EmitPrologue();
|
void EmitPrologue(IR::Block* first_block);
|
||||||
void EmitFetch(const GcnInst& inst);
|
void EmitFetch(const GcnInst& inst);
|
||||||
void EmitExport(const GcnInst& inst);
|
void EmitExport(const GcnInst& inst);
|
||||||
void EmitFlowControl(u32 pc, const GcnInst& inst);
|
void EmitFlowControl(u32 pc, const GcnInst& inst);
|
||||||
|
@ -268,6 +271,7 @@ public:
|
||||||
// Data share
|
// Data share
|
||||||
// DS
|
// DS
|
||||||
void DS_ADD_U32(const GcnInst& inst, bool rtn);
|
void DS_ADD_U32(const GcnInst& inst, bool rtn);
|
||||||
|
void DS_ADD_U64(const GcnInst& inst, bool rtn);
|
||||||
void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn);
|
void DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn);
|
||||||
void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn);
|
void DS_MAX_U32(const GcnInst& inst, bool is_signed, bool rtn);
|
||||||
void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
|
void DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool stride64, const GcnInst& inst);
|
||||||
|
@ -326,16 +330,18 @@ private:
|
||||||
void LogMissingOpcode(const GcnInst& inst);
|
void LogMissingOpcode(const GcnInst& inst);
|
||||||
|
|
||||||
IR::VectorReg GetScratchVgpr(u32 offset);
|
IR::VectorReg GetScratchVgpr(u32 offset);
|
||||||
|
IR::VectorReg GatherInterpQualifiers();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
IR::IREmitter ir;
|
IR::IREmitter ir;
|
||||||
Info& info;
|
Info& info;
|
||||||
const RuntimeInfo& runtime_info;
|
const RuntimeInfo& runtime_info;
|
||||||
const Profile& profile;
|
const Profile& profile;
|
||||||
|
u32 next_vgpr_num;
|
||||||
|
std::unordered_map<u32, IR::VectorReg> vgpr_map;
|
||||||
|
std::array<IR::Interpolation, MaxInterpVgpr> vgpr_to_interp{};
|
||||||
|
IR::VectorReg dst_frag_vreg{};
|
||||||
bool opcode_missing = false;
|
bool opcode_missing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
void Translate(IR::Block* block, u32 block_base, std::span<const GcnInst> inst_list, Info& info,
|
|
||||||
const RuntimeInfo& runtime_info, const Profile& profile);
|
|
||||||
|
|
||||||
} // namespace Shader::Gcn
|
} // namespace Shader::Gcn
|
||||||
|
|
|
@ -22,14 +22,17 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) {
|
||||||
// VINTRP
|
// VINTRP
|
||||||
|
|
||||||
void Translator::V_INTERP_P2_F32(const GcnInst& inst) {
|
void Translator::V_INTERP_P2_F32(const GcnInst& inst) {
|
||||||
auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr);
|
const u32 attr_index = inst.control.vintrp.attr;
|
||||||
const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index};
|
const auto& attr = runtime_info.fs_info.inputs.at(attr_index);
|
||||||
|
info.interp_qualifiers[attr_index] = vgpr_to_interp[inst.src[0].code];
|
||||||
|
const IR::Attribute attrib{IR::Attribute::Param0 + attr_index};
|
||||||
SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan));
|
SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Translator::V_INTERP_MOV_F32(const GcnInst& inst) {
|
void Translator::V_INTERP_MOV_F32(const GcnInst& inst) {
|
||||||
auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr);
|
const u32 attr_index = inst.control.vintrp.attr;
|
||||||
const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index};
|
const auto& attr = runtime_info.fs_info.inputs.at(attr_index);
|
||||||
|
const IR::Attribute attrib{IR::Attribute::Param0 + attr_index};
|
||||||
SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan));
|
SetDst(inst.dst[0], ir.GetAttribute(attrib, inst.control.vintrp.chan));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -70,6 +70,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
||||||
return BUFFER_ATOMIC(AtomicOp::Add, inst);
|
return BUFFER_ATOMIC(AtomicOp::Add, inst);
|
||||||
case Opcode::BUFFER_ATOMIC_SWAP:
|
case Opcode::BUFFER_ATOMIC_SWAP:
|
||||||
return BUFFER_ATOMIC(AtomicOp::Swap, inst);
|
return BUFFER_ATOMIC(AtomicOp::Swap, inst);
|
||||||
|
case Opcode::BUFFER_ATOMIC_CMPSWAP:
|
||||||
|
return BUFFER_ATOMIC(AtomicOp::CmpSwap, inst);
|
||||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||||
return BUFFER_ATOMIC(AtomicOp::Smin, inst);
|
return BUFFER_ATOMIC(AtomicOp::Smin, inst);
|
||||||
case Opcode::BUFFER_ATOMIC_UMIN:
|
case Opcode::BUFFER_ATOMIC_UMIN:
|
||||||
|
@ -331,6 +333,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
||||||
switch (op) {
|
switch (op) {
|
||||||
case AtomicOp::Swap:
|
case AtomicOp::Swap:
|
||||||
return ir.BufferAtomicSwap(handle, address, vdata_val, buffer_info);
|
return ir.BufferAtomicSwap(handle, address, vdata_val, buffer_info);
|
||||||
|
case AtomicOp::CmpSwap: {
|
||||||
|
const IR::Value cmp_val = ir.GetVectorReg(vdata + 1);
|
||||||
|
return ir.BufferAtomicCmpSwap(handle, address, vdata_val, cmp_val, buffer_info);
|
||||||
|
}
|
||||||
case AtomicOp::Add:
|
case AtomicOp::Add:
|
||||||
return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info);
|
return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info);
|
||||||
case AtomicOp::Smin:
|
case AtomicOp::Smin:
|
||||||
|
@ -348,9 +354,9 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
||||||
case AtomicOp::Xor:
|
case AtomicOp::Xor:
|
||||||
return ir.BufferAtomicXor(handle, address, vdata_val, buffer_info);
|
return ir.BufferAtomicXor(handle, address, vdata_val, buffer_info);
|
||||||
case AtomicOp::Inc:
|
case AtomicOp::Inc:
|
||||||
return ir.BufferAtomicInc(handle, address, vdata_val, buffer_info);
|
return ir.BufferAtomicInc(handle, address, buffer_info);
|
||||||
case AtomicOp::Dec:
|
case AtomicOp::Dec:
|
||||||
return ir.BufferAtomicDec(handle, address, vdata_val, buffer_info);
|
return ir.BufferAtomicDec(handle, address, buffer_info);
|
||||||
default:
|
default:
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,6 +193,8 @@ struct Info {
|
||||||
PersistentSrtInfo srt_info;
|
PersistentSrtInfo srt_info;
|
||||||
std::vector<u32> flattened_ud_buf;
|
std::vector<u32> flattened_ud_buf;
|
||||||
|
|
||||||
|
std::array<IR::Interpolation, 32> interp_qualifiers{};
|
||||||
|
|
||||||
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
||||||
s32 tess_consts_dword_offset = -1;
|
s32 tess_consts_dword_offset = -1;
|
||||||
|
|
||||||
|
@ -206,11 +208,13 @@ struct Info {
|
||||||
bool has_discard{};
|
bool has_discard{};
|
||||||
bool has_image_gather{};
|
bool has_image_gather{};
|
||||||
bool has_image_query{};
|
bool has_image_query{};
|
||||||
|
bool has_perspective_interp{};
|
||||||
|
bool has_linear_interp{};
|
||||||
bool uses_atomic_float_min_max{};
|
bool uses_atomic_float_min_max{};
|
||||||
bool uses_lane_id{};
|
bool uses_lane_id{};
|
||||||
bool uses_group_quad{};
|
bool uses_group_quad{};
|
||||||
bool uses_group_ballot{};
|
bool uses_group_ballot{};
|
||||||
bool uses_shared{};
|
IR::Type shared_types{};
|
||||||
bool uses_fp16{};
|
bool uses_fp16{};
|
||||||
bool uses_fp64{};
|
bool uses_fp64{};
|
||||||
bool uses_pack_10_11_11{};
|
bool uses_pack_10_11_11{};
|
||||||
|
|
|
@ -83,6 +83,16 @@ enum class Attribute : u64 {
|
||||||
Max,
|
Max,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class Interpolation {
|
||||||
|
Invalid = 0,
|
||||||
|
PerspectiveSample = 1,
|
||||||
|
PerspectiveCenter = 2,
|
||||||
|
PerspectiveCentroid = 3,
|
||||||
|
LinearSample = 4,
|
||||||
|
LinearCenter = 5,
|
||||||
|
LinearCentroid = 6,
|
||||||
|
};
|
||||||
|
|
||||||
constexpr size_t NumAttributes = static_cast<size_t>(Attribute::Max);
|
constexpr size_t NumAttributes = static_cast<size_t>(Attribute::Max);
|
||||||
constexpr size_t NumRenderTargets = 8;
|
constexpr size_t NumRenderTargets = 8;
|
||||||
constexpr size_t NumParams = 32;
|
constexpr size_t NumParams = 32;
|
||||||
|
@ -104,6 +114,15 @@ constexpr bool IsMrt(Attribute attribute) noexcept {
|
||||||
return attribute >= Attribute::RenderTarget0 && attribute <= Attribute::RenderTarget7;
|
return attribute >= Attribute::RenderTarget0 && attribute <= Attribute::RenderTarget7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
constexpr bool IsLinear(Interpolation interp) noexcept {
|
||||||
|
return interp >= Interpolation::LinearSample && interp <= Interpolation::LinearCentroid;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsPerspective(Interpolation interp) noexcept {
|
||||||
|
return interp >= Interpolation::PerspectiveSample &&
|
||||||
|
interp <= Interpolation::PerspectiveCentroid;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::string NameOf(Attribute attribute);
|
[[nodiscard]] std::string NameOf(Attribute attribute);
|
||||||
|
|
||||||
[[nodiscard]] constexpr Attribute operator+(Attribute attr, int num) {
|
[[nodiscard]] constexpr Attribute operator+(Attribute attr, int num) {
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <bit>
|
|
||||||
#include <source_location>
|
#include <source_location>
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
@ -294,10 +293,12 @@ void IREmitter::SetPatch(Patch patch, const F32& value) {
|
||||||
|
|
||||||
Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) {
|
Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) {
|
||||||
switch (bit_size) {
|
switch (bit_size) {
|
||||||
|
case 16:
|
||||||
|
return Inst<U16>(Opcode::LoadSharedU16, offset);
|
||||||
case 32:
|
case 32:
|
||||||
return Inst<U32>(Opcode::LoadSharedU32, offset);
|
return Inst<U32>(Opcode::LoadSharedU32, offset);
|
||||||
case 64:
|
case 64:
|
||||||
return Inst(Opcode::LoadSharedU64, offset);
|
return Inst<U64>(Opcode::LoadSharedU64, offset);
|
||||||
default:
|
default:
|
||||||
UNREACHABLE_MSG("Invalid bit size {}", bit_size);
|
UNREACHABLE_MSG("Invalid bit size {}", bit_size);
|
||||||
}
|
}
|
||||||
|
@ -305,6 +306,9 @@ Value IREmitter::LoadShared(int bit_size, bool is_signed, const U32& offset) {
|
||||||
|
|
||||||
void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) {
|
void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset) {
|
||||||
switch (bit_size) {
|
switch (bit_size) {
|
||||||
|
case 16:
|
||||||
|
Inst(Opcode::WriteSharedU16, offset, value);
|
||||||
|
break;
|
||||||
case 32:
|
case 32:
|
||||||
Inst(Opcode::WriteSharedU32, offset, value);
|
Inst(Opcode::WriteSharedU32, offset, value);
|
||||||
break;
|
break;
|
||||||
|
@ -316,10 +320,12 @@ void IREmitter::WriteShared(int bit_size, const Value& value, const U32& offset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
U32F32 IREmitter::SharedAtomicIAdd(const U32& address, const U32F32& data) {
|
U32U64 IREmitter::SharedAtomicIAdd(const U32& address, const U32U64& data) {
|
||||||
switch (data.Type()) {
|
switch (data.Type()) {
|
||||||
case Type::U32:
|
case Type::U32:
|
||||||
return Inst<U32>(Opcode::SharedAtomicIAdd32, address, data);
|
return Inst<U32>(Opcode::SharedAtomicIAdd32, address, data);
|
||||||
|
case Type::U64:
|
||||||
|
return Inst<U64>(Opcode::SharedAtomicIAdd64, address, data);
|
||||||
default:
|
default:
|
||||||
ThrowInvalidType(data.Type());
|
ThrowInvalidType(data.Type());
|
||||||
}
|
}
|
||||||
|
@ -347,12 +353,12 @@ U32 IREmitter::SharedAtomicXor(const U32& address, const U32& data) {
|
||||||
return Inst<U32>(Opcode::SharedAtomicXor32, address, data);
|
return Inst<U32>(Opcode::SharedAtomicXor32, address, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::SharedAtomicIIncrement(const U32& address) {
|
U32 IREmitter::SharedAtomicInc(const U32& address) {
|
||||||
return Inst<U32>(Opcode::SharedAtomicIIncrement32, address);
|
return Inst<U32>(Opcode::SharedAtomicInc32, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::SharedAtomicIDecrement(const U32& address) {
|
U32 IREmitter::SharedAtomicDec(const U32& address) {
|
||||||
return Inst<U32>(Opcode::SharedAtomicIDecrement32, address);
|
return Inst<U32>(Opcode::SharedAtomicDec32, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::SharedAtomicISub(const U32& address, const U32& data) {
|
U32 IREmitter::SharedAtomicISub(const U32& address, const U32& data) {
|
||||||
|
@ -367,12 +373,12 @@ U32 IREmitter::ReadConstBuffer(const Value& handle, const U32& index) {
|
||||||
return Inst<U32>(Opcode::ReadConstBuffer, handle, index);
|
return Inst<U32>(Opcode::ReadConstBuffer, handle, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) {
|
U8 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
return Inst<U32>(Opcode::LoadBufferU8, Flags{info}, handle, address);
|
return Inst<U8>(Opcode::LoadBufferU8, Flags{info}, handle, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
U32 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) {
|
U16 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
return Inst<U32>(Opcode::LoadBufferU16, Flags{info}, handle, address);
|
return Inst<U16>(Opcode::LoadBufferU16, Flags{info}, handle, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value IREmitter::LoadBufferU32(int num_dwords, const Value& handle, const Value& address,
|
Value IREmitter::LoadBufferU32(int num_dwords, const Value& handle, const Value& address,
|
||||||
|
@ -391,6 +397,10 @@ Value IREmitter::LoadBufferU32(int num_dwords, const Value& handle, const Value&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
U64 IREmitter::LoadBufferU64(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
|
return Inst<U64>(Opcode::LoadBufferU64, Flags{info}, handle, address);
|
||||||
|
}
|
||||||
|
|
||||||
Value IREmitter::LoadBufferF32(int num_dwords, const Value& handle, const Value& address,
|
Value IREmitter::LoadBufferF32(int num_dwords, const Value& handle, const Value& address,
|
||||||
BufferInstInfo info) {
|
BufferInstInfo info) {
|
||||||
switch (num_dwords) {
|
switch (num_dwords) {
|
||||||
|
@ -411,12 +421,12 @@ Value IREmitter::LoadBufferFormat(const Value& handle, const Value& address, Buf
|
||||||
return Inst(Opcode::LoadBufferFormatF32, Flags{info}, handle, address);
|
return Inst(Opcode::LoadBufferFormatF32, Flags{info}, handle, address);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IREmitter::StoreBufferU8(const Value& handle, const Value& address, const U32& data,
|
void IREmitter::StoreBufferU8(const Value& handle, const Value& address, const U8& data,
|
||||||
BufferInstInfo info) {
|
BufferInstInfo info) {
|
||||||
Inst(Opcode::StoreBufferU8, Flags{info}, handle, address, data);
|
Inst(Opcode::StoreBufferU8, Flags{info}, handle, address, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void IREmitter::StoreBufferU16(const Value& handle, const Value& address, const U32& data,
|
void IREmitter::StoreBufferU16(const Value& handle, const Value& address, const U16& data,
|
||||||
BufferInstInfo info) {
|
BufferInstInfo info) {
|
||||||
Inst(Opcode::StoreBufferU16, Flags{info}, handle, address, data);
|
Inst(Opcode::StoreBufferU16, Flags{info}, handle, address, data);
|
||||||
}
|
}
|
||||||
|
@ -441,6 +451,11 @@ void IREmitter::StoreBufferU32(int num_dwords, const Value& handle, const Value&
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void IREmitter::StoreBufferU64(const Value& handle, const Value& address, const U64& data,
|
||||||
|
BufferInstInfo info) {
|
||||||
|
Inst(Opcode::StoreBufferU64, Flags{info}, handle, address, data);
|
||||||
|
}
|
||||||
|
|
||||||
void IREmitter::StoreBufferF32(int num_dwords, const Value& handle, const Value& address,
|
void IREmitter::StoreBufferF32(int num_dwords, const Value& handle, const Value& address,
|
||||||
const Value& data, BufferInstInfo info) {
|
const Value& data, BufferInstInfo info) {
|
||||||
switch (num_dwords) {
|
switch (num_dwords) {
|
||||||
|
@ -468,7 +483,19 @@ void IREmitter::StoreBufferFormat(const Value& handle, const Value& address, con
|
||||||
|
|
||||||
Value IREmitter::BufferAtomicIAdd(const Value& handle, const Value& address, const Value& value,
|
Value IREmitter::BufferAtomicIAdd(const Value& handle, const Value& address, const Value& value,
|
||||||
BufferInstInfo info) {
|
BufferInstInfo info) {
|
||||||
|
switch (value.Type()) {
|
||||||
|
case Type::U32:
|
||||||
return Inst(Opcode::BufferAtomicIAdd32, Flags{info}, handle, address, value);
|
return Inst(Opcode::BufferAtomicIAdd32, Flags{info}, handle, address, value);
|
||||||
|
case Type::U64:
|
||||||
|
return Inst(Opcode::BufferAtomicIAdd64, Flags{info}, handle, address, value);
|
||||||
|
default:
|
||||||
|
ThrowInvalidType(value.Type());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Value IREmitter::BufferAtomicISub(const Value& handle, const Value& address, const Value& value,
|
||||||
|
BufferInstInfo info) {
|
||||||
|
return Inst(Opcode::BufferAtomicISub32, Flags{info}, handle, address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, const Value& value,
|
Value IREmitter::BufferAtomicIMin(const Value& handle, const Value& address, const Value& value,
|
||||||
|
@ -483,14 +510,12 @@ Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, con
|
||||||
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value);
|
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, const Value& value,
|
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
BufferInstInfo info) {
|
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address);
|
||||||
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, const Value& value,
|
Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||||
BufferInstInfo info) {
|
return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address);
|
||||||
return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address, value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Value IREmitter::BufferAtomicAnd(const Value& handle, const Value& address, const Value& value,
|
Value IREmitter::BufferAtomicAnd(const Value& handle, const Value& address, const Value& value,
|
||||||
|
@ -513,6 +538,11 @@ Value IREmitter::BufferAtomicSwap(const Value& handle, const Value& address, con
|
||||||
return Inst(Opcode::BufferAtomicSwap32, Flags{info}, handle, address, value);
|
return Inst(Opcode::BufferAtomicSwap32, Flags{info}, handle, address, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Value IREmitter::BufferAtomicCmpSwap(const Value& handle, const Value& address, const Value& vdata,
|
||||||
|
const Value& cmp_value, BufferInstInfo info) {
|
||||||
|
return Inst(Opcode::BufferAtomicCmpSwap32, Flags{info}, handle, address, vdata, cmp_value);
|
||||||
|
}
|
||||||
|
|
||||||
U32 IREmitter::DataAppend(const U32& counter) {
|
U32 IREmitter::DataAppend(const U32& counter) {
|
||||||
return Inst<U32>(Opcode::DataAppend, counter, Imm32(0));
|
return Inst<U32>(Opcode::DataAppend, counter, Imm32(0));
|
||||||
}
|
}
|
||||||
|
@ -1793,8 +1823,15 @@ F32F64 IREmitter::ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_s
|
||||||
: ConvertUToF(dest_bitsize, src_bitsize, value);
|
: ConvertUToF(dest_bitsize, src_bitsize, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) {
|
U8U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U8U16U32U64& value) {
|
||||||
switch (result_bitsize) {
|
switch (result_bitsize) {
|
||||||
|
case 8:
|
||||||
|
switch (value.Type()) {
|
||||||
|
case Type::U32:
|
||||||
|
return Inst<U8>(Opcode::ConvertU8U32, value);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
case 16:
|
case 16:
|
||||||
switch (value.Type()) {
|
switch (value.Type()) {
|
||||||
case Type::U32:
|
case Type::U32:
|
||||||
|
@ -1804,6 +1841,8 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) {
|
||||||
}
|
}
|
||||||
case 32:
|
case 32:
|
||||||
switch (value.Type()) {
|
switch (value.Type()) {
|
||||||
|
case Type::U8:
|
||||||
|
return Inst<U32>(Opcode::ConvertU32U8, value);
|
||||||
case Type::U16:
|
case Type::U16:
|
||||||
return Inst<U32>(Opcode::ConvertU32U16, value);
|
return Inst<U32>(Opcode::ConvertU32U16, value);
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
|
||||||
#include "shader_recompiler/info.h"
|
|
||||||
#include "shader_recompiler/ir/attribute.h"
|
#include "shader_recompiler/ir/attribute.h"
|
||||||
#include "shader_recompiler/ir/basic_block.h"
|
#include "shader_recompiler/ir/basic_block.h"
|
||||||
#include "shader_recompiler/ir/condition.h"
|
#include "shader_recompiler/ir/condition.h"
|
||||||
|
@ -17,6 +16,7 @@ namespace Shader::IR {
|
||||||
|
|
||||||
class IREmitter {
|
class IREmitter {
|
||||||
public:
|
public:
|
||||||
|
explicit IREmitter() = default;
|
||||||
explicit IREmitter(Block& block_) : block{&block_}, insertion_point{block->end()} {}
|
explicit IREmitter(Block& block_) : block{&block_}, insertion_point{block->end()} {}
|
||||||
explicit IREmitter(Block& block_, Block::iterator insertion_point_)
|
explicit IREmitter(Block& block_, Block::iterator insertion_point_)
|
||||||
: block{&block_}, insertion_point{insertion_point_} {}
|
: block{&block_}, insertion_point{insertion_point_} {}
|
||||||
|
@ -99,34 +99,36 @@ public:
|
||||||
[[nodiscard]] Value LoadShared(int bit_size, bool is_signed, const U32& offset);
|
[[nodiscard]] Value LoadShared(int bit_size, bool is_signed, const U32& offset);
|
||||||
void WriteShared(int bit_size, const Value& value, const U32& offset);
|
void WriteShared(int bit_size, const Value& value, const U32& offset);
|
||||||
|
|
||||||
[[nodiscard]] U32F32 SharedAtomicIAdd(const U32& address, const U32F32& data);
|
[[nodiscard]] U32U64 SharedAtomicIAdd(const U32& address, const U32U64& data);
|
||||||
|
[[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data);
|
||||||
[[nodiscard]] U32 SharedAtomicIMin(const U32& address, const U32& data, bool is_signed);
|
[[nodiscard]] U32 SharedAtomicIMin(const U32& address, const U32& data, bool is_signed);
|
||||||
[[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed);
|
[[nodiscard]] U32 SharedAtomicIMax(const U32& address, const U32& data, bool is_signed);
|
||||||
|
[[nodiscard]] U32 SharedAtomicInc(const U32& address);
|
||||||
|
[[nodiscard]] U32 SharedAtomicDec(const U32& address);
|
||||||
[[nodiscard]] U32 SharedAtomicAnd(const U32& address, const U32& data);
|
[[nodiscard]] U32 SharedAtomicAnd(const U32& address, const U32& data);
|
||||||
[[nodiscard]] U32 SharedAtomicOr(const U32& address, const U32& data);
|
[[nodiscard]] U32 SharedAtomicOr(const U32& address, const U32& data);
|
||||||
[[nodiscard]] U32 SharedAtomicXor(const U32& address, const U32& data);
|
[[nodiscard]] U32 SharedAtomicXor(const U32& address, const U32& data);
|
||||||
|
|
||||||
[[nodiscard]] U32 SharedAtomicIIncrement(const U32& address);
|
|
||||||
[[nodiscard]] U32 SharedAtomicIDecrement(const U32& address);
|
|
||||||
[[nodiscard]] U32 SharedAtomicISub(const U32& address, const U32& data);
|
|
||||||
|
|
||||||
[[nodiscard]] U32 ReadConst(const Value& base, const U32& offset);
|
[[nodiscard]] U32 ReadConst(const Value& base, const U32& offset);
|
||||||
[[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index);
|
[[nodiscard]] U32 ReadConstBuffer(const Value& handle, const U32& index);
|
||||||
|
|
||||||
[[nodiscard]] U32 LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info);
|
[[nodiscard]] U8 LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info);
|
||||||
[[nodiscard]] U32 LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info);
|
[[nodiscard]] U16 LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info);
|
||||||
[[nodiscard]] Value LoadBufferU32(int num_dwords, const Value& handle, const Value& address,
|
[[nodiscard]] Value LoadBufferU32(int num_dwords, const Value& handle, const Value& address,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
|
[[nodiscard]] U64 LoadBufferU64(const Value& handle, const Value& address, BufferInstInfo info);
|
||||||
[[nodiscard]] Value LoadBufferF32(int num_dwords, const Value& handle, const Value& address,
|
[[nodiscard]] Value LoadBufferF32(int num_dwords, const Value& handle, const Value& address,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
[[nodiscard]] Value LoadBufferFormat(const Value& handle, const Value& address,
|
[[nodiscard]] Value LoadBufferFormat(const Value& handle, const Value& address,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
void StoreBufferU8(const Value& handle, const Value& address, const U32& data,
|
void StoreBufferU8(const Value& handle, const Value& address, const U8& data,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
void StoreBufferU16(const Value& handle, const Value& address, const U32& data,
|
void StoreBufferU16(const Value& handle, const Value& address, const U16& data,
|
||||||
BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
void StoreBufferU32(int num_dwords, const Value& handle, const Value& address,
|
void StoreBufferU32(int num_dwords, const Value& handle, const Value& address,
|
||||||
const Value& data, BufferInstInfo info);
|
const Value& data, BufferInstInfo info);
|
||||||
|
void StoreBufferU64(const Value& handle, const Value& address, const U64& data,
|
||||||
|
BufferInstInfo info);
|
||||||
void StoreBufferF32(int num_dwords, const Value& handle, const Value& address,
|
void StoreBufferF32(int num_dwords, const Value& handle, const Value& address,
|
||||||
const Value& data, BufferInstInfo info);
|
const Value& data, BufferInstInfo info);
|
||||||
void StoreBufferFormat(const Value& handle, const Value& address, const Value& data,
|
void StoreBufferFormat(const Value& handle, const Value& address, const Value& data,
|
||||||
|
@ -134,14 +136,16 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] Value BufferAtomicIAdd(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicIAdd(const Value& handle, const Value& address,
|
||||||
const Value& value, BufferInstInfo info);
|
const Value& value, BufferInstInfo info);
|
||||||
|
[[nodiscard]] Value BufferAtomicISub(const Value& handle, const Value& address,
|
||||||
|
const Value& value, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicIMin(const Value& handle, const Value& address,
|
||||||
const Value& value, bool is_signed, BufferInstInfo info);
|
const Value& value, bool is_signed, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address,
|
||||||
const Value& value, bool is_signed, BufferInstInfo info);
|
const Value& value, bool is_signed, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicInc(const Value& handle, const Value& address,
|
||||||
const Value& value, BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicDec(const Value& handle, const Value& address,
|
||||||
const Value& value, BufferInstInfo info);
|
BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicAnd(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicAnd(const Value& handle, const Value& address,
|
||||||
const Value& value, BufferInstInfo info);
|
const Value& value, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicOr(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicOr(const Value& handle, const Value& address,
|
||||||
|
@ -150,6 +154,9 @@ public:
|
||||||
const Value& value, BufferInstInfo info);
|
const Value& value, BufferInstInfo info);
|
||||||
[[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address,
|
[[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address,
|
||||||
const Value& value, BufferInstInfo info);
|
const Value& value, BufferInstInfo info);
|
||||||
|
[[nodiscard]] Value BufferAtomicCmpSwap(const Value& handle, const Value& address,
|
||||||
|
const Value& value, const Value& cmp_value,
|
||||||
|
BufferInstInfo info);
|
||||||
|
|
||||||
[[nodiscard]] U32 DataAppend(const U32& counter);
|
[[nodiscard]] U32 DataAppend(const U32& counter);
|
||||||
[[nodiscard]] U32 DataConsume(const U32& counter);
|
[[nodiscard]] U32 DataConsume(const U32& counter);
|
||||||
|
@ -306,7 +313,7 @@ public:
|
||||||
[[nodiscard]] F32F64 ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed,
|
[[nodiscard]] F32F64 ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed,
|
||||||
const Value& value);
|
const Value& value);
|
||||||
|
|
||||||
[[nodiscard]] U16U32U64 UConvert(size_t result_bitsize, const U16U32U64& value);
|
[[nodiscard]] U8U16U32U64 UConvert(size_t result_bitsize, const U8U16U32U64& value);
|
||||||
[[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value);
|
[[nodiscard]] F16F32F64 FPConvert(size_t result_bitsize, const F16F32F64& value);
|
||||||
|
|
||||||
[[nodiscard]] Value ImageAtomicIAdd(const Value& handle, const Value& coords,
|
[[nodiscard]] Value ImageAtomicIAdd(const Value& handle, const Value& coords,
|
||||||
|
|
|
@ -60,12 +60,15 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
||||||
case Opcode::StoreBufferU32x2:
|
case Opcode::StoreBufferU32x2:
|
||||||
case Opcode::StoreBufferU32x3:
|
case Opcode::StoreBufferU32x3:
|
||||||
case Opcode::StoreBufferU32x4:
|
case Opcode::StoreBufferU32x4:
|
||||||
|
case Opcode::StoreBufferU64:
|
||||||
case Opcode::StoreBufferF32:
|
case Opcode::StoreBufferF32:
|
||||||
case Opcode::StoreBufferF32x2:
|
case Opcode::StoreBufferF32x2:
|
||||||
case Opcode::StoreBufferF32x3:
|
case Opcode::StoreBufferF32x3:
|
||||||
case Opcode::StoreBufferF32x4:
|
case Opcode::StoreBufferF32x4:
|
||||||
case Opcode::StoreBufferFormatF32:
|
case Opcode::StoreBufferFormatF32:
|
||||||
case Opcode::BufferAtomicIAdd32:
|
case Opcode::BufferAtomicIAdd32:
|
||||||
|
case Opcode::BufferAtomicIAdd64:
|
||||||
|
case Opcode::BufferAtomicISub32:
|
||||||
case Opcode::BufferAtomicSMin32:
|
case Opcode::BufferAtomicSMin32:
|
||||||
case Opcode::BufferAtomicUMin32:
|
case Opcode::BufferAtomicUMin32:
|
||||||
case Opcode::BufferAtomicSMax32:
|
case Opcode::BufferAtomicSMax32:
|
||||||
|
@ -76,15 +79,21 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
||||||
case Opcode::BufferAtomicOr32:
|
case Opcode::BufferAtomicOr32:
|
||||||
case Opcode::BufferAtomicXor32:
|
case Opcode::BufferAtomicXor32:
|
||||||
case Opcode::BufferAtomicSwap32:
|
case Opcode::BufferAtomicSwap32:
|
||||||
|
case Opcode::BufferAtomicCmpSwap32:
|
||||||
case Opcode::DataAppend:
|
case Opcode::DataAppend:
|
||||||
case Opcode::DataConsume:
|
case Opcode::DataConsume:
|
||||||
case Opcode::WriteSharedU64:
|
case Opcode::WriteSharedU16:
|
||||||
case Opcode::WriteSharedU32:
|
case Opcode::WriteSharedU32:
|
||||||
|
case Opcode::WriteSharedU64:
|
||||||
case Opcode::SharedAtomicIAdd32:
|
case Opcode::SharedAtomicIAdd32:
|
||||||
|
case Opcode::SharedAtomicIAdd64:
|
||||||
|
case Opcode::SharedAtomicISub32:
|
||||||
case Opcode::SharedAtomicSMin32:
|
case Opcode::SharedAtomicSMin32:
|
||||||
case Opcode::SharedAtomicUMin32:
|
case Opcode::SharedAtomicUMin32:
|
||||||
case Opcode::SharedAtomicSMax32:
|
case Opcode::SharedAtomicSMax32:
|
||||||
case Opcode::SharedAtomicUMax32:
|
case Opcode::SharedAtomicUMax32:
|
||||||
|
case Opcode::SharedAtomicInc32:
|
||||||
|
case Opcode::SharedAtomicDec32:
|
||||||
case Opcode::SharedAtomicAnd32:
|
case Opcode::SharedAtomicAnd32:
|
||||||
case Opcode::SharedAtomicOr32:
|
case Opcode::SharedAtomicOr32:
|
||||||
case Opcode::SharedAtomicXor32:
|
case Opcode::SharedAtomicXor32:
|
||||||
|
|
|
@ -30,23 +30,26 @@ OPCODE(EmitVertex, Void,
|
||||||
OPCODE(EmitPrimitive, Void, )
|
OPCODE(EmitPrimitive, Void, )
|
||||||
|
|
||||||
// Shared memory operations
|
// Shared memory operations
|
||||||
|
OPCODE(LoadSharedU16, U16, U32, )
|
||||||
OPCODE(LoadSharedU32, U32, U32, )
|
OPCODE(LoadSharedU32, U32, U32, )
|
||||||
OPCODE(LoadSharedU64, U32x2, U32, )
|
OPCODE(LoadSharedU64, U64, U32, )
|
||||||
|
OPCODE(WriteSharedU16, Void, U32, U16, )
|
||||||
OPCODE(WriteSharedU32, Void, U32, U32, )
|
OPCODE(WriteSharedU32, Void, U32, U32, )
|
||||||
OPCODE(WriteSharedU64, Void, U32, U32x2, )
|
OPCODE(WriteSharedU64, Void, U32, U64, )
|
||||||
|
|
||||||
// Shared atomic operations
|
// Shared atomic operations
|
||||||
OPCODE(SharedAtomicIAdd32, U32, U32, U32, )
|
OPCODE(SharedAtomicIAdd32, U32, U32, U32, )
|
||||||
|
OPCODE(SharedAtomicIAdd64, U64, U32, U64, )
|
||||||
|
OPCODE(SharedAtomicISub32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicSMin32, U32, U32, U32, )
|
OPCODE(SharedAtomicSMin32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicUMin32, U32, U32, U32, )
|
OPCODE(SharedAtomicUMin32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicSMax32, U32, U32, U32, )
|
OPCODE(SharedAtomicSMax32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicUMax32, U32, U32, U32, )
|
OPCODE(SharedAtomicUMax32, U32, U32, U32, )
|
||||||
|
OPCODE(SharedAtomicInc32, U32, U32, )
|
||||||
|
OPCODE(SharedAtomicDec32, U32, U32, )
|
||||||
OPCODE(SharedAtomicAnd32, U32, U32, U32, )
|
OPCODE(SharedAtomicAnd32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicOr32, U32, U32, U32, )
|
OPCODE(SharedAtomicOr32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicXor32, U32, U32, U32, )
|
OPCODE(SharedAtomicXor32, U32, U32, U32, )
|
||||||
OPCODE(SharedAtomicISub32, U32, U32, U32, )
|
|
||||||
OPCODE(SharedAtomicIIncrement32, U32, U32, )
|
|
||||||
OPCODE(SharedAtomicIDecrement32, U32, U32, )
|
|
||||||
|
|
||||||
// Context getters/setters
|
// Context getters/setters
|
||||||
OPCODE(GetUserData, U32, ScalarReg, )
|
OPCODE(GetUserData, U32, ScalarReg, )
|
||||||
|
@ -91,23 +94,25 @@ OPCODE(UndefU32, U32,
|
||||||
OPCODE(UndefU64, U64, )
|
OPCODE(UndefU64, U64, )
|
||||||
|
|
||||||
// Buffer operations
|
// Buffer operations
|
||||||
OPCODE(LoadBufferU8, U32, Opaque, Opaque, )
|
OPCODE(LoadBufferU8, U8, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferU16, U32, Opaque, Opaque, )
|
OPCODE(LoadBufferU16, U16, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferU32, U32, Opaque, Opaque, )
|
OPCODE(LoadBufferU32, U32, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, )
|
OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, )
|
OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, )
|
OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, )
|
||||||
|
OPCODE(LoadBufferU64, U64, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferF32, F32, Opaque, Opaque, )
|
OPCODE(LoadBufferF32, F32, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, )
|
OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, )
|
OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, )
|
OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, )
|
||||||
OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, )
|
OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, )
|
||||||
OPCODE(StoreBufferU8, Void, Opaque, Opaque, U32, )
|
OPCODE(StoreBufferU8, Void, Opaque, Opaque, U8, )
|
||||||
OPCODE(StoreBufferU16, Void, Opaque, Opaque, U32, )
|
OPCODE(StoreBufferU16, Void, Opaque, Opaque, U16, )
|
||||||
OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, )
|
OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, )
|
||||||
OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, )
|
OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, )
|
||||||
OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, )
|
OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, )
|
||||||
OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, )
|
OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, )
|
||||||
|
OPCODE(StoreBufferU64, Void, Opaque, Opaque, U64, )
|
||||||
OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, )
|
OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, )
|
||||||
OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, )
|
OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, )
|
||||||
OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, )
|
OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, )
|
||||||
|
@ -116,16 +121,19 @@ OPCODE(StoreBufferFormatF32, Void, Opaq
|
||||||
|
|
||||||
// Buffer atomic operations
|
// Buffer atomic operations
|
||||||
OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicIAdd32, U32, Opaque, Opaque, U32 )
|
||||||
|
OPCODE(BufferAtomicIAdd64, U64, Opaque, Opaque, U64 )
|
||||||
|
OPCODE(BufferAtomicISub32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicSMin32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
||||||
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, )
|
||||||
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, )
|
||||||
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
||||||
OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, )
|
||||||
OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, )
|
||||||
OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
|
OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
|
||||||
|
OPCODE(BufferAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, )
|
||||||
|
|
||||||
// Vector utility
|
// Vector utility
|
||||||
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
|
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
|
||||||
|
@ -400,6 +408,8 @@ OPCODE(ConvertF64U32, F64, U32,
|
||||||
OPCODE(ConvertF32U16, F32, U16, )
|
OPCODE(ConvertF32U16, F32, U16, )
|
||||||
OPCODE(ConvertU16U32, U16, U32, )
|
OPCODE(ConvertU16U32, U16, U32, )
|
||||||
OPCODE(ConvertU32U16, U32, U16, )
|
OPCODE(ConvertU32U16, U32, U16, )
|
||||||
|
OPCODE(ConvertU8U32, U8, U32, )
|
||||||
|
OPCODE(ConvertU32U8, U32, U8, )
|
||||||
|
|
||||||
// Image operations
|
// Image operations
|
||||||
OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, )
|
OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, )
|
||||||
|
|
|
@ -10,6 +10,8 @@
|
||||||
#include "common/io_file.h"
|
#include "common/io_file.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
#include "common/path_util.h"
|
#include "common/path_util.h"
|
||||||
|
#include "common/signal_context.h"
|
||||||
|
#include "core/signals.h"
|
||||||
#include "shader_recompiler/info.h"
|
#include "shader_recompiler/info.h"
|
||||||
#include "shader_recompiler/ir/breadth_first_search.h"
|
#include "shader_recompiler/ir/breadth_first_search.h"
|
||||||
#include "shader_recompiler/ir/opcodes.h"
|
#include "shader_recompiler/ir/opcodes.h"
|
||||||
|
@ -24,6 +26,7 @@
|
||||||
using namespace Xbyak::util;
|
using namespace Xbyak::util;
|
||||||
|
|
||||||
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
||||||
|
static const u8* g_srt_codegen_start = nullptr;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -54,6 +57,57 @@ static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t code
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool SrtWalkerSignalHandler(void* context, void* fault_address) {
|
||||||
|
// Only handle if the fault address is within the SRT code range
|
||||||
|
const u8* code_start = g_srt_codegen_start;
|
||||||
|
const u8* code_end = code_start + g_srt_codegen.getSize();
|
||||||
|
const void* code = Common::GetRip(context);
|
||||||
|
if (code < code_start || code >= code_end) {
|
||||||
|
return false; // Not in SRT code range
|
||||||
|
}
|
||||||
|
|
||||||
|
// Patch instruction to zero register
|
||||||
|
ZydisDecodedInstruction instruction;
|
||||||
|
ZydisDecodedOperand operands[ZYDIS_MAX_OPERAND_COUNT];
|
||||||
|
ZyanStatus status = Common::Decoder::Instance()->decodeInstruction(instruction, operands,
|
||||||
|
const_cast<void*>(code), 15);
|
||||||
|
|
||||||
|
ASSERT(ZYAN_SUCCESS(status) && instruction.mnemonic == ZYDIS_MNEMONIC_MOV &&
|
||||||
|
operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER &&
|
||||||
|
operands[1].type == ZYDIS_OPERAND_TYPE_MEMORY);
|
||||||
|
|
||||||
|
size_t len = instruction.length;
|
||||||
|
const size_t patch_size = 3;
|
||||||
|
u8* code_patch = const_cast<u8*>(reinterpret_cast<const u8*>(code));
|
||||||
|
|
||||||
|
// We can only encounter rdi or r10d as the first operand in a
|
||||||
|
// fault memory access for SRT walker.
|
||||||
|
switch (operands[0].reg.value) {
|
||||||
|
case ZYDIS_REGISTER_RDI:
|
||||||
|
// mov rdi, [rdi + (off_dw << 2)] -> xor rdi, rdi
|
||||||
|
code_patch[0] = 0x48;
|
||||||
|
code_patch[1] = 0x31;
|
||||||
|
code_patch[2] = 0xFF;
|
||||||
|
break;
|
||||||
|
case ZYDIS_REGISTER_R10D:
|
||||||
|
// mov r10d, [rdi + (off_dw << 2)] -> xor r10d, r10d
|
||||||
|
code_patch[0] = 0x45;
|
||||||
|
code_patch[1] = 0x31;
|
||||||
|
code_patch[2] = 0xD2;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Unsupported register for SRT walker patch");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill nops
|
||||||
|
memset(code_patch + patch_size, 0x90, len - patch_size);
|
||||||
|
|
||||||
|
LOG_DEBUG(Render_Recompiler, "Patched SRT walker at {}", code);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
using namespace Shader;
|
using namespace Shader;
|
||||||
|
|
||||||
struct PassInfo {
|
struct PassInfo {
|
||||||
|
@ -141,6 +195,15 @@ static void GenerateSrtProgram(Info& info, PassInfo& pass_info) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Register the signal handler for SRT walker, if not already registered
|
||||||
|
if (g_srt_codegen_start == nullptr) {
|
||||||
|
g_srt_codegen_start = c.getCurr();
|
||||||
|
auto* signals = Core::Signals::Instance();
|
||||||
|
// Call after the memory invalidation handler
|
||||||
|
constexpr u32 priority = 1;
|
||||||
|
signals->RegisterAccessViolationHandler(SrtWalkerSignalHandler, priority);
|
||||||
|
}
|
||||||
|
|
||||||
info.srt_info.walker_func = c.getCurr<PFN_SrtWalker>();
|
info.srt_info.walker_func = c.getCurr<PFN_SrtWalker>();
|
||||||
|
|
||||||
pass_info.dst_off_dw = NumUserDataRegs;
|
pass_info.dst_off_dw = NumUserDataRegs;
|
||||||
|
|
|
@ -438,7 +438,9 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||||
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
|
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||||
const u32 num_dwords = opcode == IR::Opcode::WriteSharedU32 ? 1 : 2;
|
const u32 num_dwords = opcode == IR::Opcode::WriteSharedU32 ? 1 : 2;
|
||||||
const IR::U32 addr{inst.Arg(0)};
|
const IR::U32 addr{inst.Arg(0)};
|
||||||
const IR::U32 data{inst.Arg(1).Resolve()};
|
const IR::Value data = num_dwords == 2
|
||||||
|
? ir.UnpackUint2x32(IR::U64{inst.Arg(1).Resolve()})
|
||||||
|
: inst.Arg(1).Resolve();
|
||||||
|
|
||||||
const auto SetOutput = [&](IR::U32 addr, IR::U32 value, AttributeRegion output_kind,
|
const auto SetOutput = [&](IR::U32 addr, IR::U32 value, AttributeRegion output_kind,
|
||||||
u32 off_dw) {
|
u32 off_dw) {
|
||||||
|
@ -466,10 +468,10 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||||
|
|
||||||
AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info);
|
AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info);
|
||||||
if (num_dwords == 1) {
|
if (num_dwords == 1) {
|
||||||
SetOutput(addr, data, region, 0);
|
SetOutput(addr, IR::U32{data}, region, 0);
|
||||||
} else {
|
} else {
|
||||||
for (auto i = 0; i < num_dwords; i++) {
|
for (auto i = 0; i < num_dwords; i++) {
|
||||||
SetOutput(addr, IR::U32{data.Inst()->Arg(i)}, region, i);
|
SetOutput(addr, IR::U32{ir.CompositeExtract(data, i)}, region, i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
inst.Invalidate();
|
inst.Invalidate();
|
||||||
|
@ -499,7 +501,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||||
ReadTessControlPointAttribute(addr, stride, ir, i, is_tcs_output_read);
|
ReadTessControlPointAttribute(addr, stride, ir, i, is_tcs_output_read);
|
||||||
read_components.push_back(ir.BitCast<IR::U32>(component));
|
read_components.push_back(ir.BitCast<IR::U32>(component));
|
||||||
}
|
}
|
||||||
attr_read = ir.CompositeConstruct(read_components);
|
attr_read = ir.PackUint2x32(ir.CompositeConstruct(read_components));
|
||||||
}
|
}
|
||||||
inst.ReplaceUsesWithAndRemove(attr_read);
|
inst.ReplaceUsesWithAndRemove(attr_read);
|
||||||
break;
|
break;
|
||||||
|
@ -578,7 +580,7 @@ void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
||||||
const IR::F32 component = GetInput(addr, i);
|
const IR::F32 component = GetInput(addr, i);
|
||||||
read_components.push_back(ir.BitCast<IR::U32>(component));
|
read_components.push_back(ir.BitCast<IR::U32>(component));
|
||||||
}
|
}
|
||||||
attr_read = ir.CompositeConstruct(read_components);
|
attr_read = ir.PackUint2x32(ir.CompositeConstruct(read_components));
|
||||||
}
|
}
|
||||||
inst.ReplaceUsesWithAndRemove(attr_read);
|
inst.ReplaceUsesWithAndRemove(attr_read);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -28,6 +28,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
||||||
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
||||||
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||||
const Profile& profile);
|
const Profile& profile);
|
||||||
|
void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile);
|
||||||
void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info,
|
void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||||
const Profile& profile);
|
const Profile& profile);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ struct FormatInfo {
|
||||||
AmdGpu::NumberFormat num_format;
|
AmdGpu::NumberFormat num_format;
|
||||||
AmdGpu::CompMapping swizzle;
|
AmdGpu::CompMapping swizzle;
|
||||||
AmdGpu::NumberConversion num_conversion;
|
AmdGpu::NumberConversion num_conversion;
|
||||||
int num_components;
|
u32 num_components;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool IsBufferFormatLoad(const IR::Inst& inst) {
|
static bool IsBufferFormatLoad(const IR::Inst& inst) {
|
||||||
|
@ -34,13 +34,13 @@ static IR::Value LoadBufferFormat(IR::IREmitter& ir, const IR::Value handle, con
|
||||||
interpreted = ir.Imm32(0.f);
|
interpreted = ir.Imm32(0.f);
|
||||||
break;
|
break;
|
||||||
case AmdGpu::DataFormat::Format8: {
|
case AmdGpu::DataFormat::Format8: {
|
||||||
const auto unpacked =
|
const auto raw = ir.UConvert(32, ir.LoadBufferU8(handle, address, info));
|
||||||
ir.Unpack4x8(format_info.num_format, ir.LoadBufferU8(handle, address, info));
|
const auto unpacked = ir.Unpack4x8(format_info.num_format, raw);
|
||||||
interpreted = ir.CompositeExtract(unpacked, 0);
|
interpreted = ir.CompositeExtract(unpacked, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AmdGpu::DataFormat::Format8_8: {
|
case AmdGpu::DataFormat::Format8_8: {
|
||||||
const auto raw = ir.LoadBufferU16(handle, address, info);
|
const auto raw = ir.UConvert(32, ir.LoadBufferU16(handle, address, info));
|
||||||
const auto unpacked = ir.Unpack4x8(format_info.num_format, raw);
|
const auto unpacked = ir.Unpack4x8(format_info.num_format, raw);
|
||||||
interpreted = ir.CompositeConstruct(ir.CompositeExtract(unpacked, 0),
|
interpreted = ir.CompositeConstruct(ir.CompositeExtract(unpacked, 0),
|
||||||
ir.CompositeExtract(unpacked, 1));
|
ir.CompositeExtract(unpacked, 1));
|
||||||
|
@ -51,8 +51,8 @@ static IR::Value LoadBufferFormat(IR::IREmitter& ir, const IR::Value handle, con
|
||||||
IR::U32{ir.LoadBufferU32(1, handle, address, info)});
|
IR::U32{ir.LoadBufferU32(1, handle, address, info)});
|
||||||
break;
|
break;
|
||||||
case AmdGpu::DataFormat::Format16: {
|
case AmdGpu::DataFormat::Format16: {
|
||||||
const auto unpacked =
|
const auto raw = ir.UConvert(32, ir.LoadBufferU16(handle, address, info));
|
||||||
ir.Unpack2x16(format_info.num_format, ir.LoadBufferU16(handle, address, info));
|
const auto unpacked = ir.Unpack2x16(format_info.num_format, raw);
|
||||||
interpreted = ir.CompositeExtract(unpacked, 0);
|
interpreted = ir.CompositeExtract(unpacked, 0);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -126,7 +126,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I
|
||||||
const auto packed =
|
const auto packed =
|
||||||
ir.Pack4x8(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f),
|
ir.Pack4x8(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f),
|
||||||
ir.Imm32(0.f), ir.Imm32(0.f)));
|
ir.Imm32(0.f), ir.Imm32(0.f)));
|
||||||
ir.StoreBufferU8(handle, address, packed, info);
|
ir.StoreBufferU8(handle, address, ir.UConvert(8, packed), info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AmdGpu::DataFormat::Format8_8: {
|
case AmdGpu::DataFormat::Format8_8: {
|
||||||
|
@ -134,7 +134,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I
|
||||||
ir.CompositeConstruct(ir.CompositeExtract(real_value, 0),
|
ir.CompositeConstruct(ir.CompositeExtract(real_value, 0),
|
||||||
ir.CompositeExtract(real_value, 1),
|
ir.CompositeExtract(real_value, 1),
|
||||||
ir.Imm32(0.f), ir.Imm32(0.f)));
|
ir.Imm32(0.f), ir.Imm32(0.f)));
|
||||||
ir.StoreBufferU16(handle, address, packed, info);
|
ir.StoreBufferU16(handle, address, ir.UConvert(16, packed), info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AmdGpu::DataFormat::Format8_8_8_8: {
|
case AmdGpu::DataFormat::Format8_8_8_8: {
|
||||||
|
@ -145,7 +145,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I
|
||||||
case AmdGpu::DataFormat::Format16: {
|
case AmdGpu::DataFormat::Format16: {
|
||||||
const auto packed =
|
const auto packed =
|
||||||
ir.Pack2x16(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f)));
|
ir.Pack2x16(format_info.num_format, ir.CompositeConstruct(real_value, ir.Imm32(0.f)));
|
||||||
ir.StoreBufferU16(handle, address, packed, info);
|
ir.StoreBufferU16(handle, address, ir.UConvert(16, packed), info);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case AmdGpu::DataFormat::Format16_16: {
|
case AmdGpu::DataFormat::Format16_16: {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
#include "shader_recompiler/ir/program.h"
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
|
||||||
namespace Shader::Optimization {
|
namespace Shader::Optimization {
|
||||||
|
|
|
@ -17,6 +17,8 @@ using SharpLocation = u32;
|
||||||
bool IsBufferAtomic(const IR::Inst& inst) {
|
bool IsBufferAtomic(const IR::Inst& inst) {
|
||||||
switch (inst.GetOpcode()) {
|
switch (inst.GetOpcode()) {
|
||||||
case IR::Opcode::BufferAtomicIAdd32:
|
case IR::Opcode::BufferAtomicIAdd32:
|
||||||
|
case IR::Opcode::BufferAtomicIAdd64:
|
||||||
|
case IR::Opcode::BufferAtomicISub32:
|
||||||
case IR::Opcode::BufferAtomicSMin32:
|
case IR::Opcode::BufferAtomicSMin32:
|
||||||
case IR::Opcode::BufferAtomicUMin32:
|
case IR::Opcode::BufferAtomicUMin32:
|
||||||
case IR::Opcode::BufferAtomicSMax32:
|
case IR::Opcode::BufferAtomicSMax32:
|
||||||
|
@ -27,6 +29,7 @@ bool IsBufferAtomic(const IR::Inst& inst) {
|
||||||
case IR::Opcode::BufferAtomicOr32:
|
case IR::Opcode::BufferAtomicOr32:
|
||||||
case IR::Opcode::BufferAtomicXor32:
|
case IR::Opcode::BufferAtomicXor32:
|
||||||
case IR::Opcode::BufferAtomicSwap32:
|
case IR::Opcode::BufferAtomicSwap32:
|
||||||
|
case IR::Opcode::BufferAtomicCmpSwap32:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -41,6 +44,7 @@ bool IsBufferStore(const IR::Inst& inst) {
|
||||||
case IR::Opcode::StoreBufferU32x2:
|
case IR::Opcode::StoreBufferU32x2:
|
||||||
case IR::Opcode::StoreBufferU32x3:
|
case IR::Opcode::StoreBufferU32x3:
|
||||||
case IR::Opcode::StoreBufferU32x4:
|
case IR::Opcode::StoreBufferU32x4:
|
||||||
|
case IR::Opcode::StoreBufferU64:
|
||||||
case IR::Opcode::StoreBufferF32:
|
case IR::Opcode::StoreBufferF32:
|
||||||
case IR::Opcode::StoreBufferF32x2:
|
case IR::Opcode::StoreBufferF32x2:
|
||||||
case IR::Opcode::StoreBufferF32x3:
|
case IR::Opcode::StoreBufferF32x3:
|
||||||
|
@ -60,6 +64,7 @@ bool IsBufferInstruction(const IR::Inst& inst) {
|
||||||
case IR::Opcode::LoadBufferU32x2:
|
case IR::Opcode::LoadBufferU32x2:
|
||||||
case IR::Opcode::LoadBufferU32x3:
|
case IR::Opcode::LoadBufferU32x3:
|
||||||
case IR::Opcode::LoadBufferU32x4:
|
case IR::Opcode::LoadBufferU32x4:
|
||||||
|
case IR::Opcode::LoadBufferU64:
|
||||||
case IR::Opcode::LoadBufferF32:
|
case IR::Opcode::LoadBufferF32:
|
||||||
case IR::Opcode::LoadBufferF32x2:
|
case IR::Opcode::LoadBufferF32x2:
|
||||||
case IR::Opcode::LoadBufferF32x3:
|
case IR::Opcode::LoadBufferF32x3:
|
||||||
|
@ -85,6 +90,10 @@ IR::Type BufferDataType(const IR::Inst& inst, AmdGpu::NumberFormat num_format) {
|
||||||
case IR::Opcode::LoadBufferU16:
|
case IR::Opcode::LoadBufferU16:
|
||||||
case IR::Opcode::StoreBufferU16:
|
case IR::Opcode::StoreBufferU16:
|
||||||
return IR::Type::U16;
|
return IR::Type::U16;
|
||||||
|
case IR::Opcode::LoadBufferU64:
|
||||||
|
case IR::Opcode::StoreBufferU64:
|
||||||
|
case IR::Opcode::BufferAtomicIAdd64:
|
||||||
|
return IR::Type::U64;
|
||||||
case IR::Opcode::LoadBufferFormatF32:
|
case IR::Opcode::LoadBufferFormatF32:
|
||||||
case IR::Opcode::StoreBufferFormatF32:
|
case IR::Opcode::StoreBufferFormatF32:
|
||||||
// Formatted buffer loads can use a variety of types.
|
// Formatted buffer loads can use a variety of types.
|
||||||
|
|
|
@ -39,11 +39,13 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
||||||
ASSERT(addr->Arg(1).IsImmediate());
|
ASSERT(addr->Arg(1).IsImmediate());
|
||||||
offset = addr->Arg(1).U32();
|
offset = addr->Arg(1).U32();
|
||||||
}
|
}
|
||||||
IR::Value data = inst.Arg(1).Resolve();
|
IR::Value data = is_composite ? ir.UnpackUint2x32(IR::U64{inst.Arg(1).Resolve()})
|
||||||
|
: inst.Arg(1).Resolve();
|
||||||
for (s32 i = 0; i < num_components; i++) {
|
for (s32 i = 0; i < num_components; i++) {
|
||||||
const auto attrib = IR::Attribute::Param0 + (offset / 16);
|
const auto attrib = IR::Attribute::Param0 + (offset / 16);
|
||||||
const auto comp = (offset / 4) % 4;
|
const auto comp = (offset / 4) % 4;
|
||||||
const IR::U32 value = IR::U32{is_composite ? data.Inst()->Arg(i) : data};
|
const IR::U32 value =
|
||||||
|
IR::U32{is_composite ? ir.CompositeExtract(data, i) : data};
|
||||||
ir.SetAttribute(attrib, ir.BitCast<IR::F32, IR::U32>(value), comp);
|
ir.SetAttribute(attrib, ir.BitCast<IR::F32, IR::U32>(value), comp);
|
||||||
offset += 4;
|
offset += 4;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +93,19 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
||||||
const auto& gs_info = runtime_info.gs_info;
|
const auto& gs_info = runtime_info.gs_info;
|
||||||
info.gs_copy_data = Shader::ParseCopyShader(gs_info.vs_copy);
|
info.gs_copy_data = Shader::ParseCopyShader(gs_info.vs_copy);
|
||||||
|
|
||||||
|
u32 output_vertices = gs_info.output_vertices;
|
||||||
|
if (info.gs_copy_data.output_vertices &&
|
||||||
|
info.gs_copy_data.output_vertices != output_vertices) {
|
||||||
|
ASSERT_MSG(output_vertices > info.gs_copy_data.output_vertices &&
|
||||||
|
gs_info.mode == AmdGpu::Liverpool::GsMode::Mode::ScenarioG,
|
||||||
|
"Invalid geometry shader vertex configuration scenario = {}, max_vert_out = "
|
||||||
|
"{}, output_vertices = {}",
|
||||||
|
u32(gs_info.mode), output_vertices, info.gs_copy_data.output_vertices);
|
||||||
|
LOG_WARNING(Render_Vulkan, "MAX_VERT_OUT {} is larger than actual output vertices {}",
|
||||||
|
output_vertices, info.gs_copy_data.output_vertices);
|
||||||
|
output_vertices = info.gs_copy_data.output_vertices;
|
||||||
|
}
|
||||||
|
|
||||||
ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) {
|
ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) {
|
||||||
const auto opcode = inst.GetOpcode();
|
const auto opcode = inst.GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
@ -122,7 +137,7 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
||||||
|
|
||||||
const auto offset = inst.Flags<IR::BufferInstInfo>().inst_offset.Value();
|
const auto offset = inst.Flags<IR::BufferInstInfo>().inst_offset.Value();
|
||||||
const auto data = ir.BitCast<IR::F32>(IR::U32{inst.Arg(2)});
|
const auto data = ir.BitCast<IR::F32>(IR::U32{inst.Arg(2)});
|
||||||
const auto comp_ofs = gs_info.output_vertices * 4u;
|
const auto comp_ofs = output_vertices * 4u;
|
||||||
const auto output_size = comp_ofs * gs_info.out_vertex_data_size;
|
const auto output_size = comp_ofs * gs_info.out_vertex_data_size;
|
||||||
|
|
||||||
const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u;
|
const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u;
|
||||||
|
|
|
@ -34,11 +34,29 @@ void Visit(Info& info, const IR::Inst& inst) {
|
||||||
info.uses_patches |= 1U << IR::GenericPatchIndex(patch);
|
info.uses_patches |= 1U << IR::GenericPatchIndex(patch);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case IR::Opcode::LoadSharedU16:
|
||||||
|
case IR::Opcode::WriteSharedU16:
|
||||||
|
info.shared_types |= IR::Type::U16;
|
||||||
|
break;
|
||||||
case IR::Opcode::LoadSharedU32:
|
case IR::Opcode::LoadSharedU32:
|
||||||
case IR::Opcode::LoadSharedU64:
|
|
||||||
case IR::Opcode::WriteSharedU32:
|
case IR::Opcode::WriteSharedU32:
|
||||||
|
case IR::Opcode::SharedAtomicIAdd32:
|
||||||
|
case IR::Opcode::SharedAtomicISub32:
|
||||||
|
case IR::Opcode::SharedAtomicSMin32:
|
||||||
|
case IR::Opcode::SharedAtomicUMin32:
|
||||||
|
case IR::Opcode::SharedAtomicSMax32:
|
||||||
|
case IR::Opcode::SharedAtomicUMax32:
|
||||||
|
case IR::Opcode::SharedAtomicInc32:
|
||||||
|
case IR::Opcode::SharedAtomicDec32:
|
||||||
|
case IR::Opcode::SharedAtomicAnd32:
|
||||||
|
case IR::Opcode::SharedAtomicOr32:
|
||||||
|
case IR::Opcode::SharedAtomicXor32:
|
||||||
|
info.shared_types |= IR::Type::U32;
|
||||||
|
break;
|
||||||
|
case IR::Opcode::LoadSharedU64:
|
||||||
case IR::Opcode::WriteSharedU64:
|
case IR::Opcode::WriteSharedU64:
|
||||||
info.uses_shared = true;
|
case IR::Opcode::SharedAtomicIAdd64:
|
||||||
|
info.shared_types |= IR::Type::U64;
|
||||||
break;
|
break;
|
||||||
case IR::Opcode::ConvertF16F32:
|
case IR::Opcode::ConvertF16F32:
|
||||||
case IR::Opcode::ConvertF32F16:
|
case IR::Opcode::ConvertF32F16:
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
#include "shader_recompiler/ir/breadth_first_search.h"
|
#include "shader_recompiler/ir/breadth_first_search.h"
|
||||||
#include "shader_recompiler/ir/ir_emitter.h"
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
#include "shader_recompiler/ir/program.h"
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
@ -9,12 +10,14 @@
|
||||||
namespace Shader::Optimization {
|
namespace Shader::Optimization {
|
||||||
|
|
||||||
static bool IsLoadShared(const IR::Inst& inst) {
|
static bool IsLoadShared(const IR::Inst& inst) {
|
||||||
return inst.GetOpcode() == IR::Opcode::LoadSharedU32 ||
|
return inst.GetOpcode() == IR::Opcode::LoadSharedU16 ||
|
||||||
|
inst.GetOpcode() == IR::Opcode::LoadSharedU32 ||
|
||||||
inst.GetOpcode() == IR::Opcode::LoadSharedU64;
|
inst.GetOpcode() == IR::Opcode::LoadSharedU64;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool IsWriteShared(const IR::Inst& inst) {
|
static bool IsWriteShared(const IR::Inst& inst) {
|
||||||
return inst.GetOpcode() == IR::Opcode::WriteSharedU32 ||
|
return inst.GetOpcode() == IR::Opcode::WriteSharedU16 ||
|
||||||
|
inst.GetOpcode() == IR::Opcode::WriteSharedU32 ||
|
||||||
inst.GetOpcode() == IR::Opcode::WriteSharedU64;
|
inst.GetOpcode() == IR::Opcode::WriteSharedU64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,11 +52,14 @@ static void EmitBarrierInBlock(IR::Block* block) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
using NodeSet = std::unordered_set<const IR::Block*>;
|
||||||
|
|
||||||
// Inserts a barrier after divergent conditional blocks to avoid undefined
|
// Inserts a barrier after divergent conditional blocks to avoid undefined
|
||||||
// behavior when some threads write and others read from shared memory.
|
// behavior when some threads write and others read from shared memory.
|
||||||
static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data) {
|
static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data,
|
||||||
|
NodeSet& divergence_end, u32& divergence_depth) {
|
||||||
const IR::U1 cond = data.if_node.cond;
|
const IR::U1 cond = data.if_node.cond;
|
||||||
const auto insert_barrier =
|
const auto is_divergent_cond =
|
||||||
IR::BreadthFirstSearch(cond, [](IR::Inst* inst) -> std::optional<bool> {
|
IR::BreadthFirstSearch(cond, [](IR::Inst* inst) -> std::optional<bool> {
|
||||||
if (inst->GetOpcode() == IR::Opcode::GetAttributeU32 &&
|
if (inst->GetOpcode() == IR::Opcode::GetAttributeU32 &&
|
||||||
inst->Arg(0).Attribute() == IR::Attribute::LocalInvocationId) {
|
inst->Arg(0).Attribute() == IR::Attribute::LocalInvocationId) {
|
||||||
|
@ -61,12 +67,16 @@ static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data) {
|
||||||
}
|
}
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
});
|
});
|
||||||
if (insert_barrier) {
|
if (is_divergent_cond) {
|
||||||
|
if (divergence_depth == 0) {
|
||||||
IR::Block* const merge = data.if_node.merge;
|
IR::Block* const merge = data.if_node.merge;
|
||||||
auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi);
|
auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi);
|
||||||
IR::IREmitter ir{*merge, insert_point};
|
IR::IREmitter ir{*merge, insert_point};
|
||||||
ir.Barrier();
|
ir.Barrier();
|
||||||
}
|
}
|
||||||
|
++divergence_depth;
|
||||||
|
divergence_end.emplace(data.if_node.merge);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr u32 GcnSubgroupSize = 64;
|
static constexpr u32 GcnSubgroupSize = 64;
|
||||||
|
@ -87,19 +97,22 @@ void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_in
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
using Type = IR::AbstractSyntaxNode::Type;
|
using Type = IR::AbstractSyntaxNode::Type;
|
||||||
u32 branch_depth{};
|
u32 divergence_depth{};
|
||||||
|
NodeSet divergence_end;
|
||||||
for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
|
for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
|
||||||
if (node.type == Type::EndIf) {
|
if (node.type == Type::EndIf) {
|
||||||
--branch_depth;
|
if (divergence_end.contains(node.data.end_if.merge)) {
|
||||||
|
--divergence_depth;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Check if branch depth is zero, we don't want to insert barrier in potentially divergent
|
// Check if branch depth is zero, we don't want to insert barrier in potentially divergent
|
||||||
// code.
|
// code.
|
||||||
if (node.type == Type::If && branch_depth++ == 0) {
|
if (node.type == Type::If) {
|
||||||
EmitBarrierInMergeBlock(node.data);
|
EmitBarrierInMergeBlock(node.data, divergence_end, divergence_depth);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node.type == Type::Block && branch_depth == 0) {
|
if (node.type == Type::Block && divergence_depth == 0) {
|
||||||
EmitBarrierInBlock(node.data.block);
|
EmitBarrierInBlock(node.data.block);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
127
src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp
Normal file
127
src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "shader_recompiler/ir/ir_emitter.h"
|
||||||
|
#include "shader_recompiler/ir/program.h"
|
||||||
|
#include "shader_recompiler/profile.h"
|
||||||
|
|
||||||
|
namespace Shader::Optimization {
|
||||||
|
|
||||||
|
static bool Requires16BitSharedAtomic(const IR::Inst& inst) {
|
||||||
|
// Nothing yet
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool Requires64BitSharedAtomic(const IR::Inst& inst) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::SharedAtomicIAdd64:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool IsNon32BitSharedLoadStore(const IR::Inst& inst) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::LoadSharedU16:
|
||||||
|
case IR::Opcode::LoadSharedU64:
|
||||||
|
case IR::Opcode::WriteSharedU16:
|
||||||
|
case IR::Opcode::WriteSharedU64:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IR::Type CalculateSpecialSharedAtomicTypes(IR::Program& program) {
|
||||||
|
IR::Type extra_atomic_types{IR::Type::Void};
|
||||||
|
for (IR::Block* const block : program.blocks) {
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
if (Requires16BitSharedAtomic(inst)) {
|
||||||
|
extra_atomic_types |= IR::Type::U16;
|
||||||
|
}
|
||||||
|
if (Requires64BitSharedAtomic(inst)) {
|
||||||
|
extra_atomic_types |= IR::Type::U64;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return extra_atomic_types;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simplifies down U16 and U64 shared memory operations to U32 when aliasing is not supported and
|
||||||
|
// atomics of the same type are not used.
|
||||||
|
void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile) {
|
||||||
|
if (program.info.stage != Stage::Compute || profile.supports_workgroup_explicit_memory_layout) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto atomic_types = CalculateSpecialSharedAtomicTypes(program);
|
||||||
|
if (True(atomic_types & IR::Type::U16) && True(atomic_types & IR::Type::U64)) {
|
||||||
|
// If both other atomic types are used, there is nothing to do.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate through shared load/store U16/U64 instructions, replacing with
|
||||||
|
// equivalent U32 ops when the types are not needed for atomics.
|
||||||
|
for (IR::Block* const block : program.blocks) {
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
if (!IsNon32BitSharedLoadStore(inst)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||||
|
const IR::U32 offset{inst.Arg(0)};
|
||||||
|
if (False(atomic_types & IR::Type::U16)) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::LoadSharedU16: {
|
||||||
|
const IR::U32 dword_offset{ir.BitwiseAnd(offset, ir.Imm32(~3U))};
|
||||||
|
const IR::U32 dword_value{ir.LoadShared(32, false, dword_offset)};
|
||||||
|
const IR::U32 bit_offset{
|
||||||
|
ir.IMul(ir.BitwiseAnd(offset, ir.Imm32(2U)), ir.Imm32(8U))};
|
||||||
|
const IR::U32 value{ir.BitFieldExtract(dword_value, bit_offset, ir.Imm32(16U))};
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.UConvert(16, value));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case IR::Opcode::WriteSharedU16: {
|
||||||
|
const IR::U32 value{ir.UConvert(32, IR::U16{inst.Arg(1)})};
|
||||||
|
const IR::U32 bit_offset{
|
||||||
|
ir.IMul(ir.BitwiseAnd(offset, ir.Imm32(2U)), ir.Imm32(8U))};
|
||||||
|
const IR::U32 dword_offset{ir.BitwiseAnd(offset, ir.Imm32(~3U))};
|
||||||
|
const IR::U32 dword_value{
|
||||||
|
ir.LoadShared(32, false, ir.BitwiseAnd(offset, dword_offset))};
|
||||||
|
const IR::U32 new_dword_value{
|
||||||
|
ir.BitFieldInsert(dword_value, value, bit_offset, ir.Imm32(16U))};
|
||||||
|
ir.WriteShared(32, new_dword_value, dword_offset);
|
||||||
|
inst.Invalidate();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (False(atomic_types & IR::Type::U64)) {
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::LoadSharedU64: {
|
||||||
|
const IR::U32 value0{ir.LoadShared(32, false, offset)};
|
||||||
|
const IR::U32 value1{ir.LoadShared(32, false, ir.IAdd(offset, ir.Imm32(4U)))};
|
||||||
|
const IR::Value value{ir.PackUint2x32(ir.CompositeConstruct(value0, value1))};
|
||||||
|
inst.ReplaceUsesWithAndRemove(value);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case IR::Opcode::WriteSharedU64: {
|
||||||
|
const IR::Value value{ir.UnpackUint2x32(IR::U64{inst.Arg(1)})};
|
||||||
|
const IR::U32 value0{ir.CompositeExtract(value, 0)};
|
||||||
|
const IR::U32 value1{ir.CompositeExtract(value, 1)};
|
||||||
|
ir.WriteShared(32, value0, offset);
|
||||||
|
ir.WriteShared(32, value1, ir.IAdd(offset, ir.Imm32(4U)));
|
||||||
|
inst.Invalidate();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Shader::Optimization
|
|
@ -10,17 +10,23 @@ namespace Shader::Optimization {
|
||||||
static bool IsSharedAccess(const IR::Inst& inst) {
|
static bool IsSharedAccess(const IR::Inst& inst) {
|
||||||
const auto opcode = inst.GetOpcode();
|
const auto opcode = inst.GetOpcode();
|
||||||
switch (opcode) {
|
switch (opcode) {
|
||||||
|
case IR::Opcode::LoadSharedU16:
|
||||||
case IR::Opcode::LoadSharedU32:
|
case IR::Opcode::LoadSharedU32:
|
||||||
case IR::Opcode::LoadSharedU64:
|
case IR::Opcode::LoadSharedU64:
|
||||||
|
case IR::Opcode::WriteSharedU16:
|
||||||
case IR::Opcode::WriteSharedU32:
|
case IR::Opcode::WriteSharedU32:
|
||||||
case IR::Opcode::WriteSharedU64:
|
case IR::Opcode::WriteSharedU64:
|
||||||
case IR::Opcode::SharedAtomicAnd32:
|
|
||||||
case IR::Opcode::SharedAtomicIAdd32:
|
case IR::Opcode::SharedAtomicIAdd32:
|
||||||
case IR::Opcode::SharedAtomicOr32:
|
case IR::Opcode::SharedAtomicIAdd64:
|
||||||
case IR::Opcode::SharedAtomicSMax32:
|
case IR::Opcode::SharedAtomicISub32:
|
||||||
case IR::Opcode::SharedAtomicUMax32:
|
|
||||||
case IR::Opcode::SharedAtomicSMin32:
|
case IR::Opcode::SharedAtomicSMin32:
|
||||||
case IR::Opcode::SharedAtomicUMin32:
|
case IR::Opcode::SharedAtomicUMin32:
|
||||||
|
case IR::Opcode::SharedAtomicSMax32:
|
||||||
|
case IR::Opcode::SharedAtomicUMax32:
|
||||||
|
case IR::Opcode::SharedAtomicInc32:
|
||||||
|
case IR::Opcode::SharedAtomicDec32:
|
||||||
|
case IR::Opcode::SharedAtomicAnd32:
|
||||||
|
case IR::Opcode::SharedAtomicOr32:
|
||||||
case IR::Opcode::SharedAtomicXor32:
|
case IR::Opcode::SharedAtomicXor32:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
|
@ -28,24 +34,74 @@ static bool IsSharedAccess(const IR::Inst& inst) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IR::Type CalculateSharedMemoryTypes(IR::Program& program) {
|
||||||
|
IR::Type used_types{IR::Type::Void};
|
||||||
|
for (IR::Block* const block : program.blocks) {
|
||||||
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
|
if (!IsSharedAccess(inst)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::LoadSharedU16:
|
||||||
|
case IR::Opcode::WriteSharedU16:
|
||||||
|
used_types |= IR::Type::U16;
|
||||||
|
break;
|
||||||
|
case IR::Opcode::LoadSharedU32:
|
||||||
|
case IR::Opcode::WriteSharedU32:
|
||||||
|
case IR::Opcode::SharedAtomicIAdd32:
|
||||||
|
case IR::Opcode::SharedAtomicISub32:
|
||||||
|
case IR::Opcode::SharedAtomicSMin32:
|
||||||
|
case IR::Opcode::SharedAtomicUMin32:
|
||||||
|
case IR::Opcode::SharedAtomicSMax32:
|
||||||
|
case IR::Opcode::SharedAtomicUMax32:
|
||||||
|
case IR::Opcode::SharedAtomicInc32:
|
||||||
|
case IR::Opcode::SharedAtomicDec32:
|
||||||
|
case IR::Opcode::SharedAtomicAnd32:
|
||||||
|
case IR::Opcode::SharedAtomicOr32:
|
||||||
|
case IR::Opcode::SharedAtomicXor32:
|
||||||
|
used_types |= IR::Type::U32;
|
||||||
|
break;
|
||||||
|
case IR::Opcode::LoadSharedU64:
|
||||||
|
case IR::Opcode::WriteSharedU64:
|
||||||
|
case IR::Opcode::SharedAtomicIAdd64:
|
||||||
|
used_types |= IR::Type::U64;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return used_types;
|
||||||
|
}
|
||||||
|
|
||||||
void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info,
|
void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||||
const Profile& profile) {
|
const Profile& profile) {
|
||||||
if (program.info.stage != Stage::Compute) {
|
if (program.info.stage != Stage::Compute) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Only perform the transform if the host shared memory is insufficient.
|
|
||||||
|
// Run this pass if:
|
||||||
|
// * There are shared memory instructions.
|
||||||
|
// * One of the following is true:
|
||||||
|
// * Requested shared memory size is too large for the host shared memory.
|
||||||
|
// * Workgroup explicit memory is not supported and multiple shared memory types are used.
|
||||||
const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size;
|
const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size;
|
||||||
if (shared_memory_size <= profile.max_shared_memory_size) {
|
const auto used_types = CalculateSharedMemoryTypes(program);
|
||||||
|
if (used_types == IR::Type::Void || (shared_memory_size <= profile.max_shared_memory_size &&
|
||||||
|
(profile.supports_workgroup_explicit_memory_layout ||
|
||||||
|
std::popcount(static_cast<u32>(used_types)) == 1))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Add buffer binding for shared memory storage buffer.
|
|
||||||
|
// Add a buffer binding for shared memory storage buffer.
|
||||||
const u32 binding = static_cast<u32>(program.info.buffers.size());
|
const u32 binding = static_cast<u32>(program.info.buffers.size());
|
||||||
program.info.buffers.push_back({
|
program.info.buffers.push_back({
|
||||||
.used_types = IR::Type::U32,
|
.used_types = used_types,
|
||||||
.inline_cbuf = AmdGpu::Buffer::Null(),
|
.inline_cbuf = AmdGpu::Buffer::Null(),
|
||||||
.buffer_type = BufferType::SharedMemory,
|
.buffer_type = BufferType::SharedMemory,
|
||||||
.is_written = true,
|
.is_written = true,
|
||||||
});
|
});
|
||||||
|
|
||||||
for (IR::Block* const block : program.blocks) {
|
for (IR::Block* const block : program.blocks) {
|
||||||
for (IR::Inst& inst : block->Instructions()) {
|
for (IR::Inst& inst : block->Instructions()) {
|
||||||
if (!IsSharedAccess(inst)) {
|
if (!IsSharedAccess(inst)) {
|
||||||
|
@ -53,58 +109,67 @@ void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_
|
||||||
}
|
}
|
||||||
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
|
IR::IREmitter ir{*block, IR::Block::InstructionList::s_iterator_to(inst)};
|
||||||
const IR::U32 handle = ir.Imm32(binding);
|
const IR::U32 handle = ir.Imm32(binding);
|
||||||
// Replace shared atomics first
|
|
||||||
switch (inst.GetOpcode()) {
|
|
||||||
case IR::Opcode::SharedAtomicAnd32:
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicAnd(handle, inst.Arg(0), inst.Arg(1), {}));
|
|
||||||
continue;
|
|
||||||
case IR::Opcode::SharedAtomicIAdd32:
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicIAdd(handle, inst.Arg(0), inst.Arg(1), {}));
|
|
||||||
continue;
|
|
||||||
case IR::Opcode::SharedAtomicOr32:
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicOr(handle, inst.Arg(0), inst.Arg(1), {}));
|
|
||||||
continue;
|
|
||||||
case IR::Opcode::SharedAtomicSMax32:
|
|
||||||
case IR::Opcode::SharedAtomicUMax32: {
|
|
||||||
const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32;
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicIMax(handle, inst.Arg(0), inst.Arg(1), is_signed, {}));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case IR::Opcode::SharedAtomicSMin32:
|
|
||||||
case IR::Opcode::SharedAtomicUMin32: {
|
|
||||||
const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32;
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicIMin(handle, inst.Arg(0), inst.Arg(1), is_signed, {}));
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
case IR::Opcode::SharedAtomicXor32:
|
|
||||||
inst.ReplaceUsesWithAndRemove(
|
|
||||||
ir.BufferAtomicXor(handle, inst.Arg(0), inst.Arg(1), {}));
|
|
||||||
continue;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Replace shared operations.
|
|
||||||
const IR::U32 offset = ir.IMul(ir.GetAttributeU32(IR::Attribute::WorkgroupIndex),
|
const IR::U32 offset = ir.IMul(ir.GetAttributeU32(IR::Attribute::WorkgroupIndex),
|
||||||
ir.Imm32(shared_memory_size));
|
ir.Imm32(shared_memory_size));
|
||||||
const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset);
|
const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset);
|
||||||
switch (inst.GetOpcode()) {
|
switch (inst.GetOpcode()) {
|
||||||
|
case IR::Opcode::SharedAtomicIAdd32:
|
||||||
|
case IR::Opcode::SharedAtomicIAdd64:
|
||||||
|
inst.ReplaceUsesWithAndRemove(
|
||||||
|
ir.BufferAtomicIAdd(handle, address, inst.Arg(1), {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicISub32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(
|
||||||
|
ir.BufferAtomicISub(handle, address, inst.Arg(1), {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicSMin32:
|
||||||
|
case IR::Opcode::SharedAtomicUMin32: {
|
||||||
|
const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMin32;
|
||||||
|
inst.ReplaceUsesWithAndRemove(
|
||||||
|
ir.BufferAtomicIMin(handle, address, inst.Arg(1), is_signed, {}));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case IR::Opcode::SharedAtomicSMax32:
|
||||||
|
case IR::Opcode::SharedAtomicUMax32: {
|
||||||
|
const bool is_signed = inst.GetOpcode() == IR::Opcode::SharedAtomicSMax32;
|
||||||
|
inst.ReplaceUsesWithAndRemove(
|
||||||
|
ir.BufferAtomicIMax(handle, address, inst.Arg(1), is_signed, {}));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
case IR::Opcode::SharedAtomicInc32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.BufferAtomicInc(handle, address, {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicDec32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.BufferAtomicDec(handle, address, {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicAnd32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.BufferAtomicAnd(handle, address, inst.Arg(1), {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicOr32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.BufferAtomicOr(handle, address, inst.Arg(1), {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::SharedAtomicXor32:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.BufferAtomicXor(handle, address, inst.Arg(1), {}));
|
||||||
|
continue;
|
||||||
|
case IR::Opcode::LoadSharedU16:
|
||||||
|
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU16(handle, address, {}));
|
||||||
|
break;
|
||||||
case IR::Opcode::LoadSharedU32:
|
case IR::Opcode::LoadSharedU32:
|
||||||
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {}));
|
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {}));
|
||||||
break;
|
break;
|
||||||
case IR::Opcode::LoadSharedU64:
|
case IR::Opcode::LoadSharedU64:
|
||||||
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(2, handle, address, {}));
|
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU64(handle, address, {}));
|
||||||
|
break;
|
||||||
|
case IR::Opcode::WriteSharedU16:
|
||||||
|
ir.StoreBufferU16(handle, address, IR::U16{inst.Arg(1)}, {});
|
||||||
|
inst.Invalidate();
|
||||||
break;
|
break;
|
||||||
case IR::Opcode::WriteSharedU32:
|
case IR::Opcode::WriteSharedU32:
|
||||||
ir.StoreBufferU32(1, handle, address, inst.Arg(1), {});
|
ir.StoreBufferU32(1, handle, address, inst.Arg(1), {});
|
||||||
inst.Invalidate();
|
inst.Invalidate();
|
||||||
break;
|
break;
|
||||||
case IR::Opcode::WriteSharedU64:
|
case IR::Opcode::WriteSharedU64:
|
||||||
ir.StoreBufferU32(2, handle, address, inst.Arg(1), {});
|
ir.StoreBufferU64(handle, address, IR::U64{inst.Arg(1)}, {});
|
||||||
inst.Invalidate();
|
inst.Invalidate();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/enum.h"
|
#include "common/enum.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "video_core/amdgpu/types.h"
|
#include "video_core/amdgpu/pixel_format.h"
|
||||||
|
|
||||||
namespace Shader::IR {
|
namespace Shader::IR {
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,7 @@ using U32F32 = TypedValue<Type::U32 | Type::F32>;
|
||||||
using U64F64 = TypedValue<Type::U64 | Type::F64>;
|
using U64F64 = TypedValue<Type::U64 | Type::F64>;
|
||||||
using U32U64 = TypedValue<Type::U32 | Type::U64>;
|
using U32U64 = TypedValue<Type::U32 | Type::U64>;
|
||||||
using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>;
|
using U16U32U64 = TypedValue<Type::U16 | Type::U32 | Type::U64>;
|
||||||
|
using U8U16U32U64 = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
|
||||||
using F32F64 = TypedValue<Type::F32 | Type::F64>;
|
using F32F64 = TypedValue<Type::F32 | Type::F64>;
|
||||||
using F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>;
|
using F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>;
|
||||||
using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
|
using UAny = TypedValue<Type::U8 | Type::U16 | Type::U32 | Type::U64>;
|
||||||
|
|
|
@ -23,13 +23,13 @@ struct Profile {
|
||||||
bool support_fp32_denorm_preserve{};
|
bool support_fp32_denorm_preserve{};
|
||||||
bool support_fp32_denorm_flush{};
|
bool support_fp32_denorm_flush{};
|
||||||
bool support_fp32_round_to_zero{};
|
bool support_fp32_round_to_zero{};
|
||||||
bool support_explicit_workgroup_layout{};
|
|
||||||
bool support_legacy_vertex_attributes{};
|
bool support_legacy_vertex_attributes{};
|
||||||
bool supports_image_load_store_lod{};
|
bool supports_image_load_store_lod{};
|
||||||
bool supports_native_cube_calc{};
|
bool supports_native_cube_calc{};
|
||||||
bool supports_trinary_minmax{};
|
bool supports_trinary_minmax{};
|
||||||
bool supports_robust_buffer_access{};
|
bool supports_robust_buffer_access{};
|
||||||
bool supports_image_fp32_atomic_min_max{};
|
bool supports_image_fp32_atomic_min_max{};
|
||||||
|
bool supports_workgroup_explicit_memory_layout{};
|
||||||
bool has_broken_spirv_clamp{};
|
bool has_broken_spirv_clamp{};
|
||||||
bool lower_left_origin_mode{};
|
bool lower_left_origin_mode{};
|
||||||
bool needs_manual_interpolation{};
|
bool needs_manual_interpolation{};
|
||||||
|
|
|
@ -78,6 +78,7 @@ IR::Program TranslateProgram(std::span<const u32> code, Pools& pools, Info& info
|
||||||
Shader::Optimization::FlattenExtendedUserdataPass(program);
|
Shader::Optimization::FlattenExtendedUserdataPass(program);
|
||||||
Shader::Optimization::ResourceTrackingPass(program);
|
Shader::Optimization::ResourceTrackingPass(program);
|
||||||
Shader::Optimization::LowerBufferFormatToRaw(program);
|
Shader::Optimization::LowerBufferFormatToRaw(program);
|
||||||
|
Shader::Optimization::SharedMemorySimplifyPass(program, profile);
|
||||||
Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile);
|
Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile);
|
||||||
Shader::Optimization::SharedMemoryBarrierPass(program, runtime_info, profile);
|
Shader::Optimization::SharedMemoryBarrierPass(program, runtime_info, profile);
|
||||||
Shader::Optimization::IdentityRemovalPass(program.blocks);
|
Shader::Optimization::IdentityRemovalPass(program.blocks);
|
||||||
|
|
|
@ -149,6 +149,7 @@ struct GeometryRuntimeInfo {
|
||||||
u32 out_vertex_data_size{};
|
u32 out_vertex_data_size{};
|
||||||
AmdGpu::PrimitiveType in_primitive;
|
AmdGpu::PrimitiveType in_primitive;
|
||||||
GsOutputPrimTypes out_primitive;
|
GsOutputPrimTypes out_primitive;
|
||||||
|
AmdGpu::Liverpool::GsMode::Mode mode;
|
||||||
std::span<const u32> vs_copy;
|
std::span<const u32> vs_copy;
|
||||||
u64 vs_copy_hash;
|
u64 vs_copy_hash;
|
||||||
|
|
||||||
|
@ -196,11 +197,13 @@ struct FragmentRuntimeInfo {
|
||||||
u32 num_inputs;
|
u32 num_inputs;
|
||||||
std::array<PsInput, 32> inputs;
|
std::array<PsInput, 32> inputs;
|
||||||
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
|
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
|
||||||
|
bool dual_source_blending;
|
||||||
|
|
||||||
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
|
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
|
||||||
return std::ranges::equal(color_buffers, other.color_buffers) &&
|
return std::ranges::equal(color_buffers, other.color_buffers) &&
|
||||||
en_flags.raw == other.en_flags.raw && addr_flags.raw == other.addr_flags.raw &&
|
en_flags.raw == other.en_flags.raw && addr_flags.raw == other.addr_flags.raw &&
|
||||||
num_inputs == other.num_inputs &&
|
num_inputs == other.num_inputs &&
|
||||||
|
dual_source_blending == other.dual_source_blending &&
|
||||||
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
|
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
|
||||||
other.inputs.begin() + num_inputs);
|
other.inputs.begin() + num_inputs);
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,9 +228,12 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
|
||||||
const u32 type = header->type;
|
const u32 type = header->type;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("Wrong PM4 type {}", type);
|
||||||
|
break;
|
||||||
case 0:
|
case 0:
|
||||||
case 1:
|
UNREACHABLE_MSG("Unimplemented PM4 type 0, base reg: {}, size: {}",
|
||||||
UNREACHABLE_MSG("Unsupported PM4 type {}", type);
|
header->type0.base.Value(), header->type0.NumWords());
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
// Type-2 packet are used for padding purposes
|
// Type-2 packet are used for padding purposes
|
||||||
|
@ -826,6 +829,19 @@ Liverpool::Task Liverpool::ProcessCompute(const u32* acb, u32 acb_dwords, u32 vq
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (header->type == 2) {
|
||||||
|
// Type-2 packet are used for padding purposes
|
||||||
|
next_dw_off = 1;
|
||||||
|
acb += next_dw_off;
|
||||||
|
acb_dwords -= next_dw_off;
|
||||||
|
|
||||||
|
if constexpr (!is_indirect) {
|
||||||
|
*queue.read_addr += next_dw_off;
|
||||||
|
*queue.read_addr %= queue.ring_size_dw;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (header->type != 3) {
|
if (header->type != 3) {
|
||||||
// No other types of packets were spotted so far
|
// No other types of packets were spotted so far
|
||||||
UNREACHABLE_MSG("Invalid PM4 type {}", header->type.Value());
|
UNREACHABLE_MSG("Invalid PM4 type {}", header->type.Value());
|
||||||
|
|
|
@ -914,7 +914,7 @@ struct Liverpool {
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t GetColorSliceSize() const {
|
size_t GetColorSliceSize() const {
|
||||||
const auto num_bytes_per_element = NumBits(info.format) / 8u;
|
const auto num_bytes_per_element = NumBitsPerBlock(info.format) / 8u;
|
||||||
const auto slice_size =
|
const auto slice_size =
|
||||||
num_bytes_per_element * (slice.tile_max + 1) * 64u * NumSamples();
|
num_bytes_per_element * (slice.tile_max + 1) * 64u * NumSamples();
|
||||||
return slice_size;
|
return slice_size;
|
||||||
|
@ -1179,8 +1179,16 @@ struct Liverpool {
|
||||||
};
|
};
|
||||||
|
|
||||||
union GsMode {
|
union GsMode {
|
||||||
|
enum class Mode : u32 {
|
||||||
|
Off = 0,
|
||||||
|
ScenarioA = 1,
|
||||||
|
ScenarioB = 2,
|
||||||
|
ScenarioG = 3,
|
||||||
|
ScenarioC = 4,
|
||||||
|
};
|
||||||
|
|
||||||
u32 raw;
|
u32 raw;
|
||||||
BitField<0, 3, u32> mode;
|
BitField<0, 3, Mode> mode;
|
||||||
BitField<3, 2, u32> cut_mode;
|
BitField<3, 2, u32> cut_mode;
|
||||||
BitField<22, 2, u32> onchip;
|
BitField<22, 2, u32> onchip;
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,136 +111,106 @@ std::string_view NameOf(NumberFormat fmt) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int NumComponents(DataFormat format) {
|
static constexpr std::array NUM_COMPONENTS = {
|
||||||
constexpr std::array num_components_per_element = {
|
0, // 0 FormatInvalid
|
||||||
0, 1, 1, 2, 1, 2, 3, 3, 4, 4, 4, 2, 4, 3, 4, -1, 3, 4, 4, 4, 2,
|
1, // 1 Format8
|
||||||
2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 4, 4, 4, 1, 2, 3, 4,
|
1, // 2 Format16
|
||||||
-1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 1};
|
2, // 3 Format8_8
|
||||||
|
1, // 4 Format32
|
||||||
const u32 index = static_cast<u32>(format);
|
2, // 5 Format16_16
|
||||||
if (index >= num_components_per_element.size()) {
|
3, // 6 Format10_11_11
|
||||||
return 0;
|
3, // 7 Format11_11_10
|
||||||
}
|
4, // 8 Format10_10_10_2
|
||||||
return num_components_per_element[index];
|
4, // 9 Format2_10_10_10
|
||||||
}
|
4, // 10 Format8_8_8_8
|
||||||
|
2, // 11 Format32_32
|
||||||
int NumBits(DataFormat format) {
|
4, // 12 Format16_16_16_16
|
||||||
const std::array num_bits_per_element = {
|
3, // 13 Format32_32_32
|
||||||
0, 8, 16, 16, 32, 32, 32, 32, 32, 32, 32, 64, 64, 96, 128, -1, 16, 16, 16, 16, 32,
|
4, // 14 Format32_32_32_32
|
||||||
32, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, 16, 32, 4, 8, 8, 4, 8, 8, 8,
|
0, // 15
|
||||||
-1, -1, 8, 8, 8, 8, 8, 8, 16, 16, 32, 32, 32, 64, 64, 8, 16, 1, 1};
|
3, // 16 Format5_6_5
|
||||||
|
4, // 17 Format1_5_5_5
|
||||||
const u32 index = static_cast<u32>(format);
|
4, // 18 Format5_5_5_1
|
||||||
if (index >= num_bits_per_element.size()) {
|
4, // 19 Format4_4_4_4
|
||||||
return 0;
|
2, // 20 Format8_24
|
||||||
}
|
2, // 21 Format24_8
|
||||||
return num_bits_per_element[index];
|
2, // 22 FormatX24_8_32
|
||||||
}
|
0, // 23
|
||||||
|
0, // 24
|
||||||
static constexpr std::array component_bits = {
|
0, // 25
|
||||||
std::array{0, 0, 0, 0}, // 0 FormatInvalid
|
0, // 26
|
||||||
std::array{8, 0, 0, 0}, // 1 Format8
|
0, // 27
|
||||||
std::array{16, 0, 0, 0}, // 2 Format16
|
0, // 28
|
||||||
std::array{8, 8, 0, 0}, // 3 Format8_8
|
0, // 29
|
||||||
std::array{32, 0, 0, 0}, // 4 Format32
|
0, // 30
|
||||||
std::array{16, 16, 0, 0}, // 5 Format16_16
|
0, // 31
|
||||||
std::array{11, 11, 10, 0}, // 6 Format10_11_11
|
3, // 32 FormatGB_GR
|
||||||
std::array{10, 11, 11, 0}, // 7 Format11_11_10
|
3, // 33 FormatBG_RG
|
||||||
std::array{2, 10, 10, 10}, // 8 Format10_10_10_2
|
4, // 34 Format5_9_9_9
|
||||||
std::array{10, 10, 10, 2}, // 9 Format2_10_10_10
|
4, // 35 FormatBc1
|
||||||
std::array{8, 8, 8, 8}, // 10 Format8_8_8_8
|
4, // 36 FormatBc2
|
||||||
std::array{32, 32, 0, 0}, // 11 Format32_32
|
4, // 37 FormatBc3
|
||||||
std::array{16, 16, 16, 16}, // 12 Format16_16_16_16
|
1, // 38 FormatBc4
|
||||||
std::array{32, 32, 32, 0}, // 13 Format32_32_32
|
2, // 39 FormatBc5
|
||||||
std::array{32, 32, 32, 32}, // 14 Format32_32_32_32
|
3, // 40 FormatBc6
|
||||||
std::array{0, 0, 0, 0}, // 15
|
4, // 41 FormatBc7
|
||||||
std::array{5, 6, 5, 0}, // 16 Format5_6_5
|
|
||||||
std::array{5, 5, 5, 1}, // 17 Format1_5_5_5
|
|
||||||
std::array{1, 5, 5, 5}, // 18 Format5_5_5_1
|
|
||||||
std::array{4, 4, 4, 4}, // 19 Format4_4_4_4
|
|
||||||
std::array{24, 8, 0, 0}, // 20 Format8_24
|
|
||||||
std::array{8, 24, 0, 0}, // 21 Format24_8
|
|
||||||
std::array{8, 24, 0, 0}, // 22 FormatX24_8_32
|
|
||||||
std::array{0, 0, 0, 0}, // 23
|
|
||||||
std::array{0, 0, 0, 0}, // 24
|
|
||||||
std::array{0, 0, 0, 0}, // 25
|
|
||||||
std::array{0, 0, 0, 0}, // 26
|
|
||||||
std::array{0, 0, 0, 0}, // 27
|
|
||||||
std::array{0, 0, 0, 0}, // 28
|
|
||||||
std::array{0, 0, 0, 0}, // 29
|
|
||||||
std::array{0, 0, 0, 0}, // 30
|
|
||||||
std::array{0, 0, 0, 0}, // 31
|
|
||||||
std::array{0, 0, 0, 0}, // 32 FormatGB_GR
|
|
||||||
std::array{0, 0, 0, 0}, // 33 FormatBG_RG
|
|
||||||
std::array{0, 0, 0, 0}, // 34 Format5_9_9_9
|
|
||||||
std::array{0, 0, 0, 0}, // 35 FormatBc1
|
|
||||||
std::array{0, 0, 0, 0}, // 36 FormatBc2
|
|
||||||
std::array{0, 0, 0, 0}, // 37 FormatBc3
|
|
||||||
std::array{0, 0, 0, 0}, // 38 FormatBc4
|
|
||||||
std::array{0, 0, 0, 0}, // 39 FormatBc5
|
|
||||||
std::array{0, 0, 0, 0}, // 40 FormatBc6
|
|
||||||
std::array{0, 0, 0, 0}, // 41 FormatBc7
|
|
||||||
};
|
};
|
||||||
|
|
||||||
u32 ComponentBits(DataFormat format, u32 comp) {
|
u32 NumComponents(DataFormat format) {
|
||||||
const u32 index = static_cast<u32>(format);
|
const u32 index = static_cast<u32>(format);
|
||||||
if (index >= component_bits.size() || comp >= 4) {
|
ASSERT_MSG(index < NUM_COMPONENTS.size(), "Invalid data format = {}", format);
|
||||||
return 0;
|
return NUM_COMPONENTS[index];
|
||||||
}
|
|
||||||
return component_bits[index][comp];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static constexpr std::array component_offset = {
|
static constexpr std::array BITS_PER_BLOCK = {
|
||||||
std::array{-1, -1, -1, -1}, // 0 FormatInvalid
|
0, // 0 FormatInvalid
|
||||||
std::array{0, -1, -1, -1}, // 1 Format8
|
8, // 1 Format8
|
||||||
std::array{0, -1, -1, -1}, // 2 Format16
|
16, // 2 Format16
|
||||||
std::array{0, 8, -1, -1}, // 3 Format8_8
|
16, // 3 Format8_8
|
||||||
std::array{0, -1, -1, -1}, // 4 Format32
|
32, // 4 Format32
|
||||||
std::array{0, 16, -1, -1}, // 5 Format16_16
|
32, // 5 Format16_16
|
||||||
std::array{0, 11, 22, -1}, // 6 Format10_11_11
|
32, // 6 Format10_11_11
|
||||||
std::array{0, 10, 21, -1}, // 7 Format11_11_10
|
32, // 7 Format11_11_10
|
||||||
std::array{0, 2, 12, 22}, // 8 Format10_10_10_2
|
32, // 8 Format10_10_10_2
|
||||||
std::array{0, 10, 20, 30}, // 9 Format2_10_10_10
|
32, // 9 Format2_10_10_10
|
||||||
std::array{0, 8, 16, 24}, // 10 Format8_8_8_8
|
32, // 10 Format8_8_8_8
|
||||||
std::array{0, 32, -1, -1}, // 11 Format32_32
|
64, // 11 Format32_32
|
||||||
std::array{0, 16, 32, 48}, // 12 Format16_16_16_16
|
64, // 12 Format16_16_16_16
|
||||||
std::array{0, 32, 64, -1}, // 13 Format32_32_32
|
96, // 13 Format32_32_32
|
||||||
std::array{0, 32, 64, 96}, // 14 Format32_32_32_32
|
128, // 14 Format32_32_32_32
|
||||||
std::array{-1, -1, -1, -1}, // 15
|
0, // 15
|
||||||
std::array{0, 5, 11, -1}, // 16 Format5_6_5
|
16, // 16 Format5_6_5
|
||||||
std::array{0, 5, 10, 15}, // 17 Format1_5_5_5
|
16, // 17 Format1_5_5_5
|
||||||
std::array{0, 1, 6, 11}, // 18 Format5_5_5_1
|
16, // 18 Format5_5_5_1
|
||||||
std::array{0, 4, 8, 12}, // 19 Format4_4_4_4
|
16, // 19 Format4_4_4_4
|
||||||
std::array{0, 24, -1, -1}, // 20 Format8_24
|
32, // 20 Format8_24
|
||||||
std::array{0, 8, -1, -1}, // 21 Format24_8
|
32, // 21 Format24_8
|
||||||
std::array{0, 8, -1, -1}, // 22 FormatX24_8_32
|
64, // 22 FormatX24_8_32
|
||||||
std::array{-1, -1, -1, -1}, // 23
|
0, // 23
|
||||||
std::array{-1, -1, -1, -1}, // 24
|
0, // 24
|
||||||
std::array{-1, -1, -1, -1}, // 25
|
0, // 25
|
||||||
std::array{-1, -1, -1, -1}, // 26
|
0, // 26
|
||||||
std::array{-1, -1, -1, -1}, // 27
|
0, // 27
|
||||||
std::array{-1, -1, -1, -1}, // 28
|
0, // 28
|
||||||
std::array{-1, -1, -1, -1}, // 29
|
0, // 29
|
||||||
std::array{-1, -1, -1, -1}, // 30
|
0, // 30
|
||||||
std::array{-1, -1, -1, -1}, // 31
|
0, // 31
|
||||||
std::array{-1, -1, -1, -1}, // 32 FormatGB_GR
|
16, // 32 FormatGB_GR
|
||||||
std::array{-1, -1, -1, -1}, // 33 FormatBG_RG
|
16, // 33 FormatBG_RG
|
||||||
std::array{-1, -1, -1, -1}, // 34 Format5_9_9_9
|
32, // 34 Format5_9_9_9
|
||||||
std::array{-1, -1, -1, -1}, // 35 FormatBc1
|
64, // 35 FormatBc1
|
||||||
std::array{-1, -1, -1, -1}, // 36 FormatBc2
|
128, // 36 FormatBc2
|
||||||
std::array{-1, -1, -1, -1}, // 37 FormatBc3
|
128, // 37 FormatBc3
|
||||||
std::array{-1, -1, -1, -1}, // 38 FormatBc4
|
64, // 38 FormatBc4
|
||||||
std::array{-1, -1, -1, -1}, // 39 FormatBc5
|
128, // 39 FormatBc5
|
||||||
std::array{-1, -1, -1, -1}, // 40 FormatBc6
|
128, // 40 FormatBc6
|
||||||
std::array{-1, -1, -1, -1}, // 41 FormatBc7
|
128, // 41 FormatBc7
|
||||||
};
|
};
|
||||||
|
|
||||||
s32 ComponentOffset(DataFormat format, u32 comp) {
|
u32 NumBitsPerBlock(DataFormat format) {
|
||||||
const u32 index = static_cast<u32>(format);
|
const u32 index = static_cast<u32>(format);
|
||||||
if (index >= component_offset.size() || comp >= 4) {
|
ASSERT_MSG(index < BITS_PER_BLOCK.size(), "Invalid data format = {}", format);
|
||||||
return -1;
|
return BITS_PER_BLOCK[index];
|
||||||
}
|
|
||||||
return component_offset[index][comp];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace AmdGpu
|
} // namespace AmdGpu
|
||||||
|
|
|
@ -5,39 +5,313 @@
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
|
#include "common/assert.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
#include "video_core/amdgpu/types.h"
|
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
|
||||||
enum NumberClass {
|
// Table 8.13 Data and Image Formats [Sea Islands Series Instruction Set Architecture]
|
||||||
|
enum class DataFormat : u32 {
|
||||||
|
FormatInvalid = 0,
|
||||||
|
Format8 = 1,
|
||||||
|
Format16 = 2,
|
||||||
|
Format8_8 = 3,
|
||||||
|
Format32 = 4,
|
||||||
|
Format16_16 = 5,
|
||||||
|
Format10_11_11 = 6,
|
||||||
|
Format11_11_10 = 7,
|
||||||
|
Format10_10_10_2 = 8,
|
||||||
|
Format2_10_10_10 = 9,
|
||||||
|
Format8_8_8_8 = 10,
|
||||||
|
Format32_32 = 11,
|
||||||
|
Format16_16_16_16 = 12,
|
||||||
|
Format32_32_32 = 13,
|
||||||
|
Format32_32_32_32 = 14,
|
||||||
|
Format5_6_5 = 16,
|
||||||
|
Format1_5_5_5 = 17,
|
||||||
|
Format5_5_5_1 = 18,
|
||||||
|
Format4_4_4_4 = 19,
|
||||||
|
Format8_24 = 20,
|
||||||
|
Format24_8 = 21,
|
||||||
|
FormatX24_8_32 = 22,
|
||||||
|
FormatGB_GR = 32,
|
||||||
|
FormatBG_RG = 33,
|
||||||
|
Format5_9_9_9 = 34,
|
||||||
|
FormatBc1 = 35,
|
||||||
|
FormatBc2 = 36,
|
||||||
|
FormatBc3 = 37,
|
||||||
|
FormatBc4 = 38,
|
||||||
|
FormatBc5 = 39,
|
||||||
|
FormatBc6 = 40,
|
||||||
|
FormatBc7 = 41,
|
||||||
|
FormatFmask8_1 = 47,
|
||||||
|
FormatFmask8_2 = 48,
|
||||||
|
FormatFmask8_4 = 49,
|
||||||
|
FormatFmask16_1 = 50,
|
||||||
|
FormatFmask16_2 = 51,
|
||||||
|
FormatFmask32_2 = 52,
|
||||||
|
FormatFmask32_4 = 53,
|
||||||
|
FormatFmask32_8 = 54,
|
||||||
|
FormatFmask64_4 = 55,
|
||||||
|
FormatFmask64_8 = 56,
|
||||||
|
Format4_4 = 57,
|
||||||
|
Format6_5_5 = 58,
|
||||||
|
Format1 = 59,
|
||||||
|
Format1_Reversed = 60,
|
||||||
|
Format32_As_8 = 61,
|
||||||
|
Format32_As_8_8 = 62,
|
||||||
|
Format32_As_32_32_32_32 = 63,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NumberFormat : u32 {
|
||||||
|
Unorm = 0,
|
||||||
|
Snorm = 1,
|
||||||
|
Uscaled = 2,
|
||||||
|
Sscaled = 3,
|
||||||
|
Uint = 4,
|
||||||
|
Sint = 5,
|
||||||
|
SnormNz = 6,
|
||||||
|
Float = 7,
|
||||||
|
Srgb = 9,
|
||||||
|
Ubnorm = 10,
|
||||||
|
UbnormNz = 11,
|
||||||
|
Ubint = 12,
|
||||||
|
Ubscaled = 13,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NumberClass {
|
||||||
Float,
|
Float,
|
||||||
Sint,
|
Sint,
|
||||||
Uint,
|
Uint,
|
||||||
};
|
};
|
||||||
|
|
||||||
[[nodiscard]] constexpr NumberClass GetNumberClass(const NumberFormat nfmt) {
|
enum class CompSwizzle : u8 {
|
||||||
switch (nfmt) {
|
Zero = 0,
|
||||||
case NumberFormat::Sint:
|
One = 1,
|
||||||
return Sint;
|
Red = 4,
|
||||||
case NumberFormat::Uint:
|
Green = 5,
|
||||||
return Uint;
|
Blue = 6,
|
||||||
|
Alpha = 7,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class NumberConversion : u32 {
|
||||||
|
None = 0,
|
||||||
|
UintToUscaled = 1,
|
||||||
|
SintToSscaled = 2,
|
||||||
|
UnormToUbnorm = 3,
|
||||||
|
Sint8ToSnormNz = 4,
|
||||||
|
Sint16ToSnormNz = 5,
|
||||||
|
Uint32ToUnorm = 6,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct CompMapping {
|
||||||
|
CompSwizzle r;
|
||||||
|
CompSwizzle g;
|
||||||
|
CompSwizzle b;
|
||||||
|
CompSwizzle a;
|
||||||
|
|
||||||
|
auto operator<=>(const CompMapping& other) const = default;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
[[nodiscard]] std::array<T, 4> Apply(const std::array<T, 4>& data) const {
|
||||||
|
return {
|
||||||
|
ApplySingle(data, r),
|
||||||
|
ApplySingle(data, g),
|
||||||
|
ApplySingle(data, b),
|
||||||
|
ApplySingle(data, a),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] CompMapping Inverse() const {
|
||||||
|
CompMapping result{};
|
||||||
|
InverseSingle(result.r, CompSwizzle::Red);
|
||||||
|
InverseSingle(result.g, CompSwizzle::Green);
|
||||||
|
InverseSingle(result.b, CompSwizzle::Blue);
|
||||||
|
InverseSingle(result.a, CompSwizzle::Alpha);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename T>
|
||||||
|
T ApplySingle(const std::array<T, 4>& data, const CompSwizzle swizzle) const {
|
||||||
|
switch (swizzle) {
|
||||||
|
case CompSwizzle::Zero:
|
||||||
|
return T(0);
|
||||||
|
case CompSwizzle::One:
|
||||||
|
return T(1);
|
||||||
|
case CompSwizzle::Red:
|
||||||
|
return data[0];
|
||||||
|
case CompSwizzle::Green:
|
||||||
|
return data[1];
|
||||||
|
case CompSwizzle::Blue:
|
||||||
|
return data[2];
|
||||||
|
case CompSwizzle::Alpha:
|
||||||
|
return data[3];
|
||||||
default:
|
default:
|
||||||
return Float;
|
UNREACHABLE();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] constexpr bool IsInteger(const NumberFormat nfmt) {
|
void InverseSingle(CompSwizzle& dst, const CompSwizzle target) const {
|
||||||
|
if (r == target) {
|
||||||
|
dst = CompSwizzle::Red;
|
||||||
|
} else if (g == target) {
|
||||||
|
dst = CompSwizzle::Green;
|
||||||
|
} else if (b == target) {
|
||||||
|
dst = CompSwizzle::Blue;
|
||||||
|
} else if (a == target) {
|
||||||
|
dst = CompSwizzle::Alpha;
|
||||||
|
} else {
|
||||||
|
dst = CompSwizzle::Zero;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static constexpr CompMapping IdentityMapping = {
|
||||||
|
.r = CompSwizzle::Red,
|
||||||
|
.g = CompSwizzle::Green,
|
||||||
|
.b = CompSwizzle::Blue,
|
||||||
|
.a = CompSwizzle::Alpha,
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr DataFormat RemapDataFormat(const DataFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case DataFormat::Format11_11_10:
|
||||||
|
return DataFormat::Format10_11_11;
|
||||||
|
case DataFormat::Format10_10_10_2:
|
||||||
|
return DataFormat::Format2_10_10_10;
|
||||||
|
case DataFormat::Format5_5_5_1:
|
||||||
|
return DataFormat::Format1_5_5_5;
|
||||||
|
default:
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr NumberFormat RemapNumberFormat(const NumberFormat format, const DataFormat data_format) {
|
||||||
|
switch (format) {
|
||||||
|
case NumberFormat::Unorm: {
|
||||||
|
switch (data_format) {
|
||||||
|
case DataFormat::Format32:
|
||||||
|
case DataFormat::Format32_32:
|
||||||
|
case DataFormat::Format32_32_32:
|
||||||
|
case DataFormat::Format32_32_32_32:
|
||||||
|
return NumberFormat::Uint;
|
||||||
|
default:
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NumberFormat::Uscaled:
|
||||||
|
return NumberFormat::Uint;
|
||||||
|
case NumberFormat::Sscaled:
|
||||||
|
case NumberFormat::SnormNz:
|
||||||
|
return NumberFormat::Sint;
|
||||||
|
case NumberFormat::Ubnorm:
|
||||||
|
return NumberFormat::Unorm;
|
||||||
|
case NumberFormat::Float:
|
||||||
|
if (data_format == DataFormat::Format8) {
|
||||||
|
// Games may ask for 8-bit float when they want to access the stencil component
|
||||||
|
// of a depth-stencil image. Change to unsigned int to match the stencil format.
|
||||||
|
// This is also the closest approximation to pass the bits through unconverted.
|
||||||
|
return NumberFormat::Uint;
|
||||||
|
}
|
||||||
|
[[fallthrough]];
|
||||||
|
default:
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizzle) {
|
||||||
|
switch (format) {
|
||||||
|
case DataFormat::Format1_5_5_5:
|
||||||
|
case DataFormat::Format11_11_10: {
|
||||||
|
CompMapping result;
|
||||||
|
result.r = swizzle.b;
|
||||||
|
result.g = swizzle.g;
|
||||||
|
result.b = swizzle.r;
|
||||||
|
result.a = swizzle.a;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case DataFormat::Format10_10_10_2: {
|
||||||
|
CompMapping result;
|
||||||
|
result.r = swizzle.a;
|
||||||
|
result.g = swizzle.b;
|
||||||
|
result.b = swizzle.g;
|
||||||
|
result.a = swizzle.r;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
case DataFormat::Format4_4_4_4: {
|
||||||
|
// Remap to a more supported component order.
|
||||||
|
CompMapping result;
|
||||||
|
result.r = swizzle.g;
|
||||||
|
result.g = swizzle.b;
|
||||||
|
result.b = swizzle.a;
|
||||||
|
result.a = swizzle.r;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return swizzle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr NumberConversion MapNumberConversion(const NumberFormat num_fmt,
|
||||||
|
const DataFormat data_fmt) {
|
||||||
|
switch (num_fmt) {
|
||||||
|
case NumberFormat::Unorm: {
|
||||||
|
switch (data_fmt) {
|
||||||
|
case DataFormat::Format32:
|
||||||
|
case DataFormat::Format32_32:
|
||||||
|
case DataFormat::Format32_32_32:
|
||||||
|
case DataFormat::Format32_32_32_32:
|
||||||
|
return NumberConversion::Uint32ToUnorm;
|
||||||
|
default:
|
||||||
|
return NumberConversion::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case NumberFormat::Uscaled:
|
||||||
|
return NumberConversion::UintToUscaled;
|
||||||
|
case NumberFormat::Sscaled:
|
||||||
|
return NumberConversion::SintToSscaled;
|
||||||
|
case NumberFormat::Ubnorm:
|
||||||
|
return NumberConversion::UnormToUbnorm;
|
||||||
|
case NumberFormat::SnormNz: {
|
||||||
|
switch (data_fmt) {
|
||||||
|
case DataFormat::Format8:
|
||||||
|
case DataFormat::Format8_8:
|
||||||
|
case DataFormat::Format8_8_8_8:
|
||||||
|
return NumberConversion::Sint8ToSnormNz;
|
||||||
|
case DataFormat::Format16:
|
||||||
|
case DataFormat::Format16_16:
|
||||||
|
case DataFormat::Format16_16_16_16:
|
||||||
|
return NumberConversion::Sint16ToSnormNz;
|
||||||
|
default:
|
||||||
|
UNREACHABLE_MSG("data_fmt = {}", u32(data_fmt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return NumberConversion::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr NumberClass GetNumberClass(const NumberFormat nfmt) {
|
||||||
|
switch (nfmt) {
|
||||||
|
case NumberFormat::Sint:
|
||||||
|
return NumberClass::Sint;
|
||||||
|
case NumberFormat::Uint:
|
||||||
|
return NumberClass::Uint;
|
||||||
|
default:
|
||||||
|
return NumberClass::Float;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsInteger(const NumberFormat nfmt) {
|
||||||
return nfmt == AmdGpu::NumberFormat::Sint || nfmt == AmdGpu::NumberFormat::Uint;
|
return nfmt == AmdGpu::NumberFormat::Sint || nfmt == AmdGpu::NumberFormat::Uint;
|
||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] std::string_view NameOf(DataFormat fmt);
|
std::string_view NameOf(DataFormat fmt);
|
||||||
[[nodiscard]] std::string_view NameOf(NumberFormat fmt);
|
std::string_view NameOf(NumberFormat fmt);
|
||||||
|
|
||||||
int NumComponents(DataFormat format);
|
u32 NumComponents(DataFormat format);
|
||||||
int NumBits(DataFormat format);
|
u32 NumBitsPerBlock(DataFormat format);
|
||||||
u32 ComponentBits(DataFormat format, u32 comp);
|
|
||||||
s32 ComponentOffset(DataFormat format, u32 comp);
|
|
||||||
|
|
||||||
} // namespace AmdGpu
|
} // namespace AmdGpu
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "common/alignment.h"
|
#include "common/alignment.h"
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
#include "common/bit_field.h"
|
#include "common/bit_field.h"
|
||||||
#include "common/types.h"
|
|
||||||
#include "video_core/amdgpu/pixel_format.h"
|
#include "video_core/amdgpu/pixel_format.h"
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "common/assert.h"
|
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
|
||||||
namespace AmdGpu {
|
namespace AmdGpu {
|
||||||
|
@ -114,281 +113,6 @@ enum class GsOutputPrimitiveType : u32 {
|
||||||
TriangleStrip = 2,
|
TriangleStrip = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Table 8.13 Data and Image Formats [Sea Islands Series Instruction Set Architecture]
|
|
||||||
enum class DataFormat : u32 {
|
|
||||||
FormatInvalid = 0,
|
|
||||||
Format8 = 1,
|
|
||||||
Format16 = 2,
|
|
||||||
Format8_8 = 3,
|
|
||||||
Format32 = 4,
|
|
||||||
Format16_16 = 5,
|
|
||||||
Format10_11_11 = 6,
|
|
||||||
Format11_11_10 = 7,
|
|
||||||
Format10_10_10_2 = 8,
|
|
||||||
Format2_10_10_10 = 9,
|
|
||||||
Format8_8_8_8 = 10,
|
|
||||||
Format32_32 = 11,
|
|
||||||
Format16_16_16_16 = 12,
|
|
||||||
Format32_32_32 = 13,
|
|
||||||
Format32_32_32_32 = 14,
|
|
||||||
Format5_6_5 = 16,
|
|
||||||
Format1_5_5_5 = 17,
|
|
||||||
Format5_5_5_1 = 18,
|
|
||||||
Format4_4_4_4 = 19,
|
|
||||||
Format8_24 = 20,
|
|
||||||
Format24_8 = 21,
|
|
||||||
FormatX24_8_32 = 22,
|
|
||||||
FormatGB_GR = 32,
|
|
||||||
FormatBG_RG = 33,
|
|
||||||
Format5_9_9_9 = 34,
|
|
||||||
FormatBc1 = 35,
|
|
||||||
FormatBc2 = 36,
|
|
||||||
FormatBc3 = 37,
|
|
||||||
FormatBc4 = 38,
|
|
||||||
FormatBc5 = 39,
|
|
||||||
FormatBc6 = 40,
|
|
||||||
FormatBc7 = 41,
|
|
||||||
FormatFmask8_1 = 47,
|
|
||||||
FormatFmask8_2 = 48,
|
|
||||||
FormatFmask8_4 = 49,
|
|
||||||
FormatFmask16_1 = 50,
|
|
||||||
FormatFmask16_2 = 51,
|
|
||||||
FormatFmask32_2 = 52,
|
|
||||||
FormatFmask32_4 = 53,
|
|
||||||
FormatFmask32_8 = 54,
|
|
||||||
FormatFmask64_4 = 55,
|
|
||||||
FormatFmask64_8 = 56,
|
|
||||||
Format4_4 = 57,
|
|
||||||
Format6_5_5 = 58,
|
|
||||||
Format1 = 59,
|
|
||||||
Format1_Reversed = 60,
|
|
||||||
Format32_As_8 = 61,
|
|
||||||
Format32_As_8_8 = 62,
|
|
||||||
Format32_As_32_32_32_32 = 63,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class NumberFormat : u32 {
|
|
||||||
Unorm = 0,
|
|
||||||
Snorm = 1,
|
|
||||||
Uscaled = 2,
|
|
||||||
Sscaled = 3,
|
|
||||||
Uint = 4,
|
|
||||||
Sint = 5,
|
|
||||||
SnormNz = 6,
|
|
||||||
Float = 7,
|
|
||||||
Srgb = 9,
|
|
||||||
Ubnorm = 10,
|
|
||||||
UbnormNz = 11,
|
|
||||||
Ubint = 12,
|
|
||||||
Ubscaled = 13,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class CompSwizzle : u8 {
|
|
||||||
Zero = 0,
|
|
||||||
One = 1,
|
|
||||||
Red = 4,
|
|
||||||
Green = 5,
|
|
||||||
Blue = 6,
|
|
||||||
Alpha = 7,
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class NumberConversion : u32 {
|
|
||||||
None = 0,
|
|
||||||
UintToUscaled = 1,
|
|
||||||
SintToSscaled = 2,
|
|
||||||
UnormToUbnorm = 3,
|
|
||||||
Sint8ToSnormNz = 4,
|
|
||||||
Sint16ToSnormNz = 5,
|
|
||||||
Uint32ToUnorm = 6,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CompMapping {
|
|
||||||
CompSwizzle r;
|
|
||||||
CompSwizzle g;
|
|
||||||
CompSwizzle b;
|
|
||||||
CompSwizzle a;
|
|
||||||
|
|
||||||
auto operator<=>(const CompMapping& other) const = default;
|
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
[[nodiscard]] std::array<T, 4> Apply(const std::array<T, 4>& data) const {
|
|
||||||
return {
|
|
||||||
ApplySingle(data, r),
|
|
||||||
ApplySingle(data, g),
|
|
||||||
ApplySingle(data, b),
|
|
||||||
ApplySingle(data, a),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[[nodiscard]] CompMapping Inverse() const {
|
|
||||||
CompMapping result{};
|
|
||||||
InverseSingle(result.r, CompSwizzle::Red);
|
|
||||||
InverseSingle(result.g, CompSwizzle::Green);
|
|
||||||
InverseSingle(result.b, CompSwizzle::Blue);
|
|
||||||
InverseSingle(result.a, CompSwizzle::Alpha);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
template <typename T>
|
|
||||||
T ApplySingle(const std::array<T, 4>& data, const CompSwizzle swizzle) const {
|
|
||||||
switch (swizzle) {
|
|
||||||
case CompSwizzle::Zero:
|
|
||||||
return T(0);
|
|
||||||
case CompSwizzle::One:
|
|
||||||
return T(1);
|
|
||||||
case CompSwizzle::Red:
|
|
||||||
return data[0];
|
|
||||||
case CompSwizzle::Green:
|
|
||||||
return data[1];
|
|
||||||
case CompSwizzle::Blue:
|
|
||||||
return data[2];
|
|
||||||
case CompSwizzle::Alpha:
|
|
||||||
return data[3];
|
|
||||||
default:
|
|
||||||
UNREACHABLE();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void InverseSingle(CompSwizzle& dst, const CompSwizzle target) const {
|
|
||||||
if (r == target) {
|
|
||||||
dst = CompSwizzle::Red;
|
|
||||||
} else if (g == target) {
|
|
||||||
dst = CompSwizzle::Green;
|
|
||||||
} else if (b == target) {
|
|
||||||
dst = CompSwizzle::Blue;
|
|
||||||
} else if (a == target) {
|
|
||||||
dst = CompSwizzle::Alpha;
|
|
||||||
} else {
|
|
||||||
dst = CompSwizzle::Zero;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr CompMapping IdentityMapping = {
|
|
||||||
.r = CompSwizzle::Red,
|
|
||||||
.g = CompSwizzle::Green,
|
|
||||||
.b = CompSwizzle::Blue,
|
|
||||||
.a = CompSwizzle::Alpha,
|
|
||||||
};
|
|
||||||
|
|
||||||
inline DataFormat RemapDataFormat(const DataFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case DataFormat::Format11_11_10:
|
|
||||||
return DataFormat::Format10_11_11;
|
|
||||||
case DataFormat::Format10_10_10_2:
|
|
||||||
return DataFormat::Format2_10_10_10;
|
|
||||||
case DataFormat::Format5_5_5_1:
|
|
||||||
return DataFormat::Format1_5_5_5;
|
|
||||||
default:
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline NumberFormat RemapNumberFormat(const NumberFormat format, const DataFormat data_format) {
|
|
||||||
switch (format) {
|
|
||||||
case NumberFormat::Unorm: {
|
|
||||||
switch (data_format) {
|
|
||||||
case DataFormat::Format32:
|
|
||||||
case DataFormat::Format32_32:
|
|
||||||
case DataFormat::Format32_32_32:
|
|
||||||
case DataFormat::Format32_32_32_32:
|
|
||||||
return NumberFormat::Uint;
|
|
||||||
default:
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NumberFormat::Uscaled:
|
|
||||||
return NumberFormat::Uint;
|
|
||||||
case NumberFormat::Sscaled:
|
|
||||||
case NumberFormat::SnormNz:
|
|
||||||
return NumberFormat::Sint;
|
|
||||||
case NumberFormat::Ubnorm:
|
|
||||||
return NumberFormat::Unorm;
|
|
||||||
case NumberFormat::Float:
|
|
||||||
if (data_format == DataFormat::Format8) {
|
|
||||||
// Games may ask for 8-bit float when they want to access the stencil component
|
|
||||||
// of a depth-stencil image. Change to unsigned int to match the stencil format.
|
|
||||||
// This is also the closest approximation to pass the bits through unconverted.
|
|
||||||
return NumberFormat::Uint;
|
|
||||||
}
|
|
||||||
[[fallthrough]];
|
|
||||||
default:
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline CompMapping RemapSwizzle(const DataFormat format, const CompMapping swizzle) {
|
|
||||||
switch (format) {
|
|
||||||
case DataFormat::Format1_5_5_5:
|
|
||||||
case DataFormat::Format11_11_10: {
|
|
||||||
CompMapping result;
|
|
||||||
result.r = swizzle.b;
|
|
||||||
result.g = swizzle.g;
|
|
||||||
result.b = swizzle.r;
|
|
||||||
result.a = swizzle.a;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
case DataFormat::Format10_10_10_2: {
|
|
||||||
CompMapping result;
|
|
||||||
result.r = swizzle.a;
|
|
||||||
result.g = swizzle.b;
|
|
||||||
result.b = swizzle.g;
|
|
||||||
result.a = swizzle.r;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
case DataFormat::Format4_4_4_4: {
|
|
||||||
// Remap to a more supported component order.
|
|
||||||
CompMapping result;
|
|
||||||
result.r = swizzle.g;
|
|
||||||
result.g = swizzle.b;
|
|
||||||
result.b = swizzle.a;
|
|
||||||
result.a = swizzle.r;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return swizzle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline NumberConversion MapNumberConversion(const NumberFormat num_fmt, const DataFormat data_fmt) {
|
|
||||||
switch (num_fmt) {
|
|
||||||
case NumberFormat::Unorm: {
|
|
||||||
switch (data_fmt) {
|
|
||||||
case DataFormat::Format32:
|
|
||||||
case DataFormat::Format32_32:
|
|
||||||
case DataFormat::Format32_32_32:
|
|
||||||
case DataFormat::Format32_32_32_32:
|
|
||||||
return NumberConversion::Uint32ToUnorm;
|
|
||||||
default:
|
|
||||||
return NumberConversion::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case NumberFormat::Uscaled:
|
|
||||||
return NumberConversion::UintToUscaled;
|
|
||||||
case NumberFormat::Sscaled:
|
|
||||||
return NumberConversion::SintToSscaled;
|
|
||||||
case NumberFormat::Ubnorm:
|
|
||||||
return NumberConversion::UnormToUbnorm;
|
|
||||||
case NumberFormat::SnormNz: {
|
|
||||||
switch (data_fmt) {
|
|
||||||
case DataFormat::Format8:
|
|
||||||
case DataFormat::Format8_8:
|
|
||||||
case DataFormat::Format8_8_8_8:
|
|
||||||
return NumberConversion::Sint8ToSnormNz;
|
|
||||||
case DataFormat::Format16:
|
|
||||||
case DataFormat::Format16_16:
|
|
||||||
case DataFormat::Format16_16_16_16:
|
|
||||||
return NumberConversion::Sint16ToSnormNz;
|
|
||||||
default:
|
|
||||||
UNREACHABLE_MSG("data_fmt = {}", u32(data_fmt));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return NumberConversion::None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AmdGpu
|
} // namespace AmdGpu
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "common/debug.h"
|
#include "common/debug.h"
|
||||||
#include "common/scope_exit.h"
|
#include "common/scope_exit.h"
|
||||||
#include "common/types.h"
|
#include "common/types.h"
|
||||||
|
#include "core/memory.h"
|
||||||
#include "video_core/amdgpu/liverpool.h"
|
#include "video_core/amdgpu/liverpool.h"
|
||||||
#include "video_core/buffer_cache/buffer_cache.h"
|
#include "video_core/buffer_cache/buffer_cache.h"
|
||||||
#include "video_core/host_shaders/fault_buffer_process_comp.h"
|
#include "video_core/host_shaders/fault_buffer_process_comp.h"
|
||||||
|
@ -22,16 +23,18 @@ static constexpr size_t DataShareBufferSize = 64_KB;
|
||||||
static constexpr size_t StagingBufferSize = 512_MB;
|
static constexpr size_t StagingBufferSize = 512_MB;
|
||||||
static constexpr size_t UboStreamBufferSize = 128_MB;
|
static constexpr size_t UboStreamBufferSize = 128_MB;
|
||||||
static constexpr size_t DownloadBufferSize = 128_MB;
|
static constexpr size_t DownloadBufferSize = 128_MB;
|
||||||
|
static constexpr size_t DeviceBufferSize = 128_MB;
|
||||||
static constexpr size_t MaxPageFaults = 1024;
|
static constexpr size_t MaxPageFaults = 1024;
|
||||||
|
|
||||||
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
||||||
Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_,
|
Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_,
|
||||||
TextureCache& texture_cache_, PageManager& tracker_)
|
TextureCache& texture_cache_, PageManager& tracker_)
|
||||||
: instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_},
|
: instance{instance_}, scheduler{scheduler_}, rasterizer{rasterizer_}, liverpool{liverpool_},
|
||||||
texture_cache{texture_cache_}, tracker{tracker_},
|
memory{Core::Memory::Instance()}, texture_cache{texture_cache_}, tracker{tracker_},
|
||||||
staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize},
|
staging_buffer{instance, scheduler, MemoryUsage::Upload, StagingBufferSize},
|
||||||
stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize},
|
stream_buffer{instance, scheduler, MemoryUsage::Stream, UboStreamBufferSize},
|
||||||
download_buffer(instance, scheduler, MemoryUsage::Download, DownloadBufferSize),
|
download_buffer{instance, scheduler, MemoryUsage::Download, DownloadBufferSize},
|
||||||
|
device_buffer{instance, scheduler, MemoryUsage::DeviceLocal, DeviceBufferSize},
|
||||||
gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize},
|
gds_buffer{instance, scheduler, MemoryUsage::Stream, 0, AllFlags, DataShareBufferSize},
|
||||||
bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal,
|
bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal,
|
||||||
0, AllFlags, BDA_PAGETABLE_SIZE},
|
0, AllFlags, BDA_PAGETABLE_SIZE},
|
||||||
|
@ -293,7 +296,7 @@ void BufferCache::BindIndexBuffer(u32 index_offset) {
|
||||||
|
|
||||||
void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {
|
void BufferCache::InlineData(VAddr address, const void* value, u32 num_bytes, bool is_gds) {
|
||||||
ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned");
|
ASSERT_MSG(address % 4 == 0, "GDS offset must be dword aligned");
|
||||||
if (!is_gds && !IsRegionRegistered(address, num_bytes)) {
|
if (!is_gds && !IsRegionGpuModified(address, num_bytes)) {
|
||||||
memcpy(std::bit_cast<void*>(address), value, num_bytes);
|
memcpy(std::bit_cast<void*>(address), value, num_bytes);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -347,7 +350,7 @@ std::pair<Buffer*, u32> BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b
|
||||||
return {&buffer, buffer.Offset(device_addr)};
|
return {&buffer, buffer.Offset(device_addr)};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::pair<Buffer*, u32> BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size, bool prefer_gpu) {
|
std::pair<Buffer*, u32> BufferCache::ObtainBufferForImage(VAddr gpu_addr, u32 size) {
|
||||||
// Check if any buffer contains the full requested range.
|
// Check if any buffer contains the full requested range.
|
||||||
const u64 page = gpu_addr >> CACHING_PAGEBITS;
|
const u64 page = gpu_addr >> CACHING_PAGEBITS;
|
||||||
const BufferId buffer_id = page_table[page].buffer_id;
|
const BufferId buffer_id = page_table[page].buffer_id;
|
||||||
|
@ -360,12 +363,14 @@ std::pair<Buffer*, u32> BufferCache::ObtainViewBuffer(VAddr gpu_addr, u32 size,
|
||||||
}
|
}
|
||||||
// If no buffer contains the full requested range but some buffer within was GPU-modified,
|
// If no buffer contains the full requested range but some buffer within was GPU-modified,
|
||||||
// fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications.
|
// fall back to ObtainBuffer to create a full buffer and avoid losing GPU modifications.
|
||||||
// This is only done if the request prefers to use GPU memory, otherwise we can skip it.
|
if (memory_tracker.IsRegionGpuModified(gpu_addr, size)) {
|
||||||
if (prefer_gpu && memory_tracker.IsRegionGpuModified(gpu_addr, size)) {
|
|
||||||
return ObtainBuffer(gpu_addr, size, false, false);
|
return ObtainBuffer(gpu_addr, size, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In all other cases, just do a CPU copy to the staging buffer.
|
// In all other cases, just do a CPU copy to the staging buffer.
|
||||||
const u32 offset = staging_buffer.Copy(gpu_addr, size, 16);
|
const auto [data, offset] = staging_buffer.Map(size, 16);
|
||||||
|
memory->CopySparseMemory(gpu_addr, data, size);
|
||||||
|
staging_buffer.Commit();
|
||||||
return {&staging_buffer, offset};
|
return {&staging_buffer, offset};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,24 +803,45 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) {
|
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) {
|
||||||
static constexpr FindFlags find_flags =
|
boost::container::small_vector<ImageId, 6> image_ids;
|
||||||
FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize;
|
texture_cache.ForEachImageInRegion(device_addr, size, [&](ImageId image_id, Image& image) {
|
||||||
TextureCache::BaseDesc desc{};
|
if (image.info.guest_address != device_addr) {
|
||||||
desc.info.guest_address = device_addr;
|
return;
|
||||||
desc.info.guest_size = size;
|
|
||||||
const ImageId image_id = texture_cache.FindImage(desc, find_flags);
|
|
||||||
if (!image_id) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
Image& image = texture_cache.GetImage(image_id);
|
|
||||||
// Only perform sync if image is:
|
// Only perform sync if image is:
|
||||||
// - GPU modified; otherwise there are no changes to synchronize.
|
// - GPU modified; otherwise there are no changes to synchronize.
|
||||||
// - Not CPU dirty; otherwise we could overwrite CPU changes with stale GPU changes.
|
// - Not CPU dirty; otherwise we could overwrite CPU changes with stale GPU changes.
|
||||||
// - Not GPU dirty; otherwise we could overwrite GPU changes with stale image data.
|
// - Not GPU dirty; otherwise we could overwrite GPU changes with stale image data.
|
||||||
if (False(image.flags & ImageFlagBits::GpuModified) ||
|
if (False(image.flags & ImageFlagBits::GpuModified) ||
|
||||||
True(image.flags & ImageFlagBits::Dirty)) {
|
True(image.flags & ImageFlagBits::Dirty)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
image_ids.push_back(image_id);
|
||||||
|
});
|
||||||
|
if (image_ids.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
ImageId image_id{};
|
||||||
|
if (image_ids.size() == 1) {
|
||||||
|
// Sometimes image size might not exactly match with requested buffer size
|
||||||
|
// If we only found 1 candidate image use it without too many questions.
|
||||||
|
image_id = image_ids[0];
|
||||||
|
} else {
|
||||||
|
for (s32 i = 0; i < image_ids.size(); ++i) {
|
||||||
|
Image& image = texture_cache.GetImage(image_ids[i]);
|
||||||
|
if (image.info.guest_size == size) {
|
||||||
|
image_id = image_ids[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!image_id) {
|
||||||
|
LOG_WARNING(Render_Vulkan,
|
||||||
|
"Failed to find exact image match for copy addr={:#x}, size={:#x}",
|
||||||
|
device_addr, size);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image& image = texture_cache.GetImage(image_id);
|
||||||
ASSERT_MSG(device_addr == image.info.guest_address,
|
ASSERT_MSG(device_addr == image.info.guest_address,
|
||||||
"Texel buffer aliases image subresources {:x} : {:x}", device_addr,
|
"Texel buffer aliases image subresources {:x} : {:x}", device_addr,
|
||||||
image.info.guest_address);
|
image.info.guest_address);
|
||||||
|
|
|
@ -17,6 +17,10 @@ namespace AmdGpu {
|
||||||
struct Liverpool;
|
struct Liverpool;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace Core {
|
||||||
|
class MemoryManager;
|
||||||
|
}
|
||||||
|
|
||||||
namespace Shader {
|
namespace Shader {
|
||||||
namespace Gcn {
|
namespace Gcn {
|
||||||
struct FetchShaderData;
|
struct FetchShaderData;
|
||||||
|
@ -76,11 +80,6 @@ public:
|
||||||
return &gds_buffer;
|
return &gds_buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Retrieves the host visible device local stream buffer.
|
|
||||||
[[nodiscard]] StreamBuffer& GetStreamBuffer() noexcept {
|
|
||||||
return stream_buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieves the device local DBA page table buffer.
|
/// Retrieves the device local DBA page table buffer.
|
||||||
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept {
|
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept {
|
||||||
return &bda_pagetable_buffer;
|
return &bda_pagetable_buffer;
|
||||||
|
@ -96,6 +95,20 @@ public:
|
||||||
return slot_buffers[id];
|
return slot_buffers[id];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Retrieves a utility buffer optimized for specified memory usage.
|
||||||
|
StreamBuffer& GetUtilityBuffer(MemoryUsage usage) noexcept {
|
||||||
|
switch (usage) {
|
||||||
|
case MemoryUsage::Stream:
|
||||||
|
return stream_buffer;
|
||||||
|
case MemoryUsage::Download:
|
||||||
|
return download_buffer;
|
||||||
|
case MemoryUsage::Upload:
|
||||||
|
return staging_buffer;
|
||||||
|
case MemoryUsage::DeviceLocal:
|
||||||
|
return device_buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Invalidates any buffer in the logical page range.
|
/// Invalidates any buffer in the logical page range.
|
||||||
void InvalidateMemory(VAddr device_addr, u64 size, bool unmap);
|
void InvalidateMemory(VAddr device_addr, u64 size, bool unmap);
|
||||||
|
|
||||||
|
@ -117,8 +130,7 @@ public:
|
||||||
BufferId buffer_id = {});
|
BufferId buffer_id = {});
|
||||||
|
|
||||||
/// Attempts to obtain a buffer without modifying the cache contents.
|
/// Attempts to obtain a buffer without modifying the cache contents.
|
||||||
[[nodiscard]] std::pair<Buffer*, u32> ObtainViewBuffer(VAddr gpu_addr, u32 size,
|
[[nodiscard]] std::pair<Buffer*, u32> ObtainBufferForImage(VAddr gpu_addr, u32 size);
|
||||||
bool prefer_gpu);
|
|
||||||
|
|
||||||
/// Return true when a region is registered on the cache
|
/// Return true when a region is registered on the cache
|
||||||
[[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
|
[[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
|
||||||
|
@ -183,11 +195,13 @@ private:
|
||||||
Vulkan::Scheduler& scheduler;
|
Vulkan::Scheduler& scheduler;
|
||||||
Vulkan::Rasterizer& rasterizer;
|
Vulkan::Rasterizer& rasterizer;
|
||||||
AmdGpu::Liverpool* liverpool;
|
AmdGpu::Liverpool* liverpool;
|
||||||
|
Core::MemoryManager* memory;
|
||||||
TextureCache& texture_cache;
|
TextureCache& texture_cache;
|
||||||
PageManager& tracker;
|
PageManager& tracker;
|
||||||
StreamBuffer staging_buffer;
|
StreamBuffer staging_buffer;
|
||||||
StreamBuffer stream_buffer;
|
StreamBuffer stream_buffer;
|
||||||
StreamBuffer download_buffer;
|
StreamBuffer download_buffer;
|
||||||
|
StreamBuffer device_buffer;
|
||||||
Buffer gds_buffer;
|
Buffer gds_buffer;
|
||||||
Buffer bda_pagetable_buffer;
|
Buffer bda_pagetable_buffer;
|
||||||
Buffer fault_buffer;
|
Buffer fault_buffer;
|
||||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
||||||
uint num_levels;
|
uint num_levels;
|
||||||
uint pitch;
|
uint pitch;
|
||||||
uint height;
|
uint height;
|
||||||
uint sizes[14];
|
uint sizes[16];
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
// Inverse morton LUT, small enough to fit into K$
|
// Inverse morton LUT, small enough to fit into K$
|
||||||
|
|
|
@ -18,7 +18,7 @@ layout(push_constant) uniform image_info {
|
||||||
uint num_levels;
|
uint num_levels;
|
||||||
uint pitch;
|
uint pitch;
|
||||||
uint height;
|
uint height;
|
||||||
uint sizes[14];
|
uint sizes[16];
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
#define MICRO_TILE_DIM 8
|
#define MICRO_TILE_DIM 8
|
||||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
||||||
uint num_levels;
|
uint num_levels;
|
||||||
uint pitch;
|
uint pitch;
|
||||||
uint height;
|
uint height;
|
||||||
uint sizes[14];
|
uint sizes[16];
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
// Inverse morton LUT, small enough to fit into K$
|
// Inverse morton LUT, small enough to fit into K$
|
||||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
||||||
uint num_levels;
|
uint num_levels;
|
||||||
uint pitch;
|
uint pitch;
|
||||||
uint height;
|
uint height;
|
||||||
uint sizes[14];
|
uint sizes[16];
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
// Inverse morton LUT, small enough to fit into K$
|
// Inverse morton LUT, small enough to fit into K$
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue