More CP/GPFifo work. Fix Super Monkey Ball SC/DC. SMB force us to hack CP differently: CPU really thinks the fifo is always empty and on idle now.

git-svn-id: https://dolphin-emu.googlecode.com/svn/trunk@1242 8ced0084-cf51-0410-be5f-012b33b47a6e
This commit is contained in:
memberTwo.mb2 2008-11-22 15:25:04 +00:00
parent 145f80fc00
commit 36bf2fedf6
4 changed files with 134 additions and 65 deletions

View File

@ -29,6 +29,8 @@
// in its JITed block (never fall in Advance() until the game-watchdog's stuff).
// That's why we can't let perform the AdvanceCallBack as usual.
// The PEToken is volatile now and in the fifo struct.
// - Super Monkey Ball: PEFinish. This game has the lamest way to deal with fifo sync for our MT's stuff.
// A hack is mandatory. DONE and should be ok for other games.
// *What I guess (thx to asynchronous DualCore mode):
// PPC have a frame-finish watchdog. Handled by system timming stuff like the decrementer.
@ -167,8 +169,10 @@ void DoState(PointerWrap &p)
void UpdateFifoRegister();
void UpdateInterrupts();
inline void WriteLow (u32& _reg, u16 lowbits) {_reg = (_reg & 0xFFFF0000) | lowbits;}
inline void WriteHigh(u32& _reg, u16 highbits) {_reg = (_reg & 0x0000FFFF) | ((u32)highbits << 16);}
//inline void WriteLow (u32& _reg, u16 lowbits) {_reg = (_reg & 0xFFFF0000) | lowbits;}
//inline void WriteHigh(u32& _reg, u16 highbits) {_reg = (_reg & 0x0000FFFF) | ((u32)highbits << 16);}
inline void WriteLow (volatile u32& _reg, u16 lowbits) {InterlockedExchange((LONG*)&_reg,(_reg & 0xFFFF0000) | lowbits);}
inline void WriteHigh(volatile u32& _reg, u16 highbits) {InterlockedExchange((LONG*)&_reg,(_reg & 0x0000FFFF) | ((u32)highbits << 16));}
inline u16 ReadLow (u32 _reg) {return (u16)(_reg & 0xFFFF);}
inline u16 ReadHigh (u32 _reg) {return (u16)(_reg >> 16);}
@ -214,13 +218,11 @@ void Init()
m_tokenReg = 0;
fifo.bFF_GPReadEnable = FALSE;
fifo.bFF_GPLinkEnable = FALSE;
fifo.bFF_BPEnable = FALSE;
fifo.PEToken = 0;
// for GP watchdog hack
fifo.Fake_GPWDToken = 0;
fake_GPWatchdogLastToken = 0;
memset(&fifo,0,sizeof(fifo));
fifo.CPCmdIdle = 1 ;
fifo.CPReadIdle = 1;
et_UpdateInterrupts = CoreTiming::RegisterEvent("UpdateInterrupts", UpdateInterrupts_Wrapper);
}
@ -237,7 +239,25 @@ void Read16(u16& _rReturnValue, const u32 _Address)
LOGV(COMMANDPROCESSOR, 1, "(r): 0x%08x", _Address);
switch (_Address & 0xFFF)
{
case STATUS_REGISTER: _rReturnValue = m_CPStatusReg.Hex; return;
case STATUS_REGISTER:
//TODO?: if really needed
//m_CPStatusReg.CommandIdle = fifo.CPCmdIdle;
// uncomment: change a bit the behaviour MP1. Not very useful though
//m_CPStatusReg.ReadIdle = fifo.CPReadIdle;
// hack: CPU will always believe fifo is empty and on idle
m_CPStatusReg.ReadIdle = 1;
m_CPStatusReg.CommandIdle = 1;
_rReturnValue = m_CPStatusReg.Hex;
LOG(COMMANDPROCESSOR, "\t iBP %s | fREADIDLE %s | fCMDIDLE %s | iOvF %s | iUndF %s"
, m_CPStatusReg.Breakpoint ? "ON" : "OFF"
, m_CPStatusReg.ReadIdle ? "ON" : "OFF"
, m_CPStatusReg.CommandIdle ? "ON" : "OFF"
, m_CPStatusReg.OverflowHiWatermark ? "ON" : "OFF"
, m_CPStatusReg.UnderflowLoWatermark ? "ON" : "OFF"
);
return;
case CTRL_REGISTER: _rReturnValue = m_CPCtrlReg.Hex; return;
case CLEAR_REGISTER: _rReturnValue = m_CPClearReg.Hex; return;
@ -255,29 +275,39 @@ void Read16(u16& _rReturnValue, const u32 _Address)
case FIFO_HI_WATERMARK_HI: _rReturnValue = ReadHigh(fifo.CPHiWatermark); return;
case FIFO_LO_WATERMARK_LO: _rReturnValue = ReadLow (fifo.CPLoWatermark); return;
case FIFO_LO_WATERMARK_HI: _rReturnValue = ReadHigh(fifo.CPLoWatermark); return;
// TODO: cases cleanup
case FIFO_RW_DISTANCE_LO:
_rReturnValue = ReadLow (fifo.CPReadWriteDistance);
//LOG(COMMANDPROCESSOR,"read FIFO_RW_DISTANCE_LO : %04x", _rReturnValue);
//_rReturnValue = ReadLow (fifo.CPReadWriteDistance);
// hack: CPU will always believe fifo is empty and on idle
_rReturnValue = 0;
LOG(COMMANDPROCESSOR,"read FIFO_RW_DISTANCE_LO : %04x", _rReturnValue);
return;
case FIFO_RW_DISTANCE_HI:
_rReturnValue = ReadHigh(fifo.CPReadWriteDistance);
//LOG(COMMANDPROCESSOR,"read FIFO_RW_DISTANCE_HI : %04x", _rReturnValue);
//_rReturnValue = ReadHigh(fifo.CPReadWriteDistance);
// hack: CPU will always believe fifo is empty and on idle
_rReturnValue = 0;
LOG(COMMANDPROCESSOR,"read FIFO_RW_DISTANCE_HI : %04x", _rReturnValue);
return;
case FIFO_WRITE_POINTER_LO:
_rReturnValue = ReadLow (fifo.CPWritePointer);
//LOG(COMMANDPROCESSOR,"read FIFO_WRITE_POINTER_LO : %04x", _rReturnValue);
LOG(COMMANDPROCESSOR,"read FIFO_WRITE_POINTER_LO : %04x", _rReturnValue);
return;
case FIFO_WRITE_POINTER_HI:
_rReturnValue = ReadHigh(fifo.CPWritePointer);
//LOG(COMMANDPROCESSOR,"read FIFO_WRITE_POINTER_HI : %04x", _rReturnValue);
LOG(COMMANDPROCESSOR,"read FIFO_WRITE_POINTER_HI : %04x", _rReturnValue);
return;
case FIFO_READ_POINTER_LO:
_rReturnValue = ReadLow (fifo.CPReadPointer);
//LOG(COMMANDPROCESSOR,"read FIFO_READ_POINTER_LO : %04x", _rReturnValue);
//_rReturnValue = ReadLow (fifo.CPReadPointer);
// hack: CPU will always believe fifo is empty and on idle
_rReturnValue = ReadLow (fifo.CPWritePointer);
LOG(COMMANDPROCESSOR,"read FIFO_READ_POINTER_LO : %04x", _rReturnValue);
return;
case FIFO_READ_POINTER_HI:
_rReturnValue = ReadHigh(fifo.CPReadPointer);
//LOG(COMMANDPROCESSOR,"read FIFO_READ_POINTER_HI : %04x", _rReturnValue);
//_rReturnValue = ReadHigh(fifo.CPReadPointer);
// hack: CPU will always believe fifo is empty and on idle
_rReturnValue = ReadHigh(fifo.CPWritePointer);
LOG(COMMANDPROCESSOR,"read FIFO_READ_POINTER_HI : %04x", _rReturnValue);
return;
case FIFO_BP_LO: _rReturnValue = ReadLow (fifo.CPBreakpoint); return;
case FIFO_BP_HI: _rReturnValue = ReadHigh(fifo.CPBreakpoint); return;
@ -321,9 +351,9 @@ void Write16(const u16 _Value, const u32 _Address)
// TODO: HLE for GX fifo's APIs?
// Here is the hack:
// - if (attempt to overwrite CTRL_REGISTER by 0x0000)
// // then we assume CPReadWriteDistance will be reset to 0x00000000 very soon.
// // then we assume CPReadWriteDistance will be overwrited very soon.
// - if (fifo is not empty)
// // (not 100% sure): shouln't happens unless PPC think having trouble with the sync
// // (not 100% sure): shouln't happen unless PPC think having trouble with the sync
// // and it attempt a fifo recovery (look for PI_FIFO_RESET in log).
// // If we want to emulate self fifo recovery we need proper GX metrics emulation... yeah sure :p
// - spin until fifo is empty
@ -333,7 +363,6 @@ void Write16(const u16 _Value, const u32 _Address)
if (((_Address&0xFFF) == CTRL_REGISTER) && (_Value == 0)) // API hack
{
// weird MP1 redo that right after linking fifo with GP... hmmm
// maybe waiting for an irq like ReadIdle... dunno
/*_dbg_assert_msg_(COMMANDPROCESSOR, fifo.CPReadWriteDistance == 0,
"WTF! Something went wrong with GP/PPC the sync! -> CPReadWriteDistance: 0x%08X\n"
" - The fifo is not empty but we are going to lock it anyway.\n"
@ -343,6 +372,7 @@ void Write16(const u16 _Value, const u32 _Address)
" - Anyway, fifo flush will be forced if you press OK and dolphin might continue to work...\n"
" - We aren't betting on that :)", fifo.CPReadWriteDistance);
/**/
LOG(COMMANDPROCESSOR, "*********************** GXSetGPFifo very soon? ***********************");
u32 ct=0;
while (fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance > 0 )
ct++;
@ -359,8 +389,6 @@ void Write16(const u16 _Value, const u32 _Address)
// set the flags to "all is okay"
m_CPStatusReg.OverflowHiWatermark = 0;
m_CPStatusReg.UnderflowLoWatermark = 0;
m_CPStatusReg.ReadIdle = 1;
m_CPStatusReg.CommandIdle = 1;
// TOCHECK (mb2): could BP irq be cleared here too?
//if (tmpStatus.Breakpoint!=m_CPStatusReg.Breakpoint) _asm int 3
@ -383,16 +411,16 @@ void Write16(const u16 _Value, const u32 _Address)
case CTRL_REGISTER:
{
m_CPCtrlReg.Hex = _Value;
UCPCtrlReg tmpCtrl(_Value);
#ifdef _WIN32
InterlockedExchange((LONG*)&fifo.bFF_GPReadEnable, m_CPCtrlReg.GPReadEnable);
InterlockedExchange((LONG*)&fifo.bFF_GPLinkEnable, m_CPCtrlReg.GPLinkEnable);
InterlockedExchange((LONG*)&fifo.bFF_BPEnable, m_CPCtrlReg.BPEnable);
InterlockedExchange((LONG*)&fifo.bFF_GPReadEnable, tmpCtrl.GPReadEnable);
InterlockedExchange((LONG*)&fifo.bFF_GPLinkEnable, tmpCtrl.GPLinkEnable);
InterlockedExchange((LONG*)&fifo.bFF_BPEnable, tmpCtrl.BPEnable);
#else
Common::InterlockedExchange((int*)&fifo.bFF_GPReadEnable, m_CPCtrlReg.GPReadEnable);
Common::InterlockedExchange((int*)&fifo.bFF_GPLinkEnable, m_CPCtrlReg.GPLinkEnable);
Common::InterlockedExchange((int*)&fifo.bFF_BPEnable, m_CPCtrlReg.BPEnable);
Common::InterlockedExchange((int*)&fifo.bFF_GPReadEnable, tmpCtrl.GPReadEnable);
Common::InterlockedExchange((int*)&fifo.bFF_GPLinkEnable, tmpCtrl.GPLinkEnable);
Common::InterlockedExchange((int*)&fifo.bFF_BPEnable, tmpCtrl.BPEnable);
#endif
// TOCHECK (mb2): could BP irq be cleared with w16 to STATUS_REGISTER?
// funny hack: eg in MP1 if we disable the clear breakpoint ability by commenting this block
@ -402,7 +430,10 @@ void Write16(const u16 _Value, const u32 _Address)
// TODO (mb2): fix this!
// BP interrupt is cleared here
/*if (m_CPCtrlReg.CPIntEnable)
/*
//if (tmpCtrl.CPIntEnable)
//if (!m_CPCtrlReg.CPIntEnable && tmpCtrl.Hex) // raising edge
//if (m_CPCtrlReg.CPIntEnable && !tmpCtrl.Hex) // falling edge
{
LOG(COMMANDPROCESSOR,"\t ClearBreakpoint interrupt");
// yes an SC hack, single core mode isn't very gc spec compliant :D
@ -413,6 +444,7 @@ void Write16(const u16 _Value, const u32 _Address)
InterlockedExchange((LONG*)&fifo.bFF_Breakpoint, 0);
}
}/**/
m_CPCtrlReg.Hex = tmpCtrl.Hex;
UpdateInterrupts();
LOG(COMMANDPROCESSOR,"\t write to CTRL_REGISTER : %04x", _Value);
LOG(COMMANDPROCESSOR, "\t GPREAD %s | CPULINK %s | BP %s || CPIntEnable %s | OvF %s | UndF %s"
@ -439,38 +471,66 @@ void Write16(const u16 _Value, const u32 _Address)
// Fifo Registers
case FIFO_TOKEN_REGISTER:
m_tokenReg = _Value;
LOG(COMMANDPROCESSOR,"write to FIFO_TOKEN_REGISTER : %04x", _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_TOKEN_REGISTER : %04x", _Value);
break;
case FIFO_BASE_LO: WriteLow ((u32 &)fifo.CPBase, _Value); fifo.CPBase &= 0xFFFFFFE0; break;
case FIFO_BASE_HI: WriteHigh((u32 &)fifo.CPBase, _Value); fifo.CPBase &= 0xFFFFFFE0; break;
case FIFO_END_LO: WriteLow ((u32 &)fifo.CPEnd, _Value); fifo.CPEnd &= 0xFFFFFFE0; break;
case FIFO_END_HI: WriteHigh((u32 &)fifo.CPEnd, _Value); fifo.CPEnd &= 0xFFFFFFE0; break;
case FIFO_BASE_LO:
WriteLow ((u32 &)fifo.CPBase, _Value);
fifo.CPBase &= 0xFFFFFFE0;
LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_LO : %04x", _Value);
break;
case FIFO_BASE_HI:
WriteHigh((u32 &)fifo.CPBase, _Value);
fifo.CPBase &= 0xFFFFFFE0;
LOG(COMMANDPROCESSOR,"\t write to FIFO_BASE_HI : %04x", _Value);
break;
case FIFO_END_LO:
WriteLow ((u32 &)fifo.CPEnd, _Value);
fifo.CPEnd &= 0xFFFFFFE0;
LOG(COMMANDPROCESSOR,"\t write to FIFO_END_LO : %04x", _Value);
break;
case FIFO_END_HI:
WriteHigh((u32 &)fifo.CPEnd, _Value);
fifo.CPEnd &= 0xFFFFFFE0;
LOG(COMMANDPROCESSOR,"\t write to FIFO_END_HI : %04x", _Value);
break;
// Hm. Should we really & these with FFFFFFE0?
// (mb2): never seen 32B not aligned values for those following regs.
// fifo.CPEnd is the only value that could be not 32B aligned so far.
case FIFO_WRITE_POINTER_LO:
WriteLow ((u32 &)fifo.CPWritePointer, _Value); fifo.CPWritePointer &= 0xFFFFFFE0;
//LOG(COMMANDPROCESSOR,"write to FIFO_WRITE_POINTER_LO : %04x", _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_LO : %04x", _Value);
break;
case FIFO_WRITE_POINTER_HI:
WriteHigh((u32 &)fifo.CPWritePointer, _Value); fifo.CPWritePointer &= 0xFFFFFFE0;
//LOG(COMMANDPROCESSOR,"write to FIFO_WRITE_POINTER_HI : %04x", _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_WRITE_POINTER_HI : %04x", _Value);
break;
case FIFO_READ_POINTER_LO:
WriteLow ((u32 &)fifo.CPReadPointer, _Value); fifo.CPReadPointer &= 0xFFFFFFE0;
//LOG(COMMANDPROCESSOR,"write to FIFO_READ_POINTER_LO : %04x", _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_LO : %04x", _Value);
break;
case FIFO_READ_POINTER_HI:
WriteHigh((u32 &)fifo.CPReadPointer, _Value); fifo.CPReadPointer &= 0xFFFFFFE0;
//LOG(COMMANDPROCESSOR,"write to FIFO_READ_POINTER_HI : %04x", _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_READ_POINTER_HI : %04x", _Value);
break;
case FIFO_HI_WATERMARK_LO: WriteLow ((u32 &)fifo.CPHiWatermark, _Value); break;
case FIFO_HI_WATERMARK_HI: WriteHigh((u32 &)fifo.CPHiWatermark, _Value); break;
case FIFO_LO_WATERMARK_LO: WriteLow ((u32 &)fifo.CPLoWatermark, _Value); break;
case FIFO_LO_WATERMARK_HI: WriteHigh((u32 &)fifo.CPLoWatermark, _Value); break;
case FIFO_HI_WATERMARK_LO:
WriteLow ((u32 &)fifo.CPHiWatermark, _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_LO : %04x", _Value);
break;
case FIFO_HI_WATERMARK_HI:
WriteHigh((u32 &)fifo.CPHiWatermark, _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_HI_WATERMARK_HI : %04x", _Value);
break;
case FIFO_LO_WATERMARK_LO:
WriteLow ((u32 &)fifo.CPLoWatermark, _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_LO : %04x", _Value);
break;
case FIFO_LO_WATERMARK_HI:
WriteHigh((u32 &)fifo.CPLoWatermark, _Value);
LOG(COMMANDPROCESSOR,"\t write to FIFO_LO_WATERMARK_HI : %04x", _Value);
break;
case FIFO_BP_LO:
WriteLow ((u32 &)fifo.CPBreakpoint, _Value);
@ -481,23 +541,23 @@ void Write16(const u16 _Value, const u32 _Address)
LOG(COMMANDPROCESSOR,"write to FIFO_BP_HI : %04x", _Value);
break;
// needed for CPReadWriteDistance reset only
// Super monkey try to overwrite CPReadWriteDistance by an old saved RWD value. Which is lame for us.
// hack: We have to force CPU to think fifo is alway empty and on idle.
// When we fall here CPReadWriteDistance should be always null and the game should always want to overwrite it by 0.
// So, we can skip it.
case FIFO_RW_DISTANCE_HI:
//WriteHigh((u32 &)fifo.CPReadWriteDistance, _Value);
LOG(COMMANDPROCESSOR,"try to write to FIFO_RW_DISTANCE_HI : %04x", _Value);
break;
case FIFO_RW_DISTANCE_LO:
LOG(COMMANDPROCESSOR,"try to write to %s : %04x",((_Address & 0xFFF) == FIFO_RW_DISTANCE_HI) ? "FIFO_RW_DISTANCE_HI" : "FIFO_RW_DISTANCE_LO", _Value);
_dbg_assert_msg_(COMMANDPROCESSOR, _Value==0, "WTF? attempt to overwrite fifo CPReadWriteDistance with a value(%04x) != 0 ",_Value);
// This SHOULD NOT be a problem to overwrite here unless this msg appears:
_dbg_assert_msg_(COMMANDPROCESSOR, fifo.CPReadWriteDistance==0, "Is that bad? WARNING: reset a non empty fifo.\n CPReadWriteDistance: %08x | fifo.bFF_GPReadEnable: %i",fifo.CPReadWriteDistance,fifo.bFF_GPReadEnable);
//_dbg_assert_msg_(COMMANDPROCESSOR, !(fifo.CPReadWriteDistance!=0 && fifo.bFF_GPReadEnable==0), "Is that bad? WARNING: reset a not empty and not readable fifo.\n CPReadWriteDistance: %08x | fifo.bFF_GPReadEnable: %i",fifo.CPReadWriteDistance,fifo.bFF_GPReadEnable);
#ifdef _WIN32
//InterlockedExchange((LONG*)&fifo.CPReadWriteDistance, 0);
#else
//Common::InterlockedExchange((int*)&fifo.CPReadWriteDistance, 0);
#endif
//WriteLow((u32 &)fifo.CPReadWriteDistance, _Value);
LOG(COMMANDPROCESSOR,"try to write to FIFO_RW_DISTANCE_LO : %04x", _Value);
break;
}
// TODO(mb2): better. Check if it help: avoid CPReadPointer overwrites when stupidly done like in Super Monkey Ball
if ((!fifo.bFF_GPReadEnable && fifo.CPReadIdle) || !Core::g_CoreStartupParameter.bUseDualCore) // TOCHECK(mb2): check again if thread safe?
UpdateFifoRegister();
}
void Read32(u32& _rReturnValue, const u32 _Address)
@ -551,8 +611,7 @@ void GatherPipeBursted()
LOG(COMMANDPROCESSOR, "(GatherPipeBursted): CPHiWatermark reached");
// Wait for GPU to catch up
//while (!(fifo.bFF_BPEnable && fifo.bFF_Breakpoint) && fifo.CPReadWriteDistance > (s32)fifo.CPLoWatermark)
while (fifo.CPReadWriteDistance > fifo.CPLoWatermark)
while (!(fifo.bFF_BPEnable && fifo.bFF_Breakpoint) && fifo.CPReadWriteDistance > (s32)fifo.CPLoWatermark)
{
ct++;
// dunno if others threads (like the audio thread) really need a forced context switch here
@ -628,7 +687,7 @@ void CatchUpGPU()
}
// __________________________________________________________________________________________________
// !!! only called on single core mode now
// !!! Temporary (I hope): re-used in DC mode
// UpdateFifoRegister
// It's no problem if the gfx falls behind a little bit. Better make sure to stop the cpu thread
// when the distance is way huge, though.
@ -651,8 +710,10 @@ void UpdateFifoRegister()
dist = wp - rp;
else
dist = (wp - fifo.CPBase) + (fifo.CPEnd - rp);
fifo.CPReadWriteDistance = dist;
CatchUpGPU();
//fifo.CPReadWriteDistance = dist;
InterlockedExchange((LONG*)&fifo.CPReadWriteDistance, dist);
if (!Core::g_CoreStartupParameter.bUseDualCore)
CatchUpGPU();
}
void UpdateInterrupts()

View File

@ -114,8 +114,9 @@ void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
//etc...
// check if we are able to run this buffer
if ((_fifo.bFF_GPReadEnable) && !(_fifo.bFF_BPEnable && _fifo.bFF_Breakpoint))
if ((_fifo.bFF_GPReadEnable) && _fifo.CPReadWriteDistance && !(_fifo.bFF_BPEnable && _fifo.bFF_Breakpoint))
{
InterlockedExchange((LONG*)&_fifo.CPReadIdle, 0);
#if defined(THREAD_VIDEO_WAKEUP_ONIDLE) && defined(_WIN32)
while(_fifo.CPReadWriteDistance > 0)
#else
@ -167,6 +168,8 @@ void Fifo_EnterLoop(const SVideoInitialize &video_initialize)
Common::InterlockedExchangeAdd((int*)&_fifo.CPReadWriteDistance, -distToSend);
#endif
}
//video_initialize.pLog("IDLE",FALSE);
InterlockedExchange((LONG*)&_fifo.CPReadIdle, 1);
}
}
#if defined(THREAD_VIDEO_WAKEUP_ONIDLE) && defined(_WIN32)

View File

@ -45,6 +45,9 @@ typedef struct
volatile BOOL bFF_GPLinkEnable;
volatile BOOL bFF_Breakpoint;
volatile BOOL CPCmdIdle;
volatile BOOL CPReadIdle;
// for GP watchdog hack
volatile u32 Fake_GPWDToken; // cicular incrementer
} SCPFifoStruct;

View File

@ -310,6 +310,8 @@ void OpcodeDecoder_Run()
DVSTARTPROFILE();
while (FifoCommandRunnable())
{
//TODO?: if really needed, do something like this: "InterlockedExchange((LONG*)&_fifo.CPCmdIdle, 0);"
Decode();
}
//TODO?: if really needed, do something like this: "InterlockedExchange((LONG*)&_fifo.CPCmdIdle, 1);"
}