Merge branch 'main' into ime

This commit is contained in:
Lander Gallastegi 2024-10-10 20:07:18 +02:00 committed by GitHub
commit 8e6ebc56e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
120 changed files with 2805 additions and 993 deletions

View File

@ -9,6 +9,8 @@ fi
export Qt6_DIR="/usr/lib/qt6" export Qt6_DIR="/usr/lib/qt6"
export PATH="$Qt6_DIR/bin:$PATH" export PATH="$Qt6_DIR/bin:$PATH"
export EXTRA_QT_PLUGINS="waylandcompositor"
export EXTRA_PLATFORM_PLUGINS="libqwayland-egl.so;libqwayland-generic.so"
# Prepare Tools for building the AppImage # Prepare Tools for building the AppImage
wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage

View File

@ -287,7 +287,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration - name: Cache CMake Configuration
uses: actions/cache@v4 uses: actions/cache@v4
@ -343,7 +343,7 @@ jobs:
submodules: recursive submodules: recursive
- name: Install dependencies - name: Install dependencies
run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev run: sudo apt-get update && sudo apt install -y libx11-dev libxext-dev libwayland-dev libdecor-0-dev libxkbcommon-dev libglfw3-dev libgles2-mesa-dev libfuse2 clang build-essential qt6-base-dev qt6-tools-dev qt6-multimedia-dev libasound2-dev libpulse-dev libopenal-dev
- name: Cache CMake Configuration - name: Cache CMake Configuration
uses: actions/cache@v4 uses: actions/cache@v4

5
.gitmodules vendored
View File

@ -94,4 +94,7 @@
[submodule "externals/pugixml"] [submodule "externals/pugixml"]
path = externals/pugixml path = externals/pugixml
url = https://github.com/zeux/pugixml.git url = https://github.com/zeux/pugixml.git
shallow = true shallow = true
[submodule "externals/discord-rpc"]
path = externals/discord-rpc
url = https://github.com/shadps4-emu/ext-discord-rpc

View File

@ -326,6 +326,10 @@ set(USBD_LIB src/core/libraries/usbd/usbd.cpp
src/core/libraries/usbd/usbd.h src/core/libraries/usbd/usbd.h
) )
set(FIBER_LIB src/core/libraries/fiber/fiber.cpp
src/core/libraries/fiber/fiber.h
)
set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp set(NP_LIBS src/core/libraries/np_manager/np_manager.cpp
src/core/libraries/np_manager/np_manager.h src/core/libraries/np_manager/np_manager.h
src/core/libraries/np_score/np_score.cpp src/core/libraries/np_score/np_score.cpp
@ -376,6 +380,8 @@ set(COMMON src/common/logging/backend.cpp
src/common/debug.h src/common/debug.h
src/common/decoder.cpp src/common/decoder.cpp
src/common/decoder.h src/common/decoder.h
src/common/discord_rpc_handler.cpp
src/common/discord_rpc_handler.h
src/common/elf_info.h src/common/elf_info.h
src/common/endian.h src/common/endian.h
src/common/enum.h src/common/enum.h
@ -464,6 +470,7 @@ set(CORE src/core/aerolib/stubs.cpp
${USBD_LIB} ${USBD_LIB}
${MISC_LIBS} ${MISC_LIBS}
${DIALOGS_LIB} ${DIALOGS_LIB}
${FIBER_LIB}
${DEV_TOOLS} ${DEV_TOOLS}
src/core/debug_state.cpp src/core/debug_state.cpp
src/core/debug_state.h src/core/debug_state.h
@ -693,6 +700,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp
src/qt_gui/game_grid_frame.h src/qt_gui/game_grid_frame.h
src/qt_gui/game_install_dialog.cpp src/qt_gui/game_install_dialog.cpp
src/qt_gui/game_install_dialog.h src/qt_gui/game_install_dialog.h
src/qt_gui/install_dir_select.cpp
src/qt_gui/install_dir_select.h
src/qt_gui/pkg_viewer.cpp src/qt_gui/pkg_viewer.cpp
src/qt_gui/pkg_viewer.h src/qt_gui/pkg_viewer.h
src/qt_gui/trophy_viewer.cpp src/qt_gui/trophy_viewer.cpp
@ -859,4 +868,7 @@ if (UNIX AND NOT APPLE)
find_package(OpenSSL REQUIRED) find_package(OpenSSL REQUIRED)
target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES}) target_link_libraries(shadps4 PRIVATE ${OPENSSL_LIBRARIES})
endif() endif()
endif() endif()
# Discord RPC
target_link_libraries(shadps4 PRIVATE discord-rpc)

View File

@ -102,7 +102,7 @@ PAD DOWN | DOWN |
PAD LEFT | LEFT | PAD LEFT | LEFT |
PAD RIGHT | RIGHT | PAD RIGHT | RIGHT |
OPTIONS | RETURN | OPTIONS | RETURN |
TOUCH PAD | SPACE | BACK BUTTON / TOUCH PAD | SPACE |
L1 | Q | L1 | Q |
R1 | U | R1 | U |
L2 | E | L2 | E |

View File

@ -3,7 +3,10 @@
set(BUILD_SHARED_LIBS OFF) set(BUILD_SHARED_LIBS OFF)
set(BUILD_TESTING OFF) set(BUILD_TESTING OFF)
set_property(DIRECTORY PROPERTY EXCLUDE_FROM_ALL ON) set_directory_properties(PROPERTIES
EXCLUDE_FROM_ALL ON
SYSTEM ON
)
if (MSVC) if (MSVC)
# Silence "deprecation" warnings # Silence "deprecation" warnings
@ -13,11 +16,8 @@ endif()
# Boost # Boost
if (NOT TARGET Boost::headers) if (NOT TARGET Boost::headers)
set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/ext-boost" CACHE STRING "") set(BOOST_ROOT "${CMAKE_SOURCE_DIR}/externals/ext-boost" CACHE STRING "")
set(Boost_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/externals/ext-boost" CACHE STRING "")
set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "") set(Boost_NO_SYSTEM_PATHS ON CACHE BOOL "")
add_library(boost INTERFACE) add_subdirectory(ext-boost)
target_include_directories(boost SYSTEM INTERFACE ${Boost_INCLUDE_DIR})
add_library(Boost::headers ALIAS boost)
endif() endif()
# fmtlib # fmtlib
@ -77,7 +77,7 @@ endif()
# RenderDoc # RenderDoc
if (NOT TARGET RenderDoc::API) if (NOT TARGET RenderDoc::API)
add_library(renderdoc INTERFACE) add_library(renderdoc INTERFACE)
target_include_directories(renderdoc SYSTEM INTERFACE ./renderdoc) target_include_directories(renderdoc INTERFACE ./renderdoc)
add_library(RenderDoc::API ALIAS renderdoc) add_library(RenderDoc::API ALIAS renderdoc)
endif() endif()
@ -184,5 +184,10 @@ if (NOT TARGET pugixml::pugixml)
add_subdirectory(pugixml) add_subdirectory(pugixml)
endif() endif()
# Discord RPC
set(BUILD_EXAMPLES OFF)
add_subdirectory(discord-rpc/)
target_include_directories(discord-rpc INTERFACE discord-rpc/include)
# GCN Headers # GCN Headers
add_subdirectory(gcn) add_subdirectory(gcn)

2
externals/date vendored

@ -1 +1 @@
Subproject commit 51ce7e131079c061533d741be5fe7cca57f2faac Subproject commit dd8affc6de5755e07638bf0a14382d29549d6ee9

1
externals/discord-rpc vendored Submodule

@ -0,0 +1 @@
Subproject commit 4ec218155d73bcb8022f8f7ca72305d801f84beb

2
externals/ext-boost vendored

@ -1 +1 @@
Subproject commit a04136add1e469f46d8ae8d3e8307779240a5c53 Subproject commit f2474e1b584fb7a3ed6f85ba875e6eacd742ec8a

2
externals/sdl3 vendored

@ -1 +1 @@
Subproject commit 0548050fc5a4edf1f47c3633c2cd06d8762b5532 Subproject commit 54e622c2e6af456bfef382fae44c17682d5ac88a

2
externals/sirit vendored

@ -1 +1 @@
Subproject commit 37090c74cc6e680f2bc334cac8fd182f7634a1f6 Subproject commit 6cecb95d679c82c413d1f989e0b7ad9af130600d

View File

@ -103,7 +103,7 @@ s32 SDLAudio::AudioOutOutput(s32 handle, const void* ptr) {
const size_t data_size = port.samples_num * port.sample_size * port.channels_num; const size_t data_size = port.samples_num * port.sample_size * port.channels_num;
SDL_bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size); bool result = SDL_PutAudioStreamData(port.stream, ptr, data_size);
lock.unlock(); // Unlock only after necessary operations lock.unlock(); // Unlock only after necessary operations

View File

@ -34,6 +34,7 @@ static bool isNeo = false;
static bool isFullscreen = false; static bool isFullscreen = false;
static bool playBGM = false; static bool playBGM = false;
static int BGMvolume = 50; static int BGMvolume = 50;
static bool enableDiscordRPC = false;
static u32 screenWidth = 1280; static u32 screenWidth = 1280;
static u32 screenHeight = 720; static u32 screenHeight = 720;
static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select static s32 gpuId = -1; // Vulkan physical device index. Set to negative for auto select
@ -41,6 +42,7 @@ static std::string logFilter;
static std::string logType = "async"; static std::string logType = "async";
static std::string userName = "shadPS4"; static std::string userName = "shadPS4";
static std::string updateChannel; static std::string updateChannel;
static std::string backButtonBehavior = "left";
static bool useSpecialPad = false; static bool useSpecialPad = false;
static int specialPadClass = 1; static int specialPadClass = 1;
static bool isDebugDump = false; static bool isDebugDump = false;
@ -56,9 +58,11 @@ static bool vkValidationGpu = false;
static bool rdocEnable = false; static bool rdocEnable = false;
static bool vkMarkers = false; static bool vkMarkers = false;
static bool vkCrashDiagnostic = false; static bool vkCrashDiagnostic = false;
static s16 cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default)
// Gui // Gui
std::filesystem::path settings_install_dir = {}; std::vector<std::filesystem::path> settings_install_dirs = {};
std::filesystem::path settings_addon_install_dir = {}; std::filesystem::path settings_addon_install_dir = {};
u32 main_window_geometry_x = 400; u32 main_window_geometry_x = 400;
u32 main_window_geometry_y = 400; u32 main_window_geometry_y = 400;
@ -95,6 +99,18 @@ int getBGMvolume() {
return BGMvolume; return BGMvolume;
} }
bool getEnableDiscordRPC() {
return enableDiscordRPC;
}
s16 getCursorState() {
return cursorState;
}
int getCursorHideTimeout() {
return cursorHideTimeout;
}
u32 getScreenWidth() { u32 getScreenWidth() {
return screenWidth; return screenWidth;
} }
@ -123,6 +139,10 @@ std::string getUpdateChannel() {
return updateChannel; return updateChannel;
} }
std::string getBackButtonBehavior() {
return backButtonBehavior;
}
bool getUseSpecialPad() { bool getUseSpecialPad() {
return useSpecialPad; return useSpecialPad;
} }
@ -251,6 +271,18 @@ void setBGMvolume(int volume) {
BGMvolume = volume; BGMvolume = volume;
} }
void setEnableDiscordRPC(bool enable) {
enableDiscordRPC = enable;
}
void setCursorState(s16 newCursorState) {
cursorState = newCursorState;
}
void setCursorHideTimeout(int newcursorHideTimeout) {
cursorHideTimeout = newcursorHideTimeout;
}
void setLanguage(u32 language) { void setLanguage(u32 language) {
m_language = language; m_language = language;
} }
@ -275,6 +307,10 @@ void setUpdateChannel(const std::string& type) {
updateChannel = type; updateChannel = type;
} }
void setBackButtonBehavior(const std::string& type) {
backButtonBehavior = type;
}
void setUseSpecialPad(bool use) { void setUseSpecialPad(bool use) {
useSpecialPad = use; useSpecialPad = use;
} }
@ -289,8 +325,9 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) {
main_window_geometry_w = w; main_window_geometry_w = w;
main_window_geometry_h = h; main_window_geometry_h = h;
} }
void setGameInstallDir(const std::filesystem::path& dir) { void setGameInstallDirs(const std::vector<std::filesystem::path>& dir) {
settings_install_dir = dir; settings_install_dirs.resize(dir.size());
settings_install_dirs = dir;
} }
void setAddonInstallDir(const std::filesystem::path& dir) { void setAddonInstallDir(const std::filesystem::path& dir) {
settings_addon_install_dir = dir; settings_addon_install_dir = dir;
@ -348,8 +385,8 @@ u32 getMainWindowGeometryW() {
u32 getMainWindowGeometryH() { u32 getMainWindowGeometryH() {
return main_window_geometry_h; return main_window_geometry_h;
} }
std::filesystem::path getGameInstallDir() { std::vector<std::filesystem::path> getGameInstallDirs() {
return settings_install_dir; return settings_install_dirs;
} }
std::filesystem::path getAddonInstallDir() { std::filesystem::path getAddonInstallDir() {
if (settings_addon_install_dir.empty()) { if (settings_addon_install_dir.empty()) {
@ -425,6 +462,7 @@ void load(const std::filesystem::path& path) {
isFullscreen = toml::find_or<bool>(general, "Fullscreen", false); isFullscreen = toml::find_or<bool>(general, "Fullscreen", false);
playBGM = toml::find_or<bool>(general, "playBGM", false); playBGM = toml::find_or<bool>(general, "playBGM", false);
BGMvolume = toml::find_or<int>(general, "BGMvolume", 50); BGMvolume = toml::find_or<int>(general, "BGMvolume", 50);
enableDiscordRPC = toml::find_or<bool>(general, "enableDiscordRPC", true);
logFilter = toml::find_or<std::string>(general, "logFilter", ""); logFilter = toml::find_or<std::string>(general, "logFilter", "");
logType = toml::find_or<std::string>(general, "logType", "sync"); logType = toml::find_or<std::string>(general, "logType", "sync");
userName = toml::find_or<std::string>(general, "userName", "shadPS4"); userName = toml::find_or<std::string>(general, "userName", "shadPS4");
@ -440,6 +478,9 @@ void load(const std::filesystem::path& path) {
if (data.contains("Input")) { if (data.contains("Input")) {
const toml::value& input = data.at("Input"); const toml::value& input = data.at("Input");
cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle);
cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", 5);
backButtonBehavior = toml::find_or<std::string>(input, "backButtonBehavior", "left");
useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", false); useSpecialPad = toml::find_or<bool>(input, "useSpecialPad", false);
specialPadClass = toml::find_or<int>(input, "specialPadClass", 1); specialPadClass = toml::find_or<int>(input, "specialPadClass", 1);
} }
@ -483,7 +524,24 @@ void load(const std::filesystem::path& path) {
mw_themes = toml::find_or<int>(gui, "theme", 0); mw_themes = toml::find_or<int>(gui, "theme", 0);
m_window_size_W = toml::find_or<int>(gui, "mw_width", 0); m_window_size_W = toml::find_or<int>(gui, "mw_width", 0);
m_window_size_H = toml::find_or<int>(gui, "mw_height", 0); m_window_size_H = toml::find_or<int>(gui, "mw_height", 0);
settings_install_dir = toml::find_fs_path_or(gui, "installDir", {});
auto old_game_install_dir = toml::find_fs_path_or(gui, "installDir", {});
if (!old_game_install_dir.empty()) {
settings_install_dirs.push_back(old_game_install_dir);
data.as_table().erase("installDir");
}
const auto install_dir_array =
toml::find_or<std::vector<std::string>>(gui, "installDirs", {});
for (const auto& dir : install_dir_array) {
bool not_already_included =
std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir) ==
settings_install_dirs.end();
if (not_already_included) {
settings_install_dirs.emplace_back(std::filesystem::path{dir});
}
}
settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {});
main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0); main_window_geometry_x = toml::find_or<int>(gui, "geometry_x", 0);
main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0); main_window_geometry_y = toml::find_or<int>(gui, "geometry_y", 0);
@ -527,12 +585,16 @@ void save(const std::filesystem::path& path) {
data["General"]["Fullscreen"] = isFullscreen; data["General"]["Fullscreen"] = isFullscreen;
data["General"]["playBGM"] = playBGM; data["General"]["playBGM"] = playBGM;
data["General"]["BGMvolume"] = BGMvolume; data["General"]["BGMvolume"] = BGMvolume;
data["General"]["enableDiscordRPC"] = enableDiscordRPC;
data["General"]["logFilter"] = logFilter; data["General"]["logFilter"] = logFilter;
data["General"]["logType"] = logType; data["General"]["logType"] = logType;
data["General"]["userName"] = userName; data["General"]["userName"] = userName;
data["General"]["updateChannel"] = updateChannel; data["General"]["updateChannel"] = updateChannel;
data["General"]["showSplash"] = isShowSplash; data["General"]["showSplash"] = isShowSplash;
data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["autoUpdate"] = isAutoUpdate;
data["Input"]["cursorState"] = cursorState;
data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
data["Input"]["backButtonBehavior"] = backButtonBehavior;
data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["useSpecialPad"] = useSpecialPad;
data["Input"]["specialPadClass"] = specialPadClass; data["Input"]["specialPadClass"] = specialPadClass;
data["GPU"]["screenWidth"] = screenWidth; data["GPU"]["screenWidth"] = screenWidth;
@ -557,7 +619,13 @@ void save(const std::filesystem::path& path) {
data["GUI"]["gameTableMode"] = m_table_mode; data["GUI"]["gameTableMode"] = m_table_mode;
data["GUI"]["mw_width"] = m_window_size_W; data["GUI"]["mw_width"] = m_window_size_W;
data["GUI"]["mw_height"] = m_window_size_H; data["GUI"]["mw_height"] = m_window_size_H;
data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data};
std::vector<std::string> install_dirs;
for (const auto& dirString : settings_install_dirs) {
install_dirs.emplace_back(std::string{fmt::UTF(dirString.u8string()).data});
}
data["GUI"]["installDirs"] = install_dirs;
data["GUI"]["addonInstallDir"] = data["GUI"]["addonInstallDir"] =
std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; std::string{fmt::UTF(settings_addon_install_dir.u8string()).data};
data["GUI"]["geometry_x"] = main_window_geometry_x; data["GUI"]["geometry_x"] = main_window_geometry_x;
@ -581,6 +649,7 @@ void setDefaultValues() {
isFullscreen = false; isFullscreen = false;
playBGM = false; playBGM = false;
BGMvolume = 50; BGMvolume = 50;
enableDiscordRPC = true;
screenWidth = 1280; screenWidth = 1280;
screenHeight = 720; screenHeight = 720;
logFilter = ""; logFilter = "";
@ -591,6 +660,9 @@ void setDefaultValues() {
} else { } else {
updateChannel = "Nightly"; updateChannel = "Nightly";
} }
cursorState = HideCursorState::Idle;
cursorHideTimeout = 5;
backButtonBehavior = "left";
useSpecialPad = false; useSpecialPad = false;
specialPadClass = 1; specialPadClass = 1;
isDebugDump = false; isDebugDump = false;

View File

@ -8,6 +8,9 @@
#include "types.h" #include "types.h"
namespace Config { namespace Config {
enum HideCursorState : s16 { Never, Idle, Always };
void load(const std::filesystem::path& path); void load(const std::filesystem::path& path);
void save(const std::filesystem::path& path); void save(const std::filesystem::path& path);
@ -15,12 +18,16 @@ bool isNeoMode();
bool isFullscreenMode(); bool isFullscreenMode();
bool getPlayBGM(); bool getPlayBGM();
int getBGMvolume(); int getBGMvolume();
bool getEnableDiscordRPC();
std::string getLogFilter(); std::string getLogFilter();
std::string getLogType(); std::string getLogType();
std::string getUserName(); std::string getUserName();
std::string getUpdateChannel(); std::string getUpdateChannel();
s16 getCursorState();
int getCursorHideTimeout();
std::string getBackButtonBehavior();
bool getUseSpecialPad(); bool getUseSpecialPad();
int getSpecialPadClass(); int getSpecialPadClass();
@ -50,11 +57,15 @@ void setScreenHeight(u32 height);
void setFullscreenMode(bool enable); void setFullscreenMode(bool enable);
void setPlayBGM(bool enable); void setPlayBGM(bool enable);
void setBGMvolume(int volume); void setBGMvolume(int volume);
void setEnableDiscordRPC(bool enable);
void setLanguage(u32 language); void setLanguage(u32 language);
void setNeoMode(bool enable); void setNeoMode(bool enable);
void setUserName(const std::string& type); void setUserName(const std::string& type);
void setUpdateChannel(const std::string& type); void setUpdateChannel(const std::string& type);
void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout);
void setBackButtonBehavior(const std::string& type);
void setUseSpecialPad(bool use); void setUseSpecialPad(bool use);
void setSpecialPadClass(int type); void setSpecialPadClass(int type);
@ -73,7 +84,7 @@ bool vkCrashDiagnosticEnabled();
// Gui // Gui
void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h);
void setGameInstallDir(const std::filesystem::path& dir); void setGameInstallDirs(const std::vector<std::filesystem::path>& dir);
void setAddonInstallDir(const std::filesystem::path& dir); void setAddonInstallDir(const std::filesystem::path& dir);
void setMainWindowTheme(u32 theme); void setMainWindowTheme(u32 theme);
void setIconSize(u32 size); void setIconSize(u32 size);
@ -92,7 +103,7 @@ u32 getMainWindowGeometryX();
u32 getMainWindowGeometryY(); u32 getMainWindowGeometryY();
u32 getMainWindowGeometryW(); u32 getMainWindowGeometryW();
u32 getMainWindowGeometryH(); u32 getMainWindowGeometryH();
std::filesystem::path getGameInstallDir(); std::vector<std::filesystem::path> getGameInstallDirs();
std::filesystem::path getAddonInstallDir(); std::filesystem::path getAddonInstallDir();
u32 getMainWindowTheme(); u32 getMainWindowTheme();
u32 getIconSize(); u32 getIconSize();

View File

@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <ctime>
#include "common/discord.h"
namespace Discord {
void RPC::init() {
DiscordEventHandlers handlers{};
Discord_Initialize("1139939140494971051", &handlers, 1, nullptr);
startTimestamp = time(nullptr);
enabled = true;
}
void RPC::update(Discord::RPCStatus status, const std::string& game) {
DiscordRichPresence rpc{};
if (status == Discord::RPCStatus::Playing) {
rpc.details = "Playing a game";
rpc.state = game.c_str();
} else {
rpc.details = "Idle";
}
rpc.largeImageKey = "shadps4";
rpc.largeImageText = "ShadPS4 is a PS4 emulator";
rpc.startTimestamp = startTimestamp;
Discord_UpdatePresence(&rpc);
}
void RPC::stop() {
if (enabled) {
enabled = false;
Discord_ClearPresence();
Discord_Shutdown();
}
}
} // namespace Discord

View File

@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <cstring>
#include <ctime>
#include "src/common/discord_rpc_handler.h"
namespace DiscordRPCHandler {
void RPC::init() {
DiscordEventHandlers handlers{};
Discord_Initialize("1139939140494971051", &handlers, 1, nullptr);
startTimestamp = time(nullptr);
rpcEnabled = true;
}
void RPC::setStatusIdling() {
DiscordRichPresence rpc{};
rpc.largeImageKey = "https://github.com/shadps4-emu/shadPS4/raw/main/.github/shadps4.png";
rpc.largeImageText = "shadPS4 is a PS4 emulator";
rpc.startTimestamp = startTimestamp;
rpc.details = "Idle";
status = RPCStatus::Idling;
Discord_UpdatePresence(&rpc);
}
void RPC::setStatusPlaying(const std::string& game_name, const std::string& game_id) {
DiscordRichPresence rpc{};
rpc.details = "Playing";
rpc.state = game_name.c_str();
std::string largeImageUrl =
"https://store.playstation.com/store/api/chihiro/00_09_000/titlecontainer/US/en/999/" +
game_id + "_00/image";
rpc.largeImageKey = largeImageUrl.c_str();
rpc.largeImageText = game_name.c_str();
rpc.startTimestamp = startTimestamp;
status = RPCStatus::Playing;
Discord_UpdatePresence(&rpc);
}
void RPC::shutdown() {
if (rpcEnabled) {
rpcEnabled = false;
Discord_ClearPresence();
Discord_Shutdown();
}
}
bool RPC::getRPCEnabled() {
return rpcEnabled;
}
} // namespace DiscordRPCHandler

View File

@ -7,7 +7,7 @@
#include <string> #include <string>
#include <discord_rpc.h> #include <discord_rpc.h>
namespace Discord { namespace DiscordRPCHandler {
enum class RPCStatus { enum class RPCStatus {
Idling, Idling,
@ -16,12 +16,15 @@ enum class RPCStatus {
class RPC { class RPC {
std::uint64_t startTimestamp; std::uint64_t startTimestamp;
bool enabled = false; bool rpcEnabled = false;
RPCStatus status;
public: public:
void init(); void init();
void update(RPCStatus status, const std::string& title); void setStatusIdling();
void stop(); void setStatusPlaying(const std::string& game_name, const std::string& game_id);
void shutdown();
bool getRPCEnabled();
}; };
} // namespace Discord } // namespace DiscordRPCHandler

View File

@ -114,6 +114,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
SUB(Lib, AvPlayer) \ SUB(Lib, AvPlayer) \
SUB(Lib, Ngs2) \ SUB(Lib, Ngs2) \
SUB(Lib, Audio3d) \ SUB(Lib, Audio3d) \
SUB(Lib, Fiber) \
CLS(Frontend) \ CLS(Frontend) \
CLS(Render) \ CLS(Render) \
SUB(Render, Vulkan) \ SUB(Render, Vulkan) \

View File

@ -81,6 +81,7 @@ enum class Class : u8 {
Lib_AvPlayer, ///< The LibSceAvPlayer implementation. Lib_AvPlayer, ///< The LibSceAvPlayer implementation.
Lib_Ngs2, ///< The LibSceNgs2 implementation. Lib_Ngs2, ///< The LibSceNgs2 implementation.
Lib_Audio3d, ///< The LibSceAudio3d implementation. Lib_Audio3d, ///< The LibSceAudio3d implementation.
Lib_Fiber, ///< The LibSceFiber implementation.
Frontend, ///< Emulator UI Frontend, ///< Emulator UI
Render, ///< Video Core Render, ///< Video Core
Render_Vulkan, ///< Vulkan backend Render_Vulkan, ///< Vulkan backend

View File

@ -95,6 +95,18 @@ static auto UserPaths = [] {
user_dir = user_dir =
std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4"; std::filesystem::path(getenv("HOME")) / "Library" / "Application Support" / "shadPS4";
} }
#elif defined(__linux__)
auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
// Check if the "user" directory exists in the current path:
if (!std::filesystem::exists(user_dir)) {
// If it doesn't exist, use XDG_DATA_HOME if it is set, and provide a standard default
const char* xdg_data_home = getenv("XDG_DATA_HOME");
if (xdg_data_home != nullptr && strlen(xdg_data_home) > 0) {
user_dir = std::filesystem::path(xdg_data_home) / "shadPS4";
} else {
user_dir = std::filesystem::path(getenv("HOME")) / ".local" / "share" / "shadPS4";
}
}
#else #else
const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR;
#endif #endif

View File

@ -25,7 +25,7 @@ asm(".zerofill GUEST_SYSTEM,GUEST_SYSTEM,__guest_system,0xFBFC00000");
namespace Core { namespace Core {
static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE; static constexpr size_t BackingSize = SCE_KERNEL_MAIN_DMEM_SIZE_PRO;
#ifdef _WIN32 #ifdef _WIN32

View File

@ -227,6 +227,12 @@ void PSF::AddBinary(std::string key, std::vector<u8> value, bool update) {
map_binaries.emplace(entry_list.size() - 1, std::move(value)); map_binaries.emplace(entry_list.size() - 1, std::move(value));
} }
void PSF::AddBinary(std::string key, uint64_t value, bool update) {
std::vector<u8> data(8);
std::memcpy(data.data(), &value, 8);
return AddBinary(std::move(key), std::move(data), update);
}
void PSF::AddString(std::string key, std::string value, bool update) { void PSF::AddString(std::string key, std::string value, bool update) {
auto [it, index] = FindEntry(key); auto [it, index] = FindEntry(key);
bool exist = it != entry_list.end(); bool exist = it != entry_list.end();

View File

@ -67,6 +67,7 @@ public:
std::optional<s32> GetInteger(std::string_view key) const; std::optional<s32> GetInteger(std::string_view key) const;
void AddBinary(std::string key, std::vector<u8> value, bool update = false); void AddBinary(std::string key, std::vector<u8> value, bool update = false);
void AddBinary(std::string key, uint64_t value, bool update = false); // rsv4 format
void AddString(std::string key, std::string value, bool update = false); void AddString(std::string key, std::string value, bool update = false);
void AddInteger(std::string key, s32 value, bool update = false); void AddInteger(std::string key, s32 value, bool update = false);

View File

@ -498,4 +498,12 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
// AppContent library // AppContent library
constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002;
constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007; constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007;
constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005; constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005;
// Fiber library
constexpr int ORBIS_FIBER_ERROR_NULL = 0x80590001;
constexpr int ORBIS_FIBER_ERROR_ALIGNMENT = 0x80590002;
constexpr int ORBIS_FIBER_ERROR_RANGE = 0x80590003;
constexpr int ORBIS_FIBER_ERROR_INVALID = 0x80590004;
constexpr int ORBIS_FIBER_ERROR_PERMISSION = 0x80590005;
constexpr int ORBIS_FIBER_ERROR_STATE = 0x80590006;

View File

@ -0,0 +1,284 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "fiber.h"
#include "common/logging/log.h"
#include "common/singleton.h"
#include "core/libraries/error_codes.h"
#include "core/libraries/libs.h"
#include "core/linker.h"
#ifdef _WIN64
#include <windows.h>
#endif
namespace Libraries::Fiber {
constexpr static u64 kFiberSignature = 0x054ad954;
thread_local SceFiber* gCurrentFiber = nullptr;
thread_local void* gFiberThread = nullptr;
void FiberEntry(void* param) {
SceFiber* fiber = static_cast<SceFiber*>(param);
u64 argRun = 0;
u64 argRet = 0;
gCurrentFiber = fiber;
if (fiber->pArgRun != nullptr) {
argRun = *fiber->pArgRun;
}
const auto* linker = Common::Singleton<Core::Linker>::Instance();
linker->ExecuteGuest(fiber->entry, fiber->argOnInitialize, argRun);
UNREACHABLE();
}
s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry,
u64 argOnInitialize, void* addrContext, u64 sizeContext,
const SceFiberOptParam* optParam) {
LOG_INFO(Lib_Fiber, "called: name = {}", name);
if (!fiber || !name || !entry) {
return ORBIS_FIBER_ERROR_NULL;
}
fiber->signature = kFiberSignature;
fiber->entry = entry;
fiber->argOnInitialize = argOnInitialize;
fiber->argRun = 0;
fiber->pArgRun = &fiber->argRun;
fiber->argReturn = 0;
fiber->pArgReturn = &fiber->argReturn;
fiber->sizeContext = sizeContext;
fiber->state = FiberState::Init;
#ifdef _WIN64
fiber->handle = CreateFiber(sizeContext, FiberEntry, fiber);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam) {
LOG_ERROR(Lib_Fiber, "called");
if (!optParam) {
return ORBIS_FIBER_ERROR_NULL;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber) {
LOG_TRACE(Lib_Fiber, "called");
if (!fiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber % 8 != 0) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
if (fiber->signature != kFiberSignature) {
return ORBIS_FIBER_ERROR_INVALID;
}
if (fiber->state != FiberState::Run) {
return ORBIS_FIBER_ERROR_STATE;
}
fiber->signature = 0;
fiber->state = FiberState::None;
#ifdef _WIN64
DeleteFiber(fiber->handle);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn) {
LOG_TRACE(Lib_Fiber, "called");
if (!fiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber % 8 != 0) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
if (fiber->signature != kFiberSignature) {
return ORBIS_FIBER_ERROR_INVALID;
}
if (fiber->state == FiberState::Run) {
return ORBIS_FIBER_ERROR_STATE;
}
if (gFiberThread == nullptr) {
#ifdef _WIN64
gFiberThread = ConvertThreadToFiber(nullptr);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
}
gCurrentFiber = fiber;
if (fiber->pArgRun != nullptr) {
*fiber->pArgRun = argOnRunTo;
}
fiber->pArgReturn = argOnReturn;
fiber->state = FiberState::Run;
#ifdef _WIN64
SwitchToFiber(fiber->handle);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun) {
LOG_TRACE(Lib_Fiber, "called");
if (!fiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber % 8 != 0) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
if (fiber->signature != kFiberSignature) {
return ORBIS_FIBER_ERROR_INVALID;
}
if (gCurrentFiber == nullptr) {
return ORBIS_FIBER_ERROR_PERMISSION;
}
if (fiber->state == FiberState::Run) {
return ORBIS_FIBER_ERROR_STATE;
}
gCurrentFiber->state = FiberState::Suspend;
// TODO: argOnRun
*fiber->pArgRun = argOnRunTo;
fiber->state = FiberState::Run;
gCurrentFiber = fiber;
#ifdef _WIN64
SwitchToFiber(fiber->handle);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber) {
LOG_TRACE(Lib_Fiber, "called");
if (!fiber || !gCurrentFiber) {
return ORBIS_FIBER_ERROR_NULL;
}
if (gCurrentFiber->signature != kFiberSignature) {
return ORBIS_FIBER_ERROR_PERMISSION;
}
*fiber = gCurrentFiber;
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun) {
LOG_TRACE(Lib_Fiber, "called");
if (gCurrentFiber->signature != kFiberSignature) {
return ORBIS_FIBER_ERROR_PERMISSION;
}
if (gCurrentFiber->pArgReturn != nullptr) {
*gCurrentFiber->pArgReturn = argOnReturn;
}
// TODO: argOnRun
gCurrentFiber->state = FiberState::Suspend;
gCurrentFiber = nullptr;
#ifdef _WIN64
SwitchToFiber(gFiberThread);
#else
UNREACHABLE_MSG("Missing implementation");
#endif
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo) {
LOG_INFO(Lib_Fiber, "called");
if (!fiber || !fiberInfo) {
return ORBIS_FIBER_ERROR_NULL;
}
fiberInfo->entry = fiber->entry;
fiberInfo->argOnInitialize = fiber->argOnInitialize;
fiberInfo->addrContext = nullptr;
fiberInfo->sizeContext = fiber->sizeContext;
fiberInfo->sizeContextMargin = 0;
strncpy(fiberInfo->name, fiber->name, ORBIS_FIBER_MAX_NAME_LENGTH);
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags) {
LOG_ERROR(Lib_Fiber, "called");
if (flags != 0) {
return ORBIS_FIBER_ERROR_INVALID;
}
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck() {
LOG_ERROR(Lib_Fiber, "called");
return ORBIS_OK;
}
s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name) {
LOG_INFO(Lib_Fiber, "called, name = {}", name);
if (!fiber || !name) {
return ORBIS_FIBER_ERROR_NULL;
}
if ((u64)fiber % 8 != 0) {
return ORBIS_FIBER_ERROR_ALIGNMENT;
}
strncpy(fiber->name, name, ORBIS_FIBER_MAX_NAME_LENGTH);
return ORBIS_OK;
}
void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym) {
LIB_FUNCTION("hVYD7Ou2pCQ", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberInitialize);
LIB_FUNCTION("asjUJJ+aa8s", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberOptParamInitialize);
LIB_FUNCTION("JeNX5F-NzQU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberFinalize);
LIB_FUNCTION("a0LLrZWac0M", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRun);
LIB_FUNCTION("PFT2S-tJ7Uk", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberSwitch);
LIB_FUNCTION("p+zLIOg27zU", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetSelf);
LIB_FUNCTION("B0ZX2hx9DMw", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberReturnToThread);
LIB_FUNCTION("uq2Y5BFz0PE", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberGetInfo);
LIB_FUNCTION("Lcqty+QNWFc", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberStartContextSizeCheck);
LIB_FUNCTION("Kj4nXMpnM8Y", "libSceFiber", 1, "libSceFiber", 1, 1,
sceFiberStopContextSizeCheck);
LIB_FUNCTION("JzyT91ucGDc", "libSceFiber", 1, "libSceFiber", 1, 1, sceFiberRename);
}
} // namespace Libraries::Fiber

View File

@ -0,0 +1,83 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/assert.h"
#include "common/types.h"
namespace Core::Loader {
class SymbolsResolver;
}
namespace Libraries::Fiber {
#define ORBIS_FIBER_MAX_NAME_LENGTH (31)
typedef void PS4_SYSV_ABI (*SceFiberEntry)(u64 argOnInitialize, u64 argOnRun);
enum FiberState : u32 {
None = 0u,
Init = 1u,
Run = 2u,
Suspend = 3u,
};
struct SceFiber {
u64 signature;
FiberState state;
SceFiberEntry entry;
u64 argOnInitialize;
u64 argRun;
u64* pArgRun;
u64 argReturn;
u64* pArgReturn;
u64 sizeContext;
char name[ORBIS_FIBER_MAX_NAME_LENGTH];
void* handle;
};
static_assert(sizeof(SceFiber) <= 256);
struct SceFiberInfo {
u64 size;
SceFiberEntry entry;
u64 argOnInitialize;
void* addrContext;
u64 sizeContext;
char name[ORBIS_FIBER_MAX_NAME_LENGTH + 1];
u64 sizeContextMargin;
};
static_assert(sizeof(SceFiberInfo) <= 128);
typedef void* SceFiberOptParam;
s32 PS4_SYSV_ABI sceFiberInitialize(SceFiber* fiber, const char* name, SceFiberEntry entry,
u64 argOnInitialize, void* addrContext, u64 sizeContext,
const SceFiberOptParam* optParam);
s32 PS4_SYSV_ABI sceFiberOptParamInitialize(SceFiberOptParam* optParam);
s32 PS4_SYSV_ABI sceFiberFinalize(SceFiber* fiber);
s32 PS4_SYSV_ABI sceFiberRun(SceFiber* fiber, u64 argOnRunTo, u64* argOnReturn);
s32 PS4_SYSV_ABI sceFiberSwitch(SceFiber* fiber, u64 argOnRunTo, u64* argOnRun);
s32 PS4_SYSV_ABI sceFiberGetSelf(SceFiber** fiber);
s32 PS4_SYSV_ABI sceFiberReturnToThread(u64 argOnReturn, u64* argOnRun);
s32 PS4_SYSV_ABI sceFiberGetInfo(SceFiber* fiber, SceFiberInfo* fiberInfo);
s32 PS4_SYSV_ABI sceFiberStartContextSizeCheck(u32 flags);
s32 PS4_SYSV_ABI sceFiberStopContextSizeCheck(void);
s32 PS4_SYSV_ABI sceFiberRename(SceFiber* fiber, const char* name);
void RegisterlibSceFiber(Core::Loader::SymbolsResolver* sym);
} // namespace Libraries::Fiber

View File

@ -1076,9 +1076,27 @@ s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size) {
return -1; return -1;
} }
int PS4_SYSV_ABI sceGnmInsertPushColorMarker() { s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color) {
LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); LOG_TRACE(Lib_GnmDriver, "called");
return ORBIS_OK;
if (cmdbuf && marker) {
const auto len = std::strlen(marker);
const u32 packet_size = ((len + 0xc) >> 2) + ((len + 0x10) >> 3) * 2;
if (packet_size + 2 == size) {
auto* nop = reinterpret_cast<PM4CmdNop*>(cmdbuf);
nop->header =
PM4Type3Header{PM4ItOpcode::Nop, packet_size, PM4ShaderType::ShaderGraphics};
nop->data_block[0] = PM4CmdNop::PayloadType::DebugColorMarkerPush;
const auto marker_len = len + 1;
std::memcpy(&nop->data_block[1], marker, marker_len);
*reinterpret_cast<u32*>(reinterpret_cast<u8*>(&nop->data_block[1]) + marker_len + 8) =
color;
std::memset(reinterpret_cast<u8*>(&nop->data_block[1]) + marker_len + 8 + sizeof(u32),
0, packet_size * 4 - marker_len - 8 - sizeof(u32));
return ORBIS_OK;
}
}
return -1;
} }
s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker) { s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker) {

View File

@ -105,7 +105,7 @@ int PS4_SYSV_ABI sceGnmGpuPaDebugEnter();
int PS4_SYSV_ABI sceGnmGpuPaDebugLeave(); int PS4_SYSV_ABI sceGnmGpuPaDebugLeave();
int PS4_SYSV_ABI sceGnmInsertDingDongMarker(); int PS4_SYSV_ABI sceGnmInsertDingDongMarker();
s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size); s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size);
int PS4_SYSV_ABI sceGnmInsertPushColorMarker(); s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color);
s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker); s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker);
int PS4_SYSV_ABI sceGnmInsertSetColorMarker(); int PS4_SYSV_ABI sceGnmInsertSetColorMarker();
int PS4_SYSV_ABI sceGnmInsertSetMarker(); int PS4_SYSV_ABI sceGnmInsertSetMarker();

View File

@ -7,6 +7,8 @@
#include "common/types.h" #include "common/types.h"
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5056_MB; // ~ 5GB constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE = 5056_MB; // ~ 5GB
// TODO: Confirm this value on hardware.
constexpr u64 SCE_KERNEL_MAIN_DMEM_SIZE_PRO = 5568_MB; // ~ 5.5GB
namespace Libraries::Kernel { namespace Libraries::Kernel {

View File

@ -11,6 +11,7 @@
#include "core/libraries/dialogs/error_dialog.h" #include "core/libraries/dialogs/error_dialog.h"
#include "core/libraries/dialogs/ime_dialog.h" #include "core/libraries/dialogs/ime_dialog.h"
#include "core/libraries/disc_map/disc_map.h" #include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/fiber/fiber.h"
#include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/gnmdriver/gnmdriver.h"
#include "core/libraries/kernel/libkernel.h" #include "core/libraries/kernel/libkernel.h"
#include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libc_internal/libc_internal.h"

View File

@ -18,6 +18,8 @@
namespace Libraries::Net { namespace Libraries::Net {
static thread_local int32_t net_errno = 0;
int PS4_SYSV_ABI in6addr_any() { int PS4_SYSV_ABI in6addr_any() {
LOG_ERROR(Lib_Net, "(STUBBED) called"); LOG_ERROR(Lib_Net, "(STUBBED) called");
return ORBIS_OK; return ORBIS_OK;
@ -563,9 +565,9 @@ int PS4_SYSV_ABI sceNetEpollWait() {
return ORBIS_OK; return ORBIS_OK;
} }
int PS4_SYSV_ABI sceNetErrnoLoc() { int* PS4_SYSV_ABI sceNetErrnoLoc() {
LOG_ERROR(Lib_Net, "(STUBBED) called"); LOG_ERROR(Lib_Net, "(STUBBED) called");
return ORBIS_OK; return &net_errno;
} }
int PS4_SYSV_ABI sceNetEtherNtostr() { int PS4_SYSV_ABI sceNetEtherNtostr() {

View File

@ -136,7 +136,7 @@ int PS4_SYSV_ABI sceNetEpollControl();
int PS4_SYSV_ABI sceNetEpollCreate(); int PS4_SYSV_ABI sceNetEpollCreate();
int PS4_SYSV_ABI sceNetEpollDestroy(); int PS4_SYSV_ABI sceNetEpollDestroy();
int PS4_SYSV_ABI sceNetEpollWait(); int PS4_SYSV_ABI sceNetEpollWait();
int PS4_SYSV_ABI sceNetErrnoLoc(); int* PS4_SYSV_ABI sceNetErrnoLoc();
int PS4_SYSV_ABI sceNetEtherNtostr(); int PS4_SYSV_ABI sceNetEtherNtostr();
int PS4_SYSV_ABI sceNetEtherStrton(); int PS4_SYSV_ABI sceNetEtherStrton();
int PS4_SYSV_ABI sceNetEventCallbackCreate(); int PS4_SYSV_ABI sceNetEventCallbackCreate();

View File

@ -902,12 +902,13 @@ int PS4_SYSV_ABI sceNpCreateAsyncRequest() {
} }
int PS4_SYSV_ABI sceNpCreateRequest() { int PS4_SYSV_ABI sceNpCreateRequest() {
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); LOG_ERROR(Lib_NpManager, "(DUMMY) called");
return ORBIS_OK; static int id = 0;
return ++id;
} }
int PS4_SYSV_ABI sceNpDeleteRequest() { int PS4_SYSV_ABI sceNpDeleteRequest(int reqId) {
LOG_ERROR(Lib_NpManager, "(STUBBED) called"); LOG_ERROR(Lib_NpManager, "(DUMMY) called reqId = {}", reqId);
return ORBIS_OK; return ORBIS_OK;
} }

View File

@ -218,7 +218,7 @@ int PS4_SYSV_ABI sceNpCheckNpReachability();
int PS4_SYSV_ABI sceNpCheckPlus(); int PS4_SYSV_ABI sceNpCheckPlus();
int PS4_SYSV_ABI sceNpCreateAsyncRequest(); int PS4_SYSV_ABI sceNpCreateAsyncRequest();
int PS4_SYSV_ABI sceNpCreateRequest(); int PS4_SYSV_ABI sceNpCreateRequest();
int PS4_SYSV_ABI sceNpDeleteRequest(); int PS4_SYSV_ABI sceNpDeleteRequest(int reqId);
int PS4_SYSV_ABI sceNpGetAccountAge(); int PS4_SYSV_ABI sceNpGetAccountAge();
int PS4_SYSV_ABI sceNpGetAccountCountry(); int PS4_SYSV_ABI sceNpGetAccountCountry();
int PS4_SYSV_ABI sceNpGetAccountCountryA(); int PS4_SYSV_ABI sceNpGetAccountCountryA();

View File

@ -12,9 +12,9 @@
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
#include "save_instance.h" #include "save_instance.h"
constexpr u32 OrbisSaveDataBlocksMax = 32768; // 1 GiB constexpr auto OrbisSaveDataBlocksMin2 = 96; // 3MiB
constexpr auto OrbisSaveDataBlocksMax = 32768; // 1 GiB
constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save constexpr std::string_view sce_sys = "sce_sys"; // system folder inside save
constexpr std::string_view max_block_file_name = "max_block.txt";
static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance(); static Core::FileSys::MntPoints* g_mnt = Common::Singleton<Core::FileSys::MntPoints>::Instance();
@ -58,18 +58,13 @@ std::filesystem::path SaveInstance::MakeDirSavePath(OrbisUserServiceUserId user_
game_serial / dir_name; game_serial / dir_name;
} }
int SaveInstance::GetMaxBlocks(const std::filesystem::path& save_path) { uint64_t SaveInstance::GetMaxBlockFromSFO(const PSF& psf) {
Common::FS::IOFile max_blocks_file{save_path / sce_sys / max_block_file_name, const auto vec = psf.GetBinary(std::string{SaveParams::SAVEDATA_BLOCKS});
Common::FS::FileAccessMode::Read}; if (!vec.has_value()) {
int max_blocks = 0; return OrbisSaveDataBlocksMax;
if (max_blocks_file.IsOpen()) {
max_blocks = std::atoi(max_blocks_file.ReadString(16).c_str());
} }
if (max_blocks <= 0) { auto value = vec.value();
max_blocks = OrbisSaveDataBlocksMax; return *(uint64_t*)value.data();
}
return max_blocks;
} }
std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) { std::filesystem::path SaveInstance::GetParamSFOPath(const std::filesystem::path& dir_path) {
@ -92,13 +87,15 @@ void SaveInstance::SetupDefaultParamSFO(PSF& param_sfo, std::string dir_name,
P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name)); P(String, SaveParams::SAVEDATA_DIRECTORY, std::move(dir_name));
P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0); P(Integer, SaveParams::SAVEDATA_LIST_PARAM, 0);
P(String, SaveParams::TITLE_ID, std::move(game_serial)); P(String, SaveParams::TITLE_ID, std::move(game_serial));
P(Binary, SaveParams::SAVEDATA_BLOCKS, {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
#undef P #undef P
} }
SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial, SaveInstance::SaveInstance(int slot_num, OrbisUserServiceUserId user_id, std::string _game_serial,
std::string_view _dir_name, int max_blocks) std::string_view _dir_name, int max_blocks)
: slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)), : slot_num(slot_num), user_id(user_id), game_serial(std::move(_game_serial)),
dir_name(_dir_name), max_blocks(max_blocks) { dir_name(_dir_name),
max_blocks(std::clamp(max_blocks, OrbisSaveDataBlocksMin2, OrbisSaveDataBlocksMax)) {
ASSERT(slot_num >= 0 && slot_num < 16); ASSERT(slot_num >= 0 && slot_num < 16);
save_path = MakeDirSavePath(user_id, game_serial, dir_name); save_path = MakeDirSavePath(user_id, game_serial, dir_name);
@ -187,7 +184,7 @@ void SaveInstance::SetupAndMount(bool read_only, bool copy_icon, bool ignore_cor
} }
} }
max_blocks = GetMaxBlocks(save_path); max_blocks = static_cast<int>(GetMaxBlockFromSFO(param_sfo));
g_mnt->Mount(save_path, mount_point, read_only); g_mnt->Mount(save_path, mount_point, read_only);
mounted = true; mounted = true;
@ -217,16 +214,13 @@ void SaveInstance::CreateFiles() {
fs::create_directories(sce_sys_dir); fs::create_directories(sce_sys_dir);
SetupDefaultParamSFO(param_sfo, dir_name, game_serial); SetupDefaultParamSFO(param_sfo, dir_name, game_serial);
param_sfo.AddBinary(std::string{SaveParams::SAVEDATA_BLOCKS}, max_blocks, true);
const bool ok = param_sfo.Encode(param_sfo_path); const bool ok = param_sfo.Encode(param_sfo_path);
if (!ok) { if (!ok) {
throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path, throw std::filesystem::filesystem_error("Failed to write param.sfo", param_sfo_path,
std::make_error_code(std::errc::permission_denied)); std::make_error_code(std::errc::permission_denied));
} }
Common::FS::IOFile max_block{sce_sys_dir / max_block_file_name,
Common::FS::FileAccessMode::Write};
max_block.WriteString(std::to_string(max_blocks == 0 ? OrbisSaveDataBlocksMax : max_blocks));
} }
} // namespace Libraries::SaveData } // namespace Libraries::SaveData

View File

@ -62,7 +62,7 @@ public:
std::string_view game_serial, std::string_view game_serial,
std::string_view dir_name); std::string_view dir_name);
static int GetMaxBlocks(const std::filesystem::path& save_path); static uint64_t GetMaxBlockFromSFO(const PSF& psf);
// Get param.sfo path from a dir_path generated by MakeDirSavePath // Get param.sfo path from a dir_path generated by MakeDirSavePath
static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path); static std::filesystem::path GetParamSFOPath(const std::filesystem::path& dir_path);

View File

@ -445,7 +445,7 @@ static Error saveDataMount(const OrbisSaveDataMount2* mount_info,
fs::create_directories(root_save); fs::create_directories(root_save);
const auto available = fs::space(root_save).available; const auto available = fs::space(root_save).available;
auto requested_size = mount_info->blocks * OrbisSaveDataBlockSize; auto requested_size = save_instance.GetMaxBlocks() * OrbisSaveDataBlockSize;
if (requested_size > available) { if (requested_size > available) {
mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize; mount_result->required_blocks = (requested_size - available) / OrbisSaveDataBlockSize;
return Error::NO_SPACE_FS; return Error::NO_SPACE_FS;
@ -830,10 +830,11 @@ Error PS4_SYSV_ABI sceSaveDataDirNameSearch(const OrbisSaveDataDirNameSearchCond
LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string())); LOG_ERROR(Lib_SaveData, "Failed to read SFO: {}", fmt::UTF(sfo_path.u8string()));
ASSERT_MSG(false, "Failed to read SFO"); ASSERT_MSG(false, "Failed to read SFO");
} }
map_dir_sfo.emplace(dir_name, std::move(sfo));
size_t size = Common::FS::GetDirectorySize(dir_path); size_t size = Common::FS::GetDirectorySize(dir_path);
size_t total = SaveInstance::GetMaxBlocks(dir_path); size_t total = SaveInstance::GetMaxBlockFromSFO(sfo);
map_dir_sfo.emplace(dir_name, std::move(sfo));
map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize); map_free_size.emplace(dir_name, total - size / OrbisSaveDataBlockSize);
map_max_blocks.emplace(dir_name, total); map_max_blocks.emplace(dir_name, total);
} }

View File

@ -3,6 +3,7 @@
#include "common/alignment.h" #include "common/alignment.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/config.h"
#include "common/debug.h" #include "common/debug.h"
#include "core/libraries/error_codes.h" #include "core/libraries/error_codes.h"
#include "core/libraries/kernel/memory_management.h" #include "core/libraries/kernel/memory_management.h"
@ -39,8 +40,10 @@ MemoryManager::MemoryManager() {
MemoryManager::~MemoryManager() = default; MemoryManager::~MemoryManager() = default;
void MemoryManager::SetupMemoryRegions(u64 flexible_size) { void MemoryManager::SetupMemoryRegions(u64 flexible_size) {
const auto total_size =
Config::isNeoMode() ? SCE_KERNEL_MAIN_DMEM_SIZE_PRO : SCE_KERNEL_MAIN_DMEM_SIZE;
total_flexible_size = flexible_size; total_flexible_size = flexible_size;
total_direct_size = SCE_KERNEL_MAIN_DMEM_SIZE - flexible_size; total_direct_size = total_size - flexible_size;
// Insert an area that covers direct memory physical block. // Insert an area that covers direct memory physical block.
// Note that this should never be called after direct memory allocations have been made. // Note that this should never be called after direct memory allocations have been made.

View File

@ -8,9 +8,11 @@
#include "common/logging/backend.h" #include "common/logging/backend.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#ifdef ENABLE_QT_GUI #ifdef ENABLE_QT_GUI
#include <QtCore>
#include "common/memory_patcher.h" #include "common/memory_patcher.h"
#endif #endif
#include "common/assert.h" #include "common/assert.h"
#include "common/discord_rpc_handler.h"
#include "common/elf_info.h" #include "common/elf_info.h"
#include "common/ntapi.h" #include "common/ntapi.h"
#include "common/path_util.h" #include "common/path_util.h"
@ -24,6 +26,7 @@
#include "core/file_format/trp.h" #include "core/file_format/trp.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"
#include "core/libraries/disc_map/disc_map.h" #include "core/libraries/disc_map/disc_map.h"
#include "core/libraries/fiber/fiber.h"
#include "core/libraries/kernel/thread_management.h" #include "core/libraries/kernel/thread_management.h"
#include "core/libraries/libc_internal/libc_internal.h" #include "core/libraries/libc_internal/libc_internal.h"
#include "core/libraries/libs.h" #include "core/libraries/libs.h"
@ -58,6 +61,7 @@ Emulator::Emulator() {
LOG_INFO(Loader, "Branch {}", Common::g_scm_branch); LOG_INFO(Loader, "Branch {}", Common::g_scm_branch);
LOG_INFO(Loader, "Description {}", Common::g_scm_desc); LOG_INFO(Loader, "Description {}", Common::g_scm_desc);
LOG_INFO(Config, "General Logtype: {}", Config::getLogType());
LOG_INFO(Config, "General isNeo: {}", Config::isNeoMode()); LOG_INFO(Config, "General isNeo: {}", Config::isNeoMode());
LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu()); LOG_INFO(Config, "GPU isNullGpu: {}", Config::nullGpu());
LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders()); LOG_INFO(Config, "GPU shouldDumpShaders: {}", Config::dumpShaders());
@ -77,6 +81,17 @@ Emulator::Emulator() {
// Load renderdoc module. // Load renderdoc module.
VideoCore::LoadRenderDoc(); VideoCore::LoadRenderDoc();
// Start the timer (Play Time)
#ifdef ENABLE_QT_GUI
start_time = std::chrono::steady_clock::now();
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
QString filePath = QString::fromStdString((user_dir / "play_time.txt").string());
QFile file(filePath);
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
LOG_INFO(Loader, "Error opening or creating play_time.txt");
}
#endif
} }
Emulator::~Emulator() { Emulator::~Emulator() {
@ -120,6 +135,14 @@ void Emulator::Run(const std::filesystem::path& file) {
} }
#ifdef ENABLE_QT_GUI #ifdef ENABLE_QT_GUI
MemoryPatcher::g_game_serial = id; MemoryPatcher::g_game_serial = id;
// Timer for 'Play Time'
QTimer* timer = new QTimer();
QObject::connect(timer, &QTimer::timeout, [this, id]() {
UpdatePlayTime(id);
start_time = std::chrono::steady_clock::now();
});
timer->start(60000); // 60000 ms = 1 minute
#endif #endif
title = param_sfo->GetString("TITLE").value_or("Unknown title"); title = param_sfo->GetString("TITLE").value_or("Unknown title");
LOG_INFO(Loader, "Game id: {} Title: {}", id, title); LOG_INFO(Loader, "Game id: {} Title: {}", id, title);
@ -209,6 +232,15 @@ void Emulator::Run(const std::filesystem::path& file) {
} }
} }
// Discord RPC
if (Config::getEnableDiscordRPC()) {
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
if (rpc->getRPCEnabled() == false) {
rpc->init();
}
rpc->setStatusPlaying(game_info.title, id);
}
// start execution // start execution
std::jthread mainthread = std::jthread mainthread =
std::jthread([this](std::stop_token stop_token) { linker->Execute(); }); std::jthread([this](std::stop_token stop_token) { linker->Execute(); });
@ -217,13 +249,17 @@ void Emulator::Run(const std::filesystem::path& file) {
window->waitEvent(); window->waitEvent();
} }
#ifdef ENABLE_QT_GUI
UpdatePlayTime(id);
#endif
std::exit(0); std::exit(0);
} }
void Emulator::LoadSystemModules(const std::filesystem::path& file) { void Emulator::LoadSystemModules(const std::filesystem::path& file) {
constexpr std::array<SysModules, 13> ModulesToLoad{ constexpr std::array<SysModules, 13> ModulesToLoad{
{{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2},
{"libSceFiber.sprx", nullptr}, {"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber},
{"libSceUlt.sprx", nullptr}, {"libSceUlt.sprx", nullptr},
{"libSceJson.sprx", nullptr}, {"libSceJson.sprx", nullptr},
{"libSceJson2.sprx", nullptr}, {"libSceJson2.sprx", nullptr},
@ -258,4 +294,74 @@ void Emulator::LoadSystemModules(const std::filesystem::path& file) {
} }
} }
#ifdef ENABLE_QT_GUI
void Emulator::UpdatePlayTime(const std::string& serial) {
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
QString filePath = QString::fromStdString((user_dir / "play_time.txt").string());
QFile file(filePath);
if (!file.open(QIODevice::ReadWrite | QIODevice::Text)) {
LOG_INFO(Loader, "Error opening play_time.txt");
return;
}
auto end_time = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::seconds>(end_time - start_time);
int totalSeconds = duration.count();
QTextStream in(&file);
QStringList lines;
QString content;
while (!in.atEnd()) {
content += in.readLine() + "\n";
}
file.close();
QStringList existingLines = content.split('\n', Qt::SkipEmptyParts);
int accumulatedSeconds = 0;
bool found = false;
for (const QString& line : existingLines) {
QStringList parts = line.split(' ');
if (parts.size() == 2 && parts[0] == QString::fromStdString(serial)) {
QStringList timeParts = parts[1].split(':');
if (timeParts.size() == 3) {
int hours = timeParts[0].toInt();
int minutes = timeParts[1].toInt();
int seconds = timeParts[2].toInt();
accumulatedSeconds = hours * 3600 + minutes * 60 + seconds;
found = true;
break;
}
}
}
accumulatedSeconds += totalSeconds;
int hours = accumulatedSeconds / 3600;
int minutes = (accumulatedSeconds % 3600) / 60;
int seconds = accumulatedSeconds % 60;
QString playTimeSaved = QString::number(hours) + ":" +
QString::number(minutes).rightJustified(2, '0') + ":" +
QString::number(seconds).rightJustified(2, '0');
if (file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&file);
bool lineUpdated = false;
for (const QString& line : existingLines) {
if (line.startsWith(QString::fromStdString(serial))) {
out << QString::fromStdString(serial) + " " + playTimeSaved + "\n";
lineUpdated = true;
} else {
out << line << "\n";
}
}
if (!lineUpdated) {
out << QString::fromStdString(serial) + " " + playTimeSaved + "\n";
}
}
LOG_INFO(Loader, "Playing time for {}: {}", serial, playTimeSaved.toStdString());
}
#endif
} // namespace Core } // namespace Core

View File

@ -26,6 +26,7 @@ public:
~Emulator(); ~Emulator();
void Run(const std::filesystem::path& file); void Run(const std::filesystem::path& file);
void UpdatePlayTime(const std::string& serial);
private: private:
void LoadSystemModules(const std::filesystem::path& file); void LoadSystemModules(const std::filesystem::path& file);
@ -34,6 +35,7 @@ private:
Input::GameController* controller; Input::GameController* controller;
Core::Linker* linker; Core::Linker* linker;
std::unique_ptr<Frontend::WindowSDL> window; std::unique_ptr<Frontend::WindowSDL> window;
std::chrono::steady_clock::time_point start_time;
}; };
} // namespace Core } // namespace Core

View File

@ -4,6 +4,7 @@
// Based on imgui_impl_sdl3.cpp from Dear ImGui repository // Based on imgui_impl_sdl3.cpp from Dear ImGui repository
#include <imgui.h> #include <imgui.h>
#include "common/config.h"
#include "imgui_impl_sdl3.h" #include "imgui_impl_sdl3.h"
// SDL // SDL
@ -36,6 +37,8 @@ struct SdlData {
SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{}; SDL_Cursor* mouse_cursors[ImGuiMouseCursor_COUNT]{};
SDL_Cursor* mouse_last_cursor{}; SDL_Cursor* mouse_last_cursor{};
int mouse_pending_leave_frame{}; int mouse_pending_leave_frame{};
ImVec2 prev_mouse_pos{0, 0};
Uint64 lastCursorMoveTime{};
// Gamepad handling // Gamepad handling
ImVector<SDL_Gamepad*> gamepads{}; ImVector<SDL_Gamepad*> gamepads{};
@ -371,6 +374,13 @@ bool ProcessEvent(const SDL_Event* event) {
? ImGuiMouseSource_TouchScreen ? ImGuiMouseSource_TouchScreen
: ImGuiMouseSource_Mouse); : ImGuiMouseSource_Mouse);
io.AddMousePosEvent(mouse_pos.x, mouse_pos.y); io.AddMousePosEvent(mouse_pos.x, mouse_pos.y);
if (mouse_pos.x != bd->prev_mouse_pos.x || mouse_pos.y != bd->prev_mouse_pos.y) {
bd->prev_mouse_pos.x = mouse_pos.x;
bd->prev_mouse_pos.y = mouse_pos.y;
if (Config::getCursorState() == Config::HideCursorState::Idle) {
bd->lastCursorMoveTime = bd->time;
}
}
return true; return true;
} }
case SDL_EVENT_MOUSE_WHEEL: { case SDL_EVENT_MOUSE_WHEEL: {
@ -447,6 +457,7 @@ bool ProcessEvent(const SDL_Event* event) {
return false; return false;
bd->mouse_window_id = event->window.windowID; bd->mouse_window_id = event->window.windowID;
bd->mouse_pending_leave_frame = 0; bd->mouse_pending_leave_frame = 0;
bd->lastCursorMoveTime = bd->time;
return true; return true;
} }
// - In some cases, when detaching a window from main viewport SDL may send // - In some cases, when detaching a window from main viewport SDL may send
@ -459,6 +470,7 @@ bool ProcessEvent(const SDL_Event* event) {
if (GetViewportForWindowId(event->window.windowID) == NULL) if (GetViewportForWindowId(event->window.windowID) == NULL)
return false; return false;
bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1; bd->mouse_pending_leave_frame = ImGui::GetFrameCount() + 1;
bd->lastCursorMoveTime = bd->time;
return true; return true;
} }
case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_GAINED:
@ -581,7 +593,7 @@ static void UpdateMouseData() {
// (below) // (below)
// SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries // SDL_CaptureMouse() let the OS know e.g. that our imgui drag outside the SDL window boundaries
// shouldn't e.g. trigger other operations outside // shouldn't e.g. trigger other operations outside
SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? SDL_TRUE : SDL_FALSE); SDL_CaptureMouse((bd->mouse_buttons_down != 0) ? true : false);
SDL_Window* focused_window = SDL_GetKeyboardFocus(); SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (bd->window == focused_window); const bool is_app_focused = (bd->window == focused_window);
@ -600,7 +612,18 @@ static void UpdateMouseData() {
int window_x, window_y; int window_x, window_y;
SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global); SDL_GetGlobalMouseState(&mouse_x_global, &mouse_y_global);
SDL_GetWindowPosition(focused_window, &window_x, &window_y); SDL_GetWindowPosition(focused_window, &window_x, &window_y);
io.AddMousePosEvent(mouse_x_global - (float)window_x, mouse_y_global - (float)window_y); mouse_x_global -= (float)window_x;
mouse_y_global -= (float)window_y;
io.AddMousePosEvent(mouse_x_global, mouse_y_global);
// SDL_EVENT_MOUSE_MOTION isn't triggered before the first frame is rendered
// force update the prev_cursor coords
if (mouse_x_global != bd->prev_mouse_pos.x || mouse_y_global != bd->prev_mouse_pos.y &&
bd->prev_mouse_pos.y == 0 &&
bd->prev_mouse_pos.x == 0) {
bd->prev_mouse_pos.x = mouse_x_global;
bd->prev_mouse_pos.y = mouse_y_global;
bd->lastCursorMoveTime = bd->time;
}
} }
} }
} }
@ -611,10 +634,25 @@ static void UpdateMouseCursor() {
return; return;
SdlData* bd = GetBackendData(); SdlData* bd = GetBackendData();
s16 cursorState = Config::getCursorState();
ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor(); ImGuiMouseCursor imgui_cursor = ImGui::GetMouseCursor();
if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None) { if (io.MouseDrawCursor || imgui_cursor == ImGuiMouseCursor_None ||
cursorState == Config::HideCursorState::Always) {
// Hide OS mouse cursor if imgui is drawing it or if it wants no cursor // Hide OS mouse cursor if imgui is drawing it or if it wants no cursor
SDL_HideCursor(); SDL_HideCursor();
} else if (cursorState == Config::HideCursorState::Idle &&
bd->time - bd->lastCursorMoveTime >=
Config::getCursorHideTimeout() * SDL_GetPerformanceFrequency()) {
bool wasCursorVisible = SDL_CursorVisible();
SDL_HideCursor();
if (wasCursorVisible) {
SDL_WarpMouseInWindow(SDL_GetKeyboardFocus(), bd->prev_mouse_pos.x,
bd->prev_mouse_pos.y); // Force refresh the cursor state
}
} else { } else {
// Show OS mouse cursor // Show OS mouse cursor
SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor] SDL_Cursor* expected_cursor = bd->mouse_cursors[imgui_cursor]

View File

@ -374,11 +374,17 @@ void CheckUpdate::Install() {
QString userPath; QString userPath;
Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir)); Common::FS::PathToQString(userPath, Common::FS::GetUserPath(Common::FS::PathType::UserDir));
QString startingUpdate = tr("Starting Update...");
QString tempDirPath = userPath + "/temp_download_update";
QString rootPath; QString rootPath;
Common::FS::PathToQString(rootPath, std::filesystem::current_path()); Common::FS::PathToQString(rootPath, std::filesystem::current_path());
QString tempDirPath = userPath + "/temp_download_update";
QString startingUpdate = tr("Starting Update...");
QString binaryStartingUpdate;
for (QChar c : startingUpdate) {
binaryStartingUpdate.append(QString::number(c.unicode(), 2).rightJustified(16, '0'));
}
QString scriptContent; QString scriptContent;
QString scriptFileName; QString scriptFileName;
QStringList arguments; QStringList arguments;
@ -389,7 +395,13 @@ void CheckUpdate::Install() {
scriptFileName = tempDirPath + "/update.ps1"; scriptFileName = tempDirPath + "/update.ps1";
scriptContent = QStringLiteral( scriptContent = QStringLiteral(
"Set-ExecutionPolicy Bypass -Scope Process -Force\n" "Set-ExecutionPolicy Bypass -Scope Process -Force\n"
"Write-Output '%1'\n" "$binaryStartingUpdate = '%1'\n"
"$chars = @()\n"
"for ($i = 0; $i -lt $binaryStartingUpdate.Length; $i += 16) {\n"
" $chars += [char]([convert]::ToInt32($binaryStartingUpdate.Substring($i, 16), 2))\n"
"}\n"
"$startingUpdate = -join $chars\n"
"Write-Output $startingUpdate\n"
"Expand-Archive -Path '%2\\temp_download_update.zip' -DestinationPath '%2' -Force\n" "Expand-Archive -Path '%2\\temp_download_update.zip' -DestinationPath '%2' -Force\n"
"Start-Sleep -Seconds 3\n" "Start-Sleep -Seconds 3\n"
"Copy-Item -Recurse -Force '%2\\*' '%3\\'\n" "Copy-Item -Recurse -Force '%2\\*' '%3\\'\n"
@ -454,6 +466,10 @@ void CheckUpdate::Install() {
" sleep 2\n" " sleep 2\n"
" extract_file\n" " extract_file\n"
" sleep 2\n" " sleep 2\n"
" if pgrep -f \"Shadps4-qt.AppImage\" > /dev/null; then\n"
" pkill -f \"Shadps4-qt.AppImage\"\n"
" sleep 2\n"
" fi\n"
" cp -r \"%2/\"* \"%3/\"\n" " cp -r \"%2/\"* \"%3/\"\n"
" sleep 2\n" " sleep 2\n"
" rm \"%3/update.sh\"\n" " rm \"%3/update.sh\"\n"
@ -508,7 +524,12 @@ void CheckUpdate::Install() {
QFile scriptFile(scriptFileName); QFile scriptFile(scriptFileName);
if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) { if (scriptFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
QTextStream out(&scriptFile); QTextStream out(&scriptFile);
#ifdef Q_OS_WIN
out << scriptContent.arg(binaryStartingUpdate).arg(tempDirPath).arg(rootPath);
#endif
#if defined(Q_OS_LINUX) || defined(Q_OS_MAC)
out << scriptContent.arg(startingUpdate).arg(tempDirPath).arg(rootPath); out << scriptContent.arg(startingUpdate).arg(tempDirPath).arg(rootPath);
#endif
scriptFile.close(); scriptFile.close();
// Make the script executable on Unix-like systems // Make the script executable on Unix-like systems
@ -525,4 +546,4 @@ void CheckUpdate::Install() {
this, tr("Error"), this, tr("Error"),
QString(tr("Failed to create the update script file") + ":\n" + scriptFileName)); QString(tr("Failed to create the update script file") + ":\n" + scriptFileName));
} }
} }

View File

@ -10,14 +10,16 @@ GameInfoClass::GameInfoClass() = default;
GameInfoClass::~GameInfoClass() = default; GameInfoClass::~GameInfoClass() = default;
void GameInfoClass::GetGameInfo(QWidget* parent) { void GameInfoClass::GetGameInfo(QWidget* parent) {
QString installDir;
Common::FS::PathToQString(installDir, Config::getGameInstallDir());
QStringList filePaths; QStringList filePaths;
QDir parentFolder(installDir); for (const auto& installLoc : Config::getGameInstallDirs()) {
QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); QString installDir;
for (const auto& fileInfo : fileList) { Common::FS::PathToQString(installDir, installLoc);
if (fileInfo.isDir()) { QDir parentFolder(installDir);
filePaths.append(fileInfo.absoluteFilePath()); QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const auto& fileInfo : fileList) {
if (fileInfo.isDir()) {
filePaths.append(fileInfo.absoluteFilePath());
}
} }
} }
m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) {

View File

@ -60,6 +60,9 @@ public:
if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) { if (auto app_ver = psf.GetString("APP_VER"); app_ver.has_value()) {
game.version = *app_ver; game.version = *app_ver;
} }
if (const auto play_time = psf.GetString("PLAY_TIME"); play_time.has_value()) {
game.play_time = *play_time;
}
} }
return game; return game;
} }

View File

@ -51,7 +51,9 @@ QWidget* GameInstallDialog::SetupGamesDirectory() {
// Input. // Input.
m_gamesDirectory = new QLineEdit(); m_gamesDirectory = new QLineEdit();
QString install_dir; QString install_dir;
Common::FS::PathToQString(install_dir, Config::getGameInstallDir()); std::filesystem::path install_path =
Config::getGameInstallDirs().empty() ? "" : Config::getGameInstallDirs().front();
Common::FS::PathToQString(install_dir, install_path);
m_gamesDirectory->setText(install_dir); m_gamesDirectory->setText(install_dir);
m_gamesDirectory->setMinimumWidth(400); m_gamesDirectory->setMinimumWidth(400);
@ -125,7 +127,9 @@ void GameInstallDialog::Save() {
} }
} }
Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); std::vector<std::filesystem::path> install_dirs;
install_dirs.emplace_back(Common::FS::PathFromQString(gamesDirectory));
Config::setGameInstallDirs(install_dirs);
Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory)); Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory));
const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
Config::save(config_dir / "config.toml"); Config::save(config_dir / "config.toml");

View File

@ -24,16 +24,17 @@ GameListFrame::GameListFrame(std::shared_ptr<GameInfoClass> game_info_get, QWidg
this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setStretchLastSection(true); this->horizontalHeader()->setStretchLastSection(true);
this->setContextMenuPolicy(Qt::CustomContextMenu); this->setContextMenuPolicy(Qt::CustomContextMenu);
this->setColumnCount(8); this->setColumnCount(9);
this->setColumnWidth(1, 300); // Name this->setColumnWidth(1, 300); // Name
this->setColumnWidth(2, 120); // Serial this->setColumnWidth(2, 120); // Serial
this->setColumnWidth(3, 90); // Region this->setColumnWidth(3, 90); // Region
this->setColumnWidth(4, 90); // Firmware this->setColumnWidth(4, 90); // Firmware
this->setColumnWidth(5, 90); // Size this->setColumnWidth(5, 90); // Size
this->setColumnWidth(6, 90); // Version this->setColumnWidth(6, 90); // Version
this->setColumnWidth(7, 100); // Play Time
QStringList headers; QStringList headers;
headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware") headers << tr("Icon") << tr("Name") << tr("Serial") << tr("Region") << tr("Firmware")
<< tr("Size") << tr("Version") << tr("Path"); << tr("Size") << tr("Version") << tr("Play Time") << tr("Path");
this->setHorizontalHeaderLabels(headers); this->setHorizontalHeaderLabels(headers);
this->horizontalHeader()->setSortIndicatorShown(true); this->horizontalHeader()->setSortIndicatorShown(true);
this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents); this->horizontalHeader()->setSectionResizeMode(0, QHeaderView::ResizeToContents);
@ -100,9 +101,37 @@ void GameListFrame::PopulateGameList() {
SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw)); SetTableItem(i, 4, QString::fromStdString(m_game_info->m_games[i].fw));
SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size)); SetTableItem(i, 5, QString::fromStdString(m_game_info->m_games[i].size));
SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version)); SetTableItem(i, 6, QString::fromStdString(m_game_info->m_games[i].version));
QString playTime = GetPlayTime(m_game_info->m_games[i].serial);
if (playTime.isEmpty()) {
m_game_info->m_games[i].play_time = "0:00:00";
SetTableItem(i, 7, "0");
} else {
QStringList timeParts = playTime.split(':');
int hours = timeParts[0].toInt();
int minutes = timeParts[1].toInt();
int seconds = timeParts[2].toInt();
QString formattedPlayTime;
if (hours > 0) {
formattedPlayTime += QString("%1h ").arg(hours);
}
if (minutes > 0) {
formattedPlayTime += QString("%1m ").arg(minutes);
}
formattedPlayTime = formattedPlayTime.trimmed();
m_game_info->m_games[i].play_time = playTime.toStdString();
if (formattedPlayTime.isEmpty()) {
SetTableItem(i, 7, "0");
} else {
SetTableItem(i, 7, formattedPlayTime);
}
}
QString path; QString path;
Common::FS::PathToQString(path, m_game_info->m_games[i].path); Common::FS::PathToQString(path, m_game_info->m_games[i].path);
SetTableItem(i, 7, path); SetTableItem(i, 8, path);
} }
} }
@ -171,7 +200,7 @@ void GameListFrame::ResizeIcons(int iconSize) {
this->setItem(index, 0, iconItem); this->setItem(index, 0, iconItem);
index++; index++;
} }
this->horizontalHeader()->setSectionResizeMode(7, QHeaderView::ResizeToContents); this->horizontalHeader()->setSectionResizeMode(8, QHeaderView::ResizeToContents);
} }
void GameListFrame::SetTableItem(int row, int column, QString itemStr) { void GameListFrame::SetTableItem(int row, int column, QString itemStr) {
@ -224,3 +253,33 @@ void GameListFrame::SetRegionFlag(int row, int column, QString itemStr) {
this->setItem(row, column, item); this->setItem(row, column, item);
this->setCellWidget(row, column, widget); this->setCellWidget(row, column, widget);
} }
QString GameListFrame::GetPlayTime(const std::string& serial) {
QString playTime;
const auto user_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir);
QString filePath = QString::fromStdString((user_dir / "play_time.txt").string());
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
return playTime;
}
while (!file.atEnd()) {
QByteArray line = file.readLine();
QString lineStr = QString::fromUtf8(line).trimmed();
QStringList parts = lineStr.split(' ');
if (parts.size() >= 2) {
QString fileSerial = parts[0];
QString time = parts[1];
if (fileSerial == QString::fromStdString(serial)) {
playTime = time;
break;
}
}
}
file.close();
return playTime;
}

View File

@ -29,6 +29,7 @@ public Q_SLOTS:
private: private:
void SetTableItem(int row, int column, QString itemStr); void SetTableItem(int row, int column, QString itemStr);
void SetRegionFlag(int row, int column, QString itemStr); void SetRegionFlag(int row, int column, QString itemStr);
QString GetPlayTime(const std::string& serial);
QList<QAction*> m_columnActs; QList<QAction*> m_columnActs;
GameInfoClass* game_inf_get = nullptr; GameInfoClass* game_inf_get = nullptr;
bool ListSortedAsc = true; bool ListSortedAsc = true;
@ -68,6 +69,8 @@ public:
case 6: case 6:
return a.version < b.version; return a.version < b.version;
case 7: case 7:
return a.play_time < b.play_time;
case 8:
return a.path < b.path; return a.path < b.path;
default: default:
return false; return false;
@ -89,9 +92,11 @@ public:
case 6: case 6:
return a.version > b.version; return a.version > b.version;
case 7: case 7:
return a.play_time > b.play_time;
case 8:
return a.path > b.path; return a.path > b.path;
default: default:
return false; return false;
} }
} }
}; };

View File

@ -19,6 +19,8 @@ struct GameInfo {
std::string version = "Unknown"; std::string version = "Unknown";
std::string region = "Unknown"; std::string region = "Unknown";
std::string fw = "Unknown"; std::string fw = "Unknown";
std::string play_time = "Unknown";
}; };
class GameListUtils { class GameListUtils {

View File

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <QDialogButtonBox>
#include <QDir>
#include <QFileDialog>
#include <QGroupBox>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QMessageBox>
#include <QPushButton>
#include <QVBoxLayout>
#include "install_dir_select.h"
InstallDirSelect::InstallDirSelect() : selected_dir() {
selected_dir = Config::getGameInstallDirs().empty() ? "" : Config::getGameInstallDirs().front();
if (!Config::getGameInstallDirs().empty() && Config::getGameInstallDirs().size() == 1) {
reject();
}
auto layout = new QVBoxLayout(this);
layout->addWidget(SetupInstallDirList());
layout->addStretch();
layout->addWidget(SetupDialogActions());
setWindowTitle(tr("shadPS4 - Choose directory"));
setWindowIcon(QIcon(":images/shadps4.ico"));
}
InstallDirSelect::~InstallDirSelect() {}
QWidget* InstallDirSelect::SetupInstallDirList() {
auto group = new QGroupBox(tr("Select which directory you want to install to."));
auto vlayout = new QVBoxLayout();
auto m_path_list = new QListWidget();
QList<QString> qt_list;
for (const auto& str : Config::getGameInstallDirs()) {
QString installDirPath;
Common::FS::PathToQString(installDirPath, str);
qt_list.append(installDirPath);
}
m_path_list->insertItems(0, qt_list);
m_path_list->setSpacing(1);
connect(m_path_list, &QListWidget::itemClicked, this, &InstallDirSelect::setSelectedDirectory);
connect(m_path_list, &QListWidget::itemActivated, this,
&InstallDirSelect::setSelectedDirectory);
vlayout->addWidget(m_path_list);
group->setLayout(vlayout);
return group;
}
void InstallDirSelect::setSelectedDirectory(QListWidgetItem* item) {
if (item) {
const auto highlighted_path = Common::FS::PathFromQString(item->text());
if (!highlighted_path.empty()) {
selected_dir = highlighted_path;
}
}
}
QWidget* InstallDirSelect::SetupDialogActions() {
auto actions = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
connect(actions, &QDialogButtonBox::accepted, this, &InstallDirSelect::accept);
connect(actions, &QDialogButtonBox::rejected, this, &InstallDirSelect::reject);
return actions;
}

View File

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QDialog>
#include <QListWidget>
#include "common/config.h"
#include "common/path_util.h"
class QLineEdit;
class InstallDirSelect final : public QDialog {
public:
InstallDirSelect();
~InstallDirSelect();
std::filesystem::path getSelectedDirectory() {
return selected_dir;
}
private slots:
void BrowseGamesDirectory();
private:
QWidget* SetupInstallDirList();
QWidget* SetupDialogActions();
void setSelectedDirectory(QListWidgetItem* item);
std::filesystem::path selected_dir;
};

View File

@ -30,7 +30,7 @@ int main(int argc, char* argv[]) {
bool has_command_line_argument = argc > 1; bool has_command_line_argument = argc > 1;
// Check if the game install directory is set // Check if the game install directory is set
if (Config::getGameInstallDir().empty() && !has_command_line_argument) { if (Config::getGameInstallDirs().empty() && !has_command_line_argument) {
GameInstallDialog dlg; GameInstallDialog dlg;
dlg.exec(); dlg.exec();
} }

View File

@ -16,6 +16,7 @@
#include "core/file_format/pkg.h" #include "core/file_format/pkg.h"
#include "core/loader.h" #include "core/loader.h"
#include "game_install_dialog.h" #include "game_install_dialog.h"
#include "install_dir_select.h"
#include "main_window.h" #include "main_window.h"
#include "settings_dialog.h" #include "settings_dialog.h"
#include "video_core/renderer_vulkan/vk_instance.h" #include "video_core/renderer_vulkan/vk_instance.h"
@ -58,6 +59,7 @@ bool MainWindow::Init() {
this->show(); this->show();
// load game list // load game list
LoadGameLists(); LoadGameLists();
// Check for update
CheckUpdateMain(true); CheckUpdateMain(true);
auto end = std::chrono::steady_clock::now(); auto end = std::chrono::steady_clock::now();
@ -69,6 +71,14 @@ bool MainWindow::Init() {
QString statusMessage = QString statusMessage =
"Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)"; "Games: " + QString::number(numGames) + " (" + QString::number(duration.count()) + "ms)";
statusBar->showMessage(statusMessage); statusBar->showMessage(statusMessage);
// Initialize Discord RPC
if (Config::getEnableDiscordRPC()) {
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
rpc->init();
rpc->setStatusIdling();
}
return true; return true;
} }
@ -663,7 +673,10 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason)); QMessageBox::critical(this, tr("PKG ERROR"), QString::fromStdString(failreason));
return; return;
} }
auto extract_path = Config::getGameInstallDir() / pkg.GetTitleID(); InstallDirSelect ids;
ids.exec();
auto game_install_dir = ids.getSelectedDirectory();
auto extract_path = game_install_dir / pkg.GetTitleID();
QString pkgType = QString::fromStdString(pkg.GetPkgFlags()); QString pkgType = QString::fromStdString(pkg.GetPkgFlags());
QString gameDirPath; QString gameDirPath;
Common::FS::PathToQString(gameDirPath, extract_path); Common::FS::PathToQString(gameDirPath, extract_path);
@ -812,7 +825,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int
connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() { connect(&futureWatcher, &QFutureWatcher<void>::finished, this, [=, this]() {
if (pkgNum == nPkg) { if (pkgNum == nPkg) {
QString path; QString path;
Common::FS::PathToQString(path, Config::getGameInstallDir()); Common::FS::PathToQString(path, game_install_dir);
QMessageBox extractMsgBox(this); QMessageBox extractMsgBox(this);
extractMsgBox.setWindowTitle(tr("Extraction Finished")); extractMsgBox.setWindowTitle(tr("Extraction Finished"));
extractMsgBox.setText( extractMsgBox.setText(

View File

@ -9,6 +9,7 @@
#include "background_music_player.h" #include "background_music_player.h"
#include "common/config.h" #include "common/config.h"
#include "common/discord_rpc_handler.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "core/file_format/psf.h" #include "core/file_format/psf.h"
#include "core/file_sys/fs.h" #include "core/file_sys/fs.h"

View File

@ -67,6 +67,15 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
completer->setCaseSensitivity(Qt::CaseInsensitive); completer->setCaseSensitivity(Qt::CaseInsensitive);
ui->consoleLanguageComboBox->setCompleter(completer); ui->consoleLanguageComboBox->setCompleter(completer);
ui->hideCursorComboBox->addItem(tr("Never"));
ui->hideCursorComboBox->addItem(tr("Idle"));
ui->hideCursorComboBox->addItem(tr("Always"));
ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Left"), "left");
ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Center"), "center");
ui->backButtonBehaviorComboBox->addItem(tr("Touchpad Right"), "right");
ui->backButtonBehaviorComboBox->addItem(tr("None"), "none");
InitializeEmulatorLanguages(); InitializeEmulatorLanguages();
LoadValuesFromConfig(); LoadValuesFromConfig();
@ -151,6 +160,37 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
Config::setBGMvolume(val); Config::setBGMvolume(val);
BackgroundMusicPlayer::getInstance().setVolume(val); BackgroundMusicPlayer::getInstance().setVolume(val);
}); });
connect(ui->discordRPCCheckbox, &QCheckBox::stateChanged, this, [](int val) {
Config::setEnableDiscordRPC(val);
auto* rpc = Common::Singleton<DiscordRPCHandler::RPC>::Instance();
if (val == Qt::Checked) {
rpc->init();
rpc->setStatusIdling();
} else {
rpc->shutdown();
}
});
}
// Input TAB
{
connect(ui->hideCursorComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
[this](s16 index) {
Config::setCursorState(index);
OnCursorStateChanged(index);
});
connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this,
[](int index) { Config::setCursorHideTimeout(index); });
connect(ui->backButtonBehaviorComboBox, QOverload<int>::of(&QComboBox::currentIndexChanged),
this, [this](int index) {
if (index >= 0 && index < ui->backButtonBehaviorComboBox->count()) {
QString data = ui->backButtonBehaviorComboBox->itemData(index).toString();
Config::setBackButtonBehavior(data.toStdString());
}
});
} }
// GPU TAB // GPU TAB
@ -176,6 +216,51 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
[](int val) { Config::setNullGpu(val); }); [](int val) { Config::setNullGpu(val); });
} }
// PATH TAB
{
ui->removeFolderButton->setEnabled(false);
connect(ui->addFolderButton, &QPushButton::clicked, this, [this]() {
const auto config_dir = Config::getGameInstallDirs();
QString file_path_string =
QFileDialog::getExistingDirectory(this, tr("Directory to install games"));
auto file_path = Common::FS::PathFromQString(file_path_string);
bool not_already_included =
std::find(config_dir.begin(), config_dir.end(), file_path) == config_dir.end();
if (!file_path.empty() && not_already_included) {
std::vector<std::filesystem::path> install_dirs = config_dir;
install_dirs.push_back(file_path);
Config::setGameInstallDirs(install_dirs);
QListWidgetItem* item = new QListWidgetItem(file_path_string);
ui->gameFoldersListWidget->addItem(item);
}
});
connect(ui->gameFoldersListWidget, &QListWidget::itemSelectionChanged, this, [this]() {
ui->removeFolderButton->setEnabled(
!ui->gameFoldersListWidget->selectedItems().isEmpty());
});
connect(ui->removeFolderButton, &QPushButton::clicked, this, [this]() {
QListWidgetItem* selected_item = ui->gameFoldersListWidget->currentItem();
QString item_path_string = selected_item ? selected_item->text() : QString();
if (!item_path_string.isEmpty()) {
auto file_path = Common::FS::PathFromQString(item_path_string);
std::vector<std::filesystem::path> install_dirs = Config::getGameInstallDirs();
auto iterator = std::remove_if(
install_dirs.begin(), install_dirs.end(),
[&file_path](const std::filesystem::path& dir) { return file_path == dir; });
if (iterator != install_dirs.end()) {
install_dirs.erase(iterator, install_dirs.end());
delete selected_item;
}
Config::setGameInstallDirs(install_dirs);
}
});
}
// DEBUG TAB // DEBUG TAB
{ {
connect(ui->debugDump, &QCheckBox::stateChanged, this, connect(ui->debugDump, &QCheckBox::stateChanged, this,
@ -205,6 +290,12 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->updaterGroupBox->installEventFilter(this); ui->updaterGroupBox->installEventFilter(this);
ui->GUIgroupBox->installEventFilter(this); ui->GUIgroupBox->installEventFilter(this);
// Input
ui->cursorGroupBox->installEventFilter(this);
ui->hideCursorGroupBox->installEventFilter(this);
ui->idleTimeoutGroupBox->installEventFilter(this);
ui->backButtonBehaviorGroupBox->installEventFilter(this);
// Graphics // Graphics
ui->graphicsAdapterGroupBox->installEventFilter(this); ui->graphicsAdapterGroupBox->installEventFilter(this);
ui->widthGroupBox->installEventFilter(this); ui->widthGroupBox->installEventFilter(this);
@ -213,6 +304,12 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->dumpShadersCheckBox->installEventFilter(this); ui->dumpShadersCheckBox->installEventFilter(this);
ui->nullGpuCheckBox->installEventFilter(this); ui->nullGpuCheckBox->installEventFilter(this);
// Paths
ui->gameFoldersGroupBox->installEventFilter(this);
ui->gameFoldersListWidget->installEventFilter(this);
ui->addFolderButton->installEventFilter(this);
ui->removeFolderButton->installEventFilter(this);
// Debug // Debug
ui->debugDump->installEventFilter(this); ui->debugDump->installEventFilter(this);
ui->vkValidationCheckBox->installEventFilter(this); ui->vkValidationCheckBox->installEventFilter(this);
@ -228,6 +325,9 @@ void SettingsDialog::LoadValuesFromConfig() {
std::find(languageIndexes.begin(), languageIndexes.end(), Config::GetLanguage())) % std::find(languageIndexes.begin(), languageIndexes.end(), Config::GetLanguage())) %
languageIndexes.size()); languageIndexes.size());
ui->emulatorLanguageComboBox->setCurrentIndex(languages[Config::getEmulatorLanguage()]); ui->emulatorLanguageComboBox->setCurrentIndex(languages[Config::getEmulatorLanguage()]);
ui->hideCursorComboBox->setCurrentIndex(Config::getCursorState());
OnCursorStateChanged(Config::getCursorState());
ui->idleTimeoutSpinBox->setValue(Config::getCursorHideTimeout());
ui->graphicsAdapterBox->setCurrentIndex(Config::getGpuId() + 1); ui->graphicsAdapterBox->setCurrentIndex(Config::getGpuId() + 1);
ui->widthSpinBox->setValue(Config::getScreenWidth()); ui->widthSpinBox->setValue(Config::getScreenWidth());
ui->heightSpinBox->setValue(Config::getScreenHeight()); ui->heightSpinBox->setValue(Config::getScreenHeight());
@ -236,6 +336,7 @@ void SettingsDialog::LoadValuesFromConfig() {
ui->nullGpuCheckBox->setChecked(Config::nullGpu()); ui->nullGpuCheckBox->setChecked(Config::nullGpu());
ui->playBGMCheckBox->setChecked(Config::getPlayBGM()); ui->playBGMCheckBox->setChecked(Config::getPlayBGM());
ui->BGMVolumeSlider->setValue((Config::getBGMvolume())); ui->BGMVolumeSlider->setValue((Config::getBGMvolume()));
ui->discordRPCCheckbox->setChecked(Config::getEnableDiscordRPC());
ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode()); ui->fullscreenCheckBox->setChecked(Config::isFullscreenMode());
ui->showSplashCheckBox->setChecked(Config::showSplash()); ui->showSplashCheckBox->setChecked(Config::showSplash());
ui->ps4proCheckBox->setChecked(Config::isNeoMode()); ui->ps4proCheckBox->setChecked(Config::isNeoMode());
@ -258,6 +359,17 @@ void SettingsDialog::LoadValuesFromConfig() {
} }
} }
ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel)); ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel));
for (const auto& dir : Config::getGameInstallDirs()) {
QString path_string;
Common::FS::PathToQString(path_string, dir);
QListWidgetItem* item = new QListWidgetItem(path_string);
ui->gameFoldersListWidget->addItem(item);
}
QString backButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior());
int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior);
ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0);
} }
void SettingsDialog::InitializeEmulatorLanguages() { void SettingsDialog::InitializeEmulatorLanguages() {
@ -289,6 +401,18 @@ void SettingsDialog::OnLanguageChanged(int index) {
emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString()); emit LanguageChanged(ui->emulatorLanguageComboBox->itemData(index).toString().toStdString());
} }
void SettingsDialog::OnCursorStateChanged(s16 index) {
if (index == -1)
return;
if (index == Config::HideCursorState::Idle) {
ui->idleTimeoutGroupBox->show();
} else {
if (!ui->idleTimeoutGroupBox->isHidden()) {
ui->idleTimeoutGroupBox->hide();
}
}
}
int SettingsDialog::exec() { int SettingsDialog::exec() {
return QDialog::exec(); return QDialog::exec();
} }
@ -321,6 +445,17 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("GUIgroupBox"); text = tr("GUIgroupBox");
} }
// Input
if (elementName == "cursorGroupBox") {
text = tr("cursorGroupBox");
} else if (elementName == "hideCursorGroupBox") {
text = tr("hideCursorGroupBox");
} else if (elementName == "idleTimeoutGroupBox") {
text = tr("idleTimeoutGroupBox");
} else if (elementName == "backButtonBehaviorGroupBox") {
text = tr("backButtonBehaviorGroupBox");
}
// Graphics // Graphics
if (elementName == "graphicsAdapterGroupBox") { if (elementName == "graphicsAdapterGroupBox") {
text = tr("graphicsAdapterGroupBox"); text = tr("graphicsAdapterGroupBox");
@ -334,8 +469,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("dumpShadersCheckBox"); text = tr("dumpShadersCheckBox");
} else if (elementName == "nullGpuCheckBox") { } else if (elementName == "nullGpuCheckBox") {
text = tr("nullGpuCheckBox"); text = tr("nullGpuCheckBox");
} else if (elementName == "dumpPM4CheckBox") { }
text = tr("dumpPM4CheckBox");
// Path
if (elementName == "gameFoldersGroupBox" || elementName == "gameFoldersListWidget") {
text = tr("gameFoldersBox");
} else if (elementName == "addFolderButton") {
text = tr("addFolderButton");
} else if (elementName == "removeFolderButton") {
text = tr("removeFolderButton");
} }
// Debug // Debug
@ -352,7 +494,7 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
ui->descriptionText->setText(text.replace("\\n", "\n")); ui->descriptionText->setText(text.replace("\\n", "\n"));
} }
bool SettingsDialog::override(QObject* obj, QEvent* event) { bool SettingsDialog::eventFilter(QObject* obj, QEvent* event) {
if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) { if (event->type() == QEvent::Enter || event->type() == QEvent::Leave) {
if (qobject_cast<QWidget*>(obj)) { if (qobject_cast<QWidget*>(obj)) {
bool hovered = (event->type() == QEvent::Enter); bool hovered = (event->type() == QEvent::Enter);

View File

@ -21,7 +21,7 @@ public:
explicit SettingsDialog(std::span<const QString> physical_devices, QWidget* parent = nullptr); explicit SettingsDialog(std::span<const QString> physical_devices, QWidget* parent = nullptr);
~SettingsDialog(); ~SettingsDialog();
bool override(QObject* obj, QEvent* event); bool eventFilter(QObject* obj, QEvent* event) override;
void updateNoteTextEdit(const QString& groupName); void updateNoteTextEdit(const QString& groupName);
int exec() override; int exec() override;
@ -33,6 +33,7 @@ private:
void LoadValuesFromConfig(); void LoadValuesFromConfig();
void InitializeEmulatorLanguages(); void InitializeEmulatorLanguages();
void OnLanguageChanged(int index); void OnLanguageChanged(int index);
void OnCursorStateChanged(s16 index);
std::unique_ptr<Ui::SettingsDialog> ui; std::unique_ptr<Ui::SettingsDialog> ui;

View File

@ -12,7 +12,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>854</width> <width>854</width>
<height>570</height> <height>605</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -34,9 +34,18 @@
<iconset> <iconset>
<normaloff>:/images/shadps4.ico</normaloff>:/images/shadps4.ico</iconset> <normaloff>:/images/shadps4.ico</normaloff>:/images/shadps4.ico</iconset>
</property> </property>
<property name="sizeGripEnabled">
<bool>false</bool>
</property>
<property name="modal">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="settingsDialogLayout"> <layout class="QVBoxLayout" name="settingsDialogLayout">
<item> <item>
<widget class="QScrollArea" name="scrollArea"> <widget class="QScrollArea" name="scrollArea">
<property name="enabled">
<bool>true</bool>
</property>
<property name="frameShape"> <property name="frameShape">
<enum>QFrame::Shape::NoFrame</enum> <enum>QFrame::Shape::NoFrame</enum>
</property> </property>
@ -51,8 +60,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>836</width> <width>832</width>
<height>446</height> <height>431</height>
</rect> </rect>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
@ -139,6 +148,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item>
<widget class="QCheckBox" name="discordRPCCheckbox">
<property name="text">
<string>Enable Discord Rich Presence</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
@ -258,6 +274,9 @@
<layout class="QHBoxLayout" name="generalTabHLayout_2"> <layout class="QHBoxLayout" name="generalTabHLayout_2">
<item> <item>
<layout class="QVBoxLayout" name="updaterTabLayoutLeft"> <layout class="QVBoxLayout" name="updaterTabLayoutLeft">
<property name="sizeConstraint">
<enum>QLayout::SizeConstraint::SetDefaultConstraint</enum>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>0</number> <number>0</number>
</property> </property>
@ -270,8 +289,157 @@
<property name="bottomMargin"> <property name="bottomMargin">
<number>0</number> <number>0</number>
</property> </property>
<item> <item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QGroupBox" name="updaterGroupBox"> <widget class="QGroupBox" name="updaterGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>275</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Update</string>
</property>
<layout class="QVBoxLayout" name="UpdateLayout" stretch="0,0,0">
<property name="spacing">
<number>5</number>
</property>
<property name="topMargin">
<number>1</number>
</property>
<property name="rightMargin">
<number>11</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item>
<widget class="QGroupBox" name="updaterComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>75</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title">
<string>Update Channel</string>
</property>
<layout class="QVBoxLayout" name="UpdateChannelLayout">
<property name="spacing">
<number>7</number>
</property>
<property name="leftMargin">
<number>11</number>
</property>
<property name="topMargin">
<number>11</number>
</property>
<property name="rightMargin">
<number>11</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item>
<widget class="QComboBox" name="updateComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>Release</string>
</property>
</item>
<item>
<property name="text">
<string>Nightly</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QPushButton" name="checkUpdateButton">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>197</width>
<height>28</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Check for Updates</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="updateCheckBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="font">
<font>
<pointsize>11</pointsize>
<bold>false</bold>
</font>
</property>
<property name="text">
<string>Check for Updates at Startup</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="GUITabLayoutMiddle" stretch="0">
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QGroupBox" name="GUIgroupBox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -280,80 +448,315 @@
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>265</width> <width>0</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="title"> <property name="title">
<string>Update</string> <string>GUI Settings</string>
</property> </property>
<widget class="QCheckBox" name="updateCheckBox"> <layout class="QVBoxLayout" name="GUILayout">
<property name="geometry"> <property name="topMargin">
<rect> <number>1</number>
<x>10</x>
<y>130</y>
<width>261</width>
<height>22</height>
</rect>
</property> </property>
<property name="text"> <property name="bottomMargin">
<string>Check for Updates at Startup</string> <number>11</number>
</property> </property>
</widget> <item>
<widget class="QGroupBox" name="updaterComboBox"> <layout class="QVBoxLayout" name="GUIMusicLayout">
<property name="geometry"> <property name="topMargin">
<rect> <number>1</number>
<x>12</x>
<y>30</y>
<width>241</width>
<height>65</height>
</rect>
</property>
<property name="title">
<string>Update Channel</string>
</property>
<widget class="QComboBox" name="updateComboBox">
<property name="geometry">
<rect>
<x>12</x>
<y>30</y>
<width>217</width>
<height>28</height>
</rect>
</property>
<item>
<property name="text">
<string>Release</string>
</property> </property>
</item> <property name="bottomMargin">
<item> <number>0</number>
<property name="text">
<string>Nightly</string>
</property> </property>
</item> <item>
</widget> <widget class="QCheckBox" name="playBGMCheckBox">
</widget> <property name="sizePolicy">
<widget class="QPushButton" name="checkUpdateButton"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<property name="geometry"> <horstretch>0</horstretch>
<rect> <verstretch>0</verstretch>
<x>25</x> </sizepolicy>
<y>100</y> </property>
<width>215</width> <property name="text">
<height>24</height> <string>Play title music</string>
</rect> </property>
</property> </widget>
<property name="text"> </item>
<string>Check for Updates</string> <item>
</property> <spacer name="GUIverticalSpacer_2">
</widget> <property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>2</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>Volume</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="BGMVolumeSlider">
<property name="toolTip">
<string>Set the volume of the background music.</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="invertedControls">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::NoTicks</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QWidget" name="GUIwidgetSpacer" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>61</height>
</size>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="GUITabLayoutMiddle" stretch="1"> <layout class="QVBoxLayout" name="EmptyTabLayoutRight">
<item> <item>
<widget class="QGroupBox" name="GUIgroupBox"> <spacer name="emptyHorizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="inputTab">
<attribute name="title">
<string>Input</string>
</attribute>
<layout class="QVBoxLayout" name="inputTabVLayout" stretch="0,0">
<item>
<layout class="QHBoxLayout" name="inputTabHLayoutTop" stretch="1,1,1">
<item>
<layout class="QVBoxLayout" name="cursorTabLayoutLeft">
<property name="spacing">
<number>7</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QGroupBox" name="cursorGroupBox">
<property name="title">
<string>Cursor</string>
</property>
<layout class="QVBoxLayout" name="inputCursorLayout">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>11</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item>
<widget class="QGroupBox" name="hideCursorGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Hide Cursor</string>
</property>
<layout class="QVBoxLayout" name="hideCursorLayout">
<property name="spacing">
<number>7</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item>
<widget class="QComboBox" name="hideCursorComboBox"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="idleTimeoutGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Hide Cursor Idle Timeout</string>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop</set>
</property>
<property name="flat">
<bool>false</bool>
</property>
<layout class="QHBoxLayout" name="IdleTimeoutLayout" stretch="0,0">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>70</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QSpinBox" name="idleTimeoutSpinBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>80</width>
<height>30</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="layoutDirection">
<enum>Qt::LayoutDirection::LeftToRight</enum>
</property>
<property name="wrapping">
<bool>false</bool>
</property>
<property name="alignment">
<set>Qt::AlignmentFlag::AlignCenter</set>
</property>
<property name="buttonSymbols">
<enum>QAbstractSpinBox::ButtonSymbols::UpDownArrows</enum>
</property>
<property name="suffix">
<string notr="true"/>
</property>
<property name="maximum">
<number>3600</number>
</property>
<property name="value">
<number>5</number>
</property>
<property name="displayIntegerBase">
<number>10</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="idleTimeoutDurationLabel">
<property name="text">
<string>s</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QVBoxLayout" name="ControllerTabLayoutMiddle">
<item>
<widget class="QGroupBox" name="ControllerGroupBox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
@ -361,98 +764,102 @@
</sizepolicy> </sizepolicy>
</property> </property>
<property name="title"> <property name="title">
<string>GUI Settings</string> <string>Controller</string>
</property> </property>
<widget class="QWidget" name="verticalLayoutWidget_3"> <layout class="QVBoxLayout" name="ControllerLayout">
<property name="geometry"> <property name="spacing">
<rect> <number>0</number>
<x>10</x>
<y>30</y>
<width>241</width>
<height>71</height>
</rect>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout"> <property name="topMargin">
<item> <number>11</number>
<widget class="QCheckBox" name="playBGMCheckBox"> </property>
<property name="sizePolicy"> <property name="bottomMargin">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed"> <number>11</number>
<horstretch>0</horstretch> </property>
<verstretch>0</verstretch> <item>
</sizepolicy> <widget class="QGroupBox" name="backButtonBehaviorGroupBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>237</width>
<height>0</height>
</size>
</property>
<property name="title">
<string>Back Button Behavior</string>
</property>
<layout class="QVBoxLayout" name="BackButtonLayout">
<property name="leftMargin">
<number>11</number>
</property> </property>
<property name="text">
<string>Play title music</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item> <item>
<layout class="QVBoxLayout" name="verticalLayout_2"> <widget class="QComboBox" name="backButtonBehaviorComboBox"/>
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Volume</string>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="BGMVolumeSlider">
<property name="toolTip">
<string>Set the volume of the background music.</string>
</property>
<property name="maximum">
<number>100</number>
</property>
<property name="singleStep">
<number>10</number>
</property>
<property name="pageStep">
<number>20</number>
</property>
<property name="value">
<number>50</number>
</property>
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="invertedAppearance">
<bool>false</bool>
</property>
<property name="invertedControls">
<bool>false</bool>
</property>
<property name="tickPosition">
<enum>QSlider::TickPosition::NoTicks</enum>
</property>
<property name="tickInterval">
<number>10</number>
</property>
</widget>
</item>
</layout>
</item> </item>
</layout> </layout>
</item> </widget>
</layout> </item>
</widget> <item>
<widget class="QWidget" name="controllerWidgetSpacer" native="true">
<property name="enabled">
<bool>true</bool>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<spacer name="horizontalSpacer"> <layout class="QVBoxLayout" name="emptyTabLayoutRight">
<item>
<spacer name="emptyhorizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="inputTabHLayoutBottom">
<property name="topMargin">
<number>0</number>
</property>
<item>
<spacer name="emptyVerticalSpacerBottom">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Horizontal</enum> <enum>Qt::Orientation::Vertical</enum>
</property> </property>
<property name="sizeType"> <property name="sizeType">
<enum>QSizePolicy::Policy::Expanding</enum> <enum>QSizePolicy::Policy::MinimumExpanding</enum>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="sizeHint" stdset="0">
<size> <size>
<width>0</width> <width>20</width>
<height>0</height> <height>20</height>
</size> </size>
</property> </property>
</spacer> </spacer>
@ -699,6 +1106,76 @@
</item> </item>
</layout> </layout>
</widget> </widget>
<widget class="QWidget" name="pathsTab">
<attribute name="title">
<string>Paths</string>
</attribute>
<layout class="QVBoxLayout" name="inputTabVLayout" stretch="0">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QGroupBox" name="gameFoldersGroupBox">
<property name="title">
<string>Game Folders</string>
</property>
<widget class="QListWidget" name="gameFoldersListWidget">
<property name="geometry">
<rect>
<x>0</x>
<y>20</y>
<width>401</width>
<height>331</height>
</rect>
</property>
</widget>
<widget class="QPushButton" name="addFolderButton">
<property name="geometry">
<rect>
<x>100</x>
<y>360</y>
<width>80</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Add...</string>
</property>
</widget>
<widget class="QPushButton" name="removeFolderButton">
<property name="geometry">
<rect>
<x>210</x>
<y>360</y>
<width>80</width>
<height>24</height>
</rect>
</property>
<property name="text">
<string>Remove</string>
</property>
</widget>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Policy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="debugTab"> <widget class="QWidget" name="debugTab">
<attribute name="title"> <attribute name="title">
<string>Debug</string> <string>Debug</string>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>تمكين وحدة معالجة الرسومات الفارغة</translation> <translation>تمكين وحدة معالجة الرسومات الفارغة</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4 تمكين تفريغ</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>تمكين GPU الافتراضية:\nلأغراض تصحيح الأخطاء التقنية، يقوم بتعطيل عرض اللعبة كما لو لم يكن هناك بطاقة رسومات.</translation> <translation>تمكين GPU الافتراضية:\nلأغراض تصحيح الأخطاء التقنية، يقوم بتعطيل عرض اللعبة كما لو لم يكن هناك بطاقة رسومات.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>تمكين تفريغ PM4:\nلأغراض تصحيح الأخطاء التقنية، يحفظ بيانات تعليمات GPU الأولية في مجلد أثناء معالجتها.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>مسار</translation> <translation>مسار</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>وقت اللعب</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Aktiver virtuel GPU:\nTil teknisk fejlfinding deaktiverer det spilvisning, som om der ikke var et grafikkort.</translation> <translation>Aktiver virtuel GPU:\nTil teknisk fejlfinding deaktiverer det spilvisning, som om der ikke var et grafikkort.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Aktiver dumping af PM4:\nTil teknisk fejlfinding gemmer det GPU-kommandoer i en mappe under behandling.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Sti</translation> <translation>Sti</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Spilletid</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPU aktivieren</translation> <translation>NULL GPU aktivieren</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4-Dumping aktivieren</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Virtuelle GPU aktivieren:\nFür das technische Debugging deaktiviert es die Spielanzeige, als ob keine Grafikkarte vorhanden wäre.</translation> <translation>Virtuelle GPU aktivieren:\nFür das technische Debugging deaktiviert es die Spielanzeige, als ob keine Grafikkarte vorhanden wäre.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4-Dumping aktivieren:\nZum technischen Debuggen speichert es rohe GPU-Befehlsdaten in einem Ordner während der Verarbeitung.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Pfad</translation> <translation>Pfad</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Spielzeit</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Ενεργοποίηση Εικονικής GPU:\ια τεχνικό εντοπισμό σφαλμάτων, απενεργοποιεί την εμφάνιση του παιχνιδιού σαν να μην υπάρχει κάρτα γραφικών.</translation> <translation>Ενεργοποίηση Εικονικής GPU:\ια τεχνικό εντοπισμό σφαλμάτων, απενεργοποιεί την εμφάνιση του παιχνιδιού σαν να μην υπάρχει κάρτα γραφικών.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Ενεργοποίηση Καταγραφής PM4:\ια τεχνικό εντοπισμό σφαλμάτων, αποθηκεύει τις ακατέργαστες εντολές της GPU σε φάκελο κατά την επεξεργασία.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Διαδρομή</translation> <translation>Διαδρομή</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Χρόνος παιχνιδιού</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -434,6 +434,41 @@
<source>Log Filter</source> <source>Log Filter</source>
<translation>Log Filter</translation> <translation>Log Filter</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="595"/>
<source>Input</source>
<translation>Input</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="611"/>
<source>Cursor</source>
<translation>Cursor</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="635"/>
<source>Hide Cursor</source>
<translation>Hide Cursor</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="668"/>
<source>Hide Cursor Idle Timeout</source>
<translation>Hide Cursor Idle Timeout</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="595"/>
<source>Input</source>
<translation>Input</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="767"/>
<source>Controller</source>
<translation>Controller</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="797"/>
<source>Back Button Behavior</source>
<translation>Back Button Behavior</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="272"/> <location filename="../settings_dialog.ui" line="272"/>
<source>Graphics</source> <source>Graphics</source>
@ -474,11 +509,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1028,6 +1058,66 @@
<source>GUIgroupBox</source> <source>GUIgroupBox</source>
<translation>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</translation> <translation>Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="392"/>
<source>cursorGroupBox</source>
<translation>Cursor:\nChange settings related to the cursor.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="394"/>
<source>hideCursorGroupBox</source>
<translation>Hide Cursor:\nSet cursor hiding behavior.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="396"/>
<source>idleTimeoutGroupBox</source>
<translation>Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="70"/>
<source>Never</source>
<translation>Never</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="71"/>
<source>Idle</source>
<translation>Idle</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="72"/>
<source>Always</source>
<translation>Always</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="330"/>
<source>backButtonBehaviorGroupBox</source>
<translation>Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="330"/>
<source>backButtonBehaviorGroupBox</source>
<translation>Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="101"/>
<source>Touchpad Left</source>
<translation>Touchpad Left</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="102"/>
<source>Touchpad Right</source>
<translation>Touchpad Right</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="103"/>
<source>Touchpad Center</source>
<translation>Touchpad Center</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="104"/>
<source>None</source>
<translation>None</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="312"/> <location filename="../settings_dialog.cpp" line="312"/>
<source>graphicsAdapterGroupBox</source> <source>graphicsAdapterGroupBox</source>
@ -1054,9 +1144,19 @@
<translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation> <translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="323"/> <location filename="../settings_dialog.cpp" line="465"/>
<source>dumpPM4CheckBox</source> <source>gameFoldersBox</source>
<translation>Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it.</translation> <translation>Game Folders: The list of folders to check for installed games.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="465"/>
<source>addFolderButton</source>
<translation>Add: Add a folder to the list.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="465"/>
<source>removeFolderButton</source>
<translation>Remove: Remove a folder from the list.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
@ -1121,6 +1221,11 @@
<source>Path</source> <source>Path</source>
<translation>Path</translation> <translation>Path</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Play Time</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Habilitar GPU NULL</translation> <translation>Habilitar GPU NULL</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Habilitar volcado de PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Habilitar GPU Nula:\nPor el bien de la depuración técnica, desactiva el renderizado del juego como si no hubiera tarjeta gráfica.</translation> <translation>Habilitar GPU Nula:\nPor el bien de la depuración técnica, desactiva el renderizado del juego como si no hubiera tarjeta gráfica.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Habilitar la Volcadura de PM4:\nPor el bien de la depuración técnica, guarda los datos de instrucciones crudas de GPU en una carpeta a medida que el emulador los procesa.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Ruta</translation> <translation>Ruta</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Tiempo de Juego</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPU فعال کردن</translation> <translation>NULL GPU فعال کردن</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4 Dumping فعال کردن</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation> <translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>مسیر</translation> <translation>مسیر</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>زمان بازی</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Ota Null GPU käyttöön:\nTeknistä vianetsintää varten pelin renderöinti estetään niin, että ikään kuin grafiikkakorttia ei olisi.</translation> <translation>Ota Null GPU käyttöön:\nTeknistä vianetsintää varten pelin renderöinti estetään niin, että ikään kuin grafiikkakorttia ei olisi.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Ota PM4 dumpaus käyttöön:\nTeknistä vianetsintää varten raakoja GPU-ohjeita tallennetaan kansioon emulaattorin käsitellessä sitä.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Polku</translation> <translation>Polku</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Peliaika</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPU</translation> <translation>NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Dumper le PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Activer le GPU nul :\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique.</translation> <translation>Activer le GPU nul :\nPour le débogage technique, désactive le rendu du jeu comme s'il n'y avait pas de carte graphique.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Activer l'exportation PM4 :\nPour le débogage technique, enregistre les données brutes des instructions GPU dans un dossier pendant que l'émulateur les traite.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Répertoire</translation> <translation>Répertoire</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Temps de jeu</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPU Engedélyezése</translation> <translation>NULL GPU Engedélyezése</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4 Dumpolás Engedélyezése</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Null GPU engedélyezése:\nMűszaki hibaelhárítás céljából letiltja a játék renderelését, mintha nem lenne grafikus kártya.</translation> <translation>Null GPU engedélyezése:\nMűszaki hibaelhárítás céljából letiltja a játék renderelését, mintha nem lenne grafikus kártya.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4 dumpolás engedélyezése:\nMűszaki hibaelhárítás céljából a nyers GPU utasítási adatokat elmenti egy mappába, ahogy az emulátor feldolgozza őket.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Útvonal</translation> <translation>Útvonal</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Játékidő</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Aktifkan GPU Null:\nUntuk tujuan debugging teknis, menonaktifkan rendering permainan seolah-olah tidak ada kartu grafis.</translation> <translation>Aktifkan GPU Null:\nUntuk tujuan debugging teknis, menonaktifkan rendering permainan seolah-olah tidak ada kartu grafis.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Aktifkan Pembuangan PM4:\nUntuk tujuan debugging teknis, menyimpan data instruksi GPU mentah ke folder saat emulator memprosesnya.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Jalur</translation> <translation>Jalur</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Waktu Bermain</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Abilita NULL GPU</translation> <translation>Abilita NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Abilita Dump PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Abilita GPU Null:\nPer scopi di debug tecnico, disabilita il rendering del gioco come se non ci fosse alcuna scheda grafica.</translation> <translation>Abilita GPU Null:\nPer scopi di debug tecnico, disabilita il rendering del gioco come se non ci fosse alcuna scheda grafica.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Abilita Pompaggio PM4:\nPer scopi di debug tecnico, salva i dati delle istruzioni GPU grezze in una cartella mentre l'emulatore li elabora.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Percorso</translation> <translation>Percorso</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Tempo di Gioco</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPUを有効にする</translation> <translation>NULL GPUを有効にする</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4ダンプを有効にする</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Null GPUを有効にする:\n技術的なデバッグの目的で</translation> <translation>Null GPUを有効にする:\n技術的なデバッグの目的で</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4ダンプを有効にする:\n技術的なデバッグの目的でGPU命令データをフォルダーに保存します</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation> <translation>Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Enable PM4 Dumping:\nFor the sake of technical debugging, saves raw GPU instruction data to a folder as the emulator processes it.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Path</translation> <translation>Path</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Play Time</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Įjungti tuščią GPU:\nTechninio derinimo tikslais išjungia žaidimo renderiavimą, tarsi nebūtų grafikos plokštės.</translation> <translation>Įjungti tuščią GPU:\nTechninio derinimo tikslais išjungia žaidimo renderiavimą, tarsi nebūtų grafikos plokštės.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Įjungti PM4 išmetimą:\nTechninio derinimo tikslais saugo žalius GPU nurodymų duomenis į aplanką, kai emuliatorius juos apdoroja.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Kelias</translation> <translation>Kelias</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Žaidimo laikas</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillrendering som om det ikke var noe grafikkort.</translation> <translation>Aktiver Null GPU:\nFor teknisk feilsøking deaktiverer spillrendering som om det ikke var noe grafikkort.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Aktiver PM4 dumping:\nFor teknisk feilsøking lagrer GPU-instruksjonsdata i en mappe mens emulatoren behandler dem.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Sti</translation> <translation>Sti</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Spilletid</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Null GPU inschakelen:\nVoor technische foutopsporing schakelt de game-rendering uit alsof er geen grafische kaart is.</translation> <translation>Null GPU inschakelen:\nVoor technische foutopsporing schakelt de game-rendering uit alsof er geen grafische kaart is.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4 dumpen inschakelen:\nVoor technische foutopsporing slaat het ruwe GPU-instructiegegevens op in een map terwijl de emulator ze verwerkt.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Pad</translation> <translation>Pad</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Speeltijd</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Wyłącz kartę graficzną</translation> <translation>Wyłącz kartę graficzną</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Włącz zgrywanie PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Włącz Null GPU:\nDla technicznego debugowania dezaktywuje renderowanie gry tak, jakby nie było karty graficznej.</translation> <translation>Włącz Null GPU:\nDla technicznego debugowania dezaktywuje renderowanie gry tak, jakby nie było karty graficznej.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Włącz zrzucanie PM4:\nDla technicznego debugowania zapisuje surowe dane instrukcji GPU w folderze, gdy emulator je przetwarza.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Ścieżka</translation> <translation>Ścieżka</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Czas gry</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Ativar GPU NULA</translation> <translation>Ativar GPU NULA</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Ativar Dumping de PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Ativar GPU NULA:\nDesativa a renderização do jogo para fins de depuração técnica, como se não houvesse nenhuma placa gráfica.</translation> <translation>Ativar GPU NULA:\nDesativa a renderização do jogo para fins de depuração técnica, como se não houvesse nenhuma placa gráfica.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Ativar Dumping de PM4:\nArmazena os dados de instrução bruta da GPU em uma pasta enquanto o emulador os processa, para fins de depuração técnica. Recomendado deixar desativado.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Diretório</translation> <translation>Diretório</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Horas Jogadas</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Activează GPU Null:\nÎn scopuri de depanare tehnică, dezactivează redarea jocului ca și cum nu ar exista o placă grafică.</translation> <translation>Activează GPU Null:\nÎn scopuri de depanare tehnică, dezactivează redarea jocului ca și cum nu ar exista o placă grafică.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Activează salvarea PM4:\nÎn scopuri de depanare tehnică, salvează datele brute ale instrucțiunilor GPU într-un folder pe măsură ce emulatorul le procesează.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Drum</translation> <translation>Drum</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Timp de Joacă</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Включить NULL GPU</translation> <translation>Включить NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Включить дамп PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.</translation> <translation>Включить NULL GPU:\nДля технической отладки отключает рендеринг игры так, как будто графической карты нет.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Включить дамп PM4:\nДля технической отладки сохраняет необработанные данные инструкций GPU в папку, пока эмулятор их обрабатывает.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Путь</translation> <translation>Путь</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Время Игры</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -417,7 +417,7 @@
<message> <message>
<location filename="../settings_dialog.ui" line="155"/> <location filename="../settings_dialog.ui" line="155"/>
<source>Username</source> <source>Username</source>
<translation>Nofka</translation> <translation>Përdoruesi</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.ui" line="178"/> <location filename="../settings_dialog.ui" line="178"/>
@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Aktivizo GPU- NULL</translation> <translation>Aktivizo GPU- NULL</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Aktivizo Zbrazjen PM4</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -517,7 +512,7 @@
<message> <message>
<location filename="../settings_dialog.ui" line="313"/> <location filename="../settings_dialog.ui" line="313"/>
<source>Update Channel</source> <source>Update Channel</source>
<translation>Kanali i Përditësimit</translation> <translation>Kanali i përditësimit</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.ui" line="322"/> <location filename="../settings_dialog.ui" line="322"/>
@ -527,7 +522,7 @@
<message> <message>
<location filename="../settings_dialog.ui" line="354"/> <location filename="../settings_dialog.ui" line="354"/>
<source>GUI Settings</source> <source>GUI Settings</source>
<translation>Parametrat e GUI</translation> <translation>Cilësimet e GUI</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.ui" line="375"/> <location filename="../settings_dialog.ui" line="375"/>
@ -537,7 +532,7 @@
<message> <message>
<location filename="../settings_dialog.ui" line="394"/> <location filename="../settings_dialog.ui" line="394"/>
<source>Volume</source> <source>Volume</source>
<translation>Volumi</translation> <translation>Vëllimi i zërit</translation>
</message> </message>
</context> </context>
<context> <context>
@ -976,12 +971,12 @@
<message> <message>
<location filename="../settings_dialog.cpp" line="72"/> <location filename="../settings_dialog.cpp" line="72"/>
<source>Point your mouse at an option to display its description.</source> <source>Point your mouse at an option to display its description.</source>
<translation>Hidhni mouse-in mbi një opsion për shfaqur përshkrimin e tij.</translation> <translation>Vendos miun mbi një rregullim për shfaqur përshkrimin e tij.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="289"/> <location filename="../settings_dialog.cpp" line="289"/>
<source>consoleLanguageGroupBox</source> <source>consoleLanguageGroupBox</source>
<translation>Gjuha e konsolës:\nPërcakton gjuhën përdor loja PS4.\nRrekomandohet vendosni këtë një gjuhë loja mbështet, e cila do ndryshojë sipas rajonit.</translation> <translation>Gjuha e konsolës:\nPërcakton gjuhën përdor loja PS4.\nKëshillohet caktosh një gjuhë loja mbështet, e cila do ndryshojë sipas rajonit.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="291"/> <location filename="../settings_dialog.cpp" line="291"/>
@ -991,17 +986,17 @@
<message> <message>
<location filename="../settings_dialog.cpp" line="293"/> <location filename="../settings_dialog.cpp" line="293"/>
<source>fullscreenCheckBox</source> <source>fullscreenCheckBox</source>
<translation>Aktivizo ekranin e plotë:\nAutomatikisht vendos dritaren e lojës modalitetin e ekranit plotë.\nKjo mund aktivizohet duke shtypur çelësin F11.</translation> <translation>Aktivizo ekranin e plotë:\nVendos automatikisht dritaren e lojës mënyrën e ekranit plotë.\nKjo mund aktivizohet duke shtypur tastin F11.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="295"/> <location filename="../settings_dialog.cpp" line="295"/>
<source>showSplashCheckBox</source> <source>showSplashCheckBox</source>
<translation>Shfaq ekranin e ngarkesës:\nShfaq ekranin e ngarkesës lojës (një imazh special) gjatë fillimit lojës.</translation> <translation>Shfaq ekranin e ngarkesës:\nShfaq ekranin e ngarkesës lojës (një pamje e veçantë) gjatë fillimit lojës.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="297"/> <location filename="../settings_dialog.cpp" line="297"/>
<source>ps4proCheckBox</source> <source>ps4proCheckBox</source>
<translation>Është PS4 Pro:\nBën emulatori veprojë si një PS4 PRO, i cili mund aktivizojë karakteristika speciale lojrat e mbështesin atë.</translation> <translation>Është PS4 Pro:\nBën emulatori veprojë si një PS4 PRO, gjë mund aktivizojë veçori veçanta lojrat e mbështesin.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="299"/> <location filename="../settings_dialog.cpp" line="299"/>
@ -1011,72 +1006,67 @@
<message> <message>
<location filename="../settings_dialog.cpp" line="301"/> <location filename="../settings_dialog.cpp" line="301"/>
<source>logTypeGroupBox</source> <source>logTypeGroupBox</source>
<translation>Tipi i logut:\nPërcakton nëse sinkronizoni daljen e dritares logut për performancën. Mund ketë efekte këqija emulim.</translation> <translation>Lloji i ditarit:\nPërcakton nëse sinkronizohet dalja e dritares ditarit për performancë. Mund ketë efekte këqija emulim.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="303"/> <location filename="../settings_dialog.cpp" line="303"/>
<source>logFilter</source> <source>logFilter</source>
<translation>Filtri i logut: Filtron logun për printuar vetëm informacione specifike. Shembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - këtë rend, një nivel specifik hesht gjitha nivelet përpara listë dhe regjistron çdo nivel pas saj.</translation> <translation>Filtri i ditarit: Filtron ditarin për shfaqur vetëm informacione specifike. Shembuj: "Core:Trace" "Lib.Pad:Debug Common.Filesystem:Error" "*:Critical" Nivelet: Trace, Debug, Info, Warning, Error, Critical - këtë rend, një nivel specifik hesht gjitha nivelet përpara listë dhe regjistron çdo nivel pas atij.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="305"/> <location filename="../settings_dialog.cpp" line="305"/>
<source>updaterGroupBox</source> <source>updaterGroupBox</source>
<translation>Aktualizimi:\nRelease: Versionet zyrtare lëshuara çdo muaj mund jenë shumë vjetra, por janë besueshme dhe testuara.\nNightly: Versionet e zhvillimit kanë gjitha veçoritë dhe rregullimet fundit, por mund përmbajnë gabime dhe janë pak qëndrueshme.</translation> <translation>Aktualizimi:\nRelease: Versionet zyrtare lëshuara çdo muaj mund jenë shumë vjetra, por janë besueshme dhe provuara.\nNightly: Versionet e zhvillimit kanë gjitha veçoritë dhe rregullimet fundit, por mund përmbajnë gabime dhe janë pak qëndrueshme.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="306"/> <location filename="../settings_dialog.cpp" line="306"/>
<source>GUIgroupBox</source> <source>GUIgroupBox</source>
<translation>Lojë muzikë titulli:\nNëse një lojë e mbështet, aktivizoja luajtjen e muzikës speciale kur zgjidhni lojën GUI.</translation> <translation>Luaj muzikën e titullit:\nNëse një lojë e mbështet, aktivizohet luajtja e muzikës veçantë kur zgjidhësh lojën GUI.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="312"/> <location filename="../settings_dialog.cpp" line="312"/>
<source>graphicsAdapterGroupBox</source> <source>graphicsAdapterGroupBox</source>
<translation>Dispositivi grafik:\nNë sistemet me GPU shumëfishta, zgjidhni GPU- do përdorë emulatori nga lista e rënies,\nor zgjidhni "Auto Select" për ta përcaktuar automatikisht.</translation> <translation>Pajisja grafike:\nNë sistemet me GPU shumëfishta, zgjidh GPU- do përdorë emulatori nga lista rënëse,\nose zgjidh "Auto Select" për ta përcaktuar automatikisht.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="314"/> <location filename="../settings_dialog.cpp" line="314"/>
<source>resolutionLayout</source> <source>resolutionLayout</source>
<translation>Gjerësia/ Lartësia:\nPërcakton madhësinë e dritares emulatorit nisje, e cila mund rregullohet gjatë lojës.\nKjo është ndryshe nga rezolucioni lojë.</translation> <translation>Gjerësia/Lartësia:\nPërcakton madhësinë e dritares emulatorit nisje, e cila mund rregullohet gjatë lojës.\nKjo është ndryshe nga rezolucioni lojë.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="318"/> <location filename="../settings_dialog.cpp" line="318"/>
<source>heightDivider</source> <source>heightDivider</source>
<translation>Pjesëtari Vblank:\nShpejtësia e kuadrit me cilën refreshohet emulatori është shumëzuar me këtë numër. Ndryshimi i këtij mund ketë efekte këqija, si rritja e shpejtësisë lojës ose shkatërrimi i funksionalitetit kritik lojës nuk e pret këtë ndryshojë!</translation> <translation>Ndarësi Vblank:\nFrekuenca pamore me cilën rifreskohet emulatori shumëzohet me këtë numër. Ndryshimi i këtij mund ketë efekte këqija, si rritja e shpejtësisë lojës ose prishja e punimit thelbësor lojës nuk e pret këtë ndryshim!</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="320"/> <location filename="../settings_dialog.cpp" line="320"/>
<source>dumpShadersCheckBox</source> <source>dumpShadersCheckBox</source>
<translation>Aktivizo dump-in e shaders:\nPër qëllime debugimit teknik, ruan shaders e lojës një folder ndërsa ato renditen.</translation> <translation>Aktivizo zbrazjen e shaders-ave:\nPër qëllime korrigjimit teknik, ruan shaders-at e lojës një dosje ndërsa ato pasqyrohen.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="322"/> <location filename="../settings_dialog.cpp" line="322"/>
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Aktivizo GPU Null:\nPër qëllime debugimit teknik, deaktivizon renditjen e lojës sikur nuk do kishte një kartë grafike.</translation> <translation>Aktivizo GPU- Null:\nPër qëllime korrigjimit teknik, çaktivizon pasqyrimin e lojës sikur nuk ka një kartë grafike.</translation>
</message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Aktivizo dump-in e PM4:\nPër qëllime debugimit teknik, ruan dhënat e instruksioneve GPU- një folder ndërsa emulatori i përpunon ato.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
<translation>Aktivizo dump-in e debugimit:\nRuani simbolet e importit dhe eksportit dhe informacionin e titullit skedarit për aplikacionin aktual PS4 po punon një katalog.</translation> <translation>Aktivizo zbrazjen për korrigjim:\nRuan simbolet e importit dhe eksportit dhe informacionin e kreut skedarit për aplikacionin PS4 po ekzekutohet një dosje.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="331"/> <location filename="../settings_dialog.cpp" line="331"/>
<source>vkValidationCheckBox</source> <source>vkValidationCheckBox</source>
<translation>Aktivizo stratet e validimit Vulkan:\nAktivizon një sistem validon gjendjen e renderizuesit Vulkan dhe regjistron informacionin lidhje me gjendjen e tij brendshme. Kjo do ulet performancën dhe ndoshta do ndryshojë sjelljen e emulimit.</translation> <translation>Aktivizo shtresat e vlefshmërisë Vulkan:\nAktivizon një sistem vërteton gjendjen e pasqyruesit Vulkan dhe regjistron informacionin lidhje me gjendjen e tij brendshme. Kjo do ul performancën dhe ndoshta do ndryshojë sjelljen e emulimit.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="333"/> <location filename="../settings_dialog.cpp" line="333"/>
<source>vkSyncValidationCheckBox</source> <source>vkSyncValidationCheckBox</source>
<translation>Aktivizo validimin e sinkronizimit Vulkan:\nAktivizon një sistem validon kohën e detyrave renderizimit Vulkan. Kjo do ulet performancën dhe ndoshta do ndryshojë sjelljen e emulimit.</translation> <translation>Aktivizo vërtetimin e sinkronizimit Vulkan:\nAktivizon një sistem vërteton kohën e detyrave pasqyrimit Vulkan. Kjo do ul performancën dhe ndoshta do ndryshojë sjelljen e emulimit.</translation>
</message> </message>
<message> <message>
<location filename="../settings_dialog.cpp" line="335"/> <location filename="../settings_dialog.cpp" line="335"/>
<source>rdocCheckBox</source> <source>rdocCheckBox</source>
<translation>Aktivizo debugimin RenderDoc:\nNëse aktivizohet, emulatori do ofrojë pajtueshmëri me Renderdoc për lejuar kapjen dhe analizën e kornizës aktuale renderizuar.</translation> <translation>Aktivizo korrigjimin RenderDoc:\nNëse aktivizohet, emulatori do ofrojë pajtueshmëri me Renderdoc për lejuar kapjen dhe analizën e pamjes pasqyruar moment.</translation>
</message> </message>
</context> </context>
<context> <context>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Shtegu</translation> <translation>Shtegu</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Kohë Lojë</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>
@ -1172,7 +1167,7 @@
<message> <message>
<location filename="../check_update.cpp" line="187"/> <location filename="../check_update.cpp" line="187"/>
<source>Update Channel</source> <source>Update Channel</source>
<translation>Kanali i Përditësimit</translation> <translation>Kanali i përditësimit</translation>
</message> </message>
<message> <message>
<location filename="../check_update.cpp" line="177"/> <location filename="../check_update.cpp" line="177"/>
@ -1250,4 +1245,4 @@
<translation>Krijimi i skedarit skript përditësimit dështoi</translation> <translation>Krijimi i skedarit skript përditësimit dështoi</translation>
</message> </message>
</context> </context>
</TS> </TS>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>NULL GPU'yu Etkinleştir</translation> <translation>NULL GPU'yu Etkinleştir</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>PM4 Kaydını Etkinleştir</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Null GPU'yu Etkinleştir:\nTeknik hata ayıklama amacıyla, oyunun render edilmesini grafik kartı yokmuş gibi devre dışı bırakır.</translation> <translation>Null GPU'yu Etkinleştir:\nTeknik hata ayıklama amacıyla, oyunun render edilmesini grafik kartı yokmuş gibi devre dışı bırakır.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4 Dışa Aktarmayı Etkinleştir:\nTeknik hata ayıklama amacıyla, emülatör bunları işlerken GPU komut verilerini bir klasöre kaydeder.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Yol</translation> <translation>Yol</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Oynama Süresi</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>Bật GPU Null:\nĐể mục đích gỡ lỗi kỹ thuật, hiệu hóa việc kết xuất trò chơi như thể không card đ họa.</translation> <translation>Bật GPU Null:\nĐể mục đích gỡ lỗi kỹ thuật, hiệu hóa việc kết xuất trò chơi như thể không card đ họa.</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>Bật xuất PM4:\nĐể mục đích gỡ lỗi kỹ thuật, lưu dữ liệu lệnh GPU vào một thư mục khi trình giả lập xử chúng.</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation>Đưng dẫn</translation> <translation>Đưng dẫn</translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation>Thời gian chơi</translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation> NULL GPU</translation> <translation> NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation> PM4 </translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation> GPU:\n为了技术调试仿</translation> <translation> GPU:\n为了技术调试仿</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation> PM4 :\n为了技术调试 GPU </translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -474,11 +474,6 @@
<source>Enable NULL GPU</source> <source>Enable NULL GPU</source>
<translation>Enable NULL GPU</translation> <translation>Enable NULL GPU</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="476"/>
<source>Enable PM4 Dumping</source>
<translation>Enable PM4 Dumping</translation>
</message>
<message> <message>
<location filename="../settings_dialog.ui" line="517"/> <location filename="../settings_dialog.ui" line="517"/>
<source>Debug</source> <source>Debug</source>
@ -1053,11 +1048,6 @@
<source>nullGpuCheckBox</source> <source>nullGpuCheckBox</source>
<translation>GPU:\n為了技術調試彿</translation> <translation>GPU:\n為了技術調試彿</translation>
</message> </message>
<message>
<location filename="../settings_dialog.cpp" line="323"/>
<source>dumpPM4CheckBox</source>
<translation>PM4轉儲:\n為了技術調試GPU指令數據在模擬器處理時保存到文件夾中</translation>
</message>
<message> <message>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>
@ -1121,6 +1111,11 @@
<source>Path</source> <source>Path</source>
<translation></translation> <translation></translation>
</message> </message>
<message>
<location filename="../game_list_frame.cpp" line="38"/>
<source>Play Time</source>
<translation></translation>
</message>
</context> </context>
<context> <context>
<name>CheckUpdate</name> <name>CheckUpdate</name>

View File

@ -145,6 +145,7 @@ void WindowSDL::onKeyPress(const SDL_Event* event) {
Input::Axis axis = Input::Axis::AxisMax; Input::Axis axis = Input::Axis::AxisMax;
int axisvalue = 0; int axisvalue = 0;
int ax = 0; int ax = 0;
std::string backButtonBehavior = Config::getBackButtonBehavior();
switch (event->key.key) { switch (event->key.key) {
case SDLK_UP: case SDLK_UP:
button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP; button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_UP;
@ -278,7 +279,15 @@ void WindowSDL::onKeyPress(const SDL_Event* event) {
ax = Input::GetAxis(0, 0x80, axisvalue); ax = Input::GetAxis(0, 0x80, axisvalue);
break; break;
case SDLK_SPACE: case SDLK_SPACE:
button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD; if (backButtonBehavior != "none") {
float x = backButtonBehavior == "left" ? 0.25f
: (backButtonBehavior == "right" ? 0.75f : 0.5f);
// trigger a touchpad event so that the touchpad emulation for back button works
controller->SetTouchpadState(0, true, x, 0.5f);
button = OrbisPadButtonDataOffset::ORBIS_PAD_BUTTON_TOUCH_PAD;
} else {
button = 0;
}
break; break;
case SDLK_F11: case SDLK_F11:
if (event->type == SDL_EVENT_KEY_DOWN) { if (event->type == SDL_EVENT_KEY_DOWN) {
@ -304,9 +313,6 @@ void WindowSDL::onKeyPress(const SDL_Event* event) {
if (axis != Input::Axis::AxisMax) { if (axis != Input::Axis::AxisMax) {
controller->Axis(0, axis, ax); controller->Axis(0, axis, ax);
} }
if (SDL_GetCursor() != NULL) {
SDL_HideCursor();
}
} }
void WindowSDL::onGamepadEvent(const SDL_Event* event) { void WindowSDL::onGamepadEvent(const SDL_Event* event) {
@ -330,10 +336,20 @@ void WindowSDL::onGamepadEvent(const SDL_Event* event) {
case SDL_EVENT_GAMEPAD_BUTTON_UP: case SDL_EVENT_GAMEPAD_BUTTON_UP:
button = sdlGamepadToOrbisButton(event->gbutton.button); button = sdlGamepadToOrbisButton(event->gbutton.button);
if (button != 0) { if (button != 0) {
controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN); if (event->gbutton.button == SDL_GAMEPAD_BUTTON_BACK) {
} std::string backButtonBehavior = Config::getBackButtonBehavior();
if (SDL_GetCursor() != NULL) { if (backButtonBehavior != "none") {
SDL_HideCursor(); float x = backButtonBehavior == "left"
? 0.25f
: (backButtonBehavior == "right" ? 0.75f : 0.5f);
// trigger a touchpad event so that the touchpad emulation for back button works
controller->SetTouchpadState(0, true, x, 0.5f);
controller->CheckButton(0, button,
event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN);
}
} else {
controller->CheckButton(0, button, event->type == SDL_EVENT_GAMEPAD_BUTTON_DOWN);
}
} }
break; break;
case SDL_EVENT_GAMEPAD_AXIS_MOTION: case SDL_EVENT_GAMEPAD_AXIS_MOTION:

View File

@ -27,8 +27,10 @@ static constexpr spv::ExecutionMode GetInputPrimitiveType(AmdGpu::PrimitiveType
case AmdGpu::PrimitiveType::TriangleList: case AmdGpu::PrimitiveType::TriangleList:
case AmdGpu::PrimitiveType::TriangleStrip: case AmdGpu::PrimitiveType::TriangleStrip:
return spv::ExecutionMode::Triangles; return spv::ExecutionMode::Triangles;
case AmdGpu::PrimitiveType::AdjTriangleList:
return spv::ExecutionMode::InputTrianglesAdjacency;
default: default:
UNREACHABLE(); UNREACHABLE_MSG("Unknown input primitive type {}", u32(type));
} }
} }
@ -41,7 +43,7 @@ static constexpr spv::ExecutionMode GetOutputPrimitiveType(AmdGpu::GsOutputPrimi
case AmdGpu::GsOutputPrimitiveType::TriangleStrip: case AmdGpu::GsOutputPrimitiveType::TriangleStrip:
return spv::ExecutionMode::OutputTriangleStrip; return spv::ExecutionMode::OutputTriangleStrip;
default: default:
UNREACHABLE(); UNREACHABLE_MSG("Unknown output primitive type {}", u32(type));
} }
} }
@ -68,6 +70,8 @@ ArgType Arg(EmitContext& ctx, const IR::Value& arg) {
return arg.ScalarReg(); return arg.ScalarReg();
} else if constexpr (std::is_same_v<ArgType, IR::VectorReg>) { } else if constexpr (std::is_same_v<ArgType, IR::VectorReg>) {
return arg.VectorReg(); return arg.VectorReg();
} else if constexpr (std::is_same_v<ArgType, const char*>) {
return arg.StringLiteral();
} }
} }
@ -202,10 +206,7 @@ Id DefineMain(EmitContext& ctx, const IR::Program& program) {
return main; return main;
} }
void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { void SetupCapabilities(const Info& info, EmitContext& ctx) {
const auto& info = program.info;
const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size());
spv::ExecutionModel execution_model{};
ctx.AddCapability(spv::Capability::Image1D); ctx.AddCapability(spv::Capability::Image1D);
ctx.AddCapability(spv::Capability::Sampled1D); ctx.AddCapability(spv::Capability::Sampled1D);
ctx.AddCapability(spv::Capability::ImageQuery); ctx.AddCapability(spv::Capability::ImageQuery);
@ -243,6 +244,19 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
if (info.uses_group_ballot) { if (info.uses_group_ballot) {
ctx.AddCapability(spv::Capability::GroupNonUniformBallot); ctx.AddCapability(spv::Capability::GroupNonUniformBallot);
} }
if (info.stage == Stage::Export || info.stage == Stage::Vertex) {
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
ctx.AddCapability(spv::Capability::DrawParameters);
}
if (info.stage == Stage::Geometry) {
ctx.AddCapability(spv::Capability::Geometry);
}
}
void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
const auto& info = program.info;
const std::span interfaces(ctx.interfaces.data(), ctx.interfaces.size());
spv::ExecutionModel execution_model{};
switch (program.info.stage) { switch (program.info.stage) {
case Stage::Compute: { case Stage::Compute: {
const std::array<u32, 3> workgroup_size{ctx.runtime_info.cs_info.workgroup_size}; const std::array<u32, 3> workgroup_size{ctx.runtime_info.cs_info.workgroup_size};
@ -286,6 +300,24 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) {
ctx.AddEntryPoint(execution_model, main, "main", interfaces); ctx.AddEntryPoint(execution_model, main, "main", interfaces);
} }
void SetupFloatMode(EmitContext& ctx, const Profile& profile, const RuntimeInfo& runtime_info,
Id main_func) {
ctx.AddExtension("SPV_KHR_float_controls");
const auto fp_denorm_mode = runtime_info.fp_denorm_mode32;
if (fp_denorm_mode == AmdGpu::FpDenormMode::InOutFlush) {
if (profile.support_fp32_denorm_flush) {
ctx.AddCapability(spv::Capability::DenormFlushToZero);
ctx.AddExecutionMode(main_func, spv::ExecutionMode::DenormFlushToZero, 32U);
}
} else {
LOG_WARNING(Render_Vulkan, "Unknown FP denorm mode {}", u32(fp_denorm_mode));
}
const auto fp_round_mode = runtime_info.fp_round_mode32;
if (fp_round_mode != AmdGpu::FpRoundMode::NearestEven) {
LOG_WARNING(Render_Vulkan, "Unknown FP rounding mode {}", u32(fp_round_mode));
}
}
void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) { void PatchPhiNodes(const IR::Program& program, EmitContext& ctx) {
auto inst{program.blocks.front()->begin()}; auto inst{program.blocks.front()->begin()};
size_t block_index{0}; size_t block_index{0};
@ -310,18 +342,8 @@ std::vector<u32> EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in
EmitContext ctx{profile, runtime_info, program.info, binding}; EmitContext ctx{profile, runtime_info, program.info, binding};
const Id main{DefineMain(ctx, program)}; const Id main{DefineMain(ctx, program)};
DefineEntryPoint(program, ctx, main); DefineEntryPoint(program, ctx, main);
switch (program.info.stage) { SetupCapabilities(program.info, ctx);
case Stage::Export: SetupFloatMode(ctx, profile, runtime_info, main);
case Stage::Vertex:
ctx.AddExtension("SPV_KHR_shader_draw_parameters");
ctx.AddCapability(spv::Capability::DrawParameters);
break;
case Stage::Geometry:
ctx.AddCapability(spv::Capability::Geometry);
break;
default:
break;
}
PatchPhiNodes(program, ctx); PatchPhiNodes(program, ctx);
binding.user_data += program.info.ud_mask.NumRegs(); binding.user_data += program.info.ud_mask.NumRegs();
return ctx.Assemble(); return ctx.Assemble();

View File

@ -59,19 +59,22 @@ struct ImageOperands {
} }
} }
void AddDerivatives(EmitContext& ctx, Id derivatives) { void AddDerivatives(EmitContext& ctx, Id derivatives_dx, Id derivatives_dy) {
if (!Sirit::ValidId(derivatives)) { if (!Sirit::ValidId(derivatives_dx) || !Sirit::ValidId(derivatives_dy)) {
return; return;
} }
const Id dx{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 0, 1)}; Add(spv::ImageOperandsMask::Grad, derivatives_dx, derivatives_dy);
const Id dy{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 2, 3)};
Add(spv::ImageOperandsMask::Grad, dx, dy);
} }
spv::ImageOperandsMask mask{}; spv::ImageOperandsMask mask{};
boost::container::static_vector<Id, 4> operands; boost::container::static_vector<Id, 4> operands;
}; };
Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
Id address3, Id address4) {
UNREACHABLE_MSG("Unreachable instruction");
}
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias,
const IR::Value& offset) { const IR::Value& offset) {
const auto& texture = ctx.images[handle & 0xFFFF]; const auto& texture = ctx.images[handle & 0xFFFF];
@ -114,7 +117,9 @@ Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle,
operands.AddOffset(ctx, offset); operands.AddOffset(ctx, offset);
const Id sample = ctx.OpImageSampleDrefImplicitLod(result_type, sampled_image, coords, dref, const Id sample = ctx.OpImageSampleDrefImplicitLod(result_type, sampled_image, coords, dref,
operands.mask, operands.operands); operands.mask, operands.operands);
return texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; const Id sample_typed = texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample;
return ctx.OpCompositeConstruct(ctx.F32[4], sample_typed, ctx.f32_zero_value,
ctx.f32_zero_value, ctx.f32_zero_value);
} }
Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref, Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id dref,
@ -129,7 +134,9 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle,
operands.Add(spv::ImageOperandsMask::Lod, lod); operands.Add(spv::ImageOperandsMask::Lod, lod);
const Id sample = ctx.OpImageSampleDrefExplicitLod(result_type, sampled_image, coords, dref, const Id sample = ctx.OpImageSampleDrefExplicitLod(result_type, sampled_image, coords, dref,
operands.mask, operands.operands); operands.mask, operands.operands);
return texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample; const Id sample_typed = texture.is_integer ? ctx.OpBitcast(ctx.F32[1], sample) : sample;
return ctx.OpCompositeConstruct(ctx.F32[4], sample_typed, ctx.f32_zero_value,
ctx.f32_zero_value, ctx.f32_zero_value);
} }
Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id EmitImageGather(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords,
@ -212,15 +219,15 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords) {
return ctx.OpImageQueryLod(ctx.F32[2], sampled_image, coords); return ctx.OpImageQueryLod(ctx.F32[2], sampled_image, coords);
} }
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx,
const IR::Value& offset, Id lod_clamp) { Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp) {
const auto& texture = ctx.images[handle & 0xFFFF]; const auto& texture = ctx.images[handle & 0xFFFF];
const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id image = ctx.OpLoad(texture.image_type, texture.id);
const Id result_type = texture.data_types->Get(4); const Id result_type = texture.data_types->Get(4);
const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]);
const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler);
ImageOperands operands; ImageOperands operands;
operands.AddDerivatives(ctx, derivatives); operands.AddDerivatives(ctx, derivatives_dx, derivatives_dy);
operands.AddOffset(ctx, offset); operands.AddOffset(ctx, offset);
const Id sample = ctx.OpImageSampleExplicitLod(result_type, sampled_image, coords, const Id sample = ctx.OpImageSampleExplicitLod(result_type, sampled_image, coords,
operands.mask, operands.operands); operands.mask, operands.operands);

View File

@ -48,6 +48,7 @@ void EmitPrologue(EmitContext& ctx);
void EmitEpilogue(EmitContext& ctx); void EmitEpilogue(EmitContext& ctx);
void EmitDiscard(EmitContext& ctx); void EmitDiscard(EmitContext& ctx);
void EmitDiscardCond(EmitContext& ctx, Id condition); void EmitDiscardCond(EmitContext& ctx, Id condition);
void EmitDebugPrint(EmitContext& ctx, IR::Inst* inst, Id arg0, Id arg1, Id arg2, Id arg3, Id arg4);
void EmitBarrier(EmitContext& ctx); void EmitBarrier(EmitContext& ctx);
void EmitWorkgroupMemoryBarrier(EmitContext& ctx); void EmitWorkgroupMemoryBarrier(EmitContext& ctx);
void EmitDeviceMemoryBarrier(EmitContext& ctx); void EmitDeviceMemoryBarrier(EmitContext& ctx);
@ -367,6 +368,8 @@ Id EmitConvertF64U64(EmitContext& ctx, Id value);
Id EmitConvertU16U32(EmitContext& ctx, Id value); Id EmitConvertU16U32(EmitContext& ctx, Id value);
Id EmitConvertU32U16(EmitContext& ctx, Id value); Id EmitConvertU32U16(EmitContext& ctx, Id value);
Id EmitImageSampleRaw(EmitContext& ctx, IR::Inst* inst, u32 handle, Id address1, Id address2,
Id address3, Id address4);
Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias, Id EmitImageSampleImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id bias,
const IR::Value& offset); const IR::Value& offset);
Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod, Id EmitImageSampleExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id lod,
@ -383,8 +386,8 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, const
Id lod, Id ms); Id lod, Id ms);
Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips); Id EmitImageQueryDimensions(EmitContext& ctx, IR::Inst* inst, u32 handle, Id lod, bool skip_mips);
Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords); Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords);
Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx,
const IR::Value& offset, Id lod_clamp); Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp);
Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords); Id EmitImageRead(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id coords);
void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id color); void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id color);

View File

@ -3,6 +3,7 @@
#include "shader_recompiler/backend/spirv/emit_spirv_instructions.h" #include "shader_recompiler/backend/spirv/emit_spirv_instructions.h"
#include "shader_recompiler/backend/spirv/spirv_emit_context.h" #include "shader_recompiler/backend/spirv/spirv_emit_context.h"
#include "shader_recompiler/ir/debug_print.h"
namespace Shader::Backend::SPIRV { namespace Shader::Backend::SPIRV {
@ -57,4 +58,11 @@ void EmitEndPrimitive(EmitContext& ctx, const IR::Value& stream) {
throw NotImplementedException("Geometry streams"); throw NotImplementedException("Geometry streams");
} }
void EmitDebugPrint(EmitContext& ctx, IR::Inst* inst, Id fmt, Id arg0, Id arg1, Id arg2, Id arg3) {
IR::DebugPrintFlags flags = inst->Flags<IR::DebugPrintFlags>();
std::array<Id, IR::DEBUGPRINT_NUM_FORMAT_ARGS> fmt_args = {arg0, arg1, arg2, arg3};
auto fmt_args_span = std::span<Id>(fmt_args.begin(), fmt_args.begin() + flags.num_args);
ctx.OpDebugPrintf(fmt, fmt_args_span);
}
} // namespace Shader::Backend::SPIRV } // namespace Shader::Backend::SPIRV

View File

@ -34,14 +34,17 @@ std::string_view StageName(Stage stage) {
throw InvalidArgument("Invalid stage {}", u32(stage)); throw InvalidArgument("Invalid stage {}", u32(stage));
} }
static constexpr u32 NumVertices(AmdGpu::GsOutputPrimitiveType type) { static constexpr u32 NumVertices(AmdGpu::PrimitiveType type) {
switch (type) { switch (type) {
case AmdGpu::GsOutputPrimitiveType::PointList: case AmdGpu::PrimitiveType::PointList:
return 1u; return 1u;
case AmdGpu::GsOutputPrimitiveType::LineStrip: case AmdGpu::PrimitiveType::LineList:
return 2u; return 2u;
case AmdGpu::GsOutputPrimitiveType::TriangleStrip: case AmdGpu::PrimitiveType::TriangleList:
case AmdGpu::PrimitiveType::TriangleStrip:
return 3u; return 3u;
case AmdGpu::PrimitiveType::AdjTriangleList:
return 6u;
default: default:
UNREACHABLE(); UNREACHABLE();
} }
@ -88,6 +91,8 @@ Id EmitContext::Def(const IR::Value& value) {
return ConstF32(value.F32()); return ConstF32(value.F32());
case IR::Type::F64: case IR::Type::F64:
return Constant(F64[1], value.F64()); return Constant(F64[1], value.F64());
case IR::Type::StringLiteral:
return String(value.StringLiteral());
default: default:
throw NotImplementedException("Immediate type {}", value.Type()); throw NotImplementedException("Immediate type {}", value.Type());
} }
@ -279,7 +284,8 @@ void EmitContext::DefineInputs() {
frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input); frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input);
frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output); frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output);
front_facing = DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input); front_facing = DefineVariable(U1[1], spv::BuiltIn::FrontFacing, spv::StorageClass::Input);
for (const auto& input : runtime_info.fs_info.inputs) { 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; const u32 semantic = input.param_index;
ASSERT(semantic < IR::NumParams); ASSERT(semantic < IR::NumParams);
if (input.is_default && !input.is_flat) { if (input.is_default && !input.is_flat) {
@ -321,16 +327,14 @@ void EmitContext::DefineInputs() {
MemberDecorate(gl_per_vertex, 2, spv::Decoration::BuiltIn, MemberDecorate(gl_per_vertex, 2, spv::Decoration::BuiltIn,
static_cast<std::uint32_t>(spv::BuiltIn::ClipDistance)); static_cast<std::uint32_t>(spv::BuiltIn::ClipDistance));
Decorate(gl_per_vertex, spv::Decoration::Block); Decorate(gl_per_vertex, spv::Decoration::Block);
const auto vertices_in = const auto num_verts_in = NumVertices(runtime_info.gs_info.in_primitive);
TypeArray(gl_per_vertex, ConstU32(NumVertices(runtime_info.gs_info.out_primitive[0]))); const auto vertices_in = TypeArray(gl_per_vertex, ConstU32(num_verts_in));
gl_in = Name(DefineVar(vertices_in, spv::StorageClass::Input), "gl_in"); gl_in = Name(DefineVar(vertices_in, spv::StorageClass::Input), "gl_in");
interfaces.push_back(gl_in); interfaces.push_back(gl_in);
const auto num_params = runtime_info.gs_info.in_vertex_data_size / 4 - 1u; const auto num_params = runtime_info.gs_info.in_vertex_data_size / 4 - 1u;
for (int param_id = 0; param_id < num_params; ++param_id) { for (int param_id = 0; param_id < num_params; ++param_id) {
const IR::Attribute param{IR::Attribute::Param0 + param_id}; const Id type{TypeArray(F32[4], ConstU32(num_verts_in))};
const Id type{
TypeArray(F32[4], ConstU32(NumVertices(runtime_info.gs_info.out_primitive[0])))};
const Id id{DefineInput(type, param_id)}; const Id id{DefineInput(type, param_id)};
Name(id, fmt::format("in_attr{}", param_id)); Name(id, fmt::format("in_attr{}", param_id));
input_params[param_id] = {id, input_f32, F32[1], 4}; input_params[param_id] = {id, input_f32, F32[1], 4};
@ -390,8 +394,7 @@ void EmitContext::DefineOutputs() {
case Stage::Geometry: { case Stage::Geometry: {
output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output); output_position = DefineVariable(F32[4], spv::BuiltIn::Position, spv::StorageClass::Output);
for (u32 attr_id = 0; attr_id < runtime_info.gs_info.copy_data.num_attrs; attr_id++) { for (u32 attr_id = 0; attr_id < info.gs_copy_data.num_attrs; attr_id++) {
const IR::Attribute param{IR::Attribute::Param0 + attr_id};
const Id id{DefineOutput(F32[4], attr_id)}; const Id id{DefineOutput(F32[4], attr_id)};
Name(id, fmt::format("out_attr{}", attr_id)); Name(id, fmt::format("out_attr{}", attr_id));
output_params[attr_id] = {id, output_f32, F32[1], 4u}; output_params[attr_id] = {id, output_f32, F32[1], 4u};

View File

@ -7,7 +7,7 @@
namespace Shader { namespace Shader {
CopyShaderData ParseCopyShader(const std::span<const u32>& code) { CopyShaderData ParseCopyShader(std::span<const u32> code) {
Gcn::GcnCodeSlice code_slice{code.data(), code.data() + code.size()}; Gcn::GcnCodeSlice code_slice{code.data(), code.data() + code.size()};
Gcn::GcnDecodeContext decoder; Gcn::GcnDecodeContext decoder;
@ -15,18 +15,18 @@ CopyShaderData ParseCopyShader(const std::span<const u32>& code) {
ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm"); ASSERT_MSG(code[0] == token_mov_vcchi, "First instruction is not s_mov_b32 vcc_hi, #imm");
std::array<s32, 32> offsets{}; std::array<s32, 32> offsets{};
std::fill(offsets.begin(), offsets.end(), -1); offsets.fill(-1);
std::array<s32, 256> sources{};
sources.fill(-1);
CopyShaderData data{}; CopyShaderData data{};
Gcn::OperandField sgpr{};
auto last_attr{IR::Attribute::Position0}; auto last_attr{IR::Attribute::Position0};
s32 soffset{0};
while (!code_slice.atEnd()) { while (!code_slice.atEnd()) {
auto inst = decoder.decodeInstruction(code_slice); auto inst = decoder.decodeInstruction(code_slice);
switch (inst.opcode) { switch (inst.opcode) {
case Gcn::Opcode::S_MOVK_I32: { case Gcn::Opcode::S_MOVK_I32: {
sgpr = inst.dst[0].field; sources[inst.dst[0].code] = inst.control.sopk.simm;
soffset = inst.control.sopk.simm;
break; break;
} }
case Gcn::Opcode::EXP: { case Gcn::Opcode::EXP: {
@ -46,8 +46,9 @@ CopyShaderData ParseCopyShader(const std::span<const u32>& code) {
case Gcn::Opcode::BUFFER_LOAD_DWORD: { case Gcn::Opcode::BUFFER_LOAD_DWORD: {
offsets[inst.src[1].code] = inst.control.mubuf.offset; offsets[inst.src[1].code] = inst.control.mubuf.offset;
if (inst.src[3].field != Gcn::OperandField::ConstZero) { if (inst.src[3].field != Gcn::OperandField::ConstZero) {
ASSERT(inst.src[3].field == sgpr); const u32 index = inst.src[3].code;
offsets[inst.src[1].code] += soffset; ASSERT(sources[index] != -1);
offsets[inst.src[1].code] += sources[index];
} }
break; break;
} }
@ -59,6 +60,7 @@ CopyShaderData ParseCopyShader(const std::span<const u32>& code) {
if (last_attr != IR::Attribute::Position0) { if (last_attr != IR::Attribute::Position0) {
data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1; data.num_attrs = static_cast<u32>(last_attr) - static_cast<u32>(IR::Attribute::Param0) + 1;
} }
return data; return data;
} }

View File

@ -16,6 +16,6 @@ struct CopyShaderData {
u32 num_attrs{0}; u32 num_attrs{0};
}; };
CopyShaderData ParseCopyShader(const std::span<const u32>& code); CopyShaderData ParseCopyShader(std::span<const u32> code);
} // namespace Shader } // namespace Shader

View File

@ -3642,8 +3642,8 @@ constexpr std::array<InstFormat, 112> InstructionFormatMIMG = {{
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined,
ScalarType::Undefined}, ScalarType::Undefined},
// 95 = IMAGE_GATHER4_C_LZ_O // 95 = IMAGE_GATHER4_C_LZ_O
{InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Undefined, {InstClass::VectorMemImgSmp, InstCategory::VectorMemory, 4, 1, ScalarType::Uint32,
ScalarType::Undefined}, ScalarType::Float32},
// 96 = IMAGE_GET_LOD // 96 = IMAGE_GET_LOD
{InstClass::VectorMemImgUt, InstCategory::VectorMemory, 4, 1, ScalarType::Float32, {InstClass::VectorMemImgUt, InstCategory::VectorMemory, 4, 1, ScalarType::Float32,
ScalarType::Float32}, ScalarType::Float32},

View File

@ -155,6 +155,8 @@ public:
void V_SUB_I32(const GcnInst& inst); void V_SUB_I32(const GcnInst& inst);
void V_SUBREV_I32(const GcnInst& inst); void V_SUBREV_I32(const GcnInst& inst);
void V_ADDC_U32(const GcnInst& inst); void V_ADDC_U32(const GcnInst& inst);
void V_SUBB_U32(const GcnInst& inst);
void V_SUBBREV_U32(const GcnInst& inst);
void V_LDEXP_F32(const GcnInst& inst); void V_LDEXP_F32(const GcnInst& inst);
void V_CVT_PKNORM_U16_F32(const GcnInst& inst); void V_CVT_PKNORM_U16_F32(const GcnInst& inst);
void V_CVT_PKRTZ_F16_F32(const GcnInst& inst); void V_CVT_PKRTZ_F16_F32(const GcnInst& inst);
@ -273,7 +275,9 @@ private:
void SetDst(const InstOperand& operand, const IR::U32F32& value); void SetDst(const InstOperand& operand, const IR::U32F32& value);
void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw); void SetDst64(const InstOperand& operand, const IR::U64F64& value_raw);
// Vector ALU Helprers // Vector ALU Helpers
IR::U32 GetCarryIn(const GcnInst& inst);
void SetCarryOut(const GcnInst& inst, const IR::U1& carry);
IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0); IR::U32 VMovRelSHelper(u32 src_vgprno, const IR::U32 m0);
void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0);

View File

@ -87,6 +87,10 @@ void Translator::EmitVectorAlu(const GcnInst& inst) {
return V_SUBREV_I32(inst); return V_SUBREV_I32(inst);
case Opcode::V_ADDC_U32: case Opcode::V_ADDC_U32:
return V_ADDC_U32(inst); return V_ADDC_U32(inst);
case Opcode::V_SUBB_U32:
return V_SUBB_U32(inst);
case Opcode::V_SUBBREV_U32:
return V_SUBBREV_U32(inst);
case Opcode::V_LDEXP_F32: case Opcode::V_LDEXP_F32:
return V_LDEXP_F32(inst); return V_LDEXP_F32(inst);
case Opcode::V_CVT_PKNORM_U16_F32: case Opcode::V_CVT_PKNORM_U16_F32:
@ -546,51 +550,71 @@ void Translator::V_MBCNT_U32_B32(bool is_low, const GcnInst& inst) {
} }
void Translator::V_ADD_I32(const GcnInst& inst) { void Translator::V_ADD_I32(const GcnInst& inst) {
// Signed or unsigned components
const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))};
SetDst(inst.dst[0], ir.IAdd(src0, src1)); const IR::U32 result{ir.IAdd(src0, src1)};
// TODO: Carry SetDst(inst.dst[0], result);
// TODO: Carry-out with signed or unsigned components
} }
void Translator::V_SUB_I32(const GcnInst& inst) { void Translator::V_SUB_I32(const GcnInst& inst) {
// Unsigned components
const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.ISub(src0, src1)); const IR::U32 result{ir.ISub(src0, src1)};
SetDst(inst.dst[0], result);
const IR::U1 did_underflow{ir.IGreaterThan(src1, src0, false)};
SetCarryOut(inst, did_underflow);
} }
void Translator::V_SUBREV_I32(const GcnInst& inst) { void Translator::V_SUBREV_I32(const GcnInst& inst) {
// Unsigned components
const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])}; const IR::U32 src1{GetSrc(inst.src[1])};
SetDst(inst.dst[0], ir.ISub(src1, src0)); const IR::U32 result{ir.ISub(src1, src0)};
// TODO: Carry-out SetDst(inst.dst[0], result);
const IR::U1 did_underflow{ir.IGreaterThan(src0, src1, false)};
SetCarryOut(inst, did_underflow);
} }
void Translator::V_ADDC_U32(const GcnInst& inst) { void Translator::V_ADDC_U32(const GcnInst& inst) {
const auto src0 = GetSrc<IR::U32>(inst.src[0]); // Unsigned components
const auto src1 = GetSrc<IR::U32>(inst.src[1]); const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
IR::U1 carry; const IR::U32 carry{GetCarryIn(inst)};
if (inst.src_count == 3) { // VOP3 const IR::U32 result{ir.IAdd(ir.IAdd(src0, src1), carry)};
if (inst.src[2].field == OperandField::VccLo) {
carry = ir.GetVcc();
} else if (inst.src[2].field == OperandField::ScalarGPR) {
carry = ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[2].code));
} else {
UNREACHABLE();
}
} else { // VOP2
carry = ir.GetVcc();
}
const IR::U32 scarry = IR::U32{ir.Select(carry, ir.Imm32(1), ir.Imm32(0))};
const IR::U32 result = ir.IAdd(ir.IAdd(src0, src1), scarry);
SetDst(inst.dst[0], result); SetDst(inst.dst[0], result);
const IR::U1 less_src0 = ir.ILessThan(result, src0, false); const IR::U1 less_src0{ir.ILessThan(result, src0, false)};
const IR::U1 less_src1 = ir.ILessThan(result, src1, false); const IR::U1 less_src1{ir.ILessThan(result, src1, false)};
const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); const IR::U1 did_overflow{ir.LogicalOr(less_src0, less_src1)};
ir.SetVcc(did_overflow); SetCarryOut(inst, did_overflow);
}
void Translator::V_SUBB_U32(const GcnInst& inst) {
// Signed or unsigned components
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 carry{GetCarryIn(inst)};
const IR::U32 result{ir.ISub(ir.ISub(src0, src1), carry)};
SetDst(inst.dst[0], result);
// TODO: Carry-out with signed or unsigned components
}
void Translator::V_SUBBREV_U32(const GcnInst& inst) {
// Signed or unsigned components
const IR::U32 src0{GetSrc(inst.src[0])};
const IR::U32 src1{GetSrc(inst.src[1])};
const IR::U32 carry{GetCarryIn(inst)};
const IR::U32 result{ir.ISub(ir.ISub(src1, src0), carry)};
SetDst(inst.dst[0], result);
// TODO: Carry-out with signed or unsigned components
} }
void Translator::V_LDEXP_F32(const GcnInst& inst) { void Translator::V_LDEXP_F32(const GcnInst& inst) {
@ -1152,6 +1176,37 @@ void Translator::V_MAD_U64_U32(const GcnInst& inst) {
ir.SetVcc(did_overflow); ir.SetVcc(did_overflow);
} }
IR::U32 Translator::GetCarryIn(const GcnInst& inst) {
IR::U1 carry;
if (inst.src_count == 3) { // VOP3
if (inst.src[2].field == OperandField::VccLo) {
carry = ir.GetVcc();
} else if (inst.src[2].field == OperandField::ScalarGPR) {
carry = ir.GetThreadBitScalarReg(IR::ScalarReg(inst.src[2].code));
} else {
UNREACHABLE();
}
} else { // VOP2
carry = ir.GetVcc();
}
return IR::U32{ir.Select(carry, ir.Imm32(1), ir.Imm32(0))};
}
void Translator::SetCarryOut(const GcnInst& inst, const IR::U1& carry) {
if (inst.dst_count == 2) { // VOP3
if (inst.dst[1].field == OperandField::VccLo) {
ir.SetVcc(carry);
} else if (inst.dst[1].field == OperandField::ScalarGPR) {
ir.SetThreadBitScalarReg(IR::ScalarReg(inst.dst[1].code), carry);
} else {
UNREACHABLE();
}
} else { // VOP2
ir.SetVcc(carry);
}
}
// TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of // TODO: add range analysis pass to hopefully put an upper bound on m0, and only select one of
// [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to // [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to

View File

@ -147,6 +147,7 @@ void Translator::EmitVectorMemory(const GcnInst& inst) {
case Opcode::IMAGE_GATHER4_C_O: case Opcode::IMAGE_GATHER4_C_O:
case Opcode::IMAGE_GATHER4_C_LZ: case Opcode::IMAGE_GATHER4_C_LZ:
case Opcode::IMAGE_GATHER4_LZ_O: case Opcode::IMAGE_GATHER4_LZ_O:
case Opcode::IMAGE_GATHER4_C_LZ_O:
return IMAGE_GATHER(inst); return IMAGE_GATHER(inst);
// Image misc operations // Image misc operations
@ -410,7 +411,7 @@ void Translator::IMAGE_LOAD(bool has_mip, const GcnInst& inst) {
ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3)); ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(addr_reg + 3));
IR::TextureInstInfo info{}; IR::TextureInstInfo info{};
info.explicit_lod.Assign(has_mip); info.has_lod.Assign(has_mip);
const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info);
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
@ -512,6 +513,76 @@ void Translator::IMAGE_ATOMIC(AtomicOp op, const GcnInst& inst) {
} }
} }
IR::Value EmitImageSample(IR::IREmitter& ir, const GcnInst& inst, const IR::ScalarReg tsharp_reg,
const IR::ScalarReg sampler_reg, const IR::VectorReg addr_reg,
bool gather) {
const auto& mimg = inst.control.mimg;
const auto flags = MimgModifierFlags(mimg.mod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
info.has_lod.Assign(flags.any(MimgModifier::Lod));
info.is_array.Assign(mimg.da);
if (gather) {
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
info.is_gather.Assign(true);
} else {
info.has_derivatives.Assign(flags.test(MimgModifier::Derivative));
}
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Determine how many address registers need to be passed.
// The image type is unknown, so add all 4 possible base registers and resolve later.
int num_addr_regs = 4;
if (info.has_offset) {
++num_addr_regs;
}
if (info.has_bias) {
++num_addr_regs;
}
if (info.is_depth) {
++num_addr_regs;
}
if (info.has_derivatives) {
// The image type is unknown, so add all 6 possible derivative registers and resolve later.
num_addr_regs += 6;
}
// Fetch all the address registers to pass in the IR instruction. There can be up to 13
// registers.
const auto get_addr_reg = [&](int index) -> IR::F32 {
if (index >= num_addr_regs) {
return ir.Imm32(0.f);
}
return ir.GetVectorReg<IR::F32>(addr_reg + index);
};
const IR::Value address1 =
ir.CompositeConstruct(get_addr_reg(0), get_addr_reg(1), get_addr_reg(2), get_addr_reg(3));
const IR::Value address2 =
ir.CompositeConstruct(get_addr_reg(4), get_addr_reg(5), get_addr_reg(6), get_addr_reg(7));
const IR::Value address3 =
ir.CompositeConstruct(get_addr_reg(8), get_addr_reg(9), get_addr_reg(10), get_addr_reg(11));
const IR::Value address4 = get_addr_reg(12);
// Issue the placeholder IR instruction.
IR::Value texel = ir.ImageSampleRaw(handle, address1, address2, address3, address4, info);
if (info.is_depth && !gather) {
// For non-gather depth sampling, only return a single value.
texel = ir.CompositeExtract(texel, 0);
}
return texel;
}
void Translator::IMAGE_SAMPLE(const GcnInst& inst) { void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
const auto& mimg = inst.control.mimg; const auto& mimg = inst.control.mimg;
IR::VectorReg addr_reg{inst.src[0].code}; IR::VectorReg addr_reg{inst.src[0].code};
@ -520,72 +591,7 @@ void Translator::IMAGE_SAMPLE(const GcnInst& inst) {
const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod); const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource const IR::Value texel = EmitImageSample(ir, inst, tsharp_reg, sampler_reg, addr_reg, false);
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::U32 offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg<IR::U32>(addr_reg++) : IR::U32{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::Value derivatives = [&] -> IR::Value {
if (!flags.test(MimgModifier::Derivative)) {
return {};
}
addr_reg = addr_reg + 4;
return ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg - 4), ir.GetVectorReg<IR::F32>(addr_reg - 3),
ir.GetVectorReg<IR::F32>(addr_reg - 2), ir.GetVectorReg<IR::F32>(addr_reg - 1));
}();
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume if
// derivatives are present, that a 2D image is bound.
const bool has_derivatives = flags.test(MimgModifier::Derivative);
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
info.explicit_lod.Assign(explicit_lod);
info.has_derivatives.Assign(has_derivatives);
info.is_array.Assign(mimg.da);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
if (has_derivatives) {
return ir.ImageGradient(handle, body, derivatives, offset, {}, info);
}
if (!flags.test(MimgModifier::Pcf)) {
if (explicit_lod) {
return ir.ImageSampleExplicitLod(handle, body, offset, info);
} else {
return ir.ImageSampleImplicitLod(handle, body, bias, offset, info);
}
}
if (explicit_lod) {
return ir.ImageSampleDrefExplicitLod(handle, body, dref, offset, info);
}
return ir.ImageSampleDrefImplicitLod(handle, body, dref, bias, offset, info);
}();
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
if (((mimg.dmask >> i) & 1) == 0) { if (((mimg.dmask >> i) & 1) == 0) {
continue; continue;
@ -608,60 +614,13 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) {
const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; const IR::ScalarReg sampler_reg{inst.src[3].code * 4};
const auto flags = MimgModifierFlags(mimg.mod); const auto flags = MimgModifierFlags(mimg.mod);
// Load first dword of T# and S#. We will use them as the handle that will guide resource
// tracking pass where to read the sharps. This will later also get patched to the SPIRV texture
// binding index.
const IR::Value handle =
ir.CompositeConstruct(ir.GetScalarReg(tsharp_reg), ir.GetScalarReg(sampler_reg));
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::Value offset =
flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::Value{};
const IR::F32 bias =
flags.test(MimgModifier::LodBias) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
const IR::F32 dref =
flags.test(MimgModifier::Pcf) ? ir.GetVectorReg<IR::F32>(addr_reg++) : IR::F32{};
// Derivatives are tricky because their number depends on the texture type which is located in
// T#. We don't have access to T# though until resource tracking pass. For now assume no
// derivatives are present, otherwise we don't know where coordinates are placed in the address
// stream.
ASSERT_MSG(!flags.test(MimgModifier::Derivative), "Derivative image instruction");
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
// Since these are at most 4 dwords, we load them into a single uvec4 and place them
// in coords field of the instruction. Then the resource tracking pass will patch the
// IR instruction to fill in lod_clamp field.
const IR::Value body = ir.CompositeConstruct(
ir.GetVectorReg<IR::F32>(addr_reg), ir.GetVectorReg<IR::F32>(addr_reg + 1),
ir.GetVectorReg<IR::F32>(addr_reg + 2), ir.GetVectorReg<IR::F32>(addr_reg + 3));
const bool explicit_lod = flags.any(MimgModifier::Level0, MimgModifier::Lod);
IR::TextureInstInfo info{};
info.is_depth.Assign(flags.test(MimgModifier::Pcf));
info.has_bias.Assign(flags.test(MimgModifier::LodBias));
info.has_lod_clamp.Assign(flags.test(MimgModifier::LodClamp));
info.force_level0.Assign(flags.test(MimgModifier::Level0));
info.has_offset.Assign(flags.test(MimgModifier::Offset));
// info.explicit_lod.Assign(explicit_lod);
info.gather_comp.Assign(std::bit_width(mimg.dmask) - 1);
info.is_array.Assign(mimg.da);
// Issue IR instruction, leaving unknown fields blank to patch later.
const IR::Value texel = [&]() -> IR::Value {
const IR::F32 lod = flags.test(MimgModifier::Level0) ? ir.Imm32(0.f) : IR::F32{};
if (!flags.test(MimgModifier::Pcf)) {
return ir.ImageGather(handle, body, offset, info);
}
ASSERT(mimg.dmask & 1); // should be always 1st (R) component
return ir.ImageGatherDref(handle, body, offset, dref, info);
}();
// For gather4 instructions dmask selects which component to read and must have // For gather4 instructions dmask selects which component to read and must have
// only one bit set to 1 // only one bit set to 1
ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask"); ASSERT_MSG(std::popcount(mimg.dmask) == 1, "Unexpected bits in gather dmask");
// should be always 1st (R) component for depth
ASSERT(!flags.test(MimgModifier::Pcf) || mimg.dmask & 1);
const IR::Value texel = EmitImageSample(ir, inst, tsharp_reg, sampler_reg, addr_reg, true);
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)};
ir.SetVectorReg(dest_reg++, value); ir.SetVectorReg(dest_reg++, value);

View File

@ -8,6 +8,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/types.h" #include "common/types.h"
#include "shader_recompiler/backend/bindings.h" #include "shader_recompiler/backend/bindings.h"
#include "shader_recompiler/frontend/copy_shader.h"
#include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/attribute.h"
#include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/reg.h"
#include "shader_recompiler/ir/type.h" #include "shader_recompiler/ir/type.h"
@ -169,6 +170,8 @@ struct Info {
}; };
UserDataMask ud_mask{}; UserDataMask ud_mask{};
CopyShaderData gs_copy_data;
s8 vertex_offset_sgpr = -1; s8 vertex_offset_sgpr = -1;
s8 instance_offset_sgpr = -1; s8 instance_offset_sgpr = -1;

View File

@ -0,0 +1,21 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/bit_field.h"
#include "shader_recompiler/ir/opcodes.h"
#include "src/common/types.h"
#pragma once
namespace Shader::IR {
constexpr size_t DEBUGPRINT_NUM_FORMAT_ARGS = NumArgsOf(IR::Opcode::DebugPrint) - 1;
union DebugPrintFlags {
u32 raw;
// For now, only flag is the number of variadic format args actually used
// So bitfield not really needed
BitField<0, 32, u32> num_args;
};
} // namespace Shader::IR

View File

@ -1,10 +1,15 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <bit> #include <bit>
#include <source_location> #include <source_location>
#include <boost/container/small_vector.hpp>
#include "common/assert.h"
#include "shader_recompiler/exception.h" #include "shader_recompiler/exception.h"
#include "shader_recompiler/ir/debug_print.h"
#include "shader_recompiler/ir/ir_emitter.h" #include "shader_recompiler/ir/ir_emitter.h"
#include "shader_recompiler/ir/opcodes.h"
#include "shader_recompiler/ir/value.h" #include "shader_recompiler/ir/value.h"
namespace Shader::IR { namespace Shader::IR {
@ -1487,27 +1492,34 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c
return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value); return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value);
} }
Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, Value IREmitter::ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2,
const U32& offset, TextureInstInfo info) { const Value& address3, const Value& address4,
return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, body, bias, offset); TextureInstInfo info) {
return Inst(Opcode::ImageSampleRaw, Flags{info}, handle, address1, address2, address3,
address4);
} }
Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& body, const U32& offset, Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias,
TextureInstInfo info) { const Value& offset, TextureInstInfo info) {
return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, body, IR::F32{}, offset); return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, coords, bias, offset);
} }
F32 IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& body, const F32& dref, Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& coords, const F32& lod,
const F32& bias, const U32& offset, const Value& offset, TextureInstInfo info) {
TextureInstInfo info) { return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, coords, lod, offset);
return Inst<F32>(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, body, dref, bias,
offset);
} }
F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body, const F32& dref, Value IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& coords,
const U32& offset, TextureInstInfo info) { const F32& dref, const F32& bias, const Value& offset,
return Inst<F32>(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, body, dref, IR::F32{}, TextureInstInfo info) {
offset); return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, coords, dref, bias,
offset);
}
Value IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& coords,
const F32& dref, const F32& lod, const Value& offset,
TextureInstInfo info) {
return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, coords, dref, lod, offset);
} }
Value IREmitter::ImageGather(const Value& handle, const Value& coords, const Value& offset, Value IREmitter::ImageGather(const Value& handle, const Value& coords, const Value& offset,
@ -1539,9 +1551,11 @@ Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, Texture
return Inst(Opcode::ImageQueryLod, Flags{info}, handle, coords); return Inst(Opcode::ImageQueryLod, Flags{info}, handle, coords);
} }
Value IREmitter::ImageGradient(const Value& handle, const Value& coords, const Value& derivatives, Value IREmitter::ImageGradient(const Value& handle, const Value& coords,
const Value& derivatives_dx, const Value& derivatives_dy,
const Value& offset, const F32& lod_clamp, TextureInstInfo info) { const Value& offset, const F32& lod_clamp, TextureInstInfo info) {
return Inst(Opcode::ImageGradient, Flags{info}, handle, coords, derivatives, offset, lod_clamp); return Inst(Opcode::ImageGradient, Flags{info}, handle, coords, derivatives_dx, derivatives_dy,
offset, lod_clamp);
} }
Value IREmitter::ImageRead(const Value& handle, const Value& coords, TextureInstInfo info) { Value IREmitter::ImageRead(const Value& handle, const Value& coords, TextureInstInfo info) {
@ -1553,6 +1567,38 @@ void IREmitter::ImageWrite(const Value& handle, const Value& coords, const Value
Inst(Opcode::ImageWrite, Flags{info}, handle, coords, color); Inst(Opcode::ImageWrite, Flags{info}, handle, coords, color);
} }
// Debug print maps to SPIRV's NonSemantic DebugPrintf instruction
// Renderdoc will hook in its own implementation of the SPIRV instruction
// Renderdoc accepts format specifiers, e.g. %u, listed here:
// https://github.com/KhronosGroup/Vulkan-ValidationLayers/blob/main/docs/debug_printf.md
//
// fmt must be a string literal (pointer is shallow copied into a Value)
// Example usage:
// ir.DebugPrint("invocation xyz: (%u, %u, %u)",
// {ir.GetVectorReg(IR::VectorReg::V0),
// ir.GetVectorReg(IR::VectorReg::V1),
// ir.GetVectorReg(IR::VectorReg::V2)});
void IREmitter::DebugPrint(const char* fmt, boost::container::small_vector<Value, 5> format_args) {
std::array<Value, DEBUGPRINT_NUM_FORMAT_ARGS> args;
ASSERT_MSG(format_args.size() < DEBUGPRINT_NUM_FORMAT_ARGS,
"DebugPrint only supports up to {} format args", DEBUGPRINT_NUM_FORMAT_ARGS);
for (int i = 0; i < format_args.size(); i++) {
args[i] = format_args[i];
}
for (int i = format_args.size(); i < DEBUGPRINT_NUM_FORMAT_ARGS; i++) {
args[i] = Inst(Opcode::Void);
}
IR::Value fmt_val{fmt};
DebugPrintFlags flags;
flags.num_args.Assign(format_args.size());
Inst(Opcode::DebugPrint, Flags{flags}, fmt_val, args[0], args[1], args[2], args[3]);
}
void IREmitter::EmitVertex() { void IREmitter::EmitVertex() {
Inst(Opcode::EmitVertex); Inst(Opcode::EmitVertex);
} }

View File

@ -6,6 +6,7 @@
#include <cstring> #include <cstring>
#include <type_traits> #include <type_traits>
#include "shader_recompiler/info.h"
#include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/attribute.h"
#include "shader_recompiler/ir/basic_block.h" #include "shader_recompiler/ir/basic_block.h"
#include "shader_recompiler/ir/condition.h" #include "shader_recompiler/ir/condition.h"
@ -43,6 +44,7 @@ public:
void Epilogue(); void Epilogue();
void Discard(); void Discard();
void Discard(const U1& cond); void Discard(const U1& cond);
void DebugPrint(const char* fmt, boost::container::small_vector<Value, 5> args);
void Barrier(); void Barrier();
void WorkgroupMemoryBarrier(); void WorkgroupMemoryBarrier();
@ -275,20 +277,25 @@ public:
[[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords, [[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords,
const Value& value, TextureInstInfo info); const Value& value, TextureInstInfo info);
[[nodiscard]] Value ImageSampleRaw(const Value& handle, const Value& address1,
const Value& address2, const Value& address3,
const Value& address4, TextureInstInfo info);
[[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body, [[nodiscard]] Value ImageSampleImplicitLod(const Value& handle, const Value& body,
const F32& bias, const U32& offset, const F32& bias, const Value& offset,
TextureInstInfo info); TextureInstInfo info);
[[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& body, [[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& body,
const U32& offset, TextureInstInfo info); const F32& lod, const Value& offset,
TextureInstInfo info);
[[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& body, [[nodiscard]] Value ImageSampleDrefImplicitLod(const Value& handle, const Value& body,
const F32& dref, const F32& bias, const F32& dref, const F32& bias,
const U32& offset, TextureInstInfo info); const Value& offset, TextureInstInfo info);
[[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& body, [[nodiscard]] Value ImageSampleDrefExplicitLod(const Value& handle, const Value& body,
const F32& dref, const U32& offset, const F32& dref, const F32& lod,
TextureInstInfo info); const Value& offset, TextureInstInfo info);
[[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod, [[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod,
const U1& skip_mips); const U1& skip_mips);
@ -304,8 +311,9 @@ public:
[[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset, [[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset,
const U32& lod, const U32& multisampling, TextureInstInfo info); const U32& lod, const U32& multisampling, TextureInstInfo info);
[[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords, [[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords,
const Value& derivatives, const Value& offset, const Value& derivatives_dx, const Value& derivatives_dy,
const F32& lod_clamp, TextureInstInfo info); const Value& offset, const F32& lod_clamp,
TextureInstInfo info);
[[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info); [[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info);
void ImageWrite(const Value& handle, const Value& coords, const Value& color, void ImageWrite(const Value& handle, const Value& coords, const Value& color,
TextureInstInfo info); TextureInstInfo info);

View File

@ -89,6 +89,7 @@ bool Inst::MayHaveSideEffects() const noexcept {
case Opcode::ImageAtomicOr32: case Opcode::ImageAtomicOr32:
case Opcode::ImageAtomicXor32: case Opcode::ImageAtomicXor32:
case Opcode::ImageAtomicExchange32: case Opcode::ImageAtomicExchange32:
case Opcode::DebugPrint:
case Opcode::EmitVertex: case Opcode::EmitVertex:
case Opcode::EmitPrimitive: case Opcode::EmitPrimitive:
return true; return true;

View File

@ -21,7 +21,7 @@ namespace Detail {
struct OpcodeMeta { struct OpcodeMeta {
std::string_view name; std::string_view name;
Type type; Type type;
std::array<Type, 5> arg_types; std::array<Type, 6> arg_types;
}; };
// using enum Type; // using enum Type;
@ -51,6 +51,7 @@ constexpr Type F32x4{Type::F32x4};
constexpr Type F64x2{Type::F64x2}; constexpr Type F64x2{Type::F64x2};
constexpr Type F64x3{Type::F64x3}; constexpr Type F64x3{Type::F64x3};
constexpr Type F64x4{Type::F64x4}; constexpr Type F64x4{Type::F64x4};
constexpr Type StringLiteral{Type::StringLiteral};
constexpr OpcodeMeta META_TABLE[]{ constexpr OpcodeMeta META_TABLE[]{
#define OPCODE(name_token, type_token, ...) \ #define OPCODE(name_token, type_token, ...) \
@ -81,7 +82,7 @@ constexpr u8 NUM_ARGS[]{
} }
/// Get the number of arguments an opcode accepts /// Get the number of arguments an opcode accepts
[[nodiscard]] inline size_t NumArgsOf(Opcode op) noexcept { [[nodiscard]] constexpr inline size_t NumArgsOf(Opcode op) noexcept {
return static_cast<size_t>(Detail::NUM_ARGS[static_cast<size_t>(op)]); return static_cast<size_t>(Detail::NUM_ARGS[static_cast<size_t>(op)]);
} }

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