From c9f894c45a80645bc50f722e37e47a949c85f348 Mon Sep 17 00:00:00 2001 From: psucien Date: Wed, 9 Oct 2024 20:44:38 +0200 Subject: [PATCH 01/22] hot-fix: catch device loss on presentation (prevents deadlock in waiting) --- src/video_core/renderer_vulkan/renderer_vulkan.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index 97e5185e5..64a483654 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -446,6 +446,8 @@ Frame* RendererVulkan::GetRenderFrame() { // Wait for the presentation to be finished so all frame resources are free while (wait() != vk::Result::eSuccess) { + ASSERT_MSG(result != vk::Result::eErrorDeviceLost, + "Device lost during waiting for a frame"); // Retry if the waiting times out if (result == vk::Result::eTimeout) { continue; From 56e8ed7833553466398d077637dc88d19f65d080 Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Thu, 10 Oct 2024 02:28:59 -0500 Subject: [PATCH 02/22] Multiple Install Folders (#1308) * multiple install folders implimentation * clang format * paths setting tab * clang format --- CMakeLists.txt | 2 + src/common/config.cpp | 33 ++++++++++--- src/common/config.h | 4 +- src/qt_gui/game_info.cpp | 16 ++++--- src/qt_gui/game_install_dialog.cpp | 8 +++- src/qt_gui/install_dir_select.cpp | 76 ++++++++++++++++++++++++++++++ src/qt_gui/install_dir_select.h | 31 ++++++++++++ src/qt_gui/main.cpp | 2 +- src/qt_gui/main_window.cpp | 8 +++- src/qt_gui/settings_dialog.cpp | 49 +++++++++++++++++++ src/qt_gui/settings_dialog.ui | 70 +++++++++++++++++++++++++++ 11 files changed, 278 insertions(+), 21 deletions(-) create mode 100644 src/qt_gui/install_dir_select.cpp create mode 100644 src/qt_gui/install_dir_select.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2db263b3a..eeca274ef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -693,6 +693,8 @@ set(QT_GUI src/qt_gui/about_dialog.cpp src/qt_gui/game_grid_frame.h src/qt_gui/game_install_dialog.cpp 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.h src/qt_gui/trophy_viewer.cpp diff --git a/src/common/config.cpp b/src/common/config.cpp index 40be5ebec..55cf29428 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -62,7 +62,7 @@ static s16 cursorState = HideCursorState::Idle; static int cursorHideTimeout = 5; // 5 seconds (default) // Gui -std::filesystem::path settings_install_dir = {}; +std::vector settings_install_dirs = {}; std::filesystem::path settings_addon_install_dir = {}; u32 main_window_geometry_x = 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_h = h; } -void setGameInstallDir(const std::filesystem::path& dir) { - settings_install_dir = dir; +void setGameInstallDirs(const std::vector& dir) { + settings_install_dirs.resize(dir.size()); + settings_install_dirs = dir; } void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; @@ -384,8 +385,8 @@ u32 getMainWindowGeometryW() { u32 getMainWindowGeometryH() { return main_window_geometry_h; } -std::filesystem::path getGameInstallDir() { - return settings_install_dir; +std::vector getGameInstallDirs() { + return settings_install_dirs; } std::filesystem::path getAddonInstallDir() { if (settings_addon_install_dir.empty()) { @@ -523,7 +524,19 @@ void load(const std::filesystem::path& path) { mw_themes = toml::find_or(gui, "theme", 0); m_window_size_W = toml::find_or(gui, "mw_width", 0); m_window_size_H = toml::find_or(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>(gui, "installDirs", {}); + for (const auto& dir : install_dir_array) { + settings_install_dirs.emplace_back(std::filesystem::path{dir}); + } + settings_addon_install_dir = toml::find_fs_path_or(gui, "addonInstallDir", {}); main_window_geometry_x = toml::find_or(gui, "geometry_x", 0); main_window_geometry_y = toml::find_or(gui, "geometry_y", 0); @@ -601,7 +614,13 @@ void save(const std::filesystem::path& path) { data["GUI"]["gameTableMode"] = m_table_mode; data["GUI"]["mw_width"] = m_window_size_W; data["GUI"]["mw_height"] = m_window_size_H; - data["GUI"]["installDir"] = std::string{fmt::UTF(settings_install_dir.u8string()).data}; + + std::vector 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"] = std::string{fmt::UTF(settings_addon_install_dir.u8string()).data}; data["GUI"]["geometry_x"] = main_window_geometry_x; diff --git a/src/common/config.h b/src/common/config.h index 90ebdb58a..e76f389c2 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -85,7 +85,7 @@ bool vkCrashDiagnosticEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); -void setGameInstallDir(const std::filesystem::path& dir); +void setGameInstallDirs(const std::vector& dir); void setAddonInstallDir(const std::filesystem::path& dir); void setMainWindowTheme(u32 theme); void setIconSize(u32 size); @@ -104,7 +104,7 @@ u32 getMainWindowGeometryX(); u32 getMainWindowGeometryY(); u32 getMainWindowGeometryW(); u32 getMainWindowGeometryH(); -std::filesystem::path getGameInstallDir(); +std::vector getGameInstallDirs(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); u32 getIconSize(); diff --git a/src/qt_gui/game_info.cpp b/src/qt_gui/game_info.cpp index 6e8d89713..d82f43f20 100644 --- a/src/qt_gui/game_info.cpp +++ b/src/qt_gui/game_info.cpp @@ -10,14 +10,16 @@ GameInfoClass::GameInfoClass() = default; GameInfoClass::~GameInfoClass() = default; void GameInfoClass::GetGameInfo(QWidget* parent) { - QString installDir; - Common::FS::PathToQString(installDir, Config::getGameInstallDir()); QStringList filePaths; - QDir parentFolder(installDir); - QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); - for (const auto& fileInfo : fileList) { - if (fileInfo.isDir()) { - filePaths.append(fileInfo.absoluteFilePath()); + for (const auto& installLoc : Config::getGameInstallDirs()) { + QString installDir; + Common::FS::PathToQString(installDir, installLoc); + QDir parentFolder(installDir); + QFileInfoList fileList = parentFolder.entryInfoList(QDir::Dirs | QDir::NoDotAndDotDot); + for (const auto& fileInfo : fileList) { + if (fileInfo.isDir()) { + filePaths.append(fileInfo.absoluteFilePath()); + } } } m_games = QtConcurrent::mapped(filePaths, [&](const QString& path) { diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp index 11daf2de0..4418d9a48 100644 --- a/src/qt_gui/game_install_dialog.cpp +++ b/src/qt_gui/game_install_dialog.cpp @@ -51,7 +51,9 @@ QWidget* GameInstallDialog::SetupGamesDirectory() { // Input. m_gamesDirectory = new QLineEdit(); 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->setMinimumWidth(400); @@ -125,7 +127,9 @@ void GameInstallDialog::Save() { } } - Config::setGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); + std::vector install_dirs; + install_dirs.emplace_back(Common::FS::PathFromQString(gamesDirectory)); + Config::setGameInstallDirs(install_dirs); Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory)); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::save(config_dir / "config.toml"); diff --git a/src/qt_gui/install_dir_select.cpp b/src/qt_gui/install_dir_select.cpp new file mode 100644 index 000000000..e0951b123 --- /dev/null +++ b/src/qt_gui/install_dir_select.cpp @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 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; +} diff --git a/src/qt_gui/install_dir_select.h b/src/qt_gui/install_dir_select.h new file mode 100644 index 000000000..fdadf2fe0 --- /dev/null +++ b/src/qt_gui/install_dir_select.h @@ -0,0 +1,31 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include + +#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; +}; diff --git a/src/qt_gui/main.cpp b/src/qt_gui/main.cpp index 8c565a19b..da8804f69 100644 --- a/src/qt_gui/main.cpp +++ b/src/qt_gui/main.cpp @@ -30,7 +30,7 @@ int main(int argc, char* argv[]) { bool has_command_line_argument = argc > 1; // 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; dlg.exec(); } diff --git a/src/qt_gui/main_window.cpp b/src/qt_gui/main_window.cpp index 2323214e6..025749dd4 100644 --- a/src/qt_gui/main_window.cpp +++ b/src/qt_gui/main_window.cpp @@ -16,6 +16,7 @@ #include "core/file_format/pkg.h" #include "core/loader.h" #include "game_install_dialog.h" +#include "install_dir_select.h" #include "main_window.h" #include "settings_dialog.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)); 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 gameDirPath; Common::FS::PathToQString(gameDirPath, extract_path); @@ -821,7 +825,7 @@ void MainWindow::InstallDragDropPkg(std::filesystem::path file, int pkgNum, int connect(&futureWatcher, &QFutureWatcher::finished, this, [=, this]() { if (pkgNum == nPkg) { QString path; - Common::FS::PathToQString(path, Config::getGameInstallDir()); + Common::FS::PathToQString(path, game_install_dir); QMessageBox extractMsgBox(this); extractMsgBox.setWindowTitle(tr("Extraction Finished")); extractMsgBox.setText( diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index efc438455..a6fe6a265 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -220,6 +220,55 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge [](int val) { Config::setNullGpu(val); }); } + // PATH TAB + { + 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); + } + + ui->removeFolderButton->setEnabled(false); + + connect(ui->addFolderButton, &QPushButton::clicked, this, [this]() { + QString file_path_string = + QFileDialog::getExistingDirectory(this, tr("Directory to install games")); + auto file_path = Common::FS::PathFromQString(file_path_string); + if (!file_path.empty()) { + std::vector install_dirs = Config::getGameInstallDirs(); + 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 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 { connect(ui->debugDump, &QCheckBox::stateChanged, this, diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index 9637c5fec..e1c064d5a 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -918,6 +918,76 @@ + + + Paths + + + + + + + + Game Folders + + + + + 0 + 20 + 401 + 331 + + + + + + + 100 + 360 + 80 + 24 + + + + Add... + + + + + + 210 + 360 + 80 + 24 + + + + Remove + + + + + + + + Qt::Orientation::Horizontal + + + QSizePolicy::Policy::Preferred + + + + 40 + 20 + + + + + + + + Debug From 100036aecfaf49cc00566035740f450f381d7994 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 10 Oct 2024 17:47:39 +0300 Subject: [PATCH 03/22] spirv: Flush denormals if possible (#1302) --- .../backend/spirv/emit_spirv.cpp | 50 ++++++++++------ .../backend/spirv/spirv_emit_context.cpp | 7 +-- .../frontend/copy_shader.cpp | 2 +- src/shader_recompiler/frontend/copy_shader.h | 2 +- src/shader_recompiler/info.h | 4 +- .../ir/passes/ring_access_elimination.cpp | 15 +++-- src/shader_recompiler/ir/reg.h | 21 ------- src/shader_recompiler/profile.h | 5 -- src/shader_recompiler/runtime_info.h | 34 ++++++----- src/video_core/amdgpu/liverpool.h | 6 ++ src/video_core/amdgpu/types.h | 16 +++++- .../renderer_vulkan/vk_instance.cpp | 3 +- src/video_core/renderer_vulkan/vk_instance.h | 6 ++ .../renderer_vulkan/vk_pipeline_cache.cpp | 57 ++++++++++--------- 14 files changed, 130 insertions(+), 98 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv.cpp b/src/shader_recompiler/backend/spirv/emit_spirv.cpp index f90e9db77..e84908a57 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv.cpp @@ -206,10 +206,7 @@ Id DefineMain(EmitContext& ctx, const IR::Program& program) { return main; } -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{}; +void SetupCapabilities(const Info& info, EmitContext& ctx) { ctx.AddCapability(spv::Capability::Image1D); ctx.AddCapability(spv::Capability::Sampled1D); ctx.AddCapability(spv::Capability::ImageQuery); @@ -247,6 +244,19 @@ void DefineEntryPoint(const IR::Program& program, EmitContext& ctx, Id main) { if (info.uses_group_ballot) { 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) { case Stage::Compute: { const std::array 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); } +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) { auto inst{program.blocks.front()->begin()}; size_t block_index{0}; @@ -314,18 +342,8 @@ std::vector EmitSPIRV(const Profile& profile, const RuntimeInfo& runtime_in EmitContext ctx{profile, runtime_info, program.info, binding}; const Id main{DefineMain(ctx, program)}; DefineEntryPoint(program, ctx, main); - switch (program.info.stage) { - case Stage::Export: - 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; - } + SetupCapabilities(program.info, ctx); + SetupFloatMode(ctx, profile, runtime_info, main); PatchPhiNodes(program, ctx); binding.user_data += program.info.ud_mask.NumRegs(); return ctx.Assemble(); diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index 5eee656dd..6581a7a56 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp @@ -284,7 +284,8 @@ void EmitContext::DefineInputs() { frag_coord = DefineVariable(F32[4], spv::BuiltIn::FragCoord, spv::StorageClass::Input); frag_depth = DefineVariable(F32[1], spv::BuiltIn::FragDepth, spv::StorageClass::Output); 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; ASSERT(semantic < IR::NumParams); 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; 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 id{DefineInput(type, param_id)}; Name(id, fmt::format("in_attr{}", param_id)); @@ -394,8 +394,7 @@ void EmitContext::DefineOutputs() { case Stage::Geometry: { 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++) { - const IR::Attribute param{IR::Attribute::Param0 + attr_id}; + for (u32 attr_id = 0; attr_id < info.gs_copy_data.num_attrs; attr_id++) { const Id id{DefineOutput(F32[4], attr_id)}; Name(id, fmt::format("out_attr{}", attr_id)); output_params[attr_id] = {id, output_f32, F32[1], 4u}; diff --git a/src/shader_recompiler/frontend/copy_shader.cpp b/src/shader_recompiler/frontend/copy_shader.cpp index 363c1c821..b2c795667 100644 --- a/src/shader_recompiler/frontend/copy_shader.cpp +++ b/src/shader_recompiler/frontend/copy_shader.cpp @@ -7,7 +7,7 @@ namespace Shader { -CopyShaderData ParseCopyShader(const std::span& code) { +CopyShaderData ParseCopyShader(std::span code) { Gcn::GcnCodeSlice code_slice{code.data(), code.data() + code.size()}; Gcn::GcnDecodeContext decoder; diff --git a/src/shader_recompiler/frontend/copy_shader.h b/src/shader_recompiler/frontend/copy_shader.h index ca3e1ac3e..55cc31ebd 100644 --- a/src/shader_recompiler/frontend/copy_shader.h +++ b/src/shader_recompiler/frontend/copy_shader.h @@ -16,6 +16,6 @@ struct CopyShaderData { u32 num_attrs{0}; }; -CopyShaderData ParseCopyShader(const std::span& code); +CopyShaderData ParseCopyShader(std::span code); } // namespace Shader diff --git a/src/shader_recompiler/info.h b/src/shader_recompiler/info.h index 78a6805fd..e727c8a08 100644 --- a/src/shader_recompiler/info.h +++ b/src/shader_recompiler/info.h @@ -3,12 +3,12 @@ #pragma once #include -#include #include #include #include "common/assert.h" #include "common/types.h" #include "shader_recompiler/backend/bindings.h" +#include "shader_recompiler/frontend/copy_shader.h" #include "shader_recompiler/ir/attribute.h" #include "shader_recompiler/ir/reg.h" #include "shader_recompiler/ir/type.h" @@ -170,6 +170,8 @@ struct Info { }; UserDataMask ud_mask{}; + CopyShaderData gs_copy_data; + s8 vertex_offset_sgpr = -1; s8 instance_offset_sgpr = -1; diff --git a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp index 857921b1f..eb1be2967 100644 --- a/src/shader_recompiler/ir/passes/ring_access_elimination.cpp +++ b/src/shader_recompiler/ir/passes/ring_access_elimination.cpp @@ -1,7 +1,7 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // 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/program.h" #include "shader_recompiler/ir/reg.h" @@ -11,6 +11,8 @@ namespace Shader::Optimization { void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtime_info, Stage stage) { + auto& info = program.info; + const auto& ForEachInstruction = [&](auto func) { for (IR::Block* block : program.blocks) { for (IR::Inst& inst : block->Instructions()) { @@ -52,6 +54,9 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim break; } 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) { const auto opcode = inst.GetOpcode(); switch (opcode) { @@ -81,12 +86,12 @@ void RingAccessElimination(const IR::Program& program, const RuntimeInfo& runtim const auto offset = inst.Flags().inst_offset.Value(); const auto data = ir.BitCast(IR::U32{inst.Arg(2)}); - const auto comp_ofs = runtime_info.gs_info.output_vertices * 4u; - const auto output_size = comp_ofs * runtime_info.gs_info.out_vertex_data_size; + const auto comp_ofs = gs_info.output_vertices * 4u; + const auto output_size = comp_ofs * gs_info.out_vertex_data_size; const auto vc_read_ofs = (((offset / comp_ofs) * comp_ofs) % output_size) * 16u; - const auto& it = runtime_info.gs_info.copy_data.attr_map.find(vc_read_ofs); - ASSERT(it != runtime_info.gs_info.copy_data.attr_map.cend()); + const auto& it = info.gs_copy_data.attr_map.find(vc_read_ofs); + ASSERT(it != info.gs_copy_data.attr_map.cend()); const auto& [attr, comp] = it->second; inst.ReplaceOpcode(IR::Opcode::SetAttribute); diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index 9ec77e5f0..5facaf5c7 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -10,20 +10,6 @@ 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 { SignalingNan = 1 << 0, QuietNan = 1 << 1, @@ -41,13 +27,6 @@ enum class FloatClassFunc : u32 { }; 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 { u32 raw; BitField<0, 1, u32> is_depth; diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h index badd54554..bbda731e0 100644 --- a/src/shader_recompiler/profile.h +++ b/src/shader_recompiler/profile.h @@ -19,13 +19,8 @@ struct Profile { bool support_float_controls{}; bool support_separate_denorm_behavior{}; bool support_separate_rounding_mode{}; - bool support_fp16_denorm_preserve{}; bool support_fp32_denorm_preserve{}; - bool support_fp16_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 has_broken_spirv_clamp{}; bool lower_left_origin_mode{}; diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 8c0838c96..4d15c2072 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -4,11 +4,9 @@ #pragma once #include +#include #include - -#include "common/assert.h" #include "common/types.h" -#include "frontend/copy_shader.h" #include "video_core/amdgpu/types.h" namespace Shader { @@ -62,7 +60,8 @@ enum class VsOutput : u8 { using VsOutputMap = std::array; struct VertexRuntimeInfo { - boost::container::static_vector outputs; + u32 num_outputs; + std::array outputs; bool emulate_depth_negative_one_to_one{}; bool operator==(const VertexRuntimeInfo& other) const noexcept { @@ -79,13 +78,13 @@ struct GeometryRuntimeInfo { u32 out_vertex_data_size{}; AmdGpu::PrimitiveType in_primitive; GsOutputPrimTypes out_primitive; - CopyShaderData copy_data; + std::span vs_copy; + u64 vs_copy_hash; bool operator==(const GeometryRuntimeInfo& other) const noexcept { return num_invocations && other.num_invocations && output_vertices == other.output_vertices && in_primitive == other.in_primitive && - std::ranges::equal(out_primitive, other.out_primitive) && - std::ranges::equal(copy_data.attr_map, other.copy_data.attr_map); + std::ranges::equal(out_primitive, other.out_primitive); } }; @@ -106,7 +105,8 @@ struct FragmentRuntimeInfo { auto operator<=>(const PsInput&) const noexcept = default; }; - boost::container::static_vector inputs; + u32 num_inputs; + std::array inputs; struct PsColorBuffer { AmdGpu::NumberFormat num_format; MrtSwizzle mrt_swizzle; @@ -117,7 +117,9 @@ struct FragmentRuntimeInfo { bool operator==(const FragmentRuntimeInfo& other) const noexcept { 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,11 +143,15 @@ struct RuntimeInfo { u32 num_user_data; u32 num_input_vgprs; u32 num_allocated_vgprs; - ExportRuntimeInfo es_info; - VertexRuntimeInfo vs_info; - GeometryRuntimeInfo gs_info; - FragmentRuntimeInfo fs_info; - ComputeRuntimeInfo cs_info; + AmdGpu::FpDenormMode fp_denorm_mode32; + AmdGpu::FpRoundMode fp_round_mode32; + union { + ExportRuntimeInfo es_info; + VertexRuntimeInfo vs_info; + GeometryRuntimeInfo gs_info; + FragmentRuntimeInfo fs_info; + ComputeRuntimeInfo cs_info; + }; RuntimeInfo(Stage stage_) : stage{stage_} {} diff --git a/src/video_core/amdgpu/liverpool.h b/src/video_core/amdgpu/liverpool.h index 508420bca..1c994d0a0 100644 --- a/src/video_core/amdgpu/liverpool.h +++ b/src/video_core/amdgpu/liverpool.h @@ -92,6 +92,12 @@ struct Liverpool { union { BitField<0, 6, u64> num_vgprs; 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<33, 5, u64> num_user_regs; } settings; diff --git a/src/video_core/amdgpu/types.h b/src/video_core/amdgpu/types.h index 8cc023a79..6b95ed910 100644 --- a/src/video_core/amdgpu/types.h +++ b/src/video_core/amdgpu/types.h @@ -7,6 +7,20 @@ 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] enum class PrimitiveType : u32 { None = 0, @@ -103,4 +117,4 @@ enum class NumberFormat : u32 { Ubscaled = 13, }; -} // namespace AmdGpu \ No newline at end of file +} // namespace AmdGpu diff --git a/src/video_core/renderer_vulkan/vk_instance.cpp b/src/video_core/renderer_vulkan/vk_instance.cpp index 21941b8e9..dda4e0d9f 100644 --- a/src/video_core/renderer_vulkan/vk_instance.cpp +++ b/src/video_core/renderer_vulkan/vk_instance.cpp @@ -217,9 +217,10 @@ bool Instance::CreateDevice() { const vk::StructureChain properties_chain = physical_device.getProperties2< vk::PhysicalDeviceProperties2, vk::PhysicalDevicePortabilitySubsetPropertiesKHR, vk::PhysicalDeviceExternalMemoryHostPropertiesEXT, vk::PhysicalDeviceVulkan11Properties, - vk::PhysicalDevicePushDescriptorPropertiesKHR>(); + vk::PhysicalDevicePushDescriptorPropertiesKHR, vk::PhysicalDeviceVulkan12Properties>(); subgroup_size = properties_chain.get().subgroupSize; push_descriptor_props = properties_chain.get(); + vk12_props = properties_chain.get(); LOG_INFO(Render_Vulkan, "Physical device subgroup size {}", subgroup_size); features = feature_chain.get().features; diff --git a/src/video_core/renderer_vulkan/vk_instance.h b/src/video_core/renderer_vulkan/vk_instance.h index d77d0c20f..474b86e9a 100644 --- a/src/video_core/renderer_vulkan/vk_instance.h +++ b/src/video_core/renderer_vulkan/vk_instance.h @@ -242,6 +242,11 @@ public: 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 bool IsShaderClipDistanceSupported() const { return features.shaderClipDistance; @@ -279,6 +284,7 @@ private: vk::UniqueDevice device; vk::PhysicalDeviceProperties properties; vk::PhysicalDevicePushDescriptorPropertiesKHR push_descriptor_props; + vk::PhysicalDeviceVulkan12Properties vk12_props; vk::PhysicalDeviceFeatures features; vk::DriverIdKHR driver_id; vk::UniqueDebugUtilsMessengerEXT debug_callback{}; diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 929fa9cc1..a06d82eb3 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -7,7 +7,6 @@ #include "common/io_file.h" #include "common/path_util.h" #include "shader_recompiler/backend/spirv/emit_spirv.h" -#include "shader_recompiler/frontend/copy_shader.h" #include "shader_recompiler/info.h" #include "shader_recompiler/recompiler.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) { if (x != VsOutput::None || y != VsOutput::None || z != 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 @@ -84,18 +83,21 @@ void GatherVertexOutputs(Shader::VertexRuntimeInfo& info, Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { auto info = Shader::RuntimeInfo{stage}; 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) { case Shader::Stage::Export: { - info.num_user_data = regs.es_program.settings.num_user_regs; - info.num_input_vgprs = regs.es_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.es_program.settings.num_vgprs * 4; + BuildCommon(regs.es_program); info.es_info.vertex_data_size = regs.vgt_esgs_ring_itemsize; break; } case Shader::Stage::Vertex: { - info.num_user_data = regs.vs_program.settings.num_user_regs; - info.num_input_vgprs = regs.vs_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.vs_program.settings.num_vgprs * 4; + BuildCommon(regs.vs_program); GatherVertexOutputs(info.vs_info, regs.vs_output_control); info.vs_info.emulate_depth_negative_one_to_one = !instance.IsDepthClipControlSupported() && @@ -103,39 +105,35 @@ Shader::RuntimeInfo PipelineCache::BuildRuntimeInfo(Shader::Stage stage) { break; } case Shader::Stage::Geometry: { - info.num_user_data = regs.gs_program.settings.num_user_regs; - info.num_input_vgprs = regs.gs_program.settings.vgpr_comp_cnt; - info.num_allocated_vgprs = regs.gs_program.settings.num_vgprs * 4; - info.gs_info.output_vertices = regs.vgt_gs_max_vert_out; - info.gs_info.num_invocations = + BuildCommon(regs.gs_program); + auto& gs_info = info.gs_info; + gs_info.output_vertices = regs.vgt_gs_max_vert_out; + gs_info.num_invocations = 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) { - info.gs_info.out_primitive[stream_id] = + gs_info.out_primitive[stream_id] = regs.vgt_gs_out_prim_type.GetPrimitiveType(stream_id); } - info.gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; - info.gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; - - // Extract semantics offsets from a copy shader - const auto vc_stage = Shader::Stage::Vertex; - const auto* pgm_vc = regs.ProgramForStage(static_cast(vc_stage)); - 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); + gs_info.in_vertex_data_size = regs.vgt_esgs_ring_itemsize; + gs_info.out_vertex_data_size = regs.vgt_gs_vert_itemsize[0]; + const auto params_vc = Liverpool::GetParams(regs.vs_program); + gs_info.vs_copy = params_vc.code; + gs_info.vs_copy_hash = params_vc.hash; + DumpShader(gs_info.vs_copy, gs_info.vs_copy_hash, Shader::Stage::Vertex, 0, "copy.bin"); break; } case Shader::Stage::Fragment: { - info.num_user_data = regs.ps_program.settings.num_user_regs; - info.num_allocated_vgprs = regs.ps_program.settings.num_vgprs * 4; + BuildCommon(regs.ps_program); const auto& ps_inputs = regs.ps_inputs; + info.fs_info.num_inputs = regs.num_interp; 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()), .is_default = bool(ps_inputs[i].use_default), .is_flat = bool(ps_inputs[i].flat_shade), .default_value = u8(ps_inputs[i].default_value), - }); + }; } for (u32 i = 0; i < Shader::MaxColorBuffers; i++) { info.fs_info.color_buffers[i] = { @@ -166,9 +164,12 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_, AmdGpu::Liverpool* liverpool_) : instance{instance_}, scheduler{scheduler_}, liverpool{liverpool_}, desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} { + const auto& vk12_props = instance.GetVk12Properties(); profile = Shader::Profile{ .supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U, .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, }; auto [cache_result, cache] = instance.GetDevice().createPipelineCacheUnique({}); From 0a12ba4120bdbb49940a898d866798386eb9e4bc Mon Sep 17 00:00:00 2001 From: "Daniel R." <47796739+polybiusproxy@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:51:23 +0200 Subject: [PATCH 04/22] core/libraries: Initial fiber implementation (#1255) --- CMakeLists.txt | 5 + src/common/logging/filter.cpp | 1 + src/common/logging/types.h | 1 + src/core/libraries/error_codes.h | 10 +- src/core/libraries/fiber/fiber.cpp | 284 +++++++++++++++++++++++++++++ src/core/libraries/fiber/fiber.h | 83 +++++++++ src/core/libraries/libs.cpp | 2 + src/emulator.cpp | 3 +- 8 files changed, 387 insertions(+), 2 deletions(-) create mode 100644 src/core/libraries/fiber/fiber.cpp create mode 100644 src/core/libraries/fiber/fiber.h diff --git a/CMakeLists.txt b/CMakeLists.txt index eeca274ef..8a41e1ac9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,6 +324,10 @@ set(USBD_LIB src/core/libraries/usbd/usbd.cpp 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 src/core/libraries/np_manager/np_manager.h src/core/libraries/np_score/np_score.cpp @@ -464,6 +468,7 @@ set(CORE src/core/aerolib/stubs.cpp ${USBD_LIB} ${MISC_LIBS} ${DIALOGS_LIB} + ${FIBER_LIB} ${DEV_TOOLS} src/core/debug_state.cpp src/core/debug_state.h diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index c3088f926..2ff2a9003 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp @@ -114,6 +114,7 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { SUB(Lib, AvPlayer) \ SUB(Lib, Ngs2) \ SUB(Lib, Audio3d) \ + SUB(Lib, Fiber) \ CLS(Frontend) \ CLS(Render) \ SUB(Render, Vulkan) \ diff --git a/src/common/logging/types.h b/src/common/logging/types.h index 749568da1..54388e590 100644 --- a/src/common/logging/types.h +++ b/src/common/logging/types.h @@ -81,6 +81,7 @@ enum class Class : u8 { Lib_AvPlayer, ///< The LibSceAvPlayer implementation. Lib_Ngs2, ///< The LibSceNgs2 implementation. Lib_Audio3d, ///< The LibSceAudio3d implementation. + Lib_Fiber, ///< The LibSceFiber implementation. Frontend, ///< Emulator UI Render, ///< Video Core Render_Vulkan, ///< Vulkan backend diff --git a/src/core/libraries/error_codes.h b/src/core/libraries/error_codes.h index b9896b6c3..041870ed7 100644 --- a/src/core/libraries/error_codes.h +++ b/src/core/libraries/error_codes.h @@ -498,4 +498,12 @@ constexpr int ORBIS_AVPLAYER_ERROR_INFO_OTHER_ENCRY = 0x806A00BF; // AppContent library constexpr int ORBIS_APP_CONTENT_ERROR_PARAMETER = 0x80D90002; constexpr int ORBIS_APP_CONTENT_ERROR_DRM_NO_ENTITLEMENT = 0x80D90007; -constexpr int ORBIS_APP_CONTENT_ERROR_NOT_FOUND = 0x80D90005; \ No newline at end of file +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; \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber.cpp b/src/core/libraries/fiber/fiber.cpp new file mode 100644 index 000000000..bd1575dda --- /dev/null +++ b/src/core/libraries/fiber/fiber.cpp @@ -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 +#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(param); + u64 argRun = 0; + u64 argRet = 0; + + gCurrentFiber = fiber; + + if (fiber->pArgRun != nullptr) { + argRun = *fiber->pArgRun; + } + + const auto* linker = Common::Singleton::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 \ No newline at end of file diff --git a/src/core/libraries/fiber/fiber.h b/src/core/libraries/fiber/fiber.h new file mode 100644 index 000000000..930409caa --- /dev/null +++ b/src/core/libraries/fiber/fiber.h @@ -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 \ No newline at end of file diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 5b6c17b10..86f9fcf9d 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -11,6 +11,7 @@ #include "core/libraries/dialogs/error_dialog.h" #include "core/libraries/dialogs/ime_dialog.h" #include "core/libraries/disc_map/disc_map.h" +#include "core/libraries/fiber/fiber.h" #include "core/libraries/gnmdriver/gnmdriver.h" #include "core/libraries/kernel/libkernel.h" #include "core/libraries/libc_internal/libc_internal.h" @@ -77,6 +78,7 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ImeDialog::RegisterlibSceImeDialog(sym); Libraries::AvPlayer::RegisterlibSceAvPlayer(sym); Libraries::Audio3d::RegisterlibSceAudio3d(sym); + Libraries::Fiber::RegisterlibSceFiber(sym); } } // namespace Libraries diff --git a/src/emulator.cpp b/src/emulator.cpp index 9f801fb83..67aaa0492 100644 --- a/src/emulator.cpp +++ b/src/emulator.cpp @@ -26,6 +26,7 @@ #include "core/file_format/trp.h" #include "core/file_sys/fs.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/libc_internal/libc_internal.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) { constexpr std::array ModulesToLoad{ {{"libSceNgs2.sprx", &Libraries::Ngs2::RegisterlibSceNgs2}, - {"libSceFiber.sprx", nullptr}, + {"libSceFiber.sprx", &Libraries::Fiber::RegisterlibSceFiber}, {"libSceUlt.sprx", nullptr}, {"libSceJson.sprx", nullptr}, {"libSceJson2.sprx", nullptr}, From 299a29e243bb49378e2264889cc168ce8ea2fef3 Mon Sep 17 00:00:00 2001 From: ElBread3 <92335081+ElBread3@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:52:20 -0500 Subject: [PATCH 05/22] Fix Multiple Install Folders (#1328) * attempt to fix pr * clang format --- src/common/config.cpp | 7 ++++++- src/qt_gui/settings_dialog.cpp | 36 +++++++++++++++++++++++++--------- src/qt_gui/translations/en.ts | 15 ++++++++++++++ 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 55cf29428..eedb69105 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -534,7 +534,12 @@ void load(const std::filesystem::path& path) { const auto install_dir_array = toml::find_or>(gui, "installDirs", {}); for (const auto& dir : install_dir_array) { - settings_install_dirs.emplace_back(std::filesystem::path{dir}); + 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", {}); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index a6fe6a265..cf64660f8 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -222,21 +222,17 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge // PATH TAB { - 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); - } - 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); - if (!file_path.empty()) { - std::vector install_dirs = Config::getGameInstallDirs(); + 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 install_dirs = config_dir; install_dirs.push_back(file_path); Config::setGameInstallDirs(install_dirs); QListWidgetItem* item = new QListWidgetItem(file_path_string); @@ -307,6 +303,12 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->dumpShadersCheckBox->installEventFilter(this); ui->nullGpuCheckBox->installEventFilter(this); + // Paths + ui->gameFoldersGroupBox->installEventFilter(this); + ui->gameFoldersListWidget->installEventFilter(this); + ui->addFolderButton->installEventFilter(this); + ui->removeFolderButton->installEventFilter(this); + // Debug ui->debugDump->installEventFilter(this); ui->vkValidationCheckBox->installEventFilter(this); @@ -357,6 +359,13 @@ void SettingsDialog::LoadValuesFromConfig() { } ui->updateComboBox->setCurrentText(QString::fromStdString(updateChannel)); + for (const auto& dir : Config::getGameInstallDirs()) { + QString path_string; + Common::FS::PathToQString(path_string, dir); + QListWidgetItem* item = new QListWidgetItem(path_string); + ui->gameFoldersListWidget->addItem(item); + } + QString backButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior()); int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); @@ -452,6 +461,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { 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 if (elementName == "debugDump") { text = tr("debugDump"); diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index 8ac683804..efbc48fff 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -1083,6 +1083,21 @@ nullGpuCheckBox Enable Null GPU:\nFor the sake of technical debugging, disables game rendering as if there were no graphics card. + + + gameFoldersBox + Game Folders: The list of folders to check for installed games. + + + + addFolderButton + Add: Add a folder to the list. + + + + removeFolderButton + Remove: Remove a folder from the list. + debugDump From 87f8f3a59e7fa53f76d2bbb42b6e15e5b4c4b192 Mon Sep 17 00:00:00 2001 From: Exhigh Date: Thu, 10 Oct 2024 18:52:39 +0400 Subject: [PATCH 06/22] qt_gui: Organize settings page (#1316) * Wire up translations and descriptions for the cursor settings. * Move controller settings to input tab and rename it to controller (to inline it with how other settings are shown). * Fixed unnecessary double initialization of the back button setting. * Organize statements and functions w/ respect to their tabs and some minor QOL changes for the settings UI in general. --- src/common/config.cpp | 8 +- src/common/config.h | 13 +- src/qt_gui/settings_dialog.cpp | 50 +-- src/qt_gui/settings_dialog.ui | 590 ++++++++++++++++++++++----------- src/qt_gui/translations/en.ts | 80 ++++- 5 files changed, 499 insertions(+), 242 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index eedb69105..37e51c655 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -473,7 +473,6 @@ void load(const std::filesystem::path& path) { } isShowSplash = toml::find_or(general, "showSplash", true); isAutoUpdate = toml::find_or(general, "autoUpdate", false); - backButtonBehavior = toml::find_or(general, "backButtonBehavior", "left"); } if (data.contains("Input")) { @@ -481,6 +480,7 @@ void load(const std::filesystem::path& path) { cursorState = toml::find_or(input, "cursorState", HideCursorState::Idle); cursorHideTimeout = toml::find_or(input, "cursorHideTimeout", 5); + backButtonBehavior = toml::find_or(input, "backButtonBehavior", "left"); useSpecialPad = toml::find_or(input, "useSpecialPad", false); specialPadClass = toml::find_or(input, "specialPadClass", 1); } @@ -594,7 +594,7 @@ void save(const std::filesystem::path& path) { data["General"]["autoUpdate"] = isAutoUpdate; data["Input"]["cursorState"] = cursorState; data["Input"]["cursorHideTimeout"] = cursorHideTimeout; - data["General"]["backButtonBehavior"] = backButtonBehavior; + data["Input"]["backButtonBehavior"] = backButtonBehavior; data["Input"]["useSpecialPad"] = useSpecialPad; data["Input"]["specialPadClass"] = specialPadClass; data["GPU"]["screenWidth"] = screenWidth; @@ -650,8 +650,6 @@ void setDefaultValues() { playBGM = false; BGMvolume = 50; enableDiscordRPC = true; - cursorState = HideCursorState::Idle; - cursorHideTimeout = 5; screenWidth = 1280; screenHeight = 720; logFilter = ""; @@ -662,6 +660,8 @@ void setDefaultValues() { } else { updateChannel = "Nightly"; } + cursorState = HideCursorState::Idle; + cursorHideTimeout = 5; backButtonBehavior = "left"; useSpecialPad = false; specialPadClass = 1; diff --git a/src/common/config.h b/src/common/config.h index e76f389c2..8e799b55d 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -20,15 +20,14 @@ bool getPlayBGM(); int getBGMvolume(); bool getEnableDiscordRPC(); -s16 getCursorState(); -int getCursorHideTimeout(); - std::string getLogFilter(); std::string getLogType(); std::string getUserName(); std::string getUpdateChannel(); -std::string getBackButtonBehavior(); +s16 getCursorState(); +int getCursorHideTimeout(); +std::string getBackButtonBehavior(); bool getUseSpecialPad(); int getSpecialPadClass(); @@ -59,14 +58,14 @@ void setFullscreenMode(bool enable); void setPlayBGM(bool enable); void setBGMvolume(int volume); void setEnableDiscordRPC(bool enable); -void setCursorState(s16 cursorState); -void setCursorHideTimeout(int newcursorHideTimeout); void setLanguage(u32 language); void setNeoMode(bool enable); void setUserName(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 setSpecialPadClass(int type); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index cf64660f8..1cc5a85e4 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -47,8 +47,6 @@ QStringList languageNames = {"Arabic", const QVector 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}; -QStringList hideCursorStates = {"Never", "Idle", "Always"}; - SettingsDialog::SettingsDialog(std::span physical_devices, QWidget* parent) : QDialog(parent), ui(new Ui::SettingsDialog) { ui->setupUi(this); @@ -69,7 +67,14 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge completer->setCaseSensitivity(Qt::CaseInsensitive); 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(); LoadValuesFromConfig(); @@ -102,15 +107,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)->setText(tr("Restore Defaults")); 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, [this]() { ui->buttonBox->button(QDialogButtonBox::Close)->setFocus(); }); @@ -175,14 +171,6 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge rpc->shutdown(); } }); - - connect(ui->backButtonBehaviorComboBox, QOverload::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 @@ -195,6 +183,14 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge connect(ui->idleTimeoutSpinBox, &QSpinBox::valueChanged, this, [](int index) { Config::setCursorHideTimeout(index); }); + + connect(ui->backButtonBehaviorComboBox, QOverload::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 @@ -293,6 +289,11 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge ui->logFilter->installEventFilter(this); ui->updaterGroupBox->installEventFilter(this); ui->GUIgroupBox->installEventFilter(this); + + // Input + ui->cursorGroupBox->installEventFilter(this); + ui->hideCursorGroupBox->installEventFilter(this); + ui->idleTimeoutGroupBox->installEventFilter(this); ui->backButtonBehaviorGroupBox->installEventFilter(this); // Graphics @@ -442,6 +443,15 @@ void SettingsDialog::updateNoteTextEdit(const QString& elementName) { text = tr("updaterGroupBox"); } else if (elementName == "GUIgroupBox") { text = tr("GUIgroupBox"); + } + + // Input + if (elementName == "cursorGroupBox") { + text = tr("cursorGroupBox"); + } else if (elementName == "hideCursorGroupBox") { + text = tr("hideCursorGroupBox"); + } else if (elementName == "idleTimeoutGroupBox") { + text = tr("idleTimeoutGroupBox"); } else if (elementName == "backButtonBehaviorGroupBox") { text = tr("backButtonBehaviorGroupBox"); } diff --git a/src/qt_gui/settings_dialog.ui b/src/qt_gui/settings_dialog.ui index e1c064d5a..9743e51bd 100644 --- a/src/qt_gui/settings_dialog.ui +++ b/src/qt_gui/settings_dialog.ui @@ -274,6 +274,9 @@ + + QLayout::SizeConstraint::SetDefaultConstraint + 0 @@ -286,8 +289,157 @@ 0 - + + + + 0 + 0 + + + + + 275 + 0 + + + + + 16777215 + 16777215 + + + + Update + + + + 5 + + + 1 + + + 11 + + + 11 + + + + + + 0 + 0 + + + + + 0 + 75 + + + + + 16777215 + 16777215 + + + + Update Channel + + + + 7 + + + 11 + + + 11 + + + 11 + + + 11 + + + + + + 0 + 0 + + + + + Release + + + + + Nightly + + + + + + + + + + + + 0 + 0 + + + + + 197 + 28 + + + + + 16777215 + 16777215 + + + + Check for Updates + + + + + + + + 0 + 0 + + + + + 11 + false + + + + Check for Updates at Startup + + + + + + + + + + + + 0 @@ -296,204 +448,141 @@ - 265 + 0 0 - - Update - - - - - 10 - 130 - 261 - 22 - - - - Check for Updates at Startup - - - - - - 12 - 30 - 241 - 65 - - - - Update Channel - - - - - 12 - 30 - 217 - 28 - - - - - Release - - - - - Nightly - - - - - - - - 25 - 100 - 215 - 24 - - - - Check for Updates - - - - - - - - - - - - - 0 - 0 - - GUI Settings - - - - 10 - 30 - 241 - 92 - + + + 1 - - - - - - 0 - 0 - - - - Play title music - - - - - - - - - - - Volume - - - - - - - Set the volume of the background music. - - - 100 - - - 10 - - - 20 - - - 50 - - - Qt::Orientation::Horizontal - - - false - - - false - - - QSlider::TickPosition::NoTicks - - - 10 - - - - - - - - - + + 11 + + + + + 1 + + + 0 + + + + + + 0 + 0 + + + + Play title music + + + + + + + Qt::Orientation::Vertical + + + QSizePolicy::Policy::Fixed + + + + 20 + 2 + + + + + + + + + 0 + 0 + + + + + 16777215 + 16777215 + + + + Volume + + + + + + + Set the volume of the background music. + + + 100 + + + 10 + + + 20 + + + 50 + + + Qt::Orientation::Horizontal + + + false + + + false + + + QSlider::TickPosition::NoTicks + + + 10 + + + + + + + + + + 0 + 61 + + + + + - + - - - - 0 - 0 - + + + Qt::Orientation::Horizontal - - Controller Settings + + + 40 + 20 + - - - - 12 - 30 - 241 - 65 - - - - Back Button Behavior - - - - - 12 - 30 - 217 - 28 - - - - - + @@ -510,18 +599,48 @@ - - + + 7 + + + 0 + + + Cursor + + 0 + + + 11 + + + 11 + + + true + + + + 0 + 0 + + Hide Cursor + + 7 + + + 11 + @@ -533,10 +652,16 @@ true + + + 0 + 0 + + 0 - 85 + 0 @@ -549,19 +674,28 @@ false + + 6 + 70 - 11 + 5 - + + 5 + + + 5 + + true - + 0 0 @@ -620,26 +754,80 @@ - + - - - Qt::Orientation::Horizontal + + + + 0 + 0 + - - - 40 - 20 - + + Controller - + + + 0 + + + 11 + + + 11 + + + + + true + + + + 0 + 0 + + + + + 237 + 0 + + + + Back Button Behavior + + + + 11 + + + + + + + + + + + true + + + + 0 + 0 + + + + + + - + Qt::Orientation::Horizontal diff --git a/src/qt_gui/translations/en.ts b/src/qt_gui/translations/en.ts index efbc48fff..974045de1 100644 --- a/src/qt_gui/translations/en.ts +++ b/src/qt_gui/translations/en.ts @@ -434,6 +434,41 @@ Log Filter Log Filter + + + Input + Input + + + + Cursor + Cursor + + + + Hide Cursor + Hide Cursor + + + + Hide Cursor Idle Timeout + Hide Cursor Idle Timeout + + + + Input + Input + + + + Controller + Controller + + + + Back Button Behavior + Back Button Behavior + Graphics @@ -534,16 +569,6 @@ Volume Volume - - - Controller Settings - Controller Settings - - - - Back Button Behavior - Back Button Behavior - MainWindow @@ -1033,6 +1058,41 @@ GUIgroupBox Play Title Music:\nIf a game supports it, enable playing special music when selecting the game in the GUI. + + + cursorGroupBox + Cursor:\nChange settings related to the cursor. + + + + hideCursorGroupBox + Hide Cursor:\nSet cursor hiding behavior. + + + + idleTimeoutGroupBox + Hide Idle Cursor Timeout:\nThe duration (seconds) after which the cursor that has been idle hides itself. + + + + Never + Never + + + + Idle + Idle + + + + Always + Always + + + + backButtonBehaviorGroupBox + Back Button Behavior:\nAllows setting which part of the touchpad the back button will emulate a touch on. + backButtonBehaviorGroupBox From ab6901ae6a93c80ee864756f3a4f811dd73308c9 Mon Sep 17 00:00:00 2001 From: robyn-dressler Date: Thu, 10 Oct 2024 09:53:18 -0500 Subject: [PATCH 07/22] Using a more standard data directory for linux (#1227) * Using a more standard data directory for linux * Fixing format * Using XDG_DATA_HOME by default --- src/common/path_util.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/common/path_util.cpp b/src/common/path_util.cpp index 27098e2d1..7551d3b05 100644 --- a/src/common/path_util.cpp +++ b/src/common/path_util.cpp @@ -95,6 +95,18 @@ static auto UserPaths = [] { user_dir = 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 const auto user_dir = std::filesystem::current_path() / PORTABLE_DIR; #endif From 0e0de5a2a01b25a00217a7b0a175242713b36e22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Miko=C5=82ajczyk?= Date: Thu, 10 Oct 2024 15:54:07 +0100 Subject: [PATCH 08/22] Stub return value of sceNpCreateRequest (#1209) --- src/core/libraries/np_manager/np_manager.cpp | 9 +++++---- src/core/libraries/np_manager/np_manager.h | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/core/libraries/np_manager/np_manager.cpp b/src/core/libraries/np_manager/np_manager.cpp index 28d28cc93..e1aaee814 100644 --- a/src/core/libraries/np_manager/np_manager.cpp +++ b/src/core/libraries/np_manager/np_manager.cpp @@ -902,12 +902,13 @@ int PS4_SYSV_ABI sceNpCreateAsyncRequest() { } int PS4_SYSV_ABI sceNpCreateRequest() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); - return ORBIS_OK; + LOG_ERROR(Lib_NpManager, "(DUMMY) called"); + static int id = 0; + return ++id; } -int PS4_SYSV_ABI sceNpDeleteRequest() { - LOG_ERROR(Lib_NpManager, "(STUBBED) called"); +int PS4_SYSV_ABI sceNpDeleteRequest(int reqId) { + LOG_ERROR(Lib_NpManager, "(DUMMY) called reqId = {}", reqId); return ORBIS_OK; } diff --git a/src/core/libraries/np_manager/np_manager.h b/src/core/libraries/np_manager/np_manager.h index 43ea49ce4..861d91e39 100644 --- a/src/core/libraries/np_manager/np_manager.h +++ b/src/core/libraries/np_manager/np_manager.h @@ -218,7 +218,7 @@ int PS4_SYSV_ABI sceNpCheckNpReachability(); int PS4_SYSV_ABI sceNpCheckPlus(); int PS4_SYSV_ABI sceNpCreateAsyncRequest(); 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 sceNpGetAccountCountry(); int PS4_SYSV_ABI sceNpGetAccountCountryA(); From 3982ef7188a7fcfa16f5d884ac1a08c703940b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Quang=20Ng=C3=B4?= Date: Thu, 10 Oct 2024 21:54:32 +0700 Subject: [PATCH 09/22] ci: add missing libs to enable Wayland backend for SDL (#1184) --- .github/linux-appimage-qt.sh | 2 ++ .github/workflows/build.yml | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/linux-appimage-qt.sh b/.github/linux-appimage-qt.sh index fe77c678c..06d5cbc11 100755 --- a/.github/linux-appimage-qt.sh +++ b/.github/linux-appimage-qt.sh @@ -9,6 +9,8 @@ fi export Qt6_DIR="/usr/lib/qt6" 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 wget -q https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7950084cd..ee09163fd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -287,7 +287,7 @@ jobs: submodules: recursive - 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 uses: actions/cache@v4 @@ -343,7 +343,7 @@ jobs: submodules: recursive - 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 uses: actions/cache@v4 From 6e986f81335129db728cc9270caeef9e781f4fd7 Mon Sep 17 00:00:00 2001 From: korenkonder Date: Thu, 10 Oct 2024 18:03:12 +0300 Subject: [PATCH 10/22] video_core: Implement sceGnmInsertPushColorMarker (#989) --- src/core/libraries/gnmdriver/gnmdriver.cpp | 24 ++++++++++++++++--- src/core/libraries/gnmdriver/gnmdriver.h | 2 +- src/video_core/amdgpu/liverpool.cpp | 11 +++++++++ .../renderer_vulkan/vk_rasterizer.cpp | 13 ++++++++++ .../renderer_vulkan/vk_rasterizer.h | 1 + 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/core/libraries/gnmdriver/gnmdriver.cpp b/src/core/libraries/gnmdriver/gnmdriver.cpp index 7e2153efa..ce30895ca 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.cpp +++ b/src/core/libraries/gnmdriver/gnmdriver.cpp @@ -1076,9 +1076,27 @@ s32 PS4_SYSV_ABI sceGnmInsertPopMarker(u32* cmdbuf, u32 size) { return -1; } -int PS4_SYSV_ABI sceGnmInsertPushColorMarker() { - LOG_ERROR(Lib_GnmDriver, "(STUBBED) called"); - return ORBIS_OK; +s32 PS4_SYSV_ABI sceGnmInsertPushColorMarker(u32* cmdbuf, u32 size, const char* marker, u32 color) { + 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(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(reinterpret_cast(&nop->data_block[1]) + marker_len + 8) = + color; + std::memset(reinterpret_cast(&nop->data_block[1]) + marker_len + 8 + sizeof(u32), + 0, packet_size * 4 - marker_len - 8 - sizeof(u32)); + return ORBIS_OK; + } + } + return -1; } s32 PS4_SYSV_ABI sceGnmInsertPushMarker(u32* cmdbuf, u32 size, const char* marker) { diff --git a/src/core/libraries/gnmdriver/gnmdriver.h b/src/core/libraries/gnmdriver/gnmdriver.h index 55a70cbf3..33bccf427 100644 --- a/src/core/libraries/gnmdriver/gnmdriver.h +++ b/src/core/libraries/gnmdriver/gnmdriver.h @@ -105,7 +105,7 @@ int PS4_SYSV_ABI sceGnmGpuPaDebugEnter(); int PS4_SYSV_ABI sceGnmGpuPaDebugLeave(); int PS4_SYSV_ABI sceGnmInsertDingDongMarker(); 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); int PS4_SYSV_ABI sceGnmInsertSetColorMarker(); int PS4_SYSV_ABI sceGnmInsertSetMarker(); diff --git a/src/video_core/amdgpu/liverpool.cpp b/src/video_core/amdgpu/liverpool.cpp index 3dce871fe..b3b718836 100644 --- a/src/video_core/amdgpu/liverpool.cpp +++ b/src/video_core/amdgpu/liverpool.cpp @@ -226,6 +226,17 @@ Liverpool::Task Liverpool::ProcessGraphics(std::span dcb, std::spanheader.count.Value() * 2; + const std::string_view label{reinterpret_cast(&nop->data_block[1]), + marker_sz}; + const u32 color = *reinterpret_cast( + reinterpret_cast(&nop->data_block[1]) + marker_sz); + if (rasterizer) { + rasterizer->ScopedMarkerInsertColor(label, color); + } + break; + } case PM4CmdNop::PayloadType::DebugMarkerPop: { if (rasterizer) { rasterizer->ScopeMarkerEnd(); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 6088d99cf..293dfbe6a 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -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)((color >> 16) & 0xff) / 255.0f, (f32)((color >> 8) & 0xff) / 255.0f, + (f32)(color & 0xff) / 255.0f, (f32)((color >> 24) & 0xff) / 255.0f})}); +} + } // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 82e8fc0c0..bc14f39a4 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -40,6 +40,7 @@ public: void ScopeMarkerBegin(const std::string_view& str); void ScopeMarkerEnd(); void ScopedMarkerInsert(const std::string_view& str); + void ScopedMarkerInsertColor(const std::string_view& str, const u32 color); void InlineDataToGds(u32 gds_offset, u32 value); u32 ReadDataFromGds(u32 gsd_offset); From fd4893f6ef0e2f774c9d2b7085cd9207bb903e64 Mon Sep 17 00:00:00 2001 From: TheTurtle <47210458+raphaelthegreat@users.noreply.github.com> Date: Thu, 10 Oct 2024 19:26:56 +0300 Subject: [PATCH 11/22] hotfix: Don't unconditionally register fiber lib --- src/core/libraries/libs.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/libraries/libs.cpp b/src/core/libraries/libs.cpp index 86f9fcf9d..fb771bc22 100644 --- a/src/core/libraries/libs.cpp +++ b/src/core/libraries/libs.cpp @@ -78,7 +78,6 @@ void InitHLELibs(Core::Loader::SymbolsResolver* sym) { Libraries::ImeDialog::RegisterlibSceImeDialog(sym); Libraries::AvPlayer::RegisterlibSceAvPlayer(sym); Libraries::Audio3d::RegisterlibSceAudio3d(sym); - Libraries::Fiber::RegisterlibSceFiber(sym); } } // namespace Libraries From d91ad6174ebc48d15e28f2bc94c15bf8f2740f17 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:27:34 -0700 Subject: [PATCH 12/22] shader_recompiler: Move sampling parameter resolution to tracking pass and support more derivative types. (#1290) * shader_recompiler: Move sampling parameter resolution to tracking pass and support more derivative types. * shader_recompiler: Only track sampler sharp on sample instructions. * shader_recompiler: Fix Inst args size. --- .../backend/spirv/emit_spirv_image.cpp | 27 +- .../backend/spirv/emit_spirv_instructions.h | 6 +- .../frontend/translate/vector_memory.cpp | 194 +++++------- src/shader_recompiler/ir/ir_emitter.cpp | 43 ++- src/shader_recompiler/ir/ir_emitter.h | 26 +- src/shader_recompiler/ir/opcodes.h | 2 +- src/shader_recompiler/ir/opcodes.inc | 11 +- .../ir/passes/resource_tracking_pass.cpp | 296 +++++++++++------- src/shader_recompiler/ir/reg.h | 3 +- src/shader_recompiler/ir/value.h | 2 +- 10 files changed, 338 insertions(+), 272 deletions(-) diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index 8f062d6e7..fc99b8925 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp @@ -59,19 +59,22 @@ struct ImageOperands { } } - void AddDerivatives(EmitContext& ctx, Id derivatives) { - if (!Sirit::ValidId(derivatives)) { + void AddDerivatives(EmitContext& ctx, Id derivatives_dx, Id derivatives_dy) { + if (!Sirit::ValidId(derivatives_dx) || !Sirit::ValidId(derivatives_dy)) { return; } - const Id dx{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 0, 1)}; - const Id dy{ctx.OpVectorShuffle(ctx.F32[2], derivatives, derivatives, 2, 3)}; - Add(spv::ImageOperandsMask::Grad, dx, dy); + Add(spv::ImageOperandsMask::Grad, derivatives_dx, derivatives_dy); } spv::ImageOperandsMask mask{}; boost::container::static_vector 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, const IR::Value& offset) { const auto& texture = ctx.images[handle & 0xFFFF]; @@ -114,7 +117,9 @@ Id EmitImageSampleDrefImplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, operands.AddOffset(ctx, offset); const Id sample = ctx.OpImageSampleDrefImplicitLod(result_type, sampled_image, coords, dref, 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, @@ -129,7 +134,9 @@ Id EmitImageSampleDrefExplicitLod(EmitContext& ctx, IR::Inst* inst, u32 handle, operands.Add(spv::ImageOperandsMask::Lod, lod); const Id sample = ctx.OpImageSampleDrefExplicitLod(result_type, sampled_image, coords, dref, 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, @@ -212,15 +219,15 @@ Id EmitImageQueryLod(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords) { return ctx.OpImageQueryLod(ctx.F32[2], sampled_image, coords); } -Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, - const IR::Value& offset, Id lod_clamp) { +Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx, + Id derivatives_dy, const IR::Value& offset, const IR::Value& lod_clamp) { const auto& texture = ctx.images[handle & 0xFFFF]; const Id image = ctx.OpLoad(texture.image_type, texture.id); const Id result_type = texture.data_types->Get(4); const Id sampler = ctx.OpLoad(ctx.sampler_type, ctx.samplers[handle >> 16]); const Id sampled_image = ctx.OpSampledImage(texture.sampled_type, image, sampler); ImageOperands operands; - operands.AddDerivatives(ctx, derivatives); + operands.AddDerivatives(ctx, derivatives_dx, derivatives_dy); operands.AddOffset(ctx, offset); const Id sample = ctx.OpImageSampleExplicitLod(result_type, sampled_image, coords, operands.mask, operands.operands); diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h index 6ae1ef24a..02b98b343 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h +++ b/src/shader_recompiler/backend/spirv/emit_spirv_instructions.h @@ -368,6 +368,8 @@ Id EmitConvertF64U64(EmitContext& ctx, Id value); Id EmitConvertU16U32(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, const IR::Value& offset); 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 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 EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives, - const IR::Value& offset, Id lod_clamp); +Id EmitImageGradient(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id derivatives_dx, + 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); void EmitImageWrite(EmitContext& ctx, IR::Inst* inst, u32 handle, Id coords, Id color); diff --git a/src/shader_recompiler/frontend/translate/vector_memory.cpp b/src/shader_recompiler/frontend/translate/vector_memory.cpp index e76ba6d8a..b7ad3b36b 100644 --- a/src/shader_recompiler/frontend/translate/vector_memory.cpp +++ b/src/shader_recompiler/frontend/translate/vector_memory.cpp @@ -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::TextureInstInfo info{}; - info.explicit_lod.Assign(has_mip); + info.has_lod.Assign(has_mip); const IR::Value texel = ir.ImageFetch(handle, body, {}, {}, {}, info); 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(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) { const auto& mimg = inst.control.mimg; 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 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::U32 offset = - flags.test(MimgModifier::Offset) ? ir.GetVectorReg(addr_reg++) : IR::U32{}; - const IR::F32 bias = - flags.test(MimgModifier::LodBias) ? ir.GetVectorReg(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(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(addr_reg - 4), ir.GetVectorReg(addr_reg - 3), - ir.GetVectorReg(addr_reg - 2), ir.GetVectorReg(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(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(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); - }(); - + const IR::Value texel = EmitImageSample(ir, inst, tsharp_reg, sampler_reg, addr_reg, false); for (u32 i = 0; i < 4; i++) { if (((mimg.dmask >> i) & 1) == 0) { continue; @@ -609,60 +614,13 @@ void Translator::IMAGE_GATHER(const GcnInst& inst) { const IR::ScalarReg sampler_reg{inst.src[3].code * 4}; 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(addr_reg++) : IR::F32{}; - const IR::F32 dref = - flags.test(MimgModifier::Pcf) ? ir.GetVectorReg(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(addr_reg), ir.GetVectorReg(addr_reg + 1), - ir.GetVectorReg(addr_reg + 2), ir.GetVectorReg(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 // only one bit set to 1 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++) { const IR::F32 value = IR::F32{ir.CompositeExtract(texel, i)}; ir.SetVectorReg(dest_reg++, value); diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index 4f5eb5c33..e1b0eeed5 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -1492,27 +1492,34 @@ Value IREmitter::ImageAtomicExchange(const Value& handle, const Value& coords, c return Inst(Opcode::ImageAtomicExchange32, Flags{info}, handle, coords, value); } -Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& body, const F32& bias, - const U32& offset, TextureInstInfo info) { - return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, body, bias, offset); +Value IREmitter::ImageSampleRaw(const Value& handle, const Value& address1, const Value& address2, + const Value& address3, const Value& address4, + 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, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, body, IR::F32{}, offset); +Value IREmitter::ImageSampleImplicitLod(const Value& handle, const Value& coords, const F32& bias, + const Value& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleImplicitLod, Flags{info}, handle, coords, bias, offset); } -F32 IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& body, const F32& dref, - const F32& bias, const U32& offset, - TextureInstInfo info) { - return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, body, dref, bias, - offset); +Value IREmitter::ImageSampleExplicitLod(const Value& handle, const Value& coords, const F32& lod, + const Value& offset, TextureInstInfo info) { + return Inst(Opcode::ImageSampleExplicitLod, Flags{info}, handle, coords, lod, offset); } -F32 IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& body, const F32& dref, - const U32& offset, TextureInstInfo info) { - return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, body, dref, IR::F32{}, - offset); +Value IREmitter::ImageSampleDrefImplicitLod(const Value& handle, const Value& coords, + const F32& dref, const F32& bias, const Value& offset, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefImplicitLod, Flags{info}, handle, coords, dref, bias, + offset); +} + +Value IREmitter::ImageSampleDrefExplicitLod(const Value& handle, const Value& coords, + const F32& dref, const F32& lod, const Value& offset, + TextureInstInfo info) { + return Inst(Opcode::ImageSampleDrefExplicitLod, Flags{info}, handle, coords, dref, lod, offset); } Value IREmitter::ImageGather(const Value& handle, const Value& coords, const Value& offset, @@ -1544,9 +1551,11 @@ Value IREmitter::ImageQueryLod(const Value& handle, const Value& coords, Texture 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) { - 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) { diff --git a/src/shader_recompiler/ir/ir_emitter.h b/src/shader_recompiler/ir/ir_emitter.h index 2ebac037e..b3f513085 100644 --- a/src/shader_recompiler/ir/ir_emitter.h +++ b/src/shader_recompiler/ir/ir_emitter.h @@ -277,20 +277,25 @@ public: [[nodiscard]] Value ImageAtomicExchange(const Value& handle, const Value& coords, 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, - const F32& bias, const U32& offset, + const F32& bias, const Value& offset, TextureInstInfo info); [[nodiscard]] Value ImageSampleExplicitLod(const Value& handle, const Value& body, - const U32& offset, TextureInstInfo info); + const F32& lod, const Value& offset, + TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefImplicitLod(const Value& handle, const Value& body, - const F32& dref, const F32& bias, - const U32& offset, TextureInstInfo info); + [[nodiscard]] Value ImageSampleDrefImplicitLod(const Value& handle, const Value& body, + const F32& dref, const F32& bias, + const Value& offset, TextureInstInfo info); - [[nodiscard]] F32 ImageSampleDrefExplicitLod(const Value& handle, const Value& body, - const F32& dref, const U32& 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, const U1& skip_mips); @@ -306,8 +311,9 @@ public: [[nodiscard]] Value ImageFetch(const Value& handle, const Value& coords, const Value& offset, const U32& lod, const U32& multisampling, TextureInstInfo info); [[nodiscard]] Value ImageGradient(const Value& handle, const Value& coords, - const Value& derivatives, const Value& offset, - const F32& lod_clamp, TextureInstInfo info); + const Value& derivatives_dx, const Value& derivatives_dy, + const Value& offset, const F32& lod_clamp, + TextureInstInfo info); [[nodiscard]] Value ImageRead(const Value& handle, const Value& coords, TextureInstInfo info); void ImageWrite(const Value& handle, const Value& coords, const Value& color, TextureInstInfo info); diff --git a/src/shader_recompiler/ir/opcodes.h b/src/shader_recompiler/ir/opcodes.h index 2cea70090..200d7f421 100644 --- a/src/shader_recompiler/ir/opcodes.h +++ b/src/shader_recompiler/ir/opcodes.h @@ -21,7 +21,7 @@ namespace Detail { struct OpcodeMeta { std::string_view name; Type type; - std::array arg_types; + std::array arg_types; }; // using enum Type; diff --git a/src/shader_recompiler/ir/opcodes.inc b/src/shader_recompiler/ir/opcodes.inc index 41e94ab13..51e10fb38 100644 --- a/src/shader_recompiler/ir/opcodes.inc +++ b/src/shader_recompiler/ir/opcodes.inc @@ -317,16 +317,17 @@ OPCODE(ConvertU16U32, U16, U32, OPCODE(ConvertU32U16, U32, U16, ) // Image operations -OPCODE(ImageSampleImplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) -OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, U32, Opaque, ) -OPCODE(ImageSampleDrefImplicitLod, F32, Opaque, Opaque, Opaque, F32, Opaque, ) -OPCODE(ImageSampleDrefExplicitLod, F32, Opaque, Opaque, Opaque, U32, Opaque, ) +OPCODE(ImageSampleRaw, F32x4, Opaque, F32x4, F32x4, F32x4, F32, ) +OPCODE(ImageSampleImplicitLod, F32x4, Opaque, F32x4, F32, Opaque, ) +OPCODE(ImageSampleExplicitLod, F32x4, Opaque, Opaque, F32, Opaque, ) +OPCODE(ImageSampleDrefImplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) +OPCODE(ImageSampleDrefExplicitLod, F32x4, Opaque, Opaque, F32, F32, Opaque, ) OPCODE(ImageGather, F32x4, Opaque, Opaque, Opaque, ) OPCODE(ImageGatherDref, F32x4, Opaque, Opaque, Opaque, F32, ) OPCODE(ImageFetch, F32x4, Opaque, Opaque, Opaque, U32, Opaque, ) OPCODE(ImageQueryDimensions, U32x4, Opaque, U32, U1, ) 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(ImageWrite, Void, Opaque, Opaque, U32x4, ) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index db0d75f0c..76ffec81f 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -132,38 +132,16 @@ bool IsImageStorageInstruction(const IR::Inst& inst) { bool IsImageInstruction(const IR::Inst& inst) { 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::ImageGather: - case IR::Opcode::ImageGatherDref: case IR::Opcode::ImageQueryDimensions: case IR::Opcode::ImageQueryLod: - case IR::Opcode::ImageGradient: + case IR::Opcode::ImageSampleRaw: return true; default: 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 { public: 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::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(); + 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 { + 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) { const auto pred = [](const IR::Inst* inst) -> std::optional { 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, .dword_offset = tsharp.dword_offset, .type = type, - .nfmt = static_cast(image.GetNumberFmt()), + .nfmt = image.GetNumberFmt(), .is_storage = is_storage, .is_depth = bool(inst_info.is_depth), .is_atomic = IsImageAtomicInstruction(inst), .is_array = bool(inst_info.is_array), }); - // Read sampler sharp. This doesn't exist for IMAGE_LOAD/IMAGE_STORE instructions - const u32 sampler_binding = [&] { - if (!has_sampler) { - return 0U; - } - 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::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); + // Sample instructions must be resolved into a new instruction using address register data. + if (inst.GetOpcode() == IR::Opcode::ImageSampleRaw) { + PatchImageSampleInstruction(block, inst, info, descriptors, producer, image_binding, image); + return; + } // Patch image handle 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); - if (inst_info.has_offset) { - // The offsets are six-bit signed integers: X=[5:0], Y=[13:8], and Z=[21:16]. - const u32 arg_pos = ImageOffsetArgumentPosition(inst); - 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); + if (inst_info.has_lod) { + ASSERT(inst.GetOpcode() == IR::Opcode::ImageFetch); + inst.SetArg(3, arg); } } diff --git a/src/shader_recompiler/ir/reg.h b/src/shader_recompiler/ir/reg.h index 5facaf5c7..d7c0b1db5 100644 --- a/src/shader_recompiler/ir/reg.h +++ b/src/shader_recompiler/ir/reg.h @@ -33,11 +33,12 @@ union TextureInstInfo { BitField<1, 1, u32> has_bias; BitField<2, 1, u32> has_lod_clamp; 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<6, 2, u32> gather_comp; BitField<8, 1, u32> has_derivatives; BitField<9, 1, u32> is_array; + BitField<10, 1, u32> is_gather; }; union BufferInstInfo { diff --git a/src/shader_recompiler/ir/value.h b/src/shader_recompiler/ir/value.h index 060b9d2bb..a282b9168 100644 --- a/src/shader_recompiler/ir/value.h +++ b/src/shader_recompiler/ir/value.h @@ -209,7 +209,7 @@ private: union { NonTriviallyDummy dummy{}; boost::container::small_vector, 2> phi_args; - std::array args; + std::array args; }; }; static_assert(sizeof(Inst) <= 128, "Inst size unintentionally increased"); From 09cbccb40be311246079e2c3d3a0c1630f51b3bc Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 09:40:19 -0700 Subject: [PATCH 13/22] shader_recompiler: Implement V_SUBB_U32 and V_SUBBREV_U32. (#1331) --- .../frontend/translate/translate.h | 6 +- .../frontend/translate/vector_alu.cpp | 111 +++++++++++++----- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/src/shader_recompiler/frontend/translate/translate.h b/src/shader_recompiler/frontend/translate/translate.h index c77588280..b70d4b829 100644 --- a/src/shader_recompiler/frontend/translate/translate.h +++ b/src/shader_recompiler/frontend/translate/translate.h @@ -155,6 +155,8 @@ public: void V_SUB_I32(const GcnInst& inst); void V_SUBREV_I32(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_CVT_PKNORM_U16_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 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); void VMovRelDHelper(u32 dst_vgprno, const IR::U32 src_val, const IR::U32 m0); diff --git a/src/shader_recompiler/frontend/translate/vector_alu.cpp b/src/shader_recompiler/frontend/translate/vector_alu.cpp index b061d3b78..279695461 100644 --- a/src/shader_recompiler/frontend/translate/vector_alu.cpp +++ b/src/shader_recompiler/frontend/translate/vector_alu.cpp @@ -87,6 +87,10 @@ void Translator::EmitVectorAlu(const GcnInst& inst) { return V_SUBREV_I32(inst); case Opcode::V_ADDC_U32: 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: return V_LDEXP_F32(inst); 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) { + // Signed or unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{ir.GetVectorReg(IR::VectorReg(inst.src[1].code))}; - SetDst(inst.dst[0], ir.IAdd(src0, src1)); - // TODO: Carry + const IR::U32 result{ir.IAdd(src0, src1)}; + SetDst(inst.dst[0], result); + + // TODO: Carry-out with signed or unsigned components } void Translator::V_SUB_I32(const GcnInst& inst) { + // Unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; 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) { + // Unsigned components const IR::U32 src0{GetSrc(inst.src[0])}; const IR::U32 src1{GetSrc(inst.src[1])}; - SetDst(inst.dst[0], ir.ISub(src1, src0)); - // TODO: Carry-out + const IR::U32 result{ir.ISub(src1, src0)}; + 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) { - const auto src0 = GetSrc(inst.src[0]); - const auto src1 = GetSrc(inst.src[1]); - - 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(); - } - - 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); - + // 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.IAdd(ir.IAdd(src0, src1), carry)}; SetDst(inst.dst[0], result); - const IR::U1 less_src0 = ir.ILessThan(result, src0, false); - const IR::U1 less_src1 = ir.ILessThan(result, src1, false); - const IR::U1 did_overflow = ir.LogicalOr(less_src0, less_src1); - ir.SetVcc(did_overflow); + const IR::U1 less_src0{ir.ILessThan(result, src0, false)}; + const IR::U1 less_src1{ir.ILessThan(result, src1, false)}; + const IR::U1 did_overflow{ir.LogicalOr(less_src0, less_src1)}; + 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) { @@ -1152,6 +1176,37 @@ void Translator::V_MAD_U64_U32(const GcnInst& inst) { 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 // [src_vgprno, src_vgprno + max_m0]. Same for dst regs we may write back to From 0a5f46942e7639e6840979da73309e001cd73684 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 10:32:13 -0700 Subject: [PATCH 14/22] shader_recompiler: Make sure RuntimeInfo is zero initialized. (#1332) --- src/shader_recompiler/runtime_info.h | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/runtime_info.h b/src/shader_recompiler/runtime_info.h index 4d15c2072..03936f3a8 100644 --- a/src/shader_recompiler/runtime_info.h +++ b/src/shader_recompiler/runtime_info.h @@ -153,7 +153,10 @@ struct RuntimeInfo { 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 { switch (stage) { From 0df0d0cb66632006f3300709d94b76b297f76c80 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 12:51:11 -0700 Subject: [PATCH 15/22] shader_recompiler: Fix last image sample address parameter. (#1334) --- src/shader_recompiler/ir/passes/resource_tracking_pass.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 76ffec81f..0cb6bff29 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -481,7 +481,7 @@ void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info, 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(); + IR::F32 body4 = IR::F32{inst.Arg(4)}; const auto get_addr_reg = [&](u32 index) -> IR::F32 { if (index <= 3) { return IR::F32{body1->Arg(index)}; @@ -493,7 +493,7 @@ void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info, return IR::F32{body3->Arg(index - 8)}; } if (index == 12) { - return IR::F32{body4}; + return body4; } UNREACHABLE(); }; From dcc4057dd87d090b55affee6fb2f3859a326cb0f Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:04:51 -0700 Subject: [PATCH 16/22] shader_recompiler: Set correct operand field for VOP3b sdst. (#1335) --- src/shader_recompiler/frontend/decode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shader_recompiler/frontend/decode.cpp b/src/shader_recompiler/frontend/decode.cpp index 98f97dd12..796bed127 100644 --- a/src/shader_recompiler/frontend/decode.cpp +++ b/src/shader_recompiler/frontend/decode.cpp @@ -654,7 +654,7 @@ void GcnDecodeContext::decodeInstructionVOP3(uint64_t hexInstruction) { OpcodeVOP3 vop3Op = static_cast(op); if (IsVop3BEncoding(m_instruction.opcode)) { - m_instruction.dst[1].field = OperandField::ScalarGPR; + m_instruction.dst[1].field = getOperandField(sdst); m_instruction.dst[1].type = ScalarType::Uint64; m_instruction.dst[1].code = sdst; } else { From 21eb175aa10a5939a4eb306143af5713e3d80cee Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:14:50 -0700 Subject: [PATCH 17/22] shader_recompiler: Add asserts for get/set register bounds. (#1336) --- src/shader_recompiler/ir/ir_emitter.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/shader_recompiler/ir/ir_emitter.cpp b/src/shader_recompiler/ir/ir_emitter.cpp index e1b0eeed5..cfd044f9e 100644 --- a/src/shader_recompiler/ir/ir_emitter.cpp +++ b/src/shader_recompiler/ir/ir_emitter.cpp @@ -130,19 +130,23 @@ void IREmitter::DeviceMemoryBarrier() { } U32 IREmitter::GetUserData(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetUserData, reg); } U1 IREmitter::GetThreadBitScalarReg(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetThreadBitScalarReg, reg); } void IREmitter::SetThreadBitScalarReg(IR::ScalarReg reg, const U1& value) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); Inst(Opcode::SetThreadBitScalarReg, reg, value); } template <> U32 IREmitter::GetScalarReg(IR::ScalarReg reg) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); return Inst(Opcode::GetScalarRegister, reg); } @@ -153,6 +157,7 @@ F32 IREmitter::GetScalarReg(IR::ScalarReg reg) { template <> U32 IREmitter::GetVectorReg(IR::VectorReg reg) { + ASSERT(static_cast(reg) < IR::NumVectorRegs); return Inst(Opcode::GetVectorRegister, reg); } @@ -162,11 +167,13 @@ F32 IREmitter::GetVectorReg(IR::VectorReg reg) { } void IREmitter::SetScalarReg(IR::ScalarReg reg, const U32F32& value) { + ASSERT(static_cast(reg) < IR::NumScalarRegs); const U32 value_typed = value.Type() == Type::F32 ? BitCast(F32{value}) : U32{value}; Inst(Opcode::SetScalarRegister, reg, value_typed); } void IREmitter::SetVectorReg(IR::VectorReg reg, const U32F32& value) { + ASSERT(static_cast(reg) < IR::NumVectorRegs); const U32 value_typed = value.Type() == Type::F32 ? BitCast(F32{value}) : U32{value}; Inst(Opcode::SetVectorRegister, reg, value_typed); } From 2f80d7565de3c93b0813b360817d5f152e5c0e24 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:30:09 -0700 Subject: [PATCH 18/22] resource_tracking_pass: Fix type handling of sample offsets. (#1337) --- src/shader_recompiler/ir/passes/resource_tracking_pass.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 0cb6bff29..21f2115d5 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -507,7 +507,11 @@ void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info, } // 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++); + IR::Value arg = get_addr_reg(addr_reg++); + if (const IR::Inst* offset_inst = arg.TryInstRecursive()) { + ASSERT(offset_inst->GetOpcode() == IR::Opcode::BitCastF32U32); + arg = offset_inst->Arg(0); + } const auto read = [&](u32 off) -> IR::U32 { if (arg.IsImmediate()) { From 0f9166166074be844bce88bf25f6cf6cef1a77a0 Mon Sep 17 00:00:00 2001 From: squidbus <175574877+squidbus@users.noreply.github.com> Date: Thu, 10 Oct 2024 13:58:01 -0700 Subject: [PATCH 19/22] resource_tracking_pass: Make sure immediate offset is accessed as correct type. (#1339) --- src/shader_recompiler/ir/passes/resource_tracking_pass.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp index 21f2115d5..0d91badda 100644 --- a/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp +++ b/src/shader_recompiler/ir/passes/resource_tracking_pass.cpp @@ -515,7 +515,9 @@ void PatchImageSampleInstruction(IR::Block& block, IR::Inst& inst, Info& info, const auto read = [&](u32 off) -> IR::U32 { if (arg.IsImmediate()) { - const u16 comp = (arg.U32() >> off) & 0x3F; + const u32 imm = + arg.Type() == IR::Type::F32 ? std::bit_cast(arg.F32()) : arg.U32(); + const u16 comp = (imm >> off) & 0x3F; return ir.Imm32(s32(comp << 26) >> 26); } return ir.BitFieldExtract(IR::U32{arg}, ir.Imm32(off), ir.Imm32(6), true); From 66f1bb937f056a899d7e7e6be8090fe98b194d0f Mon Sep 17 00:00:00 2001 From: Lander Gallastegi Date: Fri, 11 Oct 2024 03:53:32 +0200 Subject: [PATCH 20/22] ime_dialog: Initial implementation (#1267) * Add C string types * Prepare existing enums * Added missing enums * Types update * State base * Compile Ime Dialog UI * UI implementation * Scoped lock * Functional implementation * Link against iconv on macOS * Fix building on windows * Better UI * clang-format * Some fixes and cleanup * Enable reserved checks * clang-format * Fix default text encoding max size * clang-format (again) * Some review changes * Use std::vector for dynamic strings * Use CString * Accept dialog on enter press * clang-format * Use ImGUI for encoding/decoding --- CMakeLists.txt | 2 + src/common/cstring.h | 22 +- src/core/libraries/dialogs/ime_dialog.cpp | 207 ++++++++-- src/core/libraries/dialogs/ime_dialog.h | 280 +++++++------ src/core/libraries/dialogs/ime_dialog_ui.cpp | 390 +++++++++++++++++++ src/core/libraries/dialogs/ime_dialog_ui.h | 84 ++++ src/imgui/imgui_config.h | 5 +- 7 files changed, 847 insertions(+), 143 deletions(-) mode change 100644 => 100755 CMakeLists.txt create mode 100644 src/core/libraries/dialogs/ime_dialog_ui.cpp create mode 100644 src/core/libraries/dialogs/ime_dialog_ui.h diff --git a/CMakeLists.txt b/CMakeLists.txt old mode 100644 new mode 100755 index 8a41e1ac9..7c58af6bb --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -297,6 +297,8 @@ set(LIBC_SOURCES src/core/libraries/libc_internal/libc_internal.cpp set(DIALOGS_LIB src/core/libraries/dialogs/error_dialog.cpp src/core/libraries/dialogs/error_dialog.h + src/core/libraries/dialogs/ime_dialog_ui.cpp + src/core/libraries/dialogs/ime_dialog_ui.h src/core/libraries/dialogs/ime_dialog.cpp src/core/libraries/dialogs/ime_dialog.h src/core/libraries/dialogs/error_codes.h diff --git a/src/common/cstring.h b/src/common/cstring.h index fb29443ee..45c291c14 100644 --- a/src/common/cstring.h +++ b/src/common/cstring.h @@ -81,34 +81,42 @@ public: return std::basic_string_view{data}; } - char* begin() { + T* begin() { if (this == nullptr) { return nullptr; } return data; } - const char* begin() const { + const T* begin() const { if (this == nullptr) { return nullptr; } return data; } - char* end() { + T* end() { if (this == nullptr) { return nullptr; } return data + N; } - const char* end() const { + const T* end() const { if (this == nullptr) { return nullptr; } return data + N; } + constexpr std::size_t capacity() const { + return N; + } + + std::size_t size() const { + return std::char_traits::length(data); + } + T& operator[](size_t idx) { return data[idx]; } @@ -152,6 +160,12 @@ public: static_assert(sizeof(CString<13>) == sizeof(char[13])); // Ensure size still matches a simple array static_assert(std::weakly_incrementable::Iterator>); +template +using CWString = CString; + +template +using CU16String = CString; + #pragma clang diagnostic pop } // namespace Common \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog.cpp b/src/core/libraries/dialogs/ime_dialog.cpp index ddb1a89f8..3d168bb79 100644 --- a/src/core/libraries/dialogs/ime_dialog.cpp +++ b/src/core/libraries/dialogs/ime_dialog.cpp @@ -1,28 +1,75 @@ // SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include +#include #include "common/logging/log.h" #include "core/libraries/error_codes.h" #include "core/libraries/libs.h" #include "ime_dialog.h" +#include "ime_dialog_ui.h" + +static constexpr std::array MAX_X_POSITIONS = {3840.0f, 1920.0f}; +static constexpr std::array MAX_Y_POSITIONS = {2160.0f, 1080.0f}; namespace Libraries::ImeDialog { -static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; +static OrbisImeDialogStatus g_ime_dlg_status = OrbisImeDialogStatus::NONE; +static OrbisImeDialogResult g_ime_dlg_result{}; +static ImeDialogState g_ime_dlg_state{}; +static ImeDialogUi g_ime_dlg_ui; -int PS4_SYSV_ABI sceImeDialogAbort() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +static bool IsValidOption(OrbisImeDialogOption option, OrbisImeType type) { + if (False(~option & + (OrbisImeDialogOption::MULTILINE | OrbisImeDialogOption::NO_AUTO_COMPLETION))) { + return false; + } + + if (True(option & OrbisImeDialogOption::MULTILINE) && type != OrbisImeType::DEFAULT && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + if (True(option & OrbisImeDialogOption::NO_AUTO_COMPLETION) && type != OrbisImeType::NUMBER && + type != OrbisImeType::BASIC_LATIN) { + return false; + } + + return true; } -int PS4_SYSV_ABI sceImeDialogForceClose() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogAbort() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status != OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog not running"); + return Error::DIALOG_NOT_RUNNING; + } + + g_ime_dlg_status = OrbisImeDialogStatus::FINISHED; + g_ime_dlg_result.endstatus = OrbisImeDialogEndStatus::ABORTED; + + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogForTestFunction() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogForceClose() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; +} + +Error PS4_SYSV_ABI sceImeDialogForTestFunction() { + return Error::INTERNAL; } int PS4_SYSV_ABI sceImeDialogGetCurrentStarState() { @@ -45,26 +92,118 @@ int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { - result->endstatus = OrbisImeDialogEndStatus::ORBIS_IME_DIALOG_END_STATUS_OK; - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result) { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is not running"); + return Error::DIALOG_NOT_IN_USE; + } + + if (result == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with result (NULL)"); + return Error::INVALID_ADDRESS; + } + + result->endstatus = g_ime_dlg_result.endstatus; + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_state.CopyTextToOrbisBuffer(); + return Error::OK; } -int PS4_SYSV_ABI sceImeDialogGetStatus() { - if (g_ime_dlg_status == OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING) { - return OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_FINISHED; +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus() { + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + g_ime_dlg_state.CallTextFilter(); } + return g_ime_dlg_status; } -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - const std::wstring_view text = L"shadPS4"; - param->maxTextLength = text.size(); - std::memcpy(param->inputTextBuffer, text.data(), text.size() * sizeof(wchar_t)); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_RUNNING; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended) { + if (g_ime_dlg_status != OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog is already running"); + return Error::BUSY; + } + + if (param == nullptr) { + LOG_INFO(Lib_ImeDialog, "called with param (NULL)"); + return Error::INVALID_ADDRESS; + } + + if (!magic_enum::enum_contains(param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->type"); + return Error::INVALID_ADDRESS; + } + + // TODO: do correct param->option validation + // TODO: do correct param->supportedLanguages validation + + if (param->posx < 0.0f || + param->posx >= + MAX_X_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posx"); + return Error::INVALID_POSX; + } + + if (param->posy < 0.0f || + param->posy >= + MAX_Y_POSITIONS[False(param->option & OrbisImeDialogOption::LARGE_RESOLUTION)]) { + LOG_INFO(Lib_ImeDialog, "Invalid param->posy"); + return Error::INVALID_POSY; + } + + if (!magic_enum::enum_contains(param->horizontalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->horizontalAlignment"); + return Error::INVALID_HORIZONTALIGNMENT; + } + + if (!magic_enum::enum_contains(param->verticalAlignment)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->verticalAlignment"); + return Error::INVALID_VERTICALALIGNMENT; + } + + if (!IsValidOption(param->option, param->type)) { + LOG_INFO(Lib_ImeDialog, "Invalid param->option"); + return Error::INVALID_PARAM; + } + + if (param->inputTextBuffer == nullptr) { + LOG_INFO(Lib_ImeDialog, "Invalid param->inputTextBuffer"); + return Error::INVALID_INPUT_TEXT_BUFFER; + } + + if (extended) { + if (magic_enum::enum_contains(extended->priority)) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->priority"); + return Error::INVALID_EXTENDED; + } + + // TODO: do correct extended->option validation + + if ((extended->extKeyboardMode & 0xe3fffffc) != 0) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->extKeyboardMode"); + return Error::INVALID_EXTENDED; + } + + if (extended->disableDevice > 7) { + LOG_INFO(Lib_ImeDialog, "Invalid extended->disableDevice"); + return Error::INVALID_EXTENDED; + } + } + + if (param->maxTextLength > ORBIS_IME_DIALOG_MAX_TEXT_LENGTH) { + LOG_INFO(Lib_ImeDialog, "Invalid param->maxTextLength"); + return Error::INVALID_MAX_TEXT_LENGTH; + } + + g_ime_dlg_result = {}; + g_ime_dlg_state = ImeDialogState(param, extended); + g_ime_dlg_status = OrbisImeDialogStatus::RUNNING; + g_ime_dlg_ui = ImeDialogUi(&g_ime_dlg_state, &g_ime_dlg_status, &g_ime_dlg_result); + + return Error::OK; } int PS4_SYSV_ABI sceImeDialogInitInternal() { @@ -87,10 +226,22 @@ int PS4_SYSV_ABI sceImeDialogSetPanelPosition() { return ORBIS_OK; } -int PS4_SYSV_ABI sceImeDialogTerm() { - LOG_ERROR(Lib_ImeDialog, "(STUBBED) called"); - g_ime_dlg_status = OrbisImeDialogStatus::ORBIS_IME_DIALOG_STATUS_NONE; - return ORBIS_OK; +Error PS4_SYSV_ABI sceImeDialogTerm() { + if (g_ime_dlg_status == OrbisImeDialogStatus::NONE) { + LOG_INFO(Lib_ImeDialog, "IME dialog not in use"); + return Error::DIALOG_NOT_IN_USE; + } + + if (g_ime_dlg_status == OrbisImeDialogStatus::RUNNING) { + LOG_INFO(Lib_ImeDialog, "IME dialog is still running"); + return Error::DIALOG_NOT_FINISHED; + } + + g_ime_dlg_status = OrbisImeDialogStatus::NONE; + g_ime_dlg_ui = ImeDialogUi(); + g_ime_dlg_state = ImeDialogState(); + + return Error::OK; } void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym) { diff --git a/src/core/libraries/dialogs/ime_dialog.h b/src/core/libraries/dialogs/ime_dialog.h index ffe42b31a..66cf9fb93 100644 --- a/src/core/libraries/dialogs/ime_dialog.h +++ b/src/core/libraries/dialogs/ime_dialog.h @@ -3,6 +3,7 @@ #pragma once +#include "common/enum.h" #include "common/types.h" namespace Core::Loader { @@ -11,71 +12,150 @@ class SymbolsResolver; namespace Libraries::ImeDialog { -enum OrbisImeDialogStatus { - ORBIS_IME_DIALOG_STATUS_NONE = 0, - ORBIS_IME_DIALOG_STATUS_RUNNING = 1, - ORBIS_IME_DIALOG_STATUS_FINISHED = 2 +constexpr u32 ORBIS_IME_DIALOG_MAX_TEXT_LENGTH = 0x78; + +enum class Error : u32 { + OK = 0x0, + BUSY = 0x80bc0001, + NOT_OPENED = 0x80bc0002, + NO_MEMORY = 0x80bc0003, + CONNECTION_FAILED = 0x80bc0004, + TOO_MANY_REQUESTS = 0x80bc0005, + INVALID_TEXT = 0x80bc0006, + EVENT_OVERFLOW = 0x80bc0007, + NOT_ACTIVE = 0x80bc0008, + IME_SUSPENDING = 0x80bc0009, + DEVICE_IN_USE = 0x80bc000a, + INVALID_USER_ID = 0x80bc0010, + INVALID_TYPE = 0x80bc0011, + INVALID_SUPPORTED_LANGUAGES = 0x80bc0012, + INVALID_ENTER_LABEL = 0x80bc0013, + INVALID_INPUT_METHOD = 0x80bc0014, + INVALID_OPTION = 0x80bc0015, + INVALID_MAX_TEXT_LENGTH = 0x80bc0016, + INVALID_INPUT_TEXT_BUFFER = 0x80bc0017, + INVALID_POSX = 0x80bc0018, + INVALID_POSY = 0x80bc0019, + INVALID_HORIZONTALIGNMENT = 0x80bc001a, + INVALID_VERTICALALIGNMENT = 0x80bc001b, + INVALID_EXTENDED = 0x80bc001c, + INVALID_KEYBOARD_TYPE = 0x80bc001d, + INVALID_WORK = 0x80bc0020, + INVALID_ARG = 0x80bc0021, + INVALID_HANDLER = 0x80bc0022, + NO_RESOURCE_ID = 0x80bc0023, + INVALID_MODE = 0x80bc0024, + INVALID_PARAM = 0x80bc0030, + INVALID_ADDRESS = 0x80bc0031, + INVALID_RESERVED = 0x80bc0032, + INVALID_TIMING = 0x80bc0033, + INTERNAL = 0x80bc00ff, + DIALOG_INVALID_TITLE = 0x80bc0101, + DIALOG_NOT_RUNNING = 0x80bc0105, + DIALOG_NOT_FINISHED = 0x80bc0106, + DIALOG_NOT_IN_USE = 0x80bc0107, }; -enum OrbisImeDialogEndStatus { - ORBIS_IME_DIALOG_END_STATUS_OK = 0, - ORBIS_IME_DIALOG_END_STATUS_USER_CANCELED = 1, - ORBIS_IME_DIALOG_END_STATUS_ABORTED = 2 +enum class OrbisImeDialogStatus : u32 { + NONE = 0, + RUNNING = 1, + FINISHED = 2, }; -struct OrbisImeDialogResult { - OrbisImeDialogEndStatus endstatus; - s32 reserved[12]; +enum class OrbisImeDialogEndStatus : u32 { + OK = 0, + USER_CANCELED = 1, + ABORTED = 2, }; -enum OrbisImeType { - ORBIS_IME_TYPE_DEFAULT = 0, - ORBIS_IME_TYPE_BASIC_LATIN = 1, - ORBIS_IME_TYPE_URL = 2, - ORBIS_IME_TYPE_MAIL = 3, - ORBIS_IME_TYPE_NUMBER = 4 +enum class OrbisImeType : u32 { + DEFAULT = 0, + BASIC_LATIN = 1, + URL = 2, + MAIL = 3, + NUMBER = 4, }; -enum OrbisImeEnterLabel { - ORBIS_IME_ENTER_LABEL_DEFAULT = 0, - ORBIS_IME_ENTER_LABEL_SEND = 1, - ORBIS_IME_ENTER_LABEL_SEARCH = 2, - ORBIS_IME_ENTER_LABEL_GO = 3 -}; -enum OrbiImeInputMethod { ORBIS_IME_INPUT_METHOD_DEFAULT = 0 }; - -typedef int (*OrbisImeTextFilter)(wchar_t* outText, u32* outTextLength, const wchar_t* srcText, - u32 srcTextLength); - -enum OrbisImeHorizontalAlignment { - ORBIS_IME_HALIGN_LEFT = 0, - ORBIS_IME_HALIGN_CENTER = 1, - ORBIS_IME_HALIGN_RIGHT = 2 +enum class OrbisImeEnterLabel : u32 { + DEFAULT = 0, + SEND = 1, + SEARCH = 2, + GO = 3, }; -enum OrbisImeVerticalAlignment { - ORBIS_IME_VALIGN_TOP = 0, - ORBIS_IME_VALIGN_CENTER = 1, - ORBIS_IME_VALIGN_BOTTOM = 2 +enum class OrbisImeDialogOption : u32 { + DEFAULT = 0, + MULTILINE = 1, + NO_AUTO_CORRECTION = 2, + NO_AUTO_COMPLETION = 4, + // TODO: Document missing options + LARGE_RESOLUTION = 1024, }; -struct OrbisImeDialogParam { - s32 userId; - OrbisImeType type; - u64 supportedLanguages; - OrbisImeEnterLabel enterLabel; - OrbiImeInputMethod inputMethod; - OrbisImeTextFilter filter; - u32 option; - u32 maxTextLength; - wchar_t* inputTextBuffer; - float posx; - float posy; - OrbisImeHorizontalAlignment horizontalAlignment; - OrbisImeVerticalAlignment verticalAlignment; - const wchar_t* placeholder; - const wchar_t* title; - s8 reserved[16]; +DECLARE_ENUM_FLAG_OPERATORS(OrbisImeDialogOption) + +enum class OrbisImeInputMethod : u32 { + DEFAULT = 0, +}; + +enum class OrbisImeHorizontalAlignment : u32 { + LEFT = 0, + CENTER = 1, + RIGHT = 2, +}; + +enum class OrbisImeVerticalAlignment : u32 { + TOP = 0, + CENTER = 1, + BOTTOM = 2, +}; + +enum class OrbisImePanelPriority : u32 { + DEFAULT = 0, + ALPHABET = 1, + SYMBOL = 2, + ACCENT = 3, +}; + +enum class OrbisImeKeyboardType : u32 { + NONE = 0, + DANISH = 1, + GERMAN = 2, + GERMAN_SW = 3, + ENGLISH_US = 4, + ENGLISH_GB = 5, + SPANISH = 6, + SPANISH_LA = 7, + FINNISH = 8, + FRENCH = 9, + FRENCH_BR = 10, + FRENCH_CA = 11, + FRENCH_SW = 12, + ITALIAN = 13, + DUTCH = 14, + NORWEGIAN = 15, + POLISH = 16, + PORTUGUESE_BR = 17, + PORTUGUESE_PT = 18, + RUSSIAN = 19, + SWEDISH = 20, + TURKISH = 21, + JAPANESE_ROMAN = 22, + JAPANESE_KANA = 23, + KOREAN = 24, + SM_CHINESE = 25, + TR_CHINESE_ZY = 26, + TR_CHINESE_PY_HK = 27, + TR_CHINESE_PY_TW = 28, + TR_CHINESE_CG = 29, + ARABIC_AR = 30, + THAI = 31, + CZECH = 32, + GREEK = 33, + INDONESIAN = 34, + VIETNAMESE = 35, + ROMANIAN = 36, + HUNGARIAN = 37, }; struct OrbisImeColor { @@ -85,57 +165,14 @@ struct OrbisImeColor { u8 a; }; -enum OrbisImePanelPriority { - ORBIS_IME_PANEL_PRIORITY_DEFAULT = 0, - ORBIS_IME_PANEL_PRIORITY_ALPHABET = 1, - ORBIS_IME_PANEL_PRIORITY_SYMBOL = 2, - ORBIS_IME_PANEL_PRIORITY_ACCENT = 3 -}; - -enum OrbisImeKeyboardType { - ORBIS_IME_KEYBOARD_TYPE_NONE = 0, - ORBIS_IME_KEYBOARD_TYPE_DANISH = 1, - ORBIS_IME_KEYBOARD_TYPE_GERMAN = 2, - ORBIS_IME_KEYBOARD_TYPE_GERMAN_SW = 3, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_US = 4, - ORBIS_IME_KEYBOARD_TYPE_ENGLISH_GB = 5, - ORBIS_IME_KEYBOARD_TYPE_SPANISH = 6, - ORBIS_IME_KEYBOARD_TYPE_SPANISH_LA = 7, - ORBIS_IME_KEYBOARD_TYPE_FINNISH = 8, - ORBIS_IME_KEYBOARD_TYPE_FRENCH = 9, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_BR = 10, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_CA = 11, - ORBIS_IME_KEYBOARD_TYPE_FRENCH_SW = 12, - ORBIS_IME_KEYBOARD_TYPE_ITALIAN = 13, - ORBIS_IME_KEYBOARD_TYPE_DUTCH = 14, - ORBIS_IME_KEYBOARD_TYPE_NORWEGIAN = 15, - ORBIS_IME_KEYBOARD_TYPE_POLISH = 16, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_BR = 17, - ORBIS_IME_KEYBOARD_TYPE_PORTUGUESE_PT = 18, - ORBIS_IME_KEYBOARD_TYPE_RUSSIAN = 19, - ORBIS_IME_KEYBOARD_TYPE_SWEDISH = 20, - ORBIS_IME_KEYBOARD_TYPE_TURKISH = 21, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_ROMAN = 22, - ORBIS_IME_KEYBOARD_TYPE_JAPANESE_KANA = 23, - ORBIS_IME_KEYBOARD_TYPE_KOREAN = 24, - ORBIS_IME_KEYBOARD_TYPE_SM_CHINESE = 25, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_ZY = 26, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_HK = 27, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_PY_TW = 28, - ORBIS_IME_KEYBOARD_TYPE_TR_CHINESE_CG = 29, - ORBIS_IME_KEYBOARD_TYPE_ARABIC_AR = 30, - ORBIS_IME_KEYBOARD_TYPE_THAI = 31, - ORBIS_IME_KEYBOARD_TYPE_CZECH = 32, - ORBIS_IME_KEYBOARD_TYPE_GREEK = 33, - ORBIS_IME_KEYBOARD_TYPE_INDONESIAN = 34, - ORBIS_IME_KEYBOARD_TYPE_VIETNAMESE = 35, - ORBIS_IME_KEYBOARD_TYPE_ROMANIAN = 36, - ORBIS_IME_KEYBOARD_TYPE_HUNGARIAN = 37 +struct OrbisImeDialogResult { + OrbisImeDialogEndStatus endstatus; + s32 reserved[12]; }; struct OrbisImeKeycode { u16 keycode; - wchar_t character; + char16_t character; u32 status; OrbisImeKeyboardType type; s32 userId; @@ -143,11 +180,34 @@ struct OrbisImeKeycode { u64 timestamp; }; -typedef int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, u16* outKeycode, - u32* outStatus, void* reserved); +typedef PS4_SYSV_ABI int (*OrbisImeTextFilter)(char16_t* outText, u32* outTextLength, + const char16_t* srcText, u32 srcTextLength); + +typedef PS4_SYSV_ABI int (*OrbisImeExtKeyboardFilter)(const OrbisImeKeycode* srcKeycode, + u16* outKeycode, u32* outStatus, + void* reserved); + +struct OrbisImeDialogParam { + s32 userId; + OrbisImeType type; + u64 supportedLanguages; + OrbisImeEnterLabel enterLabel; + OrbisImeInputMethod inputMethod; + OrbisImeTextFilter filter; + OrbisImeDialogOption option; + u32 maxTextLength; + char16_t* inputTextBuffer; + float posx; + float posy; + OrbisImeHorizontalAlignment horizontalAlignment; + OrbisImeVerticalAlignment verticalAlignment; + const char16_t* placeholder; + const char16_t* title; + s8 reserved[16]; +}; struct OrbisImeParamExtended { - u32 option; + u32 option; // OrbisImeDialogOptionExtended OrbisImeColor colorBase; OrbisImeColor colorLine; OrbisImeColor colorTextField; @@ -165,21 +225,21 @@ struct OrbisImeParamExtended { int8_t reserved[60]; }; -int PS4_SYSV_ABI sceImeDialogAbort(); -int PS4_SYSV_ABI sceImeDialogForceClose(); -int PS4_SYSV_ABI sceImeDialogForTestFunction(); +Error PS4_SYSV_ABI sceImeDialogAbort(); +Error PS4_SYSV_ABI sceImeDialogForceClose(); +Error PS4_SYSV_ABI sceImeDialogForTestFunction(); int PS4_SYSV_ABI sceImeDialogGetCurrentStarState(); int PS4_SYSV_ABI sceImeDialogGetPanelPositionAndForm(); int PS4_SYSV_ABI sceImeDialogGetPanelSize(); int PS4_SYSV_ABI sceImeDialogGetPanelSizeExtended(); -int PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); -/*OrbisImeDialogStatus*/ int PS4_SYSV_ABI sceImeDialogGetStatus(); -int PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); +Error PS4_SYSV_ABI sceImeDialogGetResult(OrbisImeDialogResult* result); +OrbisImeDialogStatus PS4_SYSV_ABI sceImeDialogGetStatus(); +Error PS4_SYSV_ABI sceImeDialogInit(OrbisImeDialogParam* param, OrbisImeParamExtended* extended); int PS4_SYSV_ABI sceImeDialogInitInternal(); int PS4_SYSV_ABI sceImeDialogInitInternal2(); int PS4_SYSV_ABI sceImeDialogInitInternal3(); int PS4_SYSV_ABI sceImeDialogSetPanelPosition(); -int PS4_SYSV_ABI sceImeDialogTerm(); +Error PS4_SYSV_ABI sceImeDialogTerm(); void RegisterlibSceImeDialog(Core::Loader::SymbolsResolver* sym); } // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.cpp b/src/core/libraries/dialogs/ime_dialog_ui.cpp new file mode 100644 index 000000000..48f5d75dc --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.cpp @@ -0,0 +1,390 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include +#include +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "common/singleton.h" +#include "core/libraries/dialogs/ime_dialog.h" +#include "core/libraries/dialogs/ime_dialog_ui.h" +#include "core/linker.h" +#include "imgui/imgui_std.h" + +using namespace ImGui; + +static constexpr ImVec2 BUTTON_SIZE{100.0f, 30.0f}; + +namespace Libraries::ImeDialog { + +ImeDialogState::ImeDialogState(const OrbisImeDialogParam* param, + const OrbisImeParamExtended* extended) { + if (!param) + return; + + userId = param->userId; + is_multiLine = True(param->option & OrbisImeDialogOption::MULTILINE); + is_numeric = param->type == OrbisImeType::NUMBER; + type = param->type; + enter_label = param->enterLabel; + text_filter = param->filter; + keyboard_filter = extended ? extended->extKeyboardFilter : nullptr; + max_text_length = param->maxTextLength; + text_buffer = param->inputTextBuffer; + + if (param->title) { + std::size_t title_len = std::char_traits::length(param->title); + title.resize(title_len * 4 + 1); + title[title_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->title, title_len, &title[0], title_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert title to utf8 encoding"); + } + } + + if (param->placeholder) { + std::size_t placeholder_len = std::char_traits::length(param->placeholder); + placeholder.resize(placeholder_len * 4 + 1); + placeholder[placeholder_len * 4] = '\0'; + + if (!ConvertOrbisToUTF8(param->placeholder, placeholder_len, &placeholder[0], + placeholder_len * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert placeholder to utf8 encoding"); + } + } + + std::size_t text_len = std::char_traits::length(text_buffer); + if (!ConvertOrbisToUTF8(text_buffer, text_len, current_text.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + } +} + +ImeDialogState::ImeDialogState(ImeDialogState&& other) noexcept + : input_changed(other.input_changed), userId(other.userId), is_multiLine(other.is_multiLine), + is_numeric(other.is_numeric), type(other.type), enter_label(other.enter_label), + text_filter(other.text_filter), keyboard_filter(other.keyboard_filter), + max_text_length(other.max_text_length), text_buffer(other.text_buffer), + title(std::move(other.title)), placeholder(std::move(other.placeholder)), + current_text(other.current_text) { + + other.text_buffer = nullptr; +} + +ImeDialogState& ImeDialogState::operator=(ImeDialogState&& other) { + if (this != &other) { + input_changed = other.input_changed; + userId = other.userId; + is_multiLine = other.is_multiLine; + is_numeric = other.is_numeric; + type = other.type; + enter_label = other.enter_label; + text_filter = other.text_filter; + keyboard_filter = other.keyboard_filter; + max_text_length = other.max_text_length; + text_buffer = other.text_buffer; + title = std::move(other.title); + placeholder = std::move(other.placeholder); + current_text = other.current_text; + + other.text_buffer = nullptr; + } + + return *this; +} + +bool ImeDialogState::CopyTextToOrbisBuffer() { + if (!text_buffer) { + return false; + } + + return ConvertUTF8ToOrbis(current_text.begin(), current_text.capacity(), text_buffer, + max_text_length); +} + +bool ImeDialogState::CallTextFilter() { + if (!text_filter || !input_changed) { + return true; + } + + input_changed = false; + + char16_t src_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 src_text_length = current_text.size(); + char16_t out_text[ORBIS_IME_DIALOG_MAX_TEXT_LENGTH + 1] = {0}; + u32 out_text_length = ORBIS_IME_DIALOG_MAX_TEXT_LENGTH; + + if (!ConvertUTF8ToOrbis(current_text.begin(), src_text_length, src_text, + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to orbis encoding"); + return false; + } + + auto* linker = Common::Singleton::Instance(); + int ret = + linker->ExecuteGuest(text_filter, out_text, &out_text_length, src_text, src_text_length); + + if (ret != 0) { + return false; + } + + if (!ConvertOrbisToUTF8(out_text, out_text_length, current_text.begin(), + ORBIS_IME_DIALOG_MAX_TEXT_LENGTH * 4)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert text to utf8 encoding"); + return false; + } + + return true; +} + +bool ImeDialogState::CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, + u32* out_status) { + if (!keyboard_filter) { + return true; + } + + auto* linker = Common::Singleton::Instance(); + int ret = linker->ExecuteGuest(keyboard_filter, src_keycode, out_keycode, out_status, nullptr); + + return ret == 0; +} + +bool ImeDialogState::ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, + char* utf8_text, std::size_t utf8_text_len) { + + std::fill(utf8_text, utf8_text + utf8_text_len, '\0'); + const ImWchar* orbis_text_ptr = reinterpret_cast(orbis_text); + ImTextStrToUtf8(utf8_text, utf8_text_len, orbis_text_ptr, orbis_text_ptr + orbis_text_len); + + return true; +} + +bool ImeDialogState::ConvertUTF8ToOrbis(const char* utf8_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len) { + + std::fill(orbis_text, orbis_text + orbis_text_len, u'\0'); + ImTextStrFromUtf8(reinterpret_cast(orbis_text), orbis_text_len, utf8_text, nullptr); + + return true; +} + +ImeDialogUi::ImeDialogUi(ImeDialogState* state, OrbisImeDialogStatus* status, + OrbisImeDialogResult* result) + : state(state), status(status), result(result) { + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi::~ImeDialogUi() { + std::scoped_lock lock(draw_mutex); + + Free(); +} + +ImeDialogUi::ImeDialogUi(ImeDialogUi&& other) noexcept + : state(other.state), status(other.status), result(other.result), + first_render(other.first_render) { + + std::scoped_lock lock(draw_mutex, other.draw_mutex); + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } +} + +ImeDialogUi& ImeDialogUi::operator=(ImeDialogUi&& other) { + std::scoped_lock lock(draw_mutex, other.draw_mutex); + Free(); + + state = other.state; + status = other.status; + result = other.result; + first_render = other.first_render; + other.state = nullptr; + other.status = nullptr; + other.result = nullptr; + + if (state && *status == OrbisImeDialogStatus::RUNNING) { + AddLayer(this); + } + + return *this; +} + +void ImeDialogUi::Free() { + RemoveLayer(this); +} + +void ImeDialogUi::Draw() { + std::unique_lock lock{draw_mutex}; + + if (!state) { + return; + } + + if (!status || *status != OrbisImeDialogStatus::RUNNING) { + return; + } + + const auto& ctx = *GetCurrentContext(); + const auto& io = ctx.IO; + + ImVec2 window_size; + + if (state->is_multiLine) { + window_size = {500.0f, 300.0f}; + } else { + window_size = {500.0f, 150.0f}; + } + + CentralizeWindow(); + SetNextWindowSize(window_size); + SetNextWindowCollapsed(false); + + if (first_render || !io.NavActive) { + SetNextWindowFocus(); + } + + if (Begin("IME Dialog##ImeDialog", nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings)) { + DrawPrettyBackground(); + + if (!state->title.empty()) { + SetWindowFontScale(1.7f); + TextUnformatted(state->title.data()); + SetWindowFontScale(1.0f); + } + + if (state->is_multiLine) { + DrawMultiLineInputText(); + } else { + DrawInputText(); + } + + SetCursorPosY(GetCursorPosY() + 10.0f); + + const char* button_text; + + switch (state->enter_label) { + case OrbisImeEnterLabel::GO: + button_text = "Go##ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEARCH: + button_text = "Search##ImeDialogOK"; + break; + case OrbisImeEnterLabel::SEND: + button_text = "Send##ImeDialogOK"; + break; + case OrbisImeEnterLabel::DEFAULT: + default: + button_text = "OK##ImeDialogOK"; + break; + } + + float button_spacing = 10.0f; + float total_button_width = BUTTON_SIZE.x * 2 + button_spacing; + float button_start_pos = (window_size.x - total_button_width) / 2.0f; + + SetCursorPosX(button_start_pos); + + if (Button(button_text, BUTTON_SIZE) || + (!state->is_multiLine && IsKeyPressed(ImGuiKey_Enter))) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::OK; + } + + SameLine(0.0f, button_spacing); + + if (Button("Cancel##ImeDialogCancel", BUTTON_SIZE)) { + *status = OrbisImeDialogStatus::FINISHED; + result->endstatus = OrbisImeDialogEndStatus::USER_CANCELED; + } + } + End(); + + first_render = false; +} + +void ImeDialogUi::DrawInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 0.0f}; + SetCursorPosX(20.0f); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, ImGuiInputTextFlags_CallbackCharFilter, + InputTextCallback, this)) { + state->input_changed = true; + } +} + +void ImeDialogUi::DrawMultiLineInputText() { + ImVec2 input_size = {GetWindowWidth() - 40.0f, 200.0f}; + SetCursorPosX(20.0f); + ImGuiInputTextFlags flags = ImGuiInputTextFlags_CallbackCharFilter | + static_cast(ImGuiInputTextFlags_Multiline); + if (first_render) { + SetKeyboardFocusHere(); + } + const char* placeholder = state->placeholder.empty() ? nullptr : state->placeholder.data(); + if (InputTextEx("##ImeDialogInput", placeholder, state->current_text.begin(), + state->max_text_length, input_size, flags, InputTextCallback, this)) { + state->input_changed = true; + } +} + +int ImeDialogUi::InputTextCallback(ImGuiInputTextCallbackData* data) { + ImeDialogUi* ui = static_cast(data->UserData); + + ASSERT(ui); + + // Should we filter punctuation? + if (ui->state->is_numeric && (data->EventChar < '0' || data->EventChar > '9') && + data->EventChar != '\b' && data->EventChar != ',' && data->EventChar != '.') { + return 1; + } + + if (!ui->state->keyboard_filter) { + return 0; + } + + // ImGui encodes ImWchar32 as multi-byte UTF-8 characters + char* event_char = reinterpret_cast(&data->EventChar); + + // Call the keyboard filter + OrbisImeKeycode src_keycode = { + .keycode = 0, + .character = 0, + .status = 1, // ??? 1 = key pressed, 0 = key released + .type = OrbisImeKeyboardType::ENGLISH_US, // TODO set this to the correct value (maybe use + // the current language?) + .userId = ui->state->userId, + .resourceId = 0, + .timestamp = 0}; + + if (!ui->state->ConvertUTF8ToOrbis(event_char, 4, &src_keycode.character, 1)) { + LOG_ERROR(Lib_ImeDialog, "Failed to convert orbis char to utf8"); + return 0; + } + src_keycode.keycode = src_keycode.character; // TODO set this to the correct value + + u16 out_keycode; + u32 out_status; + + ui->state->CallKeyboardFilter(&src_keycode, &out_keycode, &out_status); + + // TODO. set the keycode + + return 0; +} + +} // namespace Libraries::ImeDialog \ No newline at end of file diff --git a/src/core/libraries/dialogs/ime_dialog_ui.h b/src/core/libraries/dialogs/ime_dialog_ui.h new file mode 100644 index 000000000..96c83954a --- /dev/null +++ b/src/core/libraries/dialogs/ime_dialog_ui.h @@ -0,0 +1,84 @@ +// SPDX-FileCopyrightText: Copyright 2024 shadPS4 Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#include +#include "common/cstring.h" +#include "common/types.h" +#include "core/libraries/dialogs/ime_dialog.h" +#include "imgui/imgui_layer.h" + +namespace Libraries::ImeDialog { + +class ImeDialogUi; + +class ImeDialogState final { + friend ImeDialogUi; + + bool input_changed = false; + + s32 userId{}; + bool is_multiLine{}; + bool is_numeric{}; + OrbisImeType type{}; + OrbisImeEnterLabel enter_label{}; + OrbisImeTextFilter text_filter{}; + OrbisImeExtKeyboardFilter keyboard_filter{}; + u32 max_text_length{}; + char16_t* text_buffer{}; + std::vector title; + std::vector placeholder; + + // A character can hold up to 4 bytes in UTF-8 + Common::CString current_text; + +public: + ImeDialogState(const OrbisImeDialogParam* param = nullptr, + const OrbisImeParamExtended* extended = nullptr); + ImeDialogState(const ImeDialogState& other) = delete; + ImeDialogState(ImeDialogState&& other) noexcept; + ImeDialogState& operator=(ImeDialogState&& other); + + bool CopyTextToOrbisBuffer(); + bool CallTextFilter(); + +private: + bool CallKeyboardFilter(const OrbisImeKeycode* src_keycode, u16* out_keycode, u32* out_status); + + bool ConvertOrbisToUTF8(const char16_t* orbis_text, std::size_t orbis_text_len, char* utf8_text, + std::size_t native_text_len); + bool ConvertUTF8ToOrbis(const char* native_text, std::size_t utf8_text_len, + char16_t* orbis_text, std::size_t orbis_text_len); +}; + +class ImeDialogUi final : public ImGui::Layer { + ImeDialogState* state{}; + OrbisImeDialogStatus* status{}; + OrbisImeDialogResult* result{}; + + bool first_render = true; + std::mutex draw_mutex; + +public: + explicit ImeDialogUi(ImeDialogState* state = nullptr, OrbisImeDialogStatus* status = nullptr, + OrbisImeDialogResult* result = nullptr); + ~ImeDialogUi() override; + ImeDialogUi(const ImeDialogUi& other) = delete; + ImeDialogUi(ImeDialogUi&& other) noexcept; + ImeDialogUi& operator=(ImeDialogUi&& other); + + void Draw() override; + +private: + void Free(); + + void DrawInputText(); + void DrawMultiLineInputText(); + + static int InputTextCallback(ImGuiInputTextCallbackData* data); +}; + +} // namespace Libraries::ImeDialog diff --git a/src/imgui/imgui_config.h b/src/imgui/imgui_config.h index 66d2b25c1..ccb084d94 100644 --- a/src/imgui/imgui_config.h +++ b/src/imgui/imgui_config.h @@ -21,7 +21,6 @@ extern void assert_fail_debug_msg(const char* msg); } \ }()) -#define IMGUI_USE_WCHAR32 #define IMGUI_ENABLE_STB_TRUETYPE #define IMGUI_DEFINE_MATH_OPERATORS @@ -30,3 +29,7 @@ extern void assert_fail_debug_msg(const char* msg); #define IM_VEC4_CLASS_EXTRA \ constexpr ImVec4(float _v) : x(_v), y(_v), z(_v), w(_v) {} + +#ifdef IMGUI_USE_WCHAR32 +#error "This project uses 16 bits wchar standard like Orbis" +#endif \ No newline at end of file From 04ad430115f3ccdf22d013d0c3e2efd38b444df1 Mon Sep 17 00:00:00 2001 From: Alexandre Bouvier Date: Fri, 11 Oct 2024 06:26:30 +0000 Subject: [PATCH 21/22] cmake: add install rules (#1341) --- CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7c58af6bb..3f3d7fcff 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -872,3 +872,11 @@ endif() # Discord RPC target_link_libraries(shadps4 PRIVATE discord-rpc) + +# Install rules +install(TARGETS shadps4 BUNDLE DESTINATION .) + +if (ENABLE_QT_GUI AND CMAKE_SYSTEM_NAME STREQUAL "Linux") + install(FILES ".github/shadps4.desktop" DESTINATION "share/applications") + install(FILES ".github/shadps4.png" DESTINATION "share/icons/hicolor/512x512/apps") +endif() From dc99d3ebfc5ce69ed08a1b83129e99305851c1aa Mon Sep 17 00:00:00 2001 From: KrisCris <38860226+KrisCris@users.noreply.github.com> Date: Fri, 11 Oct 2024 14:27:28 +0800 Subject: [PATCH 22/22] Slightly refactor the game install dirs code (#1329) * fix: game dir dupes on each launch * fix copy around settings_addon_install_dir vector * do not show the path if it is not added * remove installDir from config file * format * moved migration code to save, and added notice * move ui->removeFolderButton->setEnabled to LoadValuesFromConfig * avoid checking duplicates for gameDirs loaded from config * use else condition to switch to the installDirs --- src/common/config.cpp | 38 ++++++++++++++++++------------ src/common/config.h | 5 ++-- src/qt_gui/game_install_dialog.cpp | 5 +--- src/qt_gui/settings_dialog.cpp | 24 ++++--------------- 4 files changed, 32 insertions(+), 40 deletions(-) diff --git a/src/common/config.cpp b/src/common/config.cpp index 37e51c655..78da372e7 100644 --- a/src/common/config.cpp +++ b/src/common/config.cpp @@ -325,9 +325,19 @@ void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h) { main_window_geometry_w = w; main_window_geometry_h = h; } -void setGameInstallDirs(const std::vector& dir) { - settings_install_dirs.resize(dir.size()); - settings_install_dirs = dir; +bool addGameInstallDir(const std::filesystem::path& dir) { + if (std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir) == + settings_install_dirs.end()) { + settings_install_dirs.push_back(dir); + return true; + } + return false; +} +void removeGameInstallDir(const std::filesystem::path& dir) { + auto iterator = std::find(settings_install_dirs.begin(), settings_install_dirs.end(), dir); + if (iterator != settings_install_dirs.end()) { + settings_install_dirs.erase(iterator); + } } void setAddonInstallDir(const std::filesystem::path& dir) { settings_addon_install_dir = dir; @@ -385,7 +395,7 @@ u32 getMainWindowGeometryW() { u32 getMainWindowGeometryH() { return main_window_geometry_h; } -std::vector getGameInstallDirs() { +const std::vector& getGameInstallDirs() { return settings_install_dirs; } std::filesystem::path getAddonInstallDir() { @@ -525,19 +535,14 @@ void load(const std::filesystem::path& path) { m_window_size_W = toml::find_or(gui, "mw_width", 0); m_window_size_H = toml::find_or(gui, "mw_height", 0); + // TODO Migration code, after a major release this should be removed. 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>(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{old_game_install_dir}); + } else { + const auto install_dir_array = + toml::find_or>(gui, "installDirs", {}); + for (const auto& dir : install_dir_array) { settings_install_dirs.emplace_back(std::filesystem::path{dir}); } } @@ -639,6 +644,9 @@ void save(const std::filesystem::path& path) { data["Settings"]["consoleLanguage"] = m_language; + // TODO Migration code, after a major release this should be removed. + data.at("GUI").as_table().erase("installDir"); + std::ofstream file(path, std::ios::out); file << data; file.close(); diff --git a/src/common/config.h b/src/common/config.h index 8e799b55d..591d6dced 100644 --- a/src/common/config.h +++ b/src/common/config.h @@ -84,7 +84,8 @@ bool vkCrashDiagnosticEnabled(); // Gui void setMainWindowGeometry(u32 x, u32 y, u32 w, u32 h); -void setGameInstallDirs(const std::vector& dir); +bool addGameInstallDir(const std::filesystem::path& dir); +void removeGameInstallDir(const std::filesystem::path& dir); void setAddonInstallDir(const std::filesystem::path& dir); void setMainWindowTheme(u32 theme); void setIconSize(u32 size); @@ -103,7 +104,7 @@ u32 getMainWindowGeometryX(); u32 getMainWindowGeometryY(); u32 getMainWindowGeometryW(); u32 getMainWindowGeometryH(); -std::vector getGameInstallDirs(); +const std::vector& getGameInstallDirs(); std::filesystem::path getAddonInstallDir(); u32 getMainWindowTheme(); u32 getIconSize(); diff --git a/src/qt_gui/game_install_dialog.cpp b/src/qt_gui/game_install_dialog.cpp index 4418d9a48..e53c58315 100644 --- a/src/qt_gui/game_install_dialog.cpp +++ b/src/qt_gui/game_install_dialog.cpp @@ -126,10 +126,7 @@ void GameInstallDialog::Save() { return; } } - - std::vector install_dirs; - install_dirs.emplace_back(Common::FS::PathFromQString(gamesDirectory)); - Config::setGameInstallDirs(install_dirs); + Config::addGameInstallDir(Common::FS::PathFromQString(gamesDirectory)); Config::setAddonInstallDir(Common::FS::PathFromQString(addonsDirectory)); const auto config_dir = Common::FS::GetUserPath(Common::FS::PathType::UserDir); Config::save(config_dir / "config.toml"); diff --git a/src/qt_gui/settings_dialog.cpp b/src/qt_gui/settings_dialog.cpp index 1cc5a85e4..efc2cd3eb 100644 --- a/src/qt_gui/settings_dialog.cpp +++ b/src/qt_gui/settings_dialog.cpp @@ -218,19 +218,12 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge // 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 install_dirs = config_dir; - install_dirs.push_back(file_path); - Config::setGameInstallDirs(install_dirs); + if (!file_path.empty() && Config::addGameInstallDir(file_path)) { QListWidgetItem* item = new QListWidgetItem(file_path_string); ui->gameFoldersListWidget->addItem(item); } @@ -246,17 +239,8 @@ SettingsDialog::SettingsDialog(std::span physical_devices, QWidge 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 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); + Config::removeGameInstallDir(file_path); + delete selected_item; } }); } @@ -370,6 +354,8 @@ void SettingsDialog::LoadValuesFromConfig() { QString backButtonBehavior = QString::fromStdString(Config::getBackButtonBehavior()); int index = ui->backButtonBehaviorComboBox->findData(backButtonBehavior); ui->backButtonBehaviorComboBox->setCurrentIndex(index != -1 ? index : 0); + + ui->removeFolderButton->setEnabled(!ui->gameFoldersListWidget->selectedItems().isEmpty()); } void SettingsDialog::InitializeEmulatorLanguages() {