mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-08-11 02:28:51 +00:00
Merge pull request #9540 from Pokechu22/better-fifo-analyzer-part-2
Fifo analyzer quality of life improvements
This commit is contained in:
commit
d74a1068b8
28 changed files with 918 additions and 816 deletions
|
@ -25,6 +25,7 @@
|
|||
#include "VideoCommon/BPMemory.h"
|
||||
#include "VideoCommon/CPMemory.h"
|
||||
#include "VideoCommon/OpcodeDecoding.h"
|
||||
#include "VideoCommon/VertexLoaderBase.h"
|
||||
#include "VideoCommon/XFStructs.h"
|
||||
|
||||
constexpr int FRAME_ROLE = Qt::UserRole;
|
||||
|
@ -81,6 +82,9 @@ void FIFOAnalyzer::CreateWidgets()
|
|||
m_search_previous = new QPushButton(tr("Previous Match"));
|
||||
m_search_label = new QLabel;
|
||||
|
||||
m_search_next->setEnabled(false);
|
||||
m_search_previous->setEnabled(false);
|
||||
|
||||
auto* box_layout = new QHBoxLayout;
|
||||
|
||||
box_layout->addWidget(m_search_edit);
|
||||
|
@ -111,6 +115,7 @@ void FIFOAnalyzer::ConnectWidgets()
|
|||
connect(m_detail_list, &QListWidget::itemSelectionChanged, this,
|
||||
&FIFOAnalyzer::UpdateDescription);
|
||||
|
||||
connect(m_search_edit, &QLineEdit::returnPressed, this, &FIFOAnalyzer::BeginSearch);
|
||||
connect(m_search_new, &QPushButton::clicked, this, &FIFOAnalyzer::BeginSearch);
|
||||
connect(m_search_next, &QPushButton::clicked, this, &FIFOAnalyzer::FindNext);
|
||||
connect(m_search_previous, &QPushButton::clicked, this, &FIFOAnalyzer::FindPrevious);
|
||||
|
@ -139,189 +144,277 @@ void FIFOAnalyzer::UpdateTree()
|
|||
|
||||
auto* file = FifoPlayer::GetInstance().GetFile();
|
||||
|
||||
int object_count = FifoPlayer::GetInstance().GetFrameObjectCount();
|
||||
int frame_count = file->GetFrameCount();
|
||||
|
||||
for (int i = 0; i < frame_count; i++)
|
||||
const u32 frame_count = file->GetFrameCount();
|
||||
for (u32 frame = 0; frame < frame_count; frame++)
|
||||
{
|
||||
auto* frame_item = new QTreeWidgetItem({tr("Frame %1").arg(i)});
|
||||
auto* frame_item = new QTreeWidgetItem({tr("Frame %1").arg(frame)});
|
||||
|
||||
recording_item->addChild(frame_item);
|
||||
|
||||
for (int j = 0; j < object_count; j++)
|
||||
const u32 object_count = FifoPlayer::GetInstance().GetFrameObjectCount(frame);
|
||||
for (u32 object = 0; object < object_count; object++)
|
||||
{
|
||||
auto* object_item = new QTreeWidgetItem({tr("Object %1").arg(j)});
|
||||
auto* object_item = new QTreeWidgetItem({tr("Object %1").arg(object)});
|
||||
|
||||
frame_item->addChild(object_item);
|
||||
|
||||
object_item->setData(0, FRAME_ROLE, i);
|
||||
object_item->setData(0, OBJECT_ROLE, j);
|
||||
object_item->setData(0, FRAME_ROLE, frame);
|
||||
object_item->setData(0, OBJECT_ROLE, object);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static std::string GetPrimitiveName(u8 cmd)
|
||||
{
|
||||
if ((cmd & 0xC0) != 0x80)
|
||||
{
|
||||
PanicAlertFmt("Not a primitive command: {:#04x}", cmd);
|
||||
return "";
|
||||
}
|
||||
const u8 vat = cmd & OpcodeDecoder::GX_VAT_MASK; // Vertex loader index (0 - 7)
|
||||
const u8 primitive =
|
||||
(cmd & OpcodeDecoder::GX_PRIMITIVE_MASK) >> OpcodeDecoder::GX_PRIMITIVE_SHIFT;
|
||||
static constexpr std::array<const char*, 8> names = {
|
||||
"GX_DRAW_QUADS", "GX_DRAW_QUADS_2 (nonstandard)",
|
||||
"GX_DRAW_TRIANGLES", "GX_DRAW_TRIANGLE_STRIP",
|
||||
"GX_DRAW_TRIANGLE_FAN", "GX_DRAW_LINES",
|
||||
"GX_DRAW_LINE_STRIP", "GX_DRAW_POINTS",
|
||||
};
|
||||
return fmt::format("{} VAT {}", names[primitive], vat);
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::UpdateDetails()
|
||||
{
|
||||
m_detail_list->clear();
|
||||
// Clearing the detail list can update the selection, which causes UpdateDescription to be called
|
||||
// immediately. However, the object data offsets have not been recalculated yet, which can cause
|
||||
// the wrong data to be used, potentially leading to out of bounds data or other bad things.
|
||||
// Clear m_object_data_offsets first, so that UpdateDescription exits immediately.
|
||||
m_object_data_offsets.clear();
|
||||
m_detail_list->clear();
|
||||
m_search_results.clear();
|
||||
m_search_next->setEnabled(false);
|
||||
m_search_previous->setEnabled(false);
|
||||
m_search_label->clear();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
if (!FifoPlayer::GetInstance().IsPlaying())
|
||||
return;
|
||||
|
||||
const 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 u32 frame_nr = items[0]->data(0, FRAME_ROLE).toUInt();
|
||||
const u32 object_nr = items[0]->data(0, OBJECT_ROLE).toUInt();
|
||||
|
||||
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]];
|
||||
// Note that frame_info.objectStarts[object_nr] is the start of the primitive data,
|
||||
// but we want to start with the register updates which happen before that.
|
||||
const u32 object_start = (object_nr == 0 ? 0 : frame_info.objectEnds[object_nr - 1]);
|
||||
const u32 object_size = frame_info.objectEnds[object_nr] - object_start;
|
||||
|
||||
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");
|
||||
const u8* const object = &fifo_frame.fifoData[object_start];
|
||||
|
||||
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()))
|
||||
u32 object_offset = 0;
|
||||
while (object_offset < object_size)
|
||||
{
|
||||
const u8* next_objdata_start = &fifo_frame.fifoData[frame_info.objectStarts[object_nr + 1]];
|
||||
while (objectdata < next_objdata_start)
|
||||
QString new_label;
|
||||
const u32 start_offset = object_offset;
|
||||
m_object_data_offsets.push_back(start_offset);
|
||||
|
||||
const u8 command = object[object_offset++];
|
||||
switch (command)
|
||||
{
|
||||
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:
|
||||
if (object[object_offset] == OpcodeDecoder::GX_NOP)
|
||||
{
|
||||
u32 nop_count = 2;
|
||||
while (object[++object_offset] == OpcodeDecoder::GX_NOP)
|
||||
nop_count++;
|
||||
|
||||
new_label = QStringLiteral("NOP (%1x)").arg(nop_count);
|
||||
}
|
||||
else
|
||||
{
|
||||
case OpcodeDecoder::GX_NOP:
|
||||
new_label = QStringLiteral("NOP");
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_CMD_UNKNOWN_METRICS:
|
||||
new_label = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
|
||||
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;
|
||||
const auto [name, desc] = GetCPRegInfo(cmd2, value);
|
||||
ASSERT(!name.empty());
|
||||
|
||||
new_label = QStringLiteral("CP %1 %2 %3")
|
||||
.arg(cmd2, 2, 16, QLatin1Char('0'))
|
||||
.arg(value, 8, 16, QLatin1Char('0'))
|
||||
.arg(QString::fromStdString(name));
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_XF_REG:
|
||||
{
|
||||
const auto [name, desc] = GetXFTransferInfo(objectdata);
|
||||
u32 cmd2 = Common::swap32(objectdata);
|
||||
objectdata += 4;
|
||||
ASSERT(!name.empty());
|
||||
|
||||
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, 8, 16, QLatin1Char('0'));
|
||||
while (objectdata < stream_end)
|
||||
{
|
||||
new_label += QStringLiteral("%1").arg(*objectdata++, 2, 16, QLatin1Char('0'));
|
||||
|
||||
if (((objectdata - stream_start) % 4) == 0)
|
||||
new_label += QLatin1Char(' ');
|
||||
}
|
||||
|
||||
new_label += QStringLiteral(" ") + QString::fromStdString(name);
|
||||
}
|
||||
case OpcodeDecoder::GX_CMD_UNKNOWN_METRICS:
|
||||
new_label = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
|
||||
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");
|
||||
}
|
||||
case OpcodeDecoder::GX_CMD_INVL_VC:
|
||||
new_label = QStringLiteral("GX_CMD_INVL_VC");
|
||||
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_CP_REG:
|
||||
{
|
||||
const u8 cmd2 = object[object_offset++];
|
||||
const u32 value = Common::swap32(&object[object_offset]);
|
||||
object_offset += 4;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_BP_REG:
|
||||
{
|
||||
const u8 cmd2 = *objectdata++;
|
||||
const u32 cmddata = Common::swap24(objectdata);
|
||||
objectdata += 3;
|
||||
const auto [name, desc] = GetCPRegInfo(cmd2, value);
|
||||
ASSERT(!name.empty());
|
||||
|
||||
const auto [name, desc] = GetBPRegInfo(cmd2, cmddata);
|
||||
ASSERT(!name.empty());
|
||||
|
||||
new_label = QStringLiteral("BP %1 %2 %3")
|
||||
.arg(cmd2, 2, 16, QLatin1Char('0'))
|
||||
.arg(cmddata, 6, 16, QLatin1Char('0'))
|
||||
.arg(QString::fromStdString(name));
|
||||
}
|
||||
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);
|
||||
new_label = QStringLiteral("CP %1 %2 %3")
|
||||
.arg(cmd2, 2, 16, QLatin1Char('0'))
|
||||
.arg(value, 8, 16, QLatin1Char('0'))
|
||||
.arg(QString::fromStdString(name));
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_XF_REG:
|
||||
{
|
||||
const auto [name, desc] = GetXFTransferInfo(&object[object_offset]);
|
||||
const u32 cmd2 = Common::swap32(&object[object_offset]);
|
||||
object_offset += 4;
|
||||
ASSERT(!name.empty());
|
||||
|
||||
const u8 stream_size = ((cmd2 >> 16) & 15) + 1;
|
||||
|
||||
new_label = QStringLiteral("XF %1 ").arg(cmd2, 8, 16, QLatin1Char('0'));
|
||||
|
||||
for (u8 i = 0; i < stream_size; i++)
|
||||
{
|
||||
const u32 value = Common::swap32(&object[object_offset]);
|
||||
object_offset += 4;
|
||||
|
||||
new_label += QStringLiteral("%1 ").arg(value, 8, 16, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
new_label += QStringLiteral(" ") + QString::fromStdString(name);
|
||||
}
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_INDX_A:
|
||||
{
|
||||
const auto [desc, written] =
|
||||
GetXFIndexedLoadInfo(ARRAY_XF_A, Common::swap32(&object[object_offset]));
|
||||
object_offset += 4;
|
||||
new_label = QStringLiteral("LOAD INDX A %1").arg(QString::fromStdString(desc));
|
||||
}
|
||||
break;
|
||||
case OpcodeDecoder::GX_LOAD_INDX_B:
|
||||
{
|
||||
const auto [desc, written] =
|
||||
GetXFIndexedLoadInfo(ARRAY_XF_B, Common::swap32(&object[object_offset]));
|
||||
object_offset += 4;
|
||||
new_label = QStringLiteral("LOAD INDX B %1").arg(QString::fromStdString(desc));
|
||||
}
|
||||
break;
|
||||
case OpcodeDecoder::GX_LOAD_INDX_C:
|
||||
{
|
||||
const auto [desc, written] =
|
||||
GetXFIndexedLoadInfo(ARRAY_XF_C, Common::swap32(&object[object_offset]));
|
||||
object_offset += 4;
|
||||
new_label = QStringLiteral("LOAD INDX C %1").arg(QString::fromStdString(desc));
|
||||
}
|
||||
break;
|
||||
case OpcodeDecoder::GX_LOAD_INDX_D:
|
||||
{
|
||||
const auto [desc, written] =
|
||||
GetXFIndexedLoadInfo(ARRAY_XF_D, Common::swap32(&object[object_offset]));
|
||||
object_offset += 4;
|
||||
new_label = QStringLiteral("LOAD INDX D %1").arg(QString::fromStdString(desc));
|
||||
}
|
||||
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);
|
||||
object_offset += 8;
|
||||
new_label = QStringLiteral("CALL DL");
|
||||
break;
|
||||
|
||||
case OpcodeDecoder::GX_LOAD_BP_REG:
|
||||
{
|
||||
const u8 cmd2 = object[object_offset++];
|
||||
const u32 cmddata = Common::swap24(&object[object_offset]);
|
||||
object_offset += 3;
|
||||
|
||||
const auto [name, desc] = GetBPRegInfo(cmd2, cmddata);
|
||||
ASSERT(!name.empty());
|
||||
|
||||
new_label = QStringLiteral("BP %1 %2 %3")
|
||||
.arg(cmd2, 2, 16, QLatin1Char('0'))
|
||||
.arg(cmddata, 6, 16, QLatin1Char('0'))
|
||||
.arg(QString::fromStdString(name));
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
if ((command & 0xC0) == 0x80)
|
||||
{
|
||||
// Object primitive data
|
||||
|
||||
const u8 vat = command & OpcodeDecoder::GX_VAT_MASK;
|
||||
const auto& vtx_desc = frame_info.objectCPStates[object_nr].vtxDesc;
|
||||
const auto& vtx_attr = frame_info.objectCPStates[object_nr].vtxAttr[vat];
|
||||
|
||||
const auto name = GetPrimitiveName(command);
|
||||
|
||||
const u16 vertex_count = Common::swap16(&object[object_offset]);
|
||||
object_offset += 2;
|
||||
const u32 vertex_size = VertexLoaderBase::GetVertexSize(vtx_desc, vtx_attr);
|
||||
|
||||
// Note that vertex_count is allowed to be 0, with no special treatment
|
||||
// (another command just comes right after the current command, with no vertices in between)
|
||||
const u32 object_prim_size = vertex_count * vertex_size;
|
||||
|
||||
new_label = QStringLiteral("PRIMITIVE %1 (%2) %3 vertices %4 bytes/vertex %5 total bytes")
|
||||
.arg(QString::fromStdString(name))
|
||||
.arg(command, 2, 16, QLatin1Char('0'))
|
||||
.arg(vertex_count)
|
||||
.arg(vertex_size)
|
||||
.arg(object_prim_size);
|
||||
|
||||
// It's not really useful to have a massive unreadable hex string for the object primitives.
|
||||
// Put it in the description instead.
|
||||
|
||||
// #define INCLUDE_HEX_IN_PRIMITIVES
|
||||
#ifdef INCLUDE_HEX_IN_PRIMITIVES
|
||||
new_label += QStringLiteral(" ");
|
||||
for (u32 i = 0; i < object_prim_size; i++)
|
||||
{
|
||||
new_label += QStringLiteral("%1").arg(object[object_offset++], 2, 16, QLatin1Char('0'));
|
||||
}
|
||||
#else
|
||||
object_offset += object_prim_size;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
new_label = QStringLiteral("Unknown opcode %1").arg(command, 2, 16);
|
||||
}
|
||||
break;
|
||||
}
|
||||
new_label = QStringLiteral("%1: ").arg(object_start + start_offset, 8, 16, QLatin1Char('0')) +
|
||||
new_label;
|
||||
m_detail_list->addItem(new_label);
|
||||
}
|
||||
|
||||
ASSERT(object_offset == object_size);
|
||||
|
||||
// Needed to ensure the description updates when changing objects
|
||||
m_detail_list->setCurrentRow(0);
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::BeginSearch()
|
||||
{
|
||||
QString search_str = m_search_edit->text();
|
||||
const QString search_str = m_search_edit->text();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty() || items[0]->data(0, FRAME_ROLE).isNull())
|
||||
if (!FifoPlayer::GetInstance().IsPlaying())
|
||||
return;
|
||||
|
||||
if (items[0]->data(0, OBJECT_ROLE).isNull())
|
||||
const auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty() || items[0]->data(0, FRAME_ROLE).isNull() ||
|
||||
items[0]->data(0, OBJECT_ROLE).isNull())
|
||||
{
|
||||
m_search_label->setText(tr("Invalid search parameters (no object selected)"));
|
||||
return;
|
||||
|
@ -356,36 +449,35 @@ void FIFOAnalyzer::BeginSearch()
|
|||
|
||||
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 u32 frame_nr = items[0]->data(0, FRAME_ROLE).toUInt();
|
||||
const u32 object_nr = items[0]->data(0, OBJECT_ROLE).toUInt();
|
||||
|
||||
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?
|
||||
const u32 object_start = (object_nr == 0 ? 0 : frame_info.objectEnds[object_nr - 1]);
|
||||
const u32 object_size = frame_info.objectEnds[object_nr] - object_start;
|
||||
|
||||
const u8* const object = &fifo_frame.fifoData[object_start];
|
||||
|
||||
// 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)
|
||||
for (u32 cmd_nr = 0; cmd_nr < m_object_data_offsets.size(); cmd_nr++)
|
||||
{
|
||||
if (std::equal(search_val.begin(), search_val.end(), ptr))
|
||||
{
|
||||
SearchResult result;
|
||||
result.frame = frame_nr;
|
||||
const u32 cmd_start = m_object_data_offsets[cmd_nr];
|
||||
const u32 cmd_end = (cmd_nr + 1 == m_object_data_offsets.size()) ?
|
||||
object_size :
|
||||
m_object_data_offsets[cmd_nr + 1];
|
||||
|
||||
result.object = object_nr;
|
||||
result.cmd = 0;
|
||||
for (unsigned int cmd_nr = 1; cmd_nr < m_object_data_offsets.size(); ++cmd_nr)
|
||||
const u8* const cmd_start_ptr = &object[cmd_start];
|
||||
const u8* const cmd_end_ptr = &object[cmd_end];
|
||||
|
||||
for (const u8* ptr = cmd_start_ptr; ptr < cmd_end_ptr - length + 1; ptr++)
|
||||
{
|
||||
if (std::equal(search_val.begin(), search_val.end(), ptr))
|
||||
{
|
||||
if (ptr < start_ptr + m_object_data_offsets[cmd_nr])
|
||||
{
|
||||
result.cmd = cmd_nr - 1;
|
||||
break;
|
||||
}
|
||||
m_search_results.emplace_back(frame_nr, object_nr, cmd_nr);
|
||||
break;
|
||||
}
|
||||
m_search_results.push_back(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -397,41 +489,29 @@ void FIFOAnalyzer::BeginSearch()
|
|||
|
||||
void FIFOAnalyzer::FindNext()
|
||||
{
|
||||
int index = m_detail_list->currentRow();
|
||||
const int index = m_detail_list->currentRow();
|
||||
ASSERT(index >= 0);
|
||||
|
||||
if (index == -1)
|
||||
auto next_result =
|
||||
std::find_if(m_search_results.begin(), m_search_results.end(),
|
||||
[index](auto& result) { return result.m_cmd > static_cast<u32>(index); });
|
||||
if (next_result != m_search_results.end())
|
||||
{
|
||||
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;
|
||||
}
|
||||
ShowSearchResult(next_result - m_search_results.begin());
|
||||
}
|
||||
}
|
||||
|
||||
void FIFOAnalyzer::FindPrevious()
|
||||
{
|
||||
int index = m_detail_list->currentRow();
|
||||
const int index = m_detail_list->currentRow();
|
||||
ASSERT(index >= 0);
|
||||
|
||||
if (index == -1)
|
||||
auto prev_result =
|
||||
std::find_if(m_search_results.rbegin(), m_search_results.rend(),
|
||||
[index](auto& result) { return result.m_cmd < static_cast<u32>(index); });
|
||||
if (prev_result != m_search_results.rend())
|
||||
{
|
||||
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;
|
||||
}
|
||||
ShowSearchResult((m_search_results.rend() - prev_result) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -440,7 +520,7 @@ void FIFOAnalyzer::ShowSearchResult(size_t index)
|
|||
if (m_search_results.empty())
|
||||
return;
|
||||
|
||||
if (index > m_search_results.size())
|
||||
if (index >= m_search_results.size())
|
||||
{
|
||||
ShowSearchResult(m_search_results.size() - 1);
|
||||
return;
|
||||
|
@ -449,10 +529,10 @@ void FIFOAnalyzer::ShowSearchResult(size_t index)
|
|||
const auto& result = m_search_results[index];
|
||||
|
||||
QTreeWidgetItem* object_item =
|
||||
m_tree_widget->topLevelItem(0)->child(result.frame)->child(result.object);
|
||||
m_tree_widget->topLevelItem(0)->child(result.m_frame)->child(result.m_object);
|
||||
|
||||
m_tree_widget->setCurrentItem(object_item);
|
||||
m_detail_list->setCurrentRow(result.cmd);
|
||||
m_detail_list->setCurrentRow(result.m_cmd);
|
||||
|
||||
m_search_next->setEnabled(index + 1 < m_search_results.size());
|
||||
m_search_previous->setEnabled(index > 0);
|
||||
|
@ -462,20 +542,28 @@ void FIFOAnalyzer::UpdateDescription()
|
|||
{
|
||||
m_entry_detail_browser->clear();
|
||||
|
||||
auto items = m_tree_widget->selectedItems();
|
||||
|
||||
if (items.isEmpty())
|
||||
if (!FifoPlayer::GetInstance().IsPlaying())
|
||||
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 auto items = m_tree_widget->selectedItems();
|
||||
|
||||
const AnalyzedFrameInfo& frame = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr);
|
||||
if (items.isEmpty() || m_object_data_offsets.empty())
|
||||
return;
|
||||
|
||||
if (items[0]->data(0, FRAME_ROLE).isNull() || items[0]->data(0, OBJECT_ROLE).isNull())
|
||||
return;
|
||||
|
||||
const u32 frame_nr = items[0]->data(0, FRAME_ROLE).toUInt();
|
||||
const u32 object_nr = items[0]->data(0, OBJECT_ROLE).toUInt();
|
||||
const u32 entry_nr = m_detail_list->currentRow();
|
||||
|
||||
const AnalyzedFrameInfo& frame_info = 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];
|
||||
const u32 object_start = (object_nr == 0 ? 0 : frame_info.objectEnds[object_nr - 1]);
|
||||
const u32 entry_start = m_object_data_offsets[entry_nr];
|
||||
|
||||
const u8* cmddata = &fifo_frame.fifoData[object_start + entry_start];
|
||||
|
||||
// TODO: Not sure whether we should bother translating the descriptions
|
||||
|
||||
|
@ -528,6 +616,74 @@ void FIFOAnalyzer::UpdateDescription()
|
|||
else
|
||||
text += QString::fromStdString(desc);
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_INDX_A)
|
||||
{
|
||||
const auto [desc, written] = GetXFIndexedLoadInfo(ARRAY_XF_A, Common::swap32(cmddata + 1));
|
||||
|
||||
text = QString::fromStdString(desc);
|
||||
text += QLatin1Char{'\n'};
|
||||
text += tr("Usually used for position matrices");
|
||||
text += QLatin1Char{'\n'};
|
||||
text += QString::fromStdString(written);
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_INDX_B)
|
||||
{
|
||||
const auto [desc, written] = GetXFIndexedLoadInfo(ARRAY_XF_B, Common::swap32(cmddata + 1));
|
||||
|
||||
text = QString::fromStdString(desc);
|
||||
text += QLatin1Char{'\n'};
|
||||
text += tr("Usually used for normal matrices");
|
||||
text += QLatin1Char{'\n'};
|
||||
text += QString::fromStdString(written);
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_INDX_C)
|
||||
{
|
||||
const auto [desc, written] = GetXFIndexedLoadInfo(ARRAY_XF_C, Common::swap32(cmddata + 1));
|
||||
|
||||
text = QString::fromStdString(desc);
|
||||
text += QLatin1Char{'\n'};
|
||||
text += tr("Usually used for tex coord matrices");
|
||||
text += QLatin1Char{'\n'};
|
||||
text += QString::fromStdString(written);
|
||||
}
|
||||
else if (*cmddata == OpcodeDecoder::GX_LOAD_INDX_D)
|
||||
{
|
||||
const auto [desc, written] = GetXFIndexedLoadInfo(ARRAY_XF_D, Common::swap32(cmddata + 1));
|
||||
|
||||
text = QString::fromStdString(desc);
|
||||
text += QLatin1Char{'\n'};
|
||||
text += tr("Usually used for light objects");
|
||||
text += QLatin1Char{'\n'};
|
||||
text += QString::fromStdString(written);
|
||||
}
|
||||
else if ((*cmddata & 0xC0) == 0x80)
|
||||
{
|
||||
const u8 vat = *cmddata & OpcodeDecoder::GX_VAT_MASK;
|
||||
const auto name = GetPrimitiveName(*cmddata);
|
||||
const u16 vertex_count = Common::swap16(cmddata + 1);
|
||||
|
||||
text = tr("Primitive ");
|
||||
text += QString::fromStdString(name);
|
||||
text += QLatin1Char{'\n'};
|
||||
|
||||
const auto& vtx_desc = frame_info.objectCPStates[object_nr].vtxDesc;
|
||||
const auto& vtx_attr = frame_info.objectCPStates[object_nr].vtxAttr[vat];
|
||||
const auto component_sizes = VertexLoaderBase::GetVertexComponentSizes(vtx_desc, vtx_attr);
|
||||
|
||||
u32 i = 3;
|
||||
for (u32 vertex_num = 0; vertex_num < vertex_count; vertex_num++)
|
||||
{
|
||||
text += QLatin1Char{'\n'};
|
||||
for (u32 comp_size : component_sizes)
|
||||
{
|
||||
for (u32 comp_off = 0; comp_off < comp_size; comp_off++)
|
||||
{
|
||||
text += QStringLiteral("%1").arg(cmddata[i++], 2, 16, QLatin1Char('0'));
|
||||
}
|
||||
text += QLatin1Char{' '};
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
text = tr("No description available");
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
|
||||
#include <QWidget>
|
||||
|
||||
#include "Common/CommonTypes.h"
|
||||
|
||||
class QGroupBox;
|
||||
class QLabel;
|
||||
class QLineEdit;
|
||||
|
@ -57,9 +59,13 @@ private:
|
|||
|
||||
struct SearchResult
|
||||
{
|
||||
int frame;
|
||||
int object;
|
||||
int cmd;
|
||||
constexpr SearchResult(u32 frame, u32 object, u32 cmd)
|
||||
: m_frame(frame), m_object(object), m_cmd(cmd)
|
||||
{
|
||||
}
|
||||
const u32 m_frame;
|
||||
const u32 m_object;
|
||||
const u32 m_cmd;
|
||||
};
|
||||
|
||||
std::vector<int> m_object_data_offsets;
|
||||
|
|
|
@ -6,9 +6,13 @@
|
|||
|
||||
#include <QCheckBox>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QEvent>
|
||||
#include <QFileDialog>
|
||||
#include <QGroupBox>
|
||||
#include <QHBoxLayout>
|
||||
#include <QIcon>
|
||||
#include <QKeyEvent>
|
||||
#include <QKeySequence>
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QSpinBox>
|
||||
|
@ -26,12 +30,13 @@
|
|||
#include "DolphinQt/FIFO/FIFOAnalyzer.h"
|
||||
#include "DolphinQt/QtUtils/ModalMessageBox.h"
|
||||
#include "DolphinQt/QtUtils/QueueOnObject.h"
|
||||
#include "DolphinQt/Resources.h"
|
||||
#include "DolphinQt/Settings.h"
|
||||
|
||||
FIFOPlayerWindow::FIFOPlayerWindow(QWidget* parent) : QDialog(parent)
|
||||
FIFOPlayerWindow::FIFOPlayerWindow(QWidget* parent) : QWidget(parent)
|
||||
{
|
||||
setWindowTitle(tr("FIFO Player"));
|
||||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
setWindowIcon(Resources::GetAppIcon());
|
||||
|
||||
CreateWidgets();
|
||||
ConnectWidgets();
|
||||
|
@ -50,11 +55,14 @@ FIFOPlayerWindow::FIFOPlayerWindow(QWidget* parent) : QDialog(parent)
|
|||
});
|
||||
|
||||
connect(&Settings::Instance(), &Settings::EmulationStateChanged, this, [this](Core::State state) {
|
||||
if (state == Core::State::Running)
|
||||
if (state == Core::State::Running && m_emu_state != Core::State::Paused)
|
||||
OnEmulationStarted();
|
||||
else if (state == Core::State::Uninitialized)
|
||||
OnEmulationStopped();
|
||||
m_emu_state = state;
|
||||
});
|
||||
|
||||
installEventFilter(this);
|
||||
}
|
||||
|
||||
FIFOPlayerWindow::~FIFOPlayerWindow()
|
||||
|
@ -166,7 +174,7 @@ void FIFOPlayerWindow::ConnectWidgets()
|
|||
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::reject);
|
||||
connect(m_button_box, &QDialogButtonBox::rejected, this, &FIFOPlayerWindow::hide);
|
||||
connect(m_early_memory_updates, &QCheckBox::toggled, this,
|
||||
&FIFOPlayerWindow::OnEarlyMemoryUpdatesChanged);
|
||||
connect(m_frame_range_from, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
|
@ -244,6 +252,7 @@ void FIFOPlayerWindow::OnEmulationStopped()
|
|||
StopRecording();
|
||||
|
||||
UpdateControls();
|
||||
m_analyzer->Update();
|
||||
}
|
||||
|
||||
void FIFOPlayerWindow::OnRecordingDone()
|
||||
|
@ -260,7 +269,7 @@ void FIFOPlayerWindow::UpdateInfo()
|
|||
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().GetCurrentFrameObjectCount()),
|
||||
QString::number(FifoPlayer::GetInstance().GetCurrentFrameNum())));
|
||||
return;
|
||||
}
|
||||
|
@ -297,14 +306,16 @@ void FIFOPlayerWindow::OnFIFOLoaded()
|
|||
{
|
||||
FifoDataFile* file = FifoPlayer::GetInstance().GetFile();
|
||||
|
||||
auto object_count = FifoPlayer::GetInstance().GetFrameObjectCount();
|
||||
auto object_count = FifoPlayer::GetInstance().GetMaxObjectCount();
|
||||
auto frame_count = file->GetFrameCount();
|
||||
|
||||
m_frame_range_to->setMaximum(frame_count);
|
||||
m_object_range_to->setMaximum(object_count);
|
||||
m_frame_range_to->setMaximum(frame_count - 1);
|
||||
m_object_range_to->setMaximum(object_count - 1);
|
||||
|
||||
m_frame_range_to->setValue(frame_count);
|
||||
m_object_range_to->setValue(object_count);
|
||||
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();
|
||||
|
@ -331,10 +342,10 @@ void FIFOPlayerWindow::OnLimitsChanged()
|
|||
|
||||
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);
|
||||
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()
|
||||
|
@ -367,3 +378,15 @@ void FIFOPlayerWindow::UpdateControls()
|
|||
|
||||
m_save->setEnabled(FifoRecorder::GetInstance().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;
|
||||
}
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QWidget>
|
||||
|
||||
#include "Core/Core.h"
|
||||
|
||||
class QCheckBox;
|
||||
class QDialogButtonBox;
|
||||
|
@ -13,7 +15,7 @@ class QPushButton;
|
|||
class QSpinBox;
|
||||
class FIFOAnalyzer;
|
||||
|
||||
class FIFOPlayerWindow : public QDialog
|
||||
class FIFOPlayerWindow : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -43,6 +45,8 @@ private:
|
|||
void UpdateInfo();
|
||||
void UpdateLimits();
|
||||
|
||||
bool eventFilter(QObject* object, QEvent* event) final override;
|
||||
|
||||
QLabel* m_info_label;
|
||||
QPushButton* m_load;
|
||||
QPushButton* m_save;
|
||||
|
@ -62,4 +66,5 @@ private:
|
|||
QDialogButtonBox* m_button_box;
|
||||
|
||||
FIFOAnalyzer* m_analyzer;
|
||||
Core::State m_emu_state = Core::State::Uninitialized;
|
||||
};
|
||||
|
|
|
@ -1214,7 +1214,7 @@ void MainWindow::ShowFIFOPlayer()
|
|||
{
|
||||
if (!m_fifo_window)
|
||||
{
|
||||
m_fifo_window = new FIFOPlayerWindow(this);
|
||||
m_fifo_window = new FIFOPlayerWindow;
|
||||
connect(m_fifo_window, &FIFOPlayerWindow::LoadFIFORequested, this,
|
||||
[this](const QString& path) { StartGame(path, ScanForSecondDisc::No); });
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue