mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-07-24 06:39:46 -06:00
Move DolphinQt2 to DolphinQt
This commit is contained in:
500
Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp
Normal file
500
Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp
Normal file
@ -0,0 +1,500 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/FIFO/FIFOAnalyzer.h"
|
||||
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QHeaderView>
|
||||
#include <QLabel>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSplitter>
|
||||
#include <QTextBrowser>
|
||||
#include <QTreeWidget>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include "Common/Assert.h"
|
||||
#include "Common/Swap.h"
|
||||
#include "Core/FifoPlayer/FifoPlayer.h"
|
||||
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
|
||||
constexpr int FRAME_ROLE = Qt::UserRole;
|
||||
constexpr int OBJECT_ROLE = Qt::UserRole + 1;
|
||||
|
||||
FIFOAnalyzer::FIFOAnalyzer()
|
||||
{
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
UpdateTree();
|
||||
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
m_object_splitter->restoreState(
|
||||
settings.value(QStringLiteral("fifoanalyzer/objectsplitter")).toByteArray());
|
||||
m_search_splitter->restoreState(
|
||||
settings.value(QStringLiteral("fifoanalyzer/searchsplitter")).toByteArray());
|
||||
|
||||
m_detail_list->setFont(Settings::Instance().GetDebugFont());
|
||||
m_entry_detail_browser->setFont(Settings::Instance().GetDebugFont());
|
||||
|
||||
connect(&Settings::Instance(), &Settings::DebugFontChanged, [this] {
|
||||
m_detail_list->setFont(Settings::Instance().GetDebugFont());
|
||||
m_entry_detail_browser->setFont(Settings::Instance().GetDebugFont());
|
||||
});
|
||||
}
|
||||
|
||||
FIFOAnalyzer::~FIFOAnalyzer()
|
||||
{
|
||||
auto& settings = Settings::GetQSettings();
|
||||
|
||||
settings.setValue(QStringLiteral("fifoanalyzer/objectsplitter"), m_object_splitter->saveState());
|
||||
settings.setValue(QStringLiteral("fifoanalyzer/searchsplitter"), m_search_splitter->saveState());
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::CreateWidgets()
|
||||
{
|
||||
m_tree_widget = new QTreeWidget;
|
||||
m_detail_list = new QListWidget;
|
||||
m_entry_detail_browser = new QTextBrowser;
|
||||
|
||||
m_object_splitter = new QSplitter(Qt::Horizontal);
|
||||
|
||||
m_object_splitter->addWidget(m_tree_widget);
|
||||
m_object_splitter->addWidget(m_detail_list);
|
||||
|
||||
m_tree_widget->header()->hide();
|
||||
|
||||
m_search_box = new QGroupBox(tr("Search Current Object"));
|
||||
m_search_edit = new QLineEdit;
|
||||
m_search_new = new QPushButton(tr("Search"));
|
||||
m_search_next = new QPushButton(tr("Next Match"));
|
||||
m_search_previous = new QPushButton(tr("Previous Match"));
|
||||
m_search_label = new QLabel;
|
||||
|
||||
auto* box_layout = new QHBoxLayout;
|
||||
|
||||
box_layout->addWidget(m_search_edit);
|
||||
box_layout->addWidget(m_search_new);
|
||||
box_layout->addWidget(m_search_next);
|
||||
box_layout->addWidget(m_search_previous);
|
||||
box_layout->addWidget(m_search_label);
|
||||
|
||||
m_search_box->setLayout(box_layout);
|
||||
|
||||
m_search_box->setMaximumHeight(m_search_box->minimumSizeHint().height());
|
||||
|
||||
m_search_splitter = new QSplitter(Qt::Vertical);
|
||||
|
||||
m_search_splitter->addWidget(m_object_splitter);
|
||||
m_search_splitter->addWidget(m_entry_detail_browser);
|
||||
m_search_splitter->addWidget(m_search_box);
|
||||
|
||||
auto* layout = new QHBoxLayout;
|
||||
layout->addWidget(m_search_splitter);
|
||||
|
||||
setLayout(layout);
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::ConnectWidgets()
|
||||
{
|
||||
connect(m_tree_widget, &QTreeWidget::itemSelectionChanged, this, &FIFOAnalyzer::UpdateDetails);
|
||||
connect(m_detail_list, &QListWidget::itemSelectionChanged, this,
|
||||
&FIFOAnalyzer::UpdateDescription);
|
||||
|
||||
connect(m_search_new, &QPushButton::pressed, this, &FIFOAnalyzer::BeginSearch);
|
||||
connect(m_search_next, &QPushButton::pressed, this, &FIFOAnalyzer::FindNext);
|
||||
connect(m_search_previous, &QPushButton::pressed, this, &FIFOAnalyzer::FindPrevious);
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::Update()
|
||||
{
|
||||
UpdateTree();
|
||||
UpdateDetails();
|
||||
UpdateDescription();
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::UpdateTree()
|
||||
{
|
||||
m_tree_widget->clear();
|
||||
|
||||
if (!FifoPlayer::GetInstance().IsPlaying())
|
||||
{
|
||||
m_tree_widget->addTopLevelItem(new QTreeWidgetItem({tr("No recording loaded.")}));
|
||||
return;
|
||||
}
|
||||
|
||||
auto* recording_item = new QTreeWidgetItem({tr("Recording")});
|
||||
|
||||
m_tree_widget->addTopLevelItem(recording_item);
|
||||
|
||||
auto* file = FifoPlayer::GetInstance().GetFile();
|
||||
|
||||
int object_count = FifoPlayer::GetInstance().GetFrameObjectCount();
|
||||
int frame_count = file->GetFrameCount();
|
||||
|
||||
for (int i = 0; i < frame_count; i++)
|
||||
{
|
||||
auto* frame_item = new QTreeWidgetItem({tr("Frame %1").arg(i)});
|
||||
|
||||
recording_item->addChild(frame_item);
|
||||
|
||||
for (int j = 0; j < object_count; j++)
|
||||
{
|
||||
auto* object_item = new QTreeWidgetItem({tr("Object %1").arg(j)});
|
||||
|
||||
frame_item->addChild(object_item);
|
||||
|
||||
object_item->setData(0, FRAME_ROLE, i);
|
||||
object_item->setData(0, OBJECT_ROLE, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::UpdateDetails()
|
||||
{
|
||||
m_detail_list->clear();
|
||||
m_object_data_offsets.clear();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty() || items[0]->data(0, OBJECT_ROLE).isNull())
|
||||
return;
|
||||
|
||||
int frame_nr = items[0]->data(0, FRAME_ROLE).toInt();
|
||||
int object_nr = items[0]->data(0, OBJECT_ROLE).toInt();
|
||||
|
||||
const auto& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr);
|
||||
const auto& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr);
|
||||
|
||||
const u8* objectdata_start = &fifo_frame.fifoData[frame_info.objectStarts[object_nr]];
|
||||
const u8* objectdata_end = &fifo_frame.fifoData[frame_info.objectEnds[object_nr]];
|
||||
const u8* objectdata = objectdata_start;
|
||||
const std::ptrdiff_t obj_offset =
|
||||
objectdata_start - &fifo_frame.fifoData[frame_info.objectStarts[0]];
|
||||
|
||||
int cmd = *objectdata++;
|
||||
int stream_size = Common::swap16(objectdata);
|
||||
objectdata += 2;
|
||||
QString new_label = QStringLiteral("%1: %2 %3 ")
|
||||
.arg(obj_offset, 8, 16, QLatin1Char('0'))
|
||||
.arg(cmd, 2, 16, QLatin1Char('0'))
|
||||
.arg(stream_size, 4, 16, QLatin1Char('0'));
|
||||
if (stream_size && ((objectdata_end - objectdata) % stream_size))
|
||||
new_label += tr("NOTE: Stream size doesn't match actual data length\n");
|
||||
|
||||
while (objectdata < objectdata_end)
|
||||
new_label += QStringLiteral("%1").arg(*objectdata++, 2, 16, QLatin1Char('0'));
|
||||
|
||||
m_detail_list->addItem(new_label);
|
||||
m_object_data_offsets.push_back(0);
|
||||
|
||||
// Between objectdata_end and next_objdata_start, there are register setting commands
|
||||
if (object_nr + 1 < static_cast<int>(frame_info.objectStarts.size()))
|
||||
{
|
||||
const u8* next_objdata_start = &fifo_frame.fifoData[frame_info.objectStarts[object_nr + 1]];
|
||||
while (objectdata < next_objdata_start)
|
||||
{
|
||||
m_object_data_offsets.push_back(objectdata - objectdata_start);
|
||||
int new_offset = objectdata - &fifo_frame.fifoData[frame_info.objectStarts[0]];
|
||||
int command = *objectdata++;
|
||||
switch (command)
|
||||
{
|
||||
case OpcodeDecoder::GX_NOP:
|
||||
new_label = QStringLiteral("NOP");
|
||||
break;
|
||||
|
||||
case 0x44:
|
||||
new_label = QStringLiteral("0x44");
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_CMD_INVL_VC:
|
||||
new_label = QStringLiteral("GX_CMD_INVL_VC");
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_CP_REG:
|
||||
{
|
||||
u32 cmd2 = *objectdata++;
|
||||
u32 value = Common::swap32(objectdata);
|
||||
objectdata += 4;
|
||||
|
||||
new_label = QStringLiteral("CP %1 %2")
|
||||
.arg(cmd2, 2, 16, QLatin1Char('0'))
|
||||
.arg(value, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_XF_REG:
|
||||
{
|
||||
u32 cmd2 = Common::swap32(objectdata);
|
||||
objectdata += 4;
|
||||
|
||||
u8 streamSize = ((cmd2 >> 16) & 15) + 1;
|
||||
|
||||
const u8* stream_start = objectdata;
|
||||
const u8* stream_end = stream_start + streamSize * 4;
|
||||
|
||||
new_label = QStringLiteral("XF %1 ").arg(cmd2, 16, 8, QLatin1Char('0'));
|
||||
while (objectdata < stream_end)
|
||||
{
|
||||
new_label += QStringLiteral("%1").arg(*objectdata++, 16, 2, QLatin1Char('0'));
|
||||
|
||||
if (((objectdata - stream_start) % 4) == 0)
|
||||
new_label += QLatin1Char(' ');
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_INDX_A:
|
||||
case OpcodeDecoder::GX_LOAD_INDX_B:
|
||||
case OpcodeDecoder::GX_LOAD_INDX_C:
|
||||
case OpcodeDecoder::GX_LOAD_INDX_D:
|
||||
{
|
||||
objectdata += 4;
|
||||
new_label = (command == OpcodeDecoder::GX_LOAD_INDX_A) ?
|
||||
QStringLiteral("LOAD INDX A") :
|
||||
(command == OpcodeDecoder::GX_LOAD_INDX_B) ?
|
||||
QStringLiteral("LOAD INDX B") :
|
||||
(command == OpcodeDecoder::GX_LOAD_INDX_C) ? QStringLiteral("LOAD INDX C") :
|
||||
QStringLiteral("LOAD INDX D");
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_CMD_CALL_DL:
|
||||
// The recorder should have expanded display lists into the fifo stream and skipped the
|
||||
// call to start them
|
||||
// That is done to make it easier to track where memory is updated
|
||||
ASSERT(false);
|
||||
objectdata += 8;
|
||||
new_label = QStringLiteral("CALL DL");
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_BP_REG:
|
||||
{
|
||||
u32 cmd2 = Common::swap32(objectdata);
|
||||
objectdata += 4;
|
||||
new_label = QStringLiteral("BP %02X %06X")
|
||||
.arg(cmd2 >> 24, 2, 16, QLatin1Char('0'))
|
||||
.arg(cmd2 & 0xFFFFFF, 6, 16, QLatin1Char('0'));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
new_label = tr("Unexpected 0x80 call? Aborting...");
|
||||
objectdata = static_cast<const u8*>(next_objdata_start);
|
||||
break;
|
||||
}
|
||||
new_label = QStringLiteral("%1: ").arg(new_offset, 8, 16, QLatin1Char('0')) + new_label;
|
||||
m_detail_list->addItem(new_label);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::BeginSearch()
|
||||
{
|
||||
QString search_str = m_search_edit->text();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty() || items[0]->data(0, FRAME_ROLE).isNull())
|
||||
return;
|
||||
|
||||
if (items[0]->data(0, OBJECT_ROLE).isNull())
|
||||
{
|
||||
m_search_label->setText(tr("Invalid search parameters (no object selected)"));
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Remove even string length limit
|
||||
if (search_str.length() % 2)
|
||||
{
|
||||
m_search_label->setText(tr("Invalid search string (only even string lengths supported)"));
|
||||
return;
|
||||
}
|
||||
|
||||
const size_t length = search_str.length() / 2;
|
||||
|
||||
std::vector<u8> search_val;
|
||||
|
||||
for (size_t i = 0; i < length; i++)
|
||||
{
|
||||
const QString byte_str = search_str.mid(static_cast<int>(i * 2), 2);
|
||||
|
||||
bool good;
|
||||
u8 value = byte_str.toUInt(&good, 16);
|
||||
|
||||
if (!good)
|
||||
{
|
||||
m_search_label->setText(tr("Invalid search string (couldn't convert to number)"));
|
||||
return;
|
||||
}
|
||||
|
||||
search_val.push_back(value);
|
||||
}
|
||||
|
||||
m_search_results.clear();
|
||||
|
||||
int frame_nr = items[0]->data(0, FRAME_ROLE).toInt();
|
||||
int object_nr = items[0]->data(0, OBJECT_ROLE).toInt();
|
||||
|
||||
const AnalyzedFrameInfo& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr);
|
||||
const FifoFrameInfo& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr);
|
||||
|
||||
// TODO: Support searching through the last object...how do we know where the cmd data ends?
|
||||
// TODO: Support searching for bit patterns
|
||||
|
||||
const auto* start_ptr = &fifo_frame.fifoData[frame_info.objectStarts[object_nr]];
|
||||
const auto* end_ptr = &fifo_frame.fifoData[frame_info.objectStarts[object_nr + 1]];
|
||||
|
||||
for (const u8* ptr = start_ptr; ptr < end_ptr - length + 1; ++ptr)
|
||||
{
|
||||
if (std::equal(search_val.begin(), search_val.end(), ptr))
|
||||
{
|
||||
SearchResult result;
|
||||
result.frame = frame_nr;
|
||||
|
||||
result.object = object_nr;
|
||||
result.cmd = 0;
|
||||
for (unsigned int cmd_nr = 1; cmd_nr < m_object_data_offsets.size(); ++cmd_nr)
|
||||
{
|
||||
if (ptr < start_ptr + m_object_data_offsets[cmd_nr])
|
||||
{
|
||||
result.cmd = cmd_nr - 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_search_results.push_back(result);
|
||||
}
|
||||
}
|
||||
|
||||
ShowSearchResult(0);
|
||||
|
||||
m_search_label->setText(
|
||||
tr("Found %1 results for \"%2\"").arg(m_search_results.size()).arg(search_str));
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::FindNext()
|
||||
{
|
||||
int index = m_detail_list->currentRow();
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
ShowSearchResult(0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = m_search_results.begin(); it != m_search_results.end(); ++it)
|
||||
{
|
||||
if (it->cmd > index)
|
||||
{
|
||||
ShowSearchResult(it - m_search_results.begin());
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::FindPrevious()
|
||||
{
|
||||
int index = m_detail_list->currentRow();
|
||||
|
||||
if (index == -1)
|
||||
{
|
||||
ShowSearchResult(m_search_results.size() - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto it = m_search_results.rbegin(); it != m_search_results.rend(); ++it)
|
||||
{
|
||||
if (it->cmd < index)
|
||||
{
|
||||
ShowSearchResult(m_search_results.size() - 1 - (it - m_search_results.rbegin()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::ShowSearchResult(size_t index)
|
||||
{
|
||||
if (!m_search_results.size())
|
||||
return;
|
||||
|
||||
if (index > m_search_results.size())
|
||||
{
|
||||
ShowSearchResult(m_search_results.size() - 1);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& result = m_search_results[index];
|
||||
|
||||
QTreeWidgetItem* object_item =
|
||||
m_tree_widget->topLevelItem(0)->child(result.frame)->child(result.object);
|
||||
|
||||
m_tree_widget->setCurrentItem(object_item);
|
||||
m_detail_list->setCurrentRow(result.cmd);
|
||||
|
||||
m_search_next->setEnabled(index + 1 < m_search_results.size());
|
||||
m_search_previous->setEnabled(index > 0);
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::UpdateDescription()
|
||||
{
|
||||
m_entry_detail_browser->clear();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
int frame_nr = items[0]->data(0, FRAME_ROLE).toInt();
|
||||
int object_nr = items[0]->data(0, OBJECT_ROLE).toInt();
|
||||
int entry_nr = m_detail_list->currentRow();
|
||||
|
||||
const AnalyzedFrameInfo& frame = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr);
|
||||
const FifoFrameInfo& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr);
|
||||
|
||||
const u8* cmddata =
|
||||
&fifo_frame.fifoData[frame.objectStarts[object_nr]] + m_object_data_offsets[entry_nr];
|
||||
|
||||
// TODO: Not sure whether we should bother translating the descriptions
|
||||
|
||||
QString text;
|
||||
if (*cmddata == OpcodeDecoder::GX_LOAD_BP_REG)
|
||||
{
|
||||
std::string name;
|
||||
std::string desc;
|
||||
GetBPRegInfo(cmddata + 1, &name, &desc);
|
||||
|
||||
text = tr("BP register ");
|
||||
text += name.empty() ?
|
||||
QStringLiteral("UNKNOWN_%02X").arg(*(cmddata + 1), 2, 16, QLatin1Char('0')) :
|
||||
QString::fromStdString(name);
|
||||
text += QStringLiteral("\n");
|
||||
|
||||
if (desc.empty())
|
||||
text += tr("No description available");
|
||||
else
|
||||
text += QString::fromStdString(desc);
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_CP_REG)
|
||||
{
|
||||
text = tr("CP register ");
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_XF_REG)
|
||||
{
|
||||
text = tr("XF register ");
|
||||
}
|
||||
else
|
||||
{
|
||||
text = tr("No description available");
|
||||
}
|
||||
|
||||
m_entry_detail_browser->setText(text);
|
||||
}
|
67
Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h
Normal file
67
Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2018 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QSplitter;
|
||||
class QTextBrowser;
|
||||
class QTreeWidget;
|
||||
|
||||
class FIFOAnalyzer final : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit FIFOAnalyzer();
|
||||
~FIFOAnalyzer();
|
||||
|
||||
void Update();
|
||||
|
||||
private:
|
||||
void CreateWidgets();
|
||||
void ConnectWidgets();
|
||||
|
||||
void BeginSearch();
|
||||
void FindNext();
|
||||
void FindPrevious();
|
||||
|
||||
void ShowSearchResult(size_t index);
|
||||
|
||||
void UpdateTree();
|
||||
void UpdateDetails();
|
||||
void UpdateDescription();
|
||||
|
||||
QTreeWidget* m_tree_widget;
|
||||
QListWidget* m_detail_list;
|
||||
QTextBrowser* m_entry_detail_browser;
|
||||
QSplitter* m_object_splitter;
|
||||
|
||||
// Search
|
||||
QGroupBox* m_search_box;
|
||||
QLineEdit* m_search_edit;
|
||||
QPushButton* m_search_new;
|
||||
QPushButton* m_search_next;
|
||||
QPushButton* m_search_previous;
|
||||
QLabel* m_search_label;
|
||||
QSplitter* m_search_splitter;
|
||||
|
||||
struct SearchResult
|
||||
{
|
||||
int frame;
|
||||
int object;
|
||||
int cmd;
|
||||
};
|
||||
|
||||
std::vector<int> m_object_data_offsets;
|
||||
std::vector<SearchResult> m_search_results;
|
||||
};
|
367
Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp
Normal file
367
Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp
Normal file
@ -0,0 +1,367 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "DolphinQt/FIFO/FIFOPlayerWindow.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
#include <QTabWidget>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#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 "DolphinQt/FIFO/FIFOAnalyzer.h"
|
||||
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
FIFOPlayerWindow::FIFOPlayerWindow(QWidget* parent) : QDialog(parent)
|
||||
{
|
||||
setWindowTitle(tr("FIFO Player"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
||||
UpdateInfo();
|
||||
|
||||
UpdateControls();
|
||||
|
||||
FifoPlayer::GetInstance().SetFileLoadedCallback(
|
||||
[this] { QueueOnObject(this, &FIFOPlayerWindow::OnFIFOLoaded); });
|
||||
FifoPlayer::GetInstance().SetFrameWrittenCallback([this] {
|
||||
QueueOnObject(this, [this] {
|
||||
UpdateInfo();
|
||||
UpdateControls();
|
||||
});
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
QWidget* main_widget = new QWidget(this);
|
||||
main_widget->setLayout(layout);
|
||||
|
||||
auto* tab_widget = new QTabWidget(this);
|
||||
|
||||
m_analyzer = new FIFOAnalyzer;
|
||||
|
||||
tab_widget->addTab(main_widget, tr("Play / Record"));
|
||||
tab_widget->addTab(m_analyzer, tr("Analyze"));
|
||||
|
||||
auto* tab_layout = new QVBoxLayout;
|
||||
tab_layout->addWidget(tab_widget);
|
||||
|
||||
setLayout(tab_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<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&FIFOPlayerWindow::OnLimitsChanged);
|
||||
connect(m_frame_range_to, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&FIFOPlayerWindow::OnLimitsChanged);
|
||||
|
||||
connect(m_object_range_from, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&FIFOPlayerWindow::OnLimitsChanged);
|
||||
connect(m_object_range_to, static_cast<void (QSpinBox::*)(int)>(&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();
|
||||
|
||||
if (FifoPlayer::GetInstance().GetFile())
|
||||
OnFIFOLoaded();
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
m_analyzer->Update();
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
65
Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.h
Normal file
65
Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.h
Normal file
@ -0,0 +1,65 @@
|
||||
// Copyright 2017 Dolphin Emulator Project
|
||||
// Licensed under GPLv2+
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class QSpinBox;
|
||||
class FIFOAnalyzer;
|
||||
|
||||
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;
|
||||
|
||||
FIFOAnalyzer* m_analyzer;
|
||||
};
|
Reference in New Issue
Block a user