Merge branch 'shadps4-emu:main' into main

This commit is contained in:
DanielSvoboda 2024-10-10 15:52:28 -03:00 committed by GitHub
commit cfa8455008
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
55 changed files with 1833 additions and 677 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

View File

@ -324,6 +324,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
@ -464,6 +468,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 +698,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

View File

@ -62,7 +62,7 @@ static s16 cursorState = HideCursorState::Idle;
static int cursorHideTimeout = 5; // 5 seconds (default) 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;
@ -325,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;
@ -384,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()) {
@ -472,7 +473,6 @@ void load(const std::filesystem::path& path) {
} }
isShowSplash = toml::find_or<bool>(general, "showSplash", true); isShowSplash = toml::find_or<bool>(general, "showSplash", true);
isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false); isAutoUpdate = toml::find_or<bool>(general, "autoUpdate", false);
backButtonBehavior = toml::find_or<std::string>(general, "backButtonBehavior", "left");
} }
if (data.contains("Input")) { if (data.contains("Input")) {
@ -480,6 +480,7 @@ void load(const std::filesystem::path& path) {
cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle); cursorState = toml::find_or<int>(input, "cursorState", HideCursorState::Idle);
cursorHideTimeout = toml::find_or<int>(input, "cursorHideTimeout", 5); 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);
} }
@ -523,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);
@ -576,7 +594,7 @@ void save(const std::filesystem::path& path) {
data["General"]["autoUpdate"] = isAutoUpdate; data["General"]["autoUpdate"] = isAutoUpdate;
data["Input"]["cursorState"] = cursorState; data["Input"]["cursorState"] = cursorState;
data["Input"]["cursorHideTimeout"] = cursorHideTimeout; data["Input"]["cursorHideTimeout"] = cursorHideTimeout;
data["General"]["backButtonBehavior"] = backButtonBehavior; 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;
@ -601,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;
@ -626,8 +650,6 @@ void setDefaultValues() {
playBGM = false; playBGM = false;
BGMvolume = 50; BGMvolume = 50;
enableDiscordRPC = true; enableDiscordRPC = true;
cursorState = HideCursorState::Idle;
cursorHideTimeout = 5;
screenWidth = 1280; screenWidth = 1280;
screenHeight = 720; screenHeight = 720;
logFilter = ""; logFilter = "";
@ -638,6 +660,8 @@ void setDefaultValues() {
} else { } else {
updateChannel = "Nightly"; updateChannel = "Nightly";
} }
cursorState = HideCursorState::Idle;
cursorHideTimeout = 5;
backButtonBehavior = "left"; backButtonBehavior = "left";
useSpecialPad = false; useSpecialPad = false;
specialPadClass = 1; specialPadClass = 1;

View File

@ -20,15 +20,14 @@ bool getPlayBGM();
int getBGMvolume(); int getBGMvolume();
bool getEnableDiscordRPC(); bool getEnableDiscordRPC();
s16 getCursorState();
int getCursorHideTimeout();
std::string getLogFilter(); std::string getLogFilter();
std::string getLogType(); std::string getLogType();
std::string getUserName(); std::string getUserName();
std::string getUpdateChannel(); std::string getUpdateChannel();
std::string getBackButtonBehavior();
s16 getCursorState();
int getCursorHideTimeout();
std::string getBackButtonBehavior();
bool getUseSpecialPad(); bool getUseSpecialPad();
int getSpecialPadClass(); int getSpecialPadClass();
@ -59,14 +58,14 @@ 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 setEnableDiscordRPC(bool enable);
void setCursorState(s16 cursorState);
void setCursorHideTimeout(int newcursorHideTimeout);
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 setBackButtonBehavior(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);
@ -85,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);
@ -104,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

@ -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

@ -499,3 +499,11 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF;
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,10 +1076,28 @@ 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");
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 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) {
LOG_TRACE(Lib_GnmDriver, "called"); LOG_TRACE(Lib_GnmDriver, "called");

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

@ -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

@ -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

@ -26,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"
@ -258,7 +259,7 @@ void Emulator::Run(const std::filesystem::path& file) {
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},

View File

@ -10,9 +10,10 @@ 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;
for (const auto& installLoc : Config::getGameInstallDirs()) {
QString installDir;
Common::FS::PathToQString(installDir, installLoc);
QDir parentFolder(installDir); QDir parentFolder(installDir);
QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot);
for (const auto& fileInfo : fileList) { for (const auto& fileInfo : fileList) {
@ -20,6 +21,7 @@ void GameInfoClass::GetGameInfo(QWidget* parent) {
filePaths.append(fileInfo.absoluteFilePath()); filePaths.append(fileInfo.absoluteFilePath());
} }
} }
}
m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) {
return readGameInfo(Common::FS::PathFromQString(path)); return readGameInfo(Common::FS::PathFromQString(path));
}).results(); }).results();

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

@ -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"
@ -672,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);
@ -821,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

@ -47,8 +47,6 @@ QStringList languageNames = {"Arabic",
const QVector<int> languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0, const QVector<int> languageIndexes = {21, 23, 14, 6, 18, 1, 12, 22, 2, 4, 25, 24, 29, 5, 0,
9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 28}; 9, 15, 16, 17, 7, 26, 8, 11, 20, 3, 13, 27, 10, 19, 28};
QStringList hideCursorStates = {"Never", "Idle", "Always"};
SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidget* parent) SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidget* parent)
: QDialog(parent), ui(new Ui::SettingsDialog) { : QDialog(parent), ui(new Ui::SettingsDialog) {
ui->setupUi(this); ui->setupUi(this);
@ -70,7 +68,14 @@ 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->addItems(hideCursorStates); 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();
@ -103,15 +108,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults"));
ui->buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close")); ui->buttonBox->button(QDialogButtonBox::Close)->setText(tr("Close"));
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");
QString currentBackButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior());
int index = ui->backButtonBehaviorComboBox->findData(currentBackButtonBehavior);
ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0);
connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this, connect(ui->tabWidgetSettings, &QTabWidget::currentChanged, this,
[this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); }); [this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); });
@ -176,14 +172,6 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
rpc->shutdown(); rpc->shutdown();
} }
}); });
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());
}
});
} }
// Input TAB // Input TAB
@ -196,6 +184,14 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this, connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this,
[](int index) { Config::setCursorHideTimeout(index); }); [](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
@ -221,6 +217,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,
@ -249,6 +290,11 @@ SettingsDialog::SettingsDialog(std::span<const QString> physical_devices, QWidge
ui->logFilter->installEventFilter(this); ui->logFilter->installEventFilter(this);
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); ui->backButtonBehaviorGroupBox->installEventFilter(this);
// Graphics // Graphics
@ -259,6 +305,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);
@ -309,6 +361,13 @@ 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()); QString backButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior());
int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior);
ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0);
@ -385,6 +444,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("updaterGroupBox"); text = tr("updaterGroupBox");
} else if (elementName == "GUIgroupBox") { } else if (elementName == "GUIgroupBox") {
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") { } else if (elementName == "backButtonBehaviorGroupBox") {
text = tr("backButtonBehaviorGroupBox"); text = tr("backButtonBehaviorGroupBox");
} }
@ -404,6 +472,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) {
text = tr("nullGpuCheckBox"); text = tr("nullGpuCheckBox");
} }
// 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
if (elementName == "debugDump") { if (elementName == "debugDump") {
text = tr("debugDump"); text = tr("debugDump");

View File

@ -274,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>
@ -286,56 +289,88 @@
<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"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>265</width> <width>275</width>
<height>0</height> <height>0</height>
</size> </size>
</property> </property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>16777215</height>
</size>
</property>
<property name="title"> <property name="title">
<string>Update</string> <string>Update</string>
</property> </property>
<widget class="QCheckBox" name="updateCheckBox"> <layout class="QVBoxLayout" name="UpdateLayout" stretch="0,0,0">
<property name="geometry"> <property name="spacing">
<rect> <number>5</number>
<x>10</x>
<y>130</y>
<width>261</width>
<height>22</height>
</rect>
</property> </property>
<property name="text"> <property name="topMargin">
<string>Check for Updates at Startup</string> <number>1</number>
</property> </property>
</widget> <property name="rightMargin">
<number>11</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item>
<widget class="QGroupBox" name="updaterComboBox"> <widget class="QGroupBox" name="updaterComboBox">
<property name="geometry"> <property name="sizePolicy">
<rect> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<x>12</x> <horstretch>0</horstretch>
<y>30</y> <verstretch>0</verstretch>
<width>241</width> </sizepolicy>
<height>65</height> </property>
</rect> <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>
<property name="title"> <property name="title">
<string>Update Channel</string> <string>Update Channel</string>
</property> </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"> <widget class="QComboBox" name="updateComboBox">
<property name="geometry"> <property name="sizePolicy">
<rect> <sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<x>12</x> <horstretch>0</horstretch>
<y>30</y> <verstretch>0</verstretch>
<width>217</width> </sizepolicy>
<height>28</height>
</rect>
</property> </property>
<item> <item>
<property name="text"> <property name="text">
@ -348,47 +383,93 @@
</property> </property>
</item> </item>
</widget> </widget>
</item>
</layout>
</widget> </widget>
</item>
<item>
<widget class="QPushButton" name="checkUpdateButton"> <widget class="QPushButton" name="checkUpdateButton">
<property name="geometry"> <property name="sizePolicy">
<rect> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<x>25</x> <horstretch>0</horstretch>
<y>100</y> <verstretch>0</verstretch>
<width>215</width> </sizepolicy>
<height>24</height> </property>
</rect> <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>
<property name="text"> <property name="text">
<string>Check for Updates</string> <string>Check for Updates</string>
</property> </property>
</widget> </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> </widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="GUITabLayoutMiddle" stretch="1"> <layout class="QVBoxLayout" name="GUITabLayoutMiddle" stretch="0">
<item> <item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QGroupBox" name="GUIgroupBox"> <widget class="QGroupBox" name="GUIgroupBox">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="minimumSize">
<size>
<width>0</width>
<height>0</height>
</size>
</property>
<property name="title"> <property name="title">
<string>GUI Settings</string> <string>GUI Settings</string>
</property> </property>
<widget class="QWidget" name="verticalLayoutWidget_3"> <layout class="QVBoxLayout" name="GUILayout">
<property name="geometry"> <property name="topMargin">
<rect> <number>1</number>
<x>10</x> </property>
<y>30</y> <property name="bottomMargin">
<width>241</width> <number>11</number>
<height>92</height> </property>
</rect> <item>
<layout class="QVBoxLayout" name="GUIMusicLayout">
<property name="topMargin">
<number>1</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout">
<item> <item>
<widget class="QCheckBox" name="playBGMCheckBox"> <widget class="QCheckBox" name="playBGMCheckBox">
<property name="sizePolicy"> <property name="sizePolicy">
@ -403,11 +484,35 @@
</widget> </widget>
</item> </item>
<item> <item>
<layout class="QHBoxLayout" name="horizontalLayout"> <spacer name="GUIverticalSpacer_2">
<item> <property name="orientation">
<layout class="QVBoxLayout" name="verticalLayout_2"> <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> <item>
<widget class="QLabel" name="label"> <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"> <property name="text">
<string>Volume</string> <string>Volume</string>
</property> </property>
@ -449,51 +554,35 @@
</item> </item>
</layout> </layout>
</item> </item>
</layout> <item>
<widget class="QWidget" name="GUIwidgetSpacer" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>61</height>
</size>
</property>
</widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="ControllerTabLayoutRight" stretch="1"> <layout class="QVBoxLayout" name="EmptyTabLayoutRight">
<item> <item>
<widget class="QGroupBox" name="ControllerGroupBox"> <spacer name="emptyHorizontalSpacer">
<property name="sizePolicy"> <property name="orientation">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred"> <enum>Qt::Orientation::Horizontal</enum>
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="title"> <property name="sizeHint" stdset="0">
<string>Controller Settings</string> <size>
<width>40</width>
<height>20</height>
</size>
</property> </property>
<widget class="QGroupBox" name="backButtonBehaviorGroupBox"> </spacer>
<property name="geometry">
<rect>
<x>12</x>
<y>30</y>
<width>241</width>
<height>65</height>
</rect>
</property>
<property name="title">
<string>Back Button Behavior</string>
</property>
<widget class="QComboBox" name="backButtonBehaviorComboBox">
<property name="geometry">
<rect>
<x>12</x>
<y>30</y>
<width>217</width>
<height>28</height>
</rect>
</property>
</widget>
</widget>
</widget>
</item> </item>
</layout> </layout>
</item> </item>
@ -510,18 +599,48 @@
<layout class="QHBoxLayout" name="inputTabHLayoutTop" stretch="1,1,1"> <layout class="QHBoxLayout" name="inputTabHLayoutTop" stretch="1,1,1">
<item> <item>
<layout class="QVBoxLayout" name="cursorTabLayoutLeft"> <layout class="QVBoxLayout" name="cursorTabLayoutLeft">
<item> <property name="spacing">
<widget class="QGroupBox" name="HideCursor"> <number>7</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignTop">
<widget class="QGroupBox" name="cursorGroupBox">
<property name="title"> <property name="title">
<string>Cursor</string> <string>Cursor</string>
</property> </property>
<layout class="QVBoxLayout" name="inputCursorLayout"> <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> <item>
<widget class="QGroupBox" name="hideCursorGroupBox"> <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"> <property name="title">
<string>Hide Cursor</string> <string>Hide Cursor</string>
</property> </property>
<layout class="QVBoxLayout" name="hideCursorLayout"> <layout class="QVBoxLayout" name="hideCursorLayout">
<property name="spacing">
<number>7</number>
</property>
<property name="bottomMargin">
<number>11</number>
</property>
<item> <item>
<widget class="QComboBox" name="hideCursorComboBox"/> <widget class="QComboBox" name="hideCursorComboBox"/>
</item> </item>
@ -533,10 +652,16 @@
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize"> <property name="minimumSize">
<size> <size>
<width>0</width> <width>0</width>
<height>85</height> <height>0</height>
</size> </size>
</property> </property>
<property name="title"> <property name="title">
@ -549,19 +674,28 @@
<bool>false</bool> <bool>false</bool>
</property> </property>
<layout class="QHBoxLayout" name="IdleTimeoutLayout" stretch="0,0"> <layout class="QHBoxLayout" name="IdleTimeoutLayout" stretch="0,0">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin"> <property name="leftMargin">
<number>70</number> <number>70</number>
</property> </property>
<property name="topMargin"> <property name="topMargin">
<number>11</number> <number>5</number>
</property> </property>
<item> <property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item alignment="Qt::AlignmentFlag::AlignHCenter">
<widget class="QSpinBox" name="idleTimeoutSpinBox"> <widget class="QSpinBox" name="idleTimeoutSpinBox">
<property name="enabled"> <property name="enabled">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Minimum"> <sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -620,26 +754,80 @@
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="emptyTabLayoutMiddle"> <layout class="QVBoxLayout" name="ControllerTabLayoutMiddle">
<item> <item>
<spacer name="emptyHorizontalSpacerMiddle"> <widget class="QGroupBox" name="ControllerGroupBox">
<property name="orientation"> <property name="sizePolicy">
<enum>Qt::Orientation::Horizontal</enum> <sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property> </property>
<property name="sizeHint" stdset="0"> <property name="title">
<string>Controller</string>
</property>
<layout class="QVBoxLayout" name="ControllerLayout">
<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="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> <size>
<width>40</width> <width>237</width>
<height>20</height> <height>0</height>
</size> </size>
</property> </property>
</spacer> <property name="title">
<string>Back Button Behavior</string>
</property>
<layout class="QVBoxLayout" name="BackButtonLayout">
<property name="leftMargin">
<number>11</number>
</property>
<item>
<widget class="QComboBox" name="backButtonBehaviorComboBox"/>
</item>
</layout>
</widget>
</item>
<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>
</item> </item>
</layout> </layout>
</item> </item>
<item> <item>
<layout class="QVBoxLayout" name="emptyTabLayoutRight"> <layout class="QVBoxLayout" name="emptyTabLayoutRight">
<item> <item>
<spacer name="emptyHorizontalSpacerRight"> <spacer name="emptyhorizontalSpacer">
<property name="orientation"> <property name="orientation">
<enum>Qt::Orientation::Horizontal</enum> <enum>Qt::Orientation::Horizontal</enum>
</property> </property>
@ -918,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

@ -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>
@ -534,16 +569,6 @@
<source>Volume</source> <source>Volume</source>
<translation>Volume</translation> <translation>Volume</translation>
</message> </message>
<message>
<location filename="../settings_dialog.ui" line="455"/>
<source>Controller Settings</source>
<translation>Controller Settings</translation>
</message>
<message>
<location filename="../settings_dialog.ui" line="467"/>
<source>Back Button Behavior</source>
<translation>Back Button Behavior</translation>
</message>
</context> </context>
<context> <context>
<name>MainWindow</name> <name>MainWindow</name>
@ -1033,6 +1058,41 @@
<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> <message>
<location filename="../settings_dialog.cpp" line="330"/> <location filename="../settings_dialog.cpp" line="330"/>
<source>backButtonBehaviorGroupBox</source> <source>backButtonBehaviorGroupBox</source>
@ -1083,6 +1143,21 @@
<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="465"/>
<source>gameFoldersBox</source>
<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>
<location filename="../settings_dialog.cpp" line="329"/> <location filename="../settings_dialog.cpp" line="329"/>
<source>debugDump</source> <source>debugDump</source>

View File

@ -206,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);
@ -247,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};
@ -290,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};
@ -314,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

@ -368,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,
@ -384,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

@ -284,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) {
@ -333,7 +334,6 @@ void EmitContext::DefineInputs() {
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(num_verts_in))};
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));
@ -394,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;

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

@ -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

@ -411,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++) {
@ -513,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};
@ -521,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;
@ -609,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

@ -3,12 +3,12 @@
#pragma once #pragma once
#include <span> #include <span>
#include <vector>
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
#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"
@ -170,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

@ -1492,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) {
return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, coords, lod, offset);
}
Value IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& coords,
const F32& dref, const F32& bias, const Value& offset,
TextureInstInfo info) { TextureInstInfo info) {
return Inst<F32>(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, body, dref, bias, return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, coords, dref, bias,
offset); offset);
} }
F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body, const F32& dref, Value IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& coords,
const U32& offset, TextureInstInfo info) { const F32& dref, const F32& lod, const Value& offset,
return Inst<F32>(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, body, dref, IR::F32{}, TextureInstInfo info) {
offset); 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,
@ -1544,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) {

View File

@ -277,21 +277,26 @@ 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,
[[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& body,
const F32& dref, const F32& bias,
const U32& offset, TextureInstInfo info);
[[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& body,
const F32& dref, const U32& offset,
TextureInstInfo info); TextureInstInfo info);
[[nodiscard]] Value ImageSampleDrefImplicitLod(const Value& handle, const Value& body,
const F32& dref, const F32& bias,
const Value& offset, TextureInstInfo info);
[[nodiscard]] Value ImageSampleDrefExplicitLod(const Value& handle, const Value& body,
const F32& dref, const F32& lod,
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);
[[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod, [[nodiscard]] Value ImageQueryDimension(const Value& handle, const U32& lod,
@ -306,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

@ -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;

View File

@ -317,16 +317,17 @@ OPCODE(ConvertU16U32, U16, U32,
OPCODE(ConvertU32U16, U32, U16, ) OPCODE(ConvertU32U16, U32, U16, )
// Image operations // Image operations
OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, )
OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageSampleImplicitLod, F32x4, Opaque, F32x4, F32, Opaque, )
OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, Opaque, ) OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, F32, Opaque, )
OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, )
OPCODE(ImageSampleDrefExplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, )
OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, ) OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, )
OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, )
OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, )
OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, )
OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, ) OPCODE(ImageQueryLod, F32x4, Opaque, Opaque, )
OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, ) OPCODE(ImageGradient, F32x4, Opaque, Opaque, Opaque, Opaque, Opaque, F32, )
OPCODE(ImageRead, U32x4, Opaque, Opaque, ) OPCODE(ImageRead, U32x4, Opaque, Opaque, )
OPCODE(ImageWrite, Void, Opaque, Opaque, U32x4, ) OPCODE(ImageWrite, Void, Opaque, Opaque, U32x4, )

View File

@ -132,38 +132,16 @@ bool IsImageStorageInstruction(const IR::Inst& inst) {
bool IsImageInstruction(const IR::Inst& inst) { bool IsImageInstruction(const IR::Inst& inst) {
switch (inst.GetOpcode()) { switch (inst.GetOpcode()) {
case IR::Opcode::ImageSampleExplicitLod:
case IR::Opcode::ImageSampleImplicitLod:
case IR::Opcode::ImageSampleDrefExplicitLod:
case IR::Opcode::ImageSampleDrefImplicitLod:
case IR::Opcode::ImageFetch: case IR::Opcode::ImageFetch:
case IR::Opcode::ImageGather:
case IR::Opcode::ImageGatherDref:
case IR::Opcode::ImageQueryDimensions: case IR::Opcode::ImageQueryDimensions:
case IR::Opcode::ImageQueryLod: case IR::Opcode::ImageQueryLod:
case IR::Opcode::ImageGradient: case IR::Opcode::ImageSampleRaw:
return true; return true;
default: default:
return IsImageStorageInstruction(inst); return IsImageStorageInstruction(inst);
} }
} }
u32 ImageOffsetArgumentPosition(const IR::Inst& inst) {
switch (inst.GetOpcode()) {
case IR::Opcode::ImageGather:
case IR::Opcode::ImageGatherDref:
return 2;
case IR::Opcode::ImageSampleExplicitLod:
case IR::Opcode::ImageSampleImplicitLod:
return 3;
case IR::Opcode::ImageSampleDrefExplicitLod:
case IR::Opcode::ImageSampleDrefImplicitLod:
return 4;
default:
UNREACHABLE();
}
}
class Descriptors { class Descriptors {
public: public:
explicit Descriptors(Info& info_) explicit Descriptors(Info& info_)
@ -467,6 +445,185 @@ IR::Value PatchCubeCoord(IR::IREmitter& ir, const IR::Value& s, const IR::Value&
} }
} }
void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info,
Descriptors& descriptors, const IR::Inst* producer,
const u32 image_binding, const AmdGpu::Image& image) {
// Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions
const u32 sampler_binding = [&] {
ASSERT(producer->GetOpcode() == IR::Opcode::CompositeConstructU32x2);
const IR::Value& handle = producer->Arg(1);
// Inline sampler resource.
if (handle.IsImmediate()) {
LOG_WARNING(Render_Vulkan, "Inline sampler detected");
return descriptors.Add(SamplerResource{
.sgpr_base = std::numeric_limits<u32>::max(),
.dword_offset = 0,
.inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()},
});
}
// Normal sampler resource.
const auto ssharp_handle = handle.InstRecursive();
const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle);
const auto ssharp = TrackSharp(ssharp_ud);
return descriptors.Add(SamplerResource{
.sgpr_base = ssharp.sgpr_base,
.dword_offset = ssharp.dword_offset,
.associated_image = image_binding,
.disable_aniso = disable_aniso,
});
}();
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
const auto inst_info = inst.Flags<IR::TextureInstInfo>();
const IR::U32 handle = ir.Imm32(image_binding | sampler_binding << 16);
IR::Inst* body1 = inst.Arg(1).InstRecursive();
IR::Inst* body2 = inst.Arg(2).InstRecursive();
IR::Inst* body3 = inst.Arg(3).InstRecursive();
IR::Inst* body4 = inst.Arg(4).InstRecursive();
const auto get_addr_reg = [&](u32 index) -> IR::F32 {
if (index <= 3) {
return IR::F32{body1->Arg(index)};
}
if (index >= 4 && index <= 7) {
return IR::F32{body2->Arg(index - 4)};
}
if (index >= 8 && index <= 11) {
return IR::F32{body3->Arg(index - 8)};
}
if (index == 12) {
return IR::F32{body4};
}
UNREACHABLE();
};
u32 addr_reg = 0;
// Load first address components as denoted in 8.2.4 VGPR Usage Sea Islands Series Instruction
// Set Architecture
const IR::Value offset = [&] -> IR::Value {
if (!inst_info.has_offset) {
return IR::U32{};
}
// The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16].
const IR::Value arg = get_addr_reg(addr_reg++);
const auto read = [&](u32 off) -> IR::U32 {
if (arg.IsImmediate()) {
const u16 comp = (arg.U32() >> off) & 0x3F;
return ir.Imm32(s32(comp << 26) >> 26);
}
return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(off), ir.Imm32(6), true);
};
switch (image.GetType()) {
case AmdGpu::ImageType::Color1D:
case AmdGpu::ImageType::Color1DArray:
return read(0);
case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DArray:
case AmdGpu::ImageType::Color2DMsaa:
return ir.CompositeConstruct(read(0), read(8));
case AmdGpu::ImageType::Color3D:
case AmdGpu::ImageType::Cube:
return ir.CompositeConstruct(read(0), read(8), read(16));
default:
UNREACHABLE();
}
}();
const IR::F32 bias = inst_info.has_bias ? get_addr_reg(addr_reg++) : IR::F32{};
const IR::F32 dref = inst_info.is_depth ? get_addr_reg(addr_reg++) : IR::F32{};
const auto [derivatives_dx, derivatives_dy] = [&] -> std::pair<IR::Value, IR::Value> {
if (!inst_info.has_derivatives) {
return {};
}
switch (image.GetType()) {
case AmdGpu::ImageType::Color1D:
case AmdGpu::ImageType::Color1DArray:
// du/dx, du/dy
addr_reg = addr_reg + 2;
return {get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1)};
case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DArray:
case AmdGpu::ImageType::Color2DMsaa:
// (du/dx, dv/dx), (du/dy, dv/dy)
addr_reg = addr_reg + 4;
return {ir.CompositeConstruct(get_addr_reg(addr_reg - 4), get_addr_reg(addr_reg - 3)),
ir.CompositeConstruct(get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1))};
case AmdGpu::ImageType::Color3D:
case AmdGpu::ImageType::Cube:
// (du/dx, dv/dx, dw/dx), (du/dy, dv/dy, dw/dy)
addr_reg = addr_reg + 6;
return {ir.CompositeConstruct(get_addr_reg(addr_reg - 6), get_addr_reg(addr_reg - 5),
get_addr_reg(addr_reg - 4)),
ir.CompositeConstruct(get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2),
get_addr_reg(addr_reg - 1))};
default:
UNREACHABLE();
}
}();
// Now we can load body components as noted in Table 8.9 Image Opcodes with Sampler
const IR::Value coords = [&] -> IR::Value {
switch (image.GetType()) {
case AmdGpu::ImageType::Color1D: // x
addr_reg = addr_reg + 1;
return get_addr_reg(addr_reg - 1);
case AmdGpu::ImageType::Color1DArray: // x, slice
[[fallthrough]];
case AmdGpu::ImageType::Color2D: // x, y
addr_reg = addr_reg + 2;
return ir.CompositeConstruct(get_addr_reg(addr_reg - 2), get_addr_reg(addr_reg - 1));
case AmdGpu::ImageType::Color2DArray: // x, y, slice
[[fallthrough]];
case AmdGpu::ImageType::Color2DMsaa: // x, y, frag
[[fallthrough]];
case AmdGpu::ImageType::Color3D: // x, y, z
addr_reg = addr_reg + 3;
return ir.CompositeConstruct(get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2),
get_addr_reg(addr_reg - 1));
case AmdGpu::ImageType::Cube: // x, y, face
addr_reg = addr_reg + 3;
return PatchCubeCoord(ir, get_addr_reg(addr_reg - 3), get_addr_reg(addr_reg - 2),
get_addr_reg(addr_reg - 1), false, inst_info.is_array);
default:
UNREACHABLE();
}
}();
ASSERT(!inst_info.has_lod || !inst_info.has_lod_clamp);
const bool explicit_lod = inst_info.has_lod || inst_info.force_level0;
const IR::F32 lod = inst_info.has_lod ? get_addr_reg(addr_reg++)
: inst_info.force_level0 ? ir.Imm32(0.0f)
: IR::F32{};
const IR::F32 lod_clamp = inst_info.has_lod_clamp ? get_addr_reg(addr_reg++) : IR::F32{};
const auto new_inst = [&] -> IR::Value {
if (inst_info.is_gather) {
if (inst_info.is_depth) {
return ir.ImageGatherDref(handle, coords, offset, dref, inst_info);
}
return ir.ImageGather(handle, coords, offset, inst_info);
}
if (inst_info.has_derivatives) {
return ir.ImageGradient(handle, coords, derivatives_dx, derivatives_dy, offset,
lod_clamp, inst_info);
}
if (inst_info.is_depth) {
if (explicit_lod) {
return ir.ImageSampleDrefExplicitLod(handle, coords, dref, lod, offset, inst_info);
}
return ir.ImageSampleDrefImplicitLod(handle, coords, dref, bias, offset, inst_info);
}
if (explicit_lod) {
return ir.ImageSampleExplicitLod(handle, coords, lod, offset, inst_info);
}
return ir.ImageSampleImplicitLod(handle, coords, bias, offset, inst_info);
}();
inst.ReplaceUsesWith(new_inst);
}
void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) { void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descriptors& descriptors) {
const auto pred = [](const IR::Inst* inst) -> std::optional<const IR::Inst*> { const auto pred = [](const IR::Inst* inst) -> std::optional<const IR::Inst*> {
const auto opcode = inst->GetOpcode(); const auto opcode = inst->GetOpcode();
@ -498,40 +655,18 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
.sgpr_base = tsharp.sgpr_base, .sgpr_base = tsharp.sgpr_base,
.dword_offset = tsharp.dword_offset, .dword_offset = tsharp.dword_offset,
.type = type, .type = type,
.nfmt = static_cast<AmdGpu::NumberFormat>(image.GetNumberFmt()), .nfmt = image.GetNumberFmt(),
.is_storage = is_storage, .is_storage = is_storage,
.is_depth = bool(inst_info.is_depth), .is_depth = bool(inst_info.is_depth),
.is_atomic = IsImageAtomicInstruction(inst), .is_atomic = IsImageAtomicInstruction(inst),
.is_array = bool(inst_info.is_array), .is_array = bool(inst_info.is_array),
}); });
// Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions // Sample instructions must be resolved into a new instruction using address register data.
const u32 sampler_binding = [&] { if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) {
if (!has_sampler) { PatchImageSampleInstruction(block, inst, info, descriptors, producer, image_binding, image);
return 0U; return;
} }
const IR::Value& handle = producer->Arg(1);
// Inline sampler resource.
if (handle.IsImmediate()) {
LOG_WARNING(Render_Vulkan, "Inline sampler detected");
return descriptors.Add(SamplerResource{
.sgpr_base = std::numeric_limits<u32>::max(),
.dword_offset = 0,
.inline_sampler = AmdGpu::Sampler{.raw0 = handle.U32()},
});
}
// Normal sampler resource.
const auto ssharp_handle = handle.InstRecursive();
const auto& [ssharp_ud, disable_aniso] = TryDisableAnisoLod0(ssharp_handle);
const auto ssharp = TrackSharp(ssharp_ud);
return descriptors.Add(SamplerResource{
.sgpr_base = ssharp.sgpr_base,
.dword_offset = ssharp.dword_offset,
.associated_image = image_binding,
.disable_aniso = disable_aniso,
});
}();
image_binding |= (sampler_binding << 16);
// Patch image handle // Patch image handle
IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)}; IR::IREmitter ir{block, IR::Block::InstructionList::s_iterator_to(inst)};
@ -568,62 +703,9 @@ void PatchImageInstruction(IR::Block& block, IR::Inst& inst, Info& info, Descrip
}(); }();
inst.SetArg(1, coords); inst.SetArg(1, coords);
if (inst_info.has_offset) { if (inst_info.has_lod) {
// The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch);
const u32 arg_pos = ImageOffsetArgumentPosition(inst); inst.SetArg(3, arg);
const IR::Value arg = inst.Arg(arg_pos);
ASSERT_MSG(arg.Type() == IR::Type::U32, "Unexpected offset type");
const auto read = [&](u32 offset) -> IR::U32 {
if (arg.IsImmediate()) {
const u16 comp = (arg.U32() >> offset) & 0x3F;
return ir.Imm32(s32(comp << 26) >> 26);
}
return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(offset), ir.Imm32(6), true);
};
switch (image.GetType()) {
case AmdGpu::ImageType::Color1D:
case AmdGpu::ImageType::Color1DArray:
inst.SetArg(arg_pos, read(0));
break;
case AmdGpu::ImageType::Color2D:
case AmdGpu::ImageType::Color2DArray:
inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8)));
break;
case AmdGpu::ImageType::Color3D:
inst.SetArg(arg_pos, ir.CompositeConstruct(read(0), read(8), read(16)));
break;
default:
UNREACHABLE();
}
}
if (inst_info.has_derivatives) {
ASSERT_MSG(image.GetType() == AmdGpu::ImageType::Color2D ||
image.GetType() == AmdGpu::ImageType::Color2DArray,
"User derivatives only supported for 2D images");
}
if (inst_info.has_lod_clamp) {
const u32 arg_pos = [&]() -> u32 {
switch (inst.GetOpcode()) {
case IR::Opcode::ImageSampleImplicitLod:
return 2;
case IR::Opcode::ImageSampleDrefImplicitLod:
return 3;
default:
break;
}
return inst_info.is_depth ? 5 : 4;
}();
inst.SetArg(arg_pos, arg);
}
if (inst_info.explicit_lod) {
ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch ||
inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod ||
inst.GetOpcode() == IR::Opcode::ImageSampleDrefExplicitLod);
const u32 pos = inst.GetOpcode() == IR::Opcode::ImageSampleExplicitLod ? 2 : 3;
const IR::Value value = inst_info.force_level0 ? ir.Imm32(0.f) : arg;
inst.SetArg(pos, value);
} }
} }

View File

@ -1,7 +1,7 @@
// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "shader_recompiler/frontend/translate/translate.h" #include "shader_recompiler/ir/ir_emitter.h"
#include "shader_recompiler/ir/opcodes.h" #include "shader_recompiler/ir/opcodes.h"
#include "shader_recompiler/ir/program.h" #include "shader_recompiler/ir/program.h"
#include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/reg.h"
@ -11,6 +11,8 @@ namespace Shader::Optimization {
void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info, void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info,
Stage stage) { Stage stage) {
auto& info = program.info;
const auto& ForEachInstruction = [&](auto func) { const auto& ForEachInstruction = [&](auto func) {
for (IR::Block* block : program.blocks) { for (IR::Block* block : program.blocks) {
for (IR::Inst& inst : block->Instructions()) { for (IR::Inst& inst : block->Instructions()) {
@ -52,6 +54,9 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
break; break;
} }
case Stage::Geometry: { case Stage::Geometry: {
const auto& gs_info = runtime_info.gs_info;
info.gs_copy_data = Shader::ParseCopyShader(gs_info.vs_copy);
ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) { ForEachInstruction([&](IR::IREmitter& ir, IR::Inst& inst) {
const auto opcode = inst.GetOpcode(); const auto opcode = inst.GetOpcode();
switch (opcode) { switch (opcode) {
@ -81,12 +86,12 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim
const auto offset = inst.Flags<IR::BufferInstInfo>().inst_offset.Value(); const auto offset = inst.Flags<IR::BufferInstInfo>().inst_offset.Value();
const auto data = ir.BitCast<IR::F32>(IR::U32{inst.Arg(2)}); const auto data = ir.BitCast<IR::F32>(IR::U32{inst.Arg(2)});
const auto comp_ofs = runtime_info.gs_info.output_vertices * 4u; const auto comp_ofs = gs_info.output_vertices * 4u;
const auto output_size = comp_ofs * runtime_info.gs_info.out_vertex_data_size; const auto output_size = comp_ofs * gs_info.out_vertex_data_size;
const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u; const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u;
const auto& it = runtime_info.gs_info.copy_data.attr_map.find(vc_read_ofs); const auto& it = info.gs_copy_data.attr_map.find(vc_read_ofs);
ASSERT(it != runtime_info.gs_info.copy_data.attr_map.cend()); ASSERT(it != info.gs_copy_data.attr_map.cend());
const auto& [attr, comp] = it->second; const auto& [attr, comp] = it->second;
inst.ReplaceOpcode(IR::Opcode::SetAttribute); inst.ReplaceOpcode(IR::Opcode::SetAttribute);

View File

@ -10,20 +10,6 @@
namespace Shader::IR { namespace Shader::IR {
enum class FpRoundMode : u32 {
NearestEven = 0,
PlusInf = 1,
MinInf = 2,
ToZero = 3,
};
enum class FpDenormMode : u32 {
InOutFlush = 0,
InAllowOutFlush = 1,
InFlushOutAllow = 2,
InOutAllow = 3,
};
enum class FloatClassFunc : u32 { enum class FloatClassFunc : u32 {
SignalingNan = 1 << 0, SignalingNan = 1 << 0,
QuietNan = 1 << 1, QuietNan = 1 << 1,
@ -41,24 +27,18 @@ enum class FloatClassFunc : u32 {
}; };
DECLARE_ENUM_FLAG_OPERATORS(FloatClassFunc) DECLARE_ENUM_FLAG_OPERATORS(FloatClassFunc)
union Mode {
BitField<0, 4, FpRoundMode> fp_round;
BitField<4, 2, FpDenormMode> fp_denorm_single;
BitField<6, 2, FpDenormMode> fp_denorm_double;
BitField<8, 1, u32> dx10_clamp;
};
union TextureInstInfo { union TextureInstInfo {
u32 raw; u32 raw;
BitField<0, 1, u32> is_depth; BitField<0, 1, u32> is_depth;
BitField<1, 1, u32> has_bias; BitField<1, 1, u32> has_bias;
BitField<2, 1, u32> has_lod_clamp; BitField<2, 1, u32> has_lod_clamp;
BitField<3, 1, u32> force_level0; BitField<3, 1, u32> force_level0;
BitField<4, 1, u32> explicit_lod; BitField<4, 1, u32> has_lod;
BitField<5, 1, u32> has_offset; BitField<5, 1, u32> has_offset;
BitField<6, 2, u32> gather_comp; BitField<6, 2, u32> gather_comp;
BitField<8, 1, u32> has_derivatives; BitField<8, 1, u32> has_derivatives;
BitField<9, 1, u32> is_array; BitField<9, 1, u32> is_array;
BitField<10, 1, u32> is_gather;
}; };
union BufferInstInfo { union BufferInstInfo {

View File

@ -209,7 +209,7 @@ private:
union { union {
NonTriviallyDummy dummy{}; NonTriviallyDummy dummy{};
boost::container::small_vector<std::pair<Block*, Value>, 2> phi_args; boost::container::small_vector<std::pair<Block*, Value>, 2> phi_args;
std::array<Value, 5> args; std::array<Value, 6> args;
}; };
}; };
static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased"); static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased");

View File

@ -19,13 +19,8 @@ struct Profile {
bool support_float_controls{}; bool support_float_controls{};
bool support_separate_denorm_behavior{}; bool support_separate_denorm_behavior{};
bool support_separate_rounding_mode{}; bool support_separate_rounding_mode{};
bool support_fp16_denorm_preserve{};
bool support_fp32_denorm_preserve{}; bool support_fp32_denorm_preserve{};
bool support_fp16_denorm_flush{};
bool support_fp32_denorm_flush{}; bool support_fp32_denorm_flush{};
bool support_fp16_signed_zero_nan_preserve{};
bool support_fp32_signed_zero_nan_preserve{};
bool support_fp64_signed_zero_nan_preserve{};
bool support_explicit_workgroup_layout{}; bool support_explicit_workgroup_layout{};
bool has_broken_spirv_clamp{}; bool has_broken_spirv_clamp{};
bool lower_left_origin_mode{}; bool lower_left_origin_mode{};

View File

@ -4,11 +4,9 @@
#pragma once #pragma once
#include <algorithm> #include <algorithm>
#include <span>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
#include "common/assert.h"
#include "common/types.h" #include "common/types.h"
#include "frontend/copy_shader.h"
#include "video_core/amdgpu/types.h" #include "video_core/amdgpu/types.h"
namespace Shader { namespace Shader {
@ -62,7 +60,8 @@ enum class VsOutput : u8 {
using VsOutputMap = std::array<VsOutput, 4>; using VsOutputMap = std::array<VsOutput, 4>;
struct VertexRuntimeInfo { struct VertexRuntimeInfo {
boost::container::static_vector<VsOutputMap, 3> outputs; u32 num_outputs;
std::array<VsOutputMap, 3> outputs;
bool emulate_depth_negative_one_to_one{}; bool emulate_depth_negative_one_to_one{};
bool operator==(const VertexRuntimeInfo& other) const noexcept { bool operator==(const VertexRuntimeInfo& other) const noexcept {
@ -79,13 +78,13 @@ struct GeometryRuntimeInfo {
u32 out_vertex_data_size{}; u32 out_vertex_data_size{};
AmdGpu::PrimitiveType in_primitive; AmdGpu::PrimitiveType in_primitive;
GsOutputPrimTypes out_primitive; GsOutputPrimTypes out_primitive;
CopyShaderData copy_data; std::span<const u32> vs_copy;
u64 vs_copy_hash;
bool operator==(const GeometryRuntimeInfo& other) const noexcept { bool operator==(const GeometryRuntimeInfo& other) const noexcept {
return num_invocations && other.num_invocations && return num_invocations && other.num_invocations &&
output_vertices == other.output_vertices && in_primitive == other.in_primitive && output_vertices == other.output_vertices && in_primitive == other.in_primitive &&
std::ranges::equal(out_primitive, other.out_primitive) && std::ranges::equal(out_primitive, other.out_primitive);
std::ranges::equal(copy_data.attr_map, other.copy_data.attr_map);
} }
}; };
@ -106,7 +105,8 @@ struct FragmentRuntimeInfo {
auto operator<=>(const PsInput&) const noexcept = default; auto operator<=>(const PsInput&) const noexcept = default;
}; };
boost::container::static_vector<PsInput, 32> inputs; u32 num_inputs;
std::array<PsInput, 32> inputs;
struct PsColorBuffer { struct PsColorBuffer {
AmdGpu::NumberFormat num_format; AmdGpu::NumberFormat num_format;
MrtSwizzle mrt_swizzle; MrtSwizzle mrt_swizzle;
@ -117,7 +117,9 @@ struct FragmentRuntimeInfo {
bool operator==(const FragmentRuntimeInfo& other) const noexcept { bool operator==(const FragmentRuntimeInfo& other) const noexcept {
return std::ranges::equal(color_buffers, other.color_buffers) && return std::ranges::equal(color_buffers, other.color_buffers) &&
std::ranges::equal(inputs, other.inputs); num_inputs == other.num_inputs &&
std::ranges::equal(inputs.begin(), inputs.begin() + num_inputs, other.inputs.begin(),
other.inputs.begin() + num_inputs);
} }
}; };
@ -141,13 +143,20 @@ struct RuntimeInfo {
u32 num_user_data; u32 num_user_data;
u32 num_input_vgprs; u32 num_input_vgprs;
u32 num_allocated_vgprs; u32 num_allocated_vgprs;
AmdGpu::FpDenormMode fp_denorm_mode32;
AmdGpu::FpRoundMode fp_round_mode32;
union {
ExportRuntimeInfo es_info; ExportRuntimeInfo es_info;
VertexRuntimeInfo vs_info; VertexRuntimeInfo vs_info;
GeometryRuntimeInfo gs_info; GeometryRuntimeInfo gs_info;
FragmentRuntimeInfo fs_info; FragmentRuntimeInfo fs_info;
ComputeRuntimeInfo cs_info; ComputeRuntimeInfo cs_info;
};
RuntimeInfo(Stage stage_) : stage{stage_} {} RuntimeInfo(Stage stage_) {
memset(this, 0, sizeof(*this));
stage = stage_;
}
bool operator==(const RuntimeInfo& other) const noexcept { bool operator==(const RuntimeInfo& other) const noexcept {
switch (stage) { switch (stage) {

View File

@ -226,6 +226,17 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span<const u32> dcb, std::span<c
} }
break; break;
} }
case PM4CmdNop::PayloadType::DebugColorMarkerPush: {
const auto marker_sz = nop->header.count.Value() * 2;
const std::string_view label{reinterpret_cast<const char*>(&nop->data_block[1]),
marker_sz};
const u32 color = *reinterpret_cast<const u32*>(
reinterpret_cast<const u8*>(&nop->data_block[1]) + marker_sz);
if (rasterizer) {
rasterizer->ScopedMarkerInsertColor(label, color);
}
break;
}
case PM4CmdNop::PayloadType::DebugMarkerPop: { case PM4CmdNop::PayloadType::DebugMarkerPop: {
if (rasterizer) { if (rasterizer) {
rasterizer->ScopeMarkerEnd(); rasterizer->ScopeMarkerEnd();

View File

@ -92,6 +92,12 @@ struct Liverpool {
union { union {
BitField<0, 6, u64> num_vgprs; BitField<0, 6, u64> num_vgprs;
BitField<6, 4, u64> num_sgprs; BitField<6, 4, u64> num_sgprs;
BitField<10, 2, u64> priority;
BitField<12, 2, FpRoundMode> fp_round_mode32;
BitField<14, 2, FpRoundMode> fp_round_mode64;
BitField<16, 2, FpDenormMode> fp_denorm_mode32;
BitField<18, 2, FpDenormMode> fp_denorm_mode64;
BitField<12, 8, u64> float_mode;
BitField<24, 2, u64> vgpr_comp_cnt; // SPI provided per-thread inputs BitField<24, 2, u64> vgpr_comp_cnt; // SPI provided per-thread inputs
BitField<33, 5, u64> num_user_regs; BitField<33, 5, u64> num_user_regs;
} settings; } settings;

View File

@ -7,6 +7,20 @@
namespace AmdGpu { namespace AmdGpu {
enum class FpRoundMode : u32 {
NearestEven = 0,
PlusInf = 1,
MinInf = 2,
ToZero = 3,
};
enum class FpDenormMode : u32 {
InOutFlush = 0,
InAllowOutFlush = 1,
InFlushOutAllow = 2,
InOutAllow = 3,
};
// See `VGT_PRIMITIVE_TYPE` description in [Radeon Sea Islands 3D/Compute Register Reference Guide] // See `VGT_PRIMITIVE_TYPE` description in [Radeon Sea Islands 3D/Compute Register Reference Guide]
enum class PrimitiveType : u32 { enum class PrimitiveType : u32 {
None = 0, None = 0,

View File

@ -446,6 +446,8 @@ Frame* RendererVulkan::GetRenderFrame() {
// Wait for the presentation to be finished so all frame resources are free // Wait for the presentation to be finished so all frame resources are free
while (wait() != vk::Result::eSuccess) { while (wait() != vk::Result::eSuccess) {
ASSERT_MSG(result != vk::Result::eErrorDeviceLost,
"Device lost during waiting for a frame");
// Retry if the waiting times out // Retry if the waiting times out
if (result == vk::Result::eTimeout) { if (result == vk::Result::eTimeout) {
continue; continue;

View File

@ -217,9 +217,10 @@ bool Instance::CreateDevice() {
const vk::StructureChain properties_chain = physical_device.getProperties2< const vk::StructureChain properties_chain = physical_device.getProperties2<
vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR,
vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties, vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties,
vk::PhysicalDevicePushDescriptorPropertiesKHR>(); vk::PhysicalDevicePushDescriptorPropertiesKHR, vk::PhysicalDeviceVulkan12Properties>();
subgroup_size = properties_chain.get<vk::PhysicalDeviceVulkan11Properties>().subgroupSize; subgroup_size = properties_chain.get<vk::PhysicalDeviceVulkan11Properties>().subgroupSize;
push_descriptor_props = properties_chain.get<vk::PhysicalDevicePushDescriptorPropertiesKHR>(); push_descriptor_props = properties_chain.get<vk::PhysicalDevicePushDescriptorPropertiesKHR>();
vk12_props = properties_chain.get<vk::PhysicalDeviceVulkan12Properties>();
LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size);
features = feature_chain.get().features; features = feature_chain.get().features;

View File

@ -242,6 +242,11 @@ public:
return push_descriptor_props.maxPushDescriptors; return push_descriptor_props.maxPushDescriptors;
} }
/// Returns the vulkan 1.2 physical device properties.
const vk::PhysicalDeviceVulkan12Properties& GetVk12Properties() const noexcept {
return vk12_props;
}
/// Returns true if shaders can declare the ClipDistance attribute /// Returns true if shaders can declare the ClipDistance attribute
bool IsShaderClipDistanceSupported() const { bool IsShaderClipDistanceSupported() const {
return features.shaderClipDistance; return features.shaderClipDistance;
@ -279,6 +284,7 @@ private:
vk::UniqueDevice device; vk::UniqueDevice device;
vk::PhysicalDeviceProperties properties; vk::PhysicalDeviceProperties properties;
vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props;
vk::PhysicalDeviceVulkan12Properties vk12_props;
vk::PhysicalDeviceFeatures features; vk::PhysicalDeviceFeatures features;
vk::DriverIdKHR driver_id; vk::DriverIdKHR driver_id;
vk::UniqueDebugUtilsMessengerEXT debug_callback{}; vk::UniqueDebugUtilsMessengerEXT debug_callback{};

View File

@ -7,7 +7,6 @@
#include "common/io_file.h" #include "common/io_file.h"
#include "common/path_util.h" #include "common/path_util.h"
#include "shader_recompiler/backend/spirv/emit_spirv.h" #include "shader_recompiler/backend/spirv/emit_spirv.h"
#include "shader_recompiler/frontend/copy_shader.h"
#include "shader_recompiler/info.h" #include "shader_recompiler/info.h"
#include "shader_recompiler/recompiler.h" #include "shader_recompiler/recompiler.h"
#include "shader_recompiler/runtime_info.h" #include "shader_recompiler/runtime_info.h"
@ -41,7 +40,7 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info,
const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) { const auto add_output = [&](VsOutput x, VsOutput y, VsOutput z, VsOutput w) {
if (x != VsOutput::None || y != VsOutput::None || z != VsOutput::None || if (x != VsOutput::None || y != VsOutput::None || z != VsOutput::None ||
w != VsOutput::None) { w != VsOutput::None) {
info.outputs.emplace_back(Shader::VsOutputMap{x, y, z, w}); info.outputs[info.num_outputs++] = Shader::VsOutputMap{x, y, z, w};
} }
}; };
// VS_OUT_MISC_VEC // VS_OUT_MISC_VEC
@ -84,18 +83,21 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info,
Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
auto info = Shader::RuntimeInfo{stage}; auto info = Shader::RuntimeInfo{stage};
const auto& regs = liverpool->regs; const auto& regs = liverpool->regs;
const auto BuildCommon = [&](const auto& program) {
info.num_user_data = program.settings.num_user_regs;
info.num_input_vgprs = program.settings.vgpr_comp_cnt;
info.num_allocated_vgprs = program.settings.num_vgprs * 4;
info.fp_denorm_mode32 = program.settings.fp_denorm_mode32;
info.fp_round_mode32 = program.settings.fp_round_mode32;
};
switch (stage) { switch (stage) {
case Shader::Stage::Export: { case Shader::Stage::Export: {
info.num_user_data = regs.es_program.settings.num_user_regs; BuildCommon(regs.es_program);
info.num_input_vgprs = regs.es_program.settings.vgpr_comp_cnt;
info.num_allocated_vgprs = regs.es_program.settings.num_vgprs * 4;
info.es_info.vertex_data_size = regs.vgt_esgs_ring_itemsize; info.es_info.vertex_data_size = regs.vgt_esgs_ring_itemsize;
break; break;
} }
case Shader::Stage::Vertex: { case Shader::Stage::Vertex: {
info.num_user_data = regs.vs_program.settings.num_user_regs; BuildCommon(regs.vs_program);
info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt;
info.num_allocated_vgprs = regs.vs_program.settings.num_vgprs * 4;
GatherVertexOutputs(info.vs_info, regs.vs_output_control); GatherVertexOutputs(info.vs_info, regs.vs_output_control);
info.vs_info.emulate_depth_negative_one_to_one = info.vs_info.emulate_depth_negative_one_to_one =
!instance.IsDepthClipControlSupported() && !instance.IsDepthClipControlSupported() &&
@ -103,39 +105,35 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) {
break; break;
} }
case Shader::Stage::Geometry: { case Shader::Stage::Geometry: {
info.num_user_data = regs.gs_program.settings.num_user_regs; BuildCommon(regs.gs_program);
info.num_input_vgprs = regs.gs_program.settings.vgpr_comp_cnt; auto& gs_info = info.gs_info;
info.num_allocated_vgprs = regs.gs_program.settings.num_vgprs * 4; gs_info.output_vertices = regs.vgt_gs_max_vert_out;
info.gs_info.output_vertices = regs.vgt_gs_max_vert_out; gs_info.num_invocations =
info.gs_info.num_invocations =
regs.vgt_gs_instance_cnt.IsEnabled() ? regs.vgt_gs_instance_cnt.count : 1; regs.vgt_gs_instance_cnt.IsEnabled() ? regs.vgt_gs_instance_cnt.count : 1;
info.gs_info.in_primitive = regs.primitive_type; gs_info.in_primitive = regs.primitive_type;
for (u32 stream_id = 0; stream_id < Shader::GsMaxOutputStreams; ++stream_id) { for (u32 stream_id = 0; stream_id < Shader::GsMaxOutputStreams; ++stream_id) {
info.gs_info.out_primitive[stream_id] = gs_info.out_primitive[stream_id] =
regs.vgt_gs_out_prim_type.GetPrimitiveType(stream_id); regs.vgt_gs_out_prim_type.GetPrimitiveType(stream_id);
} }
info.gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize;
info.gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0];
const auto params_vc = Liverpool::GetParams(regs.vs_program);
// Extract semantics offsets from a copy shader gs_info.vs_copy = params_vc.code;
const auto vc_stage = Shader::Stage::Vertex; gs_info.vs_copy_hash = params_vc.hash;
const auto* pgm_vc = regs.ProgramForStage(static_cast<u32>(vc_stage)); DumpShader(gs_info.vs_copy, gs_info.vs_copy_hash, Shader::Stage::Vertex, 0, "copy.bin");
const auto params_vc = Liverpool::GetParams(*pgm_vc);
DumpShader(params_vc.code, params_vc.hash, Shader::Stage::Vertex, 0, "copy.bin");
info.gs_info.copy_data = Shader::ParseCopyShader(params_vc.code);
break; break;
} }
case Shader::Stage::Fragment: { case Shader::Stage::Fragment: {
info.num_user_data = regs.ps_program.settings.num_user_regs; BuildCommon(regs.ps_program);
info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4;
const auto& ps_inputs = regs.ps_inputs; const auto& ps_inputs = regs.ps_inputs;
info.fs_info.num_inputs = regs.num_interp;
for (u32 i = 0; i < regs.num_interp; i++) { for (u32 i = 0; i < regs.num_interp; i++) {
info.fs_info.inputs.push_back({ info.fs_info.inputs[i] = {
.param_index = u8(ps_inputs[i].input_offset.Value()), .param_index = u8(ps_inputs[i].input_offset.Value()),
.is_default = bool(ps_inputs[i].use_default), .is_default = bool(ps_inputs[i].use_default),
.is_flat = bool(ps_inputs[i].flat_shade), .is_flat = bool(ps_inputs[i].flat_shade),
.default_value = u8(ps_inputs[i].default_value), .default_value = u8(ps_inputs[i].default_value),
}); };
} }
for (u32 i = 0; i < Shader::MaxColorBuffers; i++) { for (u32 i = 0; i < Shader::MaxColorBuffers; i++) {
info.fs_info.color_buffers[i] = { info.fs_info.color_buffers[i] = {
@ -166,9 +164,12 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
AmdGpu::Liverpool* liverpool_) AmdGpu::Liverpool* liverpool_)
: instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_},
desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} {
const auto& vk12_props = instance.GetVk12Properties();
profile = Shader::Profile{ profile = Shader::Profile{
.supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U,
.subgroup_size = instance.SubgroupSize(), .subgroup_size = instance.SubgroupSize(),
.support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32),
.support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32),
.support_explicit_workgroup_layout = true, .support_explicit_workgroup_layout = true,
}; };
auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({}); auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({});

View File

@ -459,4 +459,17 @@ void Rasterizer::ScopedMarkerInsert(const std::string_view& str) {
}); });
} }
void Rasterizer::ScopedMarkerInsertColor(const std::string_view& str, const u32 color) {
if (Config::nullGpu() || !Config::vkMarkersEnabled()) {
return;
}
const auto cmdbuf = scheduler.CommandBuffer();
cmdbuf.insertDebugUtilsLabelEXT(vk::DebugUtilsLabelEXT{
.pLabelName = str.data(),
.color = std::array<f32, 4>(
{(f32)((color >> 16) & 0xff) / 255.0f, (f32)((color >> 8) & 0xff) / 255.0f,
(f32)(color & 0xff) / 255.0f, (f32)((color >> 24) & 0xff) / 255.0f})});
}
} // namespace Vulkan } // namespace Vulkan

View File

@ -40,6 +40,7 @@ public:
void ScopeMarkerBegin(const std::string_view& str); void ScopeMarkerBegin(const std::string_view& str);
void ScopeMarkerEnd(); void ScopeMarkerEnd();
void ScopedMarkerInsert(const std::string_view& str); void ScopedMarkerInsert(const std::string_view& str);
void ScopedMarkerInsertColor(const std::string_view& str, const u32 color);
void InlineDataToGds(u32 gds_offset, u32 value); void InlineDataToGds(u32 gds_offset, u32 value);
u32 ReadDataFromGds(u32 gsd_offset); u32 ReadDataFromGds(u32 gsd_offset);