Refactor OpcodeDecoding and FIFO analyzer to use callbacks
This commit is contained in:
parent
0441826206
commit
b5fd35f951
|
@ -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
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue