mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-02-03 04:56:10 +00:00
Yellow squiggly lines begone! Done automatically on .cpp files through `run-clang-tidy`, with manual corrections to the mistakes. If an import is directly used, but is technically unnecessary since it's recursively imported by something else, it is *not* removed. The tool doesn't touch .h files, so I did some of them by hand while fixing errors due to old recursive imports. Not everything is removed, but the cleanup should be substantial enough. Because this done on Linux, code that isn't used on it is mostly untouched. (Hopefully no open PR is depending on these imports...)
432 lines
14 KiB
C++
432 lines
14 KiB
C++
// Copyright 2017 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
|
|
|
|
#include <QCheckBox>
|
|
#include <QDialogButtonBox>
|
|
#include <QEvent>
|
|
#include <QGroupBox>
|
|
#include <QHBoxLayout>
|
|
#include <QIcon>
|
|
#include <QKeyEvent>
|
|
#include <QKeySequence>
|
|
#include <QLabel>
|
|
#include <QPushButton>
|
|
#include <QSpinBox>
|
|
#include <QTabWidget>
|
|
#include <QVBoxLayout>
|
|
|
|
#include "Core/Core.h"
|
|
#include "Core/FifoPlayer/FifoDataFile.h"
|
|
#include "Core/FifoPlayer/FifoPlayer.h"
|
|
#include "Core/FifoPlayer/FifoRecorder.h"
|
|
#include "Core/System.h"
|
|
|
|
#include "DolphinQt/Config/ToolTipControls/ToolTipCheckBox.h"
|
|
#include "DolphinQt/FIFO/FIFOAnalyzer.h"
|
|
#include "DolphinQt/QtUtils/DolphinFileDialog.h"
|
|
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
|
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
|
#include "DolphinQt/Resources.h"
|
|
#include "DolphinQt/Settings.h"
|
|
|
|
FIFOPlayerWindow::FIFOPlayerWindow(FifoPlayer& fifo_player, FifoRecorder& fifo_recorder,
|
|
QWidget* parent)
|
|
: QWidget(parent), m_fifo_player(fifo_player), m_fifo_recorder(fifo_recorder)
|
|
{
|
|
setWindowTitle(tr("FIFO Player"));
|
|
setWindowIcon(Resources::GetAppIcon());
|
|
|
|
CreateWidgets();
|
|
LoadSettings();
|
|
ConnectWidgets();
|
|
AddDescriptions();
|
|
|
|
UpdateInfo();
|
|
|
|
UpdateControls();
|
|
|
|
m_fifo_player.SetFileLoadedCallback(
|
|
[this] { QueueOnObject(this, &FIFOPlayerWindow::OnFIFOLoaded); });
|
|
m_fifo_player.SetFrameWrittenCallback([this] {
|
|
QueueOnObject(this, [this] {
|
|
UpdateInfo();
|
|
UpdateControls();
|
|
});
|
|
});
|
|
|
|
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
|
// We don't want to trigger OnEmulationStarted when going from Paused to Running,
|
|
// and nothing in UpdateControls treats Paused and Running differently
|
|
if (state == Core::State::Paused)
|
|
state = Core::State::Running;
|
|
|
|
// Skip redundant updates
|
|
if (state == m_emu_state)
|
|
return;
|
|
|
|
UpdateControls();
|
|
|
|
if (state == Core::State::Running)
|
|
OnEmulationStarted();
|
|
else if (state == Core::State::Uninitialized)
|
|
OnEmulationStopped();
|
|
|
|
m_emu_state = state;
|
|
});
|
|
|
|
installEventFilter(this);
|
|
}
|
|
|
|
FIFOPlayerWindow::~FIFOPlayerWindow()
|
|
{
|
|
Settings::GetQSettings().setValue(QStringLiteral("fifoplayerwindow/geometry"), saveGeometry());
|
|
|
|
m_fifo_player.SetFileLoadedCallback({});
|
|
m_fifo_player.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 ToolTipCheckBox(tr("Early Memory Updates"));
|
|
m_loop = new ToolTipCheckBox(tr("Loop"));
|
|
|
|
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);
|
|
playback_layout->addWidget(m_loop, 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->addStretch();
|
|
layout->addWidget(m_button_box);
|
|
|
|
m_main_widget = new QWidget(this);
|
|
m_main_widget->setLayout(layout);
|
|
|
|
m_tab_widget = new QTabWidget(this);
|
|
|
|
m_analyzer = new FIFOAnalyzer(m_fifo_player);
|
|
|
|
m_tab_widget->addTab(m_main_widget, tr("Play / Record"));
|
|
m_tab_widget->addTab(m_analyzer, tr("Analyze"));
|
|
|
|
auto* tab_layout = new QVBoxLayout;
|
|
tab_layout->addWidget(m_tab_widget);
|
|
|
|
setLayout(tab_layout);
|
|
}
|
|
|
|
void FIFOPlayerWindow::LoadSettings()
|
|
{
|
|
restoreGeometry(
|
|
Settings::GetQSettings().value(QStringLiteral("fifoplayerwindow/geometry")).toByteArray());
|
|
|
|
m_early_memory_updates->setChecked(Config::Get(Config::MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES));
|
|
m_loop->setChecked(Config::Get(Config::MAIN_FIFOPLAYER_LOOP_REPLAY));
|
|
}
|
|
|
|
void FIFOPlayerWindow::ConnectWidgets()
|
|
{
|
|
connect(m_load, &QPushButton::clicked, this, &FIFOPlayerWindow::LoadRecording);
|
|
connect(m_save, &QPushButton::clicked, this, &FIFOPlayerWindow::SaveRecording);
|
|
connect(m_record, &QPushButton::clicked, this, &FIFOPlayerWindow::StartRecording);
|
|
connect(m_stop, &QPushButton::clicked, this, &FIFOPlayerWindow::StopRecording);
|
|
connect(m_button_box, &QDialogButtonBox::rejected, this, &FIFOPlayerWindow::hide);
|
|
connect(m_early_memory_updates, &QCheckBox::toggled, this, &FIFOPlayerWindow::OnConfigChanged);
|
|
connect(m_loop, &QCheckBox::toggled, this, &FIFOPlayerWindow::OnConfigChanged);
|
|
|
|
connect(m_frame_range_from, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
|
|
connect(m_frame_range_to, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
|
|
|
|
connect(m_object_range_from, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
|
|
connect(m_object_range_to, &QSpinBox::valueChanged, this, &FIFOPlayerWindow::OnLimitsChanged);
|
|
}
|
|
|
|
void FIFOPlayerWindow::AddDescriptions()
|
|
{
|
|
static const char TR_MEMORY_UPDATES_DESCRIPTION[] = QT_TR_NOOP(
|
|
"If enabled, then all memory updates happen at once before the first frame.<br><br>"
|
|
"Causes issues with many fifologs, but can be useful for testing.<br><br>"
|
|
"<dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>");
|
|
static const char TR_LOOP_DESCRIPTION[] =
|
|
QT_TR_NOOP("If unchecked, then playback of the fifolog stops after the final frame.<br><br>"
|
|
"This is generally only useful when a frame-dumping option is enabled.<br><br>"
|
|
"<dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>");
|
|
|
|
m_early_memory_updates->SetDescription(tr(TR_MEMORY_UPDATES_DESCRIPTION));
|
|
m_loop->SetDescription(tr(TR_LOOP_DESCRIPTION));
|
|
}
|
|
|
|
void FIFOPlayerWindow::LoadRecording()
|
|
{
|
|
QString path = DolphinFileDialog::getOpenFileName(this, tr("Open FIFO Log"), QString(),
|
|
tr("Dolphin FIFO Log (*.dff)"));
|
|
|
|
if (path.isEmpty())
|
|
return;
|
|
|
|
emit LoadFIFORequested(path);
|
|
}
|
|
|
|
void FIFOPlayerWindow::SaveRecording()
|
|
{
|
|
QString path = DolphinFileDialog::getSaveFileName(this, tr("Save FIFO Log"), QString(),
|
|
tr("Dolphin FIFO Log (*.dff)"));
|
|
|
|
if (path.isEmpty())
|
|
return;
|
|
|
|
FifoDataFile* file = m_fifo_recorder.GetRecordedFile();
|
|
|
|
bool result = file->Save(path.toStdString());
|
|
|
|
if (!result)
|
|
{
|
|
ModalMessageBox::critical(this, tr("Error"), tr("Failed to save FIFO log."));
|
|
}
|
|
}
|
|
|
|
void FIFOPlayerWindow::StartRecording()
|
|
{
|
|
// Start recording
|
|
m_fifo_recorder.StartRecording(m_frame_record_count->value(),
|
|
[this] { QueueOnObject(this, [this] { OnRecordingDone(); }); });
|
|
|
|
UpdateControls();
|
|
|
|
UpdateInfo();
|
|
}
|
|
|
|
void FIFOPlayerWindow::StopRecording()
|
|
{
|
|
m_fifo_recorder.StopRecording();
|
|
|
|
UpdateControls();
|
|
UpdateInfo();
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnEmulationStarted()
|
|
{
|
|
if (m_fifo_player.GetFile())
|
|
OnFIFOLoaded();
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnEmulationStopped()
|
|
{
|
|
// If we have previously been recording, stop now.
|
|
if (m_fifo_recorder.IsRecording())
|
|
StopRecording();
|
|
|
|
// When emulation stops, switch away from the analyzer tab, as it no longer shows anything useful
|
|
m_tab_widget->setCurrentWidget(m_main_widget);
|
|
m_analyzer->Update();
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnRecordingDone()
|
|
{
|
|
UpdateInfo();
|
|
UpdateControls();
|
|
}
|
|
|
|
void FIFOPlayerWindow::UpdateInfo()
|
|
{
|
|
if (m_fifo_player.IsPlaying())
|
|
{
|
|
FifoDataFile* file = m_fifo_player.GetFile();
|
|
m_info_label->setText(tr("%1 frame(s)\n%2 object(s)\nCurrent Frame: %3")
|
|
.arg(QString::number(file->GetFrameCount()),
|
|
QString::number(m_fifo_player.GetCurrentFrameObjectCount()),
|
|
QString::number(m_fifo_player.GetCurrentFrameNum())));
|
|
return;
|
|
}
|
|
|
|
if (m_fifo_recorder.IsRecordingDone())
|
|
{
|
|
FifoDataFile* file = m_fifo_recorder.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(Core::System::GetInstance()) && m_fifo_recorder.IsRecording())
|
|
{
|
|
m_info_label->setText(tr("Recording..."));
|
|
return;
|
|
}
|
|
|
|
m_info_label->setText(tr("No file loaded / recorded."));
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnFIFOLoaded()
|
|
{
|
|
FifoDataFile* file = m_fifo_player.GetFile();
|
|
|
|
auto object_count = m_fifo_player.GetMaxObjectCount();
|
|
auto frame_count = file->GetFrameCount();
|
|
|
|
m_frame_range_to->setMaximum(frame_count - 1);
|
|
m_object_range_to->setMaximum(object_count - 1);
|
|
|
|
m_frame_range_from->setValue(0);
|
|
m_object_range_from->setValue(0);
|
|
m_frame_range_to->setValue(frame_count - 1);
|
|
m_object_range_to->setValue(object_count - 1);
|
|
|
|
UpdateInfo();
|
|
UpdateLimits();
|
|
UpdateControls();
|
|
|
|
m_analyzer->Update();
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnConfigChanged()
|
|
{
|
|
Config::SetBase(Config::MAIN_FIFOPLAYER_EARLY_MEMORY_UPDATES,
|
|
m_early_memory_updates->isChecked());
|
|
Config::SetBase(Config::MAIN_FIFOPLAYER_LOOP_REPLAY, m_loop->isChecked());
|
|
}
|
|
|
|
void FIFOPlayerWindow::OnLimitsChanged()
|
|
{
|
|
FifoPlayer& player = m_fifo_player;
|
|
|
|
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(m_frame_range_to->value());
|
|
m_frame_range_to->setMinimum(m_frame_range_from->value());
|
|
m_object_range_from->setMaximum(m_object_range_to->value());
|
|
m_object_range_to->setMinimum(m_object_range_from->value());
|
|
}
|
|
|
|
void FIFOPlayerWindow::UpdateControls()
|
|
{
|
|
Core::System& system = Core::System::GetInstance();
|
|
const bool core_is_uninitialized = Core::IsUninitialized(system);
|
|
const bool core_is_running = Core::IsRunning(system);
|
|
const bool is_recording = m_fifo_recorder.IsRecording();
|
|
const bool is_playing = m_fifo_player.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);
|
|
|
|
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(core_is_uninitialized);
|
|
m_record->setEnabled(core_is_running && !is_playing);
|
|
|
|
m_stop->setVisible(core_is_running && is_recording);
|
|
m_record->setVisible(!m_stop->isVisible());
|
|
|
|
m_save->setEnabled(m_fifo_recorder.IsRecordingDone());
|
|
}
|
|
|
|
bool FIFOPlayerWindow::eventFilter(QObject* object, QEvent* event)
|
|
{
|
|
// Close when escape is pressed
|
|
if (event->type() == QEvent::KeyPress)
|
|
{
|
|
if (static_cast<QKeyEvent*>(event)->matches(QKeySequence::Cancel))
|
|
hide();
|
|
}
|
|
|
|
return false;
|
|
}
|