From 07c4da6084e6d87cf25f87868da10c068721c19f Mon Sep 17 00:00:00 2001 From: donkopunchstania Date: Sun, 27 Mar 2011 02:55:08 +0000 Subject: [PATCH] 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 --- Source/Core/Core/CMakeLists.txt | 6 + Source/Core/Core/Core.vcxproj | 13 + Source/Core/Core/Core.vcxproj.filters | 44 ++ Source/Core/Core/Src/Boot/Boot.cpp | 4 + Source/Core/Core/Src/Core.cpp | 39 +- Source/Core/Core/Src/CoreParameter.cpp | 16 + Source/Core/Core/Src/CoreParameter.h | 3 +- .../Core/Core/Src/FifoPlayer/FifoAnalyzer.cpp | 235 ++++++++ .../Core/Core/Src/FifoPlayer/FifoAnalyzer.h | 54 ++ .../Core/Core/Src/FifoPlayer/FifoDataFile.cpp | 285 ++++++++++ .../Core/Core/Src/FifoPlayer/FifoDataFile.h | 112 ++++ .../Core/Core/Src/FifoPlayer/FifoFileStruct.h | 85 +++ .../Src/FifoPlayer/FifoPlaybackAnalyzer.cpp | 346 ++++++++++++ .../Src/FifoPlayer/FifoPlaybackAnalyzer.h | 64 +++ .../Core/Core/Src/FifoPlayer/FifoPlayer.cpp | 456 ++++++++++++++++ Source/Core/Core/Src/FifoPlayer/FifoPlayer.h | 121 +++++ .../Src/FifoPlayer/FifoRecordAnalyzer.cpp | 305 +++++++++++ .../Core/Src/FifoPlayer/FifoRecordAnalyzer.h | 56 ++ .../Core/Core/Src/FifoPlayer/FifoRecorder.cpp | 220 ++++++++ .../Core/Core/Src/FifoPlayer/FifoRecorder.h | 80 +++ Source/Core/DolphinWX/CMakeLists.txt | 1 + Source/Core/DolphinWX/Dolphin.vcxproj | 2 + Source/Core/DolphinWX/Dolphin.vcxproj.filters | 8 +- Source/Core/DolphinWX/Src/FifoPlayerDlg.cpp | 511 ++++++++++++++++++ Source/Core/DolphinWX/Src/FifoPlayerDlg.h | 96 ++++ Source/Core/DolphinWX/Src/Frame.cpp | 4 +- Source/Core/DolphinWX/Src/Frame.h | 3 + Source/Core/DolphinWX/Src/FrameTools.cpp | 19 +- Source/Core/DolphinWX/Src/Globals.h | 1 + Source/Core/VideoCommon/Src/CPMemory.h | 3 + .../Core/VideoCommon/Src/OpcodeDecoding.cpp | 14 + Source/Core/VideoCommon/Src/OpcodeDecoding.h | 3 + Source/Core/VideoCommon/Src/RenderBase.cpp | 46 ++ Source/Core/VideoCommon/Src/RenderBase.h | 5 + .../VideoCommon/Src/VertexLoaderManager.cpp | 21 + 35 files changed, 3275 insertions(+), 6 deletions(-) create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoDataFile.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoDataFile.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoFileStruct.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoPlayer.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoPlayer.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.h create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoRecorder.cpp create mode 100644 Source/Core/Core/Src/FifoPlayer/FifoRecorder.h create mode 100644 Source/Core/DolphinWX/Src/FifoPlayerDlg.cpp create mode 100644 Source/Core/DolphinWX/Src/FifoPlayerDlg.h diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index f9ac5bbb4d..b5d5954844 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -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 diff --git a/Source/Core/Core/Core.vcxproj b/Source/Core/Core/Core.vcxproj index bfb0ec3184..f7d7c97bf3 100644 --- a/Source/Core/Core/Core.vcxproj +++ b/Source/Core/Core/Core.vcxproj @@ -243,6 +243,12 @@ + + + + + + @@ -433,6 +439,13 @@ + + + + + + + diff --git a/Source/Core/Core/Core.vcxproj.filters b/Source/Core/Core/Core.vcxproj.filters index f8d14b8631..06bae14d9b 100644 --- a/Source/Core/Core/Core.vcxproj.filters +++ b/Source/Core/Core/Core.vcxproj.filters @@ -534,6 +534,25 @@ NetPlay + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + @@ -989,6 +1008,28 @@ NetPlay + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + + + FifoPlayer + @@ -1124,5 +1165,8 @@ {231ceb02-1122-402a-87a8-094a9ed768c2} + + {ca7d56f7-4e84-4d15-9aea-7ae6fa7d6586} + \ No newline at end of file diff --git a/Source/Core/Core/Src/Boot/Boot.cpp b/Source/Core/Core/Src/Boot/Boot.cpp index ff1a437255..45b33b8190 100644 --- a/Source/Core/Core/Src/Boot/Boot.cpp +++ b/Source/Core/Core/Src/Boot/Boot.cpp @@ -364,6 +364,10 @@ bool CBoot::BootUp() break; } + case SCoreStartupParameter::BOOT_DFF: + // do nothing + break; + default: { PanicAlertT("Tried to load an unknown file type."); diff --git a/Source/Core/Core/Src/Core.cpp b/Source/Core/Core/Src/Core.cpp index 404ea95f70..76164a13b0 100644 --- a/Source/Core/Core/Src/Core.cpp +++ b/Source/Core/Core/Src/Core.cpp @@ -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) { diff --git a/Source/Core/Core/Src/CoreParameter.cpp b/Source/Core/Core/Src/CoreParameter.cpp index 22a12fec62..c4b2e7ee20 100644 --- a/Source/Core/Core/Src/CoreParameter.cpp +++ b/Source/Core/Core/Src/CoreParameter.cpp @@ -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()); diff --git a/Source/Core/Core/Src/CoreParameter.h b/Source/Core/Core/Src/CoreParameter.h index 188ec63a10..4a1b43a4fa 100644 --- a/Source/Core/Core/Src/CoreParameter.h +++ b/Source/Core/Core/Src/CoreParameter.h @@ -153,7 +153,8 @@ struct SCoreStartupParameter BOOT_ELF, BOOT_DOL, BOOT_WII_NAND, - BOOT_BS2 + BOOT_BS2, + BOOT_DFF }; EBootType m_BootType; diff --git a/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.cpp b/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.cpp new file mode 100644 index 0000000000..dd975ef82a --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.cpp @@ -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; + } +} + +} \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.h b/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.h new file mode 100644 index 0000000000..55f1917e57 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoAnalyzer.h @@ -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 \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoDataFile.cpp b/Source/Core/Core/Src/FifoPlayer/FifoDataFile.cpp new file mode 100644 index 0000000000..7933e67e64 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoDataFile.cpp @@ -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 &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 &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); + } +} \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoDataFile.h b/Source/Core/Core/Src/FifoPlayer/FifoDataFile.h new file mode 100644 index 0000000000..409b74ba8e --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoDataFile.h @@ -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 + +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 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 &memUpdates, File::IOFile &file); + static void ReadMemoryUpdates(u64 fileOffset, u32 numUpdates, std::vector &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 m_Frames; +}; + +#endif \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoFileStruct.h b/Source/Core/Core/Src/FifoPlayer/FifoFileStruct.h new file mode 100644 index 0000000000..e35ebd31bf --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoFileStruct.h @@ -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 \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.cpp b/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.cpp new file mode 100644 index 0000000000..ccec508cdb --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.cpp @@ -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 &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 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::iterator newRangeIter = m_WrittenMemory.end(); + + // Search for overlapping memory regions and expand them to include the new region + for (vector::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); + } +} \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.h b/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.h new file mode 100644 index 0000000000..8c779d818d --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoPlaybackAnalyzer.h @@ -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 +#include + +struct AnalyzedFrameInfo +{ + std::vector objectStarts; + std::vector objectEnds; + std::vector memoryUpdates; +}; + +class FifoPlaybackAnalyzer +{ +public: + FifoPlaybackAnalyzer(); + + void AnalyzeFrames(FifoDataFile *file, std::vector &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 m_WrittenMemory; + + BPMemory m_BpMem; + FifoAnalyzer::CPMemory m_CpMem; +}; + +#endif \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoPlayer.cpp b/Source/Core/Core/Src/FifoPlayer/FifoPlayer.cpp new file mode 100644 index 0000000000..3acdfa41cb --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoPlayer.cpp @@ -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, ®s[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; +} diff --git a/Source/Core/Core/Src/FifoPlayer/FifoPlayer.h b/Source/Core/Core/Src/FifoPlayer/FifoPlayer.h new file mode 100644 index 0000000000..2a02544a20 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoPlayer.h @@ -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 +#include + +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 m_FrameInfo; +}; + +#endif \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.cpp b/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.cpp new file mode 100644 index 0000000000..a8214a82c0 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.cpp @@ -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); +} \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.h b/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.h new file mode 100644 index 0000000000..acfbbc38b5 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoRecordAnalyzer.h @@ -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 \ No newline at end of file diff --git a/Source/Core/Core/Src/FifoPlayer/FifoRecorder.cpp b/Source/Core/Core/Src/FifoPlayer/FifoRecorder.cpp new file mode 100644 index 0000000000..b369823c3d --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoRecorder.cpp @@ -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; +} diff --git a/Source/Core/Core/Src/FifoPlayer/FifoRecorder.h b/Source/Core/Core/Src/FifoPlayer/FifoRecorder.h new file mode 100644 index 0000000000..44e7be5862 --- /dev/null +++ b/Source/Core/Core/Src/FifoPlayer/FifoRecorder.h @@ -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 m_FifoData; + u8 *m_Ram; + u8 *m_ExRam; + FifoRecordAnalyzer m_RecordAnalyzer; +}; + +#endif \ No newline at end of file diff --git a/Source/Core/DolphinWX/CMakeLists.txt b/Source/Core/DolphinWX/CMakeLists.txt index b43b92ec50..bac3ba2c05 100644 --- a/Source/Core/DolphinWX/CMakeLists.txt +++ b/Source/Core/DolphinWX/CMakeLists.txt @@ -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 diff --git a/Source/Core/DolphinWX/Dolphin.vcxproj b/Source/Core/DolphinWX/Dolphin.vcxproj index 82dfcd69ef..314a16faaf 100644 --- a/Source/Core/DolphinWX/Dolphin.vcxproj +++ b/Source/Core/DolphinWX/Dolphin.vcxproj @@ -232,6 +232,7 @@ xcopy "$(SolutionDir)..\Externals\SDL\$(PlatformName)\*.dll" "$(TargetDir)" /e / + @@ -294,6 +295,7 @@ xcopy "$(SolutionDir)..\Externals\SDL\$(PlatformName)\*.dll" "$(TargetDir)" /e / + diff --git a/Source/Core/DolphinWX/Dolphin.vcxproj.filters b/Source/Core/DolphinWX/Dolphin.vcxproj.filters index bc3255787d..5fdf69b51b 100644 --- a/Source/Core/DolphinWX/Dolphin.vcxproj.filters +++ b/Source/Core/DolphinWX/Dolphin.vcxproj.filters @@ -129,6 +129,9 @@ GUI + + GUI + @@ -252,6 +255,9 @@ GUI + + GUI + @@ -291,4 +297,4 @@ Resources - + \ No newline at end of file diff --git a/Source/Core/DolphinWX/Src/FifoPlayerDlg.cpp b/Source/Core/DolphinWX/Src/FifoPlayerDlg.cpp new file mode 100644 index 0000000000..4f427e83d2 --- /dev/null +++ b/Source/Core/DolphinWX/Src/FifoPlayerDlg.cpp @@ -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 + +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& 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(); +} \ No newline at end of file diff --git a/Source/Core/DolphinWX/Src/FifoPlayerDlg.h b/Source/Core/DolphinWX/Src/FifoPlayerDlg.h new file mode 100644 index 0000000000..7ecbef8de3 --- /dev/null +++ b/Source/Core/DolphinWX/Src/FifoPlayerDlg.h @@ -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 +#include + +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 diff --git a/Source/Core/DolphinWX/Src/Frame.cpp b/Source/Core/DolphinWX/Src/Frame.cpp index 844743a37f..c8c1968797 100644 --- a/Source/Core/DolphinWX/Src/Frame.cpp +++ b/Source/Core/DolphinWX/Src/Frame.cpp @@ -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) { diff --git a/Source/Core/DolphinWX/Src/Frame.h b/Source/Core/DolphinWX/Src/Frame.h index 149264460e..ba7525ffe8 100644 --- a/Source/Core/DolphinWX/Src/Frame.h +++ b/Source/Core/DolphinWX/Src/Frame.h @@ -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); diff --git a/Source/Core/DolphinWX/Src/FrameTools.cpp b/Source/Core/DolphinWX/Src/FrameTools.cpp index b6962af435..b489c7c4e6 100644 --- a/Source/Core/DolphinWX/Src/FrameTools.cpp +++ b/Source/Core/DolphinWX/Src/FrameTools.cpp @@ -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) diff --git a/Source/Core/DolphinWX/Src/Globals.h b/Source/Core/DolphinWX/Src/Globals.h index 72d8d81701..06191ffa5b 100644 --- a/Source/Core/DolphinWX/Src/Globals.h +++ b/Source/Core/DolphinWX/Src/Globals.h @@ -94,6 +94,7 @@ enum IDM_PROPERTIES, IDM_GAMEWIKI, IDM_LOAD_WII_MENU, + IDM_FIFOPLAYER, IDM_CONNECT_WIIMOTE1, IDM_CONNECT_WIIMOTE2, IDM_CONNECT_WIIMOTE3, diff --git a/Source/Core/VideoCommon/Src/CPMemory.h b/Source/Core/VideoCommon/Src/CPMemory.h index c7de2afc6d..c4a24ba7a4 100644 --- a/Source/Core/VideoCommon/Src/CPMemory.h +++ b/Source/Core/VideoCommon/Src/CPMemory.h @@ -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 diff --git a/Source/Core/VideoCommon/Src/OpcodeDecoding.cpp b/Source/Core/VideoCommon/Src/OpcodeDecoding.cpp index 6d939882e7..18d6726ae3 100644 --- a/Source/Core/VideoCommon/Src/OpcodeDecoding.cpp +++ b/Source/Core/VideoCommon/Src/OpcodeDecoding.cpp @@ -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() diff --git a/Source/Core/VideoCommon/Src/OpcodeDecoding.h b/Source/Core/VideoCommon/Src/OpcodeDecoding.h index 9989f0fd24..f2e9cd1321 100644 --- a/Source/Core/VideoCommon/Src/OpcodeDecoding.h +++ b/Source/Core/VideoCommon/Src/OpcodeDecoding.h @@ -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); diff --git a/Source/Core/VideoCommon/Src/RenderBase.cpp b/Source/Core/VideoCommon/Src/RenderBase.cpp index c7b18e509c..9029f5eff9 100644 --- a/Source/Core/VideoCommon/Src/RenderBase.cpp +++ b/Source/Core/VideoCommon/Src/RenderBase.cpp @@ -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 #include @@ -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(); diff --git a/Source/Core/VideoCommon/Src/RenderBase.h b/Source/Core/VideoCommon/Src/RenderBase.h index 9872200688..98f32c443c 100644 --- a/Source/Core/VideoCommon/Src/RenderBase.h +++ b/Source/Core/VideoCommon/Src/RenderBase.h @@ -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; }; diff --git a/Source/Core/VideoCommon/Src/VertexLoaderManager.cpp b/Source/Core/VideoCommon/Src/VertexLoaderManager.cpp index 564892cb98..316941a639 100644 --- a/Source/Core/VideoCommon/Src/VertexLoaderManager.cpp +++ b/Source/Core/VideoCommon/Src/VertexLoaderManager.cpp @@ -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++)