Refactor OpcodeDecoding and FIFO analyzer to use callbacks

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

View File

@ -103,16 +103,10 @@ add_library(core
DSP/LabelMap.h DSP/LabelMap.h
DSPEmulator.cpp DSPEmulator.cpp
DSPEmulator.h DSPEmulator.h
FifoPlayer/FifoAnalyzer.cpp
FifoPlayer/FifoAnalyzer.h
FifoPlayer/FifoDataFile.cpp FifoPlayer/FifoDataFile.cpp
FifoPlayer/FifoDataFile.h FifoPlayer/FifoDataFile.h
FifoPlayer/FifoPlaybackAnalyzer.cpp
FifoPlayer/FifoPlaybackAnalyzer.h
FifoPlayer/FifoPlayer.cpp FifoPlayer/FifoPlayer.cpp
FifoPlayer/FifoPlayer.h FifoPlayer/FifoPlayer.h
FifoPlayer/FifoRecordAnalyzer.cpp
FifoPlayer/FifoRecordAnalyzer.h
FifoPlayer/FifoRecorder.cpp FifoPlayer/FifoRecorder.cpp
FifoPlayer/FifoRecorder.h FifoPlayer/FifoRecorder.h
FreeLookConfig.cpp FreeLookConfig.cpp

View File

@ -1,295 +0,0 @@
// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/FifoPlayer/FifoAnalyzer.h"
#include <numeric>
#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<int, 21> CalculateVertexElementSizes(int vatIndex, const CPMemory& cpMem)
{
const TVtxDesc& vtxDesc = cpMem.vtxDesc;
const VAT& vtxAttr = cpMem.vtxAttr[vatIndex];
// Colors
const std::array<ColorFormat, 2> colComp{
vtxAttr.g0.Color0Comp,
vtxAttr.g0.Color1Comp,
};
const std::array<TexComponentCount, 8> 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<ComponentFormat, 8> 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<int, 21> 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<Opcode>(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<CPArray>(0xc + (cmd - static_cast<u8>(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<int, 21> 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<int, NUM_VERTEX_COMPONENT_ARRAYS> 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<CPArray>(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<CPArray>(subCmd & CP_ARRAY_MASK)] = value;
break;
case ARRAY_STRIDE:
cpMem.arrayStrides[static_cast<CPArray>(subCmd & CP_ARRAY_MASK)] = value & 0xFF;
break;
}
}
} // namespace FifoAnalyzer

View File

@ -1,35 +0,0 @@
// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <array>
#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<VAT, CP_NUM_VAT_REG> vtxAttr;
Common::EnumMap<u32, CPArray::XF_D> arrayBases{};
Common::EnumMap<u32, CPArray::XF_D> arrayStrides{};
};
void LoadCPReg(u32 subCmd, u32 value, CPMemory& cpMem);
extern bool s_DrawingObject;
extern FifoAnalyzer::CPMemory s_CpMem;
} // namespace FifoAnalyzer

View File

@ -1,109 +0,0 @@
// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h"
#include <vector>
#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<AnalyzedFrameInfo>& 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<CmdData> 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);
}
}
}

View File

@ -1,46 +0,0 @@
// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <string>
#include <vector>
#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<FramePart> parts;
Common::EnumMap<u32, FramePartType::PrimitiveData> 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<AnalyzedFrameInfo>& frameInfo);
} // namespace FifoPlaybackAnalyzer

View File

@ -4,6 +4,7 @@
#include "Core/FifoPlayer/FifoPlayer.h" #include "Core/FifoPlayer/FifoPlayer.h"
#include <algorithm> #include <algorithm>
#include <cstring>
#include <mutex> #include <mutex>
#include "Common/Assert.h" #include "Common/Assert.h"
@ -12,7 +13,6 @@
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/CoreTiming.h" #include "Core/CoreTiming.h"
#include "Core/FifoPlayer/FifoAnalyzer.h"
#include "Core/FifoPlayer/FifoDataFile.h" #include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/HW/CPU.h" #include "Core/HW/CPU.h"
#include "Core/HW/GPFifo.h" #include "Core/HW/GPFifo.h"
@ -31,6 +31,121 @@
// TODO: Move texMem somewhere else so this isn't an issue. // TODO: Move texMem somewhere else so this isn't an issue.
#include "VideoCommon/TextureDecoder.h" #include "VideoCommon/TextureDecoder.h"
namespace
{
class FifoPlaybackAnalyzer : public OpcodeDecoder::Callback
{
public:
static void AnalyzeFrames(FifoDataFile* file, std::vector<AnalyzedFrameInfo>& 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<AnalyzedFrameInfo>& 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; bool IsPlayingBackFifologWithBrokenEFBCopies = false;
FifoPlayer::FifoPlayer() : m_Loop{SConfig::GetInstance().bLoopFifoReplay} FifoPlayer::FifoPlayer() : m_Loop{SConfig::GetInstance().bLoopFifoReplay}

View File

@ -8,13 +8,14 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "Common/Assert.h"
#include "Core/FifoPlayer/FifoDataFile.h" #include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h"
#include "Core/PowerPC/CPUCoreBase.h" #include "Core/PowerPC/CPUCoreBase.h"
#include "VideoCommon/CPMemory.h"
#include "VideoCommon/OpcodeDecoding.h"
class FifoDataFile; class FifoDataFile;
struct MemoryUpdate; struct MemoryUpdate;
struct AnalyzedFrameInfo;
namespace CPU namespace CPU
{ {
@ -51,6 +52,37 @@ enum class State;
// Shitty global to fix a shitty problem // Shitty global to fix a shitty problem
extern bool IsPlayingBackFifologWithBrokenEFBCopies; 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<FramePart> parts;
Common::EnumMap<u32, FramePartType::PrimitiveData> 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 class FifoPlayer
{ {
public: public:
@ -100,7 +132,6 @@ public:
private: private:
class CPUCore; class CPUCore;
FifoPlayer(); FifoPlayer();
CPU::State AdvanceFrame(); CPU::State AdvanceFrame();

View File

@ -1,103 +0,0 @@
// Copyright 2011 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Core/FifoPlayer/FifoRecordAnalyzer.h"
#include <algorithm>
#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);
}

View File

@ -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

View File

@ -6,13 +6,168 @@
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
#include "Common/Logging/Log.h"
#include "Common/MsgHandler.h" #include "Common/MsgHandler.h"
#include "Common/Thread.h" #include "Common/Thread.h"
#include "Core/ConfigManager.h" #include "Core/ConfigManager.h"
#include "Core/FifoPlayer/FifoAnalyzer.h"
#include "Core/FifoPlayer/FifoRecordAnalyzer.h"
#include "Core/HW/Memmap.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; static FifoRecorder instance;
FifoRecorder::FifoRecorder() = default; FifoRecorder::FifoRecorder() = default;
@ -76,7 +231,7 @@ void FifoRecorder::WriteGPCommand(const u8* data, u32 size)
{ {
// Assumes data contains all information for the command // Assumes data contains all information for the command
// Calls FifoRecorder::UseMemory // 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. // Make sure FifoPlayer's command analyzer agrees about the size of the command.
if (analyzed_size != size) 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); memcpy(m_File->GetTexMem(), texMem, FifoDataFile::TEX_MEM_SIZE);
} }
FifoRecordAnalyzer::Initialize(cpMem); m_record_analyzer = std::make_unique<FifoRecordAnalyzer>(this, cpMem);
} }
bool FifoRecorder::IsRecording() const bool FifoRecorder::IsRecording() const

View File

@ -8,6 +8,7 @@
#include <mutex> #include <mutex>
#include <vector> #include <vector>
#include "Common/Assert.h"
#include "Core/FifoPlayer/FifoDataFile.h" #include "Core/FifoPlayer/FifoDataFile.h"
class FifoRecorder class FifoRecorder
@ -47,6 +48,8 @@ public:
static FifoRecorder& GetInstance(); static FifoRecorder& GetInstance();
private: private:
class FifoRecordAnalyzer;
// Accessed from both GUI and video threads // Accessed from both GUI and video threads
std::recursive_mutex m_mutex; std::recursive_mutex m_mutex;
@ -65,6 +68,7 @@ private:
bool m_SkipFutureData = true; bool m_SkipFutureData = true;
bool m_FrameEnded = false; bool m_FrameEnded = false;
FifoFrameInfo m_CurrentFrame; FifoFrameInfo m_CurrentFrame;
std::unique_ptr<FifoRecordAnalyzer> m_record_analyzer;
std::vector<u8> m_FifoData; std::vector<u8> m_FifoData;
std::vector<u8> m_Ram; std::vector<u8> m_Ram;
std::vector<u8> m_ExRam; std::vector<u8> m_ExRam;

View File

@ -217,11 +217,8 @@
<ClInclude Include="Core\DSP\Jit\x64\DSPJitTables.h" /> <ClInclude Include="Core\DSP\Jit\x64\DSPJitTables.h" />
<ClInclude Include="Core\DSP\LabelMap.h" /> <ClInclude Include="Core\DSP\LabelMap.h" />
<ClInclude Include="Core\DSPEmulator.h" /> <ClInclude Include="Core\DSPEmulator.h" />
<ClInclude Include="Core\FifoPlayer\FifoAnalyzer.h" />
<ClInclude Include="Core\FifoPlayer\FifoDataFile.h" /> <ClInclude Include="Core\FifoPlayer\FifoDataFile.h" />
<ClInclude Include="Core\FifoPlayer\FifoPlaybackAnalyzer.h" />
<ClInclude Include="Core\FifoPlayer\FifoPlayer.h" /> <ClInclude Include="Core\FifoPlayer\FifoPlayer.h" />
<ClInclude Include="Core\FifoPlayer\FifoRecordAnalyzer.h" />
<ClInclude Include="Core\FifoPlayer\FifoRecorder.h" /> <ClInclude Include="Core\FifoPlayer\FifoRecorder.h" />
<ClInclude Include="Core\FreeLookConfig.h" /> <ClInclude Include="Core\FreeLookConfig.h" />
<ClInclude Include="Core\FreeLookManager.h" /> <ClInclude Include="Core\FreeLookManager.h" />
@ -815,11 +812,8 @@
<ClCompile Include="Core\DSP\Jit\x64\DSPJitUtil.cpp" /> <ClCompile Include="Core\DSP\Jit\x64\DSPJitUtil.cpp" />
<ClCompile Include="Core\DSP\LabelMap.cpp" /> <ClCompile Include="Core\DSP\LabelMap.cpp" />
<ClCompile Include="Core\DSPEmulator.cpp" /> <ClCompile Include="Core\DSPEmulator.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoAnalyzer.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoDataFile.cpp" /> <ClCompile Include="Core\FifoPlayer\FifoDataFile.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoPlaybackAnalyzer.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoPlayer.cpp" /> <ClCompile Include="Core\FifoPlayer\FifoPlayer.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoRecordAnalyzer.cpp" />
<ClCompile Include="Core\FifoPlayer\FifoRecorder.cpp" /> <ClCompile Include="Core\FifoPlayer\FifoRecorder.cpp" />
<ClCompile Include="Core\FreeLookConfig.cpp" /> <ClCompile Include="Core\FreeLookConfig.cpp" />
<ClCompile Include="Core\FreeLookManager.cpp" /> <ClCompile Include="Core\FreeLookManager.cpp" />

View File

@ -205,23 +205,130 @@ void FIFOAnalyzer::UpdateTree()
} }
} }
static std::string GetPrimitiveName(u8 cmd) namespace
{ {
if ((cmd & 0xC0) != 0x80) class DetailCallback : public OpcodeDecoder::Callback
{
public:
explicit DetailCallback(CPState cpmem) : m_cpmem(cpmem) {}
OPCODE_CALLBACK(void OnCP(u8 command, u32 value))
{ {
PanicAlertFmt("Not a primitive command: {:#04x}", cmd); // Note: No need to update m_cpmem as it already has the final value for this object
return "";
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 = OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data))
(cmd & OpcodeDecoder::GX_PRIMITIVE_MASK) >> OpcodeDecoder::GX_PRIMITIVE_SHIFT; {
return fmt::format("{} VAT {}", static_cast<OpcodeDecoder::Primitive>(primitive), vat); const auto [name, desc] = GetXFTransferInfo(address, count, data);
} ASSERT(!name.empty());
const u32 command = address | (count << 16);
text = QStringLiteral("XF %1 ").arg(command, 8, 16, QLatin1Char('0'));
for (u8 i = 0; i < count; i++)
{
const u32 value = Common::swap32(&data[i * 4]);
text += QStringLiteral("%1 ").arg(value, 8, 16, QLatin1Char('0'));
}
text += QStringLiteral(" ") + QString::fromStdString(name);
}
OPCODE_CALLBACK(void OnBP(u8 command, u32 value))
{
const auto [name, desc] = GetBPRegInfo(command, value);
ASSERT(!name.empty());
text = QStringLiteral("BP %1 %2 %3")
.arg(command, 2, 16, QLatin1Char('0'))
.arg(value, 6, 16, QLatin1Char('0'))
.arg(QString::fromStdString(name));
}
OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size))
{
const auto [desc, written] = GetXFIndexedLoadInfo(array, index, address, size);
text = QStringLiteral("LOAD INDX %1 %2")
.arg(QString::fromStdString(fmt::to_string(array)))
.arg(QString::fromStdString(desc));
}
OPCODE_CALLBACK(void OnPrimitiveCommand(OpcodeDecoder::Primitive primitive, u8 vat,
u32 vertex_size, u16 num_vertices, const u8* vertex_data))
{
const auto name = fmt::to_string(primitive);
// Note that vertex_count is allowed to be 0, with no special treatment
// (another command just comes right after the current command, with no vertices in between)
const u32 object_prim_size = num_vertices * vertex_size;
const u8 opcode =
0x80 | (static_cast<u8>(primitive) << OpcodeDecoder::GX_PRIMITIVE_SHIFT) | vat;
text = QStringLiteral("PRIMITIVE %1 (%2) %3 vertices %4 bytes/vertex %5 total bytes")
.arg(QString::fromStdString(name))
.arg(opcode, 2, 16, QLatin1Char('0'))
.arg(num_vertices)
.arg(vertex_size)
.arg(object_prim_size);
// It's not really useful to have a massive unreadable hex string for the object primitives.
// Put it in the description instead.
// #define INCLUDE_HEX_IN_PRIMITIVES
#ifdef INCLUDE_HEX_IN_PRIMITIVES
text += QStringLiteral(" ");
for (u32 i = 0; i < object_prim_size; i++)
{
text += QStringLiteral("%1").arg(vertex_data[i], 2, 16, QLatin1Char('0'));
}
#endif
}
OPCODE_CALLBACK(void OnDisplayList(u32 address, u32 size))
{
text = QObject::tr("Call display list at %1 with size %2")
.arg(address, 8, 16, QLatin1Char('0'))
.arg(size, 8, 16, QLatin1Char('0'));
}
OPCODE_CALLBACK(void OnNop(u32 count))
{
if (count > 1)
text = QStringLiteral("NOP (%1x)").arg(count);
else
text = QStringLiteral("NOP");
}
OPCODE_CALLBACK(void OnUnknown(u8 opcode, const u8* data))
{
using OpcodeDecoder::Opcode;
if (static_cast<Opcode>(opcode) == Opcode::GX_CMD_UNKNOWN_METRICS)
text = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
else if (static_cast<Opcode>(opcode) == Opcode::GX_CMD_INVL_VC)
text = QStringLiteral("GX_CMD_INVL_VC");
else
text = QStringLiteral("Unknown opcode %1").arg(opcode, 2, 16);
}
OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size)) {}
OPCODE_CALLBACK(CPState& GetCPState()) { return m_cpmem; }
QString text;
CPState m_cpmem;
};
} // namespace
void FIFOAnalyzer::UpdateDetails() void FIFOAnalyzer::UpdateDetails()
{ {
using OpcodeDecoder::Opcode;
// Clearing the detail list can update the selection, which causes UpdateDescription to be called // 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 // 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. // 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_end = frame_info.parts[end_part_nr].m_end;
const u32 object_size = object_end - object_start; const u32 object_size = object_end - object_start;
const u8* const object = &fifo_frame.fifoData[object_start];
u32 object_offset = 0; 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) while (object_offset < object_size)
{ {
QString new_label;
const u32 start_offset = object_offset; const u32 start_offset = object_offset;
m_object_data_offsets.push_back(start_offset); m_object_data_offsets.push_back(start_offset);
const Opcode opcode = static_cast<Opcode>(object[object_offset++]); object_offset += OpcodeDecoder::RunCommand(&fifo_frame.fifoData[object_start + start_offset],
switch (opcode) object_size - start_offset, callback);
{
case Opcode::GX_NOP:
if (object[object_offset] == static_cast<u8>(Opcode::GX_NOP))
{
u32 nop_count = 2;
while (object[++object_offset] == static_cast<u8>(Opcode::GX_NOP))
nop_count++;
new_label = QStringLiteral("NOP (%1x)").arg(nop_count); QString new_label =
} QStringLiteral("%1: ").arg(object_start + start_offset, 8, 16, QLatin1Char('0')) +
else callback.text;
{
new_label = QStringLiteral("NOP");
}
break;
case Opcode::GX_CMD_UNKNOWN_METRICS:
new_label = QStringLiteral("GX_CMD_UNKNOWN_METRICS");
break;
case Opcode::GX_CMD_INVL_VC:
new_label = QStringLiteral("GX_CMD_INVL_VC");
break;
case Opcode::GX_LOAD_CP_REG:
{
const u8 cmd2 = object[object_offset++];
const u32 value = Common::swap32(&object[object_offset]);
object_offset += 4;
const auto [name, desc] = GetCPRegInfo(cmd2, value);
ASSERT(!name.empty());
new_label = QStringLiteral("CP %1 %2 %3")
.arg(cmd2, 2, 16, QLatin1Char('0'))
.arg(value, 8, 16, QLatin1Char('0'))
.arg(QString::fromStdString(name));
}
break;
case Opcode::GX_LOAD_XF_REG:
{
const auto [name, desc] = GetXFTransferInfo(&object[object_offset]);
const u32 cmd2 = Common::swap32(&object[object_offset]);
object_offset += 4;
ASSERT(!name.empty());
const u8 stream_size = ((cmd2 >> 16) & 15) + 1;
new_label = QStringLiteral("XF %1 ").arg(cmd2, 8, 16, QLatin1Char('0'));
for (u8 i = 0; i < stream_size; i++)
{
const u32 value = Common::swap32(&object[object_offset]);
object_offset += 4;
new_label += QStringLiteral("%1 ").arg(value, 8, 16, QLatin1Char('0'));
}
new_label += QStringLiteral(" ") + QString::fromStdString(name);
}
break;
case Opcode::GX_LOAD_INDX_A:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_A, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX A %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_B:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_B, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX B %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_C:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_C, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX C %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_LOAD_INDX_D:
{
const auto [desc, written] =
GetXFIndexedLoadInfo(CPArray::XF_D, Common::swap32(&object[object_offset]));
object_offset += 4;
new_label = QStringLiteral("LOAD INDX D %1").arg(QString::fromStdString(desc));
}
break;
case Opcode::GX_CMD_CALL_DL:
// The recorder should have expanded display lists into the fifo stream and skipped the
// call to start them
// That is done to make it easier to track where memory is updated
ASSERT(false);
object_offset += 8;
new_label = QStringLiteral("CALL DL");
break;
case Opcode::GX_LOAD_BP_REG:
{
const u8 cmd2 = object[object_offset++];
const u32 cmddata = Common::swap24(&object[object_offset]);
object_offset += 3;
const auto [name, desc] = GetBPRegInfo(cmd2, cmddata);
ASSERT(!name.empty());
new_label = QStringLiteral("BP %1 %2 %3")
.arg(cmd2, 2, 16, QLatin1Char('0'))
.arg(cmddata, 6, 16, QLatin1Char('0'))
.arg(QString::fromStdString(name));
}
break;
default:
{
const u8 command = static_cast<u8>(opcode);
if ((command & 0xC0) == 0x80)
{
// Object primitive data
const u8 vat = command & OpcodeDecoder::GX_VAT_MASK;
const auto& vtx_desc = frame_info.parts[end_part_nr].m_cpmem.vtxDesc;
const auto& vtx_attr = frame_info.parts[end_part_nr].m_cpmem.vtxAttr[vat];
const auto name = GetPrimitiveName(command);
const u16 vertex_count = Common::swap16(&object[object_offset]);
object_offset += 2;
const u32 vertex_size = VertexLoaderBase::GetVertexSize(vtx_desc, vtx_attr);
// Note that vertex_count is allowed to be 0, with no special treatment
// (another command just comes right after the current command, with no vertices in between)
const u32 object_prim_size = vertex_count * vertex_size;
new_label = QStringLiteral("PRIMITIVE %1 (%2) %3 vertices %4 bytes/vertex %5 total bytes")
.arg(QString::fromStdString(name))
.arg(command, 2, 16, QLatin1Char('0'))
.arg(vertex_count)
.arg(vertex_size)
.arg(object_prim_size);
// It's not really useful to have a massive unreadable hex string for the object primitives.
// Put it in the description instead.
// #define INCLUDE_HEX_IN_PRIMITIVES
#ifdef INCLUDE_HEX_IN_PRIMITIVES
new_label += QStringLiteral(" ");
for (u32 i = 0; i < object_prim_size; i++)
{
new_label += QStringLiteral("%1").arg(object[object_offset++], 2, 16, QLatin1Char('0'));
}
#else
object_offset += object_prim_size;
#endif
}
else
{
new_label = QStringLiteral("Unknown opcode %1").arg(command, 2, 16);
}
break;
}
}
new_label = QStringLiteral("%1: ").arg(object_start + start_offset, 8, 16, QLatin1Char('0')) +
new_label;
m_detail_list->addItem(new_label); m_detail_list->addItem(new_label);
} }
@ -580,10 +522,143 @@ void FIFOAnalyzer::ShowSearchResult(size_t index)
m_search_previous->setEnabled(index > 0); 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() void FIFOAnalyzer::UpdateDescription()
{ {
using OpcodeDecoder::Opcode;
m_entry_detail_browser->clear(); m_entry_detail_browser->clear();
if (!FifoPlayer::GetInstance().IsPlaying()) if (!FifoPlayer::GetInstance().IsPlaying())
@ -606,138 +681,12 @@ void FIFOAnalyzer::UpdateDescription()
const FifoFrameInfo& fifo_frame = FifoPlayer::GetInstance().GetFile()->GetFrame(frame_nr); 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_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 u32 entry_start = m_object_data_offsets[entry_nr];
const u8* cmddata = &fifo_frame.fifoData[object_start + entry_start]; auto callback = DescriptionCallback(frame_info.parts[end_part_nr].m_cpmem);
const Opcode opcode = static_cast<Opcode>(*cmddata); OpcodeDecoder::RunCommand(&fifo_frame.fifoData[object_start + entry_start],
object_size - entry_start, callback);
// TODO: Not sure whether we should bother translating the descriptions m_entry_detail_browser->setText(callback.text);
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);
} }

View File

@ -21,7 +21,6 @@
#include "Core/Core.h" #include "Core/Core.h"
#include "Core/FifoPlayer/FifoDataFile.h" #include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/FifoPlayer/FifoPlaybackAnalyzer.h"
#include "Core/FifoPlayer/FifoPlayer.h" #include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/FifoPlayer/FifoRecorder.h" #include "Core/FifoPlayer/FifoRecorder.h"

View File

@ -2205,7 +2205,7 @@ struct BPMemory
extern BPMemory bpmem; extern BPMemory bpmem;
void LoadBPReg(u32 value0, int cycles_into_future); void LoadBPReg(u8 reg, u32 value, int cycles_into_future);
void LoadBPRegPreprocess(u32 value0, int cycles_into_future); void LoadBPRegPreprocess(u8 reg, u32 value, int cycles_into_future);
std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata); std::pair<std::string, std::string> GetBPRegInfo(u8 cmd, u32 cmddata);

View File

@ -716,29 +716,27 @@ static void BPWritten(const BPCmd& bp, int cycles_into_future)
bp.newvalue); bp.newvalue);
} }
// Call browser: OpcodeDecoding.cpp ExecuteDisplayList > Decode() > LoadBPReg() // Call browser: OpcodeDecoding.cpp RunCallback::OnBP()
void LoadBPReg(u32 value0, int cycles_into_future) void LoadBPReg(u8 reg, u32 value, int cycles_into_future)
{ {
int regNum = value0 >> 24; int oldval = ((u32*)&bpmem)[reg];
int oldval = ((u32*)&bpmem)[regNum]; int newval = (oldval & ~bpmem.bpMask) | (value & bpmem.bpMask);
int newval = (oldval & ~bpmem.bpMask) | (value0 & bpmem.bpMask);
int changes = (oldval ^ newval) & 0xFFFFFF; 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. // 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; bpmem.bpMask = 0xFFFFFF;
BPWritten(bp, cycles_into_future); 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 via BPMEM_BP_MASK could hypothetically be a problem
// masking could hypothetically be a problem u32 newval = value & 0xffffff;
u32 newval = value0 & 0xffffff; switch (reg)
switch (regNum)
{ {
case BPMEM_SETDRAWDONE: case BPMEM_SETDRAWDONE:
if ((newval & 0xff) == 0x02) if ((newval & 0xff) == 0x02)

View File

@ -2,7 +2,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
#include "VideoCommon/CPMemory.h" #include "VideoCommon/CPMemory.h"
#include <cstring>
#include "Common/ChunkFile.h" #include "Common/ChunkFile.h"
#include "Common/Logging/Log.h"
#include "Core/DolphinAnalytics.h"
#include "VideoCommon/CommandProcessor.h"
// CP state // CP state
CPState g_main_cp_state; CPState g_main_cp_state;
@ -28,7 +34,7 @@ void DoCPState(PointerWrap& p)
void CopyPreprocessCPStateFromMain() 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<std::string, std::string> GetCPRegInfo(u8 cmd, u32 value) std::pair<std::string, std::string> GetCPRegInfo(u8 cmd, u32 value)
@ -73,3 +79,164 @@ std::pair<std::string, std::string> GetCPRegInfo(u8 cmd, u32 value)
return std::make_pair(fmt::format("Invalid CP register {:02x} = {:08x}", cmd, 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<CPArray>(i)] = memory[ARRAY_BASE + i];
array_strides[static_cast<CPArray>(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<CPArray>(sub_cmd & CP_ARRAY_MASK)] =
value & CommandProcessor::GetPhysicalAddressMask();
bases_dirty = true;
break;
case ARRAY_STRIDE:
array_strides[static_cast<CPArray>(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<CPArray>(i)];
memory[ARRAY_STRIDE + i] = array_strides[static_cast<CPArray>(i)];
}
}

View File

@ -5,6 +5,7 @@
#include <array> #include <array>
#include <string> #include <string>
#include <type_traits>
#include <utility> #include <utility>
#include "Common/BitField.h" #include "Common/BitField.h"
@ -630,13 +631,21 @@ class VertexLoaderBase;
// STATE_TO_SAVE // STATE_TO_SAVE
struct CPState final 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<u32, CPArray::XF_D> array_bases; Common::EnumMap<u32, CPArray::XF_D> array_bases;
Common::EnumMap<u32, CPArray::XF_D> array_strides; Common::EnumMap<u32, CPArray::XF_D> array_strides;
TMatrixIndexA matrix_index_a{}; TMatrixIndexA matrix_index_a{};
TMatrixIndexB matrix_index_b{}; TMatrixIndexB matrix_index_b{};
TVtxDesc vtx_desc; TVtxDesc vtx_desc;
// Most games only use the first VtxAttr and simply reconfigure it all the time as needed. // 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<VAT, CP_NUM_VAT_REG> vtx_attr{};
// Attributes that actually belong to VertexLoaderManager: // Attributes that actually belong to VertexLoaderManager:
BitSet32 attr_dirty{}; BitSet32 attr_dirty{};
@ -644,18 +653,13 @@ struct CPState final
VertexLoaderBase* vertex_loaders[CP_NUM_VAT_REG]{}; VertexLoaderBase* vertex_loaders[CP_NUM_VAT_REG]{};
int last_id = 0; int last_id = 0;
}; };
static_assert(std::is_trivially_copyable_v<CPState>);
class PointerWrap; class PointerWrap;
extern CPState g_main_cp_state; extern CPState g_main_cp_state;
extern CPState g_preprocess_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 DoCPState(PointerWrap& p);
void CopyPreprocessCPStateFromMain(); void CopyPreprocessCPStateFromMain();

View File

@ -5,6 +5,7 @@
#include <atomic> #include <atomic>
#include <cstring> #include <cstring>
#include <fmt/format.h>
#include "Common/Assert.h" #include "Common/Assert.h"
#include "Common/ChunkFile.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 // 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" "This means one of the following:\n"
"* The emulated GPU got desynced, disabling dual core can help\n" "* The emulated GPU got desynced, disabling dual core can help\n"
"* Command stream corrupted by some spurious memory bug\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" "* Some other sort of bug\n\n"
"Further errors will be sent to the Video Backend log and\n" "Further errors will be sent to the Video Backend log and\n"
"Dolphin will now likely crash or hang. Enjoy.", "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" PanicAlertFmt("Illegal command {:02x}\n"

View File

@ -169,7 +169,7 @@ void SetCpClearRegister();
void SetCpControlRegister(); void SetCpControlRegister();
void SetCpStatusRegister(); void SetCpStatusRegister();
void HandleUnknownOpcode(u8 cmd_byte, void* buffer, bool preprocess); void HandleUnknownOpcode(u8 cmd_byte, const u8* buffer, bool preprocess);
u32 GetPhysicalAddressMask(); u32 GetPhysicalAddressMask();

View File

@ -273,8 +273,8 @@ static void ReadDataFromFifoOnCPU(u32 readPtr)
} }
} }
Memory::CopyFromEmu(s_video_buffer_write_ptr, readPtr, len); Memory::CopyFromEmu(s_video_buffer_write_ptr, readPtr, len);
s_video_buffer_pp_read_ptr = OpcodeDecoder::Run<true>( s_video_buffer_pp_read_ptr = OpcodeDecoder::RunFifo<true>(
DataReader(s_video_buffer_pp_read_ptr, write_ptr + len), nullptr, false); DataReader(s_video_buffer_pp_read_ptr, write_ptr + len), nullptr);
// This would have to be locked if the GPU thread didn't spin. // This would have to be locked if the GPU thread didn't spin.
s_video_buffer_write_ptr = write_ptr + len; s_video_buffer_write_ptr = write_ptr + len;
} }
@ -316,7 +316,7 @@ void RunGpuLoop()
if (write_ptr > seen_ptr) if (write_ptr > seen_ptr)
{ {
s_video_buffer_read_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; s_video_buffer_seen_ptr = write_ptr;
} }
} }
@ -349,8 +349,8 @@ void RunGpuLoop()
fifo.CPReadWriteDistance.load(std::memory_order_relaxed) - 32); fifo.CPReadWriteDistance.load(std::memory_order_relaxed) - 32);
u8* write_ptr = s_video_buffer_write_ptr; u8* write_ptr = s_video_buffer_write_ptr;
s_video_buffer_read_ptr = OpcodeDecoder::Run( s_video_buffer_read_ptr = OpcodeDecoder::RunFifo(
DataReader(s_video_buffer_read_ptr, write_ptr), &cyclesExecuted, false); DataReader(s_video_buffer_read_ptr, write_ptr), &cyclesExecuted);
fifo.CPReadPointer.store(readPtr, std::memory_order_relaxed); fifo.CPReadPointer.store(readPtr, std::memory_order_relaxed);
fifo.CPReadWriteDistance.fetch_sub(32, std::memory_order_seq_cst); 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)); ReadDataFromFifo(fifo.CPReadPointer.load(std::memory_order_relaxed));
u32 cycles = 0; u32 cycles = 0;
s_video_buffer_read_ptr = OpcodeDecoder::Run( s_video_buffer_read_ptr = OpcodeDecoder::RunFifo(
DataReader(s_video_buffer_read_ptr, s_video_buffer_write_ptr), &cycles, false); DataReader(s_video_buffer_read_ptr, s_video_buffer_write_ptr), &cycles);
available_ticks -= cycles; available_ticks -= cycles;
} }

View File

@ -14,7 +14,7 @@
#include "VideoCommon/OpcodeDecoding.h" #include "VideoCommon/OpcodeDecoding.h"
#include "Common/CommonTypes.h" #include "Common/Assert.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/FifoPlayer/FifoRecorder.h" #include "Core/FifoPlayer/FifoRecorder.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
@ -24,55 +24,15 @@
#include "VideoCommon/DataReader.h" #include "VideoCommon/DataReader.h"
#include "VideoCommon/Fifo.h" #include "VideoCommon/Fifo.h"
#include "VideoCommon/Statistics.h" #include "VideoCommon/Statistics.h"
#include "VideoCommon/VertexLoaderBase.h"
#include "VideoCommon/VertexLoaderManager.h" #include "VideoCommon/VertexLoaderManager.h"
#include "VideoCommon/VertexShaderManager.h"
#include "VideoCommon/XFMemory.h" #include "VideoCommon/XFMemory.h"
#include "VideoCommon/XFStructs.h"
namespace OpcodeDecoder namespace OpcodeDecoder
{ {
namespace
{
bool s_is_fifo_error_seen = false; bool s_is_fifo_error_seen = false;
u32 InterpretDisplayList(u32 address, u32 size)
{
u8* start_address;
if (Fifo::UseDeterministicGPUThread())
start_address = static_cast<u8*>(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<true>(DataReader(start_address, start_address + size), nullptr, true);
}
} // Anonymous namespace
bool g_record_fifo_data = false; bool g_record_fifo_data = false;
void Init() void Init()
@ -81,203 +41,205 @@ void Init()
} }
template <bool is_preprocess> template <bool is_preprocess>
u8* Run(DataReader src, u32* cycles, bool in_display_list) class RunCallback final : public Callback
{ {
u32 total_cycles = 0; public:
u8* opcode_start = nullptr; OPCODE_CALLBACK(void OnXF(u16 address, u8 count, const u8* data))
const auto finish_up = [cycles, &opcode_start, &total_cycles] {
if (cycles != nullptr)
{ {
*cycles = total_cycles; m_cycles += 18 + 6 * count;
}
return opcode_start;
};
while (true)
{
opcode_start = src.GetPointer();
if (!src.size())
return finish_up();
const u8 cmd_byte = src.Read<u8>();
switch (static_cast<Opcode>(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<u8>();
const u32 value = src.Read<u32>();
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<u32>();
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) if constexpr (!is_preprocess)
{ {
const u32 xf_address = cmd2 & 0xFFFF; // HACK
LoadXFReg(transfer_size, xf_address, src); LoadXFReg(count, address,
DataReader{const_cast<u8*>(data), const_cast<u8*>(data) + count * sizeof(u32)});
INCSTAT(g_stats.this_frame.num_xf_loads); INCSTAT(g_stats.this_frame.num_xf_loads);
} }
src.Skip<u32>(transfer_size);
} }
break; OPCODE_CALLBACK(void OnCP(u8 command, u32 value))
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) m_cycles += 12;
return finish_up(); 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);
total_cycles += 6; INCSTAT(g_stats.this_frame.num_cp_loads);
}
// Map the command byte to its ref array. GetCPState().LoadCPReg(command, value);
// GX_LOAD_INDX_A (32) -> 0xC }
// GX_LOAD_INDX_B (40) -> 0xD OPCODE_CALLBACK(void OnBP(u8 command, u32 value))
// GX_LOAD_INDX_C (48) -> 0xE {
// GX_LOAD_INDX_D (56) -> 0xF m_cycles += 12;
const auto array = static_cast<CPArray>((cmd_byte / 8) + 8);
if constexpr (is_preprocess) if constexpr (is_preprocess)
PreprocessIndexedXF(array, src.Read<u32>());
else
LoadIndexedXF(array, src.Read<u32>());
}
break;
case Opcode::GX_CMD_CALL_DL:
{ {
if (src.size() < 8) LoadBPRegPreprocess(command, value, m_cycles);
return finish_up();
const u32 address = src.Read<u32>();
const u32 count = src.Read<u32>();
if (in_display_list)
{
total_cycles += 6;
INFO_LOG_FMT(VIDEO, "recursive display list detected");
} }
else else
{ {
if constexpr (is_preprocess) LoadBPReg(command, value, m_cycles);
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<u32>();
if constexpr (is_preprocess)
{
LoadBPRegPreprocess(bp_cmd, total_cycles);
}
else
{
LoadBPReg(bp_cmd, total_cycles);
INCSTAT(g_stats.this_frame.num_bp_loads); INCSTAT(g_stats.this_frame.num_bp_loads);
} }
} }
break; OPCODE_CALLBACK(void OnIndexedLoad(CPArray array, u32 index, u16 address, u8 size))
{
m_cycles += 6;
// draw primitives if constexpr (is_preprocess)
default: PreprocessIndexedXF(array, index, address, size);
if ((cmd_byte & 0xC0) == 0x80) 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 // load vertices
if (src.size() < 2) const u32 size = vertex_size * num_vertices;
return finish_up();
const u16 num_vertices = src.Read<u16>(); // HACK
const int bytes = VertexLoaderManager::RunVertices( DataReader src{const_cast<u8*>(vertex_data), const_cast<u8*>(vertex_data) + size};
cmd_byte & GX_VAT_MASK, // Vertex loader index (0 - 7) const u32 bytes =
static_cast<Primitive>((cmd_byte & GX_PRIMITIVE_MASK) >> GX_PRIMITIVE_SHIFT), VertexLoaderManager::RunVertices(vat, primitive, num_vertices, src, is_preprocess);
num_vertices, src, is_preprocess);
if (bytes < 0) ASSERT(bytes == size);
return finish_up();
src.Skip(bytes);
// 4 GPU ticks per vertex, 3 CPU ticks per GPU tick // 4 GPU ticks per vertex, 3 CPU ticks per GPU tick
total_cycles += num_vertices * 4 * 3 + 6; 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 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<u8*>(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) == Opcode::GX_UNKNOWN_RESET)
{
// Datel software uses this command
m_cycles += 6;
DEBUG_LOG_FMT(VIDEO, "GX Reset?");
}
else if (static_cast<Opcode>(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) == Opcode::GX_CMD_INVL_VC)
{
// Invalidate Vertex Cache
m_cycles += 6;
DEBUG_LOG_FMT(VIDEO, "Invalidate (vertex cache?)");
} }
else else
{ {
if (!s_is_fifo_error_seen) if (!s_is_fifo_error_seen)
CommandProcessor::HandleUnknownOpcode(cmd_byte, opcode_start, is_preprocess); CommandProcessor::HandleUnknownOpcode(opcode, data, is_preprocess);
ERROR_LOG_FMT(VIDEO, "FIFO: Unknown Opcode({:#04x} @ {}, preprocessing = {})", cmd_byte, ERROR_LOG_FMT(VIDEO, "FIFO: Unknown Opcode({:#04x} @ {}, preprocessing = {})", opcode,
fmt::ptr(opcode_start), is_preprocess ? "yes" : "no"); fmt::ptr(data), is_preprocess ? "yes" : "no");
s_is_fifo_error_seen = true; s_is_fifo_error_seen = true;
total_cycles += 1; m_cycles += 1;
} }
break;
} }
// Display lists get added directly into the FIFO stream OPCODE_CALLBACK(void OnCommand(const u8* data, u32 size))
{
ASSERT(size >= 1);
if constexpr (!is_preprocess) if constexpr (!is_preprocess)
{ {
if (g_record_fifo_data && static_cast<Opcode>(cmd_byte) != Opcode::GX_CMD_CALL_DL) // 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<Opcode>(data[0]) != Opcode::GX_CMD_CALL_DL)
{ {
const u8* const opcode_end = src.GetPointer(); FifoRecorder::GetInstance().WriteGPCommand(data, size);
FifoRecorder::GetInstance().WriteGPCommand(opcode_start, u32(opcode_end - opcode_start));
} }
} }
} }
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 <bool is_preprocess>
u8* RunFifo(DataReader src, u32* cycles)
{
using CallbackT = RunCallback<is_preprocess>;
auto callback = CallbackT{};
u32 size = Run(src.GetPointer(), static_cast<u32>(src.size()), callback);
if (cycles != nullptr)
*cycles = callback.m_cycles;
src.Skip(size);
return src.GetPointer();
} }
template u8* Run<true>(DataReader src, u32* cycles, bool in_display_list); template u8* RunFifo<true>(DataReader src, u32* cycles);
template u8* Run<false>(DataReader src, u32* cycles, bool in_display_list); template u8* RunFifo<false>(DataReader src, u32* cycles);
} // namespace OpcodeDecoder } // namespace OpcodeDecoder

View File

@ -3,9 +3,17 @@
#pragma once #pragma once
#include <type_traits>
#include "Common/Assert.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumFormatter.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; class DataReader;
namespace OpcodeDecoder namespace OpcodeDecoder
@ -55,8 +63,220 @@ enum class Primitive : u8
void Init(); 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 <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
static DOLPHIN_FORCE_INLINE u32 RunCommand(const u8* data, u32 available, T& callback)
{
if (available < 1)
return 0;
const Opcode cmd = static_cast<Opcode>(data[0]);
switch (cmd)
{
case Opcode::GX_NOP:
{
u32 count = 1;
while (count < available && static_cast<Opcode>(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<CPArray>((static_cast<u8>(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<u8>(cmd);
const OpcodeDecoder::Primitive primitive = static_cast<OpcodeDecoder::Primitive>(
(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<u8>(cmd), data);
return 1;
}
} // namespace detail
template <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
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 <typename T, typename = std::enable_if_t<std::is_base_of_v<Callback, T>>>
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 <bool is_preprocess = false> template <bool is_preprocess = false>
u8* Run(DataReader src, u32* cycles, bool in_display_list); u8* RunFifo(DataReader src, u32* cycles);
} // namespace OpcodeDecoder } // namespace OpcodeDecoder

View File

@ -963,7 +963,7 @@ void Renderer::RecordVideoMemory()
const u32* xfregs_ptr = reinterpret_cast<const u32*>(&xfmem) + FifoDataFile::XF_MEM_SIZE; const u32* xfregs_ptr = reinterpret_cast<const u32*>(&xfmem) + FifoDataFile::XF_MEM_SIZE;
u32 xfregs_size = sizeof(XFMemory) / 4 - 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, FifoRecorder::GetInstance().SetVideoMemory(bpmem_ptr, cpmem, xfmem_ptr, xfregs_ptr, xfregs_size,
texMem); texMem);

View File

@ -405,7 +405,7 @@ void VertexLoaderARM64::GenerateVertexLoader()
MOV(skipped_reg, ARM64Reg::WZR); MOV(skipped_reg, ARM64Reg::WZR);
MOV(saved_count, count_reg); 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); MOVP2R(arraybase_reg, VertexLoaderManager::cached_arraybases);
if (need_scale) if (need_scale)

View File

@ -12,17 +12,14 @@
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "Common/Assert.h"
#include "Common/CommonTypes.h" #include "Common/CommonTypes.h"
#include "Common/EnumMap.h" #include "Common/EnumMap.h"
#include "Common/Logging/Log.h" #include "Common/Logging/Log.h"
#include "Core/DolphinAnalytics.h"
#include "Core/HW/Memmap.h" #include "Core/HW/Memmap.h"
#include "VideoCommon/BPMemory.h" #include "VideoCommon/BPMemory.h"
#include "VideoCommon/CPMemory.h" #include "VideoCommon/CPMemory.h"
#include "VideoCommon/CommandProcessor.h"
#include "VideoCommon/DataReader.h" #include "VideoCommon/DataReader.h"
#include "VideoCommon/IndexGenerator.h" #include "VideoCommon/IndexGenerator.h"
#include "VideoCommon/NativeVertexFormat.h" #include "VideoCommon/NativeVertexFormat.h"
@ -298,147 +295,3 @@ NativeVertexFormat* GetCurrentVertexFormat()
} }
} // namespace VertexLoaderManager } // 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<CPArray>(sub_cmd & CP_ARRAY_MASK)] =
value & CommandProcessor::GetPhysicalAddressMask();
state->bases_dirty = true;
break;
case ARRAY_STRIDE:
state->array_strides[static_cast<CPArray>(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<CPArray>(i)];
memory[ARRAY_STRIDE + i] = g_main_cp_state.array_strides[static_cast<CPArray>(i)];
}
}

View File

@ -454,10 +454,10 @@ struct XFMemory
u32 unk9[8]; // 0x1048 - 0x104f u32 unk9[8]; // 0x1048 - 0x104f
PostMtxInfo postMtxInfo[8]; // 0x1050 - 0x1057 PostMtxInfo postMtxInfo[8]; // 0x1050 - 0x1057
}; };
static_assert(sizeof(XFMemory) == sizeof(u32) * 0x1058); static_assert(sizeof(XFMemory) == sizeof(u32) * XFMEM_REGISTERS_END);
extern XFMemory xfmem; extern XFMemory xfmem;
void LoadXFReg(u32 transferSize, u32 address, DataReader src); void LoadXFReg(u32 transferSize, u32 address, DataReader src);
void LoadIndexedXF(CPArray array, u32 val); void LoadIndexedXF(CPArray array, u32 index, u16 address, u8 size);
void PreprocessIndexedXF(CPArray array, u32 val); void PreprocessIndexedXF(CPArray array, u32 index, u16 address, u8 size);

View File

@ -264,19 +264,9 @@ void LoadXFReg(u32 transferSize, u32 baseAddress, DataReader src)
} }
} }
constexpr std::tuple<u32, u32, u32> 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. // 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 // load stuff from array to address in xf mem
u32* currData = (u32*)(&xfmem) + address; 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] + const u8* new_data = Memory::GetPointer(g_preprocess_cp_state.array_bases[array] +
g_preprocess_cp_state.array_strides[array] * index); g_preprocess_cp_state.array_strides[array] * index);
@ -581,13 +569,9 @@ std::string GetXFMemDescription(u32 address, u32 value)
} }
} }
std::pair<std::string, std::string> GetXFTransferInfo(const u8* data) std::pair<std::string, std::string> 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) if (base_address > XFMEM_REGISTERS_END)
{ {
return std::make_pair("Invalid XF Transfer", "Base address past end of address space"); return std::make_pair("Invalid XF Transfer", "Base address past end of address space");
@ -655,10 +639,9 @@ std::pair<std::string, std::string> GetXFTransferInfo(const u8* data)
return std::make_pair(fmt::to_string(name), fmt::to_string(desc)); return std::make_pair(fmt::to_string(name), fmt::to_string(desc));
} }
std::pair<std::string, std::string> GetXFIndexedLoadInfo(CPArray array, u32 value) std::pair<std::string, std::string> 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, const auto desc = fmt::format("Load {} bytes to XF address {:03x} from CP array {} row {}", size,
address, array, index); address, array, index);
fmt::memory_buffer written; fmt::memory_buffer written;

View File

@ -11,5 +11,7 @@
std::pair<std::string, std::string> GetXFRegInfo(u32 address, u32 value); std::pair<std::string, std::string> GetXFRegInfo(u32 address, u32 value);
std::string GetXFMemName(u32 address); std::string GetXFMemName(u32 address);
std::string GetXFMemDescription(u32 address, u32 value); std::string GetXFMemDescription(u32 address, u32 value);
std::pair<std::string, std::string> GetXFTransferInfo(const u8* data); std::pair<std::string, std::string> GetXFTransferInfo(u16 base_address, u8 transfer_size,
std::pair<std::string, std::string> GetXFIndexedLoadInfo(CPArray array, u32 value); const u8* data);
std::pair<std::string, std::string> GetXFIndexedLoadInfo(CPArray array, u32 index, u16 address,
u8 size);