Refactor OpcodeDecoding and FIFO analyzer to use callbacks

This commit is contained in:
Pokechu22
2021-04-22 20:57:56 -07:00
parent 0441826206
commit b5fd35f951
29 changed files with 1214 additions and 1388 deletions

View File

@ -205,23 +205,130 @@ void FIFOAnalyzer::UpdateTree()
}
}
static std::string GetPrimitiveName(u8 cmd)
namespace
{
if ((cmd & 0xC0) != 0x80)
class DetailCallback : public OpcodeDecoder::Callback
{
public:
explicit DetailCallback(CPState cpmem) : m_cpmem(cpmem) {}
OPCODE_CALLBACK(void OnCP(u8 command, u32 value))
{
PanicAlertFmt("Not a primitive command: {:#04x}", cmd);
return "";
// Note: No need to update m_cpmem as it already has the final value for this object
const auto [name, desc] = GetCPRegInfo(command, value);
ASSERT(!name.empty());
text = QStringLiteral("CP %1 %2 %3")
.arg(command, 2, 16, QLatin1Char('0'))
.arg(value, 8, 16, QLatin1Char('0'))
.arg(QString::fromStdString(name));
}
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;
return fmt::format("{} VAT {}", static_cast<OpcodeDecoder::Primitive>(primitive), vat);
}
OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data))
{
const auto [name, desc] = GetXFTransferInfo(address, count, data);
ASSERT(!name.empty());
const u32 command = address | (count << 16);
text = QStringLiteral("XF %1 ").arg(command, 8, 16, QLatin1Char('0'));
for (u8 i = 0; i < count; i++)
{
const u32 value = Common::swap32(&data[i * 4]);
text += QStringLiteral("%1 ").arg(value, 8, 16, QLatin1Char('0'));
}
text += QStringLiteral(" ") + QString::fromStdString(name);
}
OPCODE_CALLBACK(void OnBP(u8 command, u32 value))
{
const auto [name, desc] = GetBPRegInfo(command, value);
ASSERT(!name.empty());
text = QStringLiteral("BP %1 %2 %3")
.arg(command, 2, 16, QLatin1Char('0'))
.arg(value, 6, 16, QLatin1Char('0'))
.arg(QString::fromStdString(name));
}
OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size))
{
const auto [desc, written] = GetXFIndexedLoadInfo(array, index, address, size);
text = QStringLiteral("LOAD INDX %1 %2")
.arg(QString::fromStdString(fmt::to_string(array)))
.arg(QString::fromStdString(desc));
}
OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat,
u32 vertex_size, u16 num_vertices, const u8* vertex_data))
{
const auto name = fmt::to_string(primitive);
// 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 = num_vertices * vertex_size;
const u8 opcode =
0x80 | (static_cast<u8>(primitive) << OpcodeDecoder::GX_PRIMITIVE_SHIFT) | vat;
text = QStringLiteral("PRIMITIVE %1 (%2) %3 vertices %4 bytes/vertex %5 total bytes")
.arg(QString::fromStdString(name))
.arg(opcode, 2, 16, QLatin1Char('0'))
.arg(num_vertices)
.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
text += QStringLiteral(" ");
for (u32 i = 0; i < object_prim_size; i++)
{
text += QStringLiteral("%1").arg(vertex_data[i], 2, 16, QLatin1Char('0'));
}
#endif
}
OPCODE_CALLBACK(void OnDisplayList(u32 address, u32 size))
{
text = QObject::tr("Call display list at %1 with size %2")
.arg(address, 8, 16, QLatin1Char('0'))
.arg(size, 8, 16, QLatin1Char('0'));
}
OPCODE_CALLBACK(void OnNop(u32 count))
{
if (count > 1)
text = QStringLiteral("NOP (%1x)").arg(count);
else
text = QStringLiteral("NOP");
}
OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data))
{
using OpcodeDecoder::Opcode;
if (static_cast<Opcode>(opcode) == Opcode::GX_CMD_UNKNOWN_METRICS)
text = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
else if (static_cast<Opcode>(opcode) == Opcode::GX_CMD_INVL_VC)
text = QStringLiteral("GX_CMD_INVL_VC");
else
text = QStringLiteral("Unknown opcode %1").arg(opcode, 2, 16);
}
OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)) {}
OPCODE_CALLBACK(CPState& GetCPState()) { return m_cpmem; }
QString text;
CPState m_cpmem;
};
} // namespace
void FIFOAnalyzer::UpdateDetails()
{
using OpcodeDecoder::Opcode;
// 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.
@ -252,188 +359,23 @@ void FIFOAnalyzer::UpdateDetails()
const u32 object_end = frame_info.parts[end_part_nr].m_end;
const u32 object_size = object_end - object_start;
const u8* const object = &fifo_frame.fifoData[object_start];
u32 object_offset = 0;
// NOTE: object_info.m_cpmem is the state of cpmem _after_ all of the commands in this object.
// However, it doesn't matter that it doesn't match the start, since it will match by the time
// primitives are reached.
auto callback = DetailCallback(frame_info.parts[end_part_nr].m_cpmem);
while (object_offset < object_size)
{
QString new_label;
const u32 start_offset = object_offset;
m_object_data_offsets.push_back(start_offset);
const Opcode opcode = static_cast<Opcode>(object[object_offset++]);
switch (opcode)
{
case Opcode::GX_NOP:
if (object[object_offset] == static_cast<u8>(Opcode::GX_NOP))
{
u32 nop_count = 2;
while (object[++object_offset] == static_cast<u8>(Opcode::GX_NOP))
nop_count++;
object_offset += OpcodeDecoder::RunCommand(&fifo_frame.fifoData[object_start + start_offset],
object_size - start_offset, callback);
new_label = QStringLiteral("NOP (%1x)").arg(nop_count);
}
else
{
new_label = QStringLiteral("NOP");
}
break;
case Opcode::GX_CMD_UNKNOWN_METRICS:
new_label = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
break;
case Opcode::GX_CMD_INVL_VC:
new_label = QStringLiteral("GX_CMD_INVL_VC");
break;
case Opcode::GX_LOAD_CP_REG:
{
const u8 cmd2 = object[object_offset++];
const u32 value = Common::swap32(&object[object_offset]);
object_offset += 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 Opcode::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 Opcode::GX_LOAD_INDX_A:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_A, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX A %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_B:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_B, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX B %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_C:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_C, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX C %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_D:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_D, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX D %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::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 Opcode::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:
{
const u8 command = static_cast<u8>(opcode);
if ((command & 0xC0) == 0x80)
{
// Object primitive data
const u8 vat = command & OpcodeDecoder::GX_VAT_MASK;
const auto& vtx_desc = frame_info.parts[end_part_nr].m_cpmem.vtxDesc;
const auto& vtx_attr = frame_info.parts[end_part_nr].m_cpmem.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;
QString new_label =
QStringLiteral("%1: ").arg(object_start + start_offset, 8, 16, QLatin1Char('0')) +
callback.text;
m_detail_list->addItem(new_label);
}
@ -580,10 +522,143 @@ void FIFOAnalyzer::ShowSearchResult(size_t index)
m_search_previous->setEnabled(index > 0);
}
namespace
{
// TODO: Not sure whether we should bother translating the descriptions
class DescriptionCallback : public OpcodeDecoder::Callback
{
public:
explicit DescriptionCallback(const CPState& cpmem) : m_cpmem(cpmem) {}
OPCODE_CALLBACK(void OnBP(u8 command, u32 value))
{
const auto [name, desc] = GetBPRegInfo(command, value);
ASSERT(!name.empty());
text = QObject::tr("BP register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += QObject::tr("No description available");
else
text += QString::fromStdString(desc);
}
OPCODE_CALLBACK(void OnCP(u8 command, u32 value))
{
// Note: No need to update m_cpmem as it already has the final value for this object
const auto [name, desc] = GetCPRegInfo(command, value);
ASSERT(!name.empty());
text = QObject::tr("CP register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += QObject::tr("No description available");
else
text += QString::fromStdString(desc);
}
OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data))
{
const auto [name, desc] = GetXFTransferInfo(address, count, data);
ASSERT(!name.empty());
text = QObject::tr("XF register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += QObject::tr("No description available");
else
text += QString::fromStdString(desc);
}
OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size))
{
const auto [desc, written] = GetXFIndexedLoadInfo(array, index, address, size);
text = QString::fromStdString(desc);
text += QLatin1Char{'\n'};
switch (array)
{
case CPArray::XF_A:
text += QObject::tr("Usually used for position matrices");
break;
case CPArray::XF_B:
// i18n: A normal matrix is a matrix used for transforming normal vectors. The word "normal"
// does not have its usual meaning here, but rather the meaning of "perpendicular to a
// surface".
text += QObject::tr("Usually used for normal matrices");
break;
case CPArray::XF_C:
// i18n: Tex coord is short for texture coordinate
text += QObject::tr("Usually used for tex coord matrices");
break;
case CPArray::XF_D:
text += QObject::tr("Usually used for light objects");
break;
default:
break;
}
text += QLatin1Char{'\n'};
text += QString::fromStdString(written);
}
OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat,
u32 vertex_size, u16 num_vertices, const u8* vertex_data))
{
const auto name = fmt::format("{} VAT {}", primitive, vat);
// i18n: In this context, a primitive means a point, line, triangle or rectangle.
// Do not translate the word primitive as if it was an adjective.
text = QObject::tr("Primitive %1").arg(QString::fromStdString(name));
text += QLatin1Char{'\n'};
const auto& vtx_desc = m_cpmem.vtx_desc;
const auto& vtx_attr = m_cpmem.vtx_attr[vat];
const auto component_sizes = VertexLoaderBase::GetVertexComponentSizes(vtx_desc, vtx_attr);
u32 i = 0;
for (u32 vertex_num = 0; vertex_num < num_vertices; 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(vertex_data[i++], 2, 16, QLatin1Char('0'));
}
text += QLatin1Char{' '};
}
}
}
OPCODE_CALLBACK(void OnDisplayList(u32 address, u32 size))
{
text = QObject::tr("No description available");
}
OPCODE_CALLBACK(void OnNop(u32 count)) { text = QObject::tr("No description available"); }
OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data))
{
text = QObject::tr("No description available");
}
OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)) {}
OPCODE_CALLBACK(CPState& GetCPState()) { return m_cpmem; }
QString text;
CPState m_cpmem;
};
} // namespace
void FIFOAnalyzer::UpdateDescription()
{
using OpcodeDecoder::Opcode;
m_entry_detail_browser->clear();
if (!FifoPlayer::GetInstance().IsPlaying())
@ -606,138 +681,12 @@ void FIFOAnalyzer::UpdateDescription()
const FifoFrameInfo& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr);
const u32 object_start = frame_info.parts[start_part_nr].m_start;
const u32 object_end = frame_info.parts[end_part_nr].m_end;
const u32 object_size = object_end - object_start;
const u32 entry_start = m_object_data_offsets[entry_nr];
const u8* cmddata = &fifo_frame.fifoData[object_start + entry_start];
const Opcode opcode = static_cast<Opcode>(*cmddata);
// TODO: Not sure whether we should bother translating the descriptions
QString text;
if (opcode == Opcode::GX_LOAD_BP_REG)
{
const u8 cmd = *(cmddata + 1);
const u32 value = Common::swap24(cmddata + 2);
const auto [name, desc] = GetBPRegInfo(cmd, value);
ASSERT(!name.empty());
text = tr("BP register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += tr("No description available");
else
text += QString::fromStdString(desc);
}
else if (opcode == Opcode::GX_LOAD_CP_REG)
{
const u8 cmd = *(cmddata + 1);
const u32 value = Common::swap32(cmddata + 2);
const auto [name, desc] = GetCPRegInfo(cmd, value);
ASSERT(!name.empty());
text = tr("CP register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += tr("No description available");
else
text += QString::fromStdString(desc);
}
else if (opcode == Opcode::GX_LOAD_XF_REG)
{
const auto [name, desc] = GetXFTransferInfo(cmddata + 1);
ASSERT(!name.empty());
text = tr("XF register ");
text += QString::fromStdString(name);
text += QLatin1Char{'\n'};
if (desc.empty())
text += tr("No description available");
else
text += QString::fromStdString(desc);
}
else if (opcode == Opcode::GX_LOAD_INDX_A)
{
const auto [desc, written] = GetXFIndexedLoadInfo(CPArray::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 (opcode == Opcode::GX_LOAD_INDX_B)
{
const auto [desc, written] = GetXFIndexedLoadInfo(CPArray::XF_B, Common::swap32(cmddata + 1));
text = QString::fromStdString(desc);
text += QLatin1Char{'\n'};
// i18n: A normal matrix is a matrix used for transforming normal vectors. The word "normal"
// does not have its usual meaning here, but rather the meaning of "perpendicular to a surface".
text += tr("Usually used for normal matrices");
text += QLatin1Char{'\n'};
text += QString::fromStdString(written);
}
else if (opcode == Opcode::GX_LOAD_INDX_C)
{
const auto [desc, written] = GetXFIndexedLoadInfo(CPArray::XF_C, Common::swap32(cmddata + 1));
text = QString::fromStdString(desc);
text += QLatin1Char{'\n'};
// i18n: Tex coord is short for texture coordinate
text += tr("Usually used for tex coord matrices");
text += QLatin1Char{'\n'};
text += QString::fromStdString(written);
}
else if (opcode == Opcode::GX_LOAD_INDX_D)
{
const auto [desc, written] = GetXFIndexedLoadInfo(CPArray::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 QString name = QString::fromStdString(GetPrimitiveName(*cmddata));
const u16 vertex_count = Common::swap16(cmddata + 1);
// i18n: In this context, a primitive means a point, line, triangle or rectangle.
// Do not translate the word primitive as if it was an adjective.
text = tr("Primitive %1").arg(name);
text += QLatin1Char{'\n'};
const auto& vtx_desc = frame_info.parts[end_part_nr].m_cpmem.vtxDesc;
const auto& vtx_attr = frame_info.parts[end_part_nr].m_cpmem.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");
}
m_entry_detail_browser->setText(text);
auto callback = DescriptionCallback(frame_info.parts[end_part_nr].m_cpmem);
OpcodeDecoder::RunCommand(&fifo_frame.fifoData[object_start + entry_start],
object_size - entry_start, callback);
m_entry_detail_browser->setText(callback.text);
}