diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp index 32d0c0871c..7936951f31 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp @@ -11,6 +11,7 @@ #include "Common/CommonTypes.h" #include "Common/MsgHandler.h" #include "Core/ConfigManager.h" +#include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/FifoPlayer/FifoAnalyzer.h" #include "Core/FifoPlayer/FifoDataFile.h" @@ -67,6 +68,11 @@ void FifoPlayer::Close() m_FrameRangeEnd = 0; } +bool FifoPlayer::IsPlaying() const +{ + return GetFile() != nullptr && Core::IsRunning(); +} + class FifoPlayer::CPUCore final : public CPUCoreBase { public: diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.h b/Source/Core/Core/FifoPlayer/FifoPlayer.h index b2db00cb0f..48b6b5db7d 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.h +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -56,7 +57,7 @@ extern bool IsPlayingBackFifologWithBrokenEFBCopies; class FifoPlayer { public: - typedef void (*CallbackFunc)(void); + using CallbackFunc = std::function; ~FifoPlayer(); @@ -70,6 +71,8 @@ public: // PowerPC state. std::unique_ptr GetCPUCore(); + bool IsPlaying() const; + FifoDataFile* GetFile() const { return m_File.get(); } u32 GetFrameObjectCount() const; u32 GetCurrentFrameNum() const { return m_CurrentFrame; } diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp index 274b4c1cd0..8d3cfb9aeb 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp @@ -63,6 +63,11 @@ void FifoRecorder::StopRecording() m_RequestedRecordingEnd = true; } +bool FifoRecorder::IsRecordingDone() const +{ + return m_WasRecording && m_File != nullptr; +} + FifoDataFile* FifoRecorder::GetRecordedFile() const { return m_File.get(); diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.h b/Source/Core/Core/FifoPlayer/FifoRecorder.h index 7389d3014d..98f7b34046 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.h +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include #include @@ -13,13 +14,15 @@ class FifoRecorder { public: - typedef void (*CallbackFunc)(void); + using CallbackFunc = std::function; FifoRecorder(); void StartRecording(s32 numFrames, CallbackFunc finishedCb); void StopRecording(); + bool IsRecordingDone() const; + FifoDataFile* GetRecordedFile() const; // Called from video thread @@ -54,7 +57,7 @@ private: bool m_WasRecording = false; bool m_RequestedRecordingEnd = false; s32 m_RecordFramesRemaining = 0; - CallbackFunc m_FinishedCb = nullptr; + CallbackFunc m_FinishedCb; std::unique_ptr m_File; // Accessed only from video thread diff --git a/Source/Core/DolphinQt2/CMakeLists.txt b/Source/Core/DolphinQt2/CMakeLists.txt index ff66dada5a..ea8b9be430 100644 --- a/Source/Core/DolphinQt2/CMakeLists.txt +++ b/Source/Core/DolphinQt2/CMakeLists.txt @@ -18,6 +18,7 @@ set(CMAKE_AUTOMOC ON) set(SRCS AboutDialog.cpp + FIFOPlayerWindow.cpp HotkeyScheduler.cpp Host.cpp InDevelopmentWarning.cpp diff --git a/Source/Core/DolphinQt2/DolphinQt2.vcxproj b/Source/Core/DolphinQt2/DolphinQt2.vcxproj index 8ac0466b49..82d1a653bb 100644 --- a/Source/Core/DolphinQt2/DolphinQt2.vcxproj +++ b/Source/Core/DolphinQt2/DolphinQt2.vcxproj @@ -83,6 +83,7 @@ + @@ -118,6 +119,7 @@ + @@ -198,6 +200,7 @@ + diff --git a/Source/Core/DolphinQt2/FIFOPlayerWindow.cpp b/Source/Core/DolphinQt2/FIFOPlayerWindow.cpp new file mode 100644 index 0000000000..2173914a18 --- /dev/null +++ b/Source/Core/DolphinQt2/FIFOPlayerWindow.cpp @@ -0,0 +1,342 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include "DolphinQt2/FIFOPlayerWindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "Core/Core.h" +#include "Core/FifoPlayer/FifoDataFile.h" +#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h" +#include "Core/FifoPlayer/FifoPlayer.h" +#include "Core/FifoPlayer/FifoRecorder.h" + +#include "DolphinQt2/QtUtils/QueueOnObject.h" +#include "DolphinQt2/Settings.h" + +FIFOPlayerWindow::FIFOPlayerWindow(QWidget* parent) : QDialog(parent) +{ + setWindowTitle(tr("FIFO Player")); + + CreateWidgets(); + ConnectWidgets(); + + UpdateInfo(); + + UpdateControls(); + + FifoPlayer::GetInstance().SetFileLoadedCallback( + [this] { QueueOnObject(this, &FIFOPlayerWindow::OnFIFOLoaded); }); + FifoPlayer::GetInstance().SetFrameWrittenCallback( + [this] { QueueOnObject(this, &FIFOPlayerWindow::OnFIFOLoaded); }); + + connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) { + if (state == Core::State::Running) + OnEmulationStarted(); + else if (state == Core::State::Uninitialized) + OnEmulationStopped(); + }); +} + +FIFOPlayerWindow::~FIFOPlayerWindow() +{ + FifoPlayer::GetInstance().SetFileLoadedCallback({}); + FifoPlayer::GetInstance().SetFrameWrittenCallback({}); +} + +void FIFOPlayerWindow::CreateWidgets() +{ + auto* layout = new QVBoxLayout; + + // Info + auto* info_group = new QGroupBox(tr("File Info")); + auto* info_layout = new QHBoxLayout; + + m_info_label = new QLabel; + info_layout->addWidget(m_info_label); + info_group->setLayout(info_layout); + + m_info_label->setFixedHeight(QFontMetrics(font()).lineSpacing() * 3); + + // Object Range + auto* object_range_group = new QGroupBox(tr("Object Range")); + auto* object_range_layout = new QHBoxLayout; + + m_object_range_from = new QSpinBox; + m_object_range_from_label = new QLabel(tr("From:")); + m_object_range_to = new QSpinBox; + m_object_range_to_label = new QLabel(tr("To:")); + + object_range_layout->addWidget(m_object_range_from_label); + object_range_layout->addWidget(m_object_range_from); + object_range_layout->addWidget(m_object_range_to_label); + object_range_layout->addWidget(m_object_range_to); + object_range_group->setLayout(object_range_layout); + + // Frame Range + auto* frame_range_group = new QGroupBox(tr("Frame Range")); + auto* frame_range_layout = new QHBoxLayout; + + m_frame_range_from = new QSpinBox; + m_frame_range_from_label = new QLabel(tr("From:")); + m_frame_range_to = new QSpinBox; + m_frame_range_to_label = new QLabel(tr("To:")); + + frame_range_layout->addWidget(m_frame_range_from_label); + frame_range_layout->addWidget(m_frame_range_from); + frame_range_layout->addWidget(m_frame_range_to_label); + frame_range_layout->addWidget(m_frame_range_to); + frame_range_group->setLayout(frame_range_layout); + + // Playback Options + auto* playback_group = new QGroupBox(tr("Playback Options")); + auto* playback_layout = new QGridLayout; + m_early_memory_updates = new QCheckBox(tr("Early Memory Updates")); + + playback_layout->addWidget(object_range_group, 0, 0); + playback_layout->addWidget(frame_range_group, 0, 1); + playback_layout->addWidget(m_early_memory_updates, 1, 0, 1, -1); + playback_group->setLayout(playback_layout); + + // Recording Options + auto* recording_group = new QGroupBox(tr("Recording Options")); + auto* recording_layout = new QHBoxLayout; + m_frame_record_count = new QSpinBox; + m_frame_record_count_label = new QLabel(tr("Frames to Record:")); + + m_frame_record_count->setMinimum(1); + m_frame_record_count->setMaximum(3600); + m_frame_record_count->setValue(3); + + recording_layout->addWidget(m_frame_record_count_label); + recording_layout->addWidget(m_frame_record_count); + recording_group->setLayout(recording_layout); + + m_button_box = new QDialogButtonBox(QDialogButtonBox::Close); + + // Action Buttons + m_load = m_button_box->addButton(tr("Load..."), QDialogButtonBox::ActionRole); + m_save = m_button_box->addButton(tr("Save..."), QDialogButtonBox::ActionRole); + m_record = m_button_box->addButton(tr("Record"), QDialogButtonBox::ActionRole); + m_stop = m_button_box->addButton(tr("Stop"), QDialogButtonBox::ActionRole); + + layout->addWidget(info_group); + layout->addWidget(playback_group); + layout->addWidget(recording_group); + layout->addWidget(m_button_box); + + setLayout(layout); +} + +void FIFOPlayerWindow::ConnectWidgets() +{ + connect(m_load, &QPushButton::pressed, this, &FIFOPlayerWindow::LoadRecording); + connect(m_save, &QPushButton::pressed, this, &FIFOPlayerWindow::SaveRecording); + connect(m_record, &QPushButton::pressed, this, &FIFOPlayerWindow::StartRecording); + connect(m_stop, &QPushButton::pressed, this, &FIFOPlayerWindow::StopRecording); + connect(m_button_box, &QDialogButtonBox::rejected, this, &FIFOPlayerWindow::reject); + connect(m_early_memory_updates, &QCheckBox::toggled, this, + &FIFOPlayerWindow::OnEarlyMemoryUpdatesChanged); + connect(m_frame_range_from, static_cast(&QSpinBox::valueChanged), this, + &FIFOPlayerWindow::OnLimitsChanged); + connect(m_frame_range_to, static_cast(&QSpinBox::valueChanged), this, + &FIFOPlayerWindow::OnLimitsChanged); + + connect(m_object_range_from, static_cast(&QSpinBox::valueChanged), this, + &FIFOPlayerWindow::OnLimitsChanged); + connect(m_object_range_to, static_cast(&QSpinBox::valueChanged), this, + &FIFOPlayerWindow::OnLimitsChanged); +} + +void FIFOPlayerWindow::LoadRecording() +{ + QString path = QFileDialog::getOpenFileName(this, tr("Open FIFO log"), QString(), + tr("Dolphin FIFO Log (*.dff)")); + + if (path.isEmpty()) + return; + + emit LoadFIFORequested(path); +} + +void FIFOPlayerWindow::SaveRecording() +{ + QString path = QFileDialog::getSaveFileName(this, tr("Save FIFO log"), QString(), + tr("Dolphin FIFO Log (*.dff)")); + + if (path.isEmpty()) + return; + + FifoDataFile* file = FifoRecorder::GetInstance().GetRecordedFile(); + + bool result = file->Save(path.toStdString()); + + if (!result) + QMessageBox::critical(this, tr("Error"), tr("Failed to save FIFO log.")); +} + +void FIFOPlayerWindow::StartRecording() +{ + // Start recording + FifoRecorder::GetInstance().StartRecording(m_frame_record_count->value(), [this] { + QueueOnObject(this, [this] { OnRecordingDone(); }); + }); + + UpdateControls(); + + UpdateInfo(); +} + +void FIFOPlayerWindow::StopRecording() +{ + FifoRecorder::GetInstance().StopRecording(); + + UpdateControls(); + UpdateInfo(); +} + +void FIFOPlayerWindow::OnEmulationStarted() +{ + UpdateControls(); +} + +void FIFOPlayerWindow::OnEmulationStopped() +{ + // If we have previously been recording, stop now. + if (FifoRecorder::GetInstance().IsRecording()) + StopRecording(); + + UpdateControls(); +} + +void FIFOPlayerWindow::OnRecordingDone() +{ + UpdateInfo(); + UpdateControls(); +} + +void FIFOPlayerWindow::UpdateInfo() +{ + if (FifoPlayer::GetInstance().IsPlaying()) + { + FifoDataFile* file = FifoPlayer::GetInstance().GetFile(); + m_info_label->setText( + tr("%1 frame(s)\n%2 object(s)\nCurrent Frame: %3") + .arg(QString::number(file->GetFrameCount()), + QString::number(FifoPlayer::GetInstance().GetFrameObjectCount()), + QString::number(FifoPlayer::GetInstance().GetCurrentFrameNum()))); + return; + } + + if (FifoRecorder::GetInstance().IsRecordingDone()) + { + FifoDataFile* file = FifoRecorder::GetInstance().GetRecordedFile(); + size_t fifo_bytes = 0; + size_t mem_bytes = 0; + + for (u32 i = 0; i < file->GetFrameCount(); ++i) + { + fifo_bytes += file->GetFrame(i).fifoData.size(); + for (const auto& mem_update : file->GetFrame(i).memoryUpdates) + mem_bytes += mem_update.data.size(); + } + + m_info_label->setText(tr("%1 FIFO bytes\n%2 memory bytes\n%3 frames") + .arg(QString::number(fifo_bytes), QString::number(mem_bytes), + QString::number(file->GetFrameCount()))); + return; + } + + if (Core::IsRunning() && FifoRecorder::GetInstance().IsRecording()) + { + m_info_label->setText(tr("Recording...")); + return; + } + + m_info_label->setText(tr("No file loaded / recorded.")); +} + +void FIFOPlayerWindow::OnFIFOLoaded() +{ + FifoDataFile* file = FifoPlayer::GetInstance().GetFile(); + + auto object_count = FifoPlayer::GetInstance().GetFrameObjectCount(); + auto frame_count = file->GetFrameCount(); + + m_frame_range_to->setMaximum(frame_count); + m_object_range_to->setMaximum(object_count); + + m_frame_range_to->setValue(frame_count); + m_object_range_to->setValue(object_count); + + UpdateInfo(); + UpdateLimits(); + UpdateControls(); +} + +void FIFOPlayerWindow::OnEarlyMemoryUpdatesChanged(bool enabled) +{ + FifoPlayer::GetInstance().SetEarlyMemoryUpdates(enabled); +} + +void FIFOPlayerWindow::OnLimitsChanged() +{ + FifoPlayer& player = FifoPlayer::GetInstance(); + + player.SetFrameRangeStart(m_frame_range_from->value()); + player.SetFrameRangeEnd(m_frame_range_to->value()); + player.SetObjectRangeStart(m_object_range_from->value()); + player.SetObjectRangeEnd(m_object_range_to->value()); + UpdateLimits(); +} + +void FIFOPlayerWindow::UpdateLimits() +{ + m_frame_range_from->setMaximum(std::max(m_frame_range_to->value() - 1, 0)); + m_frame_range_to->setMinimum(m_frame_range_from->value() + 1); + m_object_range_from->setMaximum(std::max(m_object_range_to->value() - 1, 0)); + m_object_range_to->setMinimum(m_object_range_from->value() + 1); +} + +void FIFOPlayerWindow::UpdateControls() +{ + bool running = Core::IsRunning(); + bool is_recording = FifoRecorder::GetInstance().IsRecording(); + bool is_playing = FifoPlayer::GetInstance().IsPlaying(); + + m_frame_range_from->setEnabled(is_playing); + m_frame_range_from_label->setEnabled(is_playing); + m_frame_range_to->setEnabled(is_playing); + m_frame_range_to_label->setEnabled(is_playing); + m_object_range_from->setEnabled(is_playing); + m_object_range_from_label->setEnabled(is_playing); + m_object_range_to->setEnabled(is_playing); + m_object_range_to_label->setEnabled(is_playing); + + m_early_memory_updates->setEnabled(is_playing); + + bool enable_frame_record_count = !is_playing && !is_recording; + + m_frame_record_count_label->setEnabled(enable_frame_record_count); + m_frame_record_count->setEnabled(enable_frame_record_count); + + m_load->setEnabled(!running); + m_record->setEnabled(running && !is_playing); + + m_stop->setVisible(running && is_recording); + m_record->setVisible(!m_stop->isVisible()); + + m_save->setEnabled(FifoRecorder::GetInstance().IsRecordingDone()); +} diff --git a/Source/Core/DolphinQt2/FIFOPlayerWindow.h b/Source/Core/DolphinQt2/FIFOPlayerWindow.h new file mode 100644 index 0000000000..4f422aa8f0 --- /dev/null +++ b/Source/Core/DolphinQt2/FIFOPlayerWindow.h @@ -0,0 +1,62 @@ +// Copyright 2017 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include + +class QCheckBox; +class QDialogButtonBox; +class QLabel; +class QPushButton; +class QSpinBox; + +class FIFOPlayerWindow : public QDialog +{ + Q_OBJECT +public: + explicit FIFOPlayerWindow(QWidget* parent = nullptr); + ~FIFOPlayerWindow(); + +signals: + void LoadFIFORequested(const QString& path); + +private: + void CreateWidgets(); + void ConnectWidgets(); + + void LoadRecording(); + void SaveRecording(); + void StartRecording(); + void StopRecording(); + + void OnEmulationStarted(); + void OnEmulationStopped(); + void OnLimitsChanged(); + void OnEarlyMemoryUpdatesChanged(bool enabled); + void OnRecordingDone(); + void OnFIFOLoaded(); + + void UpdateControls(); + void UpdateInfo(); + void UpdateLimits(); + + QLabel* m_info_label; + QPushButton* m_load; + QPushButton* m_save; + QPushButton* m_record; + QPushButton* m_stop; + QSpinBox* m_frame_range_from; + QLabel* m_frame_range_from_label; + QSpinBox* m_frame_range_to; + QLabel* m_frame_range_to_label; + QSpinBox* m_frame_record_count; + QLabel* m_frame_record_count_label; + QSpinBox* m_object_range_from; + QLabel* m_object_range_from_label; + QSpinBox* m_object_range_to; + QLabel* m_object_range_to_label; + QCheckBox* m_early_memory_updates; + QDialogButtonBox* m_button_box; +}; diff --git a/Source/Core/DolphinQt2/MainWindow.cpp b/Source/Core/DolphinQt2/MainWindow.cpp index 3c7d265aea..06a769554b 100644 --- a/Source/Core/DolphinQt2/MainWindow.cpp +++ b/Source/Core/DolphinQt2/MainWindow.cpp @@ -45,6 +45,7 @@ #include "DolphinQt2/Config/LogWidget.h" #include "DolphinQt2/Config/Mapping/MappingWindow.h" #include "DolphinQt2/Config/SettingsWindow.h" +#include "DolphinQt2/FIFOPlayerWindow.h" #include "DolphinQt2/Host.h" #include "DolphinQt2/HotkeyScheduler.h" #include "DolphinQt2/MainWindow.h" @@ -155,9 +156,15 @@ void MainWindow::CreateComponents() m_stack = new QStackedWidget(this); m_controllers_window = new ControllersWindow(this); m_settings_window = new SettingsWindow(this); + m_hotkey_window = new MappingWindow(this, MappingWindow::Type::MAPPING_HOTKEYS, 0); + m_log_widget = new LogWidget(this); m_log_config_widget = new LogConfigWidget(this); + m_fifo_window = new FIFOPlayerWindow(this); + + connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this, + static_cast(&MainWindow::StartGame)); #if defined(HAVE_XRANDR) && HAVE_XRANDR m_graphics_window = new GraphicsWindow( @@ -215,6 +222,7 @@ void MainWindow::ConnectMenuBar() connect(m_menu_bar, &MenuBar::PerformOnlineUpdate, this, &MainWindow::PerformOnlineUpdate); connect(m_menu_bar, &MenuBar::BootWiiSystemMenu, this, &MainWindow::BootWiiSystemMenu); connect(m_menu_bar, &MenuBar::StartNetPlay, this, &MainWindow::ShowNetPlaySetupDialog); + connect(m_menu_bar, &MenuBar::ShowFIFOPlayer, this, &MainWindow::ShowFIFOPlayer); // Movie connect(m_menu_bar, &MenuBar::PlayRecording, this, &MainWindow::OnPlayRecording); @@ -590,6 +598,13 @@ void MainWindow::ShowNetPlaySetupDialog() m_netplay_setup_dialog->activateWindow(); } +void MainWindow::ShowFIFOPlayer() +{ + m_fifo_window->show(); + m_fifo_window->raise(); + m_fifo_window->activateWindow(); +} + void MainWindow::StateLoad() { QString path = QFileDialog::getOpenFileName(this, tr("Select a File"), QDir::currentPath(), diff --git a/Source/Core/DolphinQt2/MainWindow.h b/Source/Core/DolphinQt2/MainWindow.h index d8db4fc01b..33288e9830 100644 --- a/Source/Core/DolphinQt2/MainWindow.h +++ b/Source/Core/DolphinQt2/MainWindow.h @@ -17,6 +17,7 @@ #include "DolphinQt2/ToolBar.h" struct BootParameters; +class FIFOPlayerWindow; class HotkeyScheduler; class LogConfigWidget; class LogWidget; @@ -98,6 +99,7 @@ private: void ShowAboutDialog(); void ShowHotkeyDialog(); void ShowNetPlaySetupDialog(); + void ShowFIFOPlayer(); void NetPlayInit(); bool NetPlayJoin(); @@ -137,4 +139,5 @@ private: GraphicsWindow* m_graphics_window; LogWidget* m_log_widget; LogConfigWidget* m_log_config_widget; + FIFOPlayerWindow* m_fifo_window; }; diff --git a/Source/Core/DolphinQt2/MenuBar.cpp b/Source/Core/DolphinQt2/MenuBar.cpp index 98ad117a99..485e72190d 100644 --- a/Source/Core/DolphinQt2/MenuBar.cpp +++ b/Source/Core/DolphinQt2/MenuBar.cpp @@ -116,6 +116,8 @@ void MenuBar::AddToolsMenu() AddAction(gc_ipl, tr("PAL"), this, [this] { emit BootGameCubeIPL(DiscIO::Region::PAL); }); AddAction(tools_menu, tr("Start &NetPlay..."), this, &MenuBar::StartNetPlay); + AddAction(tools_menu, tr("FIFO Player"), this, &MenuBar::ShowFIFOPlayer); + tools_menu->addSeparator(); // Label will be set by a NANDRefresh later diff --git a/Source/Core/DolphinQt2/MenuBar.h b/Source/Core/DolphinQt2/MenuBar.h index d9745e7e67..3bbb51345a 100644 --- a/Source/Core/DolphinQt2/MenuBar.h +++ b/Source/Core/DolphinQt2/MenuBar.h @@ -62,6 +62,8 @@ signals: // Tools void BootGameCubeIPL(DiscIO::Region region); + void ShowFIFOPlayer(); + void ShowAboutDialog(); // Options void Configure(); @@ -77,8 +79,6 @@ signals: void GameListPlatformVisibilityToggled(const QString& row, bool visible); void GameListRegionVisibilityToggled(const QString& row, bool visible); - void ShowAboutDialog(); - // Movie void PlayRecording(); void StartRecording();