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

View File

@ -33,6 +33,7 @@ if(wxWidgets_FOUND)
Src/Debugger/MemoryWindow.cpp
Src/Debugger/RegisterView.cpp
Src/Debugger/RegisterWindow.cpp
Src/FifoPlayerDlg.cpp
Src/Frame.cpp
Src/FrameAui.cpp
Src/FrameTools.cpp

View File

@ -232,6 +232,7 @@ xcopy "$(SolutionDir)..\Externals\SDL\$(PlatformName)\*.dll" "$(TargetDir)" /e /
<ClCompile Include="Src\Debugger\DebuggerUIUtil.cpp" />
<ClCompile Include="Src\Debugger\DSPDebugWindow.cpp" />
<ClCompile Include="Src\Debugger\DSPRegisterView.cpp" />
<ClCompile Include="Src\FifoPlayerDlg.cpp" />
<ClCompile Include="Src\Frame.cpp" />
<ClCompile Include="Src\FrameAui.cpp" />
<ClCompile Include="Src\FrameTools.cpp" />
@ -294,6 +295,7 @@ xcopy "$(SolutionDir)..\Externals\SDL\$(PlatformName)\*.dll" "$(TargetDir)" /e /
<ClInclude Include="Src\Debugger\DebuggerUIUtil.h" />
<ClInclude Include="Src\Debugger\DSPDebugWindow.h" />
<ClInclude Include="Src\Debugger\DSPRegisterView.h" />
<ClInclude Include="Src\FifoPlayerDlg.h" />
<ClInclude Include="Src\Frame.h" />
<ClInclude Include="Src\GameListCtrl.h" />
<ClInclude Include="Src\GeckoCodeDiag.h" />

View File

@ -129,6 +129,9 @@
<ClCompile Include="Src\LogConfigWindow.cpp">
<Filter>GUI</Filter>
</ClCompile>
<ClCompile Include="Src\FifoPlayerDlg.cpp">
<Filter>GUI</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="Src\Main.h" />
@ -252,6 +255,9 @@
<ClInclude Include="Src\LogConfigWindow.h">
<Filter>GUI</Filter>
</ClInclude>
<ClInclude Include="Src\FifoPlayerDlg.h">
<Filter>GUI</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="Src\SConscript" />
@ -291,4 +297,4 @@
<Filter>Resources</Filter>
</ResourceCompile>
</ItemGroup>
</Project>
</Project>

View File

@ -0,0 +1,511 @@
// 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 "Common.h"
#include "FifoPlayerDlg.h"
#include "FileUtil.h"
#include "Thread.h"
#include "FifoPlayer/FifoPlayer.h"
#include "FifoPlayer/FifoRecorder.h"
#include <wx/spinctrl.h>
DECLARE_EVENT_TYPE(RECORDING_FINISHED_EVENT, -1)
DEFINE_EVENT_TYPE(RECORDING_FINISHED_EVENT)
DECLARE_EVENT_TYPE(FRAME_WRITTEN_EVENT, -1)
DEFINE_EVENT_TYPE(FRAME_WRITTEN_EVENT)
using namespace std;
std::recursive_mutex sMutex;
wxEvtHandler *volatile FifoPlayerDlg::m_EvtHandler = NULL;
FifoPlayerDlg::FifoPlayerDlg(wxWindow * const parent) :
wxDialog(parent, wxID_ANY, _("FIFO Player"), wxDefaultPosition, wxDefaultSize),
m_FramesToRecord(1)
{
CreateGUIControls();
sMutex.lock();
m_EvtHandler = GetEventHandler();
sMutex.unlock();
FifoPlayer::GetInstance().SetFileLoadedCallback(FileLoaded);
FifoPlayer::GetInstance().SetFrameWrittenCallback(FrameWritten);
}
FifoPlayerDlg::~FifoPlayerDlg()
{
Disconnect(RECORDING_FINISHED_EVENT, wxCommandEventHandler(FifoPlayerDlg::OnRecordingFinished), NULL, this);
Disconnect(FRAME_WRITTEN_EVENT, wxCommandEventHandler(FifoPlayerDlg::OnFrameWritten), NULL, this);
// Disconnect Events
this->Disconnect( wxEVT_PAINT, wxPaintEventHandler( FifoPlayerDlg::OnPaint ), NULL, this );
m_FrameFromCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnFrameFrom ), NULL, this );
m_FrameToCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnFrameTo ), NULL, this );
m_ObjectFromCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnObjectFrom ), NULL, this );
m_ObjectToCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnObjectTo ), NULL, this );
m_EarlyMemoryUpdates->Disconnect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnCheckEarlyMemoryUpdates ), NULL, this );
m_RecordStop->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnRecordStop ), NULL, this );
m_Save->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnSaveFile ), NULL, this );
m_FramesToRecordCtrl->Disconnect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnNumFramesToRecord ), NULL, this );
m_Close->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnCloseClick ), NULL, this );
FifoPlayer::GetInstance().SetFrameWrittenCallback(NULL);
sMutex.lock();
m_EvtHandler = NULL;
sMutex.unlock();
}
void FifoPlayerDlg::CreateGUIControls()
{
SetSizeHints( wxDefaultSize, wxDefaultSize );
wxBoxSizer* sMain;
sMain = new wxBoxSizer( wxVERTICAL );
m_Notebook = new wxNotebook( this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0 );
m_PlayPage = new wxPanel( m_Notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* sPlayPage;
sPlayPage = new wxBoxSizer( wxVERTICAL );
wxStaticBoxSizer* sPlayInfo;
sPlayInfo = new wxStaticBoxSizer( new wxStaticBox( m_PlayPage, wxID_ANY, _("File Info") ), wxVERTICAL );
m_NumFramesLabel = new wxStaticText( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_NumFramesLabel->Wrap( -1 );
sPlayInfo->Add( m_NumFramesLabel, 0, wxALL, 5 );
m_CurrentFrameLabel = new wxStaticText( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_CurrentFrameLabel->Wrap( -1 );
sPlayInfo->Add( m_CurrentFrameLabel, 0, wxALL, 5 );
m_NumObjectsLabel = new wxStaticText( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_NumObjectsLabel->Wrap( -1 );
sPlayInfo->Add( m_NumObjectsLabel, 0, wxALL, 5 );
sPlayPage->Add( sPlayInfo, 1, wxEXPAND, 5 );
wxStaticBoxSizer* sFrameRange;
sFrameRange = new wxStaticBoxSizer( new wxStaticBox( m_PlayPage, wxID_ANY, _("Frame Range") ), wxHORIZONTAL );
m_FrameFromLabel = new wxStaticText( m_PlayPage, wxID_ANY, _("From"), wxDefaultPosition, wxDefaultSize, 0 );
m_FrameFromLabel->Wrap( -1 );
sFrameRange->Add( m_FrameFromLabel, 0, wxALL, 5 );
m_FrameFromCtrl = new wxSpinCtrl( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10, 0 );
sFrameRange->Add( m_FrameFromCtrl, 0, wxALL, 5 );
m_FrameToLabel = new wxStaticText( m_PlayPage, wxID_ANY, _("To"), wxDefaultPosition, wxDefaultSize, 0 );
m_FrameToLabel->Wrap( -1 );
sFrameRange->Add( m_FrameToLabel, 0, wxALL, 5 );
m_FrameToCtrl = new wxSpinCtrl( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize( -1,-1 ), wxSP_ARROW_KEYS, 0, 10, 0 );
sFrameRange->Add( m_FrameToCtrl, 0, wxALL, 5 );
sPlayPage->Add( sFrameRange, 0, wxEXPAND, 5 );
wxStaticBoxSizer* sObjectRange;
sObjectRange = new wxStaticBoxSizer( new wxStaticBox( m_PlayPage, wxID_ANY, _("Object Range") ), wxHORIZONTAL );
m_ObjectFromLabel = new wxStaticText( m_PlayPage, wxID_ANY, _("From"), wxDefaultPosition, wxDefaultSize, 0 );
m_ObjectFromLabel->Wrap( -1 );
sObjectRange->Add( m_ObjectFromLabel, 0, wxALL, 5 );
m_ObjectFromCtrl = new wxSpinCtrl( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10000, 0 );
sObjectRange->Add( m_ObjectFromCtrl, 0, wxALL, 5 );
m_ObjectToLabel = new wxStaticText( m_PlayPage, wxID_ANY, wxT("To"), wxDefaultPosition, wxDefaultSize, 0 );
m_ObjectToLabel->Wrap( -1 );
sObjectRange->Add( m_ObjectToLabel, 0, wxALL, 5 );
m_ObjectToCtrl = new wxSpinCtrl( m_PlayPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10000, 0 );
sObjectRange->Add( m_ObjectToCtrl, 0, wxALL, 5 );
sPlayPage->Add( sObjectRange, 0, wxEXPAND, 5 );
wxStaticBoxSizer* sPlayOptions;
sPlayOptions = new wxStaticBoxSizer( new wxStaticBox( m_PlayPage, wxID_ANY, _("Playback Options") ), wxVERTICAL );
m_EarlyMemoryUpdates = new wxCheckBox( m_PlayPage, wxID_ANY, _("Early Memory Updates"), wxDefaultPosition, wxDefaultSize, 0 );
sPlayOptions->Add( m_EarlyMemoryUpdates, 0, wxALL, 5 );
sPlayPage->Add( sPlayOptions, 0, wxEXPAND, 5 );
m_PlayPage->SetSizer( sPlayPage );
m_PlayPage->Layout();
sPlayPage->Fit( m_PlayPage );
m_Notebook->AddPage( m_PlayPage, wxT("Play"), true );
m_RecordPage = new wxPanel( m_Notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
wxBoxSizer* sRecordPage;
sRecordPage = new wxBoxSizer( wxVERTICAL );
wxStaticBoxSizer* sRecordInfo;
sRecordInfo = new wxStaticBoxSizer( new wxStaticBox( m_RecordPage, wxID_ANY, _("Recording Info") ), wxVERTICAL );
m_RecordingFifoSizeLabel = new wxStaticText( m_RecordPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_RecordingFifoSizeLabel->Wrap( -1 );
sRecordInfo->Add( m_RecordingFifoSizeLabel, 0, wxALL, 5 );
m_RecordingMemSizeLabel = new wxStaticText( m_RecordPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_RecordingMemSizeLabel->Wrap( -1 );
sRecordInfo->Add( m_RecordingMemSizeLabel, 0, wxALL, 5 );
m_RecordingFramesLabel = new wxStaticText( m_RecordPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_RecordingFramesLabel->Wrap( -1 );
sRecordInfo->Add( m_RecordingFramesLabel, 0, wxALL, 5 );
sRecordPage->Add( sRecordInfo, 0, wxEXPAND, 5 );
wxBoxSizer* sRecordButtons;
sRecordButtons = new wxBoxSizer( wxHORIZONTAL );
m_RecordStop = new wxButton( m_RecordPage, wxID_ANY, _("Record"), wxDefaultPosition, wxDefaultSize, 0 );
sRecordButtons->Add( m_RecordStop, 0, wxALL, 5 );
m_Save = new wxButton( m_RecordPage, wxID_ANY, _("Save"), wxDefaultPosition, wxDefaultSize, 0 );
sRecordButtons->Add( m_Save, 0, wxALL, 5 );
sRecordPage->Add( sRecordButtons, 0, wxEXPAND, 5 );
wxStaticBoxSizer* sRecordingOptions;
sRecordingOptions = new wxStaticBoxSizer( new wxStaticBox( m_RecordPage, wxID_ANY, _("Recording Options") ), wxHORIZONTAL );
m_FramesToRecordLabel = new wxStaticText( m_RecordPage, wxID_ANY, _("Frames To Record"), wxDefaultPosition, wxDefaultSize, 0 );
m_FramesToRecordLabel->Wrap( -1 );
sRecordingOptions->Add( m_FramesToRecordLabel, 0, wxALL, 5 );
m_FramesToRecordCtrl = new wxSpinCtrl( m_RecordPage, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxSP_ARROW_KEYS, 0, 10000, 1 );
sRecordingOptions->Add( m_FramesToRecordCtrl, 0, wxALL, 5 );
sRecordPage->Add( sRecordingOptions, 0, wxEXPAND, 5 );
m_RecordPage->SetSizer( sRecordPage );
m_RecordPage->Layout();
sRecordPage->Fit( m_RecordPage );
m_Notebook->AddPage( m_RecordPage, _("Record"), false );
sMain->Add( m_Notebook, 1, wxEXPAND | wxALL, 5 );
wxBoxSizer* sButtons;
sButtons = new wxBoxSizer( wxHORIZONTAL );
wxBoxSizer* sCloseButtonExpander;
sCloseButtonExpander = new wxBoxSizer( wxHORIZONTAL );
sButtons->Add( sCloseButtonExpander, 1, wxEXPAND, 5 );
m_Close = new wxButton( this, wxID_ANY, _("Close"), wxDefaultPosition, wxDefaultSize, 0 );
sButtons->Add( m_Close, 0, wxALL, 5 );
sMain->Add( sButtons, 0, wxEXPAND, 5 );
SetSizer( sMain );
Layout();
sMain->Fit( this );
this->Center( wxBOTH );
// Connect Events
this->Connect( wxEVT_PAINT, wxPaintEventHandler( FifoPlayerDlg::OnPaint ) );
m_FrameFromCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnFrameFrom ), NULL, this );
m_FrameToCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnFrameTo ), NULL, this );
m_ObjectFromCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnObjectFrom ), NULL, this );
m_ObjectToCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnObjectTo ), NULL, this );
m_EarlyMemoryUpdates->Connect( wxEVT_COMMAND_CHECKBOX_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnCheckEarlyMemoryUpdates ), NULL, this );
m_RecordStop->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnRecordStop ), NULL, this );
m_Save->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnSaveFile ), NULL, this );
m_FramesToRecordCtrl->Connect( wxEVT_COMMAND_SPINCTRL_UPDATED, wxSpinEventHandler( FifoPlayerDlg::OnNumFramesToRecord ), NULL, this );
m_Close->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( FifoPlayerDlg::OnCloseClick ), NULL, this );
Connect(RECORDING_FINISHED_EVENT, wxCommandEventHandler(FifoPlayerDlg::OnRecordingFinished), NULL, this);
Connect(FRAME_WRITTEN_EVENT, wxCommandEventHandler(FifoPlayerDlg::OnFrameWritten), NULL, this);
Show();
}
void FifoPlayerDlg::OnPaint( wxPaintEvent& event )
{
UpdatePlayGui();
UpdateRecorderGui();
event.Skip();
}
void FifoPlayerDlg::OnFrameFrom( wxSpinEvent& event )
{
FifoPlayer &player = FifoPlayer::GetInstance();
player.SetFrameRangeStart(event.GetPosition());
m_FrameFromCtrl->SetValue(player.GetFrameRangeStart());
m_FrameToCtrl->SetValue(player.GetFrameRangeEnd());
}
void FifoPlayerDlg::OnFrameTo( wxSpinEvent& event )
{
FifoPlayer &player = FifoPlayer::GetInstance();
player.SetFrameRangeEnd(event.GetPosition());
m_FrameFromCtrl->SetValue(player.GetFrameRangeStart());
m_FrameToCtrl->SetValue(player.GetFrameRangeEnd());
}
void FifoPlayerDlg::OnObjectFrom( wxSpinEvent& event )
{
FifoPlayer::GetInstance().SetObjectRangeStart(event.GetPosition());
}
void FifoPlayerDlg::OnObjectTo( wxSpinEvent& event )
{
FifoPlayer::GetInstance().SetObjectRangeEnd(event.GetPosition());
}
void FifoPlayerDlg::OnCheckEarlyMemoryUpdates( wxCommandEvent& event )
{
FifoPlayer::GetInstance().SetEarlyMemoryUpdates(event.IsChecked());
}
void FifoPlayerDlg::OnSaveFile( wxCommandEvent& WXUNUSED (event) )
{
FifoDataFile *file = FifoRecorder::GetInstance().GetRecordedFile();
if (file)
{
wxString path = wxSaveFileSelector(_("Dolphin FIFO"), wxT("dff"), wxEmptyString, this);
if (!path.empty())
{
wxBeginBusyCursor();
bool result = file->Save(path.mb_str());
wxEndBusyCursor();
if (!result)
PanicAlert("Error saving file");
}
}
}
void FifoPlayerDlg::OnRecordStop( wxCommandEvent& WXUNUSED (event) )
{
FifoRecorder& recorder = FifoRecorder::GetInstance();
if (recorder.IsRecording())
{
recorder.StopRecording();
m_RecordStop->Disable();
}
else
{
recorder.StartRecording(m_FramesToRecord, RecordingFinished);
m_RecordStop->SetLabel(_("Stop"));
}
}
void FifoPlayerDlg::OnNumFramesToRecord( wxSpinEvent& event )
{
m_FramesToRecord = event.GetPosition();
// Entering 0 frames in the control indicates infinite frames to record
// The fifo recorder takes any value < 0 to be infinite frames
if (m_FramesToRecord < 1)
m_FramesToRecord = -1;
}
void FifoPlayerDlg::OnCloseClick( wxCommandEvent& WXUNUSED (event) )
{
Hide();
}
void FifoPlayerDlg::OnRecordingFinished(wxCommandEvent& WXUNUSED (event) )
{
m_RecordStop->SetLabel(_("Record"));
m_RecordStop->Enable();
UpdateRecorderGui();
}
void FifoPlayerDlg::OnFrameWritten(wxCommandEvent& WXUNUSED (event))
{
m_CurrentFrameLabel->SetLabel(CreateCurrentFrameLabel());
m_NumObjectsLabel->SetLabel(CreateFileObjectCountLabel());
}
void FifoPlayerDlg::UpdatePlayGui()
{
m_NumFramesLabel->SetLabel(CreateFileFrameCountLabel());
m_CurrentFrameLabel->SetLabel(CreateCurrentFrameLabel());
m_NumObjectsLabel->SetLabel(CreateFileObjectCountLabel());
FifoPlayer &player = FifoPlayer::GetInstance();
FifoDataFile *file = player.GetFile();
u32 frameCount = 0;
if (file)
frameCount = file->GetFrameCount();
m_FrameFromCtrl->SetRange(0, frameCount);
m_FrameFromCtrl->SetValue(player.GetFrameRangeStart());
m_FrameToCtrl->SetRange(0, frameCount);
m_FrameToCtrl->SetValue(player.GetFrameRangeEnd());
m_ObjectFromCtrl->SetValue(player.GetObjectRangeStart());
m_ObjectToCtrl->SetValue(player.GetObjectRangeEnd());
}
void FifoPlayerDlg::UpdateRecorderGui()
{
m_RecordingFifoSizeLabel->SetLabel(CreateRecordingFifoSizeLabel());
m_RecordingMemSizeLabel->SetLabel(CreateRecordingMemSizeLabel());
m_RecordingFramesLabel->SetLabel(CreateRecordingFrameCountLabel());
m_Save->Enable(GetSaveButtonEnabled());
}
wxString FifoPlayerDlg::CreateFileFrameCountLabel() const
{
FifoDataFile *file = FifoPlayer::GetInstance().GetFile();
if (file)
return CreateIntegerLabel(file->GetFrameCount(), _("Frame"));
return _("No file loaded");
}
wxString FifoPlayerDlg::CreateCurrentFrameLabel() const
{
FifoDataFile *file = FifoPlayer::GetInstance().GetFile();
if (file)
return _("Frame ") + wxString::Format(wxT("%i"), FifoPlayer::GetInstance().GetCurrentFrameNum());
return wxEmptyString;
}
wxString FifoPlayerDlg::CreateFileObjectCountLabel() const
{
FifoDataFile *file = FifoPlayer::GetInstance().GetFile();
if (file)
return CreateIntegerLabel(FifoPlayer::GetInstance().GetFrameObjectCount(), _("Object"));
return wxEmptyString;
}
wxString FifoPlayerDlg::CreateRecordingFifoSizeLabel() const
{
FifoDataFile *file = FifoRecorder::GetInstance().GetRecordedFile();
if (file)
{
int fifoBytes = 0;
for (int i = 0; i < file->GetFrameCount(); ++i)
fifoBytes += file->GetFrame(i).fifoDataSize;
return CreateIntegerLabel(fifoBytes, _("FIFO Byte"));
}
return _("No recorded file");
}
wxString FifoPlayerDlg::CreateRecordingMemSizeLabel() const
{
FifoDataFile *file = FifoRecorder::GetInstance().GetRecordedFile();
if (file)
{
int memBytes = 0;
for (int frameNum = 0; frameNum < file->GetFrameCount(); ++frameNum)
{
const vector<MemoryUpdate>& memUpdates = file->GetFrame(frameNum).memoryUpdates;
for (unsigned int i = 0; i < memUpdates.size(); ++i)
memBytes += memUpdates[i].size;
}
return CreateIntegerLabel(memBytes, _("Memory Byte"));
}
return wxEmptyString;
}
wxString FifoPlayerDlg::CreateRecordingFrameCountLabel() const
{
FifoDataFile *file = FifoRecorder::GetInstance().GetRecordedFile();
if (file)
{
int numFrames = file->GetFrameCount();
return CreateIntegerLabel(numFrames, _("Frame"));
}
return wxEmptyString;
}
wxString FifoPlayerDlg::CreateIntegerLabel(int size, const wxString& label) const
{
wxString postfix;
if (size != 1)
postfix = _("s");
return wxString::Format(wxT("%i"), size) + wxT(" ") + label + postfix;
}
bool FifoPlayerDlg::GetSaveButtonEnabled() const
{
return (FifoRecorder::GetInstance().GetRecordedFile() != NULL);
}
void FifoPlayerDlg::RecordingFinished()
{
sMutex.lock();
wxEvtHandler* evtHandler = m_EvtHandler;
if (evtHandler)
{
wxCommandEvent event(RECORDING_FINISHED_EVENT);
evtHandler->AddPendingEvent( event );
}
sMutex.unlock();
}
void FifoPlayerDlg::FileLoaded()
{
sMutex.lock();
wxEvtHandler* evtHandler = m_EvtHandler;
if (evtHandler)
{
wxPaintEvent event;
evtHandler->AddPendingEvent( event );
}
sMutex.unlock();
}
void FifoPlayerDlg::FrameWritten()
{
sMutex.lock();
wxEvtHandler* evtHandler = m_EvtHandler;
if (evtHandler)
{
wxCommandEvent event(FRAME_WRITTEN_EVENT);
evtHandler->AddPendingEvent( event );
}
sMutex.unlock();
}

View File

@ -0,0 +1,96 @@
// 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 __FIFO_PLAYER_DLG_h__
#define __FIFO_PLAYER_DLG_h__
#include <wx/wx.h>
#include <wx/notebook.h>
class wxSpinCtrl;
class wxSpinEvent;
class FifoPlayerDlg : public wxDialog
{
public:
FifoPlayerDlg(wxWindow* parent);
~FifoPlayerDlg();
private:
void CreateGUIControls();
void OnPaint( wxPaintEvent& event );
void OnFrameFrom( wxSpinEvent& event );
void OnFrameTo( wxSpinEvent& event );
void OnObjectFrom( wxSpinEvent& event );
void OnObjectTo( wxSpinEvent& event );
void OnCheckEarlyMemoryUpdates( wxCommandEvent& event );
void OnRecordStop( wxCommandEvent& event );
void OnSaveFile( wxCommandEvent& event );
void OnNumFramesToRecord( wxSpinEvent& event );
void OnCloseClick( wxCommandEvent& event );
void OnRecordingFinished(wxCommandEvent& event);
void OnFrameWritten(wxCommandEvent& event);
void UpdatePlayGui();
void UpdateRecorderGui();
wxString CreateFileFrameCountLabel() const;
wxString CreateCurrentFrameLabel() const;
wxString CreateFileObjectCountLabel() const;
wxString CreateRecordingFifoSizeLabel() const;
wxString CreateRecordingMemSizeLabel() const;
wxString CreateRecordingFrameCountLabel() const;
wxString CreateIntegerLabel(int size, const wxString& label) const;
bool GetSaveButtonEnabled() const;
// Called from a non-GUI thread
static void RecordingFinished();
static void FileLoaded();
static void FrameWritten();
static wxEvtHandler *volatile m_EvtHandler;
wxNotebook* m_Notebook;
wxPanel* m_PlayPage;
wxStaticText* m_NumFramesLabel;
wxStaticText* m_CurrentFrameLabel;
wxStaticText* m_NumObjectsLabel;
wxStaticText* m_FrameFromLabel;
wxSpinCtrl* m_FrameFromCtrl;
wxStaticText* m_FrameToLabel;
wxSpinCtrl* m_FrameToCtrl;
wxStaticText* m_ObjectFromLabel;
wxSpinCtrl* m_ObjectFromCtrl;
wxStaticText* m_ObjectToLabel;
wxSpinCtrl* m_ObjectToCtrl;
wxCheckBox* m_EarlyMemoryUpdates;
wxPanel* m_RecordPage;
wxStaticText* m_RecordingFifoSizeLabel;
wxStaticText* m_RecordingMemSizeLabel;
wxStaticText* m_RecordingFramesLabel;
wxButton* m_RecordStop;
wxButton* m_Save;
wxStaticText* m_FramesToRecordLabel;
wxSpinCtrl* m_FramesToRecordCtrl;
wxButton* m_Close;
s32 m_FramesToRecord;
};
#endif

View File

@ -272,6 +272,7 @@ EVT_MENU(IDM_CHEATS, CFrame::OnShow_CheatsWindow)
EVT_MENU(IDM_CHANGEDISC, CFrame::OnChangeDisc)
EVT_MENU(IDM_INSTALLWAD, CFrame::OnLoadWiiMenu)
EVT_MENU(IDM_LOAD_WII_MENU, CFrame::OnLoadWiiMenu)
EVT_MENU(IDM_FIFOPLAYER, CFrame::OnFifoPlayer)
EVT_MENU(IDM_TOGGLE_FULLSCREEN, CFrame::OnToggleFullscreen)
EVT_MENU(IDM_TOGGLE_DUALCORE, CFrame::OnToggleDualCore)
@ -332,7 +333,8 @@ CFrame::CFrame(wxFrame* parent,
, m_ToolBar(NULL), m_ToolBarDebug(NULL), m_ToolBarAui(NULL)
, m_GameListCtrl(NULL), m_Panel(NULL)
, m_RenderFrame(NULL), m_RenderParent(NULL)
, m_LogWindow(NULL), m_LogConfigWindow(NULL), UseDebugger(_UseDebugger)
, m_LogWindow(NULL), m_LogConfigWindow(NULL)
, m_FifoPlayerDlg(NULL), UseDebugger(_UseDebugger)
, m_bBatchMode(_BatchMode), m_bEdit(false), m_bTabSplit(false), m_bNoDocking(false)
, m_bGameLoading(false)
{

View File

@ -48,6 +48,7 @@ static inline wxBitmap _wxGetBitmapFromMemory(const unsigned char* data, int len
class CGameListCtrl;
class GameListItem;
class CLogWindow;
class FifoPlayerDlg;
class NetPlaySetupDiag;
class wxCheatsWindow;
@ -175,6 +176,7 @@ private:
wxPanel* m_RenderParent;
CLogWindow* m_LogWindow;
LogConfigWindow* m_LogConfigWindow;
FifoPlayerDlg* m_FifoPlayerDlg;
bool UseDebugger;
bool m_bBatchMode;
bool m_bEdit;
@ -337,6 +339,7 @@ private:
void OnShow_CheatsWindow(wxCommandEvent& event);
void OnLoadWiiMenu(wxCommandEvent& event);
void OnFifoPlayer(wxCommandEvent& event);
void OnConnectWiimote(wxCommandEvent& event);
void GameListChanged(wxCommandEvent& event);

View File

@ -51,6 +51,7 @@ Core::GetWindowHandle().
#include "BootManager.h"
#include "LogWindow.h"
#include "LogConfigWindow.h"
#include "FifoPlayerDlg.h"
#include "WxUtils.h"
#include "ConfigManager.h" // Core
@ -211,6 +212,9 @@ void CFrame::CreateMenu()
toolsMenu->Append(IDM_LOAD_WII_MENU, wxString::Format(_("Load Wii System Menu %d%c"), sysmenuVersion, sysmenuRegion));
toolsMenu->Enable(IDM_LOAD_WII_MENU, DiscIO::CNANDContentManager::Access().GetNANDLoader(TITLEID_SYSMENU).IsValid());
toolsMenu->Append(IDM_FIFOPLAYER, _("Fifo Player"));
toolsMenu->AppendSeparator();
toolsMenu->AppendCheckItem(IDM_CONNECT_WIIMOTE1, GetMenuLabel(HK_WIIMOTE1_CONNECT));
toolsMenu->AppendCheckItem(IDM_CONNECT_WIIMOTE2, GetMenuLabel(HK_WIIMOTE2_CONNECT));
@ -664,7 +668,7 @@ void CFrame::DoOpen(bool Boot)
_("Select the file to load"),
wxEmptyString, wxEmptyString, wxEmptyString,
_("All GC/Wii files (elf, dol, gcm, iso, ciso, gcz, wad)") +
wxString::Format(wxT("|*.elf;*.dol;*.gcm;*.iso;*.ciso;*.gcz;*.wad|%s"),
wxString::Format(wxT("|*.elf;*.dol;*.gcm;*.iso;*.ciso;*.gcz;*.wad;*.dff|%s"),
wxGetTranslation(wxALL_FILES)),
wxFD_OPEN | wxFD_FILE_MUST_EXIST,
this);
@ -1357,6 +1361,19 @@ void CFrame::OnLoadWiiMenu(wxCommandEvent& event)
}
void CFrame::OnFifoPlayer(wxCommandEvent& WXUNUSED (event))
{
if (m_FifoPlayerDlg)
{
m_FifoPlayerDlg->Show();
m_FifoPlayerDlg->SetFocus();
}
else
{
m_FifoPlayerDlg = new FifoPlayerDlg(this);
}
}
void CFrame::ConnectWiimote(int wm_idx, bool connect)
{
if (Core::IsRunning() && SConfig::GetInstance().m_LocalCoreStartupParameter.bWii)

View File

@ -94,6 +94,7 @@ enum
IDM_PROPERTIES,
IDM_GAMEWIKI,
IDM_LOAD_WII_MENU,
IDM_FIFOPLAYER,
IDM_CONNECT_WIIMOTE1,
IDM_CONNECT_WIIMOTE2,
IDM_CONNECT_WIIMOTE3,

View File

@ -262,4 +262,7 @@ extern VAT g_VtxAttr[8];
// Might move this into its own file later.
void LoadCPReg(u32 SubCmd, u32 Value);
// Fills memory with data from CP regs
void FillCPMemoryArray(u32 *memory);
#endif // _CPMEMORY_H

View File

@ -33,6 +33,7 @@
#include "Core.h"
#include "Host.h"
#include "HW/Memmap.h"
#include "FifoPlayer/FifoRecorder.h"
#include "VertexLoaderManager.h"
@ -50,6 +51,8 @@
#include "VideoConfig.h"
u8* g_pVideoData = 0;
bool g_bRecordFifoData = false;
#if _M_SSE >= 0x301
DataReadU32xNfunc DataReadU32xFuncs_SSSE3[16] = {
DataReadU32xN_SSSE3<1>,
@ -254,6 +257,8 @@ bool FifoCommandRunnable()
static void Decode()
{
u8 *opcodeStart = g_pVideoData;
int cmd_byte = DataReadU8();
switch (cmd_byte)
{
@ -338,10 +343,16 @@ static void Decode()
}
break;
}
// Display lists get added directly into the FIFO stream
if (g_bRecordFifoData && cmd_byte != GX_CMD_CALL_DL)
FifoRecorder::GetInstance().WriteGPCommand(opcodeStart, g_pVideoData - opcodeStart);
}
static void DecodeSemiNop()
{
u8 *opcodeStart = g_pVideoData;
int cmd_byte = DataReadU8();
switch (cmd_byte)
{
@ -416,6 +427,9 @@ static void DecodeSemiNop()
}
break;
}
if (g_bRecordFifoData && cmd_byte != GX_CMD_CALL_DL)
FifoRecorder::GetInstance().WriteGPCommand(opcodeStart, g_pVideoData - opcodeStart);
}
void OpcodeDecoder_Init()

View File

@ -45,6 +45,9 @@
#define GX_DRAW_LINE_STRIP 0x6 // 0xB0
#define GX_DRAW_POINTS 0x7 // 0xB8
#define GX_DRAW_NONE 0x1; //Tis is a fake value to used in the backends
extern bool g_bRecordFifoData;
void OpcodeDecoder_Init();
void OpcodeDecoder_Shutdown();
void OpcodeDecoder_Run(bool skipped_frame);

View File

@ -28,14 +28,20 @@
#include "RenderBase.h"
#include "Atomic.h"
#include "BPMemory.h"
#include "CommandProcessor.h"
#include "CPMemory.h"
#include "MainBase.h"
#include "VideoConfig.h"
#include "FramebufferManagerBase.h"
#include "TextureCacheBase.h"
#include "Fifo.h"
#include "OpcodeDecoding.h"
#include "Timer.h"
#include "StringUtil.h"
#include "Host.h"
#include "XFMemory.h"
#include "FifoPlayer/FifoRecorder.h"
#include <cmath>
#include <string>
@ -75,6 +81,7 @@ int Renderer::s_LastEFBScale;
bool Renderer::s_skipSwap;
bool Renderer::XFBWrited;
bool Renderer::s_EnableDLCachingAfterRecording;
unsigned int Renderer::prev_efb_format = (unsigned int)-1;
@ -91,6 +98,8 @@ Renderer::~Renderer()
void Renderer::RenderToXFB(u32 xfbAddr, u32 fbWidth, u32 fbHeight, const EFBRectangle& sourceRc, float Gamma)
{
CheckFifoRecording();
if (!fbWidth || !fbHeight)
return;
@ -332,6 +341,43 @@ void Renderer::SetWindowSize(int width, int height)
Host_RequestRenderWindowSize(width, height);
}
void Renderer::CheckFifoRecording()
{
bool wasRecording = g_bRecordFifoData;
g_bRecordFifoData = FifoRecorder::GetInstance().IsRecording();
if (g_bRecordFifoData)
{
if (!wasRecording)
{
// Disable display list caching because the recorder does not handle it
s_EnableDLCachingAfterRecording = g_ActiveConfig.bDlistCachingEnable;
g_ActiveConfig.bDlistCachingEnable = false;
RecordVideoMemory();
}
FifoRecorder::GetInstance().EndFrame(CommandProcessor::fifo.CPBase, CommandProcessor::fifo.CPEnd);
}
else if (wasRecording)
{
g_ActiveConfig.bDlistCachingEnable = s_EnableDLCachingAfterRecording;
}
}
void Renderer::RecordVideoMemory()
{
u32 *bpMem = (u32*)&bpmem;
u32 cpMem[256];
u32 *xfMem = (u32*)xfmem;
u32 *xfRegs = (u32*)&xfregs;
memset(cpMem, 0, 256 * 4);
FillCPMemoryArray(cpMem);
FifoRecorder::GetInstance().SetVideoMemory(bpMem, cpMem, xfMem, xfRegs, sizeof(XFRegisters) / 4);
}
void UpdateViewport()
{
g_renderer->UpdateViewport();

View File

@ -151,6 +151,9 @@ protected:
static bool CalculateTargetSize(int multiplier = 1);
static void CalculateXYScale(const TargetRectangle& dst_rect);
static void CheckFifoRecording();
static void RecordVideoMemory();
static volatile bool s_bScreenshot;
// The framebuffer size
@ -178,6 +181,8 @@ protected:
static bool s_skipSwap;
static bool XFBWrited;
static bool s_EnableDLCachingAfterRecording;
private:
static unsigned int prev_efb_format;
};

View File

@ -214,6 +214,27 @@ void LoadCPReg(u32 sub_cmd, u32 value)
}
}
void FillCPMemoryArray(u32 *memory)
{
memory[0x30] = MatrixIndexA.Hex;
memory[0x40] = MatrixIndexB.Hex;
memory[0x50] = (u32)g_VtxDesc.Hex;
memory[0x60] = (u32)(g_VtxDesc.Hex >> 17);
for (int i = 0; i < 8; ++i)
{
memory[0x70 + i] = g_VtxAttr[i].g0.Hex;
memory[0x80 + i] = g_VtxAttr[i].g1.Hex;
memory[0x90 + i] = g_VtxAttr[i].g2.Hex;
}
for (int i = 0; i < 16; ++i)
{
memory[0xA0 + i] = arraybases[i];
memory[0xB0 + i] = arraystrides[i];
}
}
void RecomputeCachedArraybases()
{
for (int i = 0; i < 16; i++)