Add graphics FIFO recorder and player for debugging the graphics system.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@7414 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
donkopunchstania
2011-03-27 02:55:08 +00:00
parent f8037e3ccf
commit 07c4da6084
35 changed files with 3275 additions and 6 deletions

View File

@ -58,6 +58,12 @@ set(SRCS Src/ActionReplay.cpp
Src/DSP/Jit/DSPJitMultiplier.cpp
Src/DSP/Jit/DSPJitUtil.cpp
Src/DSP/Jit/DSPJitMisc.cpp
Src/FifoPlayer/FifoAnalyzer.cpp
Src/FifoPlayer/FifoDataFile.cpp
Src/FifoPlayer/FifoPlaybackAnalyzer.cpp
Src/FifoPlayer/FifoPlayer.cpp
Src/FifoPlayer/FifoRecordAnalyzer.cpp
Src/FifoPlayer/FifoRecorder.cpp
Src/HLE/HLE.cpp
Src/HLE/HLE_Misc.cpp
Src/HLE/HLE_OS.cpp

View File

@ -243,6 +243,12 @@
<ClCompile Include="Src\DSP\Jit\DSPJitRegCache.cpp" />
<ClCompile Include="Src\DSP\Jit\DSPJitUtil.cpp" />
<ClCompile Include="Src\DSP\LabelMap.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoAnalyzer.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoPlaybackAnalyzer.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoDataFile.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoPlayer.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoRecordAnalyzer.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoRecorder.cpp" />
<ClCompile Include="Src\GeckoCode.cpp" />
<ClCompile Include="Src\GeckoCodeConfig.cpp" />
<ClCompile Include="Src\HLE\HLE.cpp" />
@ -433,6 +439,13 @@
<ClInclude Include="Src\DSP\Jit\DSPJitRegCache.h" />
<ClInclude Include="Src\DSP\Jit\DSPJitUtil.h" />
<ClInclude Include="Src\DSP\LabelMap.h" />
<ClInclude Include="Src\FifoPlayer\FifoAnalyzer.h" />
<ClInclude Include="Src\FifoPlayer\FifoPlaybackAnalyzer.h" />
<ClInclude Include="Src\FifoPlayer\FifoDataFile.h" />
<ClInclude Include="Src\FifoPlayer\FifoFileStruct.h" />
<ClInclude Include="Src\FifoPlayer\FifoPlayer.h" />
<ClInclude Include="Src\FifoPlayer\FifoRecorder.h" />
<ClInclude Include="Src\FifoPlayer\FifoRecordAnalyzer.h" />
<ClInclude Include="Src\GeckoCode.h" />
<ClInclude Include="Src\GeckoCodeConfig.h" />
<ClInclude Include="Src\HLE\HLE.h" />

View File

@ -534,6 +534,25 @@
<ClCompile Include="Src\NetPlayClient.cpp">
<Filter>NetPlay</Filter>
</ClCompile>
<ClCompile Include="Src\BootManager.cpp" />
<ClCompile Include="Src\FifoPlayer\FifoDataFile.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayer\FifoPlayer.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayer\FifoRecorder.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayer\FifoPlaybackAnalyzer.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayer\FifoRecordAnalyzer.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayer\FifoAnalyzer.cpp">
<Filter>FifoPlayer</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Src\ConfigManager.h" />
@ -989,6 +1008,28 @@
<ClInclude Include="Src\NetPlay.h">
<Filter>NetPlay</Filter>
</ClInclude>
<ClInclude Include="Src\BootManager.h" />
<ClInclude Include="Src\FifoPlayer\FifoDataFile.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoFileStruct.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoPlayer.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoRecorder.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoPlaybackAnalyzer.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoRecordAnalyzer.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayer\FifoAnalyzer.h">
<Filter>FifoPlayer</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />
@ -1124,5 +1165,8 @@
<Filter Include="NetPlay">
<UniqueIdentifier>{231ceb02-1122-402a-87a8-094a9ed768c2}</UniqueIdentifier>
</Filter>
<Filter Include="FifoPlayer">
<UniqueIdentifier>{ca7d56f7-4e84-4d15-9aea-7ae6fa7d6586}</UniqueIdentifier>
</Filter>
</ItemGroup>
</Project>

View File

@ -364,6 +364,10 @@ bool CBoot::BootUp()
break;
}
case SCoreStartupParameter::BOOT_DFF:
// do nothing
break;
default:
{
PanicAlertT("Tried to load an unknown file type.");

View File

@ -35,6 +35,7 @@
#include "CPUDetect.h"
#include "CoreTiming.h"
#include "Boot/Boot.h"
#include "FifoPlayer/FifoPlayer.h"
#include "HW/Memmap.h"
#include "HW/ProcessorInterface.h"
@ -332,6 +333,33 @@ void CpuThread()
return;
}
void FifoPlayerThread()
{
const SCoreStartupParameter& _CoreParameter = SConfig::GetInstance().m_LocalCoreStartupParameter;
if (_CoreParameter.bCPUThread)
{
Common::SetCurrentThreadName("FIFO player thread");
}
else
{
g_video_backend->Video_Prepare();
Common::SetCurrentThreadName("FIFO-GPU thread");
}
if (_CoreParameter.bLockThreads)
Common::SetCurrentThreadAffinity(1); // Force to first core
// Enter CPU run loop. When we leave it - we are done.
if (FifoPlayer::GetInstance().Open(_CoreParameter.m_strFilename))
{
FifoPlayer::GetInstance().Play();
FifoPlayer::GetInstance().Close();
}
return;
}
// Initalize and create emulation thread
// Call browser: Init():g_EmuThread().
// See the BootManager.cpp file description for a complete call schedule.
@ -372,6 +400,13 @@ void EmuThread()
Host_UpdateDisasmDialog();
Host_UpdateMainFrame();
// Determine the cpu thread function
void (*cpuThreadFunc)(void);
if (_CoreParameter.m_BootType == SCoreStartupParameter::BOOT_DFF)
cpuThreadFunc = FifoPlayerThread;
else
cpuThreadFunc = CpuThread;
// ENTER THE VIDEO THREAD LOOP
if (_CoreParameter.bCPUThread)
{
@ -382,7 +417,7 @@ void EmuThread()
g_video_backend->Video_Prepare();
// Spawn the CPU thread
g_cpu_thread = std::thread(CpuThread);
g_cpu_thread = std::thread(cpuThreadFunc);
// become the GPU thread
g_video_backend->Video_EnterLoop();
@ -400,7 +435,7 @@ void EmuThread()
Common::SetCurrentThreadName("Emuthread - Idle");
// Spawn the CPU+GPU thread
g_cpu_thread = std::thread(CpuThread);
g_cpu_thread = std::thread(cpuThreadFunc);
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
{

View File

@ -29,6 +29,7 @@
#include "CoreParameter.h"
#include "ConfigManager.h"
#include "Core.h" // for bWii
#include "FifoPlayer/FifoDataFile.h"
SCoreStartupParameter::SCoreStartupParameter()
: hInstance(0),
@ -197,6 +198,21 @@ bool SCoreStartupParameter::AutoSetup(EBootBS2 _BootBS2)
m_BootType = BOOT_DOL;
bNTSC = true;
}
else if (!strcasecmp(Extension.c_str(), ".dff"))
{
bWii = true;
Region = USA_DIR;
bNTSC = true;
m_BootType = BOOT_DFF;
FifoDataFile *ddfFile = FifoDataFile::Load(m_strFilename.c_str(), true);
if (ddfFile)
{
bWii = ddfFile->GetIsWii();
delete ddfFile;
}
}
else if (DiscIO::CNANDContentManager::Access().GetNANDLoader(m_strFilename).IsValid())
{
const DiscIO::IVolume* pVolume = DiscIO::CreateVolumeFromFilename(m_strFilename.c_str());

View File

@ -153,7 +153,8 @@ struct SCoreStartupParameter
BOOT_ELF,
BOOT_DOL,
BOOT_WII_NAND,
BOOT_BS2
BOOT_BS2,
BOOT_DFF
};
EBootType m_BootType;

View File

@ -0,0 +1,235 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "FifoAnalyzer.h"
#include "Core.h"
#include "VertexLoader.h"
#include "VertexLoader_Position.h"
#include "VertexLoader_Normal.h"
#include "VertexLoader_TextCoord.h"
namespace FifoAnalyzer
{
void Init()
{
VertexLoader_Normal::Init();
VertexLoader_Position::Init();
VertexLoader_TextCoord::Init();
}
u8 ReadFifo8(u8 *&data)
{
u8 value = data[0];
data += 1;
return value;
}
u16 ReadFifo16(u8 *&data)
{
u16 value = Common::swap16(data);
data += 2;
return value;
}
u32 ReadFifo32(u8 *&data)
{
u32 value = Common::swap32(data);
data += 4;
return value;
}
void InitBPMemory(BPMemory *bpMem)
{
memset(bpMem, 0, sizeof(BPMemory));
bpMem->bpMask = 0x00FFFFFF;
}
BPCmd DecodeBPCmd(u32 value, const BPMemory &bpMem)
{
//handle the mask register
int opcode = value >> 24;
int oldval = ((u32*)&bpMem)[opcode];
int newval = (oldval & ~bpMem.bpMask) | (value & bpMem.bpMask);
int changes = (oldval ^ newval) & 0xFFFFFF;
BPCmd bp = {opcode, changes, newval};
return bp;
}
void LoadBPReg(const BPCmd &bp, BPMemory &bpMem)
{
((u32*)&bpMem)[bp.address] = bp.newvalue;
//reset the mask register
if (bp.address != 0xFE)
bpMem.bpMask = 0xFFFFFF;
}
void GetTlutLoadData(u32 &tlutAddr, u32 &memAddr, u32 &tlutXferCount, BPMemory &bpMem)
{
tlutAddr = (bpMem.tlutXferDest & 0x3FF) << 9;
tlutXferCount = (bpMem.tlutXferDest & 0x1FFC00) >> 5;
// TODO - figure out a cleaner way.
if (Core::g_CoreStartupParameter.bWii)
memAddr = bpmem.tlutXferSrc << 5;
else
memAddr = (bpmem.tlutXferSrc & 0xFFFFF) << 5;
}
void LoadCPReg(u32 subCmd, u32 value, CPMemory &cpMem)
{
switch (subCmd & 0xF0)
{
case 0x50:
cpMem.vtxDesc.Hex &= ~0x1FFFF; // keep the Upper bits
cpMem.vtxDesc.Hex |= value;
break;
case 0x60:
cpMem.vtxDesc.Hex &= 0x1FFFF; // keep the lower 17Bits
cpMem.vtxDesc.Hex |= (u64)value << 17;
break;
case 0x70:
_assert_((subCmd & 0x0F) < 8);
cpMem.vtxAttr[subCmd & 7].g0.Hex = value;
break;
case 0x80:
_assert_((subCmd & 0x0F) < 8);
cpMem.vtxAttr[subCmd & 7].g1.Hex = value;
break;
case 0x90:
_assert_((subCmd & 0x0F) < 8);
cpMem.vtxAttr[subCmd & 7].g2.Hex = value;
break;
case 0xA0:
cpMem.arrayBases[subCmd & 0xF] = value;
break;
case 0xB0:
cpMem.arrayStrides[subCmd & 0xF] = value & 0xFF;
break;
}
}
u32 CalculateVertexSize(int vatIndex, const CPMemory &cpMem)
{
u32 vertexSize = 0;
int sizes[21];
CalculateVertexElementSizes(sizes, vatIndex, cpMem);
for (int i = 0; i < 21; ++i)
vertexSize += sizes[i];
return vertexSize;
}
void CalculateVertexElementSizes(int sizes[], int vatIndex, const CPMemory &cpMem)
{
const TVtxDesc &vtxDesc = cpMem.vtxDesc;
const VAT &vtxAttr = cpMem.vtxAttr[vatIndex];
// Colors
const int colDesc[2] = {vtxDesc.Color0, vtxDesc.Color1};
const int colComp[2] = {vtxAttr.g0.Color0Comp, vtxAttr.g0.Color1Comp};
const int tcElements[8] =
{
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 int tcFormat[8] =
{
vtxAttr.g0.Tex0CoordFormat, vtxAttr.g1.Tex1CoordFormat, vtxAttr.g1.Tex2CoordFormat,
vtxAttr.g1.Tex3CoordFormat, vtxAttr.g1.Tex4CoordFormat, vtxAttr.g2.Tex5CoordFormat,
vtxAttr.g2.Tex6CoordFormat, vtxAttr.g2.Tex7CoordFormat
};
// Add position and texture matrix indices
u64 vtxDescHex = cpMem.vtxDesc.Hex;
for (int i = 0; i < 9; ++i)
{
sizes[i] = vtxDescHex & 1;
vtxDescHex >>= 1;
}
// Position
sizes[9] = VertexLoader_Position::GetSize(vtxDesc.Position, vtxAttr.g0.PosFormat, vtxAttr.g0.PosElements);
// Normals
if (vtxDesc.Normal != NOT_PRESENT)
{
sizes[10] = VertexLoader_Normal::GetSize(vtxDesc.Normal, vtxAttr.g0.NormalFormat, vtxAttr.g0.NormalElements, vtxAttr.g0.NormalIndex3);
}
else
{
sizes[10] = 0;
}
// Colors
for (int i = 0; i < 2; i++)
{
int size = 0;
switch (colDesc[i])
{
case NOT_PRESENT:
break;
case DIRECT:
switch (colComp[i])
{
case FORMAT_16B_565: size = 2; break;
case FORMAT_24B_888: size = 3; break;
case FORMAT_32B_888x: size = 4; break;
case FORMAT_16B_4444: size = 2; break;
case FORMAT_24B_6666: size = 3; break;
case FORMAT_32B_8888: size = 4; break;
default: _assert_(0); break;
}
break;
case INDEX8:
size = 1;
break;
case INDEX16:
size = 2;
break;
}
sizes[11 + i] = size;
}
// Texture coordinates
vtxDescHex = vtxDesc.Hex >> 17;
for (int i = 0; i < 8; i++)
{
sizes[13 + i] = VertexLoader_TextCoord::GetSize(vtxDescHex & 3, tcFormat[i], tcElements[i]);
vtxDescHex >>= 2;
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFOANALYZER_H
#define _FIFOANALYZER_H
#include "Common.h"
#include "BPMemory.h"
#include "CPMemory.h"
namespace FifoAnalyzer
{
void Init();
u8 ReadFifo8(u8 *&data);
u16 ReadFifo16(u8 *&data);
u32 ReadFifo32(u8 *&data);
// TODO- move to video common
void InitBPMemory(BPMemory *bpMem);
BPCmd DecodeBPCmd(u32 value, const BPMemory &bpMem);
void LoadBPReg(const BPCmd &bp, BPMemory &bpMem);
void GetTlutLoadData(u32 &tlutAddr, u32 &memAddr, u32 &tlutXferCount, BPMemory &bpMem);
struct CPMemory
{
TVtxDesc vtxDesc;
VAT vtxAttr[8];
u32 arrayBases[16];
u32 arrayStrides[16];
};
void LoadCPReg(u32 subCmd, u32 value, CPMemory &cpMem);
u32 CalculateVertexSize(int vatIndex, const CPMemory &cpMem);
void CalculateVertexElementSizes(int sizes[], int vatIndex, const CPMemory &cpMem);
}
#endif

View File

@ -0,0 +1,285 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "FifoDataFile.h"
#include "FifoFileStruct.h"
#include "FileUtil.h"
using namespace FifoFileStruct;
using namespace std;
FifoDataFile::FifoDataFile() :
m_Flags(0)
{
}
FifoDataFile::~FifoDataFile()
{
for (unsigned int frameIdx = 0; frameIdx < m_Frames.size(); ++frameIdx)
{
FifoFrameInfo &frame = m_Frames[frameIdx];
for (unsigned int i = 0; i < frame.memoryUpdates.size(); ++i)
delete []frame.memoryUpdates[i].data;
delete []frame.fifoData;
}
}
void FifoDataFile::SetIsWii(bool isWii)
{
SetFlag(FLAG_IS_WII, isWii);
}
bool FifoDataFile::GetIsWii() const
{
return GetFlag(FLAG_IS_WII);
}
void FifoDataFile::AddFrame(const FifoFrameInfo &frameInfo)
{
m_Frames.push_back(frameInfo);
}
bool FifoDataFile::Save(const char *filename)
{
File::IOFile file;
if (!file.Open(filename, "wb"))
return false;
// Add space for header
PadFile(sizeof(FileHeader), file);
// Add space for frame list
u64 frameListOffset = file.Tell();
for (unsigned int i = 0; i < m_Frames.size(); ++i)
PadFile(sizeof(FileFrameInfo), file);
u64 bpMemOffset = file.Tell();
file.WriteArray(m_BPMem, BP_MEM_SIZE);
u64 cpMemOffset = file.Tell();
file.WriteArray(m_CPMem, CP_MEM_SIZE);
u64 xfMemOffset = file.Tell();
file.WriteArray(m_XFMem, XF_MEM_SIZE);
u64 xfRegsOffset = file.Tell();
file.WriteArray(m_XFRegs, XF_REGS_SIZE);
// Write header
FileHeader header;
header.fileId = FILE_ID;
header.file_version = VERSION_NUMBER;
header.min_loader_version = MIN_LOADER_VERSION;
header.bpMemOffset = bpMemOffset;
header.bpMemSize = BP_MEM_SIZE;
header.cpMemOffset = cpMemOffset;
header.cpMemSize = CP_MEM_SIZE;
header.xfMemOffset = xfMemOffset;
header.xfMemSize = XF_MEM_SIZE;
header.xfRegsOffset = xfRegsOffset;
header.xfRegsSize = XF_REGS_SIZE;
header.frameListOffset = frameListOffset;
header.frameCount = m_Frames.size();
header.flags = m_Flags;
file.Seek(0, SEEK_SET);
file.WriteBytes(&header, sizeof(FileHeader));
// Write frames list
for (unsigned int i = 0; i < m_Frames.size(); ++i)
{
const FifoFrameInfo &srcFrame = m_Frames[i];
// Write FIFO data
file.Seek(0, SEEK_END);
u64 dataOffset = file.Tell();
file.WriteBytes(srcFrame.fifoData, srcFrame.fifoDataSize);
u64 memoryUpdatesOffset = WriteMemoryUpdates(srcFrame.memoryUpdates, file);
FileFrameInfo dstFrame;
dstFrame.fifoDataSize = srcFrame.fifoDataSize;
dstFrame.fifoDataOffset = dataOffset;
dstFrame.fifoStart = srcFrame.fifoStart;
dstFrame.fifoEnd = srcFrame.fifoEnd;
dstFrame.memoryUpdatesOffset = memoryUpdatesOffset;
dstFrame.numMemoryUpdates = srcFrame.memoryUpdates.size();
// Write frame info
u64 frameOffset = frameListOffset + (i * sizeof(FileFrameInfo));
file.Seek(frameOffset, SEEK_SET);
file.WriteBytes(&dstFrame, sizeof(FileFrameInfo));
}
if (!file.Close())
return false;
return true;
}
FifoDataFile *FifoDataFile::Load(const std::string &filename, bool flagsOnly)
{
File::IOFile file;
file.Open(filename, "rb");
if (!file)
return NULL;
FileHeader header;
file.ReadBytes(&header, sizeof(header));
if (header.fileId != FILE_ID || header.min_loader_version > VERSION_NUMBER)
{
file.Close();
return NULL;
}
FifoDataFile* dataFile = new FifoDataFile;
dataFile->m_Flags = header.flags;
if (flagsOnly)
{
file.Close();
return dataFile;
}
u32 size = std::min((u32)BP_MEM_SIZE, header.bpMemSize);
file.Seek(header.bpMemOffset, SEEK_SET);
file.ReadArray(dataFile->m_BPMem, size);
size = std::min((u32)CP_MEM_SIZE, header.cpMemSize);
file.Seek(header.cpMemOffset, SEEK_SET);
file.ReadArray(dataFile->m_CPMem, size);
size = std::min((u32)XF_MEM_SIZE, header.xfMemSize);
file.Seek(header.xfMemOffset, SEEK_SET);
file.ReadArray(dataFile->m_XFMem, size);
size = std::min((u32)XF_REGS_SIZE, header.xfRegsSize);
file.Seek(header.xfRegsOffset, SEEK_SET);
file.ReadArray(dataFile->m_XFRegs, size);
// Read frames
for (u32 i = 0; i < header.frameCount; ++i)
{
u64 frameOffset = header.frameListOffset + (i * sizeof(FileFrameInfo));
file.Seek(frameOffset, SEEK_SET);
FileFrameInfo srcFrame;
file.ReadBytes(&srcFrame, sizeof(FileFrameInfo));
FifoFrameInfo dstFrame;
dstFrame.fifoData = new u8[srcFrame.fifoDataSize];
dstFrame.fifoDataSize = srcFrame.fifoDataSize;
dstFrame.fifoStart = srcFrame.fifoStart;
dstFrame.fifoEnd = srcFrame.fifoEnd;
file.Seek(srcFrame.fifoDataOffset, SEEK_SET);
file.ReadBytes(dstFrame.fifoData, srcFrame.fifoDataSize);
ReadMemoryUpdates(srcFrame.memoryUpdatesOffset, srcFrame.numMemoryUpdates, dstFrame.memoryUpdates, file);
dataFile->AddFrame(dstFrame);
}
file.Close();
return dataFile;
}
void FifoDataFile::PadFile(u32 numBytes, File::IOFile &file)
{
FILE *handle = file.GetHandle();
for (u32 i = 0; i < numBytes; ++i)
fputc(0, handle);
}
void FifoDataFile::SetFlag(u32 flag, bool set)
{
if (set)
m_Flags |= flag;
else
m_Flags &= ~flag;
}
bool FifoDataFile::GetFlag(u32 flag) const
{
return !!(m_Flags & flag);
}
u64 FifoDataFile::WriteMemoryUpdates(const std::vector<MemoryUpdate> &memUpdates, File::IOFile &file)
{
// Add space for memory update list
u64 updateListOffset = file.Tell();
for (unsigned int i = 0; i < memUpdates.size(); ++i)
PadFile(sizeof(FileMemoryUpdate), file);
for (unsigned int i = 0; i < memUpdates.size(); ++i)
{
const MemoryUpdate &srcUpdate = memUpdates[i];
// Write memory
file.Seek(0, SEEK_END);
u64 dataOffset = file.Tell();
file.WriteBytes(srcUpdate.data, srcUpdate.size);
FileMemoryUpdate dstUpdate;
dstUpdate.address = srcUpdate.address;
dstUpdate.dataOffset = dataOffset;
dstUpdate.dataSize = srcUpdate.size;
dstUpdate.fifoPosition = srcUpdate.fifoPosition;
dstUpdate.type = srcUpdate.type;
u64 updateOffset = updateListOffset + (i * sizeof(FileMemoryUpdate));
file.Seek(updateOffset, SEEK_SET);
file.WriteBytes(&dstUpdate, sizeof(FileMemoryUpdate));
}
return updateListOffset;
}
void FifoDataFile::ReadMemoryUpdates(u64 fileOffset, u32 numUpdates, std::vector<MemoryUpdate> &memUpdates, File::IOFile &file)
{
memUpdates.resize(numUpdates);
for (u32 i = 0; i < numUpdates; ++i)
{
u64 updateOffset = fileOffset + (i * sizeof(FileMemoryUpdate));
file.Seek(updateOffset, SEEK_SET);
FileMemoryUpdate srcUpdate;
file.ReadBytes(&srcUpdate, sizeof(FileMemoryUpdate));
MemoryUpdate &dstUpdate = memUpdates[i];
dstUpdate.address = srcUpdate.address;
dstUpdate.fifoPosition = srcUpdate.fifoPosition;
dstUpdate.size = srcUpdate.dataSize;
dstUpdate.data = new u8[srcUpdate.dataSize];
dstUpdate.type = (MemoryUpdate::Type)srcUpdate.type;
file.Seek(srcUpdate.dataOffset, SEEK_SET);
file.ReadBytes(dstUpdate.data, srcUpdate.dataSize);
}
}

View File

@ -0,0 +1,112 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFODATAFILE_H_
#define _FIFODATAFILE_H_
#include "Common.h"
#include <vector>
namespace File
{
class IOFile;
}
struct MemoryUpdate
{
enum Type
{
TEXTURE_MAP = 0x01,
XF_DATA = 0x02,
VERTEX_STREAM = 0x04,
TLUT = 0x08
};
u32 fifoPosition;
u32 address;
u32 size;
u8 *data;
Type type;
};
struct FifoFrameInfo
{
u8 *fifoData;
u32 fifoDataSize;
u32 fifoStart;
u32 fifoEnd;
// Must be sorted by fifoPosition
std::vector<MemoryUpdate> memoryUpdates;
};
class FifoDataFile
{
public:
enum
{
BP_MEM_SIZE = 256,
CP_MEM_SIZE = 256,
XF_MEM_SIZE = 4096,
XF_REGS_SIZE = 96,
};
FifoDataFile();
~FifoDataFile();
void SetIsWii(bool isWii);
bool GetIsWii() const;
u32 *GetBPMem() { return m_BPMem; }
u32 *GetCPMem() { return m_CPMem; }
u32 *GetXFMem() { return m_XFMem; }
u32 *GetXFRegs() { return m_XFRegs; }
void AddFrame(const FifoFrameInfo &frameInfo);
const FifoFrameInfo &GetFrame(int frame) const { return m_Frames[frame]; }
int GetFrameCount() { return m_Frames.size(); }
bool Save(const char *filename);
static FifoDataFile *Load(const std::string &filename, bool flagsOnly);
private:
enum
{
FLAG_IS_WII = 1
};
void PadFile(u32 numBytes, File::IOFile &file);
void SetFlag(u32 flag, bool set);
bool GetFlag(u32 flag) const;
u64 WriteMemoryUpdates(const std::vector<MemoryUpdate> &memUpdates, File::IOFile &file);
static void ReadMemoryUpdates(u64 fileOffset, u32 numUpdates, std::vector<MemoryUpdate> &memUpdates, File::IOFile &file);
u32 m_BPMem[BP_MEM_SIZE];
u32 m_CPMem[CP_MEM_SIZE];
u32 m_XFMem[XF_MEM_SIZE];
u32 m_XFRegs[XF_REGS_SIZE];
u32 m_Flags;
std::vector<FifoFrameInfo> m_Frames;
};
#endif

View File

@ -0,0 +1,85 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFOFILESTRUCT_H_
#define _FIFOFILESTRUCT_H_
#include "Common.h"
namespace FifoFileStruct
{
enum
{
FILE_ID = 0x0d01f1f0,
VERSION_NUMBER = 1,
MIN_LOADER_VERSION = 1,
};
#pragma pack(push, 4)
union FileHeader
{
struct
{
u32 fileId;
u32 file_version;
u32 min_loader_version;
u64 bpMemOffset;
u32 bpMemSize;
u64 cpMemOffset;
u32 cpMemSize;
u64 xfMemOffset;
u32 xfMemSize;
u64 xfRegsOffset;
u32 xfRegsSize;
u64 frameListOffset;
u32 frameCount;
u32 flags;
};
u32 rawData[32];
};
union FileFrameInfo
{
struct
{
u64 fifoDataOffset;
u32 fifoDataSize;
u32 fifoStart;
u32 fifoEnd;
u64 memoryUpdatesOffset;
u32 numMemoryUpdates;
};
u32 rawData[16];
};
struct FileMemoryUpdate
{
u32 fifoPosition;
u32 address;
u64 dataOffset;
u32 dataSize;
u8 type;
};
#pragma pack(pop)
}
#endif

View File

@ -0,0 +1,346 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "FifoAnalyzer.h"
#include "FifoDataFile.h"
#include "FifoPlaybackAnalyzer.h"
#include "Common.h"
#include "OpcodeDecoding.h"
#include "TextureDecoder.h"
#include "VertexLoader.h"
using namespace std;
using namespace FifoAnalyzer;
// For debugging
#define LOG_FIFO_CMDS 0
struct CmdData
{
u32 size;
u32 offset;
u8 *ptr;
};
FifoPlaybackAnalyzer::FifoPlaybackAnalyzer()
{
FifoAnalyzer::Init();
}
void FifoPlaybackAnalyzer::AnalyzeFrames(FifoDataFile *file, std::vector<AnalyzedFrameInfo> &frameInfo)
{
// Load BP memory
u32 *bpMem = file->GetBPMem();
memcpy(&m_BpMem, bpMem, sizeof(BPMemory));
u32 *cpMem = file->GetCPMem();
FifoAnalyzer::LoadCPReg(0x50, cpMem[0x50], m_CpMem);
FifoAnalyzer::LoadCPReg(0x60, cpMem[0x60], m_CpMem);
for (int i = 0; i < 8; ++i)
{
FifoAnalyzer::LoadCPReg(0x70 + i, cpMem[0x70 + i], m_CpMem);
FifoAnalyzer::LoadCPReg(0x80 + i, cpMem[0x80 + i], m_CpMem);
FifoAnalyzer::LoadCPReg(0x90 + i, cpMem[0x90 + i], m_CpMem);
}
frameInfo.clear();
frameInfo.resize(file->GetFrameCount());
for (int frameIdx = 0; frameIdx < file->GetFrameCount(); ++frameIdx)
{
const FifoFrameInfo& frame = file->GetFrame(frameIdx);
AnalyzedFrameInfo& analyzed = frameInfo[frameIdx];
m_DrawingObject = false;
u32 cmdStart = 0;
u32 nextMemUpdate = 0;
// Debugging
vector<CmdData> prevCmds;
while (cmdStart < frame.fifoDataSize)
{
// Add memory updates that have occured before this point in the frame
while (nextMemUpdate < frame.memoryUpdates.size() && frame.memoryUpdates[nextMemUpdate].fifoPosition <= cmdStart)
{
AddMemoryUpdate(frame.memoryUpdates[nextMemUpdate], analyzed);
++nextMemUpdate;
}
bool wasDrawing = m_DrawingObject;
u32 cmdSize = DecodeCommand(&frame.fifoData[cmdStart]);
#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.objectStarts.clear();
analyzed.objectEnds.clear();
return;
}
if (wasDrawing != m_DrawingObject)
{
if (m_DrawingObject)
analyzed.objectStarts.push_back(cmdStart);
else
analyzed.objectEnds.push_back(cmdStart);
}
cmdStart += cmdSize;
}
if (analyzed.objectEnds.size() < analyzed.objectStarts.size())
analyzed.objectEnds.push_back(cmdStart);
}
}
void FifoPlaybackAnalyzer::AddMemoryUpdate(MemoryUpdate memUpdate, AnalyzedFrameInfo &frameInfo)
{
u32 begin = memUpdate.address;
u32 end = memUpdate.address + memUpdate.size;
// Remove portions of memUpdate that overlap with memory ranges that have been written by the GP
for (unsigned int i = 0; i < m_WrittenMemory.size(); ++i)
{
const MemoryRange &range = m_WrittenMemory[i];
if (range.begin < end &&
range.end > begin)
{
s32 preSize = range.begin - begin;
s32 postSize = end - range.end;
if (postSize > 0)
{
if (preSize > 0)
{
memUpdate.size = preSize;
AddMemoryUpdate(memUpdate, frameInfo);
}
u32 bytesToRangeEnd = range.end - memUpdate.address;
memUpdate.data += bytesToRangeEnd;
memUpdate.size = postSize;
memUpdate.address = range.end;
}
else if (preSize > 0)
{
memUpdate.size = preSize;
}
else
{
// Ignore all of memUpdate
return;
}
}
}
frameInfo.memoryUpdates.push_back(memUpdate);
}
u32 FifoPlaybackAnalyzer::DecodeCommand(u8 *data)
{
u8 *dataStart = data;
int cmd = ReadFifo8(data);
switch(cmd)
{
case GX_NOP:
case 0x44:
case GX_CMD_INVL_VC:
break;
case GX_LOAD_CP_REG:
{
m_DrawingObject = false;
u32 cmd2 = ReadFifo8(data);
u32 value = ReadFifo32(data);
FifoAnalyzer::LoadCPReg(cmd2, value, m_CpMem);
}
break;
case GX_LOAD_XF_REG:
{
m_DrawingObject = false;
u32 cmd2 = ReadFifo32(data);
u8 streamSize = ((cmd2 >> 16) & 15) + 1;
data += streamSize * 4;
}
break;
case GX_LOAD_INDX_A:
case GX_LOAD_INDX_B:
case GX_LOAD_INDX_C:
case GX_LOAD_INDX_D:
m_DrawingObject = false;
data += 4;
break;
case 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 GX_LOAD_BP_REG:
{
m_DrawingObject = false;
u32 cmd2 = ReadFifo32(data);
BPCmd bp = FifoAnalyzer::DecodeBPCmd(cmd2, m_BpMem);
FifoAnalyzer::LoadBPReg(bp, m_BpMem);
if (bp.address == BPMEM_TRIGGER_EFB_COPY)
StoreEfbCopyRegion();
}
break;
default:
if (cmd & 0x80)
{
m_DrawingObject = true;
u32 vtxAttrGroup = cmd & GX_VAT_MASK;
int vertexSize = FifoAnalyzer::CalculateVertexSize(vtxAttrGroup, m_CpMem);
u16 streamSize = ReadFifo16(data);
data += streamSize * vertexSize;
}
else
{
PanicAlert("FifoPlayer: Unknown Opcode (0x%x).\nAborting frame analysis.\n", cmd);
return 0;
}
break;
}
return data - dataStart;
}
void FifoPlaybackAnalyzer::StoreEfbCopyRegion()
{
UPE_Copy peCopy = m_BpMem.triggerEFBCopy;
u32 copyfmt = peCopy.tp_realFormat();
bool bFromZBuffer = m_BpMem.zcontrol.pixel_format == PIXELFMT_Z24;
u32 address = bpmem.copyTexDest << 5;
u32 format = copyfmt;
if (peCopy.copy_to_xfb)
{
// Fake format to calculate size correctly
format = GX_TF_IA8;
}
else if (bFromZBuffer)
{
format |= _GX_TF_ZTF;
if (copyfmt == 11)
format = GX_TF_Z16;
else if (format < GX_TF_Z8 || format > GX_TF_Z24X8)
format |= _GX_TF_CTF;
}
else
{
if (copyfmt > GX_TF_RGBA8 || (copyfmt < GX_TF_RGB565 && !peCopy.intensity_fmt))
format |= _GX_TF_CTF;
}
int width = (m_BpMem.copyTexSrcWH.x + 1) >> peCopy.half_scale;
int height = (m_BpMem.copyTexSrcWH.y + 1) >> peCopy.half_scale;
u16 blkW = TexDecoder_GetBlockWidthInTexels(format) - 1;
u16 blkH = TexDecoder_GetBlockHeightInTexels(format) - 1;
s32 expandedWidth = (width + blkW) & (~blkW);
s32 expandedHeight = (height + blkH) & (~blkH);
int sizeInBytes = TexDecoder_GetTextureSizeInBytes(expandedWidth, expandedHeight, format);
StoreWrittenRegion(address, sizeInBytes);
}
void FifoPlaybackAnalyzer::StoreWrittenRegion(u32 address, u32 size)
{
u32 end = address + size;
vector<MemoryRange>::iterator newRangeIter = m_WrittenMemory.end();
// Search for overlapping memory regions and expand them to include the new region
for (vector<MemoryRange>::iterator iter = m_WrittenMemory.begin(); iter != m_WrittenMemory.end();)
{
MemoryRange &range = *iter;
if (range.begin < end && range.end > address)
{
// range at iterator and new range overlap
if (newRangeIter == m_WrittenMemory.end())
{
// Expand range to include the written region
range.begin = std::min(address, range.begin);
range.end = std::max(end, range.end);
newRangeIter = iter;
++iter;
}
else
{
// Expand region at rangeIter to include this range
MemoryRange &used = *newRangeIter;
used.begin = std::min(used.begin, range.begin);
used.end = std::max(used.end, range.end);
// Remove this entry
iter = m_WrittenMemory.erase(iter);
}
}
else
{
++iter;
}
}
if (newRangeIter == m_WrittenMemory.end())
{
MemoryRange range;
range.begin = address;
range.end = end;
m_WrittenMemory.push_back(range);
}
}

View File

@ -0,0 +1,64 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFOPLAYBACKANALYZER_H_
#define _FIFOPLAYBACKANALYZER_H_
#include "FifoAnalyzer.h"
#include "FifoDataFile.h"
#include <string>
#include <vector>
struct AnalyzedFrameInfo
{
std::vector<u32> objectStarts;
std::vector<u32> objectEnds;
std::vector<MemoryUpdate> memoryUpdates;
};
class FifoPlaybackAnalyzer
{
public:
FifoPlaybackAnalyzer();
void AnalyzeFrames(FifoDataFile *file, std::vector<AnalyzedFrameInfo> &frameInfo);
private:
struct MemoryRange
{
u32 begin;
u32 end;
};
void AddMemoryUpdate(MemoryUpdate memUpdate, AnalyzedFrameInfo &frameInfo);
u32 DecodeCommand(u8 *data);
void LoadBP(u32 value0);
void StoreEfbCopyRegion();
void StoreWrittenRegion(u32 address, u32 size);
bool m_DrawingObject;
std::vector<MemoryRange> m_WrittenMemory;
BPMemory m_BpMem;
FifoAnalyzer::CPMemory m_CpMem;
};
#endif

View File

@ -0,0 +1,456 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "FifoDataFile.h"
#include "FifoPlayer.h"
#include "Common.h"
#include "CoreTiming.h"
#include "HW/GPFifo.h"
#include "HW/Memmap.h"
#include "HW/SystemTimers.h"
#include "PowerPC/PowerPC.h"
#include "BPMemory.h"
FifoPlayer::~FifoPlayer()
{
delete m_File;
}
bool FifoPlayer::Open(const std::string& filename)
{
Close();
m_File = FifoDataFile::Load(filename, false);
if (m_File)
{
FifoPlaybackAnalyzer analyzer;
analyzer.AnalyzeFrames(m_File, m_FrameInfo);
m_FrameRangeEnd = m_File->GetFrameCount();
}
if (m_FileLoadedCb)
m_FileLoadedCb();
return (m_File != NULL);
}
void FifoPlayer::Close()
{
delete m_File;
m_File = NULL;
m_FrameRangeStart = 0;
m_FrameRangeEnd = 0;
}
bool FifoPlayer::Play()
{
if (!m_File)
return false;
if (m_File->GetFrameCount() == 0)
return false;
m_CurrentFrame = m_FrameRangeStart;
LoadMemory();
// This loop replaces the CPU loop that occurs when a game is run
while (PowerPC::GetState() != PowerPC::CPU_POWERDOWN)
{
if (PowerPC::GetState() == PowerPC::CPU_RUNNING)
{
if (m_CurrentFrame >= m_FrameRangeEnd)
{
m_CurrentFrame = m_FrameRangeStart;
CoreTiming::downcount = 0;
CoreTiming::Advance();
}
else
{
if (m_FrameWrittenCb)
m_FrameWrittenCb();
if (m_EarlyMemoryUpdates && m_CurrentFrame == m_FrameRangeStart)
WriteAllMemoryUpdates();
WriteFrame(m_File->GetFrame(m_CurrentFrame), m_FrameInfo[m_CurrentFrame]);
++m_CurrentFrame;
}
}
}
return true;
}
u32 FifoPlayer::GetFrameObjectCount()
{
if (m_CurrentFrame < m_FrameInfo.size())
return m_FrameInfo[m_CurrentFrame].objectStarts.size();
return 0;
}
void FifoPlayer::SetFrameRangeStart(u32 start)
{
if (m_File)
{
u32 frameCount = m_File->GetFrameCount();
if (start > frameCount)
start = frameCount;
m_FrameRangeStart = start;
if (m_FrameRangeEnd < start)
m_FrameRangeEnd = start;
if (m_CurrentFrame < m_FrameRangeStart)
m_CurrentFrame = m_FrameRangeStart;
}
}
void FifoPlayer::SetFrameRangeEnd(u32 end)
{
if (m_File)
{
u32 frameCount = m_File->GetFrameCount();
if (end > frameCount)
end = frameCount;
m_FrameRangeEnd = end;
if (m_FrameRangeStart > end)
m_FrameRangeStart = end;
if (m_CurrentFrame >= m_FrameRangeEnd)
m_CurrentFrame = m_FrameRangeStart;
}
}
FifoPlayer &FifoPlayer::GetInstance()
{
static FifoPlayer instance;
return instance;
}
FifoPlayer::FifoPlayer() :
m_CurrentFrame(0),
m_FrameRangeStart(0),
m_FrameRangeEnd(0),
m_ObjectRangeStart(0),
m_ObjectRangeEnd(10000),
m_EarlyMemoryUpdates(false),
m_FileLoadedCb(NULL),
m_FrameWrittenCb(NULL),
m_File(NULL)
{
}
void FifoPlayer::WriteFrame(const FifoFrameInfo &frame, const AnalyzedFrameInfo &info)
{
// Core timing information
m_CyclesPerFrame = SystemTimers::GetTicksPerSecond() / 60;
m_ElapsedCycles = 0;
m_FrameFifoSize = frame.fifoDataSize;
// Determine start and end objects
u32 numObjects = info.objectStarts.size();
u32 drawStart = std::min(numObjects, m_ObjectRangeStart);
u32 drawEnd = std::min(numObjects - 1, m_ObjectRangeEnd);
u32 position = 0;
u32 memoryUpdate = 0;
// Skip memory updates during frame if true
if (m_EarlyMemoryUpdates)
memoryUpdate = frame.memoryUpdates.size();
if (numObjects > 0)
{
u32 objectNum = 0;
// Write fifo data skipping objects before the draw range
while (objectNum < drawStart)
{
WriteFramePart(position, info.objectStarts[objectNum], memoryUpdate, frame, info);
position = info.objectEnds[objectNum];
++objectNum;
}
// Write objects in draw range
if (objectNum < numObjects && drawStart <= drawEnd)
{
objectNum = drawEnd;
WriteFramePart(position, info.objectEnds[objectNum], memoryUpdate, frame, info);
position = info.objectEnds[objectNum];
++objectNum;
}
// Write fifo data skipping objects after the draw range
while (objectNum < numObjects)
{
WriteFramePart(position, info.objectStarts[objectNum], memoryUpdate, frame, info);
position = info.objectEnds[objectNum];
++objectNum;
}
}
// Write data after the last object
WriteFramePart(position, frame.fifoDataSize, memoryUpdate, frame, info);
FlushWGP();
}
void FifoPlayer::WriteFramePart(u32 dataStart, u32 dataEnd, u32 &nextMemUpdate, const FifoFrameInfo &frame, const AnalyzedFrameInfo &info)
{
u8 *data = frame.fifoData;
while (nextMemUpdate < frame.memoryUpdates.size() && dataStart < dataEnd)
{
const MemoryUpdate &memUpdate = info.memoryUpdates[nextMemUpdate];
if (memUpdate.fifoPosition < dataEnd)
{
if (dataStart < memUpdate.fifoPosition)
{
WriteFifo(data, dataStart, memUpdate.fifoPosition);
dataStart = memUpdate.fifoPosition;
}
WriteMemory(memUpdate);
++nextMemUpdate;
}
else
{
WriteFifo(data, dataStart, dataEnd);
dataStart = dataEnd;
}
}
if (dataStart < dataEnd)
WriteFifo(data, dataStart, dataEnd);
}
void FifoPlayer::WriteAllMemoryUpdates()
{
_assert_(m_File);
for (int frameNum = 0; frameNum < m_File->GetFrameCount(); ++frameNum)
{
const FifoFrameInfo &frame = m_File->GetFrame(frameNum);
for (unsigned int i = 0; i < frame.memoryUpdates.size(); ++i)
{
WriteMemory(frame.memoryUpdates[i]);
}
}
}
void FifoPlayer::WriteMemory(const MemoryUpdate& memUpdate)
{
u8 *mem = NULL;
if (memUpdate.address & 0x10000000)
mem = &Memory::m_pEXRAM[memUpdate.address & Memory::EXRAM_MASK];
else
mem = &Memory::m_pRAM[memUpdate.address & Memory::RAM_MASK];
memcpy(mem, memUpdate.data, memUpdate.size);
}
void FifoPlayer::WriteFifo(u8 *data, u32 start, u32 end)
{
u32 written = start;
u32 lastBurstEnd = end - 1;
// Write up to 256 bytes at a time
while (written < end)
{
u32 burstEnd = std::min(written + 255, lastBurstEnd);
while (written < burstEnd)
GPFifo::FastWrite8(data[written++]);
GPFifo::Write8(data[written++], 0);
// Advance core timing
u32 elapsedCycles = u32(((u64)written * m_CyclesPerFrame) / m_FrameFifoSize);
u32 cyclesUsed = elapsedCycles - m_ElapsedCycles;
m_ElapsedCycles = elapsedCycles;
CoreTiming::downcount -= cyclesUsed;
CoreTiming::Advance();
}
}
void FifoPlayer::SetupFifo()
{
WriteCP(0x02, 0); // disable read, BP, interrupts
WriteCP(0x04, 7); // clear overflow, underflow, metrics
const FifoFrameInfo& frame = m_File->GetFrame(m_CurrentFrame);
// Set fifo bounds
WriteCP(0x20, frame.fifoStart);
WriteCP(0x22, frame.fifoStart >> 16);
WriteCP(0x24, frame.fifoEnd);
WriteCP(0x26, frame.fifoEnd >> 16);
// Set watermarks
u32 fifoSize = frame.fifoEnd - frame.fifoStart;
WriteCP(0x28, fifoSize);
WriteCP(0x2a, fifoSize >> 16);
WriteCP(0x2c, 0);
WriteCP(0x2e, 0);
// Set R/W pointers to fifo start
WriteCP(0x30, 0);
WriteCP(0x32, 0);
WriteCP(0x34, frame.fifoStart);
WriteCP(0x36, frame.fifoStart >> 16);
WriteCP(0x38, frame.fifoStart);
WriteCP(0x3a, frame.fifoStart >> 16);
// Set fifo bounds
WritePI(12, frame.fifoStart);
WritePI(16, frame.fifoEnd);
// Set write pointer
WritePI(20, frame.fifoStart);
FlushWGP();
WritePI(20, frame.fifoStart);
WriteCP(0x02, 17); // enable read & GP link
}
void FifoPlayer::LoadMemory()
{
Memory::Clear();
SetupFifo();
u32 *regs = m_File->GetBPMem();
for (int i = 0; i < FifoDataFile::BP_MEM_SIZE; ++i)
{
if (ShouldLoadBP(i))
LoadBPReg(i, regs[i]);
}
regs = m_File->GetCPMem();
LoadCPReg(0x30, regs[0x30]);
LoadCPReg(0x40, regs[0x40]);
LoadCPReg(0x50, regs[0x50]);
LoadCPReg(0x60, regs[0x60]);
for (int i = 0; i < 8; ++i)
{
LoadCPReg(0x70 + i, regs[0x70 + i]);
LoadCPReg(0x80 + i, regs[0x80 + i]);
LoadCPReg(0x90 + i, regs[0x90 + i]);
}
for (int i = 0; i < 16; ++i)
{
LoadCPReg(0xa0 + i, regs[0xa0 + i]);
LoadCPReg(0xb0 + i, regs[0xb0 + i]);
}
regs = m_File->GetXFMem();
for (int i = 0; i < FifoDataFile::XF_MEM_SIZE; i += 16)
LoadXFMem16(i, &regs[i]);
regs = m_File->GetXFRegs();
for (int i = 0; i < FifoDataFile::XF_REGS_SIZE; ++i)
LoadXFReg(i, regs[i]);
FlushWGP();
}
void FifoPlayer::WriteCP(u32 address, u16 value)
{
Memory::Write_U16(value, 0xCC000000 | address);
}
void FifoPlayer::WritePI(u32 address, u32 value)
{
Memory::Write_U32(value, 0xCC003000 | address);
}
void FifoPlayer::FlushWGP()
{
// Send 31 0s through the WGP
for (int i = 0; i < 7; ++i)
GPFifo::Write32(0, 0);
GPFifo::Write16(0, 0);
GPFifo::Write8(0, 0);
GPFifo::ResetGatherPipe();
}
void FifoPlayer::LoadBPReg(u8 reg, u32 value)
{
GPFifo::Write8(0x61, 0); // load BP reg
u32 cmd = (reg << 24) & 0xff000000;
cmd |= (value & 0x00ffffff);
GPFifo::Write32(cmd, 0);
}
void FifoPlayer::LoadCPReg(u8 reg, u32 value)
{
GPFifo::Write8(0x08, 0); // load CP reg
GPFifo::Write8(reg, 0);
GPFifo::Write32(value, 0);
}
void FifoPlayer::LoadXFReg(u16 reg, u32 value)
{
GPFifo::Write8(0x10, 0); // load XF reg
GPFifo::Write32((reg & 0x0fff) | 0x1000, 0); // load 4 bytes into reg
GPFifo::Write32(value, 0);
}
void FifoPlayer::LoadXFMem16(u16 address, u32 *data)
{
// Loads 16 * 4 bytes in xf memory starting at address
GPFifo::Write8(0x10, 0); // load XF reg
GPFifo::Write32(0x000f0000 | (address & 0xffff), 0); // load 16 * 4 bytes into address
for (int i = 0; i < 16; ++i)
GPFifo::Write32(data[i], 0);
}
bool FifoPlayer::ShouldLoadBP(u8 address)
{
switch (address)
{
case BPMEM_SETDRAWDONE:
case BPMEM_PE_TOKEN_ID:
case BPMEM_PE_TOKEN_INT_ID:
case BPMEM_TRIGGER_EFB_COPY:
case BPMEM_LOADTLUT1:
case BPMEM_PERF1:
return false;
}
return true;
}

View File

@ -0,0 +1,121 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFOPLAYER_H_
#define _FIFOPLAYER_H_
#include "FifoPlaybackAnalyzer.h"
#include <string>
#include <vector>
class FifoDataFile;
struct MemoryUpdate;
struct AnalyzedFrameInfo;
class FifoPlayer
{
public:
typedef void(*CallbackFunc)(void);
~FifoPlayer();
bool Open(const std::string& filename);
void Close();
// Play is controlled by the state of PowerPC
bool Play();
FifoDataFile *GetFile() { return m_File; }
u32 GetFrameObjectCount();
u32 GetCurrentFrameNum() { return m_CurrentFrame; }
// Frame range
u32 GetFrameRangeStart() { return m_FrameRangeStart; }
void SetFrameRangeStart(u32 start);
u32 GetFrameRangeEnd() { return m_FrameRangeEnd; }
void SetFrameRangeEnd(u32 end);
// Object range
u32 GetObjectRangeStart() { return m_ObjectRangeStart; }
void SetObjectRangeStart(u32 start) { m_ObjectRangeStart = start; }
u32 GetObjectRangeEnd() { return m_ObjectRangeEnd; }
void SetObjectRangeEnd(u32 end) { m_ObjectRangeEnd = end; }
// If enabled then all memory updates happen at once before the first frame
// Default is disabled
void SetEarlyMemoryUpdates(bool enabled) { m_EarlyMemoryUpdates = enabled; }
// Callbacks
void SetFileLoadedCallback(CallbackFunc callback) { m_FileLoadedCb = callback; }
void SetFrameWrittenCallback(CallbackFunc callback) { m_FrameWrittenCb = callback; }
static FifoPlayer &GetInstance();
private:
FifoPlayer();
void WriteFrame(const FifoFrameInfo &frame, const AnalyzedFrameInfo &info);
void WriteFramePart(u32 dataStart, u32 dataEnd, u32 &nextMemUpdate, const FifoFrameInfo &frame, const AnalyzedFrameInfo &info);
void WriteAllMemoryUpdates();
void WriteMemory(const MemoryUpdate &memUpdate);
// writes a range of data to the fifo
// start and end must be relative to frame's fifo data so elapsed cycles are figured correctly
void WriteFifo(u8 *data, u32 start, u32 end);
void SetupFifo();
void LoadMemory();
void WriteCP(u32 address, u16 value);
void WritePI(u32 address, u32 value);
void FlushWGP();
void LoadBPReg(u8 reg, u32 value);
void LoadCPReg(u8 reg, u32 value);
void LoadXFReg(u16 reg, u32 value);
void LoadXFMem16(u16 address, u32 *data);
bool ShouldLoadBP(u8 address);
u32 m_CurrentFrame;
u32 m_FrameRangeStart;
u32 m_FrameRangeEnd;
u32 m_ObjectRangeStart;
u32 m_ObjectRangeEnd;
bool m_EarlyMemoryUpdates;
u64 m_CyclesPerFrame;
u32 m_ElapsedCycles;
u32 m_FrameFifoSize;
CallbackFunc m_FileLoadedCb;
CallbackFunc m_FrameWrittenCb;
FifoDataFile *m_File;
std::vector<AnalyzedFrameInfo> m_FrameInfo;
};
#endif

View File

@ -0,0 +1,305 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "FifoAnalyzer.h"
#include "FifoRecordAnalyzer.h"
#include "FifoRecorder.h"
#include "Core.h"
#include "HW/Memmap.h"
#include "OpcodeDecoding.h"
#include "TextureDecoder.h"
using namespace FifoAnalyzer;
FifoRecordAnalyzer::FifoRecordAnalyzer() :
m_DrawingObject(false),
m_BpMem(NULL)
{
}
void FifoRecordAnalyzer::Initialize(u32 *bpMem, u32 *cpMem)
{
m_DrawingObject = false;
m_BpMem = (BPMemory*)bpMem;
FifoAnalyzer::LoadCPReg(0x50, *(cpMem + 0x50), m_CpMem);
FifoAnalyzer::LoadCPReg(0x60, *(cpMem + 0x60), m_CpMem);
for (int i = 0; i < 8; ++i)
FifoAnalyzer::LoadCPReg(0x70 + i, *(cpMem + 0x70 + i), m_CpMem);
memcpy(m_CpMem.arrayBases, cpMem + 0xA0, 16 * 4);
memcpy(m_CpMem.arrayStrides, cpMem + 0xB0, 16 * 4);
}
void FifoRecordAnalyzer::AnalyzeGPCommand(u8 *data)
{
DecodeOpcode(data);
}
void FifoRecordAnalyzer::DecodeOpcode(u8 *data)
{
int cmd = ReadFifo8(data);
switch (cmd)
{
case GX_NOP:
case 0x44:
case GX_CMD_INVL_VC:
break;
case GX_LOAD_CP_REG:
{
m_DrawingObject = false;
u32 cmd2 = ReadFifo8(data);
u32 value = ReadFifo32(data);
FifoAnalyzer::LoadCPReg(cmd2, value, m_CpMem);
}
break;
case GX_LOAD_XF_REG:
m_DrawingObject = false;
break;
case GX_LOAD_INDX_A:
m_DrawingObject = false;
ProcessLoadIndexedXf(ReadFifo32(data), 0xc);
break;
case GX_LOAD_INDX_B:
m_DrawingObject = false;
ProcessLoadIndexedXf(ReadFifo32(data), 0xd);
break;
case GX_LOAD_INDX_C:
m_DrawingObject = false;
ProcessLoadIndexedXf(ReadFifo32(data), 0xe);
break;
case GX_LOAD_INDX_D:
m_DrawingObject = false;
ProcessLoadIndexedXf(ReadFifo32(data), 0xf);
break;
case 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);
}
break;
case GX_LOAD_BP_REG:
{
m_DrawingObject = false;
u32 cmd2 = ReadFifo32(data);
BPCmd bp = FifoAnalyzer::DecodeBPCmd(cmd2, *m_BpMem);
if (bp.address == BPMEM_LOADTLUT1)
ProcessLoadTlut1();
}
break;
default:
if (cmd & 0x80)
{
if (!m_DrawingObject)
{
m_DrawingObject = true;
ProcessTexMaps();
}
ProcessVertexArrays(data, cmd & GX_VAT_MASK);
}
else
{
PanicAlert("FifoRecordAnalyzer: Unknown Opcode (0x%x).\n", cmd);
}
}
}
void FifoRecordAnalyzer::ProcessLoadTlut1()
{
u32 tlutXferCount;
u32 tlutMemAddr;
u32 memAddr;
GetTlutLoadData(tlutMemAddr, memAddr, tlutXferCount, *m_BpMem);
FifoRecorder::GetInstance().WriteMemory(memAddr, tlutXferCount, MemoryUpdate::TLUT);
}
void FifoRecordAnalyzer::ProcessLoadIndexedXf(u32 val, int array)
{
int index = val >> 16;
int size = ((val >> 12) & 0xF) + 1;
u32 address = m_CpMem.arrayBases[array] + m_CpMem.arrayStrides[array] * index;
FifoRecorder::GetInstance().WriteMemory(address, size * 4, MemoryUpdate::XF_DATA);
}
void FifoRecordAnalyzer::ProcessVertexArrays(u8 *data, u8 vtxAttrGroup)
{
int sizes[21];
FifoAnalyzer::CalculateVertexElementSizes(sizes, vtxAttrGroup, m_CpMem);
// Determine offset of each element from start of vertex data
int offsets[12];
int offset = 0;
for (int i = 0; i < 12; ++i)
{
offsets[i] = offset;
offset += sizes[i + 9];
}
int vertexSize = offset;
int numVertices = ReadFifo16(data);
if (numVertices > 0)
{
for (int i = 0; i < 12; ++i)
{
WriteVertexArray(i, data + offsets[i], vertexSize, numVertices);
}
}
}
void FifoRecordAnalyzer::WriteVertexArray(int arrayIndex, u8 *vertexData, int vertexSize, int numVertices)
{
// Skip if not indexed array
int arrayType = (m_CpMem.vtxDesc.Hex >> (9 + (arrayIndex * 2))) & 3;
if (arrayType < 2)
return;
int maxIndex = 0;
// Determine min and max indices
if (arrayType == 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 = m_CpMem.arrayBases[arrayIndex];
u32 arraySize = m_CpMem.arrayStrides[arrayIndex] * (maxIndex + 1);
FifoRecorder::GetInstance().WriteMemory(arrayStart, arraySize, MemoryUpdate::VERTEX_STREAM);
}
void FifoRecordAnalyzer::ProcessTexMaps()
{
u32 writtenTexMaps = 0;
// Texture maps used in TEV indirect stages
for (u32 i = 0; i < m_BpMem->genMode.numindstages; ++i)
{
u32 texMap = m_BpMem->tevindref.getTexMap(i);
WriteTexMapMemory(texMap, writtenTexMaps);
}
// Texture maps used in TEV direct stages
for (u32 i = 0; i <= m_BpMem->genMode.numtevstages; ++i)
{
int stageNum2 = i >> 1;
int stageOdd = i & 1;
TwoTevStageOrders &order = m_BpMem->tevorders[stageNum2];
int texMap = order.getTexMap(stageOdd);
if (order.getEnable(stageOdd))
WriteTexMapMemory(texMap, writtenTexMaps);
}
}
void FifoRecordAnalyzer::WriteTexMapMemory(int texMap, u32 &writtenTexMaps)
{
// Avoid rechecking the same texture map
u32 texMapMask = 1 << texMap;
if (writtenTexMaps & texMapMask)
return;
writtenTexMaps |= texMapMask;
FourTexUnits& texUnit = m_BpMem->tex[(texMap >> 2) & 1];
u8 subTexmap = texMap & 3;
TexImage0& ti0 = texUnit.texImage0[subTexmap];
u32 width = ti0.width + 1;
u32 height = ti0.height + 1;
u32 imageBase = texUnit.texImage3[subTexmap].image_base << 5;
u32 fmtWidth = TexDecoder_GetBlockWidthInTexels(ti0.format) - 1;
u32 fmtHeight = TexDecoder_GetBlockHeightInTexels(ti0.format) - 1;
int fmtDepth = TexDecoder_GetTexelSizeInNibbles(ti0.format);
// Round width and height up to the next block
width = (width + fmtWidth) & (~fmtWidth);
height = (height + fmtHeight) & (~fmtHeight);
u32 textureSize = (width * height * fmtDepth) / 2;
// TODO: mip maps
int mip = texUnit.texMode1[subTexmap].max_lod;
if ((texUnit.texMode0[subTexmap].min_filter & 3) == 0)
mip = 0;
while (mip)
{
width >>= 1;
height >>= 1;
width = max(width, fmtWidth);
height = max(height, fmtHeight);
u32 size = (width * height * fmtDepth) >> 1;
textureSize += size;
mip--;
}
FifoRecorder::GetInstance().WriteMemory(imageBase, textureSize, MemoryUpdate::TEXTURE_MAP);
}

View File

@ -0,0 +1,56 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFORECORDANALYZER_H_
#define _FIFORECORDANALYZER_H_
#include "FifoAnalyzer.h"
#include "Common.h"
#include "BPMemory.h"
class FifoRecordAnalyzer
{
public:
FifoRecordAnalyzer();
// Must call this before analyzing GP commands
void Initialize(u32 *bpMem, u32 *cpMem);
// Assumes data contains all information for the command
// Calls FifoRecorder::WriteMemory
void AnalyzeGPCommand(u8 *data);
private:
void DecodeOpcode(u8 *data);
void ProcessLoadTlut1();
void ProcessLoadIndexedXf(u32 val, int array);
void ProcessVertexArrays(u8 *data, u8 vtxAttrGroup);
void ProcessTexMaps();
void WriteVertexArray(int arrayIndex, u8 *vertexData, int vertexSize, int numVertices);
void WriteTexMapMemory(int texMap, u32 &writtenTexMaps);
bool m_DrawingObject;
BPMemory *m_BpMem;
FifoAnalyzer::CPMemory m_CpMem;
};
#endif

View File

@ -0,0 +1,220 @@
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#include "ConfigManager.h"
#include "Core.h"
#include "FifoRecorder.h"
#include "Thread.h"
#include "HW/Memmap.h"
static FifoRecorder instance;
static std::recursive_mutex sMutex;
using namespace std;
FifoRecorder::FifoRecorder() :
m_IsRecording(false),
m_WasRecording(false),
m_RequestedRecordingEnd(false),
m_RecordFramesRemaining(0),
m_FinishedCb(NULL),
m_File(NULL),
m_SkipNextData(true),
m_SkipFutureData(true),
m_FrameEnded(false),
m_Ram(NULL),
m_ExRam(NULL)
{
}
FifoRecorder::~FifoRecorder()
{
m_IsRecording = false;
}
void FifoRecorder::StartRecording(s32 numFrames, CallbackFunc finishedCb)
{
sMutex.lock();
delete m_File;
delete []m_Ram;
delete []m_ExRam;
m_File = new FifoDataFile;
m_Ram = new u8[Memory::RAM_SIZE];
m_ExRam = new u8[Memory::EXRAM_SIZE];
memset(m_Ram, 0, Memory::RAM_SIZE);
memset(m_ExRam, 0, Memory::EXRAM_SIZE);
m_File->SetIsWii(SConfig::GetInstance().m_LocalCoreStartupParameter.bWii);
if (!m_IsRecording)
{
m_WasRecording = false;
m_IsRecording = true;
m_RecordFramesRemaining = numFrames;
}
m_RequestedRecordingEnd = false;
m_FinishedCb = finishedCb;
sMutex.unlock();
}
void FifoRecorder::StopRecording()
{
m_RequestedRecordingEnd = true;
}
void FifoRecorder::WriteGPCommand(u8 *data, u32 size)
{
if (!m_SkipNextData)
{
m_RecordAnalyzer.AnalyzeGPCommand(data);
// Copy data to buffer
u32 currentSize = m_FifoData.size();
m_FifoData.resize(currentSize + size);
memcpy(&m_FifoData[currentSize], data, size);
}
if (m_FrameEnded && m_FifoData.size() > 0)
{
u32 dataSize = m_FifoData.size();
m_CurrentFrame.fifoDataSize = dataSize;
m_CurrentFrame.fifoData = new u8[dataSize];
memcpy(m_CurrentFrame.fifoData, &m_FifoData[0], dataSize);
sMutex.lock();
// Copy frame to file
// The file will be responsible for freeing the memory allocated for each frame's fifoData
m_File->AddFrame(m_CurrentFrame);
if (m_FinishedCb && m_RequestedRecordingEnd)
m_FinishedCb();
sMutex.unlock();
m_CurrentFrame.memoryUpdates.clear();
m_FifoData.clear();
m_FrameEnded = false;
}
m_SkipNextData = m_SkipFutureData;
}
void FifoRecorder::WriteMemory(u32 address, u32 size, MemoryUpdate::Type type)
{
u8 *curData;
u8 *newData;
if (address & 0x10000000)
{
curData = &m_ExRam[address & Memory::EXRAM_MASK];
newData = &Memory::m_pEXRAM[address & Memory::EXRAM_MASK];
}
else
{
curData = &m_Ram[address & Memory::RAM_MASK];
newData = &Memory::m_pRAM[address & Memory::RAM_MASK];
}
if (memcmp(curData, newData, size) != 0)
{
// Update current memory
memcpy(curData, newData, size);
// Record memory update
MemoryUpdate memUpdate;
memUpdate.address = address;
memUpdate.fifoPosition = m_FifoData.size();
memUpdate.size = size;
memUpdate.type = type;
memUpdate.data = new u8[size];
memcpy(memUpdate.data, newData, size);
m_CurrentFrame.memoryUpdates.push_back(memUpdate);
}
}
void FifoRecorder::EndFrame(u32 fifoStart, u32 fifoEnd)
{
// m_IsRecording is assumed to be true at this point, otherwise this function would not be called
sMutex.lock();
m_FrameEnded = true;
m_CurrentFrame.fifoStart = fifoStart;
m_CurrentFrame.fifoEnd = fifoEnd;
if (m_WasRecording)
{
// If recording a fixed number of frames then check if the end of the recording was reached
if (m_RecordFramesRemaining > 0)
{
--m_RecordFramesRemaining;
if (m_RecordFramesRemaining == 0)
m_RequestedRecordingEnd = true;
}
}
else
{
m_WasRecording = true;
// Skip the first data which will be the frame copy command
m_SkipNextData = true;
m_SkipFutureData = false;
m_FrameEnded = false;
m_FifoData.reserve(1024 * 1024 * 4);
m_FifoData.clear();
}
if (m_RequestedRecordingEnd)
{
// Skip data after the next time WriteFifoData is called
m_SkipFutureData = true;
// Signal video backend that it should not call this function when the next frame ends
m_IsRecording = false;
}
sMutex.unlock();
}
void FifoRecorder::SetVideoMemory(u32 *bpMem, u32 *cpMem, u32 *xfMem, u32 *xfRegs, u32 xfRegsSize)
{
sMutex.lock();
if (m_File)
{
memcpy(m_File->GetBPMem(), bpMem, FifoDataFile::BP_MEM_SIZE * 4);
memcpy(m_File->GetCPMem(), cpMem, FifoDataFile::CP_MEM_SIZE * 4);
memcpy(m_File->GetXFMem(), xfMem, FifoDataFile::XF_MEM_SIZE * 4);
u32 xfRegsCopySize = std::min((u32)FifoDataFile::XF_REGS_SIZE, xfRegsSize);
memcpy(m_File->GetXFRegs(), xfRegs, xfRegsCopySize * 4);
}
m_RecordAnalyzer.Initialize(bpMem, cpMem);
sMutex.unlock();
}
FifoRecorder &FifoRecorder::GetInstance()
{
return instance;
}

View File

@ -0,0 +1,80 @@
// Copyright (C) 2003 Dolphin Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/
#ifndef _FIFORECORDER_H_
#define _FIFORECORDER_H_
#include "FifoDataFile.h"
#include "FifoRecordAnalyzer.h"
class FifoRecorder
{
public:
typedef void(*CallbackFunc)(void);
FifoRecorder();
~FifoRecorder();
void StartRecording(s32 numFrames, CallbackFunc finishedCb);
void StopRecording();
FifoDataFile *GetRecordedFile() { return m_File; }
// Called from video thread
// Must write one full GP command at a time
void WriteGPCommand(u8 *data, u32 size);
void WriteMemory(u32 address, u32 size, MemoryUpdate::Type type);
void EndFrame(u32 fifoStart, u32 fifoEnd);
// This function must be called before writting GP commands
// bpMem must point to the actual bp mem array used by the plugin because it will be read as fifo data is recorded
void SetVideoMemory(u32 *bpMem, u32 *cpMem, u32 *xfMem, u32 *xfRegs, u32 xfRegsSize);
// Checked once per frame prior to callng EndFrame()
bool IsRecording() { return m_IsRecording; }
static FifoRecorder &GetInstance();
private:
// Accessed from both GUI and video threads
// True if video thread should send data
volatile bool m_IsRecording;
// True if m_IsRecording was true during last frame
volatile bool m_WasRecording;
volatile bool m_RequestedRecordingEnd;
volatile s32 m_RecordFramesRemaining;
volatile CallbackFunc m_FinishedCb;
FifoDataFile *volatile m_File;
// Accessed only from video thread
bool m_SkipNextData;
bool m_SkipFutureData;
bool m_FrameEnded;
FifoFrameInfo m_CurrentFrame;
std::vector<u8> m_FifoData;
u8 *m_Ram;
u8 *m_ExRam;
FifoRecordAnalyzer m_RecordAnalyzer;
};
#endif