diff --git a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp b/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp index d5ddf4310f..b5dcb5cd8c 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp +++ b/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp @@ -46,7 +46,9 @@ void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, s_DrawingObject = false; u32 cmdStart = 0; - u32 nextMemUpdate = 0; + + u32 part_start = 0; + FifoAnalyzer::CPMemory cpmem; #if LOG_FIFO_CMDS // Debugging @@ -55,14 +57,6 @@ void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, while (cmdStart < frame.fifoData.size()) { - // Add memory updates that have occurred before this point in the frame - while (nextMemUpdate < frame.memoryUpdates.size() && - frame.memoryUpdates[nextMemUpdate].fifoPosition <= cmdStart) - { - analyzed.memoryUpdates.push_back(frame.memoryUpdates[nextMemUpdate]); - ++nextMemUpdate; - } - const bool wasDrawing = s_DrawingObject; const u32 cmdSize = FifoAnalyzer::AnalyzeCommand(&frame.fifoData[cmdStart], DecodeMode::Playback); @@ -79,9 +73,7 @@ void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, if (cmdSize == 0) { // Clean up frame analysis - analyzed.objectStarts.clear(); - analyzed.objectCPStates.clear(); - analyzed.objectEnds.clear(); + analyzed.parts.clear(); return; } @@ -90,22 +82,28 @@ void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, { if (s_DrawingObject) { - analyzed.objectStarts.push_back(cmdStart); - analyzed.objectCPStates.push_back(s_CpMem); + // Start of primitive data for an object + analyzed.AddPart(FramePartType::Commands, part_start, cmdStart, s_CpMem); + part_start = cmdStart; + // Copy cpmem now, because end_of_primitives isn't triggered until the first opcode after + // primitive data, and the first opcode might update cpmem + std::memcpy(&cpmem, &s_CpMem, sizeof(FifoAnalyzer::CPMemory)); } else { - analyzed.objectEnds.push_back(cmdStart); + // End of primitive data for an object, and thus end of the object + analyzed.AddPart(FramePartType::PrimitiveData, part_start, cmdStart, cpmem); + part_start = cmdStart; } } cmdStart += cmdSize; } - if (analyzed.objectEnds.size() < analyzed.objectStarts.size()) - analyzed.objectEnds.push_back(cmdStart); - - ASSERT(analyzed.objectStarts.size() == analyzed.objectCPStates.size()); - ASSERT(analyzed.objectStarts.size() == analyzed.objectEnds.size()); + if (part_start != cmdStart) + { + // Remaining data, usually without any primitives + analyzed.AddPart(FramePartType::Commands, part_start, cmdStart, s_CpMem); + } } } diff --git a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.h b/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.h index 78e4c6e7d8..071279c885 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.h +++ b/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.h @@ -9,14 +9,35 @@ #include "Core/FifoPlayer/FifoAnalyzer.h" #include "Core/FifoPlayer/FifoDataFile.h" +enum class FramePartType +{ + Commands, + PrimitiveData, +}; + +struct FramePart +{ + constexpr FramePart(FramePartType type, u32 start, u32 end, const FifoAnalyzer::CPMemory& cpmem) + : m_type(type), m_start(start), m_end(end), m_cpmem(cpmem) + { + } + + const FramePartType m_type; + const u32 m_start; + const u32 m_end; + const FifoAnalyzer::CPMemory m_cpmem; +}; + struct AnalyzedFrameInfo { - // Start of the primitives for the object (after previous update commands) - std::vector objectStarts; - std::vector objectCPStates; - // End of the primitives for the object - std::vector objectEnds; - std::vector memoryUpdates; + std::vector parts; + Common::EnumMap part_type_counts; + + void AddPart(FramePartType type, u32 start, u32 end, const FifoAnalyzer::CPMemory& cpmem) + { + parts.emplace_back(type, start, end, cpmem); + part_type_counts[type]++; + } }; namespace FifoPlaybackAnalyzer diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp index 47b9ac2f87..cae033aa93 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp @@ -191,7 +191,7 @@ u32 FifoPlayer::GetMaxObjectCount() const u32 result = 0; for (auto& frame : m_FrameInfo) { - const u32 count = static_cast(frame.objectStarts.size()); + const u32 count = frame.part_type_counts[FramePartType::PrimitiveData]; if (count > result) result = count; } @@ -202,7 +202,7 @@ u32 FifoPlayer::GetFrameObjectCount(u32 frame) const { if (frame < m_FrameInfo.size()) { - return static_cast(m_FrameInfo[frame].objectStarts.size()); + return m_FrameInfo[frame].part_type_counts[FramePartType::PrimitiveData]; } return 0; @@ -262,55 +262,35 @@ void FifoPlayer::WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo& m_ElapsedCycles = 0; m_FrameFifoSize = static_cast(frame.fifoData.size()); - // Determine start and end objects - u32 numObjects = (u32)(info.objectStarts.size()); - u32 drawStart = std::min(numObjects, m_ObjectRangeStart); - u32 drawEnd = std::min(numObjects - 1, m_ObjectRangeEnd); + u32 memory_update = 0; + u32 object_num = 0; - u32 position = 0; - u32 memoryUpdate = 0; - - // Skip memory updates during frame if true + // Skip all memory updates if early memory updates are enabled, as we already wrote them if (m_EarlyMemoryUpdates) { - memoryUpdate = (u32)(frame.memoryUpdates.size()); + memory_update = (u32)(frame.memoryUpdates.size()); } - if (numObjects > 0) + for (const FramePart& part : info.parts) { - u32 objectNum = 0; + bool show_part; - // Write fifo data skipping objects before the draw range - while (objectNum < drawStart) + if (part.m_type == FramePartType::PrimitiveData) { - WriteFramePart(position, info.objectStarts[objectNum], memoryUpdate, frame, info); - - position = info.objectEnds[objectNum]; - ++objectNum; + show_part = m_ObjectRangeStart <= object_num && object_num <= m_ObjectRangeEnd; + object_num++; + } + else + { + // We always include commands and EFB copies, as commands from earlier objects still apply to + // later ones (games generally do not reconfigure everything for each object) + show_part = true; } - // Write objects in draw range - if (objectNum < numObjects && drawStart <= drawEnd) - { - objectNum = drawEnd; - WriteFramePart(position, info.objectEnds[objectNum], memoryUpdate, frame, info); - position = info.objectEnds[objectNum]; - ++objectNum; - } - - // Write fifo data skipping objects after the draw range - while (objectNum < numObjects) - { - WriteFramePart(position, info.objectStarts[objectNum], memoryUpdate, frame, info); - - position = info.objectEnds[objectNum]; - ++objectNum; - } + if (show_part) + WriteFramePart(part, &memory_update, frame); } - // Write data after the last object - WriteFramePart(position, static_cast(frame.fifoData.size()), memoryUpdate, frame, info); - FlushWGP(); // Sleep while the GPU is active @@ -321,36 +301,39 @@ void FifoPlayer::WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo& } } -void FifoPlayer::WriteFramePart(u32 dataStart, u32 dataEnd, u32& nextMemUpdate, - const FifoFrameInfo& frame, const AnalyzedFrameInfo& info) +void FifoPlayer::WriteFramePart(const FramePart& part, u32* next_mem_update, + const FifoFrameInfo& frame) { const u8* const data = frame.fifoData.data(); - while (nextMemUpdate < frame.memoryUpdates.size() && dataStart < dataEnd) - { - const MemoryUpdate& memUpdate = info.memoryUpdates[nextMemUpdate]; + u32 data_start = part.m_start; + const u32 data_end = part.m_end; - if (memUpdate.fifoPosition < dataEnd) + while (*next_mem_update < frame.memoryUpdates.size() && data_start < data_end) + { + const MemoryUpdate& memUpdate = frame.memoryUpdates[*next_mem_update]; + + if (memUpdate.fifoPosition < data_end) { - if (dataStart < memUpdate.fifoPosition) + if (data_start < memUpdate.fifoPosition) { - WriteFifo(data, dataStart, memUpdate.fifoPosition); - dataStart = memUpdate.fifoPosition; + WriteFifo(data, data_start, memUpdate.fifoPosition); + data_start = memUpdate.fifoPosition; } WriteMemory(memUpdate); - ++nextMemUpdate; + ++*next_mem_update; } else { - WriteFifo(data, dataStart, dataEnd); - dataStart = dataEnd; + WriteFifo(data, data_start, data_end); + data_start = data_end; } } - if (dataStart < dataEnd) - WriteFifo(data, dataStart, dataEnd); + if (data_start < data_end) + WriteFifo(data, data_start, data_end); } void FifoPlayer::WriteAllMemoryUpdates() diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.h b/Source/Core/Core/FifoPlayer/FifoPlayer.h index 01ce07c4a0..8083612658 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.h +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.h @@ -108,8 +108,7 @@ private: CPU::State AdvanceFrame(); void WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo& info); - void WriteFramePart(u32 dataStart, u32 dataEnd, u32& nextMemUpdate, const FifoFrameInfo& frame, - const AnalyzedFrameInfo& info); + void WriteFramePart(const FramePart& part, u32* next_mem_update, const FifoFrameInfo& frame); void WriteAllMemoryUpdates(); void WriteMemory(const MemoryUpdate& memUpdate); diff --git a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp index 73ba823ec1..223fdd7145 100644 --- a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp +++ b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp @@ -3,6 +3,8 @@ #include "DolphinQt/FIFO/FIFOAnalyzer.h" +#include + #include #include #include @@ -27,8 +29,12 @@ #include "VideoCommon/VertexLoaderBase.h" #include "VideoCommon/XFStructs.h" +// Values range from 0 to number of frames - 1 constexpr int FRAME_ROLE = Qt::UserRole; -constexpr int OBJECT_ROLE = Qt::UserRole + 1; +// Values range from 0 to number of parts - 1 +constexpr int PART_START_ROLE = Qt::UserRole + 1; +// Values range from 1 to number of parts +constexpr int PART_END_ROLE = Qt::UserRole + 2; FIFOAnalyzer::FIFOAnalyzer() { @@ -144,22 +150,58 @@ void FIFOAnalyzer::UpdateTree() auto* file = FifoPlayer::GetInstance().GetFile(); const u32 frame_count = file->GetFrameCount(); + for (u32 frame = 0; frame < frame_count; frame++) { auto* frame_item = new QTreeWidgetItem({tr("Frame %1").arg(frame)}); recording_item->addChild(frame_item); - 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(object)}); + const AnalyzedFrameInfo& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame); + ASSERT(frame_info.parts.size() != 0); + Common::EnumMap part_counts; + u32 part_start = 0; + + for (u32 part_nr = 0; part_nr < frame_info.parts.size(); part_nr++) + { + const auto& part = frame_info.parts[part_nr]; + + const u32 part_type_nr = part_counts[part.m_type]; + part_counts[part.m_type]++; + + QTreeWidgetItem* object_item = nullptr; + if (part.m_type == FramePartType::PrimitiveData) + object_item = new QTreeWidgetItem({tr("Object %1").arg(part_type_nr)}); + // We don't create dedicated labels for FramePartType::Command; + // those are grouped with the primitive + + if (object_item != nullptr) + { + frame_item->addChild(object_item); + + object_item->setData(0, FRAME_ROLE, frame); + object_item->setData(0, PART_START_ROLE, part_start); + object_item->setData(0, PART_END_ROLE, part_nr); + + part_start = part_nr + 1; + } + } + + // Final data (the XFB copy) + if (part_start != frame_info.parts.size()) + { + QTreeWidgetItem* object_item = new QTreeWidgetItem({tr("Final Data")}); frame_item->addChild(object_item); object_item->setData(0, FRAME_ROLE, frame); - object_item->setData(0, OBJECT_ROLE, object); + object_item->setData(0, PART_START_ROLE, part_start); + object_item->setData(0, PART_END_ROLE, u32(frame_info.parts.size() - 1)); } + + // The counts we computed should match the frame's counts + ASSERT(std::equal(frame_info.part_type_counts.begin(), frame_info.part_type_counts.end(), + part_counts.begin())); } } @@ -196,19 +238,19 @@ void FIFOAnalyzer::UpdateDetails() const auto items = m_tree_widget->selectedItems(); - if (items.isEmpty() || items[0]->data(0, OBJECT_ROLE).isNull()) + if (items.isEmpty() || items[0]->data(0, PART_START_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 start_part_nr = items[0]->data(0, PART_START_ROLE).toUInt(); + const u32 end_part_nr = items[0]->data(0, PART_END_ROLE).toUInt(); - const auto& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr); + const AnalyzedFrameInfo& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr); const auto& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr); - // 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; + 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 u8* const object = &fifo_frame.fifoData[object_start]; @@ -348,10 +390,9 @@ void FIFOAnalyzer::UpdateDetails() 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& 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); @@ -396,8 +437,6 @@ void FIFOAnalyzer::UpdateDetails() 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); } @@ -412,12 +451,15 @@ void FIFOAnalyzer::BeginSearch() const auto items = m_tree_widget->selectedItems(); if (items.isEmpty() || items[0]->data(0, FRAME_ROLE).isNull() || - items[0]->data(0, OBJECT_ROLE).isNull()) + items[0]->data(0, PART_START_ROLE).isNull()) { m_search_label->setText(tr("Invalid search parameters (no object selected)")); return; } + // Having PART_START_ROLE indicates that this is valid + const int object_idx = items[0]->parent()->indexOfChild(items[0]); + // TODO: Remove even string length limit if (search_str.length() % 2) { @@ -448,13 +490,15 @@ void FIFOAnalyzer::BeginSearch() m_search_results.clear(); const u32 frame_nr = items[0]->data(0, FRAME_ROLE).toUInt(); - const u32 object_nr = items[0]->data(0, OBJECT_ROLE).toUInt(); + const u32 start_part_nr = items[0]->data(0, PART_START_ROLE).toUInt(); + const u32 end_part_nr = items[0]->data(0, PART_END_ROLE).toUInt(); const AnalyzedFrameInfo& frame_info = FifoPlayer::GetInstance().GetAnalyzedFrameInfo(frame_nr); const FifoFrameInfo& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr); - 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 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 u8* const object = &fifo_frame.fifoData[object_start]; @@ -473,7 +517,7 @@ void FIFOAnalyzer::BeginSearch() { if (std::equal(search_val.begin(), search_val.end(), ptr)) { - m_search_results.emplace_back(frame_nr, object_nr, cmd_nr); + m_search_results.emplace_back(frame_nr, object_idx, cmd_nr); break; } } @@ -527,7 +571,7 @@ void FIFOAnalyzer::ShowSearchResult(size_t index) const auto& result = m_search_results[index]; QTreeWidgetItem* object_item = - m_tree_widget->topLevelItem(0)->child(result.m_frame)->child(result.m_object); + m_tree_widget->topLevelItem(0)->child(result.m_frame)->child(result.m_object_idx); m_tree_widget->setCurrentItem(object_item); m_detail_list->setCurrentRow(result.m_cmd); @@ -550,17 +594,18 @@ void FIFOAnalyzer::UpdateDescription() if (items.isEmpty() || m_object_data_offsets.empty()) return; - if (items[0]->data(0, FRAME_ROLE).isNull() || items[0]->data(0, OBJECT_ROLE).isNull()) + if (items[0]->data(0, FRAME_ROLE).isNull() || items[0]->data(0, PART_START_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 start_part_nr = items[0]->data(0, PART_START_ROLE).toUInt(); + const u32 end_part_nr = items[0]->data(0, PART_END_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 u32 object_start = (object_nr == 0 ? 0 : frame_info.objectEnds[object_nr - 1]); + const u32 object_start = frame_info.parts[start_part_nr].m_start; const u32 entry_start = m_object_data_offsets[entry_nr]; const u8* cmddata = &fifo_frame.fifoData[object_start + entry_start]; @@ -671,8 +716,8 @@ void FIFOAnalyzer::UpdateDescription() text = tr("Primitive %1").arg(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& 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; diff --git a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h index 6a1c0a948a..222ce8e06b 100644 --- a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h +++ b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.h @@ -58,15 +58,19 @@ private: struct SearchResult { - constexpr SearchResult(u32 frame, u32 object, u32 cmd) - : m_frame(frame), m_object(object), m_cmd(cmd) + constexpr SearchResult(u32 frame, u32 object_idx, u32 cmd) + : m_frame(frame), m_object_idx(object_idx), m_cmd(cmd) { } const u32 m_frame; - const u32 m_object; + // Index in tree view. Does not correspond with object numbers or part numbers. + const u32 m_object_idx; const u32 m_cmd; }; + // Offsets from the start of the first part in an object for each command within the currently + // selected object. std::vector m_object_data_offsets; + std::vector m_search_results; };