From b42c3ce21db249d5e3bc04b4f73202e757da317c Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:15:42 +0200 Subject: input_common/tas: Base playback & recording system The base playback system supports up to 8 controllers (specified by `PLAYER_NUMBER` in `tas_input.h`), which all change their inputs simulataneously when `TAS::UpdateThread` is called. The recording system uses the controller debugger to read the state of the first controller and forwards that data to the TASing system for recording. Currently, this process sadly is not frame-perfect and pixel-accurate. Co-authored-by: Naii-the-Baf Co-authored-by: Narr-the-Reg --- src/yuzu/main.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f4e49001d..22489231b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -193,6 +193,7 @@ GMainWindow::GMainWindow() config{std::make_unique()}, vfs{std::make_shared()}, provider{std::make_unique()} { Common::Log::Initialize(); + Settings::values.inputSubsystem = input_subsystem; LoadTranslation(); setAcceptDrops(true); @@ -841,7 +842,7 @@ void GMainWindow::InitializeDebugWidgets() { waitTreeWidget->hide(); debug_menu->addAction(waitTreeWidget->toggleViewAction()); - controller_dialog = new ControllerDialog(this); + controller_dialog = new ControllerDialog(this, input_subsystem.get()); controller_dialog->hide(); debug_menu->addAction(controller_dialog->toggleViewAction()); -- cgit v1.2.3 From f25d6ebc45dc02904ef3ca6ad8efea9a0853d194 Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:20:10 +0200 Subject: settings: File selector & other settings First of all, TASing requires a script to play back. The user can select the parent directory at `System -> Filesystem`, next to an option to pause TAS during loads: This requires a "hacky" setup deeper in the code and will be added in the last commit. Also, Hotkeys are being introduced: CTRL+F5 for playback start/stop, CTRL+F6 for re-reading the script and CTRL+F7 for recording a new script. --- src/yuzu/main.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 22489231b..f3529d151 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1015,6 +1015,19 @@ void GMainWindow::InitializeHotkeys() { render_window->setAttribute(Qt::WA_Hover, true); } }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), + &QShortcut::activated, this, [&] { + Settings::values.tas_enable = !Settings::values.tas_enable; + LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable); + }); + + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), + &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; }); + connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), + &QShortcut::activated, this, [&] { + Settings::values.tas_record = !Settings::values.tas_record; + LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record); + }); } void GMainWindow::SetDefaultUIGeometry() { -- cgit v1.2.3 From 3a7b37238bc85b8220660e8469543449095bc820 Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:21:45 +0200 Subject: main: TAS Playback state label During script playback/recording, the user has to see what happens currently. For that, a new label has been added to the bottom-left corner, always displaying the current state of the TASing system. --- src/yuzu/main.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index f3529d151..10057b9ca 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -30,6 +30,8 @@ #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" +#include "input_common/tas/tas_input.h" + // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( @@ -824,6 +826,12 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); + TASlabel = new QLabel(); + TASlabel->setObjectName(QStringLiteral("TASlabel")); + TASlabel->setText(tr("TAS not running")); + TASlabel->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, TASlabel); + statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); } @@ -2894,6 +2902,7 @@ void GMainWindow::UpdateStatusBar() { return; } + TASlabel->setText(tr(input_subsystem->GetTas()->GetStatusDescription().c_str())); auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); auto& shader_notify = system.GPU().ShaderNotify(); -- cgit v1.2.3 From 4297d2fea2228ff4afe2a7c244fb8b3f1a97491a Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Fri, 18 Jun 2021 16:32:46 +0200 Subject: core: Hacky TAS syncing & load pausing To keep the TAS inputs synced to the game speed even through lag spikes and loading zones, deeper access is required. First, the `TAS::UpdateThread` has to be executed exactly once per frame. This is done by connecting it to the service method the game calls to pass parameters to the GPU: `Service::VI::QueueBuffer`. Second, the loading time of new subareas and/or kingdoms (SMO) can vary. To counteract that, the `CPU_BOOST_MODE` can be detected: In the `APM`-interface, the call to enabling/disabling the boost mode can be caught and forwarded to the TASing system, which can pause the script execution if neccessary and enabled in the settings. --- src/yuzu/main.cpp | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 10057b9ca..0ee0fd8cd 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -826,11 +826,11 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); - TASlabel = new QLabel(); - TASlabel->setObjectName(QStringLiteral("TASlabel")); - TASlabel->setText(tr("TAS not running")); - TASlabel->setFocusPolicy(Qt::NoFocus); - statusBar()->insertPermanentWidget(0, TASlabel); + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setText(tr("TAS not running")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); @@ -2896,13 +2896,28 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } +static std::string GetTasStateDescription(TasInput::TasState state) { + switch (state) { + case TasInput::TasState::RUNNING: + return "Running"; + case TasInput::TasState::RECORDING: + return "Recording"; + case TasInput::TasState::STOPPED: + return "Stopped"; + default: + return "INVALID STATE"; + } +} + void GMainWindow::UpdateStatusBar() { if (emu_thread == nullptr) { status_bar_update_timer.stop(); return; } - TASlabel->setText(tr(input_subsystem->GetTas()->GetStatusDescription().c_str())); + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames)); + auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); auto& shader_notify = system.GPU().ShaderNotify(); -- cgit v1.2.3 From c01a872c8efa90065b6ba1a74079ddf6ec12058f Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 19 Jun 2021 14:38:49 -0500 Subject: config: Move TAS options to it's own menu --- src/yuzu/main.cpp | 67 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 26 deletions(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 0ee0fd8cd..820e31fa7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -19,6 +19,7 @@ #include "common/nvidia_flags.h" #include "configuration/configure_input.h" #include "configuration/configure_per_game.h" +#include "configuration/configure_tas.h" #include "configuration/configure_vibration.h" #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" @@ -750,6 +751,11 @@ void GMainWindow::InitializeWidgets() { statusBar()->addPermanentWidget(label); } + tas_label = new QLabel(); + tas_label->setObjectName(QStringLiteral("TASlabel")); + tas_label->setFocusPolicy(Qt::NoFocus); + statusBar()->insertPermanentWidget(0, tas_label); + // Setup Dock button dock_status_button = new QPushButton(); dock_status_button->setObjectName(QStringLiteral("TogglableStatusBarButton")); @@ -826,12 +832,6 @@ void GMainWindow::InitializeWidgets() { }); statusBar()->insertPermanentWidget(0, renderer_status_button); - tas_label = new QLabel(); - tas_label->setObjectName(QStringLiteral("TASlabel")); - tas_label->setText(tr("TAS not running")); - tas_label->setFocusPolicy(Qt::NoFocus); - statusBar()->insertPermanentWidget(0, tas_label); - statusBar()->setVisible(true); setStyleSheet(QStringLiteral("QStatusBar::item{border: none;}")); } @@ -1024,18 +1024,11 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_enable = !Settings::values.tas_enable; - LOG_INFO(Frontend, "Tas enabled {}", Settings::values.tas_enable); - }); - + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), - &QShortcut::activated, this, [&] { Settings::values.tas_reset = true; }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), - &QShortcut::activated, this, [&] { - Settings::values.tas_record = !Settings::values.tas_record; - LOG_INFO(Frontend, "Tas recording {}", Settings::values.tas_record); - }); + &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Record(); }); } void GMainWindow::SetDefaultUIGeometry() { @@ -1154,6 +1147,7 @@ void GMainWindow::ConnectMenuEvents() { connect(ui.action_Open_FAQ, &QAction::triggered, this, &GMainWindow::OnOpenFAQ); connect(ui.action_Restart, &QAction::triggered, this, [this] { BootGame(QString(game_path)); }); connect(ui.action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); + connect(ui.action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas); connect(ui.action_Configure_Current_Game, &QAction::triggered, this, &GMainWindow::OnConfigurePerGame); @@ -2720,6 +2714,19 @@ void GMainWindow::OnConfigure() { UpdateStatusButtons(); } +void GMainWindow::OnConfigureTas() { + const auto& system = Core::System::GetInstance(); + ConfigureTasDialog dialog(this); + const auto result = dialog.exec(); + + if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { + Settings::RestoreGlobalState(system.IsPoweredOn()); + return; + } else if (result == QDialog::Accepted) { + dialog.ApplyConfiguration(); + } +} + void GMainWindow::OnConfigurePerGame() { const u64 title_id = Core::System::GetInstance().CurrentProcess()->GetTitleID(); OpenPerGameConfiguration(title_id, game_path.toStdString()); @@ -2898,14 +2905,14 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie static std::string GetTasStateDescription(TasInput::TasState state) { switch (state) { - case TasInput::TasState::RUNNING: - return "Running"; - case TasInput::TasState::RECORDING: - return "Recording"; - case TasInput::TasState::STOPPED: - return "Stopped"; - default: - return "INVALID STATE"; + case TasInput::TasState::Running: + return "Running"; + case TasInput::TasState::Recording: + return "Recording"; + case TasInput::TasState::Stopped: + return "Stopped"; + default: + return "INVALID STATE"; } } @@ -2915,8 +2922,16 @@ void GMainWindow::UpdateStatusBar() { return; } - auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3").arg(tr(GetTasStateDescription(tas_status).c_str())).arg(current_tas_frame).arg(total_tas_frames)); + if (Settings::values.tas_enable) { + auto [tas_status, current_tas_frame, total_tas_frames] = + input_subsystem->GetTas()->GetStatus(); + tas_label->setText(tr("%1 TAS %2/%3") + .arg(tr(GetTasStateDescription(tas_status).c_str())) + .arg(current_tas_frame) + .arg(total_tas_frames)); + } else { + tas_label->clear(); + } auto& system = Core::System::GetInstance(); auto results = system.GetAndResetPerfStats(); -- cgit v1.2.3 From f078b15565c8cab08587b8f8629d878615705cfb Mon Sep 17 00:00:00 2001 From: MonsterDruide1 <5958456@gmail.com> Date: Sun, 20 Jun 2021 00:04:34 +0200 Subject: input_common/tas: Fallback to simple update --- src/yuzu/main.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 820e31fa7..6c2835a2f 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -196,7 +196,6 @@ GMainWindow::GMainWindow() config{std::make_unique()}, vfs{std::make_shared()}, provider{std::make_unique()} { Common::Log::Initialize(); - Settings::values.inputSubsystem = input_subsystem; LoadTranslation(); setAcceptDrops(true); @@ -2903,16 +2902,17 @@ void GMainWindow::UpdateWindowTitle(std::string_view title_name, std::string_vie } } -static std::string GetTasStateDescription(TasInput::TasState state) { - switch (state) { +QString GMainWindow::GetTasStateDescription() const { + auto [tas_status, current_tas_frame, total_tas_frames] = input_subsystem->GetTas()->GetStatus(); + switch (tas_status) { case TasInput::TasState::Running: - return "Running"; + return tr("TAS state: Running %1/%2").arg(current_tas_frame).arg(total_tas_frames); case TasInput::TasState::Recording: - return "Recording"; + return tr("TAS state: Recording %1").arg(total_tas_frames); case TasInput::TasState::Stopped: - return "Stopped"; + return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: - return "INVALID STATE"; + return tr("INVALID TAS STATE"); } } @@ -2923,12 +2923,7 @@ void GMainWindow::UpdateStatusBar() { } if (Settings::values.tas_enable) { - auto [tas_status, current_tas_frame, total_tas_frames] = - input_subsystem->GetTas()->GetStatus(); - tas_label->setText(tr("%1 TAS %2/%3") - .arg(tr(GetTasStateDescription(tas_status).c_str())) - .arg(current_tas_frame) - .arg(total_tas_frames)); + tas_label->setText(GetTasStateDescription()); } else { tas_label->clear(); } -- cgit v1.2.3 From 9bb6580d89efb76534d9395bc052459d5f58e7c4 Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 26 Jun 2021 10:38:39 -0500 Subject: input_common/tas: overwrite file dialog --- src/yuzu/main.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 6c2835a2f..560de89af 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1027,7 +1027,16 @@ void GMainWindow::InitializeHotkeys() { connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), - &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Record(); }); + &QShortcut::activated, this, [&] { + bool is_recording = input_subsystem->GetTas()->Record(); + if (!is_recording) { + QMessageBox::StandardButton reply; + reply = QMessageBox::question(this, tr("TAS Recording"), + tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No); + input_subsystem->GetTas()->SaveRecording(reply == QMessageBox::Yes); + } + }); } void GMainWindow::SetDefaultUIGeometry() { -- cgit v1.2.3 From e6c4bf52f0eb2c9c78e983ffbc667891463d3253 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 27 Jun 2021 14:02:38 -0500 Subject: input_common/tas: Add swap controller --- src/yuzu/main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 560de89af..7d12fcca7 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -2921,7 +2921,7 @@ QString GMainWindow::GetTasStateDescription() const { case TasInput::TasState::Stopped: return tr("TAS state: Idle %1/%2").arg(current_tas_frame).arg(total_tas_frames); default: - return tr("INVALID TAS STATE"); + return tr("TAS State: Invalid"); } } -- cgit v1.2.3 From 5401cf6eb583092ed144d0f30fb6221a0ab25fed Mon Sep 17 00:00:00 2001 From: german77 Date: Fri, 9 Jul 2021 23:30:58 -0500 Subject: input_common/tas: new update method --- src/yuzu/main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 7d12fcca7..ea77caad5 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -31,8 +31,6 @@ #include "core/hle/service/am/applet_oe.h" #include "core/hle/service/am/applets/applets.h" -#include "input_common/tas/tas_input.h" - // These are wrappers to avoid the calls to CreateDirectory and CreateFile because of the Windows // defines. static FileSys::VirtualDir VfsFilesystemCreateDirectoryWrapper( @@ -105,6 +103,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual #include "core/perf_stats.h" #include "core/telemetry_session.h" #include "input_common/main.h" +#include "input_common/tas/tas_input.h" #include "util/overlay_dialog.h" #include "video_core/gpu.h" #include "video_core/renderer_base.h" -- cgit v1.2.3 From 75d8ec1e9f474ce6c2bfc0b8ebe574ca44f9f3d8 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 25 Jul 2021 20:52:19 -0500 Subject: UI: Relocate tas menu and add brief description --- src/yuzu/main.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'src/yuzu/main.cpp') diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index ea77caad5..3c2824362 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1022,18 +1022,25 @@ void GMainWindow::InitializeHotkeys() { } }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this), - &QShortcut::activated, this, [&] { input_subsystem->GetTas()->StartStop(); }); + &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } + input_subsystem->GetTas()->StartStop(); + }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this), &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); }); connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this), &QShortcut::activated, this, [&] { + if (!emulation_running) { + return; + } bool is_recording = input_subsystem->GetTas()->Record(); if (!is_recording) { - QMessageBox::StandardButton reply; - reply = QMessageBox::question(this, tr("TAS Recording"), - tr("Overwrite file of player 1?"), - QMessageBox::Yes | QMessageBox::No); - input_subsystem->GetTas()->SaveRecording(reply == QMessageBox::Yes); + const auto res = QMessageBox::question(this, tr("TAS Recording"), + tr("Overwrite file of player 1?"), + QMessageBox::Yes | QMessageBox::No); + input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes); } }); } @@ -1487,6 +1494,8 @@ void GMainWindow::ShutdownGame() { game_list->show(); } game_list->SetFilterFocus(); + tas_label->clear(); + input_subsystem->GetTas()->Stop(); render_window->removeEventFilter(render_window); render_window->setAttribute(Qt::WA_Hover, false); -- cgit v1.2.3