diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index a06a391c06..c0608572df 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -103,16 +103,10 @@ add_library(core DSP/LabelMap.h DSPEmulator.cpp DSPEmulator.h - FifoPlayer/FifoAnalyzer.cpp - FifoPlayer/FifoAnalyzer.h FifoPlayer/FifoDataFile.cpp FifoPlayer/FifoDataFile.h - FifoPlayer/FifoPlaybackAnalyzer.cpp - FifoPlayer/FifoPlaybackAnalyzer.h FifoPlayer/FifoPlayer.cpp FifoPlayer/FifoPlayer.h - FifoPlayer/FifoRecordAnalyzer.cpp - FifoPlayer/FifoRecordAnalyzer.h FifoPlayer/FifoRecorder.cpp FifoPlayer/FifoRecorder.h FreeLookConfig.cpp diff --git a/Source/Core/Core/FifoPlayer/FifoAnalyzer.cpp b/Source/Core/Core/FifoPlayer/FifoAnalyzer.cpp deleted file mode 100644 index ca9ed3574d..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoAnalyzer.cpp +++ /dev/null @@ -1,295 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/FifoPlayer/FifoAnalyzer.h" - -#include - -#include "Common/Assert.h" -#include "Common/MsgHandler.h" -#include "Common/Swap.h" - -#include "Core/FifoPlayer/FifoRecordAnalyzer.h" - -#include "VideoCommon/OpcodeDecoding.h" -#include "VideoCommon/VertexLoader.h" -#include "VideoCommon/VertexLoader_Normal.h" -#include "VideoCommon/VertexLoader_Position.h" -#include "VideoCommon/VertexLoader_TextCoord.h" - -namespace FifoAnalyzer -{ -namespace -{ -u8 ReadFifo8(const u8*& data) -{ - const u8 value = data[0]; - data += 1; - return value; -} - -u16 ReadFifo16(const u8*& data) -{ - const u16 value = Common::swap16(data); - data += 2; - return value; -} - -u32 ReadFifo32(const u8*& data) -{ - const u32 value = Common::swap32(data); - data += 4; - return value; -} - -std::array CalculateVertexElementSizes(int vatIndex, const CPMemory& cpMem) -{ - const TVtxDesc& vtxDesc = cpMem.vtxDesc; - const VAT& vtxAttr = cpMem.vtxAttr[vatIndex]; - - // Colors - const std::array colComp{ - vtxAttr.g0.Color0Comp, - vtxAttr.g0.Color1Comp, - }; - - const std::array tcElements{ - vtxAttr.g0.Tex0CoordElements, vtxAttr.g1.Tex1CoordElements, vtxAttr.g1.Tex2CoordElements, - vtxAttr.g1.Tex3CoordElements, vtxAttr.g1.Tex4CoordElements, vtxAttr.g2.Tex5CoordElements, - vtxAttr.g2.Tex6CoordElements, vtxAttr.g2.Tex7CoordElements, - }; - const std::array tcFormat{ - vtxAttr.g0.Tex0CoordFormat, vtxAttr.g1.Tex1CoordFormat, vtxAttr.g1.Tex2CoordFormat, - vtxAttr.g1.Tex3CoordFormat, vtxAttr.g1.Tex4CoordFormat, vtxAttr.g2.Tex5CoordFormat, - vtxAttr.g2.Tex6CoordFormat, vtxAttr.g2.Tex7CoordFormat, - }; - - std::array sizes{}; - - // Add position and texture matrix indices - sizes[0] = vtxDesc.low.PosMatIdx; - for (size_t i = 0; i < vtxDesc.low.TexMatIdx.Size(); ++i) - { - sizes[i + 1] = vtxDesc.low.TexMatIdx[i]; - } - - // Position - sizes[9] = VertexLoader_Position::GetSize(vtxDesc.low.Position, vtxAttr.g0.PosFormat, - vtxAttr.g0.PosElements); - - // Normals - if (vtxDesc.low.Normal != VertexComponentFormat::NotPresent) - { - sizes[10] = VertexLoader_Normal::GetSize(vtxDesc.low.Normal, vtxAttr.g0.NormalFormat, - vtxAttr.g0.NormalElements, vtxAttr.g0.NormalIndex3); - } - else - { - sizes[10] = 0; - } - - // Colors - for (size_t i = 0; i < vtxDesc.low.Color.Size(); i++) - { - int size = 0; - - switch (vtxDesc.low.Color[i]) - { - case VertexComponentFormat::NotPresent: - break; - case VertexComponentFormat::Direct: - switch (colComp[i]) - { - case ColorFormat::RGB565: - size = 2; - break; - case ColorFormat::RGB888: - size = 3; - break; - case ColorFormat::RGB888x: - size = 4; - break; - case ColorFormat::RGBA4444: - size = 2; - break; - case ColorFormat::RGBA6666: - size = 3; - break; - case ColorFormat::RGBA8888: - size = 4; - break; - default: - ASSERT(0); - break; - } - break; - case VertexComponentFormat::Index8: - size = 1; - break; - case VertexComponentFormat::Index16: - size = 2; - break; - } - - sizes[11 + i] = size; - } - - // Texture coordinates - for (size_t i = 0; i < tcFormat.size(); i++) - { - sizes[13 + i] = - VertexLoader_TextCoord::GetSize(vtxDesc.high.TexCoord[i], tcFormat[i], tcElements[i]); - } - - return sizes; -} -} // Anonymous namespace - -bool s_DrawingObject; -FifoAnalyzer::CPMemory s_CpMem; - -u32 AnalyzeCommand(const u8* data, DecodeMode mode) -{ - using OpcodeDecoder::Opcode; - const u8* dataStart = data; - - int cmd = ReadFifo8(data); - - switch (static_cast(cmd)) - { - case Opcode::GX_NOP: - case Opcode::GX_CMD_UNKNOWN_METRICS: - case Opcode::GX_CMD_INVL_VC: - break; - - case Opcode::GX_LOAD_CP_REG: - { - s_DrawingObject = false; - - u32 cmd2 = ReadFifo8(data); - u32 value = ReadFifo32(data); - LoadCPReg(cmd2, value, s_CpMem); - break; - } - - case Opcode::GX_LOAD_XF_REG: - { - s_DrawingObject = false; - - u32 cmd2 = ReadFifo32(data); - u8 streamSize = ((cmd2 >> 16) & 15) + 1; - - data += streamSize * 4; - break; - } - - case Opcode::GX_LOAD_INDX_A: - case Opcode::GX_LOAD_INDX_B: - case Opcode::GX_LOAD_INDX_C: - case Opcode::GX_LOAD_INDX_D: - { - s_DrawingObject = false; - - CPArray array = static_cast(0xc + (cmd - static_cast(Opcode::GX_LOAD_INDX_A)) / 8); - u32 value = ReadFifo32(data); - - if (mode == DecodeMode::Record) - FifoRecordAnalyzer::ProcessLoadIndexedXf(array, value); - 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); - data += 8; - break; - - case Opcode::GX_LOAD_BP_REG: - { - s_DrawingObject = false; - ReadFifo32(data); - break; - } - - default: - if (cmd & 0x80) - { - s_DrawingObject = true; - - const std::array sizes = - CalculateVertexElementSizes(cmd & OpcodeDecoder::GX_VAT_MASK, s_CpMem); - - // Determine offset of each element that might be a vertex array - // The first 9 elements are never vertex arrays so we just accumulate their sizes. - int offset = std::accumulate(sizes.begin(), sizes.begin() + 9, 0u); - std::array offsets; - for (size_t i = 0; i < offsets.size(); ++i) - { - offsets[i] = offset; - offset += sizes[i + 9]; - } - - const int vertexSize = offset; - const int numVertices = ReadFifo16(data); - - if (mode == DecodeMode::Record && numVertices > 0) - { - for (size_t i = 0; i < offsets.size(); ++i) - { - FifoRecordAnalyzer::WriteVertexArray(static_cast(i), data + offsets[i], - vertexSize, numVertices); - } - } - - data += numVertices * vertexSize; - } - else - { - PanicAlertFmt("FifoPlayer: Unknown Opcode ({:#x}).\n", cmd); - return 0; - } - break; - } - - return (u32)(data - dataStart); -} - -void LoadCPReg(u32 subCmd, u32 value, CPMemory& cpMem) -{ - switch (subCmd & CP_COMMAND_MASK) - { - case VCD_LO: - cpMem.vtxDesc.low.Hex = value; - break; - - case VCD_HI: - cpMem.vtxDesc.high.Hex = value; - break; - - case CP_VAT_REG_A: - ASSERT(subCmd - CP_VAT_REG_A < CP_NUM_VAT_REG); - cpMem.vtxAttr[subCmd & CP_VAT_MASK].g0.Hex = value; - break; - - case CP_VAT_REG_B: - ASSERT(subCmd - CP_VAT_REG_B < CP_NUM_VAT_REG); - cpMem.vtxAttr[subCmd & CP_VAT_MASK].g1.Hex = value; - break; - - case CP_VAT_REG_C: - ASSERT(subCmd - CP_VAT_REG_C < CP_NUM_VAT_REG); - cpMem.vtxAttr[subCmd & CP_VAT_MASK].g2.Hex = value; - break; - - case ARRAY_BASE: - cpMem.arrayBases[static_cast(subCmd & CP_ARRAY_MASK)] = value; - break; - - case ARRAY_STRIDE: - cpMem.arrayStrides[static_cast(subCmd & CP_ARRAY_MASK)] = value & 0xFF; - break; - } -} -} // namespace FifoAnalyzer diff --git a/Source/Core/Core/FifoPlayer/FifoAnalyzer.h b/Source/Core/Core/FifoPlayer/FifoAnalyzer.h deleted file mode 100644 index e9604ce918..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoAnalyzer.h +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include - -#include "Common/CommonTypes.h" -#include "Common/EnumMap.h" - -#include "VideoCommon/CPMemory.h" - -namespace FifoAnalyzer -{ -enum class DecodeMode -{ - Record, - Playback, -}; - -u32 AnalyzeCommand(const u8* data, DecodeMode mode); - -struct CPMemory -{ - TVtxDesc vtxDesc; - std::array vtxAttr; - Common::EnumMap arrayBases{}; - Common::EnumMap arrayStrides{}; -}; - -void LoadCPReg(u32 subCmd, u32 value, CPMemory& cpMem); - -extern bool s_DrawingObject; -extern FifoAnalyzer::CPMemory s_CpMem; -} // namespace FifoAnalyzer diff --git a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp b/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp deleted file mode 100644 index b5dcb5cd8c..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.cpp +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h" - -#include - -#include "Common/Assert.h" -#include "Common/CommonTypes.h" -#include "Core/FifoPlayer/FifoAnalyzer.h" -#include "Core/FifoPlayer/FifoDataFile.h" - -using namespace FifoAnalyzer; - -// For debugging -#define LOG_FIFO_CMDS 0 -struct CmdData -{ - u32 size; - u32 offset; - const u8* ptr; -}; - -void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, - std::vector& frameInfo) -{ - u32* cpMem = file->GetCPMem(); - FifoAnalyzer::LoadCPReg(VCD_LO, cpMem[VCD_LO], s_CpMem); - FifoAnalyzer::LoadCPReg(VCD_HI, cpMem[VCD_HI], s_CpMem); - - for (u32 i = 0; i < CP_NUM_VAT_REG; ++i) - { - FifoAnalyzer::LoadCPReg(CP_VAT_REG_A + i, cpMem[CP_VAT_REG_A + i], s_CpMem); - FifoAnalyzer::LoadCPReg(CP_VAT_REG_B + i, cpMem[CP_VAT_REG_B + i], s_CpMem); - FifoAnalyzer::LoadCPReg(CP_VAT_REG_C + i, cpMem[CP_VAT_REG_C + i], s_CpMem); - } - - frameInfo.clear(); - frameInfo.resize(file->GetFrameCount()); - - for (u32 frameIdx = 0; frameIdx < file->GetFrameCount(); ++frameIdx) - { - const FifoFrameInfo& frame = file->GetFrame(frameIdx); - AnalyzedFrameInfo& analyzed = frameInfo[frameIdx]; - - s_DrawingObject = false; - - u32 cmdStart = 0; - - u32 part_start = 0; - FifoAnalyzer::CPMemory cpmem; - -#if LOG_FIFO_CMDS - // Debugging - std::vector prevCmds; -#endif - - while (cmdStart < frame.fifoData.size()) - { - const bool wasDrawing = s_DrawingObject; - const u32 cmdSize = - FifoAnalyzer::AnalyzeCommand(&frame.fifoData[cmdStart], DecodeMode::Playback); - -#if LOG_FIFO_CMDS - CmdData cmdData; - cmdData.offset = cmdStart; - cmdData.ptr = &frame.fifoData[cmdStart]; - cmdData.size = cmdSize; - prevCmds.push_back(cmdData); -#endif - - // Check for error - if (cmdSize == 0) - { - // Clean up frame analysis - analyzed.parts.clear(); - - return; - } - - if (wasDrawing != s_DrawingObject) - { - if (s_DrawingObject) - { - // 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 - { - // 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 (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 deleted file mode 100644 index 071279c885..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoPlaybackAnalyzer.h +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#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 -{ - 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 -{ -void AnalyzeFrames(FifoDataFile* file, std::vector& frameInfo); -} // namespace FifoPlaybackAnalyzer diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp index cae033aa93..bbb08a7ddd 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.cpp +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.cpp @@ -4,6 +4,7 @@ #include "Core/FifoPlayer/FifoPlayer.h" #include +#include #include #include "Common/Assert.h" @@ -12,7 +13,6 @@ #include "Core/ConfigManager.h" #include "Core/Core.h" #include "Core/CoreTiming.h" -#include "Core/FifoPlayer/FifoAnalyzer.h" #include "Core/FifoPlayer/FifoDataFile.h" #include "Core/HW/CPU.h" #include "Core/HW/GPFifo.h" @@ -31,6 +31,121 @@ // TODO: Move texMem somewhere else so this isn't an issue. #include "VideoCommon/TextureDecoder.h" +namespace +{ +class FifoPlaybackAnalyzer : public OpcodeDecoder::Callback +{ +public: + static void AnalyzeFrames(FifoDataFile* file, std::vector& frame_info); + + explicit FifoPlaybackAnalyzer(const u32* cpmem) : m_cpmem(cpmem) {} + + OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data)) {} + OPCODE_CALLBACK(void OnCP(u8 command, u32 value)) { GetCPState().LoadCPReg(command, value); } + OPCODE_CALLBACK(void OnBP(u8 command, u32 value)) {} + OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size)) {} + OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, + u32 vertex_size, u16 num_vertices, + const u8* vertex_data)); + OPCODE_CALLBACK(void OnDisplayList(u32 address, u32 size)) {} + OPCODE_CALLBACK(void OnNop(u32 count)); + OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data)) {} + + OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)); + + OPCODE_CALLBACK(CPState& GetCPState()) { return m_cpmem; } + + bool m_start_of_primitives = false; + bool m_end_of_primitives = false; + // Internal state, copied to above in OnCommand + bool m_was_primitive = false; + bool m_is_primitive = false; + bool m_is_nop = false; + CPState m_cpmem; +}; + +void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile* file, + std::vector& frame_info) +{ + FifoPlaybackAnalyzer analyzer(file->GetCPMem()); + frame_info.clear(); + frame_info.resize(file->GetFrameCount()); + + for (u32 frame_no = 0; frame_no < file->GetFrameCount(); frame_no++) + { + const FifoFrameInfo& frame = file->GetFrame(frame_no); + AnalyzedFrameInfo& analyzed = frame_info[frame_no]; + + u32 offset = 0; + + u32 part_start = 0; + CPState cpmem; + + while (offset < frame.fifoData.size()) + { + const u32 cmd_size = OpcodeDecoder::RunCommand(&frame.fifoData[offset], + u32(frame.fifoData.size()) - offset, analyzer); + + if (analyzer.m_start_of_primitives) + { + // Start of primitive data for an object + analyzed.AddPart(FramePartType::Commands, part_start, offset, analyzer.m_cpmem); + part_start = offset; + // 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, &analyzer.m_cpmem, sizeof(CPState)); + } + if (analyzer.m_end_of_primitives) + { + // End of primitive data for an object, and thus end of the object + analyzed.AddPart(FramePartType::PrimitiveData, part_start, offset, cpmem); + part_start = offset; + } + + offset += cmd_size; + } + + if (part_start != offset) + { + // Remaining data, usually without any primitives + analyzed.AddPart(FramePartType::Commands, part_start, offset, analyzer.m_cpmem); + } + + ASSERT(offset == frame.fifoData.size()); + } +} + +void FifoPlaybackAnalyzer::OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, + u32 vertex_size, u16 num_vertices, + const u8* vertex_data) +{ + m_is_primitive = true; +} + +void FifoPlaybackAnalyzer::OnNop(u32 count) +{ + m_is_nop = true; +} + +void FifoPlaybackAnalyzer::OnCommand(const u8* data, u32 size) +{ + m_start_of_primitives = false; + m_end_of_primitives = false; + + if (!m_is_nop) + { + if (m_is_primitive && !m_was_primitive) + m_start_of_primitives = true; + else if (m_was_primitive && !m_is_primitive) + m_end_of_primitives = true; + + m_was_primitive = m_is_primitive; + } + m_is_primitive = false; + m_is_nop = false; +} +} // namespace + bool IsPlayingBackFifologWithBrokenEFBCopies = false; FifoPlayer::FifoPlayer() : m_Loop{SConfig::GetInstance().bLoopFifoReplay} diff --git a/Source/Core/Core/FifoPlayer/FifoPlayer.h b/Source/Core/Core/FifoPlayer/FifoPlayer.h index 96a170810c..ffae2e92d4 100644 --- a/Source/Core/Core/FifoPlayer/FifoPlayer.h +++ b/Source/Core/Core/FifoPlayer/FifoPlayer.h @@ -8,13 +8,14 @@ #include #include +#include "Common/Assert.h" #include "Core/FifoPlayer/FifoDataFile.h" -#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h" #include "Core/PowerPC/CPUCoreBase.h" +#include "VideoCommon/CPMemory.h" +#include "VideoCommon/OpcodeDecoding.h" class FifoDataFile; struct MemoryUpdate; -struct AnalyzedFrameInfo; namespace CPU { @@ -51,6 +52,37 @@ enum class State; // Shitty global to fix a shitty problem extern bool IsPlayingBackFifologWithBrokenEFBCopies; +enum class FramePartType +{ + Commands, + PrimitiveData, +}; + +struct FramePart +{ + constexpr FramePart(FramePartType type, u32 start, u32 end, const CPState& 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 CPState m_cpmem; +}; + +struct AnalyzedFrameInfo +{ + std::vector parts; + Common::EnumMap part_type_counts; + + void AddPart(FramePartType type, u32 start, u32 end, const CPState& cpmem) + { + parts.emplace_back(type, start, end, cpmem); + part_type_counts[type]++; + } +}; + class FifoPlayer { public: @@ -100,7 +132,6 @@ public: private: class CPUCore; - FifoPlayer(); CPU::State AdvanceFrame(); diff --git a/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.cpp b/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.cpp deleted file mode 100644 index 4ada443fcf..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.cpp +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#include "Core/FifoPlayer/FifoRecordAnalyzer.h" - -#include - -#include "Common/MsgHandler.h" -#include "Core/FifoPlayer/FifoAnalyzer.h" -#include "Core/FifoPlayer/FifoRecorder.h" -#include "Core/HW/Memmap.h" - -using namespace FifoAnalyzer; - -void FifoRecordAnalyzer::Initialize(const u32* cpMem) -{ - s_DrawingObject = false; - - FifoAnalyzer::LoadCPReg(VCD_LO, cpMem[VCD_LO], s_CpMem); - FifoAnalyzer::LoadCPReg(VCD_HI, cpMem[VCD_HI], s_CpMem); - for (u32 i = 0; i < CP_NUM_VAT_REG; ++i) - FifoAnalyzer::LoadCPReg(CP_VAT_REG_A + i, cpMem[CP_VAT_REG_A + i], s_CpMem); - - const u32* const bases_start = cpMem + ARRAY_BASE; - const u32* const bases_end = bases_start + s_CpMem.arrayBases.size(); - std::copy(bases_start, bases_end, s_CpMem.arrayBases.begin()); - - const u32* const strides_start = cpMem + ARRAY_STRIDE; - const u32* const strides_end = strides_start + s_CpMem.arrayStrides.size(); - std::copy(strides_start, strides_end, s_CpMem.arrayStrides.begin()); -} - -void FifoRecordAnalyzer::ProcessLoadIndexedXf(CPArray array, u32 val) -{ - int index = val >> 16; - int size = ((val >> 12) & 0xF) + 1; - - u32 address = s_CpMem.arrayBases[array] + s_CpMem.arrayStrides[array] * index; - - FifoRecorder::GetInstance().UseMemory(address, size * 4, MemoryUpdate::XF_DATA); -} - -void FifoRecordAnalyzer::WriteVertexArray(CPArray arrayIndex, const u8* vertexData, int vertexSize, - int numVertices) -{ - // Skip if not indexed array - VertexComponentFormat arrayType; - if (arrayIndex == CPArray::Position) - arrayType = s_CpMem.vtxDesc.low.Position; - else if (arrayIndex == CPArray::Normal) - arrayType = s_CpMem.vtxDesc.low.Normal; - else if (arrayIndex >= CPArray::Color0 && arrayIndex <= CPArray::Color1) - arrayType = s_CpMem.vtxDesc.low.Color[u8(arrayIndex) - u8(CPArray::Color0)]; - else if (arrayIndex >= CPArray::TexCoord0 && arrayIndex <= CPArray::TexCoord7) - arrayType = s_CpMem.vtxDesc.high.TexCoord[u8(arrayIndex) - u8(CPArray::TexCoord0)]; - else - { - PanicAlertFmt("Invalid arrayIndex {}", arrayIndex); - return; - } - - if (!IsIndexed(arrayType)) - return; - - int maxIndex = 0; - - // Determine min and max indices - if (arrayType == VertexComponentFormat::Index8) - { - for (int i = 0; i < numVertices; ++i) - { - int index = *vertexData; - vertexData += vertexSize; - - // 0xff skips the vertex - if (index != 0xff) - { - if (index > maxIndex) - maxIndex = index; - } - } - } - else - { - for (int i = 0; i < numVertices; ++i) - { - int index = Common::swap16(vertexData); - vertexData += vertexSize; - - // 0xffff skips the vertex - if (index != 0xffff) - { - if (index > maxIndex) - maxIndex = index; - } - } - } - - u32 arrayStart = s_CpMem.arrayBases[arrayIndex]; - u32 arraySize = s_CpMem.arrayStrides[arrayIndex] * (maxIndex + 1); - - FifoRecorder::GetInstance().UseMemory(arrayStart, arraySize, MemoryUpdate::VERTEX_STREAM); -} diff --git a/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.h b/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.h deleted file mode 100644 index d1ac21c09b..0000000000 --- a/Source/Core/Core/FifoPlayer/FifoRecordAnalyzer.h +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2011 Dolphin Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include "Common/CommonTypes.h" - -enum class CPArray : u8; - -namespace FifoRecordAnalyzer -{ -// Must call this before analyzing Fifo commands with FifoAnalyzer::AnalyzeCommand() -void Initialize(const u32* cpMem); - -void ProcessLoadIndexedXf(CPArray array, u32 val); -void WriteVertexArray(CPArray arrayIndex, const u8* vertexData, int vertexSize, int numVertices); -} // namespace FifoRecordAnalyzer diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp index 8c4bf184bc..47cb25d84b 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.cpp +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.cpp @@ -6,13 +6,168 @@ #include #include +#include "Common/Logging/Log.h" #include "Common/MsgHandler.h" #include "Common/Thread.h" + #include "Core/ConfigManager.h" -#include "Core/FifoPlayer/FifoAnalyzer.h" -#include "Core/FifoPlayer/FifoRecordAnalyzer.h" #include "Core/HW/Memmap.h" +#include "VideoCommon/OpcodeDecoding.h" +#include "VideoCommon/XFStructs.h" + +class FifoRecorder::FifoRecordAnalyzer : public OpcodeDecoder::Callback +{ +public: + explicit FifoRecordAnalyzer(FifoRecorder* owner) : m_owner(owner) {} + explicit FifoRecordAnalyzer(FifoRecorder* owner, const u32* cpmem) + : m_owner(owner), m_cpmem(cpmem) + { + } + + OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data)) {} + OPCODE_CALLBACK(void OnCP(u8 command, u32 value)) { GetCPState().LoadCPReg(command, value); } + OPCODE_CALLBACK(void OnBP(u8 command, u32 value)) {} + OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size)); + OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, + u32 vertex_size, u16 num_vertices, + const u8* vertex_data)); + OPCODE_CALLBACK(void OnDisplayList(u32 address, u32 size)) + { + WARN_LOG_FMT(VIDEO, + "Unhandled display list call {:08x} {:08x}; should have been inlined earlier", + address, size); + } + OPCODE_CALLBACK(void OnNop(u32 count)) {} + OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data)) {} + + OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)) {} + + OPCODE_CALLBACK(CPState& GetCPState()) { return m_cpmem; } + +private: + void ProcessVertexComponent(CPArray array_index, VertexComponentFormat array_type, + u32 component_offset, u32 vertex_size, u16 num_vertices, + const u8* vertex_data); + + FifoRecorder* const m_owner; + CPState m_cpmem; +}; + +void FifoRecorder::FifoRecordAnalyzer::OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size) +{ + const u32 load_address = m_cpmem.array_bases[array] + m_cpmem.array_strides[array] * index; + + m_owner->UseMemory(load_address, size * sizeof(u32), MemoryUpdate::XF_DATA); +} + +// TODO: The following code is copied with modifications from VertexLoaderBase. +// Surely there's a better solution? +#include "VideoCommon/VertexLoader_Color.h" +#include "VideoCommon/VertexLoader_Normal.h" +#include "VideoCommon/VertexLoader_Position.h" +#include "VideoCommon/VertexLoader_TextCoord.h" + +void FifoRecorder::FifoRecordAnalyzer::OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, + u8 vat, u32 vertex_size, u16 num_vertices, + const u8* vertex_data) +{ + const auto& vtx_desc = m_cpmem.vtx_desc; + const auto& vtx_attr = m_cpmem.vtx_attr[vat]; + + u32 offset = 0; + + if (vtx_desc.low.PosMatIdx) + offset++; + for (auto texmtxidx : vtx_desc.low.TexMatIdx) + { + if (texmtxidx) + offset++; + } + const u32 pos_size = VertexLoader_Position::GetSize(vtx_desc.low.Position, vtx_attr.g0.PosFormat, + vtx_attr.g0.PosElements); + ProcessVertexComponent(CPArray::Position, vtx_desc.low.Position, offset, vertex_size, num_vertices, + vertex_data); + offset += pos_size; + + const u32 norm_size = + VertexLoader_Normal::GetSize(vtx_desc.low.Normal, vtx_attr.g0.NormalFormat, + vtx_attr.g0.NormalElements, vtx_attr.g0.NormalIndex3); + ProcessVertexComponent(CPArray::Normal, vtx_desc.low.Position, offset, vertex_size, num_vertices, + vertex_data); + offset += norm_size; + + for (u32 i = 0; i < vtx_desc.low.Color.Size(); i++) + { + const u32 color_size = + VertexLoader_Color::GetSize(vtx_desc.low.Color[i], vtx_attr.GetColorFormat(i)); + ProcessVertexComponent(CPArray::Color0 + i, vtx_desc.low.Position, offset, vertex_size, + num_vertices, vertex_data); + offset += color_size; + } + for (u32 i = 0; i < vtx_desc.high.TexCoord.Size(); i++) + { + const u32 tc_size = VertexLoader_TextCoord::GetSize( + vtx_desc.high.TexCoord[i], vtx_attr.GetTexFormat(i), vtx_attr.GetTexElements(i)); + ProcessVertexComponent(CPArray::TexCoord0 + i, vtx_desc.low.Position, offset, vertex_size, + num_vertices, vertex_data); + offset += tc_size; + } + + ASSERT(offset == vertex_size); +} + +// If a component is indexed, the array it indexes into for data must be saved. +void FifoRecorder::FifoRecordAnalyzer::ProcessVertexComponent(CPArray array_index, + VertexComponentFormat array_type, + u32 component_offset, u32 vertex_size, + u16 num_vertices, + const u8* vertex_data) +{ + // Skip if not indexed array + if (!IsIndexed(array_type)) + return; + + u16 max_index = 0; + + // Determine min and max indices + if (array_type == VertexComponentFormat::Index8) + { + for (u16 vertex_num = 0; vertex_num < num_vertices; vertex_num++) + { + const u8 index = vertex_data[component_offset]; + vertex_data += vertex_size; + + // 0xff skips the vertex + if (index != 0xff) + { + if (index > max_index) + max_index = index; + } + } + } + else + { + for (u16 vertex_num = 0; vertex_num < num_vertices; vertex_num++) + { + const u16 index = Common::swap16(&vertex_data[component_offset]); + vertex_data += vertex_size; + + // 0xffff skips the vertex + if (index != 0xffff) + { + if (index > max_index) + max_index = index; + } + } + } + + const u32 array_start = m_cpmem.array_bases[array_index]; + const u32 array_size = m_cpmem.array_strides[array_index] * (max_index + 1); + + m_owner->UseMemory(array_start, array_size, MemoryUpdate::VERTEX_STREAM); +} + static FifoRecorder instance; FifoRecorder::FifoRecorder() = default; @@ -76,7 +231,7 @@ void FifoRecorder::WriteGPCommand(const u8* data, u32 size) { // Assumes data contains all information for the command // Calls FifoRecorder::UseMemory - const u32 analyzed_size = FifoAnalyzer::AnalyzeCommand(data, FifoAnalyzer::DecodeMode::Record); + const u32 analyzed_size = OpcodeDecoder::RunCommand(data, size, *m_record_analyzer); // Make sure FifoPlayer's command analyzer agrees about the size of the command. if (analyzed_size != size) @@ -211,7 +366,7 @@ void FifoRecorder::SetVideoMemory(const u32* bpMem, const u32* cpMem, const u32* memcpy(m_File->GetTexMem(), texMem, FifoDataFile::TEX_MEM_SIZE); } - FifoRecordAnalyzer::Initialize(cpMem); + m_record_analyzer = std::make_unique(this, cpMem); } bool FifoRecorder::IsRecording() const diff --git a/Source/Core/Core/FifoPlayer/FifoRecorder.h b/Source/Core/Core/FifoPlayer/FifoRecorder.h index cbef424561..3a28d05bce 100644 --- a/Source/Core/Core/FifoPlayer/FifoRecorder.h +++ b/Source/Core/Core/FifoPlayer/FifoRecorder.h @@ -8,6 +8,7 @@ #include #include +#include "Common/Assert.h" #include "Core/FifoPlayer/FifoDataFile.h" class FifoRecorder @@ -47,6 +48,8 @@ public: static FifoRecorder& GetInstance(); private: + class FifoRecordAnalyzer; + // Accessed from both GUI and video threads std::recursive_mutex m_mutex; @@ -65,6 +68,7 @@ private: bool m_SkipFutureData = true; bool m_FrameEnded = false; FifoFrameInfo m_CurrentFrame; + std::unique_ptr m_record_analyzer; std::vector m_FifoData; std::vector m_Ram; std::vector m_ExRam; diff --git a/Source/Core/DolphinLib.props b/Source/Core/DolphinLib.props index 5897714436..49d2cfe994 100644 --- a/Source/Core/DolphinLib.props +++ b/Source/Core/DolphinLib.props @@ -217,11 +217,8 @@ - - - @@ -815,11 +812,8 @@ - - - diff --git a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp index 223fdd7145..9135d70e09 100644 --- a/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp +++ b/Source/Core/DolphinQt/FIFO/FIFOAnalyzer.cpp @@ -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(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(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::GX_CMD_UNKNOWN_METRICS) + text = QStringLiteral("GX_CMD_UNKNOWN_METRICS"); + else if (static_cast(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(object[object_offset++]); - switch (opcode) - { - case Opcode::GX_NOP: - if (object[object_offset] == static_cast(Opcode::GX_NOP)) - { - u32 nop_count = 2; - while (object[++object_offset] == static_cast(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(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(*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); } diff --git a/Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp b/Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp index cdcad8eeb2..253017a952 100644 --- a/Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp +++ b/Source/Core/DolphinQt/FIFO/FIFOPlayerWindow.cpp @@ -21,7 +21,6 @@ #include "Core/Core.h" #include "Core/FifoPlayer/FifoDataFile.h" -#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h" #include "Core/FifoPlayer/FifoPlayer.h" #include "Core/FifoPlayer/FifoRecorder.h" diff --git a/Source/Core/VideoCommon/BPMemory.h b/Source/Core/VideoCommon/BPMemory.h index fdd32746a9..97dc5993bd 100644 --- a/Source/Core/VideoCommon/BPMemory.h +++ b/Source/Core/VideoCommon/BPMemory.h @@ -2205,7 +2205,7 @@ struct BPMemory extern BPMemory bpmem; -void LoadBPReg(u32 value0, int cycles_into_future); -void LoadBPRegPreprocess(u32 value0, int cycles_into_future); +void LoadBPReg(u8 reg, u32 value, int cycles_into_future); +void LoadBPRegPreprocess(u8 reg, u32 value, int cycles_into_future); std::pair GetBPRegInfo(u8 cmd, u32 cmddata); diff --git a/Source/Core/VideoCommon/BPStructs.cpp b/Source/Core/VideoCommon/BPStructs.cpp index 0fc4ca6785..503ef6154f 100644 --- a/Source/Core/VideoCommon/BPStructs.cpp +++ b/Source/Core/VideoCommon/BPStructs.cpp @@ -716,29 +716,27 @@ static void BPWritten(const BPCmd& bp, int cycles_into_future) bp.newvalue); } -// Call browser: OpcodeDecoding.cpp ExecuteDisplayList > Decode() > LoadBPReg() -void LoadBPReg(u32 value0, int cycles_into_future) +// Call browser: OpcodeDecoding.cpp RunCallback::OnBP() +void LoadBPReg(u8 reg, u32 value, int cycles_into_future) { - int regNum = value0 >> 24; - int oldval = ((u32*)&bpmem)[regNum]; - int newval = (oldval & ~bpmem.bpMask) | (value0 & bpmem.bpMask); + int oldval = ((u32*)&bpmem)[reg]; + int newval = (oldval & ~bpmem.bpMask) | (value & bpmem.bpMask); int changes = (oldval ^ newval) & 0xFFFFFF; - BPCmd bp = {regNum, changes, newval}; + BPCmd bp = {reg, changes, newval}; // Reset the mask register if we're not trying to set it ourselves. - if (regNum != BPMEM_BP_MASK) + if (reg != BPMEM_BP_MASK) bpmem.bpMask = 0xFFFFFF; BPWritten(bp, cycles_into_future); } -void LoadBPRegPreprocess(u32 value0, int cycles_into_future) +void LoadBPRegPreprocess(u8 reg, u32 value, int cycles_into_future) { - int regNum = value0 >> 24; - // masking could hypothetically be a problem - u32 newval = value0 & 0xffffff; - switch (regNum) + // masking via BPMEM_BP_MASK could hypothetically be a problem + u32 newval = value & 0xffffff; + switch (reg) { case BPMEM_SETDRAWDONE: if ((newval & 0xff) == 0x02) diff --git a/Source/Core/VideoCommon/CPMemory.cpp b/Source/Core/VideoCommon/CPMemory.cpp index afa354b4e4..4781595e8c 100644 --- a/Source/Core/VideoCommon/CPMemory.cpp +++ b/Source/Core/VideoCommon/CPMemory.cpp @@ -2,7 +2,13 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include "VideoCommon/CPMemory.h" + +#include + #include "Common/ChunkFile.h" +#include "Common/Logging/Log.h" +#include "Core/DolphinAnalytics.h" +#include "VideoCommon/CommandProcessor.h" // CP state CPState g_main_cp_state; @@ -28,7 +34,7 @@ void DoCPState(PointerWrap& p) void CopyPreprocessCPStateFromMain() { - memcpy(&g_preprocess_cp_state, &g_main_cp_state, sizeof(CPState)); + std::memcpy(&g_preprocess_cp_state, &g_main_cp_state, sizeof(CPState)); } std::pair GetCPRegInfo(u8 cmd, u32 value) @@ -73,3 +79,164 @@ std::pair GetCPRegInfo(u8 cmd, u32 value) return std::make_pair(fmt::format("Invalid CP register {:02x} = {:08x}", cmd, value), ""); } } + +CPState::CPState(const u32* memory) : CPState() +{ + matrix_index_a.Hex = memory[MATINDEX_A]; + matrix_index_b.Hex = memory[MATINDEX_B]; + vtx_desc.low.Hex = memory[VCD_LO]; + vtx_desc.high.Hex = memory[VCD_HI]; + + for (u32 i = 0; i < CP_NUM_VAT_REG; i++) + { + vtx_attr[i].g0.Hex = memory[CP_VAT_REG_A + i]; + vtx_attr[i].g1.Hex = memory[CP_VAT_REG_B + i]; + vtx_attr[i].g2.Hex = memory[CP_VAT_REG_C + i]; + } + + for (u32 i = 0; i < CP_NUM_ARRAYS; i++) + { + array_bases[static_cast(i)] = memory[ARRAY_BASE + i]; + array_strides[static_cast(i)] = memory[ARRAY_STRIDE + i]; + } +} + +void CPState::LoadCPReg(u8 sub_cmd, u32 value) +{ + switch (sub_cmd & CP_COMMAND_MASK) + { + case UNKNOWN_00: + case UNKNOWN_10: + case UNKNOWN_20: + if (!(sub_cmd == UNKNOWN_20 && value == 0)) + { + // All titles using libogc or the official SDK issue 0x20 with value=0 on startup + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_CP_PERF_COMMAND); + DEBUG_LOG_FMT(VIDEO, "Unknown CP command possibly relating to perf queries used: {:02x}", + sub_cmd); + } + break; + + case MATINDEX_A: + if (sub_cmd != MATINDEX_A) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, + "CP MATINDEX_A: an exact value of {:02x} was expected " + "but instead a value of {:02x} was seen", + MATINDEX_A, sub_cmd); + } + + matrix_index_a.Hex = value; + break; + + case MATINDEX_B: + if (sub_cmd != MATINDEX_B) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, + "CP MATINDEX_B: an exact value of {:02x} was expected " + "but instead a value of {:02x} was seen", + MATINDEX_B, sub_cmd); + } + + matrix_index_b.Hex = value; + break; + + case VCD_LO: + if (sub_cmd != VCD_LO) // Stricter than YAGCD + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, + "CP VCD_LO: an exact value of {:02x} was expected " + "but instead a value of {:02x} was seen", + VCD_LO, sub_cmd); + } + + vtx_desc.low.Hex = value; + attr_dirty = BitSet32::AllTrue(CP_NUM_VAT_REG); + bases_dirty = true; + break; + + case VCD_HI: + if (sub_cmd != VCD_HI) // Stricter than YAGCD + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, + "CP VCD_HI: an exact value of {:02x} was expected " + "but instead a value of {:02x} was seen", + VCD_HI, sub_cmd); + } + + vtx_desc.high.Hex = value; + attr_dirty = BitSet32::AllTrue(CP_NUM_VAT_REG); + bases_dirty = true; + break; + + case CP_VAT_REG_A: + if ((sub_cmd - CP_VAT_REG_A) >= CP_NUM_VAT_REG) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, "CP_VAT_REG_A: Invalid VAT {}", sub_cmd - CP_VAT_REG_A); + } + vtx_attr[sub_cmd & CP_VAT_MASK].g0.Hex = value; + attr_dirty[sub_cmd & CP_VAT_MASK] = true; + break; + + case CP_VAT_REG_B: + if ((sub_cmd - CP_VAT_REG_B) >= CP_NUM_VAT_REG) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, "CP_VAT_REG_B: Invalid VAT {}", sub_cmd - CP_VAT_REG_B); + } + vtx_attr[sub_cmd & CP_VAT_MASK].g1.Hex = value; + attr_dirty[sub_cmd & CP_VAT_MASK] = true; + break; + + case CP_VAT_REG_C: + if ((sub_cmd - CP_VAT_REG_C) >= CP_NUM_VAT_REG) + { + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); + WARN_LOG_FMT(VIDEO, "CP_VAT_REG_C: Invalid VAT {}", sub_cmd - CP_VAT_REG_C); + } + vtx_attr[sub_cmd & CP_VAT_MASK].g2.Hex = value; + attr_dirty[sub_cmd & CP_VAT_MASK] = true; + break; + + // Pointers to vertex arrays in GC RAM + case ARRAY_BASE: + array_bases[static_cast(sub_cmd & CP_ARRAY_MASK)] = + value & CommandProcessor::GetPhysicalAddressMask(); + bases_dirty = true; + break; + + case ARRAY_STRIDE: + array_strides[static_cast(sub_cmd & CP_ARRAY_MASK)] = value & 0xFF; + break; + + default: + DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_UNKNOWN_CP_COMMAND); + WARN_LOG_FMT(VIDEO, "Unknown CP register {:02x} set to {:08x}", sub_cmd, value); + } +} + +void CPState::FillCPMemoryArray(u32* memory) const +{ + memory[MATINDEX_A] = matrix_index_a.Hex; + memory[MATINDEX_B] = matrix_index_b.Hex; + memory[VCD_LO] = vtx_desc.low.Hex; + memory[VCD_HI] = vtx_desc.high.Hex; + + for (int i = 0; i < CP_NUM_VAT_REG; ++i) + { + memory[CP_VAT_REG_A + i] = vtx_attr[i].g0.Hex; + memory[CP_VAT_REG_B + i] = vtx_attr[i].g1.Hex; + memory[CP_VAT_REG_C + i] = vtx_attr[i].g2.Hex; + } + + for (int i = 0; i < CP_NUM_ARRAYS; ++i) + { + memory[ARRAY_BASE + i] = array_bases[static_cast(i)]; + memory[ARRAY_STRIDE + i] = array_strides[static_cast(i)]; + } +} diff --git a/Source/Core/VideoCommon/CPMemory.h b/Source/Core/VideoCommon/CPMemory.h index 1a937d15c9..defac506b2 100644 --- a/Source/Core/VideoCommon/CPMemory.h +++ b/Source/Core/VideoCommon/CPMemory.h @@ -5,6 +5,7 @@ #include #include +#include #include #include "Common/BitField.h" @@ -630,13 +631,21 @@ class VertexLoaderBase; // STATE_TO_SAVE struct CPState final { + CPState() = default; + explicit CPState(const u32* memory); + + // Mutates the CP state based on the given command and value. + void LoadCPReg(u8 sub_cmd, u32 value); + // Fills memory with data from CP regs. There should be space for 0x100 values in memory. + void FillCPMemoryArray(u32* memory) const; + Common::EnumMap array_bases; Common::EnumMap array_strides; TMatrixIndexA matrix_index_a{}; TMatrixIndexB matrix_index_b{}; TVtxDesc vtx_desc; // Most games only use the first VtxAttr and simply reconfigure it all the time as needed. - VAT vtx_attr[CP_NUM_VAT_REG]{}; + std::array vtx_attr{}; // Attributes that actually belong to VertexLoaderManager: BitSet32 attr_dirty{}; @@ -644,18 +653,13 @@ struct CPState final VertexLoaderBase* vertex_loaders[CP_NUM_VAT_REG]{}; int last_id = 0; }; +static_assert(std::is_trivially_copyable_v); class PointerWrap; extern CPState g_main_cp_state; extern CPState g_preprocess_cp_state; -// Might move this into its own file later. -void LoadCPReg(u32 SubCmd, u32 Value, bool is_preprocess = false); - -// Fills memory with data from CP regs -void FillCPMemoryArray(u32* memory); - void DoCPState(PointerWrap& p); void CopyPreprocessCPStateFromMain(); diff --git a/Source/Core/VideoCommon/CommandProcessor.cpp b/Source/Core/VideoCommon/CommandProcessor.cpp index 83784a137f..ddaa0e72a3 100644 --- a/Source/Core/VideoCommon/CommandProcessor.cpp +++ b/Source/Core/VideoCommon/CommandProcessor.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "Common/Assert.h" #include "Common/ChunkFile.h" @@ -607,10 +608,10 @@ void SetCpClearRegister() { } -void HandleUnknownOpcode(u8 cmd_byte, void* buffer, bool preprocess) +void HandleUnknownOpcode(u8 cmd_byte, const u8* buffer, bool preprocess) { // TODO(Omega): Maybe dump FIFO to file on this error - PanicAlertFmtT("GFX FIFO: Unknown Opcode ({0:#04x} @ {1}, {2}).\n" + PanicAlertFmtT("GFX FIFO: Unknown Opcode ({0:#04x} @ {1}, preprocess={2}).\n" "This means one of the following:\n" "* The emulated GPU got desynced, disabling dual core can help\n" "* Command stream corrupted by some spurious memory bug\n" @@ -618,7 +619,7 @@ void HandleUnknownOpcode(u8 cmd_byte, void* buffer, bool preprocess) "* Some other sort of bug\n\n" "Further errors will be sent to the Video Backend log and\n" "Dolphin will now likely crash or hang. Enjoy.", - cmd_byte, buffer, preprocess ? "preprocess=true" : "preprocess=false"); + cmd_byte, fmt::ptr(buffer), preprocess); { PanicAlertFmt("Illegal command {:02x}\n" diff --git a/Source/Core/VideoCommon/CommandProcessor.h b/Source/Core/VideoCommon/CommandProcessor.h index 4ca73c71d2..2da7f1c84d 100644 --- a/Source/Core/VideoCommon/CommandProcessor.h +++ b/Source/Core/VideoCommon/CommandProcessor.h @@ -169,7 +169,7 @@ void SetCpClearRegister(); void SetCpControlRegister(); void SetCpStatusRegister(); -void HandleUnknownOpcode(u8 cmd_byte, void* buffer, bool preprocess); +void HandleUnknownOpcode(u8 cmd_byte, const u8* buffer, bool preprocess); u32 GetPhysicalAddressMask(); diff --git a/Source/Core/VideoCommon/Fifo.cpp b/Source/Core/VideoCommon/Fifo.cpp index 04fc00d33a..f96c71b550 100644 --- a/Source/Core/VideoCommon/Fifo.cpp +++ b/Source/Core/VideoCommon/Fifo.cpp @@ -273,8 +273,8 @@ static void ReadDataFromFifoOnCPU(u32 readPtr) } } Memory::CopyFromEmu(s_video_buffer_write_ptr, readPtr, len); - s_video_buffer_pp_read_ptr = OpcodeDecoder::Run( - DataReader(s_video_buffer_pp_read_ptr, write_ptr + len), nullptr, false); + s_video_buffer_pp_read_ptr = OpcodeDecoder::RunFifo( + DataReader(s_video_buffer_pp_read_ptr, write_ptr + len), nullptr); // This would have to be locked if the GPU thread didn't spin. s_video_buffer_write_ptr = write_ptr + len; } @@ -316,7 +316,7 @@ void RunGpuLoop() if (write_ptr > seen_ptr) { s_video_buffer_read_ptr = - OpcodeDecoder::Run(DataReader(s_video_buffer_read_ptr, write_ptr), nullptr, false); + OpcodeDecoder::RunFifo(DataReader(s_video_buffer_read_ptr, write_ptr), nullptr); s_video_buffer_seen_ptr = write_ptr; } } @@ -349,8 +349,8 @@ void RunGpuLoop() fifo.CPReadWriteDistance.load(std::memory_order_relaxed) - 32); u8* write_ptr = s_video_buffer_write_ptr; - s_video_buffer_read_ptr = OpcodeDecoder::Run( - DataReader(s_video_buffer_read_ptr, write_ptr), &cyclesExecuted, false); + s_video_buffer_read_ptr = OpcodeDecoder::RunFifo( + DataReader(s_video_buffer_read_ptr, write_ptr), &cyclesExecuted); fifo.CPReadPointer.store(readPtr, std::memory_order_relaxed); fifo.CPReadWriteDistance.fetch_sub(32, std::memory_order_seq_cst); @@ -466,8 +466,8 @@ static int RunGpuOnCpu(int ticks) } ReadDataFromFifo(fifo.CPReadPointer.load(std::memory_order_relaxed)); u32 cycles = 0; - s_video_buffer_read_ptr = OpcodeDecoder::Run( - DataReader(s_video_buffer_read_ptr, s_video_buffer_write_ptr), &cycles, false); + s_video_buffer_read_ptr = OpcodeDecoder::RunFifo( + DataReader(s_video_buffer_read_ptr, s_video_buffer_write_ptr), &cycles); available_ticks -= cycles; } diff --git a/Source/Core/VideoCommon/OpcodeDecoding.cpp b/Source/Core/VideoCommon/OpcodeDecoding.cpp index be879ddfbc..239aec158a 100644 --- a/Source/Core/VideoCommon/OpcodeDecoding.cpp +++ b/Source/Core/VideoCommon/OpcodeDecoding.cpp @@ -14,7 +14,7 @@ #include "VideoCommon/OpcodeDecoding.h" -#include "Common/CommonTypes.h" +#include "Common/Assert.h" #include "Common/Logging/Log.h" #include "Core/FifoPlayer/FifoRecorder.h" #include "Core/HW/Memmap.h" @@ -24,55 +24,15 @@ #include "VideoCommon/DataReader.h" #include "VideoCommon/Fifo.h" #include "VideoCommon/Statistics.h" +#include "VideoCommon/VertexLoaderBase.h" #include "VideoCommon/VertexLoaderManager.h" +#include "VideoCommon/VertexShaderManager.h" #include "VideoCommon/XFMemory.h" +#include "VideoCommon/XFStructs.h" namespace OpcodeDecoder { -namespace -{ bool s_is_fifo_error_seen = false; - -u32 InterpretDisplayList(u32 address, u32 size) -{ - u8* start_address; - - if (Fifo::UseDeterministicGPUThread()) - start_address = static_cast(Fifo::PopFifoAuxBuffer(size)); - else - start_address = Memory::GetPointer(address); - - u32 cycles = 0; - - // Avoid the crash if Memory::GetPointer failed .. - if (start_address != nullptr) - { - // temporarily swap dl and non-dl (small "hack" for the stats) - g_stats.SwapDL(); - - Run(DataReader(start_address, start_address + size), &cycles, true); - INCSTAT(g_stats.this_frame.num_dlists_called); - - // un-swap - g_stats.SwapDL(); - } - - return cycles; -} - -void InterpretDisplayListPreprocess(u32 address, u32 size) -{ - u8* const start_address = Memory::GetPointer(address); - - Fifo::PushFifoAuxBuffer(start_address, size); - - if (start_address == nullptr) - return; - - Run(DataReader(start_address, start_address + size), nullptr, true); -} -} // Anonymous namespace - bool g_record_fifo_data = false; void Init() @@ -81,203 +41,205 @@ void Init() } template -u8* Run(DataReader src, u32* cycles, bool in_display_list) +class RunCallback final : public Callback { - u32 total_cycles = 0; - u8* opcode_start = nullptr; - - const auto finish_up = [cycles, &opcode_start, &total_cycles] { - if (cycles != nullptr) - { - *cycles = total_cycles; - } - return opcode_start; - }; - - while (true) +public: + OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data)) { - opcode_start = src.GetPointer(); + m_cycles += 18 + 6 * count; - if (!src.size()) - return finish_up(); - - const u8 cmd_byte = src.Read(); - switch (static_cast(cmd_byte)) - { - case Opcode::GX_NOP: - total_cycles += 6; // Hm, this means that we scan over nop streams pretty slowly... - break; - - case Opcode::GX_UNKNOWN_RESET: - total_cycles += 6; // Datel software uses this command - DEBUG_LOG_FMT(VIDEO, "GX Reset?: {:08x}", cmd_byte); - break; - - case Opcode::GX_LOAD_CP_REG: - { - if (src.size() < 1 + 4) - return finish_up(); - - total_cycles += 12; - - const u8 sub_cmd = src.Read(); - const u32 value = src.Read(); - LoadCPReg(sub_cmd, value, is_preprocess); - if constexpr (!is_preprocess) - INCSTAT(g_stats.this_frame.num_cp_loads); - } - break; - - case Opcode::GX_LOAD_XF_REG: - { - if (src.size() < 4) - return finish_up(); - - const u32 cmd2 = src.Read(); - const u32 transfer_size = ((cmd2 >> 16) & 15) + 1; - if (src.size() < transfer_size * sizeof(u32)) - return finish_up(); - - total_cycles += 18 + 6 * transfer_size; - - if constexpr (!is_preprocess) - { - const u32 xf_address = cmd2 & 0xFFFF; - LoadXFReg(transfer_size, xf_address, src); - - INCSTAT(g_stats.this_frame.num_xf_loads); - } - src.Skip(transfer_size); - } - break; - - case Opcode::GX_LOAD_INDX_A: // Used for position matrices - case Opcode::GX_LOAD_INDX_B: // Used for normal matrices - case Opcode::GX_LOAD_INDX_C: // Used for postmatrices - case Opcode::GX_LOAD_INDX_D: // Used for lights - { - if (src.size() < 4) - return finish_up(); - - total_cycles += 6; - - // Map the command byte to its ref array. - // GX_LOAD_INDX_A (32) -> 0xC - // GX_LOAD_INDX_B (40) -> 0xD - // GX_LOAD_INDX_C (48) -> 0xE - // GX_LOAD_INDX_D (56) -> 0xF - const auto array = static_cast((cmd_byte / 8) + 8); - - if constexpr (is_preprocess) - PreprocessIndexedXF(array, src.Read()); - else - LoadIndexedXF(array, src.Read()); - } - break; - - case Opcode::GX_CMD_CALL_DL: - { - if (src.size() < 8) - return finish_up(); - - const u32 address = src.Read(); - const u32 count = src.Read(); - - if (in_display_list) - { - total_cycles += 6; - INFO_LOG_FMT(VIDEO, "recursive display list detected"); - } - else - { - if constexpr (is_preprocess) - InterpretDisplayListPreprocess(address, count); - else - total_cycles += 6 + InterpretDisplayList(address, count); - } - } - break; - - case Opcode::GX_CMD_UNKNOWN_METRICS: // zelda 4 swords calls it and checks the metrics - // registers after that - total_cycles += 6; - DEBUG_LOG_FMT(VIDEO, "GX 0x44: {:08x}", cmd_byte); - break; - - case Opcode::GX_CMD_INVL_VC: // Invalidate Vertex Cache - total_cycles += 6; - DEBUG_LOG_FMT(VIDEO, "Invalidate (vertex cache?)"); - break; - - case Opcode::GX_LOAD_BP_REG: - // In skipped_frame case: We have to let BP writes through because they set - // tokens and stuff. TODO: Call a much simplified LoadBPReg instead. - { - if (src.size() < 4) - return finish_up(); - - total_cycles += 12; - - const u32 bp_cmd = src.Read(); - if constexpr (is_preprocess) - { - LoadBPRegPreprocess(bp_cmd, total_cycles); - } - else - { - LoadBPReg(bp_cmd, total_cycles); - INCSTAT(g_stats.this_frame.num_bp_loads); - } - } - break; - - // draw primitives - default: - if ((cmd_byte & 0xC0) == 0x80) - { - // load vertices - if (src.size() < 2) - return finish_up(); - - const u16 num_vertices = src.Read(); - const int bytes = VertexLoaderManager::RunVertices( - cmd_byte & GX_VAT_MASK, // Vertex loader index (0 - 7) - static_cast((cmd_byte & GX_PRIMITIVE_MASK) >> GX_PRIMITIVE_SHIFT), - num_vertices, src, is_preprocess); - - if (bytes < 0) - return finish_up(); - - src.Skip(bytes); - - // 4 GPU ticks per vertex, 3 CPU ticks per GPU tick - total_cycles += num_vertices * 4 * 3 + 6; - } - else - { - if (!s_is_fifo_error_seen) - CommandProcessor::HandleUnknownOpcode(cmd_byte, opcode_start, is_preprocess); - ERROR_LOG_FMT(VIDEO, "FIFO: Unknown Opcode({:#04x} @ {}, preprocessing = {})", cmd_byte, - fmt::ptr(opcode_start), is_preprocess ? "yes" : "no"); - s_is_fifo_error_seen = true; - total_cycles += 1; - } - break; - } - - // Display lists get added directly into the FIFO stream if constexpr (!is_preprocess) { - if (g_record_fifo_data && static_cast(cmd_byte) != Opcode::GX_CMD_CALL_DL) + // HACK + LoadXFReg(count, address, + DataReader{const_cast(data), const_cast(data) + count * sizeof(u32)}); + + INCSTAT(g_stats.this_frame.num_xf_loads); + } + } + OPCODE_CALLBACK(void OnCP(u8 command, u32 value)) + { + m_cycles += 12; + if constexpr (!is_preprocess) + { + // TODO: Move all dirty state checking here or to VertexLoaderManager, + // instead of it being in CPState + if (command == MATINDEX_A) + VertexShaderManager::SetTexMatrixChangedA(value); + else if (command == MATINDEX_B) + VertexShaderManager::SetTexMatrixChangedB(value); + + INCSTAT(g_stats.this_frame.num_cp_loads); + } + GetCPState().LoadCPReg(command, value); + } + OPCODE_CALLBACK(void OnBP(u8 command, u32 value)) + { + m_cycles += 12; + + if constexpr (is_preprocess) + { + LoadBPRegPreprocess(command, value, m_cycles); + } + else + { + LoadBPReg(command, value, m_cycles); + INCSTAT(g_stats.this_frame.num_bp_loads); + } + } + OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size)) + { + m_cycles += 6; + + if constexpr (is_preprocess) + PreprocessIndexedXF(array, index, address, size); + else + LoadIndexedXF(array, index, address, size); + } + OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, + u32 vertex_size, u16 num_vertices, const u8* vertex_data)) + { + // load vertices + const u32 size = vertex_size * num_vertices; + + // HACK + DataReader src{const_cast(vertex_data), const_cast(vertex_data) + size}; + const u32 bytes = + VertexLoaderManager::RunVertices(vat, primitive, num_vertices, src, is_preprocess); + + ASSERT(bytes == size); + + // 4 GPU ticks per vertex, 3 CPU ticks per GPU tick + m_cycles += num_vertices * 4 * 3 + 6; + } + // This can't be inlined since it calls Run, which makes it recursive + // m_in_display_list prevents it from actually recursing infinitely, but there's no real benefit + // to inlining Run for the display list directly. + OPCODE_CALLBACK_NOINLINE(void OnDisplayList(u32 address, u32 size)) + { + m_cycles += 6; + + if (m_in_display_list) + { + WARN_LOG_FMT(VIDEO, "recursive display list detected"); + } + else + { + m_in_display_list = true; + + if constexpr (is_preprocess) { - const u8* const opcode_end = src.GetPointer(); - FifoRecorder::GetInstance().WriteGPCommand(opcode_start, u32(opcode_end - opcode_start)); + const u8* const start_address = Memory::GetPointer(address); + + Fifo::PushFifoAuxBuffer(start_address, size); + + if (start_address != nullptr) + { + Run(start_address, size, *this); + } + } + else + { + const u8* start_address; + + if (Fifo::UseDeterministicGPUThread()) + start_address = static_cast(Fifo::PopFifoAuxBuffer(size)); + else + start_address = Memory::GetPointer(address); + + // Avoid the crash if Memory::GetPointer failed .. + if (start_address != nullptr) + { + // temporarily swap dl and non-dl (small "hack" for the stats) + g_stats.SwapDL(); + + Run(start_address, size, *this); + INCSTAT(g_stats.this_frame.num_dlists_called); + + // un-swap + g_stats.SwapDL(); + } + } + + m_in_display_list = false; + } + } + OPCODE_CALLBACK(void OnNop(u32 count)) + { + m_cycles += 6 * count; // Hm, this means that we scan over nop streams pretty slowly... + } + OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data)) + { + if (static_cast(opcode) == Opcode::GX_UNKNOWN_RESET) + { + // Datel software uses this command + m_cycles += 6; + DEBUG_LOG_FMT(VIDEO, "GX Reset?"); + } + else if (static_cast(opcode) == Opcode::GX_CMD_UNKNOWN_METRICS) + { + // 'Zelda Four Swords' calls it and checks the metrics registers after that + m_cycles += 6; + DEBUG_LOG_FMT(VIDEO, "GX 0x44"); + } + else if (static_cast(opcode) == Opcode::GX_CMD_INVL_VC) + { + // Invalidate Vertex Cache + m_cycles += 6; + DEBUG_LOG_FMT(VIDEO, "Invalidate (vertex cache?)"); + } + else + { + if (!s_is_fifo_error_seen) + CommandProcessor::HandleUnknownOpcode(opcode, data, is_preprocess); + ERROR_LOG_FMT(VIDEO, "FIFO: Unknown Opcode({:#04x} @ {}, preprocessing = {})", opcode, + fmt::ptr(data), is_preprocess ? "yes" : "no"); + s_is_fifo_error_seen = true; + m_cycles += 1; + } + } + + OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)) + { + ASSERT(size >= 1); + if constexpr (!is_preprocess) + { + // Display lists get added directly into the FIFO stream since this same callback is used to + // process them. + if (g_record_fifo_data && static_cast(data[0]) != Opcode::GX_CMD_CALL_DL) + { + FifoRecorder::GetInstance().WriteGPCommand(data, size); } } } + + OPCODE_CALLBACK(CPState& GetCPState()) + { + if constexpr (is_preprocess) + return g_preprocess_cp_state; + else + return g_main_cp_state; + } + + u32 m_cycles = 0; + bool m_in_display_list = false; +}; + +template +u8* RunFifo(DataReader src, u32* cycles) +{ + using CallbackT = RunCallback; + auto callback = CallbackT{}; + u32 size = Run(src.GetPointer(), static_cast(src.size()), callback); + + if (cycles != nullptr) + *cycles = callback.m_cycles; + + src.Skip(size); + return src.GetPointer(); } -template u8* Run(DataReader src, u32* cycles, bool in_display_list); -template u8* Run(DataReader src, u32* cycles, bool in_display_list); +template u8* RunFifo(DataReader src, u32* cycles); +template u8* RunFifo(DataReader src, u32* cycles); } // namespace OpcodeDecoder diff --git a/Source/Core/VideoCommon/OpcodeDecoding.h b/Source/Core/VideoCommon/OpcodeDecoding.h index df1059f221..2d1632efc3 100644 --- a/Source/Core/VideoCommon/OpcodeDecoding.h +++ b/Source/Core/VideoCommon/OpcodeDecoding.h @@ -3,9 +3,17 @@ #pragma once +#include + +#include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/EnumFormatter.h" +#include "Common/Inline.h" +#include "Common/Swap.h" +#include "VideoCommon/CPMemory.h" +#include "VideoCommon/VertexLoaderBase.h" +struct CPState; class DataReader; namespace OpcodeDecoder @@ -55,8 +63,220 @@ enum class Primitive : u8 void Init(); +// Interface for the Run and RunCommand functions below. +// The functions themselves are templates so that the compiler generates separate versions for each +// callback (with the callback functions inlined), so the callback doesn't actually need to be +// publicly inherited. +// Compilers don't generate warnings for failed inlining with virtual functions, so this define +// allows disabling the use of virtual functions to generate those warnings. However, this means +// that missing functions will generate errors on their use in RunCommand, instead of in the +// subclass, which can be confusing. +#define OPCODE_CALLBACK_USE_INHERITANCE + +#ifdef OPCODE_CALLBACK_USE_INHERITANCE +#define OPCODE_CALLBACK(sig) DOLPHIN_FORCE_INLINE sig override +#define OPCODE_CALLBACK_NOINLINE(sig) sig override +#else +#define OPCODE_CALLBACK(sig) DOLPHIN_FORCE_INLINE sig +#define OPCODE_CALLBACK_NOINLINE(sig) sig +#endif +class Callback +{ +#ifdef OPCODE_CALLBACK_USE_INHERITANCE +public: + virtual ~Callback() = default; + + // Called on any XF command. + virtual void OnXF(u16 address, u8 count, const u8* data) = 0; + // Called on any CP command. + // Subclasses should update the CP state with GetCPState().LoadCPReg(command, value) so that + // primitive commands decode properly. + virtual void OnCP(u8 command, u32 value) = 0; + // Called on any BP command. + virtual void OnBP(u8 command, u32 value) = 0; + // Called on any indexed XF load command. + virtual void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size) = 0; + // Called on any primitive command. + virtual void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat, u32 vertex_size, + u16 num_vertices, const u8* vertex_data) = 0; + // Called on a display list. + virtual void OnDisplayList(u32 address, u32 size) = 0; + // Called on any NOP commands (which are all merged into a single call). + virtual void OnNop(u32 count) = 0; + // Called on an unknown opcode, or an opcode that is known but not implemented. + // data[0] is opcode. + virtual void OnUnknown(u8 opcode, const u8* data) = 0; + + // Called on ANY command. The first byte of data is the opcode. Size will be at least 1. + // This function is called after one of the above functions is called. + virtual void OnCommand(const u8* data, u32 size) = 0; + + // Get the current CP state. Needed for vertex decoding; will also be mutated for CP commands. + virtual CPState& GetCPState() = 0; +#endif +}; + +namespace detail +{ +// Main logic; split so that the main RunCommand can call OnCommand with the returned size. +template >> +static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback) +{ + if (available < 1) + return 0; + + const Opcode cmd = static_cast(data[0]); + + switch (cmd) + { + case Opcode::GX_NOP: + { + u32 count = 1; + while (count < available && static_cast(data[count]) == Opcode::GX_NOP) + count++; + callback.OnNop(count); + return count; + } + + case Opcode::GX_LOAD_CP_REG: + { + if (available < 6) + return 0; + + const u8 cmd2 = data[1]; + const u32 value = Common::swap32(&data[2]); + + callback.OnCP(cmd2, value); + + return 6; + } + + case Opcode::GX_LOAD_XF_REG: + { + if (available < 5) + return 0; + + const u32 cmd2 = Common::swap32(&data[1]); + const u16 base_address = cmd2 & 0xffff; + + const u16 stream_size_temp = cmd2 >> 16; + ASSERT(stream_size_temp < 16); + const u8 stream_size = (stream_size_temp & 0xf) + 1; + + if (available < u32(5 + stream_size * 4)) + return 0; + + callback.OnXF(base_address, stream_size, &data[5]); + + return 5 + stream_size * 4; + } + + case Opcode::GX_LOAD_INDX_A: // Used for position matrices + case Opcode::GX_LOAD_INDX_B: // Used for normal matrices + case Opcode::GX_LOAD_INDX_C: // Used for postmatrices + case Opcode::GX_LOAD_INDX_D: // Used for lights + { + if (available < 5) + return 0; + + const u32 value = Common::swap32(&data[1]); + + const u32 index = value >> 16; + const u16 address = value & 0xFFF; // TODO: check mask + const u8 size = ((value >> 12) & 0xF) + 1; + + // Map the command byte to its ref array. + // GX_LOAD_INDX_A (32 = 8*4) . CPArray::XF_A (4+8 = 12) + // GX_LOAD_INDX_B (40 = 8*5) . CPArray::XF_B (5+8 = 13) + // GX_LOAD_INDX_C (48 = 8*6) . CPArray::XF_C (6+8 = 14) + // GX_LOAD_INDX_D (56 = 8*7) . CPArray::XF_D (7+8 = 15) + const auto ref_array = static_cast((static_cast(cmd) / 8) + 8); + + callback.OnIndexedLoad(ref_array, index, address, size); + return 5; + } + + case Opcode::GX_CMD_CALL_DL: + { + if (available < 9) + return 0; + + const u32 address = Common::swap32(&data[1]); + const u32 size = Common::swap32(&data[5]); + + callback.OnDisplayList(address, size); + return 9; + } + + case Opcode::GX_LOAD_BP_REG: + { + if (available < 5) + return 0; + + const u8 cmd2 = data[1]; + const u32 value = Common::swap24(&data[2]); + + callback.OnBP(cmd2, value); + + return 5; + } + + default: + if (cmd >= Opcode::GX_PRIMITIVE_START && cmd <= Opcode::GX_PRIMITIVE_END) + { + if (available < 3) + return 0; + + const u8 cmdbyte = static_cast(cmd); + const OpcodeDecoder::Primitive primitive = static_cast( + (cmdbyte & OpcodeDecoder::GX_PRIMITIVE_MASK) >> OpcodeDecoder::GX_PRIMITIVE_SHIFT); + const u8 vat = cmdbyte & OpcodeDecoder::GX_VAT_MASK; + + const u32 vertex_size = VertexLoaderBase::GetVertexSize(callback.GetCPState().vtx_desc, + callback.GetCPState().vtx_attr[vat]); + const u16 num_vertices = Common::swap16(&data[1]); + + if (available < 3 + num_vertices * vertex_size) + return 0; + + callback.OnPrimitiveCommand(primitive, vat, vertex_size, num_vertices, &data[3]); + + return 3 + num_vertices * vertex_size; + } + } + + callback.OnUnknown(static_cast(cmd), data); + return 1; +} +} // namespace detail + +template >> +DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback) +{ + const u32 size = detail::RunCommand(data, available, callback); + if (size > 0) + { + callback.OnCommand(data, size); + } + return size; +} + +template >> +DOLPHIN_FORCE_INLINE u32 Run(const u8* data, u32 available, T& callback) +{ + u32 size = 0; + while (size < available) + { + const u32 command_size = RunCommand(&data[size], available - size, callback); + if (command_size == 0) + break; + size += command_size; + } + return size; +} + template -u8* Run(DataReader src, u32* cycles, bool in_display_list); +u8* RunFifo(DataReader src, u32* cycles); } // namespace OpcodeDecoder diff --git a/Source/Core/VideoCommon/RenderBase.cpp b/Source/Core/VideoCommon/RenderBase.cpp index e606a464e6..16d36f7453 100644 --- a/Source/Core/VideoCommon/RenderBase.cpp +++ b/Source/Core/VideoCommon/RenderBase.cpp @@ -963,7 +963,7 @@ void Renderer::RecordVideoMemory() const u32* xfregs_ptr = reinterpret_cast(&xfmem) + FifoDataFile::XF_MEM_SIZE; u32 xfregs_size = sizeof(XFMemory) / 4 - FifoDataFile::XF_MEM_SIZE; - FillCPMemoryArray(cpmem); + g_main_cp_state.FillCPMemoryArray(cpmem); FifoRecorder::GetInstance().SetVideoMemory(bpmem_ptr, cpmem, xfmem_ptr, xfregs_ptr, xfregs_size, texMem); diff --git a/Source/Core/VideoCommon/VertexLoaderARM64.cpp b/Source/Core/VideoCommon/VertexLoaderARM64.cpp index 6663e6c8ae..75afefed16 100644 --- a/Source/Core/VideoCommon/VertexLoaderARM64.cpp +++ b/Source/Core/VideoCommon/VertexLoaderARM64.cpp @@ -405,7 +405,7 @@ void VertexLoaderARM64::GenerateVertexLoader() MOV(skipped_reg, ARM64Reg::WZR); MOV(saved_count, count_reg); - MOVP2R(stride_reg, g_main_cp_state.array_strides); + MOVP2R(stride_reg, g_main_cp_state.array_strides.data()); MOVP2R(arraybase_reg, VertexLoaderManager::cached_arraybases); if (need_scale) diff --git a/Source/Core/VideoCommon/VertexLoaderManager.cpp b/Source/Core/VideoCommon/VertexLoaderManager.cpp index e2475d666f..901e14044f 100644 --- a/Source/Core/VideoCommon/VertexLoaderManager.cpp +++ b/Source/Core/VideoCommon/VertexLoaderManager.cpp @@ -12,17 +12,14 @@ #include #include -#include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/EnumMap.h" #include "Common/Logging/Log.h" -#include "Core/DolphinAnalytics.h" #include "Core/HW/Memmap.h" #include "VideoCommon/BPMemory.h" #include "VideoCommon/CPMemory.h" -#include "VideoCommon/CommandProcessor.h" #include "VideoCommon/DataReader.h" #include "VideoCommon/IndexGenerator.h" #include "VideoCommon/NativeVertexFormat.h" @@ -298,147 +295,3 @@ NativeVertexFormat* GetCurrentVertexFormat() } } // namespace VertexLoaderManager - -void LoadCPReg(u32 sub_cmd, u32 value, bool is_preprocess) -{ - bool update_global_state = !is_preprocess; - CPState* state = is_preprocess ? &g_preprocess_cp_state : &g_main_cp_state; - switch (sub_cmd & CP_COMMAND_MASK) - { - case UNKNOWN_00: - case UNKNOWN_10: - case UNKNOWN_20: - if (!(sub_cmd == UNKNOWN_20 && value == 0)) - { - // All titles using libogc or the official SDK issue 0x20 with value=0 on startup - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_CP_PERF_COMMAND); - DEBUG_LOG_FMT(VIDEO, "Unknown CP command possibly relating to perf queries used: {:02x}", - sub_cmd); - } - break; - - case MATINDEX_A: - if (sub_cmd != MATINDEX_A) - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, - "CP MATINDEX_A: an exact value of {:02x} was expected " - "but instead a value of {:02x} was seen", - MATINDEX_A, sub_cmd); - } - - if (update_global_state) - VertexShaderManager::SetTexMatrixChangedA(value); - break; - - case MATINDEX_B: - if (sub_cmd != MATINDEX_B) - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, - "CP MATINDEX_B: an exact value of {:02x} was expected " - "but instead a value of {:02x} was seen", - MATINDEX_B, sub_cmd); - } - - if (update_global_state) - VertexShaderManager::SetTexMatrixChangedB(value); - break; - - case VCD_LO: - if (sub_cmd != VCD_LO) // Stricter than YAGCD - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, - "CP VCD_LO: an exact value of {:02x} was expected " - "but instead a value of {:02x} was seen", - VCD_LO, sub_cmd); - } - - state->vtx_desc.low.Hex = value; - state->attr_dirty = BitSet32::AllTrue(CP_NUM_VAT_REG); - state->bases_dirty = true; - break; - - case VCD_HI: - if (sub_cmd != VCD_HI) // Stricter than YAGCD - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, - "CP VCD_HI: an exact value of {:02x} was expected " - "but instead a value of {:02x} was seen", - VCD_HI, sub_cmd); - } - - state->vtx_desc.high.Hex = value; - state->attr_dirty = BitSet32::AllTrue(CP_NUM_VAT_REG); - state->bases_dirty = true; - break; - - case CP_VAT_REG_A: - if ((sub_cmd - CP_VAT_REG_A) >= CP_NUM_VAT_REG) - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, "CP_VAT_REG_A: Invalid VAT {}", sub_cmd - CP_VAT_REG_A); - } - state->vtx_attr[sub_cmd & CP_VAT_MASK].g0.Hex = value; - state->attr_dirty[sub_cmd & CP_VAT_MASK] = true; - break; - - case CP_VAT_REG_B: - if ((sub_cmd - CP_VAT_REG_B) >= CP_NUM_VAT_REG) - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, "CP_VAT_REG_B: Invalid VAT {}", sub_cmd - CP_VAT_REG_B); - } - state->vtx_attr[sub_cmd & CP_VAT_MASK].g1.Hex = value; - state->attr_dirty[sub_cmd & CP_VAT_MASK] = true; - break; - - case CP_VAT_REG_C: - if ((sub_cmd - CP_VAT_REG_C) >= CP_NUM_VAT_REG) - { - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_MAYBE_INVALID_CP_COMMAND); - WARN_LOG_FMT(VIDEO, "CP_VAT_REG_C: Invalid VAT {}", sub_cmd - CP_VAT_REG_C); - } - state->vtx_attr[sub_cmd & CP_VAT_MASK].g2.Hex = value; - state->attr_dirty[sub_cmd & CP_VAT_MASK] = true; - break; - - // Pointers to vertex arrays in GC RAM - case ARRAY_BASE: - state->array_bases[static_cast(sub_cmd & CP_ARRAY_MASK)] = - value & CommandProcessor::GetPhysicalAddressMask(); - state->bases_dirty = true; - break; - - case ARRAY_STRIDE: - state->array_strides[static_cast(sub_cmd & CP_ARRAY_MASK)] = value & 0xFF; - break; - - default: - DolphinAnalytics::Instance().ReportGameQuirk(GameQuirk::USES_UNKNOWN_CP_COMMAND); - WARN_LOG_FMT(VIDEO, "Unknown CP register {:02x} set to {:08x}", sub_cmd, value); - } -} - -void FillCPMemoryArray(u32* memory) -{ - memory[MATINDEX_A] = g_main_cp_state.matrix_index_a.Hex; - memory[MATINDEX_B] = g_main_cp_state.matrix_index_b.Hex; - memory[VCD_LO] = g_main_cp_state.vtx_desc.low.Hex; - memory[VCD_HI] = g_main_cp_state.vtx_desc.high.Hex; - - for (int i = 0; i < CP_NUM_VAT_REG; ++i) - { - memory[CP_VAT_REG_A + i] = g_main_cp_state.vtx_attr[i].g0.Hex; - memory[CP_VAT_REG_B + i] = g_main_cp_state.vtx_attr[i].g1.Hex; - memory[CP_VAT_REG_C + i] = g_main_cp_state.vtx_attr[i].g2.Hex; - } - - for (u8 i = 0; i < CP_NUM_ARRAYS; ++i) - { - memory[ARRAY_BASE + i] = g_main_cp_state.array_bases[static_cast(i)]; - memory[ARRAY_STRIDE + i] = g_main_cp_state.array_strides[static_cast(i)]; - } -} diff --git a/Source/Core/VideoCommon/XFMemory.h b/Source/Core/VideoCommon/XFMemory.h index 32c5dbb607..4fb538b09e 100644 --- a/Source/Core/VideoCommon/XFMemory.h +++ b/Source/Core/VideoCommon/XFMemory.h @@ -454,10 +454,10 @@ struct XFMemory u32 unk9[8]; // 0x1048 - 0x104f PostMtxInfo postMtxInfo[8]; // 0x1050 - 0x1057 }; -static_assert(sizeof(XFMemory) == sizeof(u32) * 0x1058); +static_assert(sizeof(XFMemory) == sizeof(u32) * XFMEM_REGISTERS_END); extern XFMemory xfmem; void LoadXFReg(u32 transferSize, u32 address, DataReader src); -void LoadIndexedXF(CPArray array, u32 val); -void PreprocessIndexedXF(CPArray array, u32 val); +void LoadIndexedXF(CPArray array, u32 index, u16 address, u8 size); +void PreprocessIndexedXF(CPArray array, u32 index, u16 address, u8 size); diff --git a/Source/Core/VideoCommon/XFStructs.cpp b/Source/Core/VideoCommon/XFStructs.cpp index c31b8cbb54..65ebd548b8 100644 --- a/Source/Core/VideoCommon/XFStructs.cpp +++ b/Source/Core/VideoCommon/XFStructs.cpp @@ -264,19 +264,9 @@ void LoadXFReg(u32 transferSize, u32 baseAddress, DataReader src) } } -constexpr std::tuple ExtractIndexedXF(u32 val) -{ - const u32 index = val >> 16; - const u32 address = val & 0xFFF; // check mask - const u32 size = ((val >> 12) & 0xF) + 1; - - return {index, address, size}; -} - // TODO - verify that it is correct. Seems to work, though. -void LoadIndexedXF(CPArray array, u32 val) +void LoadIndexedXF(CPArray array, u32 index, u16 address, u8 size) { - const auto [index, address, size] = ExtractIndexedXF(val); // load stuff from array to address in xf mem u32* currData = (u32*)(&xfmem) + address; @@ -307,10 +297,8 @@ void LoadIndexedXF(CPArray array, u32 val) } } -void PreprocessIndexedXF(CPArray array, u32 val) +void PreprocessIndexedXF(CPArray array, u32 index, u16 address, u8 size) { - const auto [index, address, size] = ExtractIndexedXF(val); - const u8* new_data = Memory::GetPointer(g_preprocess_cp_state.array_bases[array] + g_preprocess_cp_state.array_strides[array] * index); @@ -581,13 +569,9 @@ std::string GetXFMemDescription(u32 address, u32 value) } } -std::pair GetXFTransferInfo(const u8* data) +std::pair GetXFTransferInfo(u16 base_address, u8 transfer_size, + const u8* data) { - const u32 cmd = Common::swap32(data); - data += 4; - u32 base_address = cmd & 0xFFFF; - const u32 transfer_size = ((cmd >> 16) & 15) + 1; - if (base_address > XFMEM_REGISTERS_END) { return std::make_pair("Invalid XF Transfer", "Base address past end of address space"); @@ -655,10 +639,9 @@ std::pair GetXFTransferInfo(const u8* data) return std::make_pair(fmt::to_string(name), fmt::to_string(desc)); } -std::pair GetXFIndexedLoadInfo(CPArray array, u32 value) +std::pair GetXFIndexedLoadInfo(CPArray array, u32 index, u16 address, + u8 size) { - const auto [index, address, size] = ExtractIndexedXF(value); - const auto desc = fmt::format("Load {} bytes to XF address {:03x} from CP array {} row {}", size, address, array, index); fmt::memory_buffer written; diff --git a/Source/Core/VideoCommon/XFStructs.h b/Source/Core/VideoCommon/XFStructs.h index 7e1cc2c49f..caf197b7f0 100644 --- a/Source/Core/VideoCommon/XFStructs.h +++ b/Source/Core/VideoCommon/XFStructs.h @@ -11,5 +11,7 @@ std::pair GetXFRegInfo(u32 address, u32 value); std::string GetXFMemName(u32 address); std::string GetXFMemDescription(u32 address, u32 value); -std::pair GetXFTransferInfo(const u8* data); -std::pair GetXFIndexedLoadInfo(CPArray array, u32 value); +std::pair GetXFTransferInfo(u16 base_address, u8 transfer_size, + const u8* data); +std::pair GetXFIndexedLoadInfo(CPArray array, u32 index, u16 address, + u8 size);