Files
dolphin/Source/Core/Core/FifoPlayer/FifoPlayer.cpp
EmptyChaos c1922783f8 Core: Threadsafety Synchronization Fixes (Frame Advance / FifoPlayer)
Fix Frame Advance and FifoPlayer pause/unpause/stop.

CPU::EnableStepping is not atomic but is called from multiple threads
which races and leaves the system in a random state; also instruction
stepping was unstable, m_StepEvent had an almost random value because
of the dual purpose it served which could cause races where CPU::Run
would SingleStep when it was supposed to be sleeping.

FifoPlayer never FinishStateMove()d which was causing it to deadlock.
Rather than partially reimplementing CPU::Run, just use CPUCoreBase
and then call CPU::Run(). More DRY and less likely to have weird bugs
specific to the player (i.e the previous freezing on pause/stop).

Refactor PowerPC::state into CPU since it manages the state of the
CPU Thread which is controlled by CPU, not PowerPC. This simplifies
the architecture somewhat and eliminates races that can be caused by
calling PowerPC state functions directly instead of using CPU's
(because they bypassed the EnableStepping lock).
2016-05-13 09:23:44 +10:00

552 lines
12 KiB
C++

// Copyright 2011 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include <algorithm>
#include <mutex>
#include "Common/Assert.h"
#include "Common/CommonTypes.h"
#include "Common/MsgHandler.h"
#include "Core/ConfigManager.h"
#include "Core/Core.h"
#include "Core/CoreTiming.h"
#include "Core/Host.h"
#include "Core/FifoPlayer/FifoDataFile.h"
#include "Core/FifoPlayer/FifoPlayer.h"
#include "Core/HW/CPU.h"
#include "Core/HW/GPFifo.h"
#include "Core/HW/Memmap.h"
#include "Core/HW/ProcessorInterface.h"
#include "Core/HW/SystemTimers.h"
#include "Core/HW/VideoInterface.h"
#include "Core/PowerPC/PowerPC.h"
#include "VideoCommon/BPMemory.h"
#include "VideoCommon/CommandProcessor.h"
bool IsPlayingBackFifologWithBrokenEFBCopies = false;
FifoPlayer::~FifoPlayer()
{
delete m_File;
}
bool FifoPlayer::Open(const std::string& filename)
{
Close();
m_File = FifoDataFile::Load(filename, false);
if (m_File)
{
FifoAnalyzer::Init();
FifoPlaybackAnalyzer::AnalyzeFrames(m_File, m_FrameInfo);
m_FrameRangeEnd = m_File->GetFrameCount();
}
if (m_FileLoadedCb)
m_FileLoadedCb();
return (m_File != nullptr);
}
void FifoPlayer::Close()
{
delete m_File;
m_File = nullptr;
m_FrameRangeStart = 0;
m_FrameRangeEnd = 0;
}
class FifoPlayer::CPUCore final : public CPUCoreBase
{
public:
explicit CPUCore(FifoPlayer* parent)
: m_parent(parent)
{
}
CPUCore(const CPUCore&) = delete;
~CPUCore()
{
}
CPUCore& operator=(const CPUCore&) = delete;
void Init() override
{
IsPlayingBackFifologWithBrokenEFBCopies = m_parent->m_File->HasBrokenEFBCopies();
m_parent->m_CurrentFrame = m_parent->m_FrameRangeStart;
m_parent->LoadMemory();
}
void Shutdown() override
{
IsPlayingBackFifologWithBrokenEFBCopies = false;
}
void ClearCache() override
{
// Nothing to clear.
}
void SingleStep() override
{
// NOTE: AdvanceFrame() will get stuck forever in Dual Core because the FIFO
// is disabled by CPU::EnableStepping(true) so the frame never gets displayed.
PanicAlertT("Cannot SingleStep the FIFO. Use Frame Advance instead.");
}
const char* GetName() override
{
return "FifoPlayer";
}
void Run() override
{
while (CPU::GetState() == CPU::CPU_RUNNING)
{
switch (m_parent->AdvanceFrame())
{
case CPU::CPU_POWERDOWN:
CPU::Break();
Host_Message(WM_USER_STOP);
break;
case CPU::CPU_STEPPING:
CPU::Break();
Host_UpdateMainFrame();
break;
}
}
}
private:
FifoPlayer* m_parent;
};
int FifoPlayer::AdvanceFrame()
{
if (m_CurrentFrame >= m_FrameRangeEnd)
{
if (!m_Loop)
return CPU::CPU_POWERDOWN;
// If there are zero frames in the range then sleep instead of busy spinning
if (m_FrameRangeStart >= m_FrameRangeEnd)
return CPU::CPU_STEPPING;
m_CurrentFrame = m_FrameRangeStart;
}
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 CPU::CPU_RUNNING;
}
std::unique_ptr<CPUCoreBase> FifoPlayer::GetCPUCore()
{
if (!m_File || m_File->GetFrameCount() == 0)
return nullptr;
return std::make_unique<CPUCore>(this);
}
u32 FifoPlayer::GetFrameObjectCount()
{
if (m_CurrentFrame < m_FrameInfo.size())
{
return (u32)(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(nullptr),
m_FrameWrittenCb(nullptr),
m_File(nullptr)
{
m_Loop = SConfig::GetInstance().bLoopFifoReplay;
}
void FifoPlayer::WriteFrame(const FifoFrameInfo& frame, const AnalyzedFrameInfo& info)
{
// Core timing information
m_CyclesPerFrame = SystemTimers::GetTicksPerSecond() / VideoInterface::GetTargetRefreshRate();
m_ElapsedCycles = 0;
m_FrameFifoSize = frame.fifoDataSize;
// Determine start and end objects
u32 numObjects = (u32)(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 = (u32)(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();
// Sleep while the GPU is active
while (!IsIdleSet())
{
CoreTiming::Idle();
CoreTiming::Advance();
}
}
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 (u32 frameNum = 0; frameNum < m_File->GetFrameCount(); ++frameNum)
{
const FifoFrameInfo &frame = m_File->GetFrame(frameNum);
for (auto& update : frame.memoryUpdates)
{
WriteMemory(update);
}
}
}
void FifoPlayer::WriteMemory(const MemoryUpdate& memUpdate)
{
u8 *mem = nullptr;
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)
{
while (IsHighWatermarkSet())
{
CoreTiming::Idle();
CoreTiming::Advance();
}
u32 burstEnd = std::min(written + 255, lastBurstEnd);
while (written < burstEnd)
GPFifo::FastWrite8(data[written++]);
GPFifo::Write8(data[written++]);
// Advance core timing
u32 elapsedCycles = u32(((u64)written * m_CyclesPerFrame) / m_FrameFifoSize);
u32 cyclesUsed = elapsedCycles - m_ElapsedCycles;
m_ElapsedCycles = elapsedCycles;
PowerPC::ppcState.downcount -= cyclesUsed;
CoreTiming::Advance();
}
}
void FifoPlayer::SetupFifo()
{
WriteCP(CommandProcessor::CTRL_REGISTER, 0); // disable read, BP, interrupts
WriteCP(CommandProcessor::CLEAR_REGISTER, 7); // clear overflow, underflow, metrics
const FifoFrameInfo& frame = m_File->GetFrame(m_CurrentFrame);
// Set fifo bounds
WriteCP(CommandProcessor::FIFO_BASE_LO, frame.fifoStart);
WriteCP(CommandProcessor::FIFO_BASE_HI, frame.fifoStart >> 16);
WriteCP(CommandProcessor::FIFO_END_LO, frame.fifoEnd);
WriteCP(CommandProcessor::FIFO_END_HI, frame.fifoEnd >> 16);
// Set watermarks, high at 75%, low at 0%
u32 hi_watermark = (frame.fifoEnd - frame.fifoStart) * 3 / 4;
WriteCP(CommandProcessor::FIFO_HI_WATERMARK_LO, hi_watermark);
WriteCP(CommandProcessor::FIFO_HI_WATERMARK_HI, hi_watermark >> 16);
WriteCP(CommandProcessor::FIFO_LO_WATERMARK_LO, 0);
WriteCP(CommandProcessor::FIFO_LO_WATERMARK_HI, 0);
// Set R/W pointers to fifo start
WriteCP(CommandProcessor::FIFO_RW_DISTANCE_LO, 0);
WriteCP(CommandProcessor::FIFO_RW_DISTANCE_HI, 0);
WriteCP(CommandProcessor::FIFO_WRITE_POINTER_LO, frame.fifoStart);
WriteCP(CommandProcessor::FIFO_WRITE_POINTER_HI, frame.fifoStart >> 16);
WriteCP(CommandProcessor::FIFO_READ_POINTER_LO, frame.fifoStart);
WriteCP(CommandProcessor::FIFO_READ_POINTER_HI, frame.fifoStart >> 16);
// Set fifo bounds
WritePI(ProcessorInterface::PI_FIFO_BASE, frame.fifoStart);
WritePI(ProcessorInterface::PI_FIFO_END, frame.fifoEnd);
// Set write pointer
WritePI(ProcessorInterface::PI_FIFO_WPTR, frame.fifoStart);
FlushWGP();
WritePI(ProcessorInterface::PI_FIFO_WPTR, frame.fifoStart);
WriteCP(CommandProcessor::CTRL_REGISTER, 17); // enable read & GP link
}
void FifoPlayer::LoadMemory()
{
UReg_MSR newMSR;
newMSR.DR = 1;
newMSR.IR = 1;
MSR = newMSR.Hex;
PowerPC::ppcState.spr[SPR_IBAT0U] = 0x80001fff;
PowerPC::ppcState.spr[SPR_IBAT0L] = 0x00000002;
PowerPC::ppcState.spr[SPR_DBAT0U] = 0x80001fff;
PowerPC::ppcState.spr[SPR_DBAT0L] = 0x00000002;
PowerPC::ppcState.spr[SPR_DBAT1U] = 0xc0001fff;
PowerPC::ppcState.spr[SPR_DBAT1L] = 0x0000002a;
SetupFifo();
u32 *regs = m_File->GetBPMem();
for (int i = 0; i < FifoDataFile::BP_MEM_SIZE; ++i)
{
if (ShouldLoadBP(i))
LoadBPReg(i, regs[i]);
}
regs = m_File->GetCPMem();
LoadCPReg(0x30, regs[0x30]);
LoadCPReg(0x40, regs[0x40]);
LoadCPReg(0x50, regs[0x50]);
LoadCPReg(0x60, regs[0x60]);
for (int i = 0; i < 8; ++i)
{
LoadCPReg(0x70 + i, regs[0x70 + i]);
LoadCPReg(0x80 + i, regs[0x80 + i]);
LoadCPReg(0x90 + i, regs[0x90 + i]);
}
for (int i = 0; i < 16; ++i)
{
LoadCPReg(0xa0 + i, regs[0xa0 + i]);
LoadCPReg(0xb0 + i, regs[0xb0 + i]);
}
regs = m_File->GetXFMem();
for (int i = 0; i < FifoDataFile::XF_MEM_SIZE; i += 16)
LoadXFMem16(i, &regs[i]);
regs = m_File->GetXFRegs();
for (int i = 0; i < FifoDataFile::XF_REGS_SIZE; ++i)
LoadXFReg(i, regs[i]);
FlushWGP();
}
void FifoPlayer::WriteCP(u32 address, u16 value)
{
PowerPC::Write_U16(value, 0xCC000000 | address);
}
void FifoPlayer::WritePI(u32 address, u32 value)
{
PowerPC::Write_U32(value, 0xCC003000 | address);
}
void FifoPlayer::FlushWGP()
{
// Send 31 0s through the WGP
for (int i = 0; i < 7; ++i)
GPFifo::Write32(0);
GPFifo::Write16(0);
GPFifo::Write8(0);
GPFifo::ResetGatherPipe();
}
void FifoPlayer::LoadBPReg(u8 reg, u32 value)
{
GPFifo::Write8(0x61); // load BP reg
u32 cmd = (reg << 24) & 0xff000000;
cmd |= (value & 0x00ffffff);
GPFifo::Write32(cmd);
}
void FifoPlayer::LoadCPReg(u8 reg, u32 value)
{
GPFifo::Write8(0x08); // load CP reg
GPFifo::Write8(reg);
GPFifo::Write32(value);
}
void FifoPlayer::LoadXFReg(u16 reg, u32 value)
{
GPFifo::Write8(0x10); // load XF reg
GPFifo::Write32((reg & 0x0fff) | 0x1000); // load 4 bytes into reg
GPFifo::Write32(value);
}
void FifoPlayer::LoadXFMem16(u16 address, u32* data)
{
// Loads 16 * 4 bytes in xf memory starting at address
GPFifo::Write8(0x10); // load XF reg
GPFifo::Write32(0x000f0000 | (address & 0xffff)); // load 16 * 4 bytes into address
for (int i = 0; i < 16; ++i)
GPFifo::Write32(data[i]);
}
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;
default:
return true;
}
}
bool FifoPlayer::IsIdleSet()
{
CommandProcessor::UCPStatusReg status = PowerPC::Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
return status.CommandIdle;
}
bool FifoPlayer::IsHighWatermarkSet()
{
CommandProcessor::UCPStatusReg status = PowerPC::Read_U16(0xCC000000 | CommandProcessor::STATUS_REGISTER);
return status.OverflowHiWatermark;
}