mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-13 05:05:57 +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/shader_info_collection_pass.cpp
|
||||
src/shader_recompiler/ir/passes/shared_memory_barrier_pass.cpp
|
||||
src/shader_recompiler/ir/passes/shared_memory_simplify_pass.cpp
|
||||
src/shader_recompiler/ir/passes/shared_memory_to_storage_pass.cpp
|
||||
src/shader_recompiler/ir/passes/ssa_rewrite_pass.cpp
|
||||
src/shader_recompiler/ir/abstract_syntax_list.cpp
|
||||
|
@ -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.h
|
||||
src/video_core/texture_cache/types.h
|
||||
src/video_core/texture_cache/host_compatibility.cpp
|
||||
src/video_core/texture_cache/host_compatibility.h
|
||||
src/video_core/page_manager.cpp
|
||||
src/video_core/page_manager.h
|
||||
|
@ -1056,6 +1058,10 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
|
|||
src/qt_gui/settings_dialog.h
|
||||
src/qt_gui/settings_dialog.ui
|
||||
src/qt_gui/main.cpp
|
||||
src/qt_gui/gui_settings.cpp
|
||||
src/qt_gui/gui_settings.h
|
||||
src/qt_gui/settings.cpp
|
||||
src/qt_gui/settings.h
|
||||
${EMULATOR}
|
||||
${RESOURCE_FILES}
|
||||
${TRANSLATIONS}
|
||||
|
@ -1121,6 +1127,10 @@ if (APPLE)
|
|||
set(MVK_BUNDLE_PATH "Resources/vulkan/icd.d")
|
||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../${MVK_BUNDLE_PATH}")
|
||||
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/${MVK_BUNDLE_PATH})
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${MVK_DST}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
|
||||
else()
|
||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
|
||||
set(MVK_DST ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
@ -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_DST ${MVK_DST}/MoltenVK_icd.json)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${MVK_DST}
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DST})
|
||||
add_custom_command(
|
||||
OUTPUT ${MVK_ICD_DST}
|
||||
DEPENDS ${MVK_ICD_SRC} ${MVK_DST}
|
||||
|
@ -1148,17 +1155,13 @@ if (APPLE)
|
|||
|
||||
if (ARCHITECTURE STREQUAL "x86_64")
|
||||
# Reserve system-managed memory space.
|
||||
target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
|
||||
target_link_options(shadps4 PRIVATE -Wl,-ld_classic,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
|
||||
endif()
|
||||
|
||||
# Replacement for std::chrono::time_zone
|
||||
target_link_libraries(shadps4 PRIVATE date::date-tz)
|
||||
endif()
|
||||
|
||||
if (NOT ENABLE_QT_GUI)
|
||||
target_link_libraries(shadps4 PRIVATE SDL3::SDL3)
|
||||
endif()
|
||||
|
||||
if (ENABLE_QT_GUI)
|
||||
target_link_libraries(shadps4 PRIVATE Qt6::Widgets Qt6::Concurrent Qt6::Network Qt6::Multimedia)
|
||||
add_definitions(-DENABLE_QT_GUI)
|
||||
|
|
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++.
|
||||
|
||||
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/blob/main/documents/Quickstart/Quickstart.md).\
|
||||
If you encounter problems or have doubts, do not hesitate to look at the [**Quickstart**](https://github.com/shadps4-emu/shadPS4/wiki/I.-Quick-start-%5BUsers%5D).\
|
||||
To verify that a game works, you can look at [**shadPS4 Game Compatibility**](https://github.com/shadps4-emu/shadps4-game-compatibility).\
|
||||
To discuss shadPS4 development, suggest ideas or to ask for help, join our [**Discord server**](https://discord.gg/bFJxfftGW6).\
|
||||
To get the latest news, go to our [**X (Twitter)**](https://x.com/shadps4) or our [**website**](https://shadps4.net/).\
|
||||
|
@ -124,8 +124,8 @@ Keyboard and mouse inputs can be customized in the settings menu by clicking the
|
|||
|
||||
# Firmware files
|
||||
|
||||
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.\
|
||||
The following firmware modules are supported and must be placed in shadPS4's `user/sys_modules` folder.
|
||||
shadPS4 can load some PlayStation 4 firmware files, these must be dumped from your legally owned PlayStation 4 console.
|
||||
The following firmware modules are supported and must be placed in shadPS4's `sys_modules` folder.
|
||||
|
||||
<div align="center">
|
||||
|
||||
|
@ -138,8 +138,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
|
|||
</div>
|
||||
|
||||
> [!Caution]
|
||||
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.\
|
||||
> **We do not provide any information or support on how to do this**.
|
||||
> The above modules are required to run the games properly and must be extracted from your PlayStation 4.
|
||||
|
||||
|
||||
|
||||
|
@ -148,7 +147,7 @@ The following firmware modules are supported and must be placed in shadPS4's `us
|
|||
- [**georgemoralis**](https://github.com/georgemoralis)
|
||||
- [**psucien**](https://github.com/psucien)
|
||||
- [**viniciuslrangel**](https://github.com/viniciuslrangel)
|
||||
- [**roamic**](https://github.com/vladmikhalin)
|
||||
- [**roamic**](https://github.com/roamic)
|
||||
- [**squidbus**](https://github.com/squidbus)
|
||||
- [**frodo**](https://github.com/baggins183)
|
||||
- [**Stephen Miller**](https://github.com/StevenMiller123)
|
||||
|
@ -158,7 +157,7 @@ Logo is done by [**Xphalnos**](https://github.com/Xphalnos)
|
|||
|
||||
# Contributing
|
||||
|
||||
If you want to contribute, please look the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
|
||||
If you want to contribute, please read the [**CONTRIBUTING.md**](https://github.com/shadps4-emu/shadPS4/blob/main/CONTRIBUTING.md) file.\
|
||||
Open a PR and we'll check it :)
|
||||
|
||||
# Translations
|
||||
|
|
|
@ -25,7 +25,7 @@ sudo apt install build-essential clang git cmake libasound2-dev \
|
|||
|
||||
```bash
|
||||
sudo dnf install clang git cmake libatomic alsa-lib-devel \
|
||||
pipewire-jack-audio-connection-kit-devel openal-devel \
|
||||
pipewire-jack-audio-connection-kit-devel openal-soft-devel \
|
||||
openssl-devel libevdev-devel libudev-devel libXext-devel \
|
||||
qt6-qtbase-devel qt6-qtbase-private-devel \
|
||||
qt6-qtmultimedia-devel qt6-qtsvg-devel qt6-qttools-devel \
|
||||
|
|
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 isDevKit = false;
|
||||
static bool playBGM = false;
|
||||
static bool isTrophyPopupDisabled = false;
|
||||
static int BGMvolume = 50;
|
||||
static bool enableDiscordRPC = false;
|
||||
static u32 screenWidth = 1280;
|
||||
static u32 screenHeight = 720;
|
||||
|
@ -43,7 +41,6 @@ static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto
|
|||
static std::string logFilter;
|
||||
static std::string logType = "sync";
|
||||
static std::string userName = "shadPS4";
|
||||
static std::string updateChannel;
|
||||
static std::string chooseHomeTab;
|
||||
static std::string backButtonBehavior = "left";
|
||||
static bool useSpecialPad = false;
|
||||
|
@ -52,8 +49,6 @@ static bool isMotionControlsEnabled = true;
|
|||
static bool isDebugDump = false;
|
||||
static bool isShaderDebug = false;
|
||||
static bool isShowSplash = false;
|
||||
static bool isAutoUpdate = false;
|
||||
static bool isAlwaysShowChangelog = false;
|
||||
static std::string isSideTrophy = "right";
|
||||
static bool isNullGpu = false;
|
||||
static bool shouldCopyGPUBuffers = false;
|
||||
|
@ -86,27 +81,13 @@ static std::vector<GameInstallDir> settings_install_dirs = {};
|
|||
std::vector<bool> install_dirs_enabled = {};
|
||||
std::filesystem::path settings_addon_install_dir = {};
|
||||
std::filesystem::path save_data_path = {};
|
||||
u32 main_window_geometry_x = 400;
|
||||
u32 main_window_geometry_y = 400;
|
||||
u32 main_window_geometry_w = 1280;
|
||||
u32 main_window_geometry_h = 720;
|
||||
u32 mw_themes = 0;
|
||||
u32 m_icon_size = 36;
|
||||
u32 m_icon_size_grid = 69;
|
||||
u32 m_slider_pos = 0;
|
||||
u32 m_slider_pos_grid = 0;
|
||||
u32 m_table_mode = 0;
|
||||
u32 m_window_size_W = 1280;
|
||||
u32 m_window_size_H = 720;
|
||||
std::vector<std::string> m_elf_viewer;
|
||||
std::vector<std::string> m_recent_files;
|
||||
std::string emulator_language = "en_US";
|
||||
static int backgroundImageOpacity = 50;
|
||||
static bool showBackgroundImage = true;
|
||||
static bool isFullscreen = false;
|
||||
static std::string fullscreenMode = "Windowed";
|
||||
static bool isHDRAllowed = false;
|
||||
static bool showLabelsUnderIcons = true;
|
||||
|
||||
// Language
|
||||
u32 m_language = 1; // english
|
||||
|
@ -176,14 +157,6 @@ bool getIsFullscreen() {
|
|||
return isFullscreen;
|
||||
}
|
||||
|
||||
bool getShowLabelsUnderIcons() {
|
||||
return showLabelsUnderIcons;
|
||||
}
|
||||
|
||||
bool setShowLabelsUnderIcons() {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string getFullscreenMode() {
|
||||
return fullscreenMode;
|
||||
}
|
||||
|
@ -192,14 +165,6 @@ bool getisTrophyPopupDisabled() {
|
|||
return isTrophyPopupDisabled;
|
||||
}
|
||||
|
||||
bool getPlayBGM() {
|
||||
return playBGM;
|
||||
}
|
||||
|
||||
int getBGMvolume() {
|
||||
return BGMvolume;
|
||||
}
|
||||
|
||||
bool getEnableDiscordRPC() {
|
||||
return enableDiscordRPC;
|
||||
}
|
||||
|
@ -240,10 +205,6 @@ std::string getUserName() {
|
|||
return userName;
|
||||
}
|
||||
|
||||
std::string getUpdateChannel() {
|
||||
return updateChannel;
|
||||
}
|
||||
|
||||
std::string getChooseHomeTab() {
|
||||
return chooseHomeTab;
|
||||
}
|
||||
|
@ -276,14 +237,6 @@ bool showSplash() {
|
|||
return isShowSplash;
|
||||
}
|
||||
|
||||
bool autoUpdate() {
|
||||
return isAutoUpdate;
|
||||
}
|
||||
|
||||
bool alwaysShowChangelog() {
|
||||
return isAlwaysShowChangelog;
|
||||
}
|
||||
|
||||
std::string sideTrophy() {
|
||||
return isSideTrophy;
|
||||
}
|
||||
|
@ -384,14 +337,6 @@ void setShowSplash(bool enable) {
|
|||
isShowSplash = enable;
|
||||
}
|
||||
|
||||
void setAutoUpdate(bool enable) {
|
||||
isAutoUpdate = enable;
|
||||
}
|
||||
|
||||
void setAlwaysShowChangelog(bool enable) {
|
||||
isAlwaysShowChangelog = enable;
|
||||
}
|
||||
|
||||
void setSideTrophy(std::string side) {
|
||||
isSideTrophy = side;
|
||||
}
|
||||
|
@ -431,9 +376,6 @@ void setVblankDiv(u32 value) {
|
|||
void setIsFullscreen(bool enable) {
|
||||
isFullscreen = enable;
|
||||
}
|
||||
static void setShowLabelsUnderIcons(bool enable) {
|
||||
showLabelsUnderIcons = enable;
|
||||
}
|
||||
|
||||
void setFullscreenMode(std::string mode) {
|
||||
fullscreenMode = mode;
|
||||
|
@ -443,14 +385,6 @@ void setisTrophyPopupDisabled(bool disable) {
|
|||
isTrophyPopupDisabled = disable;
|
||||
}
|
||||
|
||||
void setPlayBGM(bool enable) {
|
||||
playBGM = enable;
|
||||
}
|
||||
|
||||
void setBGMvolume(int volume) {
|
||||
BGMvolume = volume;
|
||||
}
|
||||
|
||||
void setEnableDiscordRPC(bool enable) {
|
||||
enableDiscordRPC = enable;
|
||||
}
|
||||
|
@ -490,9 +424,6 @@ void setUserName(const std::string& type) {
|
|||
userName = type;
|
||||
}
|
||||
|
||||
void setUpdateChannel(const std::string& type) {
|
||||
updateChannel = type;
|
||||
}
|
||||
void setChooseHomeTab(const std::string& type) {
|
||||
chooseHomeTab = type;
|
||||
}
|
||||
|
@ -521,13 +452,6 @@ void setCheckCompatibilityOnStartup(bool use) {
|
|||
checkCompatibilityOnStartup = use;
|
||||
}
|
||||
|
||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
|
||||
main_window_geometry_x = x;
|
||||
main_window_geometry_y = y;
|
||||
main_window_geometry_w = w;
|
||||
main_window_geometry_h = h;
|
||||
}
|
||||
|
||||
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled) {
|
||||
for (const auto& install_dir : settings_install_dirs) {
|
||||
if (install_dir.path == dir) {
|
||||
|
@ -564,34 +488,6 @@ void setMainWindowTheme(u32 theme) {
|
|||
mw_themes = theme;
|
||||
}
|
||||
|
||||
void setIconSize(u32 size) {
|
||||
m_icon_size = size;
|
||||
}
|
||||
|
||||
void setIconSizeGrid(u32 size) {
|
||||
m_icon_size_grid = size;
|
||||
}
|
||||
|
||||
void setSliderPosition(u32 pos) {
|
||||
m_slider_pos = pos;
|
||||
}
|
||||
|
||||
void setSliderPositionGrid(u32 pos) {
|
||||
m_slider_pos_grid = pos;
|
||||
}
|
||||
|
||||
void setTableMode(u32 mode) {
|
||||
m_table_mode = mode;
|
||||
}
|
||||
|
||||
void setMainWindowWidth(u32 width) {
|
||||
m_window_size_W = width;
|
||||
}
|
||||
|
||||
void setMainWindowHeight(u32 height) {
|
||||
m_window_size_H = height;
|
||||
}
|
||||
|
||||
void setElfViewer(const std::vector<std::string>& elfList) {
|
||||
m_elf_viewer.resize(elfList.size());
|
||||
m_elf_viewer = elfList;
|
||||
|
@ -621,22 +517,6 @@ void setSaveDataPath(const std::filesystem::path& path) {
|
|||
save_data_path = path;
|
||||
}
|
||||
|
||||
u32 getMainWindowGeometryX() {
|
||||
return main_window_geometry_x;
|
||||
}
|
||||
|
||||
u32 getMainWindowGeometryY() {
|
||||
return main_window_geometry_y;
|
||||
}
|
||||
|
||||
u32 getMainWindowGeometryW() {
|
||||
return main_window_geometry_w;
|
||||
}
|
||||
|
||||
u32 getMainWindowGeometryH() {
|
||||
return main_window_geometry_h;
|
||||
}
|
||||
|
||||
const std::vector<std::filesystem::path> getGameInstallDirs() {
|
||||
std::vector<std::filesystem::path> enabled_dirs;
|
||||
for (const auto& dir : settings_install_dirs) {
|
||||
|
@ -667,34 +547,6 @@ u32 getMainWindowTheme() {
|
|||
return mw_themes;
|
||||
}
|
||||
|
||||
u32 getIconSize() {
|
||||
return m_icon_size;
|
||||
}
|
||||
|
||||
u32 getIconSizeGrid() {
|
||||
return m_icon_size_grid;
|
||||
}
|
||||
|
||||
u32 getSliderPosition() {
|
||||
return m_slider_pos;
|
||||
}
|
||||
|
||||
u32 getSliderPositionGrid() {
|
||||
return m_slider_pos_grid;
|
||||
}
|
||||
|
||||
u32 getTableMode() {
|
||||
return m_table_mode;
|
||||
}
|
||||
|
||||
u32 getMainWindowWidth() {
|
||||
return m_window_size_W;
|
||||
}
|
||||
|
||||
u32 getMainWindowHeight() {
|
||||
return m_window_size_H;
|
||||
}
|
||||
|
||||
std::vector<std::string> getElfViewer() {
|
||||
return m_elf_viewer;
|
||||
}
|
||||
|
@ -715,22 +567,6 @@ bool getSeparateLogFilesEnabled() {
|
|||
return isSeparateLogFilesEnabled;
|
||||
}
|
||||
|
||||
int getBackgroundImageOpacity() {
|
||||
return backgroundImageOpacity;
|
||||
}
|
||||
|
||||
void setBackgroundImageOpacity(int opacity) {
|
||||
backgroundImageOpacity = std::clamp(opacity, 0, 100);
|
||||
}
|
||||
|
||||
bool getShowBackgroundImage() {
|
||||
return showBackgroundImage;
|
||||
}
|
||||
|
||||
void setShowBackgroundImage(bool show) {
|
||||
showBackgroundImage = show;
|
||||
}
|
||||
|
||||
bool getPSNSignedIn() {
|
||||
return isPSNSignedIn;
|
||||
}
|
||||
|
@ -764,23 +600,14 @@ void load(const std::filesystem::path& path) {
|
|||
isNeo = toml::find_or<bool>(general, "isPS4Pro", false);
|
||||
isDevKit = toml::find_or<bool>(general, "isDevKit", false);
|
||||
isPSNSignedIn = toml::find_or<bool>(general, "isPSNSignedIn", false);
|
||||
playBGM = toml::find_or<bool>(general, "playBGM", false);
|
||||
isTrophyPopupDisabled = toml::find_or<bool>(general, "isTrophyPopupDisabled", false);
|
||||
trophyNotificationDuration =
|
||||
toml::find_or<double>(general, "trophyNotificationDuration", 5.0);
|
||||
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
|
||||
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
|
||||
logFilter = toml::find_or<std::string>(general, "logFilter", "");
|
||||
logType = toml::find_or<std::string>(general, "logType", "sync");
|
||||
userName = toml::find_or<std::string>(general, "userName", "shadPS4");
|
||||
if (Common::g_is_release) {
|
||||
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Release");
|
||||
} else {
|
||||
updateChannel = toml::find_or<std::string>(general, "updateChannel", "Nightly");
|
||||
}
|
||||
isShowSplash = toml::find_or<bool>(general, "showSplash", true);
|
||||
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
|
||||
isAlwaysShowChangelog = toml::find_or<bool>(general, "alwaysShowChangelog", false);
|
||||
isSideTrophy = toml::find_or<std::string>(general, "sideTrophy", "right");
|
||||
compatibilityData = toml::find_or<bool>(general, "compatibilityEnabled", false);
|
||||
checkCompatibilityOnStartup =
|
||||
|
@ -841,13 +668,7 @@ void load(const std::filesystem::path& path) {
|
|||
const toml::value& gui = data.at("GUI");
|
||||
|
||||
load_game_size = toml::find_or<bool>(gui, "loadGameSizeEnabled", true);
|
||||
m_icon_size = toml::find_or<int>(gui, "iconSize", 0);
|
||||
m_icon_size_grid = toml::find_or<int>(gui, "iconSizeGrid", 0);
|
||||
m_slider_pos = toml::find_or<int>(gui, "sliderPos", 0);
|
||||
m_slider_pos_grid = toml::find_or<int>(gui, "sliderPosGrid", 0);
|
||||
mw_themes = toml::find_or<int>(gui, "theme", 0);
|
||||
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
|
||||
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
|
||||
|
||||
const auto install_dir_array =
|
||||
toml::find_or<std::vector<std::u8string>>(gui, "installDirs", {});
|
||||
|
@ -872,16 +693,9 @@ void load(const std::filesystem::path& path) {
|
|||
save_data_path = toml::find_fs_path_or(gui, "saveDataPath", {});
|
||||
|
||||
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
|
||||
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
|
||||
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
|
||||
main_window_geometry_w = toml::find_or<int>(gui, "geometry_w", 0);
|
||||
main_window_geometry_h = toml::find_or<int>(gui, "geometry_h", 0);
|
||||
m_elf_viewer = toml::find_or<std::vector<std::string>>(gui, "elfDirs", {});
|
||||
m_recent_files = toml::find_or<std::vector<std::string>>(gui, "recentFiles", {});
|
||||
m_table_mode = toml::find_or<int>(gui, "gameTableMode", 0);
|
||||
emulator_language = toml::find_or<std::string>(gui, "emulatorLanguage", "en_US");
|
||||
backgroundImageOpacity = toml::find_or<int>(gui, "backgroundImageOpacity", 50);
|
||||
showBackgroundImage = toml::find_or<bool>(gui, "showBackgroundImage", true);
|
||||
}
|
||||
|
||||
if (data.contains("Settings")) {
|
||||
|
@ -897,9 +711,10 @@ void load(const std::filesystem::path& path) {
|
|||
|
||||
// Check if the loaded language is in the allowed list
|
||||
const std::vector<std::string> allowed_languages = {
|
||||
"ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI", "fr_FR", "hu_HU",
|
||||
"id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO", "nl_NL", "pl_PL", "pt_BR", "pt_PT",
|
||||
"ro_RO", "ru_RU", "sq_AL", "sv_SE", "tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW"};
|
||||
"ar_SA", "da_DK", "de_DE", "el_GR", "en_US", "es_ES", "fa_IR", "fi_FI",
|
||||
"fr_FR", "hu_HU", "id_ID", "it_IT", "ja_JP", "ko_KR", "lt_LT", "nb_NO",
|
||||
"nl_NL", "pl_PL", "pt_BR", "pt_PT", "ro_RO", "ru_RU", "sq_AL", "sv_SE",
|
||||
"tr_TR", "uk_UA", "vi_VN", "zh_CN", "zh_TW", "ca_ES", "sr_CS"};
|
||||
|
||||
if (std::find(allowed_languages.begin(), allowed_languages.end(), emulator_language) ==
|
||||
allowed_languages.end()) {
|
||||
|
@ -966,17 +781,12 @@ void save(const std::filesystem::path& path) {
|
|||
data["General"]["isPSNSignedIn"] = isPSNSignedIn;
|
||||
data["General"]["isTrophyPopupDisabled"] = isTrophyPopupDisabled;
|
||||
data["General"]["trophyNotificationDuration"] = trophyNotificationDuration;
|
||||
data["General"]["playBGM"] = playBGM;
|
||||
data["General"]["BGMvolume"] = BGMvolume;
|
||||
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
|
||||
data["General"]["logFilter"] = logFilter;
|
||||
data["General"]["logType"] = logType;
|
||||
data["General"]["userName"] = userName;
|
||||
data["General"]["updateChannel"] = updateChannel;
|
||||
data["General"]["chooseHomeTab"] = chooseHomeTab;
|
||||
data["General"]["showSplash"] = isShowSplash;
|
||||
data["General"]["autoUpdate"] = isAutoUpdate;
|
||||
data["General"]["alwaysShowChangelog"] = isAlwaysShowChangelog;
|
||||
data["General"]["sideTrophy"] = isSideTrophy;
|
||||
data["General"]["compatibilityEnabled"] = compatibilityData;
|
||||
data["General"]["checkCompatibilityOnStartup"] = checkCompatibilityOnStartup;
|
||||
|
@ -1046,8 +856,6 @@ void save(const std::filesystem::path& path) {
|
|||
data["GUI"]["addonInstallDir"] =
|
||||
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
|
||||
data["GUI"]["emulatorLanguage"] = emulator_language;
|
||||
data["GUI"]["backgroundImageOpacity"] = backgroundImageOpacity;
|
||||
data["GUI"]["showBackgroundImage"] = showBackgroundImage;
|
||||
data["Settings"]["consoleLanguage"] = m_language;
|
||||
|
||||
// Sorting of TOML sections
|
||||
|
@ -1082,18 +890,7 @@ void saveMainWindow(const std::filesystem::path& path) {
|
|||
fmt::print("Saving new configuration file {}\n", fmt::UTF(path.u8string()));
|
||||
}
|
||||
|
||||
data["GUI"]["mw_width"] = m_window_size_W;
|
||||
data["GUI"]["mw_height"] = m_window_size_H;
|
||||
data["GUI"]["theme"] = mw_themes;
|
||||
data["GUI"]["iconSize"] = m_icon_size;
|
||||
data["GUI"]["sliderPos"] = m_slider_pos;
|
||||
data["GUI"]["iconSizeGrid"] = m_icon_size_grid;
|
||||
data["GUI"]["sliderPosGrid"] = m_slider_pos_grid;
|
||||
data["GUI"]["gameTableMode"] = m_table_mode;
|
||||
data["GUI"]["geometry_x"] = main_window_geometry_x;
|
||||
data["GUI"]["geometry_y"] = main_window_geometry_y;
|
||||
data["GUI"]["geometry_w"] = main_window_geometry_w;
|
||||
data["GUI"]["geometry_h"] = main_window_geometry_h;
|
||||
data["GUI"]["elfDirs"] = m_elf_viewer;
|
||||
data["GUI"]["recentFiles"] = m_recent_files;
|
||||
|
||||
|
@ -1112,19 +909,13 @@ void setDefaultValues() {
|
|||
isPSNSignedIn = false;
|
||||
isFullscreen = false;
|
||||
isTrophyPopupDisabled = false;
|
||||
playBGM = false;
|
||||
BGMvolume = 50;
|
||||
enableDiscordRPC = true;
|
||||
screenWidth = 1280;
|
||||
screenHeight = 720;
|
||||
logFilter = "";
|
||||
logType = "sync";
|
||||
userName = "shadPS4";
|
||||
if (Common::g_is_release) {
|
||||
updateChannel = "Release";
|
||||
} else {
|
||||
updateChannel = "Nightly";
|
||||
}
|
||||
|
||||
chooseHomeTab = "General";
|
||||
cursorState = HideCursorState::Idle;
|
||||
cursorHideTimeout = 5;
|
||||
|
@ -1135,8 +926,6 @@ void setDefaultValues() {
|
|||
isDebugDump = false;
|
||||
isShaderDebug = false;
|
||||
isShowSplash = false;
|
||||
isAutoUpdate = false;
|
||||
isAlwaysShowChangelog = false;
|
||||
isSideTrophy = "right";
|
||||
isNullGpu = false;
|
||||
shouldDumpShaders = false;
|
||||
|
@ -1153,8 +942,6 @@ void setDefaultValues() {
|
|||
gpuId = -1;
|
||||
compatibilityData = false;
|
||||
checkCompatibilityOnStartup = false;
|
||||
backgroundImageOpacity = 50;
|
||||
showBackgroundImage = true;
|
||||
}
|
||||
|
||||
constexpr std::string_view GetDefaultKeyboardConfig() {
|
||||
|
|
|
@ -26,25 +26,18 @@ bool GetLoadGameSizeEnabled();
|
|||
std::filesystem::path GetSaveDataPath();
|
||||
void setLoadGameSizeEnabled(bool enable);
|
||||
bool getIsFullscreen();
|
||||
bool getShowLabelsUnderIcons();
|
||||
bool setShowLabelsUnderIcons();
|
||||
std::string getFullscreenMode();
|
||||
bool isNeoModeConsole();
|
||||
bool isDevKitConsole();
|
||||
bool getPlayBGM();
|
||||
int getBGMvolume();
|
||||
bool getisTrophyPopupDisabled();
|
||||
bool getEnableDiscordRPC();
|
||||
bool getCompatibilityEnabled();
|
||||
bool getCheckCompatibilityOnStartup();
|
||||
int getBackgroundImageOpacity();
|
||||
bool getShowBackgroundImage();
|
||||
bool getPSNSignedIn();
|
||||
|
||||
std::string getLogFilter();
|
||||
std::string getLogType();
|
||||
std::string getUserName();
|
||||
std::string getUpdateChannel();
|
||||
std::string getChooseHomeTab();
|
||||
|
||||
s16 getCursorState();
|
||||
|
@ -69,8 +62,6 @@ bool allowHDR();
|
|||
bool debugDump();
|
||||
bool collectShadersForDebug();
|
||||
bool showSplash();
|
||||
bool autoUpdate();
|
||||
bool alwaysShowChangelog();
|
||||
std::string sideTrophy();
|
||||
bool nullGpu();
|
||||
bool copyGPUCmdBuffers();
|
||||
|
@ -83,8 +74,6 @@ u32 vblankDiv();
|
|||
void setDebugDump(bool enable);
|
||||
void setCollectShaderForDebug(bool enable);
|
||||
void setShowSplash(bool enable);
|
||||
void setAutoUpdate(bool enable);
|
||||
void setAlwaysShowChangelog(bool enable);
|
||||
void setSideTrophy(std::string side);
|
||||
void setNullGpu(bool enable);
|
||||
void setAllowHDR(bool enable);
|
||||
|
@ -97,21 +86,16 @@ void setScreenHeight(u32 height);
|
|||
void setIsFullscreen(bool enable);
|
||||
void setFullscreenMode(std::string mode);
|
||||
void setisTrophyPopupDisabled(bool disable);
|
||||
void setPlayBGM(bool enable);
|
||||
void setBGMvolume(int volume);
|
||||
void setEnableDiscordRPC(bool enable);
|
||||
void setLanguage(u32 language);
|
||||
void setNeoMode(bool enable);
|
||||
void setUserName(const std::string& type);
|
||||
void setUpdateChannel(const std::string& type);
|
||||
void setChooseHomeTab(const std::string& type);
|
||||
void setGameInstallDirs(const std::vector<std::filesystem::path>& dirs_config);
|
||||
void setAllGameInstallDirs(const std::vector<GameInstallDir>& dirs_config);
|
||||
void setSaveDataPath(const std::filesystem::path& path);
|
||||
void setCompatibilityEnabled(bool use);
|
||||
void setCheckCompatibilityOnStartup(bool use);
|
||||
void setBackgroundImageOpacity(int opacity);
|
||||
void setShowBackgroundImage(bool show);
|
||||
void setPSNSignedIn(bool sign);
|
||||
|
||||
void setCursorState(s16 cursorState);
|
||||
|
@ -141,38 +125,19 @@ void setVkHostMarkersEnabled(bool enable);
|
|||
void setVkGuestMarkersEnabled(bool enable);
|
||||
|
||||
// Gui
|
||||
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
|
||||
bool addGameInstallDir(const std::filesystem::path& dir, bool enabled = true);
|
||||
void removeGameInstallDir(const std::filesystem::path& dir);
|
||||
void setGameInstallDirEnabled(const std::filesystem::path& dir, bool enabled);
|
||||
void setAddonInstallDir(const std::filesystem::path& dir);
|
||||
void setMainWindowTheme(u32 theme);
|
||||
void setIconSize(u32 size);
|
||||
void setIconSizeGrid(u32 size);
|
||||
void setSliderPosition(u32 pos);
|
||||
void setSliderPositionGrid(u32 pos);
|
||||
void setTableMode(u32 mode);
|
||||
void setMainWindowWidth(u32 width);
|
||||
void setMainWindowHeight(u32 height);
|
||||
void setElfViewer(const std::vector<std::string>& elfList);
|
||||
void setRecentFiles(const std::vector<std::string>& recentFiles);
|
||||
void setEmulatorLanguage(std::string language);
|
||||
|
||||
u32 getMainWindowGeometryX();
|
||||
u32 getMainWindowGeometryY();
|
||||
u32 getMainWindowGeometryW();
|
||||
u32 getMainWindowGeometryH();
|
||||
const std::vector<std::filesystem::path> getGameInstallDirs();
|
||||
const std::vector<bool> getGameInstallDirsEnabled();
|
||||
std::filesystem::path getAddonInstallDir();
|
||||
u32 getMainWindowTheme();
|
||||
u32 getIconSize();
|
||||
u32 getIconSizeGrid();
|
||||
u32 getSliderPosition();
|
||||
u32 getSliderPositionGrid();
|
||||
u32 getTableMode();
|
||||
u32 getMainWindowWidth();
|
||||
u32 getMainWindowHeight();
|
||||
std::vector<std::string> getElfViewer();
|
||||
std::vector<std::string> getRecentFiles();
|
||||
std::string getEmulatorLanguage();
|
||||
|
|
|
@ -88,7 +88,8 @@ static bool FilterTcbAccess(const ZydisDecodedOperand* operands) {
|
|||
dst_op.reg.value <= ZYDIS_REGISTER_R15;
|
||||
}
|
||||
|
||||
static void GenerateTcbAccess(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
|
||||
static void GenerateTcbAccess(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
const auto dst = ZydisToXbyakRegisterOperand(operands[0]);
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
@ -126,7 +127,8 @@ static bool FilterNoSSE4a(const ZydisDecodedOperand*) {
|
|||
return !cpu.has(Cpu::tSSE4a);
|
||||
}
|
||||
|
||||
static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
|
||||
static void GenerateEXTRQ(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
bool immediateForm = operands[1].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||
operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||
|
||||
|
@ -245,7 +247,8 @@ static void GenerateEXTRQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenera
|
|||
}
|
||||
}
|
||||
|
||||
static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGenerator& c) {
|
||||
static void GenerateINSERTQ(void* /* address */, const ZydisDecodedOperand* operands,
|
||||
Xbyak::CodeGenerator& c) {
|
||||
bool immediateForm = operands[2].type == ZYDIS_OPERAND_TYPE_IMMEDIATE &&
|
||||
operands[3].type == ZYDIS_OPERAND_TYPE_IMMEDIATE;
|
||||
|
||||
|
@ -383,8 +386,44 @@ static void GenerateINSERTQ(const ZydisDecodedOperand* operands, Xbyak::CodeGene
|
|||
}
|
||||
}
|
||||
|
||||
static void ReplaceMOVNT(void* address, u8 rep_prefix) {
|
||||
// Find the opcode byte
|
||||
// There can be any amount of prefixes but the instruction can't be more than 15 bytes
|
||||
// And we know for sure this is a MOVNTSS/MOVNTSD
|
||||
bool found = false;
|
||||
bool rep_prefix_found = false;
|
||||
int index = 0;
|
||||
u8* ptr = reinterpret_cast<u8*>(address);
|
||||
for (int i = 0; i < 15; i++) {
|
||||
if (ptr[i] == rep_prefix) {
|
||||
rep_prefix_found = true;
|
||||
} else if (ptr[i] == 0x2B) {
|
||||
index = i;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Some sanity checks
|
||||
ASSERT(found);
|
||||
ASSERT(index >= 2);
|
||||
ASSERT(ptr[index - 1] == 0x0F);
|
||||
ASSERT(rep_prefix_found);
|
||||
|
||||
// This turns the MOVNTSS/MOVNTSD to a MOVSS/MOVSD m, xmm
|
||||
ptr[index] = 0x11;
|
||||
}
|
||||
|
||||
static void ReplaceMOVNTSS(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
|
||||
ReplaceMOVNT(address, 0xF3);
|
||||
}
|
||||
|
||||
static void ReplaceMOVNTSD(void* address, const ZydisDecodedOperand*, Xbyak::CodeGenerator&) {
|
||||
ReplaceMOVNT(address, 0xF2);
|
||||
}
|
||||
|
||||
using PatchFilter = bool (*)(const ZydisDecodedOperand*);
|
||||
using InstructionGenerator = void (*)(const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
|
||||
using InstructionGenerator = void (*)(void*, const ZydisDecodedOperand*, Xbyak::CodeGenerator&);
|
||||
struct PatchInfo {
|
||||
/// Filter for more granular patch conditions past just the instruction mnemonic.
|
||||
PatchFilter filter;
|
||||
|
@ -400,6 +439,8 @@ static const std::unordered_map<ZydisMnemonic, PatchInfo> Patches = {
|
|||
// SSE4a
|
||||
{ZYDIS_MNEMONIC_EXTRQ, {FilterNoSSE4a, GenerateEXTRQ, true}},
|
||||
{ZYDIS_MNEMONIC_INSERTQ, {FilterNoSSE4a, GenerateINSERTQ, true}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSS, {FilterNoSSE4a, ReplaceMOVNTSS, false}},
|
||||
{ZYDIS_MNEMONIC_MOVNTSD, {FilterNoSSE4a, ReplaceMOVNTSD, false}},
|
||||
|
||||
#if defined(_WIN32)
|
||||
// Windows needs a trampoline.
|
||||
|
@ -477,7 +518,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
|||
auto& trampoline_gen = module->trampoline_gen;
|
||||
const auto trampoline_ptr = trampoline_gen.getCurr();
|
||||
|
||||
patch_info.generator(operands, trampoline_gen);
|
||||
patch_info.generator(code, operands, trampoline_gen);
|
||||
|
||||
// Return to the following instruction at the end of the trampoline.
|
||||
trampoline_gen.jmp(code + instruction.length);
|
||||
|
@ -485,7 +526,7 @@ static std::pair<bool, u64> TryPatch(u8* code, PatchModule* module) {
|
|||
// Replace instruction with near jump to the trampoline.
|
||||
patch_gen.jmp(trampoline_ptr, Xbyak::CodeGenerator::LabelType::T_NEAR);
|
||||
} else {
|
||||
patch_info.generator(operands, patch_gen);
|
||||
patch_info.generator(code, operands, patch_gen);
|
||||
}
|
||||
|
||||
const auto patch_size = patch_gen.getCurr() - code;
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
namespace Core::FileSys {
|
||||
|
||||
bool MntPoints::ignore_game_patches = false;
|
||||
|
||||
std::string RemoveTrailingSlashes(const std::string& path) {
|
||||
// Remove trailing slashes to make comparisons simpler.
|
||||
std::string path_sanitized = path;
|
||||
|
@ -77,7 +79,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
|||
patch_path /= rel_path;
|
||||
|
||||
if ((corrected_path.starts_with("/app0") || corrected_path.starts_with("/hostapp")) &&
|
||||
!force_base_path && std::filesystem::exists(patch_path)) {
|
||||
!force_base_path && !ignore_game_patches && std::filesystem::exists(patch_path)) {
|
||||
return patch_path;
|
||||
}
|
||||
|
||||
|
@ -137,7 +139,7 @@ std::filesystem::path MntPoints::GetHostPath(std::string_view path, bool* is_rea
|
|||
return std::optional<std::filesystem::path>(current_path);
|
||||
};
|
||||
|
||||
if (!force_base_path) {
|
||||
if (!force_base_path && !ignore_game_patches) {
|
||||
if (const auto path = search(patch_path)) {
|
||||
return *path;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class MntPoints {
|
|||
static constexpr bool NeedsCaseInsensitiveSearch = true;
|
||||
#endif
|
||||
public:
|
||||
static bool ignore_game_patches;
|
||||
struct MntPair {
|
||||
std::filesystem::path host_path;
|
||||
std::string mount; // e.g /app0
|
||||
|
|
|
@ -125,7 +125,6 @@ int EqueueInternal::WaitForEvents(SceKernelEvent* ev, int num, u32 micros) {
|
|||
.count();
|
||||
count = WaitForSmallTimer(ev, num, std::max(0l, long(micros - time_waited)));
|
||||
}
|
||||
small_timer_event.event.data = 0;
|
||||
}
|
||||
|
||||
if (ev->flags & SceKernelEvent::Flags::OneShot) {
|
||||
|
@ -179,39 +178,46 @@ int EqueueInternal::GetTriggeredEvents(SceKernelEvent* ev, int num) {
|
|||
}
|
||||
|
||||
bool EqueueInternal::AddSmallTimer(EqueueEvent& ev) {
|
||||
// We assume that only one timer event (with the same ident across calls)
|
||||
// can be posted to the queue, based on observations so far. In the opposite case,
|
||||
// the small timer storage and wait logic should be reworked.
|
||||
ASSERT(!HasSmallTimer() || small_timer_event.event.ident == ev.event.ident);
|
||||
ev.time_added = std::chrono::steady_clock::now();
|
||||
small_timer_event = std::move(ev);
|
||||
SmallTimer st;
|
||||
st.event = ev.event;
|
||||
st.added = std::chrono::steady_clock::now();
|
||||
st.interval = std::chrono::microseconds{ev.event.data};
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
m_small_timers[st.event.ident] = std::move(st);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int EqueueInternal::WaitForSmallTimer(SceKernelEvent* ev, int num, u32 micros) {
|
||||
int count{};
|
||||
|
||||
ASSERT(num == 1);
|
||||
ASSERT(num >= 1);
|
||||
|
||||
auto curr_clock = std::chrono::steady_clock::now();
|
||||
const auto wait_end_us = (micros == 0) ? std::chrono::steady_clock::time_point::max()
|
||||
: curr_clock + std::chrono::microseconds{micros};
|
||||
|
||||
int count = 0;
|
||||
do {
|
||||
curr_clock = std::chrono::steady_clock::now();
|
||||
{
|
||||
std::scoped_lock lock{m_mutex};
|
||||
if ((curr_clock - small_timer_event.time_added) >
|
||||
std::chrono::microseconds{small_timer_event.event.data}) {
|
||||
ev[count++] = small_timer_event.event;
|
||||
small_timer_event.event.data = 0;
|
||||
break;
|
||||
for (auto it = m_small_timers.begin(); it != m_small_timers.end() && count < num;) {
|
||||
const SmallTimer& st = it->second;
|
||||
|
||||
if (curr_clock - st.added >= st.interval) {
|
||||
ev[count++] = st.event;
|
||||
it = m_small_timers.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
return count;
|
||||
}
|
||||
std::this_thread::yield();
|
||||
} while (curr_clock < wait_end_us);
|
||||
|
||||
return count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EqueueInternal::EventExists(u64 id, s16 filter) {
|
||||
|
@ -326,6 +332,11 @@ s32 PS4_SYSV_ABI sceKernelAddHRTimerEvent(SceKernelEqueue eq, int id, timespec*
|
|||
// `HrTimerSpinlockThresholdUs`) and fall back to boost asio timers if the time to tick is
|
||||
// large. Even for large delays, we truncate a small portion to complete the wait
|
||||
// using the spinlock, prioritizing precision.
|
||||
|
||||
if (eq->EventExists(event.event.ident, event.event.filter)) {
|
||||
eq->RemoveEvent(id, SceKernelEvent::Filter::HrTimer);
|
||||
}
|
||||
|
||||
if (total_us < HrTimerSpinlockThresholdUs) {
|
||||
return eq->AddSmallTimer(event) ? ORBIS_OK : ORBIS_KERNEL_ERROR_ENOMEM;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <vector>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
|
||||
#include <unordered_map>
|
||||
#include "common/rdtsc.h"
|
||||
#include "common/types.h"
|
||||
|
||||
|
@ -135,6 +136,12 @@ private:
|
|||
};
|
||||
|
||||
class EqueueInternal {
|
||||
struct SmallTimer {
|
||||
SceKernelEvent event;
|
||||
std::chrono::steady_clock::time_point added;
|
||||
std::chrono::microseconds interval;
|
||||
};
|
||||
|
||||
public:
|
||||
explicit EqueueInternal(std::string_view name) : m_name(name) {}
|
||||
|
||||
|
@ -151,13 +158,14 @@ public:
|
|||
int GetTriggeredEvents(SceKernelEvent* ev, int num);
|
||||
|
||||
bool AddSmallTimer(EqueueEvent& event);
|
||||
bool HasSmallTimer() const {
|
||||
return small_timer_event.event.data != 0;
|
||||
bool HasSmallTimer() {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return !m_small_timers.empty();
|
||||
}
|
||||
bool RemoveSmallTimer(u64 id) {
|
||||
if (HasSmallTimer() && small_timer_event.event.ident == id) {
|
||||
small_timer_event = {};
|
||||
return true;
|
||||
if (HasSmallTimer()) {
|
||||
std::scoped_lock lock{m_mutex};
|
||||
return m_small_timers.erase(id) > 0;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -170,8 +178,8 @@ private:
|
|||
std::string m_name;
|
||||
std::mutex m_mutex;
|
||||
std::vector<EqueueEvent> m_events;
|
||||
EqueueEvent small_timer_event{};
|
||||
std::condition_variable m_cond;
|
||||
std::unordered_map<u64, SmallTimer> m_small_timers;
|
||||
};
|
||||
|
||||
u64 PS4_SYSV_ABI sceKernelGetEventData(const SceKernelEvent* ev);
|
||||
|
|
|
@ -1050,6 +1050,7 @@ void RegisterFileSystem(Core::Loader::SymbolsResolver* sym) {
|
|||
LIB_FUNCTION("4wSze92BhLI", "libkernel", 1, "libkernel", 1, 1, sceKernelWrite);
|
||||
LIB_FUNCTION("+WRlkKjZvag", "libkernel", 1, "libkernel", 1, 1, readv);
|
||||
LIB_FUNCTION("YSHRBRLn2pI", "libkernel", 1, "libkernel", 1, 1, writev);
|
||||
LIB_FUNCTION("kAt6VDbHmro", "libkernel", 1, "libkernel", 1, 1, sceKernelWritev);
|
||||
LIB_FUNCTION("Oy6IpwgtYOk", "libScePosix", 1, "libkernel", 1, 1, posix_lseek);
|
||||
LIB_FUNCTION("Oy6IpwgtYOk", "libkernel", 1, "libkernel", 1, 1, posix_lseek);
|
||||
LIB_FUNCTION("oib76F-12fk", "libkernel", 1, "libkernel", 1, 1, sceKernelLseek);
|
||||
|
|
|
@ -99,8 +99,8 @@ s32 PS4_SYSV_ABI sceKernelReleaseDirectMemory(u64 start, size_t len) {
|
|||
s32 PS4_SYSV_ABI sceKernelAvailableDirectMemorySize(u64 searchStart, u64 searchEnd,
|
||||
size_t alignment, u64* physAddrOut,
|
||||
size_t* sizeOut) {
|
||||
LOG_WARNING(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
|
||||
searchStart, searchEnd, alignment);
|
||||
LOG_INFO(Kernel_Vmm, "called searchStart = {:#x}, searchEnd = {:#x}, alignment = {:#x}",
|
||||
searchStart, searchEnd, alignment);
|
||||
|
||||
if (physAddrOut == nullptr || sizeOut == nullptr) {
|
||||
return ORBIS_KERNEL_ERROR_EINVAL;
|
||||
|
@ -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,
|
||||
size_t infoSize) {
|
||||
LOG_WARNING(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
|
||||
LOG_INFO(Kernel_Vmm, "called offset = {:#x}, flags = {:#x}", offset, flags);
|
||||
auto* memory = Core::Memory::Instance();
|
||||
return memory->DirectMemoryQuery(offset, flags == 1, query_info);
|
||||
}
|
||||
|
@ -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,
|
||||
address, size);
|
||||
|
||||
auto* memory = Core::Memory::Instance();
|
||||
memory->SetPrtArea(id, address, size);
|
||||
|
||||
PrtApertures[id] = {address, size};
|
||||
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);
|
||||
contexts_internal[key].context_id = ctx_id.index;
|
||||
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", ctx_id.index,
|
||||
user_id, service_label);
|
||||
*context = ctx_id.index;
|
||||
|
||||
*context = ctx_id.index + 1;
|
||||
contexts_internal[key].context_id = *context;
|
||||
LOG_INFO(Lib_NpTrophy, "New context = {}, user_id = {} service label = {}", *context, user_id,
|
||||
service_label);
|
||||
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
|
@ -179,21 +181,23 @@ s32 PS4_SYSV_ABI sceNpTrophyCreateHandle(OrbisNpTrophyHandle* handle) {
|
|||
if (trophy_handles.size() >= MaxTrophyHandles) {
|
||||
return ORBIS_NP_TROPHY_ERROR_HANDLE_EXCEEDS_MAX;
|
||||
}
|
||||
const auto handle_id = trophy_handles.insert();
|
||||
LOG_INFO(Lib_NpTrophy, "New handle = {}", handle_id.index);
|
||||
|
||||
*handle = handle_id.index;
|
||||
const auto handle_id = trophy_handles.insert();
|
||||
|
||||
*handle = handle_id.index + 1;
|
||||
LOG_INFO(Lib_NpTrophy, "New handle = {}", *handle);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
||||
int PS4_SYSV_ABI sceNpTrophyDestroyContext(OrbisNpTrophyContext context) {
|
||||
LOG_INFO(Lib_NpTrophy, "Destroyed Context {}", context);
|
||||
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT)
|
||||
if (context == ORBIS_NP_TROPHY_INVALID_CONTEXT) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_CONTEXT;
|
||||
}
|
||||
|
||||
Common::SlotId contextId;
|
||||
contextId.index = context;
|
||||
contextId.index = context - 1;
|
||||
|
||||
ContextKey contextkey = trophy_contexts[contextId];
|
||||
trophy_contexts.erase(contextId);
|
||||
|
@ -206,15 +210,17 @@ s32 PS4_SYSV_ABI sceNpTrophyDestroyHandle(OrbisNpTrophyHandle handle) {
|
|||
if (handle == ORBIS_NP_TROPHY_INVALID_HANDLE)
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
|
||||
if (handle >= trophy_handles.size()) {
|
||||
s32 handle_index = handle - 1;
|
||||
if (handle_index >= trophy_handles.size()) {
|
||||
LOG_ERROR(Lib_NpTrophy, "Invalid handle {}", handle);
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
if (!trophy_handles.is_allocated({static_cast<u32>(handle)})) {
|
||||
|
||||
if (!trophy_handles.is_allocated({static_cast<u32>(handle_index)})) {
|
||||
return ORBIS_NP_TROPHY_ERROR_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
trophy_handles.erase({static_cast<u32>(handle)});
|
||||
trophy_handles.erase({static_cast<u32>(handle_index)});
|
||||
LOG_INFO(Lib_NpTrophy, "Handle {} destroyed", handle);
|
||||
return ORBIS_OK;
|
||||
}
|
||||
|
|
|
@ -140,7 +140,7 @@ s32 PS4_SYSV_ABI sceVideodec2Flush(OrbisVideodec2Decoder decoder,
|
|||
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
|
||||
}
|
||||
if (frameBuffer->thisSize != sizeof(OrbisVideodec2FrameBuffer) ||
|
||||
outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
|
||||
(outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
|
|||
LOG_ERROR(Lib_Vdec2, "Invalid arguments");
|
||||
return ORBIS_VIDEODEC2_ERROR_ARGUMENT_POINTER;
|
||||
}
|
||||
if (outputInfo->thisSize != sizeof(OrbisVideodec2OutputInfo)) {
|
||||
if ((outputInfo->thisSize | 8) != sizeof(OrbisVideodec2OutputInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
|
@ -179,7 +179,7 @@ s32 PS4_SYSV_ABI sceVideodec2GetPictureInfo(const OrbisVideodec2OutputInfo* outp
|
|||
if (p1stPictureInfoOut) {
|
||||
OrbisVideodec2AvcPictureInfo* picInfo =
|
||||
static_cast<OrbisVideodec2AvcPictureInfo*>(p1stPictureInfoOut);
|
||||
if (picInfo->thisSize != sizeof(OrbisVideodec2AvcPictureInfo)) {
|
||||
if ((picInfo->thisSize | 16) != sizeof(OrbisVideodec2AvcPictureInfo)) {
|
||||
LOG_ERROR(Lib_Vdec2, "Invalid struct size");
|
||||
return ORBIS_VIDEODEC2_ERROR_STRUCT_SIZE;
|
||||
}
|
||||
|
|
|
@ -73,8 +73,10 @@ struct OrbisVideodec2OutputInfo {
|
|||
u32 frameHeight;
|
||||
void* frameBuffer;
|
||||
u64 frameBufferSize;
|
||||
u32 frameFormat;
|
||||
u32 framePitchInBytes;
|
||||
};
|
||||
static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x30);
|
||||
static_assert(sizeof(OrbisVideodec2OutputInfo) == 0x38);
|
||||
|
||||
struct OrbisVideodec2FrameBuffer {
|
||||
u64 thisSize;
|
||||
|
|
|
@ -55,6 +55,23 @@ struct OrbisVideodec2AvcPictureInfo {
|
|||
u8 pic_struct;
|
||||
u8 field_pic_flag;
|
||||
u8 bottom_field_flag;
|
||||
|
||||
u8 sequenceParameterSetPresentFlag;
|
||||
u8 pictureParameterSetPresentFlag;
|
||||
u8 auDelimiterPresentFlag;
|
||||
u8 endOfSequencePresentFlag;
|
||||
u8 endOfStreamPresentFlag;
|
||||
u8 fillerDataPresentFlag;
|
||||
u8 pictureTimingSeiPresentFlag;
|
||||
u8 bufferingPeriodSeiPresentFlag;
|
||||
|
||||
u8 constraint_set0_flag;
|
||||
u8 constraint_set1_flag;
|
||||
u8 constraint_set2_flag;
|
||||
u8 constraint_set3_flag;
|
||||
u8 constraint_set4_flag;
|
||||
u8 constraint_set5_flag;
|
||||
};
|
||||
static_assert(sizeof(OrbisVideodec2AvcPictureInfo) == 0x78);
|
||||
|
||||
} // namespace Libraries::Vdec2
|
|
@ -44,11 +44,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
|
|||
OrbisVideodec2FrameBuffer& frameBuffer,
|
||||
OrbisVideodec2OutputInfo& outputInfo) {
|
||||
frameBuffer.isAccepted = false;
|
||||
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
|
||||
outputInfo.isValid = false;
|
||||
outputInfo.isErrorFrame = true;
|
||||
outputInfo.pictureCount = 0;
|
||||
|
||||
// Only set frameFormat if the game uses the newer struct version.
|
||||
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
|
||||
outputInfo.frameFormat = 0;
|
||||
}
|
||||
|
||||
if (!inputData.auData) {
|
||||
return ORBIS_VIDEODEC2_ERROR_ACCESS_UNIT_POINTER;
|
||||
}
|
||||
|
@ -113,6 +117,11 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
|
|||
outputInfo.isErrorFrame = false;
|
||||
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
|
||||
|
||||
// Only set framePitchInBytes if the game uses the newer struct version.
|
||||
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
|
||||
outputInfo.framePitchInBytes = frame->linesize[0];
|
||||
}
|
||||
|
||||
if (outputInfo.isValid) {
|
||||
OrbisVideodec2AvcPictureInfo pictureInfo = {};
|
||||
|
||||
|
@ -140,11 +149,15 @@ s32 VdecDecoder::Decode(const OrbisVideodec2InputData& inputData,
|
|||
s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
|
||||
OrbisVideodec2OutputInfo& outputInfo) {
|
||||
frameBuffer.isAccepted = false;
|
||||
outputInfo.thisSize = sizeof(OrbisVideodec2OutputInfo);
|
||||
outputInfo.isValid = false;
|
||||
outputInfo.isErrorFrame = true;
|
||||
outputInfo.pictureCount = 0;
|
||||
|
||||
// Only set frameFormat if the game uses the newer struct version.
|
||||
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
|
||||
outputInfo.frameFormat = 0;
|
||||
}
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
if (!frame) {
|
||||
LOG_ERROR(Lib_Vdec2, "Failed to allocate frame");
|
||||
|
@ -182,6 +195,11 @@ s32 VdecDecoder::Flush(OrbisVideodec2FrameBuffer& frameBuffer,
|
|||
outputInfo.isErrorFrame = false;
|
||||
outputInfo.pictureCount = 1; // TODO: 2 pictures for interlaced video
|
||||
|
||||
// Only set framePitchInBytes if the game uses the newer struct version.
|
||||
if (outputInfo.thisSize == sizeof(OrbisVideodec2OutputInfo)) {
|
||||
outputInfo.framePitchInBytes = frame->linesize[0];
|
||||
}
|
||||
|
||||
// FIXME: Should we add picture info here too?
|
||||
}
|
||||
|
||||
|
|
|
@ -332,21 +332,22 @@ bool Linker::Resolve(const std::string& name, Loader::SymbolType sym_type, Modul
|
|||
sr.type = sym_type;
|
||||
|
||||
const auto* record = m_hle_symbols.FindSymbol(sr);
|
||||
if (!record) {
|
||||
// Check if it an export function
|
||||
const auto* p = FindExportedModule(*module, *library);
|
||||
if (p && p->export_sym.GetSize() > 0) {
|
||||
record = p->export_sym.FindSymbol(sr);
|
||||
}
|
||||
}
|
||||
if (record) {
|
||||
*return_info = *record;
|
||||
|
||||
Core::Devtools::Widget::ModuleList::AddModule(sr.library);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if it an export function
|
||||
const auto* p = FindExportedModule(*module, *library);
|
||||
if (p && p->export_sym.GetSize() > 0) {
|
||||
record = p->export_sym.FindSymbol(sr);
|
||||
if (record) {
|
||||
*return_info = *record;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
const auto aeronid = AeroLib::FindByNid(sr.name.c_str());
|
||||
if (aeronid) {
|
||||
return_info->name = aeronid->name;
|
||||
|
|
|
@ -95,6 +95,46 @@ u64 MemoryManager::ClampRangeSize(VAddr virtual_addr, u64 size) {
|
|||
return clamped_size;
|
||||
}
|
||||
|
||||
void MemoryManager::SetPrtArea(u32 id, VAddr address, u64 size) {
|
||||
PrtArea& area = prt_areas[id];
|
||||
if (area.mapped) {
|
||||
rasterizer->UnmapMemory(area.start, area.end - area.start);
|
||||
}
|
||||
|
||||
area.start = address;
|
||||
area.end = address + size;
|
||||
area.mapped = true;
|
||||
|
||||
// Pretend the entire PRT area is mapped to avoid GPU tracking errors.
|
||||
// The caches will use CopySparseMemory to fetch data which avoids unmapped areas.
|
||||
rasterizer->MapMemory(address, size);
|
||||
}
|
||||
|
||||
void MemoryManager::CopySparseMemory(VAddr virtual_addr, u8* dest, u64 size) {
|
||||
const bool is_sparse = std::ranges::any_of(
|
||||
prt_areas, [&](const PrtArea& area) { return area.Overlaps(virtual_addr, size); });
|
||||
if (!is_sparse) {
|
||||
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), size);
|
||||
return;
|
||||
}
|
||||
|
||||
auto vma = FindVMA(virtual_addr);
|
||||
ASSERT_MSG(vma->second.Contains(virtual_addr, 0),
|
||||
"Attempted to access invalid GPU address {:#x}", virtual_addr);
|
||||
while (size) {
|
||||
u64 copy_size = std::min<u64>(vma->second.size - (virtual_addr - vma->first), size);
|
||||
if (vma->second.IsFree()) {
|
||||
std::memset(dest, 0, copy_size);
|
||||
} else {
|
||||
std::memcpy(dest, std::bit_cast<const u8*>(virtual_addr), copy_size);
|
||||
}
|
||||
size -= copy_size;
|
||||
virtual_addr += copy_size;
|
||||
dest += copy_size;
|
||||
++vma;
|
||||
}
|
||||
}
|
||||
|
||||
bool MemoryManager::TryWriteBacking(void* address, const void* data, u32 num_bytes) {
|
||||
const VAddr virtual_addr = std::bit_cast<VAddr>(address);
|
||||
const auto& vma = FindVMA(virtual_addr)->second;
|
||||
|
|
|
@ -75,6 +75,9 @@ struct DirectMemoryArea {
|
|||
if (base + size != next.base) {
|
||||
return false;
|
||||
}
|
||||
if (memory_type != next.memory_type) {
|
||||
return false;
|
||||
}
|
||||
if (is_free != next.is_free) {
|
||||
return false;
|
||||
}
|
||||
|
@ -172,6 +175,10 @@ public:
|
|||
|
||||
u64 ClampRangeSize(VAddr virtual_addr, u64 size);
|
||||
|
||||
void SetPrtArea(u32 id, VAddr address, u64 size);
|
||||
|
||||
void CopySparseMemory(VAddr source, u8* dest, u64 size);
|
||||
|
||||
bool TryWriteBacking(void* address, const void* data, u32 num_bytes);
|
||||
|
||||
void SetupMemoryRegions(u64 flexible_size, bool use_extended_mem1, bool use_extended_mem2);
|
||||
|
@ -275,6 +282,18 @@ private:
|
|||
size_t pool_budget{};
|
||||
Vulkan::Rasterizer* rasterizer{};
|
||||
|
||||
struct PrtArea {
|
||||
VAddr start;
|
||||
VAddr end;
|
||||
bool mapped;
|
||||
|
||||
bool Overlaps(VAddr test_address, u64 test_size) const {
|
||||
const VAddr overlap_end = test_address + test_size;
|
||||
return start < overlap_end && test_address < end;
|
||||
}
|
||||
};
|
||||
std::array<PrtArea, 3> prt_areas{};
|
||||
|
||||
friend class ::Core::Devtools::Widget::MemoryMapViewer;
|
||||
};
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <windows.h>
|
||||
#else
|
||||
#include <csignal>
|
||||
#include <pthread.h>
|
||||
#ifdef ARCH_X86_64
|
||||
#include <Zydis/Formatter.h>
|
||||
#endif
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <set>
|
||||
#include "common/singleton.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ Tcb* GetTcbBase() {
|
|||
// Apple x86_64
|
||||
|
||||
// Reserve space in the 32-bit address range for allocating TCB pages.
|
||||
asm(".zerofill TCB_SPACE,TCB_SPACE,__guest_system,0x3FC000");
|
||||
asm(".zerofill TCB_SPACE,TCB_SPACE,__tcb_space,0x3FC000");
|
||||
|
||||
struct LdtPage {
|
||||
void* tcb;
|
||||
|
|
|
@ -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")) {
|
||||
// If an executable was launched from a separate update directory,
|
||||
// use the base game directory as the game folder.
|
||||
const auto base_name = game_folder_name.substr(0, game_folder_name.size() - 7);
|
||||
const std::string base_name = game_folder_name.substr(0, game_folder_name.rfind('-'));
|
||||
const auto base_path = game_folder.parent_path() / base_name;
|
||||
if (std::filesystem::is_directory(base_path)) {
|
||||
game_folder = base_path;
|
||||
|
|
46
src/main.cpp
46
src/main.cpp
|
@ -35,17 +35,20 @@ int main(int argc, char* argv[]) {
|
|||
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
|
||||
{"-h",
|
||||
[&](int&) {
|
||||
std::cout << "Usage: shadps4 [options] <elf or eboot.bin path>\n"
|
||||
"Options:\n"
|
||||
" -g, --game <path|ID> Specify game path to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" -h, --help Display this help message\n";
|
||||
std::cout
|
||||
<< "Usage: shadps4 [options] <elf or eboot.bin path>\n"
|
||||
"Options:\n"
|
||||
" -g, --game <path|ID> Specify game path to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" --set-addon-folder <folder> Sets the addon folder to the config.\n"
|
||||
" -h, --help Display this help message\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--help", [&](int& i) { arg_map["-h"](i); }},
|
||||
|
@ -72,6 +75,8 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
}},
|
||||
{"--patch", [&](int& i) { arg_map["-p"](i); }},
|
||||
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
|
||||
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
|
||||
{"-f",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
|
@ -112,7 +117,24 @@ int main(int argc, char* argv[]) {
|
|||
std::cout << "Game folder successfully saved.\n";
|
||||
exit(0);
|
||||
}},
|
||||
};
|
||||
{"--set-addon-folder", [&](int& i) {
|
||||
if (++i >= argc) {
|
||||
std::cerr << "Error: Missing argument for --add-addon-folder\n";
|
||||
exit(1);
|
||||
}
|
||||
std::string config_dir(argv[i]);
|
||||
std::filesystem::path config_path = std::filesystem::path(config_dir);
|
||||
std::error_code discard;
|
||||
if (!std::filesystem::exists(config_path, discard)) {
|
||||
std::cerr << "Error: File does not exist: " << config_path << "\n";
|
||||
exit(1);
|
||||
}
|
||||
|
||||
Config::setAddonInstallDir(config_path);
|
||||
Config::save(Common::FS::GetUserPath(Common::FS::PathType::UserDir) / "config.toml");
|
||||
std::cout << "Addon folder successfully saved.\n";
|
||||
exit(0);
|
||||
}}};
|
||||
|
||||
if (argc == 1) {
|
||||
int dummy = 0; // one does not simply pass 0 directly
|
||||
|
|
|
@ -28,8 +28,10 @@
|
|||
|
||||
using namespace Common::FS;
|
||||
|
||||
CheckUpdate::CheckUpdate(const bool showMessage, QWidget* parent)
|
||||
: QDialog(parent), networkManager(new QNetworkAccessManager(this)) {
|
||||
CheckUpdate::CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
|
||||
QWidget* parent)
|
||||
: QDialog(parent), m_gui_settings(std::move(gui_settings)),
|
||||
networkManager(new QNetworkAccessManager(this)) {
|
||||
setWindowTitle(tr("Auto Updater"));
|
||||
setFixedSize(0, 0);
|
||||
CheckForUpdates(showMessage);
|
||||
|
@ -43,7 +45,7 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
|
|||
|
||||
bool checkName = true;
|
||||
while (checkName) {
|
||||
updateChannel = QString::fromStdString(Config::getUpdateChannel());
|
||||
updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
|
||||
if (updateChannel == "Nightly") {
|
||||
url = QUrl("https://api.github.com/repos/shadps4-emu/shadPS4/releases");
|
||||
checkName = false;
|
||||
|
@ -52,12 +54,10 @@ void CheckUpdate::CheckForUpdates(const bool showMessage) {
|
|||
checkName = false;
|
||||
} else {
|
||||
if (Common::g_is_release) {
|
||||
Config::setUpdateChannel("Release");
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
|
||||
} else {
|
||||
Config::setUpdateChannel("Nightly");
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
|
||||
}
|
||||
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
Config::save(config_dir / "config.toml");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -198,7 +198,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
|
|||
titleLayout->addWidget(titleLabel);
|
||||
layout->addLayout(titleLayout);
|
||||
|
||||
QString updateChannel = QString::fromStdString(Config::getUpdateChannel());
|
||||
QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
|
||||
|
||||
QString updateText = QString("<p><b>" + tr("Update Channel") + ": </b>" + updateChannel +
|
||||
"<br>"
|
||||
|
@ -273,7 +273,7 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
|
|||
}
|
||||
});
|
||||
|
||||
if (Config::alwaysShowChangelog()) {
|
||||
if (m_gui_settings->GetValue(gui::gen_showChangeLog).toBool()) {
|
||||
requestChangelog(currentRev, latestRev, downloadUrl, latestDate, currentDate);
|
||||
textField->setVisible(true);
|
||||
toggleButton->setText(tr("Hide Changelog"));
|
||||
|
@ -290,14 +290,14 @@ void CheckUpdate::setupUI(const QString& downloadUrl, const QString& latestDate,
|
|||
|
||||
connect(noButton, &QPushButton::clicked, this, [this]() { close(); });
|
||||
|
||||
autoUpdateCheckBox->setChecked(Config::autoUpdate());
|
||||
autoUpdateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
|
||||
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [](int state) {
|
||||
connect(autoUpdateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
#else
|
||||
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [](Qt::CheckState state) {
|
||||
connect(autoUpdateCheckBox, &QCheckBox::checkStateChanged, this, [this](Qt::CheckState state) {
|
||||
#endif
|
||||
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
|
||||
Config::setAutoUpdate(state == Qt::Checked);
|
||||
m_gui_settings->SetValue(gui::gen_checkForUpdates, (state == Qt::Checked));
|
||||
Config::save(user_dir / "config.toml");
|
||||
});
|
||||
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
#include <QDialog>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QPushButton>
|
||||
#include "gui_settings.h"
|
||||
|
||||
class CheckUpdate : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CheckUpdate(const bool showMessage, QWidget* parent = nullptr);
|
||||
explicit CheckUpdate(std::shared_ptr<gui_settings> gui_settings, const bool showMessage,
|
||||
QWidget* parent = nullptr);
|
||||
~CheckUpdate();
|
||||
|
||||
private slots:
|
||||
|
@ -35,6 +37,7 @@ private:
|
|||
QString updateDownloadUrl;
|
||||
|
||||
QNetworkAccessManager* networkManager;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
};
|
||||
|
||||
#endif // CHECKUPDATE_H
|
||||
|
|
|
@ -5,11 +5,13 @@
|
|||
#include "game_grid_frame.h"
|
||||
#include "qt_gui/compatibility_info.h"
|
||||
|
||||
GameGridFrame::GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
GameGridFrame::GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
|
||||
QWidget* parent)
|
||||
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) {
|
||||
icon_size = Config::getIconSizeGrid();
|
||||
: QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
|
||||
m_compat_info(compat_info_get) {
|
||||
icon_size = m_gui_settings->GetValue(gui::gg_icon_size).toInt();
|
||||
windowWidth = parent->width();
|
||||
this->setShowGrid(false);
|
||||
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
@ -74,7 +76,7 @@ void GameGridFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
|
|||
}
|
||||
|
||||
void GameGridFrame::PlayBackgroundMusic(QString path) {
|
||||
if (path.isEmpty() || !Config::getPlayBGM()) {
|
||||
if (path.isEmpty() || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
return;
|
||||
}
|
||||
|
@ -91,7 +93,8 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
|
|||
else
|
||||
m_games_ = m_game_info->m_games;
|
||||
m_games_shared = std::make_shared<QVector<GameInfo>>(m_games_);
|
||||
icon_size = Config::getIconSizeGrid(); // update icon size for resize event.
|
||||
icon_size =
|
||||
m_gui_settings->GetValue(gui::gg_icon_size).toInt(); // update icon size for resize event.
|
||||
|
||||
int gamesPerRow = windowWidth / (icon_size + 20); // 2 x cell widget border size.
|
||||
int row = 0;
|
||||
|
@ -118,7 +121,7 @@ void GameGridFrame::PopulateGameGrid(QVector<GameInfo> m_games_search, bool from
|
|||
layout->addWidget(name_label);
|
||||
|
||||
// Resizing of font-size.
|
||||
float fontSize = (Config::getIconSizeGrid() / 5.5f);
|
||||
float fontSize = (m_gui_settings->GetValue(gui::gg_icon_size).toInt() / 5.5f);
|
||||
QString styleSheet =
|
||||
QString("color: white; font-weight: bold; font-size: %1px;").arg(fontSize);
|
||||
name_label->setStyleSheet(styleSheet);
|
||||
|
@ -168,7 +171,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
|
|||
}
|
||||
|
||||
// If background images are hidden, clear the background image
|
||||
if (!Config::getShowBackgroundImage()) {
|
||||
if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
|
||||
backgroundImage = QImage();
|
||||
m_last_opacity = -1; // Reset opacity tracking when disabled
|
||||
m_current_game_path.clear(); // Reset current game path
|
||||
|
@ -177,7 +180,7 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
|
|||
}
|
||||
|
||||
const auto& game = (*m_games_shared)[itemID];
|
||||
const int opacity = Config::getBackgroundImageOpacity();
|
||||
const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
|
||||
|
||||
// Recompute if opacity changed or we switched to a different game
|
||||
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
|
||||
|
@ -195,7 +198,8 @@ void GameGridFrame::SetGridBackgroundImage(int row, int column) {
|
|||
|
||||
void GameGridFrame::RefreshGridBackgroundImage() {
|
||||
QPalette palette;
|
||||
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
|
||||
if (!backgroundImage.isNull() &&
|
||||
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
|
||||
QSize widgetSize = size();
|
||||
QPixmap scaledPixmap =
|
||||
QPixmap::fromImage(backgroundImage)
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "game_info.h"
|
||||
#include "game_list_utils.h"
|
||||
#include "gui_context_menus.h"
|
||||
#include "gui_settings.h"
|
||||
#include "qt_gui/compatibility_info.h"
|
||||
|
||||
class GameGridFrame : public QTableWidget {
|
||||
|
@ -37,9 +38,11 @@ private:
|
|||
bool validCellSelected = false;
|
||||
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
|
||||
std::filesystem::path m_current_game_path; // Track current game path to detect changes
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
|
||||
public:
|
||||
explicit GameGridFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
explicit GameGridFrame(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
|
||||
QWidget* parent = nullptr);
|
||||
void PopulateGameGrid(QVector<GameInfo> m_games, bool fromSearch);
|
||||
|
|
|
@ -9,11 +9,13 @@
|
|||
#include "game_list_frame.h"
|
||||
#include "game_list_utils.h"
|
||||
|
||||
GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
GameListFrame::GameListFrame(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
|
||||
QWidget* parent)
|
||||
: QTableWidget(parent), m_game_info(game_info_get), m_compat_info(compat_info_get) {
|
||||
icon_size = Config::getIconSize();
|
||||
: QTableWidget(parent), m_gui_settings(std::move(gui_settings)), m_game_info(game_info_get),
|
||||
m_compat_info(compat_info_get) {
|
||||
icon_size = m_gui_settings->GetValue(gui::gl_icon_size).toInt();
|
||||
this->setShowGrid(false);
|
||||
this->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
this->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
|
@ -97,7 +99,7 @@ void GameListFrame::onCurrentCellChanged(int currentRow, int currentColumn, int
|
|||
}
|
||||
|
||||
void GameListFrame::PlayBackgroundMusic(QTableWidgetItem* item) {
|
||||
if (!item || !Config::getPlayBGM()) {
|
||||
if (!item || !m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool()) {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
return;
|
||||
}
|
||||
|
@ -172,7 +174,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
|
|||
}
|
||||
|
||||
// If background images are hidden, clear the background image
|
||||
if (!Config::getShowBackgroundImage()) {
|
||||
if (!m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
|
||||
backgroundImage = QImage();
|
||||
m_last_opacity = -1; // Reset opacity tracking when disabled
|
||||
m_current_game_path.clear(); // Reset current game path
|
||||
|
@ -181,7 +183,7 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
|
|||
}
|
||||
|
||||
const auto& game = m_game_info->m_games[item->row()];
|
||||
const int opacity = Config::getBackgroundImageOpacity();
|
||||
const int opacity = m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
|
||||
|
||||
// Recompute if opacity changed or we switched to a different game
|
||||
if (opacity != m_last_opacity || game.pic_path != m_current_game_path) {
|
||||
|
@ -200,7 +202,8 @@ void GameListFrame::SetListBackgroundImage(QTableWidgetItem* item) {
|
|||
|
||||
void GameListFrame::RefreshListBackgroundImage() {
|
||||
QPalette palette;
|
||||
if (!backgroundImage.isNull() && Config::getShowBackgroundImage()) {
|
||||
if (!backgroundImage.isNull() &&
|
||||
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool()) {
|
||||
QSize widgetSize = size();
|
||||
QPixmap scaledPixmap =
|
||||
QPixmap::fromImage(backgroundImage)
|
||||
|
|
|
@ -17,11 +17,13 @@
|
|||
#include "game_info.h"
|
||||
#include "game_list_utils.h"
|
||||
#include "gui_context_menus.h"
|
||||
#include "gui_settings.h"
|
||||
|
||||
class GameListFrame : public QTableWidget {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GameListFrame(std::shared_ptr<GameInfoClass> game_info_get,
|
||||
explicit GameListFrame(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<GameInfoClass> game_info_get,
|
||||
std::shared_ptr<CompatibilityInfoClass> compat_info_get,
|
||||
QWidget* parent = nullptr);
|
||||
Q_SIGNALS:
|
||||
|
@ -48,6 +50,7 @@ private:
|
|||
QTableWidgetItem* m_current_item = nullptr;
|
||||
int m_last_opacity = -1; // Track last opacity to avoid unnecessary recomputation
|
||||
std::filesystem::path m_current_game_path; // Track current game path to detect changes
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
|
||||
public:
|
||||
void PopulateGameList(bool isInitialPopulation = true);
|
||||
|
|
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;
|
||||
};
|
|
@ -33,13 +33,13 @@ KBMSettings::KBMSettings(std::shared_ptr<GameInfoClass> game_info_get, QWidget*
|
|||
}
|
||||
|
||||
ButtonsList = {
|
||||
ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton,
|
||||
ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button,
|
||||
ui->L3Button, ui->R3Button, ui->TouchpadButton, ui->OptionsButton,
|
||||
ui->TouchpadButton, ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton,
|
||||
ui->DpadRightButton, ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton,
|
||||
ui->LStickRightButton, ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton,
|
||||
ui->RStickRightButton, ui->LHalfButton, ui->RHalfButton};
|
||||
ui->CrossButton, ui->CircleButton, ui->TriangleButton, ui->SquareButton,
|
||||
ui->L1Button, ui->R1Button, ui->L2Button, ui->R2Button,
|
||||
ui->L3Button, ui->R3Button, ui->OptionsButton, ui->TouchpadButton,
|
||||
ui->DpadUpButton, ui->DpadDownButton, ui->DpadLeftButton, ui->DpadRightButton,
|
||||
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
|
||||
ui->RStickUpButton, ui->RStickDownButton, ui->RStickLeftButton, ui->RStickRightButton,
|
||||
ui->LHalfButton, ui->RHalfButton};
|
||||
|
||||
ButtonConnects();
|
||||
SetUIValuestoMappings("default");
|
||||
|
@ -372,14 +372,31 @@ void KBMSettings::SaveKBMConfig(bool CloseOnSave) {
|
|||
file.close();
|
||||
|
||||
// Prevent duplicate inputs for KBM as this breaks the engine
|
||||
bool duplicateFound = false;
|
||||
QSet<QString> duplicateMappings;
|
||||
for (auto it = inputs.begin(); it != inputs.end(); ++it) {
|
||||
if (std::find(it + 1, inputs.end(), *it) != inputs.end()) {
|
||||
QMessageBox::information(this, tr("Unable to Save"),
|
||||
tr("Cannot bind any unique input more than once"));
|
||||
return;
|
||||
duplicateFound = true;
|
||||
duplicateMappings.insert(QString::fromStdString(*it));
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicateFound) {
|
||||
QStringList duplicatesList;
|
||||
for (const QString mapping : duplicateMappings) {
|
||||
for (const auto& button : ButtonsList) {
|
||||
if (button->text() == mapping)
|
||||
duplicatesList.append(button->objectName() + " - " + mapping);
|
||||
}
|
||||
}
|
||||
QMessageBox::information(
|
||||
this, tr("Unable to Save"),
|
||||
// clang-format off
|
||||
QString(tr("Cannot bind any unique input more than once. Duplicate inputs mapped to the following buttons:\n\n%1").arg(duplicatesList.join("\n"))));
|
||||
// clang-format on
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<std::string> save;
|
||||
bool CurrentLineEmpty = false, LastLineEmpty = false;
|
||||
for (auto const& line : lines) {
|
||||
|
|
|
@ -41,20 +41,22 @@ int main(int argc, char* argv[]) {
|
|||
std::unordered_map<std::string, std::function<void(int&)>> arg_map = {
|
||||
{"-h",
|
||||
[&](int&) {
|
||||
std::cout << "Usage: shadps4 [options]\n"
|
||||
"Options:\n"
|
||||
" No arguments: Opens the GUI.\n"
|
||||
" -g, --game <path|ID> Specify <eboot.bin or elf path> or "
|
||||
"<game ID (CUSAXXXXX)> to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -s, --show-gui Show the GUI\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" -h, --help Display this help message\n";
|
||||
std::cout
|
||||
<< "Usage: shadps4 [options]\n"
|
||||
"Options:\n"
|
||||
" No arguments: Opens the GUI.\n"
|
||||
" -g, --game <path|ID> Specify <eboot.bin or elf path> or "
|
||||
"<game ID (CUSAXXXXX)> to launch\n"
|
||||
" -- ... Parameters passed to the game ELF. "
|
||||
"Needs to be at the end of the line, and everything after \"--\" is a "
|
||||
"game argument.\n"
|
||||
" -p, --patch <patch_file> Apply specified patch file\n"
|
||||
" -i, --ignore-game-patch Disable automatic loading of game patch\n"
|
||||
" -s, --show-gui Show the GUI\n"
|
||||
" -f, --fullscreen <true|false> Specify window initial fullscreen "
|
||||
"state. Does not overwrite the config file.\n"
|
||||
" --add-game-folder <folder> Adds a new game folder to the config.\n"
|
||||
" -h, --help Display this help message\n";
|
||||
exit(0);
|
||||
}},
|
||||
{"--help", [&](int& i) { arg_map["-h"](i); }}, // Redirect --help to -h
|
||||
|
@ -84,6 +86,8 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
}},
|
||||
{"--patch", [&](int& i) { arg_map["-p"](i); }},
|
||||
{"-i", [&](int&) { Core::FileSys::MntPoints::ignore_game_patches = true; }},
|
||||
{"--ignore-game-patch", [&](int& i) { arg_map["-i"](i); }},
|
||||
{"-f",
|
||||
[&](int& i) {
|
||||
if (++i >= argc) {
|
||||
|
|
|
@ -32,6 +32,9 @@ MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent), ui(new Ui::MainWi
|
|||
ui->setupUi(this);
|
||||
installEventFilter(this);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
m_gui_settings = std::make_shared<gui_settings>();
|
||||
ui->toggleLabelsAct->setChecked(
|
||||
m_gui_settings->GetValue(gui::mw_showLabelsUnderIcons).toBool());
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow() {
|
||||
|
@ -139,7 +142,7 @@ void MainWindow::PauseGame() {
|
|||
|
||||
void MainWindow::toggleLabelsUnderIcons() {
|
||||
bool showLabels = ui->toggleLabelsAct->isChecked();
|
||||
Config::setShowLabelsUnderIcons();
|
||||
m_gui_settings->SetValue(gui::mw_showLabelsUnderIcons, showLabels);
|
||||
UpdateToolbarLabels();
|
||||
if (isGameRunning) {
|
||||
UpdateToolbarButtons();
|
||||
|
@ -290,21 +293,21 @@ void MainWindow::CreateDockWindows() {
|
|||
setCentralWidget(phCentralWidget);
|
||||
|
||||
m_dock_widget.reset(new QDockWidget(tr("Game List"), this));
|
||||
m_game_list_frame.reset(new GameListFrame(m_game_info, m_compat_info, this));
|
||||
m_game_list_frame.reset(new GameListFrame(m_gui_settings, m_game_info, m_compat_info, this));
|
||||
m_game_list_frame->setObjectName("gamelist");
|
||||
m_game_grid_frame.reset(new GameGridFrame(m_game_info, m_compat_info, this));
|
||||
m_game_grid_frame.reset(new GameGridFrame(m_gui_settings, m_game_info, m_compat_info, this));
|
||||
m_game_grid_frame->setObjectName("gamegridlist");
|
||||
m_elf_viewer.reset(new ElfViewer(this));
|
||||
m_elf_viewer->setObjectName("elflist");
|
||||
|
||||
int table_mode = Config::getTableMode();
|
||||
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
||||
int slider_pos = 0;
|
||||
if (table_mode == 0) { // List
|
||||
m_game_grid_frame->hide();
|
||||
m_elf_viewer->hide();
|
||||
m_game_list_frame->show();
|
||||
m_dock_widget->setWidget(m_game_list_frame.data());
|
||||
slider_pos = Config::getSliderPosition();
|
||||
slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt();
|
||||
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
|
||||
isTableList = true;
|
||||
} else if (table_mode == 1) { // Grid
|
||||
|
@ -312,7 +315,7 @@ void MainWindow::CreateDockWindows() {
|
|||
m_elf_viewer->hide();
|
||||
m_game_grid_frame->show();
|
||||
m_dock_widget->setWidget(m_game_grid_frame.data());
|
||||
slider_pos = Config::getSliderPositionGrid();
|
||||
slider_pos = m_gui_settings->GetValue(gui::gg_slider_pos).toInt();
|
||||
ui->sizeSlider->setSliderPosition(slider_pos); // set slider pos at start;
|
||||
isTableList = false;
|
||||
} else {
|
||||
|
@ -356,11 +359,11 @@ void MainWindow::LoadGameLists() {
|
|||
#ifdef ENABLE_UPDATER
|
||||
void MainWindow::CheckUpdateMain(bool checkSave) {
|
||||
if (checkSave) {
|
||||
if (!Config::autoUpdate()) {
|
||||
if (!m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
auto checkUpdate = new CheckUpdate(false);
|
||||
auto checkUpdate = new CheckUpdate(m_gui_settings, false);
|
||||
checkUpdate->exec();
|
||||
}
|
||||
#endif
|
||||
|
@ -380,13 +383,13 @@ void MainWindow::CreateConnects() {
|
|||
m_game_list_frame->icon_size =
|
||||
48 + value; // 48 is the minimum icon size to use due to text disappearing.
|
||||
m_game_list_frame->ResizeIcons(48 + value);
|
||||
Config::setIconSize(48 + value);
|
||||
Config::setSliderPosition(value);
|
||||
m_gui_settings->SetValue(gui::gl_icon_size, 48 + value);
|
||||
m_gui_settings->SetValue(gui::gl_slider_pos, value);
|
||||
} else {
|
||||
m_game_grid_frame->icon_size = 69 + value;
|
||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
Config::setIconSizeGrid(69 + value);
|
||||
Config::setSliderPositionGrid(value);
|
||||
m_gui_settings->SetValue(gui::gg_icon_size, 69 + value);
|
||||
m_gui_settings->SetValue(gui::gg_slider_pos, value);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -404,7 +407,7 @@ void MainWindow::CreateConnects() {
|
|||
&MainWindow::StartGame);
|
||||
|
||||
connect(ui->configureAct, &QAction::triggered, this, [this]() {
|
||||
auto settingsDialog = new SettingsDialog(m_compat_info, this);
|
||||
auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this);
|
||||
|
||||
connect(settingsDialog, &SettingsDialog::LanguageChanged, this,
|
||||
&MainWindow::OnLanguageChanged);
|
||||
|
@ -418,7 +421,8 @@ void MainWindow::CreateConnects() {
|
|||
|
||||
connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this,
|
||||
[this](int opacity) {
|
||||
Config::setBackgroundImageOpacity(opacity);
|
||||
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
|
||||
std::clamp(opacity, 0, 100));
|
||||
if (m_game_list_frame) {
|
||||
QTableWidgetItem* current = m_game_list_frame->GetCurrentItem();
|
||||
if (current) {
|
||||
|
@ -437,7 +441,7 @@ void MainWindow::CreateConnects() {
|
|||
});
|
||||
|
||||
connect(ui->settingsButton, &QPushButton::clicked, this, [this]() {
|
||||
auto settingsDialog = new SettingsDialog(m_compat_info, this);
|
||||
auto settingsDialog = new SettingsDialog(m_gui_settings, m_compat_info, this);
|
||||
|
||||
connect(settingsDialog, &SettingsDialog::LanguageChanged, this,
|
||||
&MainWindow::OnLanguageChanged);
|
||||
|
@ -451,7 +455,8 @@ void MainWindow::CreateConnects() {
|
|||
|
||||
connect(settingsDialog, &SettingsDialog::BackgroundOpacityChanged, this,
|
||||
[this](int opacity) {
|
||||
Config::setBackgroundImageOpacity(opacity);
|
||||
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
|
||||
std::clamp(opacity, 0, 100));
|
||||
if (m_game_list_frame) {
|
||||
QTableWidgetItem* current = m_game_list_frame->GetCurrentItem();
|
||||
if (current) {
|
||||
|
@ -481,7 +486,7 @@ void MainWindow::CreateConnects() {
|
|||
|
||||
#ifdef ENABLE_UPDATER
|
||||
connect(ui->updaterAct, &QAction::triggered, this, [this]() {
|
||||
auto checkUpdate = new CheckUpdate(true);
|
||||
auto checkUpdate = new CheckUpdate(m_gui_settings, true);
|
||||
checkUpdate->exec();
|
||||
});
|
||||
#endif
|
||||
|
@ -496,13 +501,13 @@ void MainWindow::CreateConnects() {
|
|||
m_game_list_frame->icon_size =
|
||||
36; // 36 is the minimum icon size to use due to text disappearing.
|
||||
ui->sizeSlider->setValue(0); // icone_size - 36
|
||||
Config::setIconSize(36);
|
||||
Config::setSliderPosition(0);
|
||||
m_gui_settings->SetValue(gui::gl_icon_size, 36);
|
||||
m_gui_settings->SetValue(gui::gl_slider_pos, 0);
|
||||
} else {
|
||||
m_game_grid_frame->icon_size = 69;
|
||||
ui->sizeSlider->setValue(0); // icone_size - 36
|
||||
Config::setIconSizeGrid(69);
|
||||
Config::setSliderPositionGrid(0);
|
||||
m_gui_settings->SetValue(gui::gg_icon_size, 69);
|
||||
m_gui_settings->SetValue(gui::gg_slider_pos, 9);
|
||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
}
|
||||
});
|
||||
|
@ -511,13 +516,13 @@ void MainWindow::CreateConnects() {
|
|||
if (isTableList) {
|
||||
m_game_list_frame->icon_size = 64;
|
||||
ui->sizeSlider->setValue(28);
|
||||
Config::setIconSize(64);
|
||||
Config::setSliderPosition(28);
|
||||
m_gui_settings->SetValue(gui::gl_icon_size, 64);
|
||||
m_gui_settings->SetValue(gui::gl_slider_pos, 28);
|
||||
} else {
|
||||
m_game_grid_frame->icon_size = 97;
|
||||
ui->sizeSlider->setValue(28);
|
||||
Config::setIconSizeGrid(97);
|
||||
Config::setSliderPositionGrid(28);
|
||||
m_gui_settings->SetValue(gui::gg_icon_size, 97);
|
||||
m_gui_settings->SetValue(gui::gg_slider_pos, 28);
|
||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
}
|
||||
});
|
||||
|
@ -526,13 +531,13 @@ void MainWindow::CreateConnects() {
|
|||
if (isTableList) {
|
||||
m_game_list_frame->icon_size = 128;
|
||||
ui->sizeSlider->setValue(92);
|
||||
Config::setIconSize(128);
|
||||
Config::setSliderPosition(92);
|
||||
m_gui_settings->SetValue(gui::gl_icon_size, 128);
|
||||
m_gui_settings->SetValue(gui::gl_slider_pos, 92);
|
||||
} else {
|
||||
m_game_grid_frame->icon_size = 161;
|
||||
ui->sizeSlider->setValue(92);
|
||||
Config::setIconSizeGrid(161);
|
||||
Config::setSliderPositionGrid(92);
|
||||
m_gui_settings->SetValue(gui::gg_icon_size, 161);
|
||||
m_gui_settings->SetValue(gui::gg_slider_pos, 92);
|
||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
}
|
||||
});
|
||||
|
@ -541,13 +546,13 @@ void MainWindow::CreateConnects() {
|
|||
if (isTableList) {
|
||||
m_game_list_frame->icon_size = 256;
|
||||
ui->sizeSlider->setValue(220);
|
||||
Config::setIconSize(256);
|
||||
Config::setSliderPosition(220);
|
||||
m_gui_settings->SetValue(gui::gl_icon_size, 256);
|
||||
m_gui_settings->SetValue(gui::gl_slider_pos, 220);
|
||||
} else {
|
||||
m_game_grid_frame->icon_size = 256;
|
||||
ui->sizeSlider->setValue(220);
|
||||
Config::setIconSizeGrid(256);
|
||||
Config::setSliderPositionGrid(220);
|
||||
m_gui_settings->SetValue(gui::gg_icon_size, 256);
|
||||
m_gui_settings->SetValue(gui::gg_slider_pos, 220);
|
||||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
}
|
||||
});
|
||||
|
@ -563,8 +568,8 @@ void MainWindow::CreateConnects() {
|
|||
m_game_list_frame->PopulateGameList();
|
||||
}
|
||||
isTableList = true;
|
||||
Config::setTableMode(0);
|
||||
int slider_pos = Config::getSliderPosition();
|
||||
m_gui_settings->SetValue(gui::gl_mode, 0);
|
||||
int slider_pos = m_gui_settings->GetValue(gui::gl_slider_pos).toInt();
|
||||
ui->sizeSlider->setEnabled(true);
|
||||
ui->sizeSlider->setSliderPosition(slider_pos);
|
||||
ui->mw_searchbar->setText("");
|
||||
|
@ -582,8 +587,8 @@ void MainWindow::CreateConnects() {
|
|||
m_game_grid_frame->PopulateGameGrid(m_game_info->m_games, false);
|
||||
}
|
||||
isTableList = false;
|
||||
Config::setTableMode(1);
|
||||
int slider_pos_grid = Config::getSliderPositionGrid();
|
||||
m_gui_settings->SetValue(gui::gl_mode, 1);
|
||||
int slider_pos_grid = m_gui_settings->GetValue(gui::gg_slider_pos).toInt();
|
||||
ui->sizeSlider->setEnabled(true);
|
||||
ui->sizeSlider->setSliderPosition(slider_pos_grid);
|
||||
ui->mw_searchbar->setText("");
|
||||
|
@ -598,7 +603,7 @@ void MainWindow::CreateConnects() {
|
|||
m_elf_viewer->show();
|
||||
isTableList = false;
|
||||
ui->sizeSlider->setDisabled(true);
|
||||
Config::setTableMode(2);
|
||||
m_gui_settings->SetValue(gui::gl_mode, 2);
|
||||
SetLastIconSizeBullet();
|
||||
});
|
||||
|
||||
|
@ -840,7 +845,7 @@ void MainWindow::CreateConnects() {
|
|||
void MainWindow::StartGame() {
|
||||
BackgroundMusicPlayer::getInstance().stopMusic();
|
||||
QString gamePath = "";
|
||||
int table_mode = Config::getTableMode();
|
||||
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
||||
if (table_mode == 0) {
|
||||
if (m_game_list_frame->currentItem()) {
|
||||
int itemID = m_game_list_frame->currentItem()->row();
|
||||
|
@ -925,25 +930,25 @@ void MainWindow::RefreshGameTable() {
|
|||
}
|
||||
|
||||
void MainWindow::ConfigureGuiFromSettings() {
|
||||
setGeometry(Config::getMainWindowGeometryX(), Config::getMainWindowGeometryY(),
|
||||
Config::getMainWindowGeometryW(), Config::getMainWindowGeometryH());
|
||||
|
||||
if (!restoreGeometry(m_gui_settings->GetValue(gui::mw_geometry).toByteArray())) {
|
||||
// By default, set the window to 70% of the screen
|
||||
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
|
||||
}
|
||||
ui->showGameListAct->setChecked(true);
|
||||
if (Config::getTableMode() == 0) {
|
||||
int table_mode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
||||
if (table_mode == 0) {
|
||||
ui->setlistModeListAct->setChecked(true);
|
||||
} else if (Config::getTableMode() == 1) {
|
||||
} else if (table_mode == 1) {
|
||||
ui->setlistModeGridAct->setChecked(true);
|
||||
} else if (Config::getTableMode() == 2) {
|
||||
} else if (table_mode == 2) {
|
||||
ui->setlistElfAct->setChecked(true);
|
||||
}
|
||||
BackgroundMusicPlayer::getInstance().setVolume(Config::getBGMvolume());
|
||||
BackgroundMusicPlayer::getInstance().setVolume(
|
||||
m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt());
|
||||
}
|
||||
|
||||
void MainWindow::SaveWindowState() const {
|
||||
Config::setMainWindowWidth(this->width());
|
||||
Config::setMainWindowHeight(this->height());
|
||||
Config::setMainWindowGeometry(this->geometry().x(), this->geometry().y(),
|
||||
this->geometry().width(), this->geometry().height());
|
||||
void MainWindow::SaveWindowState() {
|
||||
m_gui_settings->SetValue(gui::mw_geometry, saveGeometry(), false);
|
||||
}
|
||||
|
||||
void MainWindow::BootGame() {
|
||||
|
@ -1024,8 +1029,8 @@ void MainWindow::SetLastUsedTheme() {
|
|||
|
||||
void MainWindow::SetLastIconSizeBullet() {
|
||||
// set QAction bullet point if applicable
|
||||
int lastSize = Config::getIconSize();
|
||||
int lastSizeGrid = Config::getIconSizeGrid();
|
||||
int lastSize = m_gui_settings->GetValue(gui::gl_icon_size).toInt();
|
||||
int lastSizeGrid = m_gui_settings->GetValue(gui::gg_icon_size).toInt();
|
||||
if (isTableList) {
|
||||
switch (lastSize) {
|
||||
case 36:
|
||||
|
@ -1195,7 +1200,7 @@ bool MainWindow::eventFilter(QObject* obj, QEvent* event) {
|
|||
if (event->type() == QEvent::KeyPress) {
|
||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||
if (keyEvent->key() == Qt::Key_Enter || keyEvent->key() == Qt::Key_Return) {
|
||||
auto tblMode = Config::getTableMode();
|
||||
auto tblMode = m_gui_settings->GetValue(gui::gl_mode).toInt();
|
||||
if (tblMode != 2 && (tblMode != 1 || m_game_grid_frame->IsValidCellSelected())) {
|
||||
StartGame();
|
||||
return true;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "game_info.h"
|
||||
#include "game_list_frame.h"
|
||||
#include "game_list_utils.h"
|
||||
#include "gui_settings.h"
|
||||
#include "main_window_themes.h"
|
||||
#include "main_window_ui.h"
|
||||
|
||||
|
@ -41,7 +42,7 @@ public:
|
|||
|
||||
private Q_SLOTS:
|
||||
void ConfigureGuiFromSettings();
|
||||
void SaveWindowState() const;
|
||||
void SaveWindowState();
|
||||
void SearchGameTable(const QString& text);
|
||||
void ShowGameList();
|
||||
void RefreshGameTable();
|
||||
|
@ -102,6 +103,7 @@ private:
|
|||
std::make_shared<CompatibilityInfoClass>();
|
||||
|
||||
QTranslator* translator;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||
|
|
|
@ -107,7 +107,6 @@ public:
|
|||
toggleLabelsAct = new QAction(MainWindow);
|
||||
toggleLabelsAct->setObjectName("toggleLabelsAct");
|
||||
toggleLabelsAct->setCheckable(true);
|
||||
toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons());
|
||||
|
||||
setIconSizeTinyAct = new QAction(MainWindow);
|
||||
setIconSizeTinyAct->setObjectName("setIconSizeTinyAct");
|
||||
|
|
77
src/qt_gui/settings.cpp
Normal file
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;
|
||||
|
||||
SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
||||
SettingsDialog::SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
||||
QWidget* parent)
|
||||
: QDialog(parent), ui(new Ui::SettingsDialog) {
|
||||
: QDialog(parent), ui(new Ui::SettingsDialog), m_gui_settings(std::move(gui_settings)) {
|
||||
ui->setupUi(this);
|
||||
ui->tabWidgetSettings->setUsesScrollButtons(false);
|
||||
|
||||
|
@ -147,6 +148,7 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
|
|||
Config::save(config_dir / "config.toml");
|
||||
} else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
|
||||
Config::setDefaultValues();
|
||||
setDefaultValues();
|
||||
Config::save(config_dir / "config.toml");
|
||||
LoadValuesFromConfig();
|
||||
} else if (button == ui->buttonBox->button(QDialogButtonBox::Close)) {
|
||||
|
@ -175,28 +177,34 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
|
|||
{
|
||||
#ifdef ENABLE_UPDATER
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
|
||||
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int state) { Config::setAutoUpdate(state == Qt::Checked); });
|
||||
connect(ui->updateCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked);
|
||||
});
|
||||
|
||||
connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this,
|
||||
[](int state) { Config::setAlwaysShowChangelog(state == Qt::Checked); });
|
||||
connect(ui->changelogCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked);
|
||||
});
|
||||
#else
|
||||
connect(ui->updateCheckBox, &QCheckBox::checkStateChanged, this,
|
||||
[](Qt::CheckState state) { Config::setAutoUpdate(state == Qt::Checked); });
|
||||
[this](Qt::CheckState state) {
|
||||
m_gui_settings->SetValue(gui::gen_checkForUpdates, state == Qt::Checked);
|
||||
});
|
||||
|
||||
connect(ui->changelogCheckBox, &QCheckBox::checkStateChanged, this,
|
||||
[](Qt::CheckState state) { Config::setAlwaysShowChangelog(state == Qt::Checked); });
|
||||
[this](Qt::CheckState state) {
|
||||
m_gui_settings->SetValue(gui::gen_showChangeLog, state == Qt::Checked);
|
||||
});
|
||||
#endif
|
||||
|
||||
connect(ui->updateComboBox, &QComboBox::currentTextChanged, this,
|
||||
[this](const QString& channel) {
|
||||
if (channelMap.contains(channel)) {
|
||||
Config::setUpdateChannel(channelMap.value(channel).toStdString());
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel, channelMap.value(channel));
|
||||
}
|
||||
});
|
||||
|
||||
connect(ui->checkUpdateButton, &QPushButton::clicked, this, []() {
|
||||
auto checkUpdate = new CheckUpdate(true);
|
||||
connect(ui->checkUpdateButton, &QPushButton::clicked, this, [this]() {
|
||||
auto checkUpdate = new CheckUpdate(m_gui_settings, true);
|
||||
checkUpdate->exec();
|
||||
});
|
||||
#else
|
||||
|
@ -235,12 +243,12 @@ SettingsDialog::SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_
|
|||
[](const QString& hometab) { Config::setChooseHomeTab(hometab.toStdString()); });
|
||||
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 7, 0))
|
||||
connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [](int state) {
|
||||
connect(ui->showBackgroundImageCheckBox, &QCheckBox::stateChanged, this, [this](int state) {
|
||||
#else
|
||||
connect(ui->showBackgroundImageCheckBox, &QCheckBox::checkStateChanged, this,
|
||||
[](Qt::CheckState state) {
|
||||
[this](Qt::CheckState state) {
|
||||
#endif
|
||||
Config::setShowBackgroundImage(state == Qt::Checked);
|
||||
m_gui_settings->SetValue(gui::gl_showBackgroundImage, state == Qt::Checked);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -448,7 +456,7 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||
ui->dumpShadersCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "dumpShaders", false));
|
||||
ui->nullGpuCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "nullGpu", false));
|
||||
ui->enableHDRCheckBox->setChecked(toml::find_or<bool>(data, "GPU", "allowHDR", false));
|
||||
ui->playBGMCheckBox->setChecked(toml::find_or<bool>(data, "General", "playBGM", false));
|
||||
ui->playBGMCheckBox->setChecked(m_gui_settings->GetValue(gui::gl_playBackgroundMusic).toBool());
|
||||
ui->disableTrophycheckBox->setChecked(
|
||||
toml::find_or<bool>(data, "General", "isTrophyPopupDisabled", false));
|
||||
ui->popUpDurationSpinBox->setValue(Config::getTrophyNotificationDuration());
|
||||
|
@ -460,7 +468,7 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||
ui->radioButton_Top->setChecked(side == "top");
|
||||
ui->radioButton_Bottom->setChecked(side == "bottom");
|
||||
|
||||
ui->BGMVolumeSlider->setValue(toml::find_or<int>(data, "General", "BGMvolume", 50));
|
||||
ui->BGMVolumeSlider->setValue(m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt());
|
||||
ui->discordRPCCheckbox->setChecked(
|
||||
toml::find_or<bool>(data, "General", "enableDiscordRPC", true));
|
||||
QString translatedText_FullscreenMode =
|
||||
|
@ -501,11 +509,10 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||
toml::find_or<bool>(data, "General", "checkCompatibilityOnStartup", false));
|
||||
|
||||
#ifdef ENABLE_UPDATER
|
||||
ui->updateCheckBox->setChecked(toml::find_or<bool>(data, "General", "autoUpdate", false));
|
||||
ui->changelogCheckBox->setChecked(
|
||||
toml::find_or<bool>(data, "General", "alwaysShowChangelog", false));
|
||||
ui->updateCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_checkForUpdates).toBool());
|
||||
ui->changelogCheckBox->setChecked(m_gui_settings->GetValue(gui::gen_showChangeLog).toBool());
|
||||
|
||||
QString updateChannel = QString::fromStdString(Config::getUpdateChannel());
|
||||
QString updateChannel = m_gui_settings->GetValue(gui::gen_updateChannel).toString();
|
||||
ui->updateComboBox->setCurrentText(
|
||||
channelMap.key(updateChannel != "Release" && updateChannel != "Nightly"
|
||||
? (Common::g_is_release ? "Release" : "Nightly")
|
||||
|
@ -536,11 +543,14 @@ void SettingsDialog::LoadValuesFromConfig() {
|
|||
|
||||
ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty());
|
||||
ResetInstallFolders();
|
||||
ui->backgroundImageOpacitySlider->setValue(Config::getBackgroundImageOpacity());
|
||||
ui->showBackgroundImageCheckBox->setChecked(Config::getShowBackgroundImage());
|
||||
ui->backgroundImageOpacitySlider->setValue(
|
||||
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt());
|
||||
ui->showBackgroundImageCheckBox->setChecked(
|
||||
m_gui_settings->GetValue(gui::gl_showBackgroundImage).toBool());
|
||||
|
||||
backgroundImageOpacitySlider_backup = Config::getBackgroundImageOpacity();
|
||||
bgm_volume_backup = Config::getBGMvolume();
|
||||
backgroundImageOpacitySlider_backup =
|
||||
m_gui_settings->GetValue(gui::gl_backgroundImageOpacity).toInt();
|
||||
bgm_volume_backup = m_gui_settings->GetValue(gui::gl_backgroundMusicVolume).toInt();
|
||||
}
|
||||
|
||||
void SettingsDialog::InitializeEmulatorLanguages() {
|
||||
|
@ -754,8 +764,7 @@ void SettingsDialog::UpdateSettings() {
|
|||
} else if (ui->radioButton_Bottom->isChecked()) {
|
||||
Config::setSideTrophy("bottom");
|
||||
}
|
||||
|
||||
Config::setPlayBGM(ui->playBGMCheckBox->isChecked());
|
||||
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, ui->playBGMCheckBox->isChecked());
|
||||
Config::setAllowHDR(ui->enableHDRCheckBox->isChecked());
|
||||
Config::setLogType(logTypeMap.value(ui->logTypeComboBox->currentText()).toStdString());
|
||||
Config::setLogFilter(ui->logFilterLineEdit->text().toStdString());
|
||||
|
@ -764,7 +773,7 @@ void SettingsDialog::UpdateSettings() {
|
|||
Config::setCursorState(ui->hideCursorComboBox->currentIndex());
|
||||
Config::setCursorHideTimeout(ui->idleTimeoutSpinBox->value());
|
||||
Config::setGpuId(ui->graphicsAdapterBox->currentIndex() - 1);
|
||||
Config::setBGMvolume(ui->BGMVolumeSlider->value());
|
||||
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, ui->BGMVolumeSlider->value());
|
||||
Config::setLanguage(languageIndexes[ui->consoleLanguageComboBox->currentIndex()]);
|
||||
Config::setEnableDiscordRPC(ui->discordRPCCheckbox->isChecked());
|
||||
Config::setScreenWidth(ui->widthSpinBox->value());
|
||||
|
@ -784,16 +793,19 @@ void SettingsDialog::UpdateSettings() {
|
|||
Config::setVkCrashDiagnosticEnabled(ui->crashDiagnosticsCheckBox->isChecked());
|
||||
Config::setCollectShaderForDebug(ui->collectShaderCheckBox->isChecked());
|
||||
Config::setCopyGPUCmdBuffers(ui->copyGPUBuffersCheckBox->isChecked());
|
||||
Config::setAutoUpdate(ui->updateCheckBox->isChecked());
|
||||
Config::setAlwaysShowChangelog(ui->changelogCheckBox->isChecked());
|
||||
Config::setUpdateChannel(channelMap.value(ui->updateComboBox->currentText()).toStdString());
|
||||
m_gui_settings->SetValue(gui::gen_checkForUpdates, ui->updateCheckBox->isChecked());
|
||||
m_gui_settings->SetValue(gui::gen_showChangeLog, ui->changelogCheckBox->isChecked());
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel,
|
||||
channelMap.value(ui->updateComboBox->currentText()));
|
||||
Config::setChooseHomeTab(
|
||||
chooseHomeTabMap.value(ui->chooseHomeTabComboBox->currentText()).toStdString());
|
||||
Config::setCompatibilityEnabled(ui->enableCompatibilityCheckBox->isChecked());
|
||||
Config::setCheckCompatibilityOnStartup(ui->checkCompatibilityOnStartupCheckBox->isChecked());
|
||||
Config::setBackgroundImageOpacity(ui->backgroundImageOpacitySlider->value());
|
||||
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity,
|
||||
std::clamp(ui->backgroundImageOpacitySlider->value(), 0, 100));
|
||||
emit BackgroundOpacityChanged(ui->backgroundImageOpacitySlider->value());
|
||||
Config::setShowBackgroundImage(ui->showBackgroundImageCheckBox->isChecked());
|
||||
m_gui_settings->SetValue(gui::gl_showBackgroundImage,
|
||||
ui->showBackgroundImageCheckBox->isChecked());
|
||||
|
||||
std::vector<Config::GameInstallDir> dirs_with_states;
|
||||
for (int i = 0; i < ui->gameFoldersListWidget->count(); i++) {
|
||||
|
@ -862,3 +874,16 @@ void SettingsDialog::ResetInstallFolders() {
|
|||
Config::setAllGameInstallDirs(settings_install_dirs_config);
|
||||
}
|
||||
}
|
||||
void SettingsDialog::setDefaultValues() {
|
||||
m_gui_settings->SetValue(gui::gl_showBackgroundImage, true);
|
||||
m_gui_settings->SetValue(gui::gl_backgroundImageOpacity, 50);
|
||||
m_gui_settings->SetValue(gui::gl_playBackgroundMusic, false);
|
||||
m_gui_settings->SetValue(gui::gl_backgroundMusicVolume, 50);
|
||||
m_gui_settings->SetValue(gui::gen_checkForUpdates, false);
|
||||
m_gui_settings->SetValue(gui::gen_showChangeLog, false);
|
||||
if (Common::g_is_release) {
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel, "Release");
|
||||
} else {
|
||||
m_gui_settings->SetValue(gui::gen_updateChannel, "Nightly");
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include "common/config.h"
|
||||
#include "common/path_util.h"
|
||||
#include "gui_settings.h"
|
||||
#include "qt_gui/compatibility_info.h"
|
||||
|
||||
namespace Ui {
|
||||
|
@ -20,7 +21,8 @@ class SettingsDialog;
|
|||
class SettingsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit SettingsDialog(std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
||||
explicit SettingsDialog(std::shared_ptr<gui_settings> gui_settings,
|
||||
std::shared_ptr<CompatibilityInfoClass> m_compat_info,
|
||||
QWidget* parent = nullptr);
|
||||
~SettingsDialog();
|
||||
|
||||
|
@ -42,6 +44,7 @@ private:
|
|||
void OnLanguageChanged(int index);
|
||||
void OnCursorStateChanged(s16 index);
|
||||
void closeEvent(QCloseEvent* event) override;
|
||||
void setDefaultValues();
|
||||
|
||||
std::unique_ptr<Ui::SettingsDialog> ui;
|
||||
|
||||
|
@ -52,4 +55,5 @@ private:
|
|||
int initialHeight;
|
||||
|
||||
bool is_saving = false;
|
||||
std::shared_ptr<gui_settings> m_gui_settings;
|
||||
};
|
||||
|
|
|
@ -2049,7 +2049,7 @@ Nightly: نُسخ تحتوي على أحدث الميزات، لكنها أقل
|
|||
</message>
|
||||
<message>
|
||||
<source> * Unsupported Vulkan Version</source>
|
||||
<translation type="unfinished"> * Unsupported Vulkan Version</translation>
|
||||
<translation>نسخ Vulkan غير مدعومة</translation>
|
||||
</message>
|
||||
</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>
|
||||
<source>Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n</source>
|
||||
<translation type="unfinished">Cheats/Patches are experimental.\nUse with caution.\n\nDownload cheats individually by selecting the repository and clicking the download button.\nIn the Patches tab, you can download all patches at once, choose which ones you want to use, and save your selection.\n\nSince we do not develop the Cheats/Patches,\nplease report issues to the cheat author.\n\nCreated a new cheat? Visit:\n</translation>
|
||||
<translation>تقلبها/پچها آزمایشی هستند.\n با احتیاط استفاده کنید.\n\n با انتخاب مخزن و کلیک روی دکمه دانلود، تقلبها را بهصورت جداگانه دانلود کنید.\n در تب پچها، میتوانید همه پچها را بهطور همزمان دانلود کنید، انتخاب کنید که میخواهید از کدام استفاده کنید و انتخاب خود را ذخیره کنید.\n\n از آنجایی که ما تقلبها/پچها را توسعه نمیدهیم،\n لطفاً مشکلات را به نویسنده تقلب گزارش دهید.\n\n تقلب جدیدی ایجاد کردهاید؟ به این صفحه مراجعه کنید: \n</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No Image Available</source>
|
||||
|
@ -214,7 +214,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>XML ERROR:</source>
|
||||
<translation type="unfinished">XML ERROR:</translation>
|
||||
<translation>XML خطای :</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Failed to open files.json for writing</source>
|
||||
|
@ -407,43 +407,43 @@
|
|||
<name>ControlSettings</name>
|
||||
<message>
|
||||
<source>Configure Controls</source>
|
||||
<translation type="unfinished">Configure Controls</translation>
|
||||
<translation>پیکربندی دسته ها</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>D-Pad</source>
|
||||
<translation type="unfinished">D-Pad</translation>
|
||||
<translation>D-Pad</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Up</source>
|
||||
<translation type="unfinished">Up</translation>
|
||||
<translation>بالا</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Left</source>
|
||||
<translation type="unfinished">Left</translation>
|
||||
<translation>چپ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Right</source>
|
||||
<translation type="unfinished">Right</translation>
|
||||
<translation>راست</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Down</source>
|
||||
<translation type="unfinished">Down</translation>
|
||||
<translation>پایین</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Left Stick Deadzone (def:2 max:127)</source>
|
||||
<translation type="unfinished">Left Stick Deadzone (def:2 max:127)</translation>
|
||||
<translation>منطقهی حساس به حرکت چپ (def:2 max:127)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Left Deadzone</source>
|
||||
<translation type="unfinished">Left Deadzone</translation>
|
||||
<translation>منطقه مرده چپ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Left Stick</source>
|
||||
<translation type="unfinished">Left Stick</translation>
|
||||
<translation>جواستیک چپ</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Config Selection</source>
|
||||
<translation type="unfinished">Config Selection</translation>
|
||||
<translation>انتخاب پیکربندی</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Common Config</source>
|
||||
|
@ -451,7 +451,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Use per-game configs</source>
|
||||
<translation type="unfinished">Use per-game configs</translation>
|
||||
<translation>از پیکربندیهای مخصوص هر بازی استفاده کنید</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>L1 / LB</source>
|
||||
|
@ -483,7 +483,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>R3</source>
|
||||
<translation type="unfinished">R3</translation>
|
||||
<translation>R3</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Face Buttons</source>
|
||||
|
@ -491,7 +491,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Triangle / Y</source>
|
||||
<translation type="unfinished">Triangle / Y</translation>
|
||||
<translation>مثلث / Y</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Square / X</source>
|
||||
|
@ -531,7 +531,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>B:</source>
|
||||
<translation type="unfinished">B:</translation>
|
||||
<translation>B:</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Override Lightbar Color</source>
|
||||
|
@ -543,7 +543,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Unable to Save</source>
|
||||
<translation type="unfinished">Unable to Save</translation>
|
||||
<translation>ذخیره امکان پذیر نیست</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Cannot bind axis values more than once</source>
|
||||
|
@ -570,7 +570,7 @@
|
|||
<name>EditorDialog</name>
|
||||
<message>
|
||||
<source>Edit Keyboard + Mouse and Controller input bindings</source>
|
||||
<translation type="unfinished">Edit Keyboard + Mouse and Controller input bindings</translation>
|
||||
<translation>تغییر دکمه های کیبرد + ماوس و دسته</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Use Per-Game configs</source>
|
||||
|
@ -582,7 +582,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Could not open the file for reading</source>
|
||||
<translation type="unfinished">Could not open the file for reading</translation>
|
||||
<translation>نمی تواند فایل را برای خواندن باز کند</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Could not open the file for writing</source>
|
||||
|
@ -602,7 +602,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Do you want to reset your custom default config to the original default config?</source>
|
||||
<translation type="unfinished">Do you want to reset your custom default config to the original default config?</translation>
|
||||
<translation>آیا میخواهید پیکربندی سفارشی خود را به پیکربندی پیشفرض اصلی بازگردانید ؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you want to reset this config to your custom default config?</source>
|
||||
|
@ -860,7 +860,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>View report</source>
|
||||
<translation type="unfinished">View report</translation>
|
||||
<translation>مشاهده گزارش</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Submit a report</source>
|
||||
|
@ -916,11 +916,11 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Delete Save Data</source>
|
||||
<translation type="unfinished">Delete Save Data</translation>
|
||||
<translation>پاک کردن داده های ذخیره شده</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This game has no update folder to open!</source>
|
||||
<translation type="unfinished">This game has no update folder to open!</translation>
|
||||
<translation>این بازی هیچ پوشهی بهروزرسانی برای باز کردن ندارد!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>No log file found for this game!</source>
|
||||
|
@ -948,7 +948,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>SFO Viewer for </source>
|
||||
<translation type="unfinished">SFO Viewer for </translation>
|
||||
<translation>SFO مشاهده </translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
|
@ -986,7 +986,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Up</source>
|
||||
<translation type="unfinished">Up</translation>
|
||||
<translation/>
|
||||
</message>
|
||||
<message>
|
||||
<source>unmapped</source>
|
||||
|
@ -1058,7 +1058,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Touchpad Click</source>
|
||||
<translation type="unfinished">Touchpad Click</translation>
|
||||
<translation>کلیک روی تاچپد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Mouse to Joystick</source>
|
||||
|
@ -1078,7 +1078,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Mouse Movement Parameters</source>
|
||||
<translation type="unfinished">Mouse Movement Parameters</translation>
|
||||
<translation/>
|
||||
</message>
|
||||
<message>
|
||||
<source>note: click Help Button/Special Keybindings for more information</source>
|
||||
|
@ -1102,7 +1102,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Cross</source>
|
||||
<translation type="unfinished">Cross</translation>
|
||||
<translation>ضربدر</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Right Analog Halfmode</source>
|
||||
|
@ -1122,7 +1122,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Copy from Common Config</source>
|
||||
<translation type="unfinished">Copy from Common Config</translation>
|
||||
<translation>کپی از پیکربندی مشترک</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Deadzone Offset (def 0.50):</source>
|
||||
|
@ -1130,23 +1130,23 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Speed Multiplier (def 1.0):</source>
|
||||
<translation type="unfinished">Speed Multiplier (def 1.0):</translation>
|
||||
<translation>ضریب سرعت (def 1.0):</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Common Config Selected</source>
|
||||
<translation type="unfinished">Common Config Selected</translation>
|
||||
<translation>پیکربندی مشترک انتخاب شده</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config.</source>
|
||||
<translation type="unfinished">This button copies mappings from the Common Config to the currently selected profile, and cannot be used when the currently selected profile is the Common Config.</translation>
|
||||
<translation>این دکمه نگاشتها را از پیکربندی مشترک به پروفایل انتخابشدهی فعلی کپی میکند و وقتی پروفایل انتخابشدهی فعلی پیکربندی مشترک باشد، نمیتوان از آن استفاده کرد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy values from Common Config</source>
|
||||
<translation type="unfinished">Copy values from Common Config</translation>
|
||||
<translation>کپی کردن مقادیر از پیکربندی مشترک</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Do you want to overwrite existing mappings with the mappings from the Common Config?</source>
|
||||
<translation type="unfinished">Do you want to overwrite existing mappings with the mappings from the Common Config?</translation>
|
||||
<translation>آیا میخواهید نگاشتهای موجود را با نگاشتهای پیکربندی مشترک جایگزین کنید؟</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Unable to Save</source>
|
||||
|
@ -1170,7 +1170,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Save</source>
|
||||
<translation type="unfinished">Save</translation>
|
||||
<translation>ذخیرهسازی</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Apply</source>
|
||||
|
@ -1213,7 +1213,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Open shadPS4 Folder</source>
|
||||
<translation type="unfinished">Open shadPS4 Folder</translation>
|
||||
<translation>پوشه shadPS4 را باز کنید</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Exit</source>
|
||||
|
@ -1624,7 +1624,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Collect Shaders</source>
|
||||
<translation type="unfinished">Collect Shaders</translation>
|
||||
<translation>جمع آوری شیدرها</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Copy GPU Buffers</source>
|
||||
|
@ -1664,7 +1664,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Title Music</source>
|
||||
<translation type="unfinished">Title Music</translation>
|
||||
<translation/>
|
||||
</message>
|
||||
<message>
|
||||
<source>Disable Trophy Notification</source>
|
||||
|
@ -1728,7 +1728,7 @@
|
|||
</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>
|
||||
<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>
|
||||
<source>Emulator Language:\nSets the language of the emulator's user interface.</source>
|
||||
|
@ -1748,7 +1748,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</source>
|
||||
<translation type="unfinished">Trophy Key:\nKey used to decrypt trophies. Must be obtained from your jailbroken console.\nMust contain only hex characters.</translation>
|
||||
<translation>کلید تروفی:\و کلیدی که برای رمزگشایی تروفیها استفاده میشود. باید از کنسول جیلبریک شده شما دریافت شود.\باید فقط شامل کاراکترهای هگز باشد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Log Type:\nSets whether to synchronize the output of the log window for performance. May have adverse effects on emulation.</source>
|
||||
|
@ -1756,7 +1756,7 @@
|
|||
</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>
|
||||
<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>
|
||||
<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>
|
||||
<source>Background Image:\nControl the opacity of the game background image.</source>
|
||||
<translation type="unfinished">Background Image:\nControl the opacity of the game background image.</translation>
|
||||
<translation>تصویر پسزمینه: میزان شفافیت تصویر پسزمینه بازی را کنترل کنید.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</source>
|
||||
|
@ -1844,11 +1844,11 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</source>
|
||||
<translation type="unfinished">Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation>
|
||||
<translation>فعال کردن پردازنده گرافیکی خالی:\برای رفع اشکال فنی، رندر بازی را طوری غیرفعال کنید که انگار هیچ کارت گرافیکی وجود ندارد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format.</source>
|
||||
<translation type="unfinished">Enable HDR:\nEnables HDR in games that support it.\nYour monitor must have support for the BT2020 PQ color space and the RGB10A2 swapchain format.</translation>
|
||||
<translation>فعال کردن HDR و :\n این گزینه HDR را در بازیهایی که از آن پشتیبانی میکنند فعال میکند.\n مانیتور شما باید از فضای رنگی BT2020 PQ و فرمت swapchain RGB10A2 پشتیبانی کند.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Game Folders:\nThe list of folders to check for installed games.</source>
|
||||
|
@ -1860,7 +1860,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Remove:\nRemove a folder from the list.</source>
|
||||
<translation>حذف:\nیک پوشه را از لیست حذف کنید.</translation>
|
||||
<translation>حذف:\n یک پوشه را از لیست حذف کنید.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Debug Dumping:\nSaves the import and export symbols and file header information of the currently running PS4 program to a directory.</source>
|
||||
|
@ -1868,11 +1868,11 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</source>
|
||||
<translation type="unfinished">Enable Vulkan Validation Layers:\nEnables a system that validates the state of the Vulkan renderer and logs information about its internal state.\nThis will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>فعال کردن لایههای اعتبارسنجی Vulkan: \nسیستمی را فعال میکند که وضعیت رندرکننده Vulkan را اعتبارسنجی کرده و اطلاعات مربوط به وضعیت داخلی آن را ثبت میکند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیهسازی میشود.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</source>
|
||||
<translation type="unfinished">Enable Vulkan Synchronization Validation:\nEnables a system that validates the timing of Vulkan rendering tasks.\nThis will reduce performance and likely change the behavior of emulation.</translation>
|
||||
<translation>فعال کردن اعتبارسنجی همگامسازی Vulkan: \nسیستمی را فعال میکند که زمانبندی وظایف رندر Vulkan را اعتبارسنجی میکند.\n این کار باعث کاهش عملکرد و احتمالاً تغییر رفتار شبیهسازی میشود.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Enable RenderDoc Debugging:\nIf enabled, the emulator will provide compatibility with Renderdoc to allow capture and analysis of the currently rendered frame.</source>
|
||||
|
@ -1880,7 +1880,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</source>
|
||||
<translation type="unfinished">Collect Shaders:\nYou need this enabled to edit shaders with the debug menu (Ctrl + F10).</translation>
|
||||
<translation>جمعآوری سایهزنها:\n برای ویرایش سایهزنها با منوی اشکالزدایی (Ctrl + F10) باید این گزینه فعال باشد.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Crash Diagnostics:\nCreates a .yaml file with info about the Vulkan state at the time of crashing.\nUseful for debugging '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>
|
||||
<source>Nightly</source>
|
||||
<translation type="unfinished">Nightly</translation>
|
||||
<translation>اخرین نسخه شبانه</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Set the volume of the background music.</source>
|
||||
|
@ -1936,7 +1936,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>sync</source>
|
||||
<translation type="unfinished">sync</translation>
|
||||
<translation>همزمان</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Auto Select</source>
|
||||
|
@ -2000,7 +2000,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Right</source>
|
||||
<translation type="unfinished">Right</translation>
|
||||
<translation>راست</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Top</source>
|
||||
|
@ -2032,7 +2032,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>%1 already exists</source>
|
||||
<translation type="unfinished">%1 already exists</translation>
|
||||
<translation>%1 از قبل وجود دارد</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Portable user folder created</source>
|
||||
|
@ -2044,7 +2044,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions.</source>
|
||||
<translation type="unfinished">Open the custom trophy images/sounds folder:\nYou can add custom images to the trophies and an audio.\nAdd the files to custom_trophy with the following names:\ntrophy.wav OR trophy.mp3, bronze.png, gold.png, platinum.png, silver.png\nNote: The sound will only work in QT versions.</translation>
|
||||
<translation>پوشه تصاویر/صداهای تروفی سفارشی را باز کنید:\n شما میتوانید تصاویر و صدای سفارشی به تروفیها اضافه کنید.\n فایلها را با نامهای زیر به custom_trophy اضافه کنید:\ntrophy.wav یا trophy.mp3، bronze.png، gold.png، platinum.png، silver.png \nتوجه: صدا فقط در نسخههای QT کار میکند.</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source> * Unsupported Vulkan Version</source>
|
||||
|
@ -2075,7 +2075,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Show Hidden Trophies</source>
|
||||
<translation type="unfinished">Show Hidden Trophies</translation>
|
||||
<translation>نمایش جوایز مخفی</translation>
|
||||
</message>
|
||||
</context>
|
||||
</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.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) {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/div_ceil.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||
|
||||
|
@ -15,42 +17,40 @@ std::pair<Id, Id> AtomicArgs(EmitContext& ctx) {
|
|||
Id SharedAtomicU32(EmitContext& ctx, Id offset, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
const Id shift_id{ctx.ConstU32(2U)};
|
||||
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
|
||||
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)};
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
|
||||
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics, value);
|
||||
});
|
||||
}
|
||||
|
||||
Id SharedAtomicU32_IncDec(EmitContext& ctx, Id offset,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
|
||||
Id SharedAtomicU32IncDec(EmitContext& ctx, Id offset,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
|
||||
const Id shift_id{ctx.ConstU32(2U)};
|
||||
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
|
||||
const Id pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index)};
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics);
|
||||
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], pointer, scope, semantics);
|
||||
});
|
||||
}
|
||||
|
||||
Id BufferAtomicU32BoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) {
|
||||
if (Sirit::ValidId(buffer_size)) {
|
||||
// Bounds checking enabled, wrap in a conditional branch to make sure that
|
||||
// the atomic is not mistakenly executed when the index is out of bounds.
|
||||
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], index, buffer_size);
|
||||
const Id ib_label = ctx.OpLabel();
|
||||
const Id oob_label = ctx.OpLabel();
|
||||
const Id end_label = ctx.OpLabel();
|
||||
ctx.OpSelectionMerge(end_label, spv::SelectionControlMask::MaskNone);
|
||||
ctx.OpBranchConditional(in_bounds, ib_label, oob_label);
|
||||
ctx.AddLabel(ib_label);
|
||||
const Id ib_result = emit_func();
|
||||
ctx.OpBranch(end_label);
|
||||
ctx.AddLabel(oob_label);
|
||||
const Id oob_result = ctx.u32_zero_value;
|
||||
ctx.OpBranch(end_label);
|
||||
ctx.AddLabel(end_label);
|
||||
return ctx.OpPhi(ctx.U32[1], ib_result, ib_label, oob_result, oob_label);
|
||||
}
|
||||
// Bounds checking not enabled, just perform the atomic operation.
|
||||
return emit_func();
|
||||
Id SharedAtomicU64(EmitContext& ctx, Id offset, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
const Id shift_id{ctx.ConstU32(3U)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
return (ctx.*atomic_func)(ctx.U64, pointer, scope, semantics, value);
|
||||
});
|
||||
}
|
||||
|
||||
Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
|
@ -63,11 +63,57 @@ Id BufferAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id
|
|||
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
|
||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return BufferAtomicU32BoundsCheck(ctx, index, buffer.size_dwords, [&] {
|
||||
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, value);
|
||||
});
|
||||
}
|
||||
|
||||
Id BufferAtomicU32IncDec(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id)) {
|
||||
const auto& buffer = ctx.buffers[handle];
|
||||
if (Sirit::ValidId(buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
||||
}
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
|
||||
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
|
||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics);
|
||||
});
|
||||
}
|
||||
|
||||
Id BufferAtomicU32CmpSwap(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id, Id, Id)) {
|
||||
const auto& buffer = ctx.buffers[handle];
|
||||
if (Sirit::ValidId(buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
||||
}
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(2u));
|
||||
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U32];
|
||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return AccessBoundsCheck<32>(ctx, index, buffer.size_dwords, [&] {
|
||||
return (ctx.*atomic_func)(ctx.U32[1], ptr, scope, semantics, semantics, value, cmp_value);
|
||||
});
|
||||
}
|
||||
|
||||
Id BufferAtomicU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
const auto& buffer = ctx.buffers[handle];
|
||||
if (Sirit::ValidId(buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, buffer.offset);
|
||||
}
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u));
|
||||
const auto [id, pointer_type] = buffer[EmitContext::PointerType::U64];
|
||||
const Id ptr = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index);
|
||||
const auto [scope, semantics]{AtomicArgs(ctx)};
|
||||
return AccessBoundsCheck<64>(ctx, index, buffer.size_qwords, [&] {
|
||||
return (ctx.*atomic_func)(ctx.U64, ptr, scope, semantics, value);
|
||||
});
|
||||
}
|
||||
|
||||
Id ImageAtomicU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value,
|
||||
Id (Sirit::Module::*atomic_func)(Id, Id, Id, Id, Id)) {
|
||||
const auto& texture = ctx.images[handle & 0xFFFF];
|
||||
|
@ -89,6 +135,10 @@ Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value) {
|
|||
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value) {
|
||||
return SharedAtomicU64(ctx, offset, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value) {
|
||||
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicUMax);
|
||||
}
|
||||
|
@ -121,18 +171,26 @@ Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value) {
|
|||
return SharedAtomicU32(ctx, offset, value, &Sirit::Module::OpAtomicISub);
|
||||
}
|
||||
|
||||
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset) {
|
||||
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement);
|
||||
Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset) {
|
||||
return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIIncrement);
|
||||
}
|
||||
|
||||
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset) {
|
||||
return SharedAtomicU32_IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement);
|
||||
Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset) {
|
||||
return SharedAtomicU32IncDec(ctx, offset, &Sirit::Module::OpAtomicIDecrement);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
return BufferAtomicU64(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicISub);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicSMin);
|
||||
}
|
||||
|
@ -149,14 +207,12 @@ Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicUMax);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicInc32(EmitContext&, IR::Inst*, u32, Id, Id) {
|
||||
// TODO
|
||||
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicInc32);
|
||||
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIIncrement);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicDec32(EmitContext&, IR::Inst*, u32, Id, Id) {
|
||||
// TODO
|
||||
UNREACHABLE_MSG("Unsupported BUFFER_ATOMIC opcode: ", IR::Opcode::BufferAtomicDec32);
|
||||
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
return BufferAtomicU32IncDec(ctx, inst, handle, address, &Sirit::Module::OpAtomicIDecrement);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
|
@ -175,6 +231,12 @@ Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||
return BufferAtomicU32(ctx, inst, handle, address, value, &Sirit::Module::OpAtomicExchange);
|
||||
}
|
||||
|
||||
Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value) {
|
||||
return BufferAtomicU32CmpSwap(ctx, inst, handle, address, value, cmp_value,
|
||||
&Sirit::Module::OpAtomicCompareExchange);
|
||||
}
|
||||
|
||||
Id EmitImageAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id value) {
|
||||
return ImageAtomicU32(ctx, inst, handle, coords, value, &Sirit::Module::OpAtomicIAdd);
|
||||
}
|
||||
|
|
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 "emit_spirv_bounds.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
namespace {
|
||||
|
||||
|
@ -239,8 +241,8 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index) {
|
|||
}
|
||||
|
||||
if (IR::IsParam(attr)) {
|
||||
const u32 index{u32(attr) - u32(IR::Attribute::Param0)};
|
||||
const auto& param{ctx.input_params.at(index)};
|
||||
const u32 param_index{u32(attr) - u32(IR::Attribute::Param0)};
|
||||
const auto& param{ctx.input_params.at(param_index)};
|
||||
if (param.buffer_handle >= 0) {
|
||||
const auto step_rate = EmitReadStepRate(ctx, param.id.value);
|
||||
const auto offset = ctx.OpIAdd(
|
||||
|
@ -415,27 +417,6 @@ void EmitSetPatch(EmitContext& ctx, IR::Patch patch, Id value) {
|
|||
ctx.OpStore(pointer, value);
|
||||
}
|
||||
|
||||
template <u32 N>
|
||||
static Id EmitLoadBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, Id result,
|
||||
bool is_float) {
|
||||
if (Sirit::ValidId(buffer_size)) {
|
||||
// Bounds checking enabled, wrap in a select.
|
||||
const auto result_type = is_float ? ctx.F32[N] : ctx.U32[N];
|
||||
auto compare_index = index;
|
||||
auto zero_value = is_float ? ctx.f32_zero_value : ctx.u32_zero_value;
|
||||
if (N > 1) {
|
||||
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1));
|
||||
std::array<Id, N> zero_ids;
|
||||
zero_ids.fill(zero_value);
|
||||
zero_value = ctx.ConstantComposite(result_type, zero_ids);
|
||||
}
|
||||
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
|
||||
return ctx.OpSelect(result_type, in_bounds, result, zero_value);
|
||||
}
|
||||
// Bounds checking not enabled, just return the plain value.
|
||||
return result;
|
||||
}
|
||||
|
||||
template <u32 N, PointerType alias>
|
||||
static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
const auto flags = inst->Flags<IR::BufferInstInfo>();
|
||||
|
@ -454,8 +435,9 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a
|
|||
const Id result_i = ctx.OpLoad(data_types[1], ptr_i);
|
||||
if (!flags.typed) {
|
||||
// Untyped loads have bounds checking per-component.
|
||||
ids.push_back(EmitLoadBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords,
|
||||
result_i, alias == PointerType::F32));
|
||||
ids.push_back(LoadAccessBoundsCheck < 32, 1,
|
||||
alias ==
|
||||
PointerType::F32 > (ctx, index_i, spv_buffer.size_dwords, result_i));
|
||||
} else {
|
||||
ids.push_back(result_i);
|
||||
}
|
||||
|
@ -464,8 +446,8 @@ static Id EmitLoadBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id a
|
|||
const Id result = N == 1 ? ids[0] : ctx.OpCompositeConstruct(data_types[N], ids);
|
||||
if (flags.typed) {
|
||||
// Typed loads have single bounds check for the whole load.
|
||||
return EmitLoadBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, result,
|
||||
alias == PointerType::F32);
|
||||
return LoadAccessBoundsCheck < 32, N,
|
||||
alias == PointerType::F32 > (ctx, index, spv_buffer.size_dwords, result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -477,8 +459,8 @@ Id EmitLoadBufferU8(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
|||
}
|
||||
const auto [id, pointer_type] = spv_buffer[PointerType::U8];
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
|
||||
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U8, ptr))};
|
||||
return EmitLoadBufferBoundsCheck<1>(ctx, address, spv_buffer.size, result, false);
|
||||
const Id result{ctx.OpLoad(ctx.U8, ptr)};
|
||||
return LoadAccessBoundsCheck<8>(ctx, address, spv_buffer.size, result);
|
||||
}
|
||||
|
||||
Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
|
@ -489,8 +471,8 @@ Id EmitLoadBufferU16(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
|||
const auto [id, pointer_type] = spv_buffer[PointerType::U16];
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u));
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
|
||||
const Id result{ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, ptr))};
|
||||
return EmitLoadBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts, result, false);
|
||||
const Id result{ctx.OpLoad(ctx.U16, ptr)};
|
||||
return LoadAccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, result);
|
||||
}
|
||||
|
||||
Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
|
@ -509,6 +491,18 @@ Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address)
|
|||
return EmitLoadBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address);
|
||||
}
|
||||
|
||||
Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
const auto& spv_buffer = ctx.buffers[handle];
|
||||
if (Sirit::ValidId(spv_buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset);
|
||||
}
|
||||
const auto [id, pointer_type] = spv_buffer[PointerType::U64];
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u));
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)};
|
||||
const Id result{ctx.OpLoad(ctx.U64, ptr)};
|
||||
return LoadAccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, result);
|
||||
}
|
||||
|
||||
Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address) {
|
||||
return EmitLoadBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address);
|
||||
}
|
||||
|
@ -529,29 +523,6 @@ Id EmitLoadBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addr
|
|||
UNREACHABLE_MSG("SPIR-V instruction");
|
||||
}
|
||||
|
||||
template <u32 N>
|
||||
void EmitStoreBufferBoundsCheck(EmitContext& ctx, Id index, Id buffer_size, auto emit_func) {
|
||||
if (Sirit::ValidId(buffer_size)) {
|
||||
// Bounds checking enabled, wrap in a conditional branch.
|
||||
auto compare_index = index;
|
||||
if (N > 1) {
|
||||
compare_index = ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(N - 1));
|
||||
}
|
||||
const Id in_bounds = ctx.OpULessThan(ctx.U1[1], compare_index, buffer_size);
|
||||
const Id in_bounds_label = ctx.OpLabel();
|
||||
const Id merge_label = ctx.OpLabel();
|
||||
ctx.OpSelectionMerge(merge_label, spv::SelectionControlMask::MaskNone);
|
||||
ctx.OpBranchConditional(in_bounds, in_bounds_label, merge_label);
|
||||
ctx.AddLabel(in_bounds_label);
|
||||
emit_func();
|
||||
ctx.OpBranch(merge_label);
|
||||
ctx.AddLabel(merge_label);
|
||||
return;
|
||||
}
|
||||
// Bounds checking not enabled, just perform the store.
|
||||
emit_func();
|
||||
}
|
||||
|
||||
template <u32 N, PointerType alias>
|
||||
static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address,
|
||||
Id value) {
|
||||
|
@ -569,19 +540,25 @@ static void EmitStoreBufferB32xN(EmitContext& ctx, IR::Inst* inst, u32 handle, I
|
|||
const Id index_i = i == 0 ? index : ctx.OpIAdd(ctx.U32[1], index, ctx.ConstU32(i));
|
||||
const Id ptr_i = ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index_i);
|
||||
const Id value_i = N == 1 ? value : ctx.OpCompositeExtract(data_types[1], value, i);
|
||||
auto store_i = [&]() { ctx.OpStore(ptr_i, value_i); };
|
||||
auto store_i = [&] {
|
||||
ctx.OpStore(ptr_i, value_i);
|
||||
return Id{};
|
||||
};
|
||||
if (!flags.typed) {
|
||||
// Untyped stores have bounds checking per-component.
|
||||
EmitStoreBufferBoundsCheck<1>(ctx, index_i, spv_buffer.size_dwords, store_i);
|
||||
AccessBoundsCheck<32, 1, alias == PointerType::F32>(
|
||||
ctx, index_i, spv_buffer.size_dwords, store_i);
|
||||
} else {
|
||||
store_i();
|
||||
}
|
||||
}
|
||||
return Id{};
|
||||
};
|
||||
|
||||
if (flags.typed) {
|
||||
// Typed stores have single bounds check for the whole store.
|
||||
EmitStoreBufferBoundsCheck<N>(ctx, index, spv_buffer.size_dwords, store);
|
||||
AccessBoundsCheck<32, N, alias == PointerType::F32>(ctx, index, spv_buffer.size_dwords,
|
||||
store);
|
||||
} else {
|
||||
store();
|
||||
}
|
||||
|
@ -594,8 +571,10 @@ void EmitStoreBufferU8(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id v
|
|||
}
|
||||
const auto [id, pointer_type] = spv_buffer[PointerType::U8];
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, address)};
|
||||
const Id result{ctx.OpUConvert(ctx.U8, value)};
|
||||
EmitStoreBufferBoundsCheck<1>(ctx, address, spv_buffer.size, [&] { ctx.OpStore(ptr, result); });
|
||||
AccessBoundsCheck<8>(ctx, address, spv_buffer.size, [&] {
|
||||
ctx.OpStore(ptr, value);
|
||||
return Id{};
|
||||
});
|
||||
}
|
||||
|
||||
void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
|
||||
|
@ -606,9 +585,10 @@ void EmitStoreBufferU16(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id
|
|||
const auto [id, pointer_type] = spv_buffer[PointerType::U16];
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(1u));
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u32_zero_value, index)};
|
||||
const Id result{ctx.OpUConvert(ctx.U16, value)};
|
||||
EmitStoreBufferBoundsCheck<1>(ctx, index, spv_buffer.size_shorts,
|
||||
[&] { ctx.OpStore(ptr, result); });
|
||||
AccessBoundsCheck<16>(ctx, index, spv_buffer.size_shorts, [&] {
|
||||
ctx.OpStore(ptr, value);
|
||||
return Id{};
|
||||
});
|
||||
}
|
||||
|
||||
void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
|
@ -627,6 +607,20 @@ void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id addre
|
|||
EmitStoreBufferB32xN<4, PointerType::U32>(ctx, inst, handle, address, value);
|
||||
}
|
||||
|
||||
void EmitStoreBufferU64(EmitContext& ctx, IR::Inst*, u32 handle, Id address, Id value) {
|
||||
const auto& spv_buffer = ctx.buffers[handle];
|
||||
if (Sirit::ValidId(spv_buffer.offset)) {
|
||||
address = ctx.OpIAdd(ctx.U32[1], address, spv_buffer.offset);
|
||||
}
|
||||
const auto [id, pointer_type] = spv_buffer[PointerType::U64];
|
||||
const Id index = ctx.OpShiftRightLogical(ctx.U32[1], address, ctx.ConstU32(3u));
|
||||
const Id ptr{ctx.OpAccessChain(pointer_type, id, ctx.u64_zero_value, index)};
|
||||
AccessBoundsCheck<64>(ctx, index, spv_buffer.size_qwords, [&] {
|
||||
ctx.OpStore(ptr, value);
|
||||
return Id{};
|
||||
});
|
||||
}
|
||||
|
||||
void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value) {
|
||||
EmitStoreBufferB32xN<1, PointerType::F32>(ctx, inst, handle, address, value);
|
||||
}
|
||||
|
|
|
@ -263,4 +263,12 @@ Id EmitConvertU32U16(EmitContext& ctx, Id value) {
|
|||
return ctx.OpUConvert(ctx.U32[1], value);
|
||||
}
|
||||
|
||||
Id EmitConvertU8U32(EmitContext& ctx, Id value) {
|
||||
return ctx.OpUConvert(ctx.U8, value);
|
||||
}
|
||||
|
||||
Id EmitConvertU32U8(EmitContext& ctx, Id value) {
|
||||
return ctx.OpUConvert(ctx.U32[1], value);
|
||||
}
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -69,6 +69,7 @@ Id EmitLoadBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
|||
Id EmitLoadBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitLoadBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
|
@ -80,22 +81,27 @@ void EmitStoreBufferU32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address
|
|||
void EmitStoreBufferU32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferU32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferU32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferU64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferF32x2(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferF32x3(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferF32x4(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
void EmitStoreBufferFormatF32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicIAdd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicIAdd64(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicISub32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicSMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicUMin32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicSMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicUMax32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicInc32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitBufferAtomicDec32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address);
|
||||
Id EmitBufferAtomicAnd32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicOr32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicXor32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value);
|
||||
Id EmitBufferAtomicCmpSwap32(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address, Id value,
|
||||
Id cmp_value);
|
||||
Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, u32 comp, Id index);
|
||||
Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, u32 comp);
|
||||
void EmitSetAttribute(EmitContext& ctx, IR::Attribute attr, Id value, u32 comp);
|
||||
|
@ -118,11 +124,14 @@ Id EmitUndefU8(EmitContext& ctx);
|
|||
Id EmitUndefU16(EmitContext& ctx);
|
||||
Id EmitUndefU32(EmitContext& ctx);
|
||||
Id EmitUndefU64(EmitContext& ctx);
|
||||
Id EmitLoadSharedU16(EmitContext& ctx, Id offset);
|
||||
Id EmitLoadSharedU32(EmitContext& ctx, Id offset);
|
||||
Id EmitLoadSharedU64(EmitContext& ctx, Id offset);
|
||||
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value);
|
||||
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value);
|
||||
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicIAdd32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicIAdd64(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicUMax32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicSMax32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicUMin32(EmitContext& ctx, Id offset, Id value);
|
||||
|
@ -130,8 +139,8 @@ Id EmitSharedAtomicSMin32(EmitContext& ctx, Id offset, Id value);
|
|||
Id EmitSharedAtomicAnd32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicOr32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicXor32(EmitContext& ctx, Id offset, Id value);
|
||||
Id EmitSharedAtomicIIncrement32(EmitContext& ctx, Id offset);
|
||||
Id EmitSharedAtomicIDecrement32(EmitContext& ctx, Id offset);
|
||||
Id EmitSharedAtomicInc32(EmitContext& ctx, Id offset);
|
||||
Id EmitSharedAtomicDec32(EmitContext& ctx, Id offset);
|
||||
Id EmitSharedAtomicISub32(EmitContext& ctx, Id offset, Id value);
|
||||
|
||||
Id EmitCompositeConstructU32x2(EmitContext& ctx, IR::Inst* inst, Id e1, Id e2);
|
||||
|
@ -455,6 +464,8 @@ Id EmitConvertF64U32(EmitContext& ctx, Id value);
|
|||
Id EmitConvertF64U64(EmitContext& ctx, Id value);
|
||||
Id EmitConvertU16U32(EmitContext& ctx, Id value);
|
||||
Id EmitConvertU32U16(EmitContext& ctx, Id value);
|
||||
Id EmitConvertU8U32(EmitContext& ctx, Id value);
|
||||
Id EmitConvertU32U8(EmitContext& ctx, Id value);
|
||||
|
||||
Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
|
||||
Id address3, Id address4);
|
||||
|
|
|
@ -1,43 +1,86 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/div_ceil.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv_bounds.h"
|
||||
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
|
||||
#include "shader_recompiler/backend/spirv/spirv_emit_context.h"
|
||||
|
||||
namespace Shader::Backend::SPIRV {
|
||||
|
||||
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
|
||||
const Id shift_id{ctx.ConstU32(1U)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)};
|
||||
|
||||
return AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer =
|
||||
ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index);
|
||||
return ctx.OpLoad(ctx.U16, pointer);
|
||||
});
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
|
||||
const Id shift_id{ctx.ConstU32(2U)};
|
||||
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
|
||||
const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, index);
|
||||
return ctx.OpLoad(ctx.U32[1], pointer);
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
|
||||
|
||||
return AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer =
|
||||
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index);
|
||||
return ctx.OpLoad(ctx.U32[1], pointer);
|
||||
});
|
||||
}
|
||||
|
||||
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
|
||||
const Id shift_id{ctx.ConstU32(2U)};
|
||||
const Id base_index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
|
||||
const Id next_index{ctx.OpIAdd(ctx.U32[1], base_index, ctx.ConstU32(1U))};
|
||||
const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, base_index)};
|
||||
const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_index)};
|
||||
return ctx.OpCompositeConstruct(ctx.U32[2], ctx.OpLoad(ctx.U32[1], lhs_pointer),
|
||||
ctx.OpLoad(ctx.U32[1], rhs_pointer));
|
||||
const Id shift_id{ctx.ConstU32(3U)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift_id)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
|
||||
|
||||
return AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
|
||||
return ctx.OpLoad(ctx.U64, pointer);
|
||||
});
|
||||
}
|
||||
|
||||
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
|
||||
const Id shift{ctx.ConstU32(1U)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 2u)};
|
||||
|
||||
AccessBoundsCheck<16>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer =
|
||||
ctx.OpAccessChain(ctx.shared_u16, ctx.shared_memory_u16, ctx.u32_zero_value, index);
|
||||
ctx.OpStore(pointer, value);
|
||||
return Id{0};
|
||||
});
|
||||
}
|
||||
|
||||
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
|
||||
const Id shift{ctx.ConstU32(2U)};
|
||||
const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)};
|
||||
const Id pointer = ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset);
|
||||
ctx.OpStore(pointer, value);
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 4u)};
|
||||
|
||||
AccessBoundsCheck<32>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer =
|
||||
ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, ctx.u32_zero_value, index);
|
||||
ctx.OpStore(pointer, value);
|
||||
return Id{0};
|
||||
});
|
||||
}
|
||||
|
||||
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
|
||||
const Id shift{ctx.ConstU32(2U)};
|
||||
const Id word_offset{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift)};
|
||||
const Id next_offset{ctx.OpIAdd(ctx.U32[1], word_offset, ctx.ConstU32(1U))};
|
||||
const Id lhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, word_offset)};
|
||||
const Id rhs_pointer{ctx.OpAccessChain(ctx.shared_u32, ctx.shared_memory_u32, next_offset)};
|
||||
ctx.OpStore(lhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 0U));
|
||||
ctx.OpStore(rhs_pointer, ctx.OpCompositeExtract(ctx.U32[1], value, 1U));
|
||||
const Id shift{ctx.ConstU32(3U)};
|
||||
const Id index{ctx.OpShiftRightLogical(ctx.U32[1], offset, shift)};
|
||||
const u32 num_elements{Common::DivCeil(ctx.runtime_info.cs_info.shared_memory_size, 8u)};
|
||||
|
||||
AccessBoundsCheck<64>(ctx, index, ctx.ConstU32(num_elements), [&] {
|
||||
const Id pointer{
|
||||
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
|
||||
ctx.OpStore(pointer, value);
|
||||
return Id{0};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace Shader::Backend::SPIRV
|
||||
|
|
|
@ -146,6 +146,7 @@ void EmitContext::DefineArithmeticTypes() {
|
|||
false_value = ConstantFalse(U1[1]);
|
||||
u8_one_value = Constant(U8, 1U);
|
||||
u8_zero_value = Constant(U8, 0U);
|
||||
u16_zero_value = Constant(U16, 0U);
|
||||
u32_one_value = ConstU32(1U);
|
||||
u32_zero_value = ConstU32(0U);
|
||||
f32_zero_value = ConstF32(0.0f);
|
||||
|
@ -285,6 +286,8 @@ void EmitContext::DefineBufferProperties() {
|
|||
Name(buffer.size_shorts, fmt::format("buf{}_short_size", binding));
|
||||
buffer.size_dwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(2U));
|
||||
Name(buffer.size_dwords, fmt::format("buf{}_dword_size", binding));
|
||||
buffer.size_qwords = OpShiftRightLogical(U32[1], buffer.size, ConstU32(3U));
|
||||
Name(buffer.size_qwords, fmt::format("buf{}_qword_size", binding));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -296,8 +299,7 @@ void EmitContext::DefineInterpolatedAttribs() {
|
|||
// Iterate all input attributes, load them and manually interpolate.
|
||||
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
|
||||
const auto& input = runtime_info.fs_info.inputs[i];
|
||||
const u32 semantic = input.param_index;
|
||||
auto& params = input_params[semantic];
|
||||
auto& params = input_params[i];
|
||||
if (input.is_flat || params.is_loaded) {
|
||||
continue;
|
||||
}
|
||||
|
@ -307,13 +309,15 @@ void EmitContext::DefineInterpolatedAttribs() {
|
|||
const Id p2{OpCompositeExtract(F32[4], p_array, 2U)};
|
||||
const Id p10{OpFSub(F32[4], p1, p0)};
|
||||
const Id p20{OpFSub(F32[4], p2, p0)};
|
||||
const Id bary_coord{OpLoad(F32[3], gl_bary_coord_id)};
|
||||
const Id bary_coord{OpLoad(F32[3], IsLinear(info.interp_qualifiers[i])
|
||||
? bary_coord_linear_id
|
||||
: bary_coord_persp_id)};
|
||||
const Id bary_coord_y{OpCompositeExtract(F32[1], bary_coord, 1)};
|
||||
const Id bary_coord_z{OpCompositeExtract(F32[1], bary_coord, 2)};
|
||||
const Id p10_y{OpVectorTimesScalar(F32[4], p10, bary_coord_y)};
|
||||
const Id p20_z{OpVectorTimesScalar(F32[4], p20, bary_coord_z)};
|
||||
params.id = OpFAdd(F32[4], p0, OpFAdd(F32[4], p10_y, p20_z));
|
||||
Name(params.id, fmt::format("fs_in_attr{}", semantic));
|
||||
Name(params.id, fmt::format("fs_in_attr{}", i));
|
||||
params.is_loaded = true;
|
||||
}
|
||||
}
|
||||
|
@ -411,35 +415,47 @@ void EmitContext::DefineInputs() {
|
|||
DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input);
|
||||
}
|
||||
if (profile.needs_manual_interpolation) {
|
||||
gl_bary_coord_id =
|
||||
DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input);
|
||||
if (info.has_perspective_interp) {
|
||||
bary_coord_persp_id =
|
||||
DefineVariable(F32[3], spv::BuiltIn::BaryCoordKHR, spv::StorageClass::Input);
|
||||
}
|
||||
if (info.has_linear_interp) {
|
||||
bary_coord_linear_id = DefineVariable(F32[3], spv::BuiltIn::BaryCoordNoPerspKHR,
|
||||
spv::StorageClass::Input);
|
||||
}
|
||||
}
|
||||
for (s32 i = 0; i < runtime_info.fs_info.num_inputs; i++) {
|
||||
const auto& input = runtime_info.fs_info.inputs[i];
|
||||
const u32 semantic = input.param_index;
|
||||
ASSERT(semantic < IR::NumParams);
|
||||
if (input.IsDefault()) {
|
||||
input_params[semantic] = {
|
||||
MakeDefaultValue(*this, input.default_value), input_f32, F32[1], 4, false, true,
|
||||
input_params[i] = {
|
||||
.id = MakeDefaultValue(*this, input.default_value),
|
||||
.pointer_type = input_f32,
|
||||
.component_type = F32[1],
|
||||
.num_components = 4,
|
||||
.is_integer = false,
|
||||
.is_loaded = true,
|
||||
};
|
||||
continue;
|
||||
}
|
||||
const IR::Attribute param{IR::Attribute::Param0 + input.param_index};
|
||||
const IR::Attribute param{IR::Attribute::Param0 + i};
|
||||
const u32 num_components = info.loads.NumComponents(param);
|
||||
const Id type{F32[num_components]};
|
||||
Id attr_id{};
|
||||
if (profile.needs_manual_interpolation && !input.is_flat) {
|
||||
attr_id = DefineInput(TypeArray(type, ConstU32(3U)), semantic);
|
||||
attr_id = DefineInput(TypeArray(type, ConstU32(3U)), input.param_index);
|
||||
Decorate(attr_id, spv::Decoration::PerVertexKHR);
|
||||
Name(attr_id, fmt::format("fs_in_attr{}_p", semantic));
|
||||
Name(attr_id, fmt::format("fs_in_attr{}_p", i));
|
||||
} else {
|
||||
attr_id = DefineInput(type, semantic);
|
||||
Name(attr_id, fmt::format("fs_in_attr{}", semantic));
|
||||
attr_id = DefineInput(type, input.param_index);
|
||||
Name(attr_id, fmt::format("fs_in_attr{}", i));
|
||||
|
||||
if (input.is_flat) {
|
||||
Decorate(attr_id, spv::Decoration::Flat);
|
||||
} else if (IsLinear(info.interp_qualifiers[i])) {
|
||||
Decorate(attr_id, spv::Decoration::NoPerspective);
|
||||
}
|
||||
}
|
||||
if (input.is_flat) {
|
||||
Decorate(attr_id, spv::Decoration::Flat);
|
||||
}
|
||||
input_params[semantic] =
|
||||
input_params[i] =
|
||||
GetAttributeInfo(AmdGpu::NumberFormat::Float, attr_id, num_components, false);
|
||||
}
|
||||
break;
|
||||
|
@ -634,7 +650,8 @@ void EmitContext::DefineOutputs() {
|
|||
}
|
||||
break;
|
||||
}
|
||||
case LogicalStage::Fragment:
|
||||
case LogicalStage::Fragment: {
|
||||
u32 num_render_targets = 0;
|
||||
for (u32 i = 0; i < IR::NumRenderTargets; i++) {
|
||||
const IR::Attribute mrt{IR::Attribute::RenderTarget0 + i};
|
||||
if (!info.stores.GetAny(mrt)) {
|
||||
|
@ -643,11 +660,21 @@ void EmitContext::DefineOutputs() {
|
|||
const u32 num_components = info.stores.NumComponents(mrt);
|
||||
const AmdGpu::NumberFormat num_format{runtime_info.fs_info.color_buffers[i].num_format};
|
||||
const Id type{GetAttributeType(*this, num_format)[num_components]};
|
||||
const Id id{DefineOutput(type, i)};
|
||||
Id id;
|
||||
if (runtime_info.fs_info.dual_source_blending) {
|
||||
id = DefineOutput(type, 0);
|
||||
Decorate(id, spv::Decoration::Index, i);
|
||||
} else {
|
||||
id = DefineOutput(type, i);
|
||||
}
|
||||
Name(id, fmt::format("frag_color{}", i));
|
||||
frag_outputs[i] = GetAttributeInfo(num_format, id, num_components, true);
|
||||
++num_render_targets;
|
||||
}
|
||||
ASSERT_MSG(!runtime_info.fs_info.dual_source_blending || num_render_targets == 2,
|
||||
"Dual source blending enabled, there must be exactly two MRT exports");
|
||||
break;
|
||||
}
|
||||
case LogicalStage::Geometry: {
|
||||
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
|
||||
|
||||
|
@ -952,18 +979,46 @@ void EmitContext::DefineImagesAndSamplers() {
|
|||
}
|
||||
|
||||
void EmitContext::DefineSharedMemory() {
|
||||
if (!info.uses_shared) {
|
||||
const auto num_types = std::popcount(static_cast<u32>(info.shared_types));
|
||||
if (num_types == 0) {
|
||||
return;
|
||||
}
|
||||
ASSERT(info.stage == Stage::Compute);
|
||||
const u32 shared_memory_size = runtime_info.cs_info.shared_memory_size;
|
||||
const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)};
|
||||
const Id type{TypeArray(U32[1], ConstU32(num_elements))};
|
||||
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type);
|
||||
shared_u32 = TypePointer(spv::StorageClass::Workgroup, U32[1]);
|
||||
shared_memory_u32 = AddGlobalVariable(shared_memory_u32_type, spv::StorageClass::Workgroup);
|
||||
Name(shared_memory_u32, "shared_mem");
|
||||
interfaces.push_back(shared_memory_u32);
|
||||
|
||||
const auto make_type = [&](IR::Type type, Id element_type, u32 element_size,
|
||||
std::string_view name) {
|
||||
if (False(info.shared_types & type)) {
|
||||
// Skip unused shared memory types.
|
||||
return std::make_tuple(Id{}, Id{}, Id{});
|
||||
}
|
||||
|
||||
const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)};
|
||||
const Id array_type{TypeArray(element_type, ConstU32(num_elements))};
|
||||
Decorate(array_type, spv::Decoration::ArrayStride, element_size);
|
||||
|
||||
const Id struct_type{TypeStruct(array_type)};
|
||||
MemberDecorate(struct_type, 0u, spv::Decoration::Offset, 0u);
|
||||
|
||||
const Id pointer = TypePointer(spv::StorageClass::Workgroup, struct_type);
|
||||
const Id element_pointer = TypePointer(spv::StorageClass::Workgroup, element_type);
|
||||
const Id variable = AddGlobalVariable(pointer, spv::StorageClass::Workgroup);
|
||||
Name(variable, name);
|
||||
interfaces.push_back(variable);
|
||||
|
||||
if (num_types > 1) {
|
||||
Decorate(struct_type, spv::Decoration::Block);
|
||||
Decorate(variable, spv::Decoration::Aliased);
|
||||
}
|
||||
|
||||
return std::make_tuple(variable, element_pointer, pointer);
|
||||
};
|
||||
std::tie(shared_memory_u16, shared_u16, shared_memory_u16_type) =
|
||||
make_type(IR::Type::U16, U16, 2u, "shared_mem_u16");
|
||||
std::tie(shared_memory_u32, shared_u32, shared_memory_u32_type) =
|
||||
make_type(IR::Type::U32, U32[1], 4u, "shared_mem_u32");
|
||||
std::tie(shared_memory_u64, shared_u64, shared_memory_u64_type) =
|
||||
make_type(IR::Type::U64, U64, 8u, "shared_mem_u64");
|
||||
}
|
||||
|
||||
Id EmitContext::DefineFloat32ToUfloatM5(u32 mantissa_bits, const std::string_view name) {
|
||||
|
|
|
@ -235,17 +235,16 @@ public:
|
|||
Id false_value{};
|
||||
Id u8_one_value{};
|
||||
Id u8_zero_value{};
|
||||
Id u16_zero_value{};
|
||||
Id u32_one_value{};
|
||||
Id u32_zero_value{};
|
||||
Id f32_zero_value{};
|
||||
Id u64_one_value{};
|
||||
Id u64_zero_value{};
|
||||
|
||||
Id shared_u8{};
|
||||
Id shared_u16{};
|
||||
Id shared_u32{};
|
||||
Id shared_u32x2{};
|
||||
Id shared_u32x4{};
|
||||
Id shared_u64{};
|
||||
|
||||
Id input_u32{};
|
||||
Id input_f32{};
|
||||
|
@ -285,16 +284,16 @@ public:
|
|||
Id image_u32{};
|
||||
Id image_f32{};
|
||||
|
||||
Id shared_memory_u8{};
|
||||
Id shared_memory_u16{};
|
||||
Id shared_memory_u32{};
|
||||
Id shared_memory_u32x2{};
|
||||
Id shared_memory_u32x4{};
|
||||
Id shared_memory_u64{};
|
||||
|
||||
Id shared_memory_u16_type{};
|
||||
Id shared_memory_u32_type{};
|
||||
Id shared_memory_u64_type{};
|
||||
|
||||
Id interpolate_func{};
|
||||
Id gl_bary_coord_id{};
|
||||
Id bary_coord_persp_id{};
|
||||
Id bary_coord_linear_id{};
|
||||
|
||||
struct TextureDefinition {
|
||||
const VectorIds* data_types;
|
||||
|
@ -320,6 +319,7 @@ public:
|
|||
Id size;
|
||||
Id size_shorts;
|
||||
Id size_dwords;
|
||||
Id size_qwords;
|
||||
std::array<BufferSpv, u32(PointerType::NumAlias)> aliases;
|
||||
|
||||
const BufferSpv& operator[](PointerType alias) const {
|
||||
|
|
|
@ -67,6 +67,9 @@ CopyShaderData ParseCopyShader(std::span<const u32> code) {
|
|||
|
||||
if (last_attr != IR::Attribute::Position0) {
|
||||
data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1;
|
||||
const auto it = data.attr_map.begin();
|
||||
const u32 comp_stride = std::next(it)->first - it->first;
|
||||
data.output_vertices = comp_stride / 64;
|
||||
}
|
||||
|
||||
return data;
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/types.h"
|
||||
#include "shader_recompiler/ir/attribute.h"
|
||||
|
@ -12,8 +12,9 @@
|
|||
namespace Shader {
|
||||
|
||||
struct CopyShaderData {
|
||||
std::unordered_map<u32, std::pair<Shader::IR::Attribute, u32>> attr_map;
|
||||
std::map<u32, std::pair<Shader::IR::Attribute, u32>> attr_map;
|
||||
u32 num_attrs{0};
|
||||
u32 output_vertices{0};
|
||||
};
|
||||
|
||||
CopyShaderData ParseCopyShader(std::span<const u32> code);
|
||||
|
|
|
@ -605,11 +605,12 @@ public:
|
|||
Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
|
||||
: stmt_pool{stmt_pool_}, inst_pool{inst_pool_}, block_pool{block_pool_},
|
||||
syntax_list{syntax_list_}, inst_list{inst_list_}, info{info_},
|
||||
runtime_info{runtime_info_}, profile{profile_} {
|
||||
runtime_info{runtime_info_}, profile{profile_},
|
||||
translator{info_, runtime_info_, profile_} {
|
||||
Visit(root_stmt, nullptr, nullptr);
|
||||
|
||||
IR::Block& first_block{*syntax_list.front().data.block};
|
||||
Translator{&first_block, info, runtime_info, profile}.EmitPrologue();
|
||||
IR::Block* first_block = syntax_list.front().data.block;
|
||||
translator.EmitPrologue(first_block);
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -637,8 +638,8 @@ private:
|
|||
current_block->has_multiple_predecessors = stmt.block->num_predecessors > 1;
|
||||
const u32 start = stmt.block->begin_index;
|
||||
const u32 size = stmt.block->end_index - start + 1;
|
||||
Translate(current_block, stmt.block->begin, inst_list.subspan(start, size),
|
||||
info, runtime_info, profile);
|
||||
translator.Translate(current_block, stmt.block->begin,
|
||||
inst_list.subspan(start, size));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -820,6 +821,7 @@ private:
|
|||
Info& info;
|
||||
const RuntimeInfo& runtime_info;
|
||||
const Profile& profile;
|
||||
Translator translator;
|
||||
};
|
||||
} // Anonymous namespace
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ void Translator::EmitDataShare(const GcnInst& inst) {
|
|||
// DS
|
||||
case Opcode::DS_ADD_U32:
|
||||
return DS_ADD_U32(inst, false);
|
||||
case Opcode::DS_ADD_U64:
|
||||
return DS_ADD_U64(inst, false);
|
||||
case Opcode::DS_SUB_U32:
|
||||
return DS_SUB_U32(inst, false);
|
||||
case Opcode::DS_INC_U32:
|
||||
|
@ -61,10 +63,14 @@ void Translator::EmitDataShare(const GcnInst& inst) {
|
|||
return DS_READ(32, false, true, false, inst);
|
||||
case Opcode::DS_READ2ST64_B32:
|
||||
return DS_READ(32, false, true, true, inst);
|
||||
case Opcode::DS_READ_U16:
|
||||
return DS_READ(16, false, false, false, inst);
|
||||
case Opcode::DS_CONSUME:
|
||||
return DS_CONSUME(inst);
|
||||
case Opcode::DS_APPEND:
|
||||
return DS_APPEND(inst);
|
||||
case Opcode::DS_WRITE_B16:
|
||||
return DS_WRITE(16, false, false, false, inst);
|
||||
case Opcode::DS_WRITE_B64:
|
||||
return DS_WRITE(64, false, false, false, inst);
|
||||
case Opcode::DS_WRITE2_B64:
|
||||
|
@ -123,6 +129,18 @@ void Translator::DS_ADD_U32(const GcnInst& inst, bool rtn) {
|
|||
}
|
||||
}
|
||||
|
||||
void Translator::DS_ADD_U64(const GcnInst& inst, bool rtn) {
|
||||
const IR::U32 addr{GetSrc(inst.src[0])};
|
||||
const IR::U64 data{GetSrc64(inst.src[1])};
|
||||
const IR::U32 offset =
|
||||
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
|
||||
const IR::U32 addr_offset = ir.IAdd(addr, offset);
|
||||
const IR::Value original_val = ir.SharedAtomicIAdd(addr_offset, data);
|
||||
if (rtn) {
|
||||
SetDst64(inst.dst[0], IR::U64{original_val});
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::DS_MIN_U32(const GcnInst& inst, bool is_signed, bool rtn) {
|
||||
const IR::U32 addr{GetSrc(inst.src[0])};
|
||||
const IR::U32 data{GetSrc(inst.src[1])};
|
||||
|
@ -198,29 +216,38 @@ void Translator::DS_WRITE(int bit_size, bool is_signed, bool is_pair, bool strid
|
|||
if (is_pair) {
|
||||
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
|
||||
if (bit_size == 32) {
|
||||
if (bit_size == 64) {
|
||||
ir.WriteShared(64,
|
||||
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data0),
|
||||
ir.GetVectorReg(data0 + 1))),
|
||||
addr0);
|
||||
} else if (bit_size == 32) {
|
||||
ir.WriteShared(32, ir.GetVectorReg(data0), addr0);
|
||||
} else {
|
||||
ir.WriteShared(
|
||||
64, ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1)),
|
||||
addr0);
|
||||
} else if (bit_size == 16) {
|
||||
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
|
||||
}
|
||||
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
|
||||
if (bit_size == 32) {
|
||||
if (bit_size == 64) {
|
||||
ir.WriteShared(64,
|
||||
ir.PackUint2x32(ir.CompositeConstruct(ir.GetVectorReg(data1),
|
||||
ir.GetVectorReg(data1 + 1))),
|
||||
addr1);
|
||||
} else if (bit_size == 32) {
|
||||
ir.WriteShared(32, ir.GetVectorReg(data1), addr1);
|
||||
} else {
|
||||
ir.WriteShared(
|
||||
64, ir.CompositeConstruct(ir.GetVectorReg(data1), ir.GetVectorReg(data1 + 1)),
|
||||
addr1);
|
||||
} else if (bit_size == 16) {
|
||||
ir.WriteShared(16, ir.UConvert(16, ir.GetVectorReg(data1)), addr1);
|
||||
}
|
||||
} else if (bit_size == 64) {
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||
const IR::Value data =
|
||||
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
|
||||
ir.WriteShared(bit_size, data, addr0);
|
||||
} else {
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
|
||||
if (bit_size == 64) {
|
||||
const IR::Value data =
|
||||
ir.CompositeConstruct(ir.GetVectorReg(data0), ir.GetVectorReg(data0 + 1));
|
||||
ir.WriteShared(bit_size, ir.PackUint2x32(data), addr0);
|
||||
} else if (bit_size == 32) {
|
||||
ir.WriteShared(bit_size, ir.GetVectorReg(data0), addr0);
|
||||
} else if (bit_size == 16) {
|
||||
ir.WriteShared(bit_size, ir.UConvert(16, ir.GetVectorReg(data0)), addr0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -241,7 +268,7 @@ void Translator::DS_INC_U32(const GcnInst& inst, bool rtn) {
|
|||
const IR::U32 offset =
|
||||
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
|
||||
const IR::U32 addr_offset = ir.IAdd(addr, offset);
|
||||
const IR::Value original_val = ir.SharedAtomicIIncrement(addr_offset);
|
||||
const IR::Value original_val = ir.SharedAtomicInc(addr_offset);
|
||||
if (rtn) {
|
||||
SetDst(inst.dst[0], IR::U32{original_val});
|
||||
}
|
||||
|
@ -252,7 +279,7 @@ void Translator::DS_DEC_U32(const GcnInst& inst, bool rtn) {
|
|||
const IR::U32 offset =
|
||||
ir.Imm32((u32(inst.control.ds.offset1) << 8u) + u32(inst.control.ds.offset0));
|
||||
const IR::U32 addr_offset = ir.IAdd(addr, offset);
|
||||
const IR::Value original_val = ir.SharedAtomicIDecrement(addr_offset);
|
||||
const IR::Value original_val = ir.SharedAtomicDec(addr_offset);
|
||||
if (rtn) {
|
||||
SetDst(inst.dst[0], IR::U32{original_val});
|
||||
}
|
||||
|
@ -286,29 +313,38 @@ void Translator::DS_READ(int bit_size, bool is_signed, bool is_pair, bool stride
|
|||
const u32 adj = (bit_size == 32 ? 4 : 8) * (stride64 ? 64 : 1);
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset0 * adj)));
|
||||
const IR::Value data0 = ir.LoadShared(bit_size, is_signed, addr0);
|
||||
if (bit_size == 32) {
|
||||
if (bit_size == 64) {
|
||||
const auto vector = ir.UnpackUint2x32(IR::U64{data0});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)});
|
||||
} else if (bit_size == 32) {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{data0});
|
||||
} else {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 0)});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data0, 1)});
|
||||
} else if (bit_size == 16) {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data0})});
|
||||
}
|
||||
const IR::U32 addr1 = ir.IAdd(addr, ir.Imm32(u32(inst.control.ds.offset1 * adj)));
|
||||
const IR::Value data1 = ir.LoadShared(bit_size, is_signed, addr1);
|
||||
if (bit_size == 32) {
|
||||
if (bit_size == 64) {
|
||||
const auto vector = ir.UnpackUint2x32(IR::U64{data1});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 0)});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(vector, 1)});
|
||||
} else if (bit_size == 32) {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{data1});
|
||||
} else {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 0)});
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.CompositeExtract(data1, 1)});
|
||||
} else if (bit_size == 16) {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data1})});
|
||||
}
|
||||
} else if (bit_size == 64) {
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||
const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0);
|
||||
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(data, 0)});
|
||||
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(data, 1)});
|
||||
} else {
|
||||
const IR::U32 addr0 = ir.IAdd(addr, ir.Imm32(offset));
|
||||
const IR::U32 data = IR::U32{ir.LoadShared(bit_size, is_signed, addr0)};
|
||||
ir.SetVectorReg(dst_reg, data);
|
||||
const IR::Value data = ir.LoadShared(bit_size, is_signed, addr0);
|
||||
if (bit_size == 64) {
|
||||
const auto vector = ir.UnpackUint2x32(IR::U64{data});
|
||||
ir.SetVectorReg(dst_reg, IR::U32{ir.CompositeExtract(vector, 0)});
|
||||
ir.SetVectorReg(dst_reg + 1, IR::U32{ir.CompositeExtract(vector, 1)});
|
||||
} else if (bit_size == 32) {
|
||||
ir.SetVectorReg(dst_reg, IR::U32{data});
|
||||
} else if (bit_size == 16) {
|
||||
ir.SetVectorReg(dst_reg++, IR::U32{ir.UConvert(32, IR::U16{data})});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,8 +26,11 @@ void Translator::ExportMrtValue(IR::Attribute attribute, u32 comp, const IR::F32
|
|||
}
|
||||
|
||||
void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR::U32& value) {
|
||||
const u32 color_buffer_idx =
|
||||
u32 color_buffer_idx =
|
||||
static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::RenderTarget0);
|
||||
if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) {
|
||||
color_buffer_idx = 0;
|
||||
}
|
||||
const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
|
||||
|
||||
AmdGpu::NumberFormat num_format;
|
||||
|
@ -68,8 +71,11 @@ void Translator::ExportMrtCompressed(IR::Attribute attribute, u32 idx, const IR:
|
|||
}
|
||||
|
||||
void Translator::ExportMrtUncompressed(IR::Attribute attribute, u32 comp, const IR::F32& value) {
|
||||
const u32 color_buffer_idx =
|
||||
u32 color_buffer_idx =
|
||||
static_cast<u32>(attribute) - static_cast<u32>(IR::Attribute::RenderTarget0);
|
||||
if (runtime_info.fs_info.dual_source_blending && attribute == IR::Attribute::RenderTarget1) {
|
||||
color_buffer_idx = 0;
|
||||
}
|
||||
const auto color_buffer = runtime_info.fs_info.color_buffers[color_buffer_idx];
|
||||
const auto swizzled_comp = SwizzleMrtComponent(color_buffer, comp);
|
||||
|
||||
|
|
|
@ -21,16 +21,60 @@
|
|||
|
||||
namespace Shader::Gcn {
|
||||
|
||||
static u32 next_vgpr_num;
|
||||
static std::unordered_map<u32, IR::VectorReg> vgpr_map;
|
||||
|
||||
Translator::Translator(IR::Block* block_, Info& info_, const RuntimeInfo& runtime_info_,
|
||||
const Profile& profile_)
|
||||
: ir{*block_, block_->begin()}, info{info_}, runtime_info{runtime_info_}, profile{profile_} {
|
||||
next_vgpr_num = vgpr_map.empty() ? runtime_info.num_allocated_vgprs : next_vgpr_num;
|
||||
Translator::Translator(Info& info_, const RuntimeInfo& runtime_info_, const Profile& profile_)
|
||||
: info{info_}, runtime_info{runtime_info_}, profile{profile_},
|
||||
next_vgpr_num{runtime_info.num_allocated_vgprs} {
|
||||
if (info.l_stage == LogicalStage::Fragment) {
|
||||
dst_frag_vreg = GatherInterpQualifiers();
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::EmitPrologue() {
|
||||
IR::VectorReg Translator::GatherInterpQualifiers() {
|
||||
u32 dst_vreg{};
|
||||
if (runtime_info.fs_info.addr_flags.persp_sample_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveSample; // J
|
||||
info.has_perspective_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_center_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCenter; // J
|
||||
info.has_perspective_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_centroid_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::PerspectiveCentroid; // J
|
||||
info.has_perspective_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) {
|
||||
++dst_vreg; // I/W
|
||||
++dst_vreg; // J/W
|
||||
++dst_vreg; // 1/W
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_sample_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearSample; // J
|
||||
info.has_linear_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_center_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCenter; // J
|
||||
info.has_linear_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_centroid_ena) {
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // I
|
||||
vgpr_to_interp[dst_vreg++] = IR::Interpolation::LinearCentroid; // J
|
||||
info.has_linear_interp = true;
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) {
|
||||
++dst_vreg;
|
||||
}
|
||||
return IR::VectorReg(dst_vreg);
|
||||
}
|
||||
|
||||
void Translator::EmitPrologue(IR::Block* first_block) {
|
||||
ir = IR::IREmitter(*first_block, first_block->begin());
|
||||
|
||||
ir.Prologue();
|
||||
ir.SetExec(ir.Imm1(true));
|
||||
|
||||
|
@ -60,39 +104,7 @@ void Translator::EmitPrologue() {
|
|||
}
|
||||
break;
|
||||
case LogicalStage::Fragment:
|
||||
dst_vreg = IR::VectorReg::V0;
|
||||
if (runtime_info.fs_info.addr_flags.persp_sample_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_center_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_centroid_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.persp_pull_model_ena) {
|
||||
++dst_vreg; // I/W
|
||||
++dst_vreg; // J/W
|
||||
++dst_vreg; // 1/W
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_sample_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_center_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.linear_centroid_ena) {
|
||||
++dst_vreg; // I
|
||||
++dst_vreg; // J
|
||||
}
|
||||
if (runtime_info.fs_info.addr_flags.line_stipple_tex_ena) {
|
||||
++dst_vreg;
|
||||
}
|
||||
dst_vreg = dst_frag_vreg;
|
||||
if (runtime_info.fs_info.addr_flags.pos_x_float_ena) {
|
||||
if (runtime_info.fs_info.en_flags.pos_x_float_ena) {
|
||||
ir.SetVectorReg(dst_vreg++, ir.GetAttribute(IR::Attribute::FragCoord, 0));
|
||||
|
@ -543,6 +555,26 @@ void Translator::LogMissingOpcode(const GcnInst& inst) {
|
|||
info.translation_failed = true;
|
||||
}
|
||||
|
||||
void Translator::Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list) {
|
||||
if (inst_list.empty()) {
|
||||
return;
|
||||
}
|
||||
ir = IR::IREmitter{*block, block->begin()};
|
||||
for (const auto& inst : inst_list) {
|
||||
pc += inst.length;
|
||||
|
||||
// Special case for emitting fetch shader.
|
||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||
ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export ||
|
||||
info.stage == Stage::Local);
|
||||
EmitFetch(inst);
|
||||
continue;
|
||||
}
|
||||
|
||||
TranslateInstruction(inst, pc);
|
||||
}
|
||||
}
|
||||
|
||||
void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) {
|
||||
// Emit instructions for each category.
|
||||
switch (inst.category) {
|
||||
|
@ -577,25 +609,4 @@ void Translator::TranslateInstruction(const GcnInst& inst, const u32 pc) {
|
|||
}
|
||||
}
|
||||
|
||||
void Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list, Info& info,
|
||||
const RuntimeInfo& runtime_info, const Profile& profile) {
|
||||
if (inst_list.empty()) {
|
||||
return;
|
||||
}
|
||||
Translator translator{block, info, runtime_info, profile};
|
||||
for (const auto& inst : inst_list) {
|
||||
pc += inst.length;
|
||||
|
||||
// Special case for emitting fetch shader.
|
||||
if (inst.opcode == Opcode::S_SWAPPC_B64) {
|
||||
ASSERT(info.stage == Stage::Vertex || info.stage == Stage::Export ||
|
||||
info.stage == Stage::Local);
|
||||
translator.EmitFetch(inst);
|
||||
continue;
|
||||
}
|
||||
|
||||
translator.TranslateInstruction(inst, pc);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Shader::Gcn
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <span>
|
||||
#include <unordered_map>
|
||||
#include "shader_recompiler/frontend/instruction.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "shader_recompiler/ir/basic_block.h"
|
||||
|
@ -53,15 +54,17 @@ enum class NegateMode : u32 {
|
|||
Result,
|
||||
};
|
||||
|
||||
static constexpr size_t MaxInterpVgpr = 16;
|
||||
|
||||
class Translator {
|
||||
public:
|
||||
explicit Translator(IR::Block* block_, Info& info, const RuntimeInfo& runtime_info,
|
||||
const Profile& profile);
|
||||
explicit Translator(Info& info, const RuntimeInfo& runtime_info, const Profile& profile);
|
||||
|
||||
void Translate(IR::Block* block, u32 pc, std::span<const GcnInst> inst_list);
|
||||
void TranslateInstruction(const GcnInst& inst, u32 pc);
|
||||
|
||||
// Instruction categories
|
||||
void EmitPrologue();
|
||||
void EmitPrologue(IR::Block* first_block);
|
||||
void EmitFetch(const GcnInst& inst);
|
||||
void EmitExport(const GcnInst& inst);
|
||||
void EmitFlowControl(u32 pc, const GcnInst& inst);
|
||||
|
@ -268,6 +271,7 @@ public:
|
|||
// Data share
|
||||
// DS
|
||||
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_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);
|
||||
|
@ -326,16 +330,18 @@ private:
|
|||
void LogMissingOpcode(const GcnInst& inst);
|
||||
|
||||
IR::VectorReg GetScratchVgpr(u32 offset);
|
||||
IR::VectorReg GatherInterpQualifiers();
|
||||
|
||||
private:
|
||||
IR::IREmitter ir;
|
||||
Info& info;
|
||||
const RuntimeInfo& runtime_info;
|
||||
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;
|
||||
};
|
||||
|
||||
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
|
||||
|
|
|
@ -22,14 +22,17 @@ void Translator::EmitVectorInterpolation(const GcnInst& inst) {
|
|||
// VINTRP
|
||||
|
||||
void Translator::V_INTERP_P2_F32(const GcnInst& inst) {
|
||||
auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr);
|
||||
const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index};
|
||||
const u32 attr_index = inst.control.vintrp.attr;
|
||||
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));
|
||||
}
|
||||
|
||||
void Translator::V_INTERP_MOV_F32(const GcnInst& inst) {
|
||||
auto& attr = runtime_info.fs_info.inputs.at(inst.control.vintrp.attr);
|
||||
const IR::Attribute attrib{IR::Attribute::Param0 + attr.param_index};
|
||||
const u32 attr_index = inst.control.vintrp.attr;
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -70,6 +70,8 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
|
|||
return BUFFER_ATOMIC(AtomicOp::Add, inst);
|
||||
case Opcode::BUFFER_ATOMIC_SWAP:
|
||||
return BUFFER_ATOMIC(AtomicOp::Swap, inst);
|
||||
case Opcode::BUFFER_ATOMIC_CMPSWAP:
|
||||
return BUFFER_ATOMIC(AtomicOp::CmpSwap, inst);
|
||||
case Opcode::BUFFER_ATOMIC_SMIN:
|
||||
return BUFFER_ATOMIC(AtomicOp::Smin, inst);
|
||||
case Opcode::BUFFER_ATOMIC_UMIN:
|
||||
|
@ -331,6 +333,10 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
|||
switch (op) {
|
||||
case AtomicOp::Swap:
|
||||
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:
|
||||
return ir.BufferAtomicIAdd(handle, address, vdata_val, buffer_info);
|
||||
case AtomicOp::Smin:
|
||||
|
@ -348,9 +354,9 @@ void Translator::BUFFER_ATOMIC(AtomicOp op, const GcnInst& inst) {
|
|||
case AtomicOp::Xor:
|
||||
return ir.BufferAtomicXor(handle, address, vdata_val, buffer_info);
|
||||
case AtomicOp::Inc:
|
||||
return ir.BufferAtomicInc(handle, address, vdata_val, buffer_info);
|
||||
return ir.BufferAtomicInc(handle, address, buffer_info);
|
||||
case AtomicOp::Dec:
|
||||
return ir.BufferAtomicDec(handle, address, vdata_val, buffer_info);
|
||||
return ir.BufferAtomicDec(handle, address, buffer_info);
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
|
|
@ -193,6 +193,8 @@ struct Info {
|
|||
PersistentSrtInfo srt_info;
|
||||
std::vector<u32> flattened_ud_buf;
|
||||
|
||||
std::array<IR::Interpolation, 32> interp_qualifiers{};
|
||||
|
||||
IR::ScalarReg tess_consts_ptr_base = IR::ScalarReg::Max;
|
||||
s32 tess_consts_dword_offset = -1;
|
||||
|
||||
|
@ -206,11 +208,13 @@ struct Info {
|
|||
bool has_discard{};
|
||||
bool has_image_gather{};
|
||||
bool has_image_query{};
|
||||
bool has_perspective_interp{};
|
||||
bool has_linear_interp{};
|
||||
bool uses_atomic_float_min_max{};
|
||||
bool uses_lane_id{};
|
||||
bool uses_group_quad{};
|
||||
bool uses_group_ballot{};
|
||||
bool uses_shared{};
|
||||
IR::Type shared_types{};
|
||||
bool uses_fp16{};
|
||||
bool uses_fp64{};
|
||||
bool uses_pack_10_11_11{};
|
||||
|
|
|
@ -83,6 +83,16 @@ enum class Attribute : u64 {
|
|||
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 NumRenderTargets = 8;
|
||||
constexpr size_t NumParams = 32;
|
||||
|
@ -104,6 +114,15 @@ constexpr bool IsMrt(Attribute attribute) noexcept {
|
|||
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]] constexpr Attribute operator+(Attribute attr, int num) {
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <array>
|
||||
#include <bit>
|
||||
#include <source_location>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#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) {
|
||||
switch (bit_size) {
|
||||
case 16:
|
||||
return Inst<U16>(Opcode::LoadSharedU16, offset);
|
||||
case 32:
|
||||
return Inst<U32>(Opcode::LoadSharedU32, offset);
|
||||
case 64:
|
||||
return Inst(Opcode::LoadSharedU64, offset);
|
||||
return Inst<U64>(Opcode::LoadSharedU64, offset);
|
||||
default:
|
||||
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) {
|
||||
switch (bit_size) {
|
||||
case 16:
|
||||
Inst(Opcode::WriteSharedU16, offset, value);
|
||||
break;
|
||||
case 32:
|
||||
Inst(Opcode::WriteSharedU32, offset, value);
|
||||
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()) {
|
||||
case Type::U32:
|
||||
return Inst<U32>(Opcode::SharedAtomicIAdd32, address, data);
|
||||
case Type::U64:
|
||||
return Inst<U64>(Opcode::SharedAtomicIAdd64, address, data);
|
||||
default:
|
||||
ThrowInvalidType(data.Type());
|
||||
}
|
||||
|
@ -347,12 +353,12 @@ U32 IREmitter::SharedAtomicXor(const U32& address, const U32& data) {
|
|||
return Inst<U32>(Opcode::SharedAtomicXor32, address, data);
|
||||
}
|
||||
|
||||
U32 IREmitter::SharedAtomicIIncrement(const U32& address) {
|
||||
return Inst<U32>(Opcode::SharedAtomicIIncrement32, address);
|
||||
U32 IREmitter::SharedAtomicInc(const U32& address) {
|
||||
return Inst<U32>(Opcode::SharedAtomicInc32, address);
|
||||
}
|
||||
|
||||
U32 IREmitter::SharedAtomicIDecrement(const U32& address) {
|
||||
return Inst<U32>(Opcode::SharedAtomicIDecrement32, address);
|
||||
U32 IREmitter::SharedAtomicDec(const U32& address) {
|
||||
return Inst<U32>(Opcode::SharedAtomicDec32, address);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
U32 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst<U32>(Opcode::LoadBufferU8, Flags{info}, handle, address);
|
||||
U8 IREmitter::LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst<U8>(Opcode::LoadBufferU8, Flags{info}, handle, address);
|
||||
}
|
||||
|
||||
U32 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst<U32>(Opcode::LoadBufferU16, Flags{info}, handle, address);
|
||||
U16 IREmitter::LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst<U16>(Opcode::LoadBufferU16, Flags{info}, handle, 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,
|
||||
BufferInstInfo info) {
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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,
|
||||
const Value& data, BufferInstInfo info) {
|
||||
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,
|
||||
BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicIAdd32, Flags{info}, handle, address, value);
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
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,
|
||||
|
@ -483,14 +510,12 @@ Value IREmitter::BufferAtomicIMax(const Value& handle, const Value& address, con
|
|||
: Inst(Opcode::BufferAtomicUMax32, Flags{info}, handle, address, value);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, const Value& value,
|
||||
BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address, value);
|
||||
Value IREmitter::BufferAtomicInc(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicInc32, Flags{info}, handle, address);
|
||||
}
|
||||
|
||||
Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, const Value& value,
|
||||
BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address, value);
|
||||
Value IREmitter::BufferAtomicDec(const Value& handle, const Value& address, BufferInstInfo info) {
|
||||
return Inst(Opcode::BufferAtomicDec32, Flags{info}, handle, address);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
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);
|
||||
}
|
||||
|
||||
U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) {
|
||||
U8U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U8U16U32U64& value) {
|
||||
switch (result_bitsize) {
|
||||
case 8:
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
return Inst<U8>(Opcode::ConvertU8U32, value);
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case 16:
|
||||
switch (value.Type()) {
|
||||
case Type::U32:
|
||||
|
@ -1804,6 +1841,8 @@ U16U32U64 IREmitter::UConvert(size_t result_bitsize, const U16U32U64& value) {
|
|||
}
|
||||
case 32:
|
||||
switch (value.Type()) {
|
||||
case Type::U8:
|
||||
return Inst<U32>(Opcode::ConvertU32U8, value);
|
||||
case Type::U16:
|
||||
return Inst<U32>(Opcode::ConvertU32U16, value);
|
||||
default:
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include <cstring>
|
||||
#include <type_traits>
|
||||
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "shader_recompiler/ir/attribute.h"
|
||||
#include "shader_recompiler/ir/basic_block.h"
|
||||
#include "shader_recompiler/ir/condition.h"
|
||||
|
@ -17,6 +16,7 @@ namespace Shader::IR {
|
|||
|
||||
class IREmitter {
|
||||
public:
|
||||
explicit IREmitter() = default;
|
||||
explicit IREmitter(Block& block_) : block{&block_}, insertion_point{block->end()} {}
|
||||
explicit IREmitter(Block& block_, Block::iterator 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);
|
||||
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 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 SharedAtomicOr(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 ReadConstBuffer(const Value& handle, const U32& index);
|
||||
|
||||
[[nodiscard]] U32 LoadBufferU8(const Value& handle, const Value& address, BufferInstInfo info);
|
||||
[[nodiscard]] U32 LoadBufferU16(const Value& handle, const Value& address, BufferInstInfo info);
|
||||
[[nodiscard]] U8 LoadBufferU8(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,
|
||||
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,
|
||||
BufferInstInfo info);
|
||||
[[nodiscard]] Value LoadBufferFormat(const Value& handle, const Value& address,
|
||||
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);
|
||||
void StoreBufferU16(const Value& handle, const Value& address, const U32& data,
|
||||
void StoreBufferU16(const Value& handle, const Value& address, const U16& data,
|
||||
BufferInstInfo info);
|
||||
void StoreBufferU32(int num_dwords, const Value& handle, const Value& address,
|
||||
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,
|
||||
const Value& data, BufferInstInfo info);
|
||||
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,
|
||||
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,
|
||||
const Value& value, bool is_signed, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicIMax(const Value& handle, const Value& address,
|
||||
const Value& value, bool is_signed, BufferInstInfo info);
|
||||
[[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,
|
||||
const Value& value, BufferInstInfo info);
|
||||
BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicAnd(const Value& handle, const Value& address,
|
||||
const Value& value, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicOr(const Value& handle, const Value& address,
|
||||
|
@ -150,6 +154,9 @@ public:
|
|||
const Value& value, BufferInstInfo info);
|
||||
[[nodiscard]] Value BufferAtomicSwap(const Value& handle, const Value& address,
|
||||
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 DataConsume(const U32& counter);
|
||||
|
@ -306,7 +313,7 @@ public:
|
|||
[[nodiscard]] F32F64 ConvertIToF(size_t dest_bitsize, size_t src_bitsize, bool is_signed,
|
||||
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]] Value ImageAtomicIAdd(const Value& handle, const Value& coords,
|
||||
|
|
|
@ -60,12 +60,15 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
|||
case Opcode::StoreBufferU32x2:
|
||||
case Opcode::StoreBufferU32x3:
|
||||
case Opcode::StoreBufferU32x4:
|
||||
case Opcode::StoreBufferU64:
|
||||
case Opcode::StoreBufferF32:
|
||||
case Opcode::StoreBufferF32x2:
|
||||
case Opcode::StoreBufferF32x3:
|
||||
case Opcode::StoreBufferF32x4:
|
||||
case Opcode::StoreBufferFormatF32:
|
||||
case Opcode::BufferAtomicIAdd32:
|
||||
case Opcode::BufferAtomicIAdd64:
|
||||
case Opcode::BufferAtomicISub32:
|
||||
case Opcode::BufferAtomicSMin32:
|
||||
case Opcode::BufferAtomicUMin32:
|
||||
case Opcode::BufferAtomicSMax32:
|
||||
|
@ -76,15 +79,21 @@ bool Inst::MayHaveSideEffects() const noexcept {
|
|||
case Opcode::BufferAtomicOr32:
|
||||
case Opcode::BufferAtomicXor32:
|
||||
case Opcode::BufferAtomicSwap32:
|
||||
case Opcode::BufferAtomicCmpSwap32:
|
||||
case Opcode::DataAppend:
|
||||
case Opcode::DataConsume:
|
||||
case Opcode::WriteSharedU64:
|
||||
case Opcode::WriteSharedU16:
|
||||
case Opcode::WriteSharedU32:
|
||||
case Opcode::WriteSharedU64:
|
||||
case Opcode::SharedAtomicIAdd32:
|
||||
case Opcode::SharedAtomicIAdd64:
|
||||
case Opcode::SharedAtomicISub32:
|
||||
case Opcode::SharedAtomicSMin32:
|
||||
case Opcode::SharedAtomicUMin32:
|
||||
case Opcode::SharedAtomicSMax32:
|
||||
case Opcode::SharedAtomicUMax32:
|
||||
case Opcode::SharedAtomicInc32:
|
||||
case Opcode::SharedAtomicDec32:
|
||||
case Opcode::SharedAtomicAnd32:
|
||||
case Opcode::SharedAtomicOr32:
|
||||
case Opcode::SharedAtomicXor32:
|
||||
|
|
|
@ -30,23 +30,26 @@ OPCODE(EmitVertex, Void,
|
|||
OPCODE(EmitPrimitive, Void, )
|
||||
|
||||
// Shared memory operations
|
||||
OPCODE(LoadSharedU16, U16, U32, )
|
||||
OPCODE(LoadSharedU32, U32, U32, )
|
||||
OPCODE(LoadSharedU64, U32x2, U32, )
|
||||
OPCODE(LoadSharedU64, U64, U32, )
|
||||
OPCODE(WriteSharedU16, Void, U32, U16, )
|
||||
OPCODE(WriteSharedU32, Void, U32, U32, )
|
||||
OPCODE(WriteSharedU64, Void, U32, U32x2, )
|
||||
OPCODE(WriteSharedU64, Void, U32, U64, )
|
||||
|
||||
// Shared atomic operations
|
||||
OPCODE(SharedAtomicIAdd32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicIAdd64, U64, U32, U64, )
|
||||
OPCODE(SharedAtomicISub32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicSMin32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicUMin32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicSMax32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicUMax32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicInc32, U32, U32, )
|
||||
OPCODE(SharedAtomicDec32, U32, U32, )
|
||||
OPCODE(SharedAtomicAnd32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicOr32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicXor32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicISub32, U32, U32, U32, )
|
||||
OPCODE(SharedAtomicIIncrement32, U32, U32, )
|
||||
OPCODE(SharedAtomicIDecrement32, U32, U32, )
|
||||
|
||||
// Context getters/setters
|
||||
OPCODE(GetUserData, U32, ScalarReg, )
|
||||
|
@ -91,23 +94,25 @@ OPCODE(UndefU32, U32,
|
|||
OPCODE(UndefU64, U64, )
|
||||
|
||||
// Buffer operations
|
||||
OPCODE(LoadBufferU8, U32, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU16, U32, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU8, U8, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU16, U16, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU32, U32, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU32x2, U32x2, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU32x3, U32x3, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU32x4, U32x4, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferU64, U64, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferF32, F32, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferF32x2, F32x2, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferF32x3, F32x3, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferF32x4, F32x4, Opaque, Opaque, )
|
||||
OPCODE(LoadBufferFormatF32, F32x4, Opaque, Opaque, )
|
||||
OPCODE(StoreBufferU8, Void, Opaque, Opaque, U32, )
|
||||
OPCODE(StoreBufferU16, Void, Opaque, Opaque, U32, )
|
||||
OPCODE(StoreBufferU8, Void, Opaque, Opaque, U8, )
|
||||
OPCODE(StoreBufferU16, Void, Opaque, Opaque, U16, )
|
||||
OPCODE(StoreBufferU32, Void, Opaque, Opaque, U32, )
|
||||
OPCODE(StoreBufferU32x2, Void, Opaque, Opaque, U32x2, )
|
||||
OPCODE(StoreBufferU32x3, Void, Opaque, Opaque, U32x3, )
|
||||
OPCODE(StoreBufferU32x4, Void, Opaque, Opaque, U32x4, )
|
||||
OPCODE(StoreBufferU64, Void, Opaque, Opaque, U64, )
|
||||
OPCODE(StoreBufferF32, Void, Opaque, Opaque, F32, )
|
||||
OPCODE(StoreBufferF32x2, Void, Opaque, Opaque, F32x2, )
|
||||
OPCODE(StoreBufferF32x3, Void, Opaque, Opaque, F32x3, )
|
||||
|
@ -116,16 +121,19 @@ OPCODE(StoreBufferFormatF32, Void, Opaq
|
|||
|
||||
// Buffer atomic operations
|
||||
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(BufferAtomicUMin32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicSMax32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicUMax32, U32, Opaque, Opaque, U32 )
|
||||
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicInc32, U32, Opaque, Opaque, )
|
||||
OPCODE(BufferAtomicDec32, U32, Opaque, Opaque, )
|
||||
OPCODE(BufferAtomicAnd32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicOr32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicXor32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicSwap32, U32, Opaque, Opaque, U32, )
|
||||
OPCODE(BufferAtomicCmpSwap32, U32, Opaque, Opaque, U32, U32, )
|
||||
|
||||
// Vector utility
|
||||
OPCODE(CompositeConstructU32x2, U32x2, U32, U32, )
|
||||
|
@ -400,6 +408,8 @@ OPCODE(ConvertF64U32, F64, U32,
|
|||
OPCODE(ConvertF32U16, F32, U16, )
|
||||
OPCODE(ConvertU16U32, U16, U32, )
|
||||
OPCODE(ConvertU32U16, U32, U16, )
|
||||
OPCODE(ConvertU8U32, U8, U32, )
|
||||
OPCODE(ConvertU32U8, U32, U8, )
|
||||
|
||||
// Image operations
|
||||
OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, )
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
#include "common/io_file.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/path_util.h"
|
||||
#include "common/signal_context.h"
|
||||
#include "core/signals.h"
|
||||
#include "shader_recompiler/info.h"
|
||||
#include "shader_recompiler/ir/breadth_first_search.h"
|
||||
#include "shader_recompiler/ir/opcodes.h"
|
||||
|
@ -24,6 +26,7 @@
|
|||
using namespace Xbyak::util;
|
||||
|
||||
static Xbyak::CodeGenerator g_srt_codegen(32_MB);
|
||||
static const u8* g_srt_codegen_start = nullptr;
|
||||
|
||||
namespace {
|
||||
|
||||
|
@ -54,6 +57,57 @@ static void DumpSrtProgram(const Shader::Info& info, const u8* code, size_t code
|
|||
#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;
|
||||
|
||||
struct PassInfo {
|
||||
|
@ -141,6 +195,15 @@ static void GenerateSrtProgram(Info& info, PassInfo& pass_info) {
|
|||
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>();
|
||||
|
||||
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)};
|
||||
const u32 num_dwords = opcode == IR::Opcode::WriteSharedU32 ? 1 : 2;
|
||||
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,
|
||||
u32 off_dw) {
|
||||
|
@ -466,10 +468,10 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
|||
|
||||
AttributeRegion region = GetAttributeRegionKind(&inst, info, runtime_info);
|
||||
if (num_dwords == 1) {
|
||||
SetOutput(addr, data, region, 0);
|
||||
SetOutput(addr, IR::U32{data}, region, 0);
|
||||
} else {
|
||||
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();
|
||||
|
@ -499,7 +501,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
|||
ReadTessControlPointAttribute(addr, stride, ir, i, is_tcs_output_read);
|
||||
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);
|
||||
break;
|
||||
|
@ -578,7 +580,7 @@ void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info) {
|
|||
const IR::F32 component = GetInput(addr, i);
|
||||
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);
|
||||
break;
|
||||
|
|
|
@ -28,6 +28,7 @@ void HullShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
|||
void DomainShaderTransform(IR::Program& program, RuntimeInfo& runtime_info);
|
||||
void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||
const Profile& profile);
|
||||
void SharedMemorySimplifyPass(IR::Program& program, const Profile& profile);
|
||||
void SharedMemoryToStoragePass(IR::Program& program, const RuntimeInfo& runtime_info,
|
||||
const Profile& profile);
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@ struct FormatInfo {
|
|||
AmdGpu::NumberFormat num_format;
|
||||
AmdGpu::CompMapping swizzle;
|
||||
AmdGpu::NumberConversion num_conversion;
|
||||
int num_components;
|
||||
u32 num_components;
|
||||
};
|
||||
|
||||
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);
|
||||
break;
|
||||
case AmdGpu::DataFormat::Format8: {
|
||||
const auto unpacked =
|
||||
ir.Unpack4x8(format_info.num_format, ir.LoadBufferU8(handle, address, info));
|
||||
const auto raw = ir.UConvert(32, ir.LoadBufferU8(handle, address, info));
|
||||
const auto unpacked = ir.Unpack4x8(format_info.num_format, raw);
|
||||
interpreted = ir.CompositeExtract(unpacked, 0);
|
||||
break;
|
||||
}
|
||||
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);
|
||||
interpreted = ir.CompositeConstruct(ir.CompositeExtract(unpacked, 0),
|
||||
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)});
|
||||
break;
|
||||
case AmdGpu::DataFormat::Format16: {
|
||||
const auto unpacked =
|
||||
ir.Unpack2x16(format_info.num_format, ir.LoadBufferU16(handle, address, info));
|
||||
const auto raw = ir.UConvert(32, ir.LoadBufferU16(handle, address, info));
|
||||
const auto unpacked = ir.Unpack2x16(format_info.num_format, raw);
|
||||
interpreted = ir.CompositeExtract(unpacked, 0);
|
||||
break;
|
||||
}
|
||||
|
@ -126,7 +126,7 @@ static void StoreBufferFormat(IR::IREmitter& ir, const IR::Value handle, const I
|
|||
const auto packed =
|
||||
ir.Pack4x8(format_info.num_format, ir.CompositeConstruct(real_value, 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;
|
||||
}
|
||||
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.CompositeExtract(real_value, 1),
|
||||
ir.Imm32(0.f), ir.Imm32(0.f)));
|
||||
ir.StoreBufferU16(handle, address, packed, info);
|
||||
ir.StoreBufferU16(handle, address, ir.UConvert(16, packed), info);
|
||||
break;
|
||||
}
|
||||
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: {
|
||||
const auto packed =
|
||||
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;
|
||||
}
|
||||
case AmdGpu::DataFormat::Format16_16: {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2025 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <unordered_map>
|
||||
#include "shader_recompiler/ir/program.h"
|
||||
|
||||
namespace Shader::Optimization {
|
||||
|
|
|
@ -17,6 +17,8 @@ using SharpLocation = u32;
|
|||
bool IsBufferAtomic(const IR::Inst& inst) {
|
||||
switch (inst.GetOpcode()) {
|
||||
case IR::Opcode::BufferAtomicIAdd32:
|
||||
case IR::Opcode::BufferAtomicIAdd64:
|
||||
case IR::Opcode::BufferAtomicISub32:
|
||||
case IR::Opcode::BufferAtomicSMin32:
|
||||
case IR::Opcode::BufferAtomicUMin32:
|
||||
case IR::Opcode::BufferAtomicSMax32:
|
||||
|
@ -27,6 +29,7 @@ bool IsBufferAtomic(const IR::Inst& inst) {
|
|||
case IR::Opcode::BufferAtomicOr32:
|
||||
case IR::Opcode::BufferAtomicXor32:
|
||||
case IR::Opcode::BufferAtomicSwap32:
|
||||
case IR::Opcode::BufferAtomicCmpSwap32:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -41,6 +44,7 @@ bool IsBufferStore(const IR::Inst& inst) {
|
|||
case IR::Opcode::StoreBufferU32x2:
|
||||
case IR::Opcode::StoreBufferU32x3:
|
||||
case IR::Opcode::StoreBufferU32x4:
|
||||
case IR::Opcode::StoreBufferU64:
|
||||
case IR::Opcode::StoreBufferF32:
|
||||
case IR::Opcode::StoreBufferF32x2:
|
||||
case IR::Opcode::StoreBufferF32x3:
|
||||
|
@ -60,6 +64,7 @@ bool IsBufferInstruction(const IR::Inst& inst) {
|
|||
case IR::Opcode::LoadBufferU32x2:
|
||||
case IR::Opcode::LoadBufferU32x3:
|
||||
case IR::Opcode::LoadBufferU32x4:
|
||||
case IR::Opcode::LoadBufferU64:
|
||||
case IR::Opcode::LoadBufferF32:
|
||||
case IR::Opcode::LoadBufferF32x2:
|
||||
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::StoreBufferU16:
|
||||
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::StoreBufferFormatF32:
|
||||
// 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());
|
||||
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++) {
|
||||
const auto attrib = IR::Attribute::Param0 + (offset / 16);
|
||||
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);
|
||||
offset += 4;
|
||||
}
|
||||
|
@ -91,6 +93,19 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
|
|||
const auto& gs_info = runtime_info.gs_info;
|
||||
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) {
|
||||
const auto opcode = inst.GetOpcode();
|
||||
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 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 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);
|
||||
break;
|
||||
}
|
||||
case IR::Opcode::LoadSharedU16:
|
||||
case IR::Opcode::WriteSharedU16:
|
||||
info.shared_types |= IR::Type::U16;
|
||||
break;
|
||||
case IR::Opcode::LoadSharedU32:
|
||||
case IR::Opcode::LoadSharedU64:
|
||||
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:
|
||||
info.uses_shared = true;
|
||||
case IR::Opcode::SharedAtomicIAdd64:
|
||||
info.shared_types |= IR::Type::U64;
|
||||
break;
|
||||
case IR::Opcode::ConvertF16F32:
|
||||
case IR::Opcode::ConvertF32F16:
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <unordered_set>
|
||||
#include "shader_recompiler/ir/breadth_first_search.h"
|
||||
#include "shader_recompiler/ir/ir_emitter.h"
|
||||
#include "shader_recompiler/ir/program.h"
|
||||
|
@ -9,12 +10,14 @@
|
|||
namespace Shader::Optimization {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// 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 auto insert_barrier =
|
||||
const auto is_divergent_cond =
|
||||
IR::BreadthFirstSearch(cond, [](IR::Inst* inst) -> std::optional<bool> {
|
||||
if (inst->GetOpcode() == IR::Opcode::GetAttributeU32 &&
|
||||
inst->Arg(0).Attribute() == IR::Attribute::LocalInvocationId) {
|
||||
|
@ -61,11 +67,15 @@ static void EmitBarrierInMergeBlock(const IR::AbstractSyntaxNode::Data& data) {
|
|||
}
|
||||
return std::nullopt;
|
||||
});
|
||||
if (insert_barrier) {
|
||||
IR::Block* const merge = data.if_node.merge;
|
||||
auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi);
|
||||
IR::IREmitter ir{*merge, insert_point};
|
||||
ir.Barrier();
|
||||
if (is_divergent_cond) {
|
||||
if (divergence_depth == 0) {
|
||||
IR::Block* const merge = data.if_node.merge;
|
||||
auto insert_point = std::ranges::find_if_not(merge->Instructions(), IR::IsPhi);
|
||||
IR::IREmitter ir{*merge, insert_point};
|
||||
ir.Barrier();
|
||||
}
|
||||
++divergence_depth;
|
||||
divergence_end.emplace(data.if_node.merge);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,19 +97,22 @@ void SharedMemoryBarrierPass(IR::Program& program, const RuntimeInfo& runtime_in
|
|||
return;
|
||||
}
|
||||
using Type = IR::AbstractSyntaxNode::Type;
|
||||
u32 branch_depth{};
|
||||
u32 divergence_depth{};
|
||||
NodeSet divergence_end;
|
||||
for (const IR::AbstractSyntaxNode& node : program.syntax_list) {
|
||||
if (node.type == Type::EndIf) {
|
||||
--branch_depth;
|
||||
if (divergence_end.contains(node.data.end_if.merge)) {
|
||||
--divergence_depth;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// Check if branch depth is zero, we don't want to insert barrier in potentially divergent
|
||||
// code.
|
||||
if (node.type == Type::If && branch_depth++ == 0) {
|
||||
EmitBarrierInMergeBlock(node.data);
|
||||
if (node.type == Type::If) {
|
||||
EmitBarrierInMergeBlock(node.data, divergence_end, divergence_depth);
|
||||
continue;
|
||||
}
|
||||
if (node.type == Type::Block && branch_depth == 0) {
|
||||
if (node.type == Type::Block && divergence_depth == 0) {
|
||||
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) {
|
||||
const auto opcode = inst.GetOpcode();
|
||||
switch (opcode) {
|
||||
case IR::Opcode::LoadSharedU16:
|
||||
case IR::Opcode::LoadSharedU32:
|
||||
case IR::Opcode::LoadSharedU64:
|
||||
case IR::Opcode::WriteSharedU16:
|
||||
case IR::Opcode::WriteSharedU32:
|
||||
case IR::Opcode::WriteSharedU64:
|
||||
case IR::Opcode::SharedAtomicAnd32:
|
||||
case IR::Opcode::SharedAtomicIAdd32:
|
||||
case IR::Opcode::SharedAtomicOr32:
|
||||
case IR::Opcode::SharedAtomicSMax32:
|
||||
case IR::Opcode::SharedAtomicUMax32:
|
||||
case IR::Opcode::SharedAtomicIAdd64:
|
||||
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:
|
||||
return true;
|
||||
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,
|
||||
const Profile& profile) {
|
||||
if (program.info.stage != Stage::Compute) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
// 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());
|
||||
program.info.buffers.push_back({
|
||||
.used_types = IR::Type::U32,
|
||||
.used_types = used_types,
|
||||
.inline_cbuf = AmdGpu::Buffer::Null(),
|
||||
.buffer_type = BufferType::SharedMemory,
|
||||
.is_written = true,
|
||||
});
|
||||
|
||||
for (IR::Block* const block : program.blocks) {
|
||||
for (IR::Inst& inst : block->Instructions()) {
|
||||
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)};
|
||||
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),
|
||||
ir.Imm32(shared_memory_size));
|
||||
const IR::U32 address = ir.IAdd(IR::U32{inst.Arg(0)}, offset);
|
||||
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:
|
||||
inst.ReplaceUsesWithAndRemove(ir.LoadBufferU32(1, handle, address, {}));
|
||||
break;
|
||||
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;
|
||||
case IR::Opcode::WriteSharedU32:
|
||||
ir.StoreBufferU32(1, handle, address, inst.Arg(1), {});
|
||||
inst.Invalidate();
|
||||
break;
|
||||
case IR::Opcode::WriteSharedU64:
|
||||
ir.StoreBufferU32(2, handle, address, inst.Arg(1), {});
|
||||
ir.StoreBufferU64(handle, address, IR::U64{inst.Arg(1)}, {});
|
||||
inst.Invalidate();
|
||||
break;
|
||||
default:
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
#include "common/bit_field.h"
|
||||
#include "common/enum.h"
|
||||
#include "common/types.h"
|
||||
#include "video_core/amdgpu/types.h"
|
||||
#include "video_core/amdgpu/pixel_format.h"
|
||||
|
||||
namespace Shader::IR {
|
||||
|
||||
|
|
|
@ -265,6 +265,7 @@ using U32F32 = TypedValue<Type::U32 | Type::F32>;
|
|||
using U64F64 = TypedValue<Type::U64 | Type::F64>;
|
||||
using U32U64 = TypedValue<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 F16F32F64 = TypedValue<Type::F16 | Type::F32 | Type::F64>;
|
||||
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_flush{};
|
||||
bool support_fp32_round_to_zero{};
|
||||
bool support_explicit_workgroup_layout{};
|
||||
bool support_legacy_vertex_attributes{};
|
||||
bool supports_image_load_store_lod{};
|
||||
bool supports_native_cube_calc{};
|
||||
bool supports_trinary_minmax{};
|
||||
bool supports_robust_buffer_access{};
|
||||
bool supports_image_fp32_atomic_min_max{};
|
||||
bool supports_workgroup_explicit_memory_layout{};
|
||||
bool has_broken_spirv_clamp{};
|
||||
bool lower_left_origin_mode{};
|
||||
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::ResourceTrackingPass(program);
|
||||
Shader::Optimization::LowerBufferFormatToRaw(program);
|
||||
Shader::Optimization::SharedMemorySimplifyPass(program, profile);
|
||||
Shader::Optimization::SharedMemoryToStoragePass(program, runtime_info, profile);
|
||||
Shader::Optimization::SharedMemoryBarrierPass(program, runtime_info, profile);
|
||||
Shader::Optimization::IdentityRemovalPass(program.blocks);
|
||||
|
|
|
@ -149,6 +149,7 @@ struct GeometryRuntimeInfo {
|
|||
u32 out_vertex_data_size{};
|
||||
AmdGpu::PrimitiveType in_primitive;
|
||||
GsOutputPrimTypes out_primitive;
|
||||
AmdGpu::Liverpool::GsMode::Mode mode;
|
||||
std::span<const u32> vs_copy;
|
||||
u64 vs_copy_hash;
|
||||
|
||||
|
@ -196,11 +197,13 @@ struct FragmentRuntimeInfo {
|
|||
u32 num_inputs;
|
||||
std::array<PsInput, 32> inputs;
|
||||
std::array<PsColorBuffer, MaxColorBuffers> color_buffers;
|
||||
bool dual_source_blending;
|
||||
|
||||
bool operator==(const FragmentRuntimeInfo& other) const noexcept {
|
||||
return std::ranges::equal(color_buffers, other.color_buffers) &&
|
||||
en_flags.raw == other.en_flags.raw && addr_flags.raw == other.addr_flags.raw &&
|
||||
num_inputs == other.num_inputs &&
|
||||
dual_source_blending == other.dual_source_blending &&
|
||||
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
|
||||
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;
|
||||
|
||||
switch (type) {
|
||||
default:
|
||||
UNREACHABLE_MSG("Wrong PM4 type {}", type);
|
||||
break;
|
||||
case 0:
|
||||
case 1:
|
||||
UNREACHABLE_MSG("Unsupported PM4 type {}", type);
|
||||
UNREACHABLE_MSG("Unimplemented PM4 type 0, base reg: {}, size: {}",
|
||||
header->type0.base.Value(), header->type0.NumWords());
|
||||
break;
|
||||
case 2:
|
||||
// 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
// No other types of packets were spotted so far
|
||||
UNREACHABLE_MSG("Invalid PM4 type {}", header->type.Value());
|
||||
|
|
|
@ -914,7 +914,7 @@ struct Liverpool {
|
|||
}
|
||||
|
||||
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 =
|
||||
num_bytes_per_element * (slice.tile_max + 1) * 64u * NumSamples();
|
||||
return slice_size;
|
||||
|
@ -1179,8 +1179,16 @@ struct Liverpool {
|
|||
};
|
||||
|
||||
union GsMode {
|
||||
enum class Mode : u32 {
|
||||
Off = 0,
|
||||
ScenarioA = 1,
|
||||
ScenarioB = 2,
|
||||
ScenarioG = 3,
|
||||
ScenarioC = 4,
|
||||
};
|
||||
|
||||
u32 raw;
|
||||
BitField<0, 3, u32> mode;
|
||||
BitField<0, 3, Mode> mode;
|
||||
BitField<3, 2, u32> cut_mode;
|
||||
BitField<22, 2, u32> onchip;
|
||||
};
|
||||
|
|
|
@ -111,136 +111,106 @@ std::string_view NameOf(NumberFormat fmt) {
|
|||
}
|
||||
}
|
||||
|
||||
int NumComponents(DataFormat format) {
|
||||
constexpr std::array num_components_per_element = {
|
||||
0, 1, 1, 2, 1, 2, 3, 3, 4, 4, 4, 2, 4, 3, 4, -1, 3, 4, 4, 4, 2,
|
||||
2, 2, -1, -1, -1, -1, -1, -1, -1, -1, -1, 3, 3, 3, 4, 4, 4, 1, 2, 3, 4,
|
||||
-1, -1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 1, 1};
|
||||
|
||||
const u32 index = static_cast<u32>(format);
|
||||
if (index >= num_components_per_element.size()) {
|
||||
return 0;
|
||||
}
|
||||
return num_components_per_element[index];
|
||||
}
|
||||
|
||||
int NumBits(DataFormat format) {
|
||||
const std::array num_bits_per_element = {
|
||||
0, 8, 16, 16, 32, 32, 32, 32, 32, 32, 32, 64, 64, 96, 128, -1, 16, 16, 16, 16, 32,
|
||||
32, 64, -1, -1, -1, -1, -1, -1, -1, -1, -1, 16, 16, 32, 4, 8, 8, 4, 8, 8, 8,
|
||||
-1, -1, 8, 8, 8, 8, 8, 8, 16, 16, 32, 32, 32, 64, 64, 8, 16, 1, 1};
|
||||
|
||||
const u32 index = static_cast<u32>(format);
|
||||
if (index >= num_bits_per_element.size()) {
|
||||
return 0;
|
||||
}
|
||||
return num_bits_per_element[index];
|
||||
}
|
||||
|
||||
static constexpr std::array component_bits = {
|
||||
std::array{0, 0, 0, 0}, // 0 FormatInvalid
|
||||
std::array{8, 0, 0, 0}, // 1 Format8
|
||||
std::array{16, 0, 0, 0}, // 2 Format16
|
||||
std::array{8, 8, 0, 0}, // 3 Format8_8
|
||||
std::array{32, 0, 0, 0}, // 4 Format32
|
||||
std::array{16, 16, 0, 0}, // 5 Format16_16
|
||||
std::array{11, 11, 10, 0}, // 6 Format10_11_11
|
||||
std::array{10, 11, 11, 0}, // 7 Format11_11_10
|
||||
std::array{2, 10, 10, 10}, // 8 Format10_10_10_2
|
||||
std::array{10, 10, 10, 2}, // 9 Format2_10_10_10
|
||||
std::array{8, 8, 8, 8}, // 10 Format8_8_8_8
|
||||
std::array{32, 32, 0, 0}, // 11 Format32_32
|
||||
std::array{16, 16, 16, 16}, // 12 Format16_16_16_16
|
||||
std::array{32, 32, 32, 0}, // 13 Format32_32_32
|
||||
std::array{32, 32, 32, 32}, // 14 Format32_32_32_32
|
||||
std::array{0, 0, 0, 0}, // 15
|
||||
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
|
||||
static constexpr std::array NUM_COMPONENTS = {
|
||||
0, // 0 FormatInvalid
|
||||
1, // 1 Format8
|
||||
1, // 2 Format16
|
||||
2, // 3 Format8_8
|
||||
1, // 4 Format32
|
||||
2, // 5 Format16_16
|
||||
3, // 6 Format10_11_11
|
||||
3, // 7 Format11_11_10
|
||||
4, // 8 Format10_10_10_2
|
||||
4, // 9 Format2_10_10_10
|
||||
4, // 10 Format8_8_8_8
|
||||
2, // 11 Format32_32
|
||||
4, // 12 Format16_16_16_16
|
||||
3, // 13 Format32_32_32
|
||||
4, // 14 Format32_32_32_32
|
||||
0, // 15
|
||||
3, // 16 Format5_6_5
|
||||
4, // 17 Format1_5_5_5
|
||||
4, // 18 Format5_5_5_1
|
||||
4, // 19 Format4_4_4_4
|
||||
2, // 20 Format8_24
|
||||
2, // 21 Format24_8
|
||||
2, // 22 FormatX24_8_32
|
||||
0, // 23
|
||||
0, // 24
|
||||
0, // 25
|
||||
0, // 26
|
||||
0, // 27
|
||||
0, // 28
|
||||
0, // 29
|
||||
0, // 30
|
||||
0, // 31
|
||||
3, // 32 FormatGB_GR
|
||||
3, // 33 FormatBG_RG
|
||||
4, // 34 Format5_9_9_9
|
||||
4, // 35 FormatBc1
|
||||
4, // 36 FormatBc2
|
||||
4, // 37 FormatBc3
|
||||
1, // 38 FormatBc4
|
||||
2, // 39 FormatBc5
|
||||
3, // 40 FormatBc6
|
||||
4, // 41 FormatBc7
|
||||
};
|
||||
|
||||
u32 ComponentBits(DataFormat format, u32 comp) {
|
||||
u32 NumComponents(DataFormat format) {
|
||||
const u32 index = static_cast<u32>(format);
|
||||
if (index >= component_bits.size() || comp >= 4) {
|
||||
return 0;
|
||||
}
|
||||
return component_bits[index][comp];
|
||||
ASSERT_MSG(index < NUM_COMPONENTS.size(), "Invalid data format = {}", format);
|
||||
return NUM_COMPONENTS[index];
|
||||
}
|
||||
|
||||
static constexpr std::array component_offset = {
|
||||
std::array{-1, -1, -1, -1}, // 0 FormatInvalid
|
||||
std::array{0, -1, -1, -1}, // 1 Format8
|
||||
std::array{0, -1, -1, -1}, // 2 Format16
|
||||
std::array{0, 8, -1, -1}, // 3 Format8_8
|
||||
std::array{0, -1, -1, -1}, // 4 Format32
|
||||
std::array{0, 16, -1, -1}, // 5 Format16_16
|
||||
std::array{0, 11, 22, -1}, // 6 Format10_11_11
|
||||
std::array{0, 10, 21, -1}, // 7 Format11_11_10
|
||||
std::array{0, 2, 12, 22}, // 8 Format10_10_10_2
|
||||
std::array{0, 10, 20, 30}, // 9 Format2_10_10_10
|
||||
std::array{0, 8, 16, 24}, // 10 Format8_8_8_8
|
||||
std::array{0, 32, -1, -1}, // 11 Format32_32
|
||||
std::array{0, 16, 32, 48}, // 12 Format16_16_16_16
|
||||
std::array{0, 32, 64, -1}, // 13 Format32_32_32
|
||||
std::array{0, 32, 64, 96}, // 14 Format32_32_32_32
|
||||
std::array{-1, -1, -1, -1}, // 15
|
||||
std::array{0, 5, 11, -1}, // 16 Format5_6_5
|
||||
std::array{0, 5, 10, 15}, // 17 Format1_5_5_5
|
||||
std::array{0, 1, 6, 11}, // 18 Format5_5_5_1
|
||||
std::array{0, 4, 8, 12}, // 19 Format4_4_4_4
|
||||
std::array{0, 24, -1, -1}, // 20 Format8_24
|
||||
std::array{0, 8, -1, -1}, // 21 Format24_8
|
||||
std::array{0, 8, -1, -1}, // 22 FormatX24_8_32
|
||||
std::array{-1, -1, -1, -1}, // 23
|
||||
std::array{-1, -1, -1, -1}, // 24
|
||||
std::array{-1, -1, -1, -1}, // 25
|
||||
std::array{-1, -1, -1, -1}, // 26
|
||||
std::array{-1, -1, -1, -1}, // 27
|
||||
std::array{-1, -1, -1, -1}, // 28
|
||||
std::array{-1, -1, -1, -1}, // 29
|
||||
std::array{-1, -1, -1, -1}, // 30
|
||||
std::array{-1, -1, -1, -1}, // 31
|
||||
std::array{-1, -1, -1, -1}, // 32 FormatGB_GR
|
||||
std::array{-1, -1, -1, -1}, // 33 FormatBG_RG
|
||||
std::array{-1, -1, -1, -1}, // 34 Format5_9_9_9
|
||||
std::array{-1, -1, -1, -1}, // 35 FormatBc1
|
||||
std::array{-1, -1, -1, -1}, // 36 FormatBc2
|
||||
std::array{-1, -1, -1, -1}, // 37 FormatBc3
|
||||
std::array{-1, -1, -1, -1}, // 38 FormatBc4
|
||||
std::array{-1, -1, -1, -1}, // 39 FormatBc5
|
||||
std::array{-1, -1, -1, -1}, // 40 FormatBc6
|
||||
std::array{-1, -1, -1, -1}, // 41 FormatBc7
|
||||
static constexpr std::array BITS_PER_BLOCK = {
|
||||
0, // 0 FormatInvalid
|
||||
8, // 1 Format8
|
||||
16, // 2 Format16
|
||||
16, // 3 Format8_8
|
||||
32, // 4 Format32
|
||||
32, // 5 Format16_16
|
||||
32, // 6 Format10_11_11
|
||||
32, // 7 Format11_11_10
|
||||
32, // 8 Format10_10_10_2
|
||||
32, // 9 Format2_10_10_10
|
||||
32, // 10 Format8_8_8_8
|
||||
64, // 11 Format32_32
|
||||
64, // 12 Format16_16_16_16
|
||||
96, // 13 Format32_32_32
|
||||
128, // 14 Format32_32_32_32
|
||||
0, // 15
|
||||
16, // 16 Format5_6_5
|
||||
16, // 17 Format1_5_5_5
|
||||
16, // 18 Format5_5_5_1
|
||||
16, // 19 Format4_4_4_4
|
||||
32, // 20 Format8_24
|
||||
32, // 21 Format24_8
|
||||
64, // 22 FormatX24_8_32
|
||||
0, // 23
|
||||
0, // 24
|
||||
0, // 25
|
||||
0, // 26
|
||||
0, // 27
|
||||
0, // 28
|
||||
0, // 29
|
||||
0, // 30
|
||||
0, // 31
|
||||
16, // 32 FormatGB_GR
|
||||
16, // 33 FormatBG_RG
|
||||
32, // 34 Format5_9_9_9
|
||||
64, // 35 FormatBc1
|
||||
128, // 36 FormatBc2
|
||||
128, // 37 FormatBc3
|
||||
64, // 38 FormatBc4
|
||||
128, // 39 FormatBc5
|
||||
128, // 40 FormatBc6
|
||||
128, // 41 FormatBc7
|
||||
};
|
||||
|
||||
s32 ComponentOffset(DataFormat format, u32 comp) {
|
||||
u32 NumBitsPerBlock(DataFormat format) {
|
||||
const u32 index = static_cast<u32>(format);
|
||||
if (index >= component_offset.size() || comp >= 4) {
|
||||
return -1;
|
||||
}
|
||||
return component_offset[index][comp];
|
||||
ASSERT_MSG(index < BITS_PER_BLOCK.size(), "Invalid data format = {}", format);
|
||||
return BITS_PER_BLOCK[index];
|
||||
}
|
||||
|
||||
} // namespace AmdGpu
|
||||
|
|
|
@ -5,39 +5,313 @@
|
|||
|
||||
#include <string_view>
|
||||
#include <fmt/format.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
#include "video_core/amdgpu/types.h"
|
||||
|
||||
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,
|
||||
Sint,
|
||||
Uint,
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr NumberClass GetNumberClass(const NumberFormat nfmt) {
|
||||
switch (nfmt) {
|
||||
case NumberFormat::Sint:
|
||||
return Sint;
|
||||
case NumberFormat::Uint:
|
||||
return Uint;
|
||||
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,
|
||||
};
|
||||
|
||||
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 Float;
|
||||
return format;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr bool IsInteger(const NumberFormat nfmt) {
|
||||
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;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string_view NameOf(DataFormat fmt);
|
||||
[[nodiscard]] std::string_view NameOf(NumberFormat fmt);
|
||||
std::string_view NameOf(DataFormat fmt);
|
||||
std::string_view NameOf(NumberFormat fmt);
|
||||
|
||||
int NumComponents(DataFormat format);
|
||||
int NumBits(DataFormat format);
|
||||
u32 ComponentBits(DataFormat format, u32 comp);
|
||||
s32 ComponentOffset(DataFormat format, u32 comp);
|
||||
u32 NumComponents(DataFormat format);
|
||||
u32 NumBitsPerBlock(DataFormat format);
|
||||
|
||||
} // namespace AmdGpu
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/types.h"
|
||||
#include "video_core/amdgpu/pixel_format.h"
|
||||
|
||||
namespace AmdGpu {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
|
||||
#include <string_view>
|
||||
#include <fmt/format.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/types.h"
|
||||
|
||||
namespace AmdGpu {
|
||||
|
@ -114,281 +113,6 @@ enum class GsOutputPrimitiveType : u32 {
|
|||
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
|
||||
|
||||
template <>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include "common/debug.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/amdgpu/liverpool.h"
|
||||
#include "video_core/buffer_cache/buffer_cache.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 UboStreamBufferSize = 128_MB;
|
||||
static constexpr size_t DownloadBufferSize = 128_MB;
|
||||
static constexpr size_t DeviceBufferSize = 128_MB;
|
||||
static constexpr size_t MaxPageFaults = 1024;
|
||||
|
||||
BufferCache::BufferCache(const Vulkan::Instance& instance_, Vulkan::Scheduler& scheduler_,
|
||||
Vulkan::Rasterizer& rasterizer_, AmdGpu::Liverpool* liverpool_,
|
||||
TextureCache& texture_cache_, PageManager& tracker_)
|
||||
: 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},
|
||||
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},
|
||||
bda_pagetable_buffer{instance, scheduler, MemoryUsage::DeviceLocal,
|
||||
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) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
@ -347,7 +350,7 @@ std::pair<Buffer*, u32> BufferCache::ObtainBuffer(VAddr device_addr, u32 size, b
|
|||
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.
|
||||
const u64 page = gpu_addr >> CACHING_PAGEBITS;
|
||||
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,
|
||||
// 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 (prefer_gpu && memory_tracker.IsRegionGpuModified(gpu_addr, size)) {
|
||||
if (memory_tracker.IsRegionGpuModified(gpu_addr, size)) {
|
||||
return ObtainBuffer(gpu_addr, size, false, false);
|
||||
}
|
||||
|
||||
// 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};
|
||||
}
|
||||
|
||||
|
@ -798,24 +803,45 @@ void BufferCache::SynchronizeBuffer(Buffer& buffer, VAddr device_addr, u32 size,
|
|||
}
|
||||
|
||||
bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, VAddr device_addr, u32 size) {
|
||||
static constexpr FindFlags find_flags =
|
||||
FindFlags::NoCreate | FindFlags::RelaxDim | FindFlags::RelaxFmt | FindFlags::RelaxSize;
|
||||
TextureCache::BaseDesc desc{};
|
||||
desc.info.guest_address = device_addr;
|
||||
desc.info.guest_size = size;
|
||||
const ImageId image_id = texture_cache.FindImage(desc, find_flags);
|
||||
if (!image_id) {
|
||||
boost::container::small_vector<ImageId, 6> image_ids;
|
||||
texture_cache.ForEachImageInRegion(device_addr, size, [&](ImageId image_id, Image& image) {
|
||||
if (image.info.guest_address != device_addr) {
|
||||
return;
|
||||
}
|
||||
// Only perform sync if image is:
|
||||
// - GPU modified; otherwise there are no changes to synchronize.
|
||||
// - 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.
|
||||
if (False(image.flags & ImageFlagBits::GpuModified) ||
|
||||
True(image.flags & ImageFlagBits::Dirty)) {
|
||||
return;
|
||||
}
|
||||
image_ids.push_back(image_id);
|
||||
});
|
||||
if (image_ids.empty()) {
|
||||
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);
|
||||
// Only perform sync if image is:
|
||||
// - GPU modified; otherwise there are no changes to synchronize.
|
||||
// - 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.
|
||||
if (False(image.flags & ImageFlagBits::GpuModified) ||
|
||||
True(image.flags & ImageFlagBits::Dirty)) {
|
||||
return false;
|
||||
}
|
||||
ASSERT_MSG(device_addr == image.info.guest_address,
|
||||
"Texel buffer aliases image subresources {:x} : {:x}", device_addr,
|
||||
image.info.guest_address);
|
||||
|
|
|
@ -17,6 +17,10 @@ namespace AmdGpu {
|
|||
struct Liverpool;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
class MemoryManager;
|
||||
}
|
||||
|
||||
namespace Shader {
|
||||
namespace Gcn {
|
||||
struct FetchShaderData;
|
||||
|
@ -76,11 +80,6 @@ public:
|
|||
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.
|
||||
[[nodiscard]] Buffer* GetBdaPageTableBuffer() noexcept {
|
||||
return &bda_pagetable_buffer;
|
||||
|
@ -96,6 +95,20 @@ public:
|
|||
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.
|
||||
void InvalidateMemory(VAddr device_addr, u64 size, bool unmap);
|
||||
|
||||
|
@ -117,8 +130,7 @@ public:
|
|||
BufferId buffer_id = {});
|
||||
|
||||
/// Attempts to obtain a buffer without modifying the cache contents.
|
||||
[[nodiscard]] std::pair<Buffer*, u32> ObtainViewBuffer(VAddr gpu_addr, u32 size,
|
||||
bool prefer_gpu);
|
||||
[[nodiscard]] std::pair<Buffer*, u32> ObtainBufferForImage(VAddr gpu_addr, u32 size);
|
||||
|
||||
/// Return true when a region is registered on the cache
|
||||
[[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
|
||||
|
@ -183,11 +195,13 @@ private:
|
|||
Vulkan::Scheduler& scheduler;
|
||||
Vulkan::Rasterizer& rasterizer;
|
||||
AmdGpu::Liverpool* liverpool;
|
||||
Core::MemoryManager* memory;
|
||||
TextureCache& texture_cache;
|
||||
PageManager& tracker;
|
||||
StreamBuffer staging_buffer;
|
||||
StreamBuffer stream_buffer;
|
||||
StreamBuffer download_buffer;
|
||||
StreamBuffer device_buffer;
|
||||
Buffer gds_buffer;
|
||||
Buffer bda_pagetable_buffer;
|
||||
Buffer fault_buffer;
|
||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
|||
uint num_levels;
|
||||
uint pitch;
|
||||
uint height;
|
||||
uint sizes[14];
|
||||
uint sizes[16];
|
||||
} info;
|
||||
|
||||
// Inverse morton LUT, small enough to fit into K$
|
||||
|
|
|
@ -18,7 +18,7 @@ layout(push_constant) uniform image_info {
|
|||
uint num_levels;
|
||||
uint pitch;
|
||||
uint height;
|
||||
uint sizes[14];
|
||||
uint sizes[16];
|
||||
} info;
|
||||
|
||||
#define MICRO_TILE_DIM 8
|
||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
|||
uint num_levels;
|
||||
uint pitch;
|
||||
uint height;
|
||||
uint sizes[14];
|
||||
uint sizes[16];
|
||||
} info;
|
||||
|
||||
// Inverse morton LUT, small enough to fit into K$
|
||||
|
|
|
@ -16,7 +16,7 @@ layout(push_constant) uniform image_info {
|
|||
uint num_levels;
|
||||
uint pitch;
|
||||
uint height;
|
||||
uint sizes[14];
|
||||
uint sizes[16];
|
||||
} info;
|
||||
|
||||
// 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