mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-01-09 23:59:27 +01:00
Merge branch 'FIFO-BP'
# By skidau (30) and Pierre Bourdon (1) * FIFO-BP: (31 commits) Set g_bSignalTokenInterrupt on the main thread. Fixes the random hang in Harry Potter: Prisoner of Azkaban. Used a scheduled event to generate the ARAM DMA interrupt if the DMA is greater than a certain size. Fixes NFS:HP2 GC. Bumped up the disc transfer speed enough to prevent audio stuttering in Gauntlet: Dark Legacy. Enabled Synchronise GPU on "SPEED CHALLENGE - Jacques Villeneuve's Racing Vision". Required to go in-game. Added direct GameCube controller commands to the Serial Interface emulation. Fixes the controls in MaxPlay Classic Games Volume 1 and the Action Replay disc. Increased the FIFO buffer size to 2MB from 1MB. Fixes Killer 7's Angel boss. Used an immediate GenerateDSPInterrupt when transferring data from ARAM to MRAM and a scheduled DSP interrupt when transferring data from MRAM to ARAM. Fixes the audio cutting in and out in the Resident Evil GC games using DSP HLE. Triggered the ARAM interrupt by the scheduler instead of directly in function. Implemented proper timing for the sample counter in the AudioInterface, removing the previous hack. Cleaned up some of the audio streaming code. Skipped the EE check if there is a CP interrupt pending. Disabled "Speed up disc transfer" from the ZTP GC game ini. Removed the disc seek times for GC games and removed the disc speed option on Wii games. Checked for external exceptions only in mtmsr. Delayed the interrupts in the EXI Channel. Merge aram-dma-fixes (r76a13604ef49b522281af75675f044d59a74e871) Added a patch that bypasses the FIFO reset code in Wallace and Gromit: Project Zoo, allowing it to go in-game. Made vertex loading take constant time. Increased the cycle time of the vertex command. Fixes "Speed Challenge: Jacques Villeneuve's Racing Vision". Moved the setting of the Finish interrupt signal back to the main thread as it was causing Wii games like Resident Evil 4 (Wii) to hang. Profile stores, fp stores and ps stores only to the fifo write addresses list. This should make the JIT a little faster as it will not be checking for external exceptions unnecessarily. ... Conflicts: Source/Core/VideoCommon/Src/PixelEngine.cpp
This commit is contained in:
commit
83fc5f4747
@ -1,5 +1,6 @@
|
|||||||
# GKBEAF - Baten Kaitos
|
# GKBEAF - Baten Kaitos
|
||||||
[Core] Values set here will override the main dolphin settings.
|
[Core] Values set here will override the main dolphin settings.
|
||||||
|
SyncGPU = 1
|
||||||
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
EmulationStateId = 4
|
EmulationStateId = 4
|
||||||
EmulationIssues =
|
EmulationIssues =
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# GKBPAF - Baten Kaitos
|
# GKBPAF - Baten Kaitos
|
||||||
[Core] Values set here will override the main dolphin settings.
|
[Core] Values set here will override the main dolphin settings.
|
||||||
|
SyncGPU = 1
|
||||||
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
EmulationStateId = 4
|
EmulationStateId = 4
|
||||||
EmulationIssues =
|
EmulationIssues =
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
# GLSE64 - LucasArts Gladius
|
# GLSE64 - LucasArts Gladius
|
||||||
[Core] Values set here will override the main dolphin settings.
|
[Core] Values set here will override the main dolphin settings.
|
||||||
TLBHack = 1
|
TLBHack = 1
|
||||||
|
SyncGPU = 1
|
||||||
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
EmulationStateId = 1
|
EmulationStateId = 4
|
||||||
EmulationIssues =
|
EmulationIssues =
|
||||||
[OnFrame] Add memory patches to be applied every frame here.
|
[OnFrame] Add memory patches to be applied every frame here.
|
||||||
[ActionReplay] Add action replay cheats here.
|
[ActionReplay] Add action replay cheats here.
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
#Values set here will override the main dolphin settings.
|
#Values set here will override the main dolphin settings.
|
||||||
[EmuState]
|
[EmuState]
|
||||||
#The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
#The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
EmulationStateId = 1
|
EmulationStateId = 4
|
||||||
EmulationIssues =
|
EmulationIssues = Enable the GameCube BIOS to allow the game to boot.
|
||||||
[OnFrame]
|
[OnFrame]
|
||||||
+$Nop Hack
|
+$Nop Hack
|
||||||
0x80025BA0:dword:0x60000000
|
0x80025BA0:dword:0x60000000
|
||||||
|
17
Data/User/GameConfig/GSZP41.ini
Normal file
17
Data/User/GameConfig/GSZP41.ini
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
# GSZP41 - SPEED CHALLENGE - Jacques Villeneuve's Racing Vision
|
||||||
|
[Core] Values set here will override the main dolphin settings.
|
||||||
|
SyncGPU = 1
|
||||||
|
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
|
EmulationStateId = 4
|
||||||
|
EmulationIssues =
|
||||||
|
[OnFrame] Add memory patches to be applied every frame here.
|
||||||
|
[ActionReplay] Add action replay cheats here.
|
||||||
|
[Video]
|
||||||
|
ProjectionHack = 0
|
||||||
|
PH_SZNear = 0
|
||||||
|
PH_SZFar = 0
|
||||||
|
PH_ExtraParam = 0
|
||||||
|
PH_ZNear =
|
||||||
|
PH_ZFar =
|
||||||
|
UseBBox = 1
|
||||||
|
[Gecko]
|
10
Data/User/GameConfig/GWLE6L.ini
Normal file
10
Data/User/GameConfig/GWLE6L.ini
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
# GWLE6L - Project Zoo
|
||||||
|
[Core] Values set here will override the main dolphin settings.
|
||||||
|
TLBHack = 1
|
||||||
|
[EmuState] The Emulation State. 1 is worst, 5 is best, 0 is not set.
|
||||||
|
EmulationStateId = 3
|
||||||
|
EmulationIssues =
|
||||||
|
[OnFrame] Add memory patches to be applied every frame here.
|
||||||
|
+$Bypass FIFO reset
|
||||||
|
0x8028EF00:dword:0x48000638
|
||||||
|
[ActionReplay] Add action replay cheats here.
|
@ -1,6 +1,5 @@
|
|||||||
# GZ2E01 - The Legend of Zelda: Twilight Princess
|
# GZ2E01 - The Legend of Zelda: Twilight Princess
|
||||||
[Core]
|
[Core]
|
||||||
FastDiscSpeed = 1
|
|
||||||
[EmuState]
|
[EmuState]
|
||||||
#The Emulation State.
|
#The Emulation State.
|
||||||
EmulationStateId = 4
|
EmulationStateId = 4
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
# GZ2J01 - The Legend of Zelda: Twilight Princess
|
# GZ2J01 - The Legend of Zelda: Twilight Princess
|
||||||
[Core]
|
[Core]
|
||||||
FastDiscSpeed = 1
|
|
||||||
[EmuState]
|
[EmuState]
|
||||||
#The Emulation State.
|
#The Emulation State.
|
||||||
EmulationStateId = 4
|
EmulationStateId = 4
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
# GZ2P01 - The Legend of Zelda Twilight Princess
|
# GZ2P01 - The Legend of Zelda Twilight Princess
|
||||||
[Core]
|
[Core]
|
||||||
#Values set here will override the main dolphin settings.
|
#Values set here will override the main dolphin settings.
|
||||||
FastDiscSpeed = 1
|
|
||||||
[Speedhacks]
|
[Speedhacks]
|
||||||
0x803420bc=200
|
0x803420bc=200
|
||||||
[EmuState]
|
[EmuState]
|
||||||
|
@ -55,7 +55,7 @@ namespace BootManager
|
|||||||
struct ConfigCache
|
struct ConfigCache
|
||||||
{
|
{
|
||||||
bool valid, bCPUThread, bSkipIdle, bEnableFPRF, bMMU, bDCBZOFF,
|
bool valid, bCPUThread, bSkipIdle, bEnableFPRF, bMMU, bDCBZOFF,
|
||||||
bVBeam, bFastDiscSpeed, bMergeBlocks, bDSPHLE, bHLE_BS2;
|
bVBeam, bSyncGPU, bFastDiscSpeed, bMergeBlocks, bDSPHLE, bHLE_BS2;
|
||||||
int iTLBHack, iCPUCore;
|
int iTLBHack, iCPUCore;
|
||||||
std::string strBackend;
|
std::string strBackend;
|
||||||
};
|
};
|
||||||
@ -95,6 +95,7 @@ bool BootCore(const std::string& _rFilename)
|
|||||||
config_cache.bDCBZOFF = StartUp.bDCBZOFF;
|
config_cache.bDCBZOFF = StartUp.bDCBZOFF;
|
||||||
config_cache.iTLBHack = StartUp.iTLBHack;
|
config_cache.iTLBHack = StartUp.iTLBHack;
|
||||||
config_cache.bVBeam = StartUp.bVBeam;
|
config_cache.bVBeam = StartUp.bVBeam;
|
||||||
|
config_cache.bSyncGPU = StartUp.bSyncGPU;
|
||||||
config_cache.bFastDiscSpeed = StartUp.bFastDiscSpeed;
|
config_cache.bFastDiscSpeed = StartUp.bFastDiscSpeed;
|
||||||
config_cache.bMergeBlocks = StartUp.bMergeBlocks;
|
config_cache.bMergeBlocks = StartUp.bMergeBlocks;
|
||||||
config_cache.bDSPHLE = StartUp.bDSPHLE;
|
config_cache.bDSPHLE = StartUp.bDSPHLE;
|
||||||
@ -109,6 +110,7 @@ bool BootCore(const std::string& _rFilename)
|
|||||||
game_ini.Get("Core", "TLBHack", &StartUp.iTLBHack, StartUp.iTLBHack);
|
game_ini.Get("Core", "TLBHack", &StartUp.iTLBHack, StartUp.iTLBHack);
|
||||||
game_ini.Get("Core", "DCBZ", &StartUp.bDCBZOFF, StartUp.bDCBZOFF);
|
game_ini.Get("Core", "DCBZ", &StartUp.bDCBZOFF, StartUp.bDCBZOFF);
|
||||||
game_ini.Get("Core", "VBeam", &StartUp.bVBeam, StartUp.bVBeam);
|
game_ini.Get("Core", "VBeam", &StartUp.bVBeam, StartUp.bVBeam);
|
||||||
|
game_ini.Get("Core", "SyncGPU", &StartUp.bSyncGPU, StartUp.bSyncGPU);
|
||||||
game_ini.Get("Core", "FastDiscSpeed", &StartUp.bFastDiscSpeed, StartUp.bFastDiscSpeed);
|
game_ini.Get("Core", "FastDiscSpeed", &StartUp.bFastDiscSpeed, StartUp.bFastDiscSpeed);
|
||||||
game_ini.Get("Core", "BlockMerging", &StartUp.bMergeBlocks, StartUp.bMergeBlocks);
|
game_ini.Get("Core", "BlockMerging", &StartUp.bMergeBlocks, StartUp.bMergeBlocks);
|
||||||
game_ini.Get("Core", "DSPHLE", &StartUp.bDSPHLE, StartUp.bDSPHLE);
|
game_ini.Get("Core", "DSPHLE", &StartUp.bDSPHLE, StartUp.bDSPHLE);
|
||||||
@ -168,6 +170,7 @@ void Stop()
|
|||||||
StartUp.bDCBZOFF = config_cache.bDCBZOFF;
|
StartUp.bDCBZOFF = config_cache.bDCBZOFF;
|
||||||
StartUp.iTLBHack = config_cache.iTLBHack;
|
StartUp.iTLBHack = config_cache.iTLBHack;
|
||||||
StartUp.bVBeam = config_cache.bVBeam;
|
StartUp.bVBeam = config_cache.bVBeam;
|
||||||
|
StartUp.bSyncGPU = config_cache.bSyncGPU;
|
||||||
StartUp.bFastDiscSpeed = config_cache.bFastDiscSpeed;
|
StartUp.bFastDiscSpeed = config_cache.bFastDiscSpeed;
|
||||||
StartUp.bMergeBlocks = config_cache.bMergeBlocks;
|
StartUp.bMergeBlocks = config_cache.bMergeBlocks;
|
||||||
StartUp.bDSPHLE = config_cache.bDSPHLE;
|
StartUp.bDSPHLE = config_cache.bDSPHLE;
|
||||||
|
@ -404,6 +404,7 @@ void SConfig::LoadSettings()
|
|||||||
ini.Get("Core", "MMU", &m_LocalCoreStartupParameter.bMMU, false);
|
ini.Get("Core", "MMU", &m_LocalCoreStartupParameter.bMMU, false);
|
||||||
ini.Get("Core", "TLBHack", &m_LocalCoreStartupParameter.iTLBHack, 0);
|
ini.Get("Core", "TLBHack", &m_LocalCoreStartupParameter.iTLBHack, 0);
|
||||||
ini.Get("Core", "VBeam", &m_LocalCoreStartupParameter.bVBeam, false);
|
ini.Get("Core", "VBeam", &m_LocalCoreStartupParameter.bVBeam, false);
|
||||||
|
ini.Get("Core", "SyncGPU", &m_LocalCoreStartupParameter.bSyncGPU, false);
|
||||||
ini.Get("Core", "FastDiscSpeed", &m_LocalCoreStartupParameter.bFastDiscSpeed, false);
|
ini.Get("Core", "FastDiscSpeed", &m_LocalCoreStartupParameter.bFastDiscSpeed, false);
|
||||||
ini.Get("Core", "DCBZ", &m_LocalCoreStartupParameter.bDCBZOFF, false);
|
ini.Get("Core", "DCBZ", &m_LocalCoreStartupParameter.bDCBZOFF, false);
|
||||||
ini.Get("Core", "FrameLimit", &m_Framelimit, 1); // auto frame limit by default
|
ini.Get("Core", "FrameLimit", &m_Framelimit, 1); // auto frame limit by default
|
||||||
|
@ -50,7 +50,7 @@ SCoreStartupParameter::SCoreStartupParameter()
|
|||||||
bDPL2Decoder(false), iLatency(14),
|
bDPL2Decoder(false), iLatency(14),
|
||||||
bRunCompareServer(false), bRunCompareClient(false),
|
bRunCompareServer(false), bRunCompareClient(false),
|
||||||
bMMU(false), bDCBZOFF(false), iTLBHack(0), bVBeam(false),
|
bMMU(false), bDCBZOFF(false), iTLBHack(0), bVBeam(false),
|
||||||
bFastDiscSpeed(false),
|
bSyncGPU(false), bFastDiscSpeed(false),
|
||||||
SelectedLanguage(0), bWii(false),
|
SelectedLanguage(0), bWii(false),
|
||||||
bConfirmStop(false), bHideCursor(false),
|
bConfirmStop(false), bHideCursor(false),
|
||||||
bAutoHideCursor(false), bUsePanicHandlers(true), bOnScreenDisplayMessages(true),
|
bAutoHideCursor(false), bUsePanicHandlers(true), bOnScreenDisplayMessages(true),
|
||||||
@ -78,6 +78,7 @@ void SCoreStartupParameter::LoadDefaults()
|
|||||||
bDCBZOFF = false;
|
bDCBZOFF = false;
|
||||||
iTLBHack = 0;
|
iTLBHack = 0;
|
||||||
bVBeam = false;
|
bVBeam = false;
|
||||||
|
bSyncGPU = false;
|
||||||
bFastDiscSpeed = false;
|
bFastDiscSpeed = false;
|
||||||
bMergeBlocks = false;
|
bMergeBlocks = false;
|
||||||
SelectedLanguage = 0;
|
SelectedLanguage = 0;
|
||||||
|
@ -116,6 +116,7 @@ struct SCoreStartupParameter
|
|||||||
bool bDCBZOFF;
|
bool bDCBZOFF;
|
||||||
int iTLBHack;
|
int iTLBHack;
|
||||||
bool bVBeam;
|
bool bVBeam;
|
||||||
|
bool bSyncGPU;
|
||||||
bool bFastDiscSpeed;
|
bool bFastDiscSpeed;
|
||||||
|
|
||||||
int SelectedLanguage;
|
int SelectedLanguage;
|
||||||
|
@ -145,6 +145,8 @@ static void GenerateAudioInterrupt();
|
|||||||
static void UpdateInterrupts();
|
static void UpdateInterrupts();
|
||||||
static void IncreaseSampleCount(const u32 _uAmount);
|
static void IncreaseSampleCount(const u32 _uAmount);
|
||||||
void ReadStreamBlock(s16* _pPCM);
|
void ReadStreamBlock(s16* _pPCM);
|
||||||
|
u64 GetAIPeriod();
|
||||||
|
int et_AI;
|
||||||
|
|
||||||
void Init()
|
void Init()
|
||||||
{
|
{
|
||||||
@ -159,6 +161,8 @@ void Init()
|
|||||||
|
|
||||||
g_AISSampleRate = 48000;
|
g_AISSampleRate = 48000;
|
||||||
g_AIDSampleRate = 32000;
|
g_AIDSampleRate = 32000;
|
||||||
|
|
||||||
|
et_AI = CoreTiming::RegisterEvent("AICallback", Update);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shutdown()
|
void Shutdown()
|
||||||
@ -178,11 +182,8 @@ void Read32(u32& _rReturnValue, const u32 _Address)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case AI_SAMPLE_COUNTER:
|
case AI_SAMPLE_COUNTER:
|
||||||
|
Update(0, 0);
|
||||||
_rReturnValue = m_SampleCounter;
|
_rReturnValue = m_SampleCounter;
|
||||||
// HACK - AI SRC init will do while (oldval == sample_counter) {}
|
|
||||||
// in order to pass this, we need to increment the counter whenever read
|
|
||||||
if (m_Control.PSTAT)
|
|
||||||
m_SampleCounter++;
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case AI_INTERRUPT_TIMING:
|
case AI_INTERRUPT_TIMING:
|
||||||
@ -236,6 +237,9 @@ void Write32(const u32 _Value, const u32 _Address)
|
|||||||
|
|
||||||
// Tell Drive Interface to start/stop streaming
|
// Tell Drive Interface to start/stop streaming
|
||||||
DVDInterface::g_bStream = tmpAICtrl.PSTAT;
|
DVDInterface::g_bStream = tmpAICtrl.PSTAT;
|
||||||
|
|
||||||
|
CoreTiming::RemoveEvent(et_AI);
|
||||||
|
CoreTiming::ScheduleEvent(((int)GetAIPeriod() / 2), et_AI);
|
||||||
}
|
}
|
||||||
|
|
||||||
// AI Interrupt
|
// AI Interrupt
|
||||||
@ -271,6 +275,8 @@ void Write32(const u32 _Value, const u32 _Address)
|
|||||||
|
|
||||||
case AI_INTERRUPT_TIMING:
|
case AI_INTERRUPT_TIMING:
|
||||||
m_InterruptTiming = _Value;
|
m_InterruptTiming = _Value;
|
||||||
|
CoreTiming::RemoveEvent(et_AI);
|
||||||
|
CoreTiming::ScheduleEvent(((int)GetAIPeriod() / 2), et_AI);
|
||||||
DEBUG_LOG(AUDIO_INTERFACE, "Set interrupt: %08x samples", m_InterruptTiming);
|
DEBUG_LOG(AUDIO_INTERFACE, "Set interrupt: %08x samples", m_InterruptTiming);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -448,7 +454,7 @@ unsigned int GetAIDSampleRate()
|
|||||||
return g_AIDSampleRate;
|
return g_AIDSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Update()
|
void Update(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
if (m_Control.PSTAT)
|
if (m_Control.PSTAT)
|
||||||
{
|
{
|
||||||
@ -459,7 +465,16 @@ void Update()
|
|||||||
g_LastCPUTime += Samples * g_CPUCyclesPerSample;
|
g_LastCPUTime += Samples * g_CPUCyclesPerSample;
|
||||||
IncreaseSampleCount(Samples);
|
IncreaseSampleCount(Samples);
|
||||||
}
|
}
|
||||||
|
CoreTiming::ScheduleEvent(((int)GetAIPeriod() / 2) - cyclesLate, et_AI);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u64 GetAIPeriod()
|
||||||
|
{
|
||||||
|
u64 period = g_CPUCyclesPerSample * m_InterruptTiming;
|
||||||
|
if (period == 0)
|
||||||
|
period = 32000 * g_CPUCyclesPerSample;
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
} // end of namespace AudioInterface
|
} // end of namespace AudioInterface
|
||||||
|
@ -31,7 +31,7 @@ void Init();
|
|||||||
void Shutdown();
|
void Shutdown();
|
||||||
void DoState(PointerWrap &p);
|
void DoState(PointerWrap &p);
|
||||||
|
|
||||||
void Update();
|
void Update(u64 userdata, int cyclesLate);
|
||||||
|
|
||||||
// Called by DSP emulator
|
// Called by DSP emulator
|
||||||
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate);
|
void Callback_GetSampleRate(unsigned int &_AISampleRate, unsigned int &_DACSampleRate);
|
||||||
|
@ -436,10 +436,6 @@ void Write16(const u16 _Value, const u32 _Address)
|
|||||||
if (tmpControl.ARAM) g_dspState.DSPControl.ARAM = 0;
|
if (tmpControl.ARAM) g_dspState.DSPControl.ARAM = 0;
|
||||||
if (tmpControl.DSP) g_dspState.DSPControl.DSP = 0;
|
if (tmpControl.DSP) g_dspState.DSPControl.DSP = 0;
|
||||||
|
|
||||||
// Tracking DMAState fixes Knockout Kings 2003 in DSP HLE mode
|
|
||||||
if (GetDSPEmulator()->IsLLE())
|
|
||||||
g_dspState.DSPControl.DMAState = 0; // keep g_ARAM DMA State zero
|
|
||||||
|
|
||||||
// unknown
|
// unknown
|
||||||
g_dspState.DSPControl.unk3 = tmpControl.unk3;
|
g_dspState.DSPControl.unk3 = tmpControl.unk3;
|
||||||
g_dspState.DSPControl.pad = tmpControl.pad;
|
g_dspState.DSPControl.pad = tmpControl.pad;
|
||||||
@ -691,7 +687,7 @@ void UpdateAudioDMA()
|
|||||||
{
|
{
|
||||||
// Send silence. Yeah, it's a bit of a waste to sample rate convert
|
// Send silence. Yeah, it's a bit of a waste to sample rate convert
|
||||||
// silence. or hm. Maybe we shouldn't do this :)
|
// silence. or hm. Maybe we shouldn't do this :)
|
||||||
//dsp_emulator->DSP_SendAIBuffer(0, AudioInterface::GetAIDSampleRate());
|
dsp_emulator->DSP_SendAIBuffer(0, AudioInterface::GetAIDSampleRate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,7 +697,10 @@ void Do_ARAM_DMA()
|
|||||||
if (!GetDSPEmulator()->IsLLE())
|
if (!GetDSPEmulator()->IsLLE())
|
||||||
g_dspState.DSPControl.DMAState = 1;
|
g_dspState.DSPControl.DMAState = 1;
|
||||||
|
|
||||||
GenerateDSPInterrupt(INT_ARAM, true);
|
if (g_arDMA.Cnt.dir || g_arDMA.Cnt.count > 10240)
|
||||||
|
CoreTiming::ScheduleEvent_Threadsafe(0, et_GenerateDSPInterrupt, INT_ARAM | (1<<16));
|
||||||
|
else
|
||||||
|
GenerateDSPInterrupt(INT_ARAM);
|
||||||
|
|
||||||
// Real hardware DMAs in 32byte chunks, but we can get by with 8byte chunks
|
// Real hardware DMAs in 32byte chunks, but we can get by with 8byte chunks
|
||||||
if (g_arDMA.Cnt.dir)
|
if (g_arDMA.Cnt.dir)
|
||||||
|
@ -32,11 +32,7 @@
|
|||||||
#include "../Movie.h"
|
#include "../Movie.h"
|
||||||
|
|
||||||
// Disc transfer rate measured in bytes per second
|
// Disc transfer rate measured in bytes per second
|
||||||
static const u32 DISC_TRANSFER_RATE_GC = 3125 * 1024;
|
static const u32 DISC_TRANSFER_RATE_GC = 4 * 1024 * 1024;
|
||||||
static const u32 DISC_TRANSFER_RATE_WII = 7926 * 1024;
|
|
||||||
|
|
||||||
// Disc access time measured in milliseconds
|
|
||||||
static const u32 DISC_ACCESS_TIME_MS = 128;
|
|
||||||
|
|
||||||
namespace DVDInterface
|
namespace DVDInterface
|
||||||
{
|
{
|
||||||
@ -501,8 +497,7 @@ void Write32(const u32 _iValue, const u32 _iAddress)
|
|||||||
if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
|
if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bFastDiscSpeed)
|
||||||
{
|
{
|
||||||
u64 ticksUntilTC = m_DILENGTH.Length *
|
u64 ticksUntilTC = m_DILENGTH.Length *
|
||||||
(SystemTimers::GetTicksPerSecond() / (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii?DISC_TRANSFER_RATE_WII:DISC_TRANSFER_RATE_GC)) +
|
(SystemTimers::GetTicksPerSecond() / (SConfig::GetInstance().m_LocalCoreStartupParameter.bWii ? 1 : DISC_TRANSFER_RATE_GC));
|
||||||
(SystemTimers::GetTicksPerSecond() * DISC_ACCESS_TIME_MS / 1000);
|
|
||||||
CoreTiming::ScheduleEvent((int)ticksUntilTC, tc);
|
CoreTiming::ScheduleEvent((int)ticksUntilTC, tc);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -25,9 +25,9 @@
|
|||||||
#define EXI_WRITE 1
|
#define EXI_WRITE 1
|
||||||
#define EXI_READWRITE 2
|
#define EXI_READWRITE 2
|
||||||
|
|
||||||
|
|
||||||
#include "ProcessorInterface.h"
|
#include "ProcessorInterface.h"
|
||||||
#include "../PowerPC/PowerPC.h"
|
#include "../PowerPC/PowerPC.h"
|
||||||
|
#include "CoreTiming.h"
|
||||||
|
|
||||||
CEXIChannel::CEXIChannel(u32 ChannelId) :
|
CEXIChannel::CEXIChannel(u32 ChannelId) :
|
||||||
m_DMAMemoryAddress(0),
|
m_DMAMemoryAddress(0),
|
||||||
@ -45,6 +45,8 @@ CEXIChannel::CEXIChannel(u32 ChannelId) :
|
|||||||
|
|
||||||
for (int i = 0; i < NUM_DEVICES; i++)
|
for (int i = 0; i < NUM_DEVICES; i++)
|
||||||
m_pDevices[i] = EXIDevice_Create(EXIDEVICE_NONE, m_ChannelId);
|
m_pDevices[i] = EXIDevice_Create(EXIDEVICE_NONE, m_ChannelId);
|
||||||
|
|
||||||
|
updateInterrupts = CoreTiming::RegisterEvent("EXIInterrupt", UpdateInterrupts);
|
||||||
}
|
}
|
||||||
|
|
||||||
CEXIChannel::~CEXIChannel()
|
CEXIChannel::~CEXIChannel()
|
||||||
@ -88,12 +90,12 @@ void CEXIChannel::AddDevice(IEXIDevice* pDevice, const int device_num, bool noti
|
|||||||
if (m_ChannelId != 2)
|
if (m_ChannelId != 2)
|
||||||
{
|
{
|
||||||
m_Status.EXTINT = 1;
|
m_Status.EXTINT = 1;
|
||||||
UpdateInterrupts();
|
CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CEXIChannel::UpdateInterrupts()
|
void CEXIChannel::UpdateInterrupts(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
ExpansionInterface::UpdateInterrupts();
|
ExpansionInterface::UpdateInterrupts();
|
||||||
}
|
}
|
||||||
@ -149,7 +151,9 @@ void CEXIChannel::Read32(u32& _uReturnValue, const u32 _iRegister)
|
|||||||
if (m_ChannelId == 2)
|
if (m_ChannelId == 2)
|
||||||
m_Status.EXT = 0;
|
m_Status.EXT = 0;
|
||||||
else
|
else
|
||||||
|
{
|
||||||
m_Status.EXT = GetDevice(1)->IsPresent() ? 1 : 0;
|
m_Status.EXT = GetDevice(1)->IsPresent() ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
_uReturnValue = m_Status.Hex;
|
_uReturnValue = m_Status.Hex;
|
||||||
break;
|
break;
|
||||||
@ -213,7 +217,7 @@ void CEXIChannel::Write32(const u32 _iValue, const u32 _iRegister)
|
|||||||
if (pDevice != NULL)
|
if (pDevice != NULL)
|
||||||
pDevice->SetCS(m_Status.CHIP_SELECT);
|
pDevice->SetCS(m_Status.CHIP_SELECT);
|
||||||
|
|
||||||
UpdateInterrupts();
|
CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -264,7 +268,7 @@ void CEXIChannel::Write32(const u32 _iValue, const u32 _iRegister)
|
|||||||
if(!m_Control.TSTART) // completed !
|
if(!m_Control.TSTART) // completed !
|
||||||
{
|
{
|
||||||
m_Status.TCINT = 1;
|
m_Status.TCINT = 1;
|
||||||
UpdateInterrupts();
|
CoreTiming::ScheduleEvent_Threadsafe_Immediate(updateInterrupts, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -110,6 +110,9 @@ private:
|
|||||||
// Since channels operate a bit differently from each other
|
// Since channels operate a bit differently from each other
|
||||||
u32 m_ChannelId;
|
u32 m_ChannelId;
|
||||||
|
|
||||||
|
int updateInterrupts;
|
||||||
|
|
||||||
|
static void UpdateInterrupts(u64 userdata, int cyclesLate);
|
||||||
public:
|
public:
|
||||||
// get device
|
// get device
|
||||||
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
|
IEXIDevice* GetDevice(const u8 _CHIP_SELECT);
|
||||||
@ -129,7 +132,6 @@ public:
|
|||||||
|
|
||||||
void Update();
|
void Update();
|
||||||
bool IsCausingInterrupt();
|
bool IsCausingInterrupt();
|
||||||
void UpdateInterrupts();
|
|
||||||
void DoState(PointerWrap &p);
|
void DoState(PointerWrap &p);
|
||||||
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
|
void PauseAndLock(bool doLock, bool unpauseOnUnlock);
|
||||||
|
|
||||||
|
@ -47,6 +47,14 @@ void CEXIMemoryCard::FlushCallback(u64 userdata, int cyclesLate)
|
|||||||
pThis->Flush();
|
pThis->Flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CEXIMemoryCard::CmdDoneCallback(u64 userdata, int cyclesLate)
|
||||||
|
{
|
||||||
|
int card_index = (int)userdata;
|
||||||
|
CEXIMemoryCard* pThis = (CEXIMemoryCard*)ExpansionInterface::FindDevice(EXIDEVICE_MEMORYCARD, card_index);
|
||||||
|
if (pThis)
|
||||||
|
pThis->CmdDone();
|
||||||
|
}
|
||||||
|
|
||||||
CEXIMemoryCard::CEXIMemoryCard(const int index)
|
CEXIMemoryCard::CEXIMemoryCard(const int index)
|
||||||
: card_index(index)
|
: card_index(index)
|
||||||
, m_bDirty(false)
|
, m_bDirty(false)
|
||||||
@ -56,7 +64,8 @@ CEXIMemoryCard::CEXIMemoryCard(const int index)
|
|||||||
m_strFilename = "Movie.raw";
|
m_strFilename = "Movie.raw";
|
||||||
|
|
||||||
// we're potentially leaking events here, since there's no UnregisterEvent until emu shutdown, but I guess it's inconsequential
|
// we're potentially leaking events here, since there's no UnregisterEvent until emu shutdown, but I guess it's inconsequential
|
||||||
et_this_card = CoreTiming::RegisterEvent((card_index == 0) ? "memcardA" : "memcardB", FlushCallback);
|
et_this_card = CoreTiming::RegisterEvent((card_index == 0) ? "memcardFlushA" : "memcardFlushB", FlushCallback);
|
||||||
|
et_cmd_done = CoreTiming::RegisterEvent((card_index == 0) ? "memcardDoneA" : "memcardDoneB", CmdDoneCallback);
|
||||||
|
|
||||||
interruptSwitch = 0;
|
interruptSwitch = 0;
|
||||||
m_bInterruptSet = 0;
|
m_bInterruptSet = 0;
|
||||||
@ -178,6 +187,21 @@ bool CEXIMemoryCard::IsPresent()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CEXIMemoryCard::CmdDone()
|
||||||
|
{
|
||||||
|
status |= MC_STATUS_READY;
|
||||||
|
status &= ~MC_STATUS_BUSY;
|
||||||
|
|
||||||
|
m_bInterruptSet = 1;
|
||||||
|
m_bDirty = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CEXIMemoryCard::CmdDoneLater(u64 cycles)
|
||||||
|
{
|
||||||
|
CoreTiming::RemoveEvent(et_cmd_done);
|
||||||
|
CoreTiming::ScheduleEvent(cycles, et_cmd_done, (u64)card_index);
|
||||||
|
}
|
||||||
|
|
||||||
void CEXIMemoryCard::SetCS(int cs)
|
void CEXIMemoryCard::SetCS(int cs)
|
||||||
{
|
{
|
||||||
// So that memory card won't be invalidated during flushing
|
// So that memory card won't be invalidated during flushing
|
||||||
@ -201,11 +225,7 @@ void CEXIMemoryCard::SetCS(int cs)
|
|||||||
|
|
||||||
//???
|
//???
|
||||||
|
|
||||||
status |= MC_STATUS_READY;
|
CmdDoneLater(5000);
|
||||||
status &= ~MC_STATUS_BUSY;
|
|
||||||
|
|
||||||
m_bInterruptSet = 1;
|
|
||||||
m_bDirty = true;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -232,11 +252,7 @@ void CEXIMemoryCard::SetCS(int cs)
|
|||||||
address = (address & ~0x1FF) | ((address+1) & 0x1FF);
|
address = (address & ~0x1FF) | ((address+1) & 0x1FF);
|
||||||
}
|
}
|
||||||
|
|
||||||
status |= MC_STATUS_READY;
|
CmdDoneLater(5000);
|
||||||
status &= ~MC_STATUS_BUSY;
|
|
||||||
|
|
||||||
m_bInterruptSet = 1;
|
|
||||||
m_bDirty = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec)
|
// Page written to memory card, not just to buffer - let's schedule a flush 0.5b cycles into the future (1 sec)
|
||||||
|
@ -47,9 +47,18 @@ private:
|
|||||||
// through the userdata parameter, so that it can then call Flush on the right card.
|
// through the userdata parameter, so that it can then call Flush on the right card.
|
||||||
static void FlushCallback(u64 userdata, int cyclesLate);
|
static void FlushCallback(u64 userdata, int cyclesLate);
|
||||||
|
|
||||||
|
// Scheduled when a command that required delayed end signaling is done.
|
||||||
|
static void CmdDoneCallback(u64 userdata, int cyclesLate);
|
||||||
|
|
||||||
// Flushes the memory card contents to disk.
|
// Flushes the memory card contents to disk.
|
||||||
void Flush(bool exiting = false);
|
void Flush(bool exiting = false);
|
||||||
|
|
||||||
|
// Signals that the command that was previously executed is now done.
|
||||||
|
void CmdDone();
|
||||||
|
|
||||||
|
// Variant of CmdDone which schedules an event later in the future to complete the command.
|
||||||
|
void CmdDoneLater(u64 cycles);
|
||||||
|
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
cmdNintendoID = 0x00,
|
cmdNintendoID = 0x00,
|
||||||
@ -71,7 +80,7 @@ private:
|
|||||||
|
|
||||||
std::string m_strFilename;
|
std::string m_strFilename;
|
||||||
int card_index;
|
int card_index;
|
||||||
int et_this_card;
|
int et_this_card, et_cmd_done;
|
||||||
//! memory card state
|
//! memory card state
|
||||||
|
|
||||||
// STATE_TO_SAVE
|
// STATE_TO_SAVE
|
||||||
|
@ -98,7 +98,11 @@ void STACKALIGN CheckGatherPipe()
|
|||||||
memmove(m_gatherPipe, m_gatherPipe + cnt, m_gatherPipeCount);
|
memmove(m_gatherPipe, m_gatherPipe + cnt, m_gatherPipeCount);
|
||||||
|
|
||||||
// Profile where the FIFO writes are occurring.
|
// Profile where the FIFO writes are occurring.
|
||||||
if (jit && (jit->js.fifoWriteAddresses.find(PC)) == (jit->js.fifoWriteAddresses.end()))
|
if (jit && PC != 0 && (jit->js.fifoWriteAddresses.find(PC)) == (jit->js.fifoWriteAddresses.end()))
|
||||||
|
{
|
||||||
|
// Log only stores, fp stores and ps stores, filtering out other instructions arrived via optimizeGatherPipe
|
||||||
|
int type = GetOpInfo(Memory::ReadUnchecked_U32(PC))->type;
|
||||||
|
if (type == OPTYPE_STORE || type == OPTYPE_STOREFP || (type == OPTYPE_PS && GetOpInfo(Memory::ReadUnchecked_U32(PC))->opname=="psq_st"))
|
||||||
{
|
{
|
||||||
jit->js.fifoWriteAddresses.insert(PC);
|
jit->js.fifoWriteAddresses.insert(PC);
|
||||||
|
|
||||||
@ -107,6 +111,7 @@ void STACKALIGN CheckGatherPipe()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void Write8(const u8 _iValue, const u32 _iAddress)
|
void Write8(const u8 _iValue, const u32 _iAddress)
|
||||||
{
|
{
|
||||||
|
@ -99,7 +99,7 @@ void Init()
|
|||||||
m_ResetCode = 0x80000000; // Cold reset
|
m_ResetCode = 0x80000000; // Cold reset
|
||||||
m_InterruptCause = INT_CAUSE_RST_BUTTON | INT_CAUSE_VI;
|
m_InterruptCause = INT_CAUSE_RST_BUTTON | INT_CAUSE_VI;
|
||||||
|
|
||||||
toggleResetButton = CoreTiming::RegisterEvent("ToggleResetButton", &ToggleResetButtonCallback);
|
toggleResetButton = CoreTiming::RegisterEvent("ToggleResetButton", ToggleResetButtonCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Read16(u16& _uReturnValue, const u32 _iAddress)
|
void Read16(u16& _uReturnValue, const u32 _iAddress)
|
||||||
|
@ -69,6 +69,19 @@ int CSIDevice_GCController::RunBuffer(u8* _pBuffer, int _iLength)
|
|||||||
*(u32*)&_pBuffer[0] = SI_GC_CONTROLLER;
|
*(u32*)&_pBuffer[0] = SI_GC_CONTROLLER;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case CMD_DIRECT:
|
||||||
|
{
|
||||||
|
INFO_LOG(SERIALINTERFACE, "PAD - Direct (Length: %d)", _iLength);
|
||||||
|
u32 high, low;
|
||||||
|
GetData(high, low);
|
||||||
|
for (int i = 0; i < (_iLength - 1) / 2; i++)
|
||||||
|
{
|
||||||
|
_pBuffer[0 + i] = (high >> (i * 8)) & 0xff;
|
||||||
|
_pBuffer[4 + i] = (low >> (i * 8)) & 0xff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case CMD_ORIGIN:
|
case CMD_ORIGIN:
|
||||||
{
|
{
|
||||||
INFO_LOG(SERIALINTERFACE, "PAD - Get Origin");
|
INFO_LOG(SERIALINTERFACE, "PAD - Get Origin");
|
||||||
@ -96,7 +109,7 @@ int CSIDevice_GCController::RunBuffer(u8* _pBuffer, int _iLength)
|
|||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
ERROR_LOG(SERIALINTERFACE, "unknown SI command (0x%x)", command);
|
ERROR_LOG(SERIALINTERFACE, "unknown SI command (0x%x)", command);
|
||||||
PanicAlert("SI: Unknown command");
|
PanicAlert("SI: Unknown command (0x%x)", command);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ private:
|
|||||||
enum EBufferCommands
|
enum EBufferCommands
|
||||||
{
|
{
|
||||||
CMD_RESET = 0x00,
|
CMD_RESET = 0x00,
|
||||||
|
CMD_DIRECT = 0x40,
|
||||||
CMD_ORIGIN = 0x41,
|
CMD_ORIGIN = 0x41,
|
||||||
CMD_RECALIBRATE = 0x42,
|
CMD_RECALIBRATE = 0x42,
|
||||||
};
|
};
|
||||||
|
@ -74,6 +74,7 @@ IPC_HLE_PERIOD: For the Wiimote this is the call schedule:
|
|||||||
#include "Thread.h"
|
#include "Thread.h"
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
#include "VideoBackendBase.h"
|
#include "VideoBackendBase.h"
|
||||||
|
#include "CommandProcessor.h"
|
||||||
|
|
||||||
|
|
||||||
namespace SystemTimers
|
namespace SystemTimers
|
||||||
@ -109,7 +110,7 @@ enum
|
|||||||
int et_Dec;
|
int et_Dec;
|
||||||
int et_VI;
|
int et_VI;
|
||||||
int et_SI;
|
int et_SI;
|
||||||
int et_AI;
|
int et_CP;
|
||||||
int et_AudioDMA;
|
int et_AudioDMA;
|
||||||
int et_DSP;
|
int et_DSP;
|
||||||
int et_IPC_HLE;
|
int et_IPC_HLE;
|
||||||
@ -118,15 +119,15 @@ int et_PatchEngine; // PatchEngine updates every 1/60th of a second by default
|
|||||||
// These are badly educated guesses
|
// These are badly educated guesses
|
||||||
// Feel free to experiment. Set these in Init below.
|
// Feel free to experiment. Set these in Init below.
|
||||||
int
|
int
|
||||||
// This one should simply be determined by the increasing counter in AI.
|
|
||||||
AI_PERIOD,
|
|
||||||
|
|
||||||
// These shouldn't be period controlled either, most likely.
|
// These shouldn't be period controlled either, most likely.
|
||||||
DSP_PERIOD,
|
DSP_PERIOD,
|
||||||
|
|
||||||
// This is a fixed value, don't change it
|
// This is a fixed value, don't change it
|
||||||
AUDIO_DMA_PERIOD,
|
AUDIO_DMA_PERIOD,
|
||||||
|
|
||||||
|
// Regulates the speed of the Command Processor
|
||||||
|
CP_PERIOD,
|
||||||
|
|
||||||
// This is completely arbitrary. If we find that we need lower latency, we can just
|
// This is completely arbitrary. If we find that we need lower latency, we can just
|
||||||
// increase this number.
|
// increase this number.
|
||||||
IPC_HLE_PERIOD;
|
IPC_HLE_PERIOD;
|
||||||
@ -143,12 +144,6 @@ u32 ConvertMillisecondsToTicks(u32 _Milliseconds)
|
|||||||
return GetTicksPerSecond() / 1000 * _Milliseconds;
|
return GetTicksPerSecond() / 1000 * _Milliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AICallback(u64 userdata, int cyclesLate)
|
|
||||||
{
|
|
||||||
AudioInterface::Update();
|
|
||||||
CoreTiming::ScheduleEvent(AI_PERIOD - cyclesLate, et_AI);
|
|
||||||
}
|
|
||||||
|
|
||||||
// DSP/CPU timeslicing.
|
// DSP/CPU timeslicing.
|
||||||
void DSPCallback(u64 userdata, int cyclesLate)
|
void DSPCallback(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
@ -187,6 +182,12 @@ void SICallback(u64 userdata, int cyclesLate)
|
|||||||
CoreTiming::ScheduleEvent(SerialInterface::GetTicksToNextSIPoll() - cyclesLate, et_SI);
|
CoreTiming::ScheduleEvent(SerialInterface::GetTicksToNextSIPoll() - cyclesLate, et_SI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void CPCallback(u64 userdata, int cyclesLate)
|
||||||
|
{
|
||||||
|
CommandProcessor::Update();
|
||||||
|
CoreTiming::ScheduleEvent(CP_PERIOD - cyclesLate, et_CP);
|
||||||
|
}
|
||||||
|
|
||||||
void DecrementerCallback(u64 userdata, int cyclesLate)
|
void DecrementerCallback(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
PowerPC::ppcState.spr[SPR_DEC] = 0xFFFFFFFF;
|
PowerPC::ppcState.spr[SPR_DEC] = 0xFFFFFFFF;
|
||||||
@ -266,12 +267,12 @@ void Init()
|
|||||||
if (DSP::GetDSPEmulator()->IsLLE())
|
if (DSP::GetDSPEmulator()->IsLLE())
|
||||||
DSP_PERIOD = 12000; // TO BE TWEAKED
|
DSP_PERIOD = 12000; // TO BE TWEAKED
|
||||||
|
|
||||||
// This is the biggest question mark.
|
|
||||||
AI_PERIOD = GetTicksPerSecond() / 80;
|
|
||||||
|
|
||||||
// System internal sample rate is fixed at 32KHz * 4 (16bit Stereo) / 32 bytes DMA
|
// System internal sample rate is fixed at 32KHz * 4 (16bit Stereo) / 32 bytes DMA
|
||||||
AUDIO_DMA_PERIOD = CPU_CORE_CLOCK / (AudioInterface::GetAIDSampleRate() * 4 / 32);
|
AUDIO_DMA_PERIOD = CPU_CORE_CLOCK / (AudioInterface::GetAIDSampleRate() * 4 / 32);
|
||||||
|
|
||||||
|
// Emulated gekko <-> flipper bus speed ratio (cpu clock / flipper clock)
|
||||||
|
CP_PERIOD = GetTicksPerSecond() / 10000;
|
||||||
|
|
||||||
Common::Timer::IncreaseResolution();
|
Common::Timer::IncreaseResolution();
|
||||||
// store and convert localtime at boot to timebase ticks
|
// store and convert localtime at boot to timebase ticks
|
||||||
CoreTiming::SetFakeTBStartValue((u64)(CPU_CORE_CLOCK / TIMER_RATIO) * (u64)CEXIIPL::GetGCTime());
|
CoreTiming::SetFakeTBStartValue((u64)(CPU_CORE_CLOCK / TIMER_RATIO) * (u64)CEXIIPL::GetGCTime());
|
||||||
@ -281,19 +282,21 @@ void Init()
|
|||||||
CoreTiming::SetFakeDecStartTicks(CoreTiming::GetTicks());
|
CoreTiming::SetFakeDecStartTicks(CoreTiming::GetTicks());
|
||||||
|
|
||||||
et_Dec = CoreTiming::RegisterEvent("DecCallback", DecrementerCallback);
|
et_Dec = CoreTiming::RegisterEvent("DecCallback", DecrementerCallback);
|
||||||
et_AI = CoreTiming::RegisterEvent("AICallback", AICallback);
|
|
||||||
et_VI = CoreTiming::RegisterEvent("VICallback", VICallback);
|
et_VI = CoreTiming::RegisterEvent("VICallback", VICallback);
|
||||||
et_SI = CoreTiming::RegisterEvent("SICallback", SICallback);
|
et_SI = CoreTiming::RegisterEvent("SICallback", SICallback);
|
||||||
|
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU)
|
||||||
|
et_CP = CoreTiming::RegisterEvent("CPCallback", CPCallback);
|
||||||
et_DSP = CoreTiming::RegisterEvent("DSPCallback", DSPCallback);
|
et_DSP = CoreTiming::RegisterEvent("DSPCallback", DSPCallback);
|
||||||
et_AudioDMA = CoreTiming::RegisterEvent("AudioDMACallback", AudioDMACallback);
|
et_AudioDMA = CoreTiming::RegisterEvent("AudioDMACallback", AudioDMACallback);
|
||||||
et_IPC_HLE = CoreTiming::RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback);
|
et_IPC_HLE = CoreTiming::RegisterEvent("IPC_HLE_UpdateCallback", IPC_HLE_UpdateCallback);
|
||||||
et_PatchEngine = CoreTiming::RegisterEvent("PatchEngine", PatchEngineCallback);
|
et_PatchEngine = CoreTiming::RegisterEvent("PatchEngine", PatchEngineCallback);
|
||||||
|
|
||||||
CoreTiming::ScheduleEvent(AI_PERIOD, et_AI);
|
|
||||||
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerLine(), et_VI);
|
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerLine(), et_VI);
|
||||||
CoreTiming::ScheduleEvent(DSP_PERIOD, et_DSP);
|
CoreTiming::ScheduleEvent(DSP_PERIOD, et_DSP);
|
||||||
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerFrame(), et_SI);
|
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerFrame(), et_SI);
|
||||||
CoreTiming::ScheduleEvent(AUDIO_DMA_PERIOD, et_AudioDMA);
|
CoreTiming::ScheduleEvent(AUDIO_DMA_PERIOD, et_AudioDMA);
|
||||||
|
if (SConfig::GetInstance().m_LocalCoreStartupParameter.bSyncGPU)
|
||||||
|
CoreTiming::ScheduleEvent(CP_PERIOD, et_CP);
|
||||||
|
|
||||||
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerFrame(), et_PatchEngine);
|
CoreTiming::ScheduleEvent(VideoInterface::GetTicksPerFrame(), et_PatchEngine);
|
||||||
|
|
||||||
|
@ -193,6 +193,7 @@ void Interpreter::mtmsr(UGeckoInstruction _inst)
|
|||||||
{
|
{
|
||||||
// Privileged?
|
// Privileged?
|
||||||
MSR = m_GPR[_inst.RS];
|
MSR = m_GPR[_inst.RS];
|
||||||
|
PowerPC::CheckExceptions();
|
||||||
m_EndBlock = true;
|
m_EndBlock = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -291,7 +291,9 @@ static void ImHere()
|
|||||||
void Jit64::Cleanup()
|
void Jit64::Cleanup()
|
||||||
{
|
{
|
||||||
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock > 0)
|
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock > 0)
|
||||||
|
{
|
||||||
ABI_CallFunction((void *)&GPFifo::CheckGatherPipe);
|
ABI_CallFunction((void *)&GPFifo::CheckGatherPipe);
|
||||||
|
}
|
||||||
|
|
||||||
// SPEED HACK: MMCR0/MMCR1 should be checked at run-time, not at compile time.
|
// SPEED HACK: MMCR0/MMCR1 should be checked at run-time, not at compile time.
|
||||||
if (MMCR0.Hex || MMCR1.Hex)
|
if (MMCR0.Hex || MMCR1.Hex)
|
||||||
@ -301,6 +303,7 @@ void Jit64::Cleanup()
|
|||||||
void Jit64::WriteExit(u32 destination, int exit_num)
|
void Jit64::WriteExit(u32 destination, int exit_num)
|
||||||
{
|
{
|
||||||
Cleanup();
|
Cleanup();
|
||||||
|
|
||||||
SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount));
|
SUB(32, M(&CoreTiming::downcount), js.downcountAmount > 127 ? Imm32(js.downcountAmount) : Imm8(js.downcountAmount));
|
||||||
|
|
||||||
//If nobody has taken care of this yet (this can be removed when all branches are done)
|
//If nobody has taken care of this yet (this can be removed when all branches are done)
|
||||||
@ -561,6 +564,7 @@ const u8* Jit64::DoJit(u32 em_address, PPCAnalyst::CodeBuffer *code_buf, JitBloc
|
|||||||
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock >= 32)
|
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock >= 32)
|
||||||
{
|
{
|
||||||
js.fifoBytesThisBlock -= 32;
|
js.fifoBytesThisBlock -= 32;
|
||||||
|
MOV(32, M(&PC), Imm32(jit->js.compilerPC)); // Helps external systems know which instruction triggered the write
|
||||||
ABI_CallFunction(thunks.ProtectFunction((void *)&GPFifo::CheckGatherPipe, 0));
|
ABI_CallFunction(thunks.ProtectFunction((void *)&GPFifo::CheckGatherPipe, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,6 +302,7 @@ void Jit64::stX(UGeckoInstruction inst)
|
|||||||
addr += offset;
|
addr += offset;
|
||||||
if ((addr & 0xFFFFF000) == 0xCC008000 && jo.optimizeGatherPipe)
|
if ((addr & 0xFFFFF000) == 0xCC008000 && jo.optimizeGatherPipe)
|
||||||
{
|
{
|
||||||
|
MOV(32, M(&PC), Imm32(jit->js.compilerPC)); // Helps external systems know which instruction triggered the write
|
||||||
gpr.FlushLockX(ABI_PARAM1);
|
gpr.FlushLockX(ABI_PARAM1);
|
||||||
MOV(32, R(ABI_PARAM1), gpr.R(s));
|
MOV(32, R(ABI_PARAM1), gpr.R(s));
|
||||||
if (update)
|
if (update)
|
||||||
@ -329,6 +330,7 @@ void Jit64::stX(UGeckoInstruction inst)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
MOV(32, M(&PC), Imm32(jit->js.compilerPC)); // Helps external systems know which instruction triggered the write
|
||||||
switch (accessSize)
|
switch (accessSize)
|
||||||
{
|
{
|
||||||
case 32: ABI_CallFunctionAC(thunks.ProtectFunction(true ? ((void *)&Memory::Write_U32) : ((void *)&Memory::Write_U32_Swap), 2), gpr.R(s), addr); break;
|
case 32: ABI_CallFunctionAC(thunks.ProtectFunction(true ? ((void *)&Memory::Write_U32) : ((void *)&Memory::Write_U32_Swap), 2), gpr.R(s), addr); break;
|
||||||
|
@ -42,6 +42,7 @@ const u8 GC_ALIGNED16(pbswapShuffle2x4[16]) = {3, 2, 1, 0, 7, 6, 5, 4, 8, 9, 10,
|
|||||||
#if 0
|
#if 0
|
||||||
static void WriteDual32(u64 value, u32 address)
|
static void WriteDual32(u64 value, u32 address)
|
||||||
{
|
{
|
||||||
|
MOV(32, M(&PC), Imm32(jit->js.compilerPC)); // Helps external systems know which instruction triggered the write
|
||||||
Memory::Write_U32((u32)(value >> 32), address);
|
Memory::Write_U32((u32)(value >> 32), address);
|
||||||
Memory::Write_U32((u32)value, address + 4);
|
Memory::Write_U32((u32)value, address + 4);
|
||||||
}
|
}
|
||||||
|
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "Jit.h"
|
#include "Jit.h"
|
||||||
#include "JitRegCache.h"
|
#include "JitRegCache.h"
|
||||||
|
#include "HW/ProcessorInterface.h"
|
||||||
|
|
||||||
void Jit64::mtspr(UGeckoInstruction inst)
|
void Jit64::mtspr(UGeckoInstruction inst)
|
||||||
{
|
{
|
||||||
@ -122,7 +123,29 @@ void Jit64::mtmsr(UGeckoInstruction inst)
|
|||||||
gpr.UnlockAll();
|
gpr.UnlockAll();
|
||||||
gpr.Flush(FLUSH_ALL);
|
gpr.Flush(FLUSH_ALL);
|
||||||
fpr.Flush(FLUSH_ALL);
|
fpr.Flush(FLUSH_ALL);
|
||||||
|
|
||||||
|
// If some exceptions are pending and EE are now enabled, force checking
|
||||||
|
// external exceptions when going out of mtmsr in order to execute delayed
|
||||||
|
// interrupts as soon as possible.
|
||||||
|
TEST(32, M(&MSR), Imm32(0x8000));
|
||||||
|
FixupBranch eeDisabled = J_CC(CC_Z);
|
||||||
|
|
||||||
|
TEST(32, M((void*)&PowerPC::ppcState.Exceptions), Imm32(EXCEPTION_EXTERNAL_INT | EXCEPTION_PERFORMANCE_MONITOR | EXCEPTION_DECREMENTER));
|
||||||
|
FixupBranch noExceptionsPending = J_CC(CC_Z);
|
||||||
|
|
||||||
|
// Check if a CP interrupt is waiting and keep the GPU emulation in sync (issue 4336)
|
||||||
|
TEST(32, M((void *)&ProcessorInterface::m_InterruptCause), Imm32(ProcessorInterface::INT_CAUSE_CP));
|
||||||
|
FixupBranch cpInt = J_CC(CC_NZ);
|
||||||
|
|
||||||
|
MOV(32, M(&PC), Imm32(js.compilerPC + 4));
|
||||||
|
WriteExternalExceptionExit();
|
||||||
|
|
||||||
|
SetJumpTarget(cpInt);
|
||||||
|
SetJumpTarget(noExceptionsPending);
|
||||||
|
SetJumpTarget(eeDisabled);
|
||||||
|
|
||||||
WriteExit(js.compilerPC + 4, 0);
|
WriteExit(js.compilerPC + 4, 0);
|
||||||
|
|
||||||
js.firstFPInstructionFound = false;
|
js.firstFPInstructionFound = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,8 +276,8 @@ public:
|
|||||||
InstLoc EmitLoadMSR() {
|
InstLoc EmitLoadMSR() {
|
||||||
return FoldZeroOp(LoadMSR, 0);
|
return FoldZeroOp(LoadMSR, 0);
|
||||||
}
|
}
|
||||||
InstLoc EmitStoreMSR(InstLoc val) {
|
InstLoc EmitStoreMSR(InstLoc val, InstLoc pc) {
|
||||||
return FoldUOp(StoreMSR, val);
|
return FoldBiOp(StoreMSR, val, pc);
|
||||||
}
|
}
|
||||||
InstLoc EmitStoreFPRF(InstLoc value) {
|
InstLoc EmitStoreFPRF(InstLoc value) {
|
||||||
return FoldUOp(StoreFPRF, value);
|
return FoldUOp(StoreFPRF, value);
|
||||||
|
@ -994,8 +994,26 @@ static void DoWriteCode(IRBuilder* ibuild, JitIL* Jit, bool UseProfile, bool Mak
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case StoreMSR: {
|
case StoreMSR: {
|
||||||
|
unsigned InstLoc = ibuild->GetImmValue(getOp2(I));
|
||||||
regStoreInstToConstLoc(RI, 32, getOp1(I), &MSR);
|
regStoreInstToConstLoc(RI, 32, getOp1(I), &MSR);
|
||||||
regNormalRegClear(RI, I);
|
regNormalRegClear(RI, I);
|
||||||
|
|
||||||
|
// If some exceptions are pending and EE are now enabled, force checking
|
||||||
|
// external exceptions when going out of mtmsr in order to execute delayed
|
||||||
|
// interrupts as soon as possible.
|
||||||
|
Jit->MOV(32, R(EAX), M(&MSR));
|
||||||
|
Jit->TEST(32, R(EAX), Imm32(0x8000));
|
||||||
|
FixupBranch eeDisabled = Jit->J_CC(CC_Z);
|
||||||
|
|
||||||
|
Jit->MOV(32, R(EAX), M((void*)&PowerPC::ppcState.Exceptions));
|
||||||
|
Jit->TEST(32, R(EAX), R(EAX));
|
||||||
|
FixupBranch noExceptionsPending = Jit->J_CC(CC_Z);
|
||||||
|
|
||||||
|
Jit->MOV(32, M(&PC), Imm32(InstLoc + 4));
|
||||||
|
Jit->WriteExceptionExit(); // TODO: Implement WriteExternalExceptionExit for JitIL
|
||||||
|
|
||||||
|
Jit->SetJumpTarget(eeDisabled);
|
||||||
|
Jit->SetJumpTarget(noExceptionsPending);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case StoreGQR: {
|
case StoreGQR: {
|
||||||
|
@ -389,7 +389,9 @@ static void ImHere()
|
|||||||
void JitIL::Cleanup()
|
void JitIL::Cleanup()
|
||||||
{
|
{
|
||||||
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock > 0)
|
if (jo.optimizeGatherPipe && js.fifoBytesThisBlock > 0)
|
||||||
|
{
|
||||||
ABI_CallFunction((void *)&GPFifo::CheckGatherPipe);
|
ABI_CallFunction((void *)&GPFifo::CheckGatherPipe);
|
||||||
|
}
|
||||||
|
|
||||||
// SPEED HACK: MMCR0/MMCR1 should be checked at run-time, not at compile time.
|
// SPEED HACK: MMCR0/MMCR1 should be checked at run-time, not at compile time.
|
||||||
if (MMCR0.Hex || MMCR1.Hex)
|
if (MMCR0.Hex || MMCR1.Hex)
|
||||||
|
@ -106,7 +106,7 @@ void JitIL::mfspr(UGeckoInstruction inst)
|
|||||||
// --------------
|
// --------------
|
||||||
void JitIL::mtmsr(UGeckoInstruction inst)
|
void JitIL::mtmsr(UGeckoInstruction inst)
|
||||||
{
|
{
|
||||||
ibuild.EmitStoreMSR(ibuild.EmitLoadGReg(inst.RS));
|
ibuild.EmitStoreMSR(ibuild.EmitLoadGReg(inst.RS), ibuild.EmitIntConst(js.compilerPC));
|
||||||
ibuild.EmitBranchUncond(ibuild.EmitIntConst(js.compilerPC + 4));
|
ibuild.EmitBranchUncond(ibuild.EmitIntConst(js.compilerPC + 4));
|
||||||
}
|
}
|
||||||
// ==============
|
// ==============
|
||||||
|
@ -32,6 +32,7 @@
|
|||||||
#include "../../HW/GPFifo.h"
|
#include "../../HW/GPFifo.h"
|
||||||
#include "../../Core.h"
|
#include "../../Core.h"
|
||||||
#include "JitAsmCommon.h"
|
#include "JitAsmCommon.h"
|
||||||
|
#include "JitBase.h"
|
||||||
|
|
||||||
using namespace Gen;
|
using namespace Gen;
|
||||||
|
|
||||||
|
@ -316,6 +316,8 @@ void CISOProperties::CreateGUIControls(bool IsWad)
|
|||||||
DCBZOFF->SetToolTip(_("Bypass the clearing of the data cache by the DCBZ instruction. Usually leave this option disabled."));
|
DCBZOFF->SetToolTip(_("Bypass the clearing of the data cache by the DCBZ instruction. Usually leave this option disabled."));
|
||||||
VBeam = new wxCheckBox(m_GameConfig, ID_VBEAM, _("Accurate VBeam emulation"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
VBeam = new wxCheckBox(m_GameConfig, ID_VBEAM, _("Accurate VBeam emulation"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
||||||
VBeam->SetToolTip(_("If the FPS is erratic, this option may help. (ON = Compatible, OFF = Fast)"));
|
VBeam->SetToolTip(_("If the FPS is erratic, this option may help. (ON = Compatible, OFF = Fast)"));
|
||||||
|
SyncGPU = new wxCheckBox(m_GameConfig, ID_SYNCGPU, _("Sychronise GPU thread"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
||||||
|
SyncGPU->SetToolTip(_("Synchonises the GPU and CPU threads to help prevent random freezes in Dual Core mode. (ON = Compatible, OFF = Fast)"));
|
||||||
FastDiscSpeed = new wxCheckBox(m_GameConfig, ID_DISCSPEED, _("Speed up Disc Transfer Rate"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
FastDiscSpeed = new wxCheckBox(m_GameConfig, ID_DISCSPEED, _("Speed up Disc Transfer Rate"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
||||||
FastDiscSpeed->SetToolTip(_("Enable fast disc access. Needed for a few games. (ON = Fast, OFF = Compatible)"));
|
FastDiscSpeed->SetToolTip(_("Enable fast disc access. Needed for a few games. (ON = Fast, OFF = Compatible)"));
|
||||||
BlockMerging = new wxCheckBox(m_GameConfig, ID_MERGEBLOCKS, _("Enable Block Merging"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
BlockMerging = new wxCheckBox(m_GameConfig, ID_MERGEBLOCKS, _("Enable Block Merging"), wxDefaultPosition, wxDefaultSize, wxCHK_3STATE|wxCHK_ALLOW_3RD_STATE_FOR_USER, wxDefaultValidator);
|
||||||
@ -357,9 +359,10 @@ void CISOProperties::CreateGUIControls(bool IsWad)
|
|||||||
sbCoreOverrides->Add(CPUThread, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(CPUThread, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(SkipIdle, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(SkipIdle, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(MMU, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(MMU, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(DCBZOFF, 0, wxLEFT, 5);
|
|
||||||
sbCoreOverrides->Add(TLBHack, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(TLBHack, 0, wxLEFT, 5);
|
||||||
|
sbCoreOverrides->Add(DCBZOFF, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(VBeam, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(VBeam, 0, wxLEFT, 5);
|
||||||
|
sbCoreOverrides->Add(SyncGPU, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(FastDiscSpeed, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(FastDiscSpeed, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(BlockMerging, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(BlockMerging, 0, wxLEFT, 5);
|
||||||
sbCoreOverrides->Add(DSPHLE, 0, wxLEFT, 5);
|
sbCoreOverrides->Add(DSPHLE, 0, wxLEFT, 5);
|
||||||
@ -936,6 +939,11 @@ void CISOProperties::LoadGameConfig()
|
|||||||
else
|
else
|
||||||
VBeam->Set3StateValue(wxCHK_UNDETERMINED);
|
VBeam->Set3StateValue(wxCHK_UNDETERMINED);
|
||||||
|
|
||||||
|
if (GameIni.Get("Core", "SyncGPU", &bTemp))
|
||||||
|
SyncGPU->Set3StateValue((wxCheckBoxState)bTemp);
|
||||||
|
else
|
||||||
|
SyncGPU->Set3StateValue(wxCHK_UNDETERMINED);
|
||||||
|
|
||||||
if (GameIni.Get("Core", "FastDiscSpeed", &bTemp))
|
if (GameIni.Get("Core", "FastDiscSpeed", &bTemp))
|
||||||
FastDiscSpeed->Set3StateValue((wxCheckBoxState)bTemp);
|
FastDiscSpeed->Set3StateValue((wxCheckBoxState)bTemp);
|
||||||
else
|
else
|
||||||
@ -1025,6 +1033,11 @@ bool CISOProperties::SaveGameConfig()
|
|||||||
else
|
else
|
||||||
GameIni.Set("Core", "VBeam", VBeam->Get3StateValue());
|
GameIni.Set("Core", "VBeam", VBeam->Get3StateValue());
|
||||||
|
|
||||||
|
if (SyncGPU->Get3StateValue() == wxCHK_UNDETERMINED)
|
||||||
|
GameIni.DeleteKey("Core", "SyncGPU");
|
||||||
|
else
|
||||||
|
GameIni.Set("Core", "SyncGPU", SyncGPU->Get3StateValue());
|
||||||
|
|
||||||
if (FastDiscSpeed->Get3StateValue() == wxCHK_UNDETERMINED)
|
if (FastDiscSpeed->Get3StateValue() == wxCHK_UNDETERMINED)
|
||||||
GameIni.DeleteKey("Core", "FastDiscSpeed");
|
GameIni.DeleteKey("Core", "FastDiscSpeed");
|
||||||
else
|
else
|
||||||
|
@ -70,7 +70,7 @@ private:
|
|||||||
|
|
||||||
// Core
|
// Core
|
||||||
wxCheckBox *CPUThread, *SkipIdle, *MMU, *DCBZOFF, *TLBHack;
|
wxCheckBox *CPUThread, *SkipIdle, *MMU, *DCBZOFF, *TLBHack;
|
||||||
wxCheckBox *VBeam, *FastDiscSpeed, *BlockMerging, *DSPHLE;
|
wxCheckBox *VBeam, *SyncGPU, *FastDiscSpeed, *BlockMerging, *DSPHLE;
|
||||||
// Wii
|
// Wii
|
||||||
wxCheckBox *EnableWideScreen;
|
wxCheckBox *EnableWideScreen;
|
||||||
// Video
|
// Video
|
||||||
@ -130,6 +130,7 @@ private:
|
|||||||
ID_DCBZOFF,
|
ID_DCBZOFF,
|
||||||
ID_TLBHACK,
|
ID_TLBHACK,
|
||||||
ID_VBEAM,
|
ID_VBEAM,
|
||||||
|
ID_SYNCGPU,
|
||||||
ID_DISCSPEED,
|
ID_DISCSPEED,
|
||||||
ID_MERGEBLOCKS,
|
ID_MERGEBLOCKS,
|
||||||
ID_AUDIO_DSP_HLE,
|
ID_AUDIO_DSP_HLE,
|
||||||
|
@ -32,6 +32,8 @@
|
|||||||
#include "HW/GPFifo.h"
|
#include "HW/GPFifo.h"
|
||||||
#include "HW/Memmap.h"
|
#include "HW/Memmap.h"
|
||||||
#include "DLCache.h"
|
#include "DLCache.h"
|
||||||
|
#include "HW/SystemTimers.h"
|
||||||
|
#include "Core.h"
|
||||||
|
|
||||||
namespace CommandProcessor
|
namespace CommandProcessor
|
||||||
{
|
{
|
||||||
@ -57,12 +59,15 @@ static bool bProcessFifoAllDistance = false;
|
|||||||
|
|
||||||
volatile bool isPossibleWaitingSetDrawDone = false;
|
volatile bool isPossibleWaitingSetDrawDone = false;
|
||||||
volatile bool isHiWatermarkActive = false;
|
volatile bool isHiWatermarkActive = false;
|
||||||
|
volatile bool isLoWatermarkActive = false;
|
||||||
volatile bool interruptSet= false;
|
volatile bool interruptSet= false;
|
||||||
volatile bool interruptWaiting= false;
|
volatile bool interruptWaiting= false;
|
||||||
volatile bool interruptTokenWaiting = false;
|
volatile bool interruptTokenWaiting = false;
|
||||||
volatile bool interruptFinishWaiting = false;
|
volatile bool interruptFinishWaiting = false;
|
||||||
volatile bool waitingForPEInterruptDisable = false;
|
volatile bool waitingForPEInterruptDisable = false;
|
||||||
|
|
||||||
|
volatile u32 VITicks = CommandProcessor::m_cpClockOrigin;
|
||||||
|
|
||||||
bool IsOnThread()
|
bool IsOnThread()
|
||||||
{
|
{
|
||||||
return SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread;
|
return SConfig::GetInstance().m_LocalCoreStartupParameter.bCPUThread;
|
||||||
@ -88,6 +93,7 @@ void DoState(PointerWrap &p)
|
|||||||
p.Do(bProcessFifoToLoWatermark);
|
p.Do(bProcessFifoToLoWatermark);
|
||||||
p.Do(bProcessFifoAllDistance);
|
p.Do(bProcessFifoAllDistance);
|
||||||
p.Do(isHiWatermarkActive);
|
p.Do(isHiWatermarkActive);
|
||||||
|
p.Do(isLoWatermarkActive);
|
||||||
p.Do(isPossibleWaitingSetDrawDone);
|
p.Do(isPossibleWaitingSetDrawDone);
|
||||||
p.Do(interruptSet);
|
p.Do(interruptSet);
|
||||||
p.Do(interruptWaiting);
|
p.Do(interruptWaiting);
|
||||||
@ -136,6 +142,7 @@ void Init()
|
|||||||
bProcessFifoAllDistance = false;
|
bProcessFifoAllDistance = false;
|
||||||
isPossibleWaitingSetDrawDone = false;
|
isPossibleWaitingSetDrawDone = false;
|
||||||
isHiWatermarkActive = false;
|
isHiWatermarkActive = false;
|
||||||
|
isLoWatermarkActive = false;
|
||||||
|
|
||||||
et_UpdateInterrupts = CoreTiming::RegisterEvent("UpdateInterrupts", UpdateInterrupts_Wrapper);
|
et_UpdateInterrupts = CoreTiming::RegisterEvent("UpdateInterrupts", UpdateInterrupts_Wrapper);
|
||||||
}
|
}
|
||||||
@ -294,7 +301,6 @@ void Read16(u16& _rReturnValue, const u32 _Address)
|
|||||||
|
|
||||||
void Write16(const u16 _Value, const u32 _Address)
|
void Write16(const u16 _Value, const u32 _Address)
|
||||||
{
|
{
|
||||||
|
|
||||||
INFO_LOG(COMMANDPROCESSOR, "(write16): 0x%04x @ 0x%08x",_Value,_Address);
|
INFO_LOG(COMMANDPROCESSOR, "(write16): 0x%04x @ 0x%08x",_Value,_Address);
|
||||||
|
|
||||||
switch (_Address & 0xFFF)
|
switch (_Address & 0xFFF)
|
||||||
@ -405,7 +411,8 @@ void Write16(const u16 _Value, const u32 _Address)
|
|||||||
{
|
{
|
||||||
GPFifo::ResetGatherPipe();
|
GPFifo::ResetGatherPipe();
|
||||||
ResetVideoBuffer();
|
ResetVideoBuffer();
|
||||||
}else
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
ResetVideoBuffer();
|
ResetVideoBuffer();
|
||||||
}
|
}
|
||||||
@ -461,7 +468,7 @@ void STACKALIGN GatherPipeBursted()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsOnThread())
|
if (IsOnThread())
|
||||||
SetOverflowStatusFromGatherPipe();
|
SetCpStatus();
|
||||||
|
|
||||||
// update the fifo-pointer
|
// update the fifo-pointer
|
||||||
if (fifo.CPWritePointer >= fifo.CPEnd)
|
if (fifo.CPWritePointer >= fifo.CPEnd)
|
||||||
@ -511,19 +518,6 @@ void AbortFrame()
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetOverflowStatusFromGatherPipe()
|
|
||||||
{
|
|
||||||
fifo.bFF_HiWatermark = (fifo.CPReadWriteDistance > fifo.CPHiWatermark);
|
|
||||||
isHiWatermarkActive = fifo.bFF_HiWatermark && fifo.bFF_HiWatermarkInt && m_CPCtrlReg.GPReadEnable;
|
|
||||||
|
|
||||||
if (isHiWatermarkActive)
|
|
||||||
{
|
|
||||||
interruptSet = true;
|
|
||||||
INFO_LOG(COMMANDPROCESSOR,"Interrupt set");
|
|
||||||
ProcessorInterface::SetInterrupt(INT_CAUSE_CP, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetCpStatus()
|
void SetCpStatus()
|
||||||
{
|
{
|
||||||
// overflow & underflow check
|
// overflow & underflow check
|
||||||
@ -531,6 +525,8 @@ void SetCpStatus()
|
|||||||
fifo.bFF_LoWatermark = (fifo.CPReadWriteDistance < fifo.CPLoWatermark);
|
fifo.bFF_LoWatermark = (fifo.CPReadWriteDistance < fifo.CPLoWatermark);
|
||||||
|
|
||||||
// breakpoint
|
// breakpoint
|
||||||
|
if (Core::IsGPUThread())
|
||||||
|
{
|
||||||
if (fifo.bFF_BPEnable)
|
if (fifo.bFF_BPEnable)
|
||||||
{
|
{
|
||||||
if (fifo.CPBreakpoint == fifo.CPReadPointer)
|
if (fifo.CPBreakpoint == fifo.CPReadPointer)
|
||||||
@ -555,6 +551,7 @@ void SetCpStatus()
|
|||||||
INFO_LOG(COMMANDPROCESSOR, "Cleared breakpoint at %i", fifo.CPReadPointer);
|
INFO_LOG(COMMANDPROCESSOR, "Cleared breakpoint at %i", fifo.CPReadPointer);
|
||||||
fifo.bFF_Breakpoint = false;
|
fifo.bFF_Breakpoint = false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool bpInt = fifo.bFF_Breakpoint && fifo.bFF_BPInt;
|
bool bpInt = fifo.bFF_Breakpoint && fifo.bFF_BPInt;
|
||||||
bool ovfInt = fifo.bFF_HiWatermark && fifo.bFF_HiWatermarkInt;
|
bool ovfInt = fifo.bFF_HiWatermark && fifo.bFF_HiWatermarkInt;
|
||||||
@ -563,17 +560,27 @@ void SetCpStatus()
|
|||||||
bool interrupt = (bpInt || ovfInt || undfInt) && m_CPCtrlReg.GPReadEnable;
|
bool interrupt = (bpInt || ovfInt || undfInt) && m_CPCtrlReg.GPReadEnable;
|
||||||
|
|
||||||
isHiWatermarkActive = ovfInt && m_CPCtrlReg.GPReadEnable;
|
isHiWatermarkActive = ovfInt && m_CPCtrlReg.GPReadEnable;
|
||||||
|
isLoWatermarkActive = undfInt && m_CPCtrlReg.GPReadEnable;
|
||||||
|
|
||||||
if (interrupt != interruptSet && !interruptWaiting)
|
if (interrupt != interruptSet && !interruptWaiting)
|
||||||
{
|
{
|
||||||
u64 userdata = interrupt?1:0;
|
u64 userdata = interrupt?1:0;
|
||||||
if (IsOnThread())
|
if (IsOnThread())
|
||||||
{
|
{
|
||||||
if(!interrupt || bpInt || undfInt)
|
if(!interrupt || bpInt || undfInt || ovfInt)
|
||||||
|
{
|
||||||
|
if (Core::IsGPUThread())
|
||||||
{
|
{
|
||||||
interruptWaiting = true;
|
interruptWaiting = true;
|
||||||
CommandProcessor::UpdateInterruptsFromVideoBackend(userdata);
|
CommandProcessor::UpdateInterruptsFromVideoBackend(userdata);
|
||||||
}
|
}
|
||||||
|
else if (Core::IsCPUThread())
|
||||||
|
{
|
||||||
|
interruptSet = interrupt;
|
||||||
|
INFO_LOG(COMMANDPROCESSOR,"Interrupt set");
|
||||||
|
ProcessorInterface::SetInterrupt(INT_CAUSE_CP, interrupt);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
CommandProcessor::UpdateInterrupts(userdata);
|
CommandProcessor::UpdateInterrupts(userdata);
|
||||||
@ -596,7 +603,7 @@ void ProcessFifoAllDistance()
|
|||||||
if (IsOnThread())
|
if (IsOnThread())
|
||||||
{
|
{
|
||||||
while (!CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable &&
|
while (!CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable &&
|
||||||
fifo.CPReadWriteDistance && !AtBreakpoint() && !PixelEngine::WaitingForPEInterrupt())
|
fifo.CPReadWriteDistance && !AtBreakpoint())
|
||||||
Common::YieldCPU();
|
Common::YieldCPU();
|
||||||
}
|
}
|
||||||
bProcessFifoAllDistance = false;
|
bProcessFifoAllDistance = false;
|
||||||
@ -617,15 +624,11 @@ void SetCpStatusRegister()
|
|||||||
{
|
{
|
||||||
// Here always there is one fifo attached to the GPU
|
// Here always there is one fifo attached to the GPU
|
||||||
m_CPStatusReg.Breakpoint = fifo.bFF_Breakpoint;
|
m_CPStatusReg.Breakpoint = fifo.bFF_Breakpoint;
|
||||||
m_CPStatusReg.ReadIdle = !fifo.CPReadWriteDistance || (fifo.CPReadPointer == fifo.CPWritePointer) || (fifo.CPReadPointer == fifo.CPBreakpoint) ;
|
m_CPStatusReg.ReadIdle = !fifo.CPReadWriteDistance || AtBreakpoint() || (fifo.CPReadPointer == fifo.CPWritePointer);
|
||||||
m_CPStatusReg.CommandIdle = !fifo.CPReadWriteDistance;
|
m_CPStatusReg.CommandIdle = !fifo.CPReadWriteDistance || AtBreakpoint() || !fifo.bFF_GPReadEnable;
|
||||||
m_CPStatusReg.UnderflowLoWatermark = fifo.bFF_LoWatermark;
|
m_CPStatusReg.UnderflowLoWatermark = fifo.bFF_LoWatermark;
|
||||||
m_CPStatusReg.OverflowHiWatermark = fifo.bFF_HiWatermark;
|
m_CPStatusReg.OverflowHiWatermark = fifo.bFF_HiWatermark;
|
||||||
|
|
||||||
// HACK to compensate for slow response to PE interrupts in Time Splitters: Future Perfect
|
|
||||||
if (IsOnThread())
|
|
||||||
PixelEngine::ResumeWaitingForPEInterrupt();
|
|
||||||
|
|
||||||
INFO_LOG(COMMANDPROCESSOR,"\t Read from STATUS_REGISTER : %04x", m_CPStatusReg.Hex);
|
INFO_LOG(COMMANDPROCESSOR,"\t Read from STATUS_REGISTER : %04x", m_CPStatusReg.Hex);
|
||||||
DEBUG_LOG(COMMANDPROCESSOR, "(r) status: iBP %s | fReadIdle %s | fCmdIdle %s | iOvF %s | iUndF %s"
|
DEBUG_LOG(COMMANDPROCESSOR, "(r) status: iBP %s | fReadIdle %s | fCmdIdle %s | iOvF %s | iUndF %s"
|
||||||
, m_CPStatusReg.Breakpoint ? "ON" : "OFF"
|
, m_CPStatusReg.Breakpoint ? "ON" : "OFF"
|
||||||
@ -693,4 +696,12 @@ void SetCpClearRegister()
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Update()
|
||||||
|
{
|
||||||
|
while (VITicks > m_cpClockOrigin && fifo.isGpuReadingData && IsOnThread())
|
||||||
|
Common::YieldCPU();
|
||||||
|
|
||||||
|
if (fifo.isGpuReadingData)
|
||||||
|
Common::AtomicAdd(VITicks, SystemTimers::GetTicksPerSecond() / 10000);
|
||||||
|
}
|
||||||
} // end of namespace CommandProcessor
|
} // end of namespace CommandProcessor
|
||||||
|
@ -31,6 +31,7 @@ namespace CommandProcessor
|
|||||||
extern SCPFifoStruct fifo; //This one is shared between gfx thread and emulator thread.
|
extern SCPFifoStruct fifo; //This one is shared between gfx thread and emulator thread.
|
||||||
extern volatile bool isPossibleWaitingSetDrawDone; //This one is used for sync gfx thread and emulator thread.
|
extern volatile bool isPossibleWaitingSetDrawDone; //This one is used for sync gfx thread and emulator thread.
|
||||||
extern volatile bool isHiWatermarkActive;
|
extern volatile bool isHiWatermarkActive;
|
||||||
|
extern volatile bool isLoWatermarkActive;
|
||||||
extern volatile bool interruptSet;
|
extern volatile bool interruptSet;
|
||||||
extern volatile bool interruptWaiting;
|
extern volatile bool interruptWaiting;
|
||||||
extern volatile bool interruptTokenWaiting;
|
extern volatile bool interruptTokenWaiting;
|
||||||
@ -140,6 +141,9 @@ union UCPClearReg
|
|||||||
UCPClearReg(u16 _hex) {Hex = _hex; }
|
UCPClearReg(u16 _hex) {Hex = _hex; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Can be any number, low enough to not be below the number of clocks executed by the GPU per CP_PERIOD
|
||||||
|
const static u32 m_cpClockOrigin = 200000;
|
||||||
|
|
||||||
// Init
|
// Init
|
||||||
void Init();
|
void Init();
|
||||||
void Shutdown();
|
void Shutdown();
|
||||||
@ -161,11 +165,14 @@ bool AllowIdleSkipping();
|
|||||||
void SetCpClearRegister();
|
void SetCpClearRegister();
|
||||||
void SetCpControlRegister();
|
void SetCpControlRegister();
|
||||||
void SetCpStatusRegister();
|
void SetCpStatusRegister();
|
||||||
void SetOverflowStatusFromGatherPipe();
|
|
||||||
void ProcessFifoToLoWatermark();
|
void ProcessFifoToLoWatermark();
|
||||||
void ProcessFifoAllDistance();
|
void ProcessFifoAllDistance();
|
||||||
void ProcessFifoEvents();
|
void ProcessFifoEvents();
|
||||||
void AbortFrame();
|
void AbortFrame();
|
||||||
|
|
||||||
|
void Update();
|
||||||
|
extern volatile u32 VITicks;
|
||||||
|
|
||||||
} // namespace CommandProcessor
|
} // namespace CommandProcessor
|
||||||
|
|
||||||
#endif // _COMMANDPROCESSOR_H
|
#endif // _COMMANDPROCESSOR_H
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
#include "Fifo.h"
|
#include "Fifo.h"
|
||||||
#include "HW/Memmap.h"
|
#include "HW/Memmap.h"
|
||||||
#include "Core.h"
|
#include "Core.h"
|
||||||
|
#include "CoreTiming.h"
|
||||||
|
|
||||||
volatile bool g_bSkipCurrentFrame = false;
|
volatile bool g_bSkipCurrentFrame = false;
|
||||||
extern u8* g_pVideoData;
|
extern u8* g_pVideoData;
|
||||||
@ -72,6 +73,7 @@ void Fifo_Init()
|
|||||||
videoBuffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
|
videoBuffer = (u8*)AllocateMemoryPages(FIFO_SIZE);
|
||||||
size = 0;
|
size = 0;
|
||||||
GpuRunningState = false;
|
GpuRunningState = false;
|
||||||
|
Common::AtomicStore(CommandProcessor::VITicks, CommandProcessor::m_cpClockOrigin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Fifo_Shutdown()
|
void Fifo_Shutdown()
|
||||||
@ -123,7 +125,7 @@ void ReadDataFromFifo(u8* _uData, u32 len)
|
|||||||
size -= pos;
|
size -= pos;
|
||||||
if (size + len > FIFO_SIZE)
|
if (size + len > FIFO_SIZE)
|
||||||
{
|
{
|
||||||
PanicAlert("FIFO out of bounds (sz = %i, at %08x)", size, pos);
|
PanicAlert("FIFO out of bounds (sz = %i, len = %i at %08x)", size, len, pos);
|
||||||
}
|
}
|
||||||
memmove(&videoBuffer[0], &videoBuffer[pos], size);
|
memmove(&videoBuffer[0], &videoBuffer[pos], size);
|
||||||
g_pVideoData = videoBuffer;
|
g_pVideoData = videoBuffer;
|
||||||
@ -147,6 +149,7 @@ void RunGpuLoop()
|
|||||||
std::lock_guard<std::mutex> lk(m_csHWVidOccupied);
|
std::lock_guard<std::mutex> lk(m_csHWVidOccupied);
|
||||||
GpuRunningState = true;
|
GpuRunningState = true;
|
||||||
SCPFifoStruct &fifo = CommandProcessor::fifo;
|
SCPFifoStruct &fifo = CommandProcessor::fifo;
|
||||||
|
u32 cyclesExecuted = 0;
|
||||||
|
|
||||||
while (GpuRunningState)
|
while (GpuRunningState)
|
||||||
{
|
{
|
||||||
@ -155,14 +158,19 @@ void RunGpuLoop()
|
|||||||
VideoFifo_CheckAsyncRequest();
|
VideoFifo_CheckAsyncRequest();
|
||||||
|
|
||||||
CommandProcessor::SetCpStatus();
|
CommandProcessor::SetCpStatus();
|
||||||
|
|
||||||
|
Common::AtomicStore(CommandProcessor::VITicks, CommandProcessor::m_cpClockOrigin);
|
||||||
|
|
||||||
// check if we are able to run this buffer
|
// check if we are able to run this buffer
|
||||||
while (GpuRunningState && !CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint() && !PixelEngine::WaitingForPEInterrupt())
|
while (GpuRunningState && !CommandProcessor::interruptWaiting && fifo.bFF_GPReadEnable && fifo.CPReadWriteDistance && !AtBreakpoint())
|
||||||
{
|
{
|
||||||
if (!GpuRunningState) break;
|
if (!GpuRunningState) break;
|
||||||
|
|
||||||
fifo.isGpuReadingData = true;
|
fifo.isGpuReadingData = true;
|
||||||
CommandProcessor::isPossibleWaitingSetDrawDone = fifo.bFF_GPLinkEnable ? true : false;
|
CommandProcessor::isPossibleWaitingSetDrawDone = fifo.bFF_GPLinkEnable ? true : false;
|
||||||
|
|
||||||
|
if (Common::AtomicLoad(CommandProcessor::VITicks) > CommandProcessor::m_cpClockOrigin || !Core::g_CoreStartupParameter.bSyncGPU)
|
||||||
|
{
|
||||||
u32 readPtr = fifo.CPReadPointer;
|
u32 readPtr = fifo.CPReadPointer;
|
||||||
u8 *uData = Memory::GetPointer(readPtr);
|
u8 *uData = Memory::GetPointer(readPtr);
|
||||||
|
|
||||||
@ -170,16 +178,21 @@ void RunGpuLoop()
|
|||||||
else readPtr += 32;
|
else readPtr += 32;
|
||||||
|
|
||||||
_assert_msg_(COMMANDPROCESSOR, (s32)fifo.CPReadWriteDistance - 32 >= 0 ,
|
_assert_msg_(COMMANDPROCESSOR, (s32)fifo.CPReadWriteDistance - 32 >= 0 ,
|
||||||
"Negative fifo.CPReadWriteDistance = %i in FIFO Loop !\nThat can produce inestabilty in the game. Please report it.", fifo.CPReadWriteDistance - 32);
|
"Negative fifo.CPReadWriteDistance = %i in FIFO Loop !\nThat can produce instabilty in the game. Please report it.", fifo.CPReadWriteDistance - 32);
|
||||||
|
|
||||||
ReadDataFromFifo(uData, 32);
|
ReadDataFromFifo(uData, 32);
|
||||||
|
|
||||||
OpcodeDecoder_Run(g_bSkipCurrentFrame);
|
cyclesExecuted = OpcodeDecoder_Run(g_bSkipCurrentFrame);
|
||||||
|
|
||||||
|
if (Common::AtomicLoad(CommandProcessor::VITicks) > cyclesExecuted && Core::g_CoreStartupParameter.bSyncGPU)
|
||||||
|
Common::AtomicAdd(CommandProcessor::VITicks, -(s32)cyclesExecuted);
|
||||||
|
|
||||||
Common::AtomicStore(fifo.CPReadPointer, readPtr);
|
Common::AtomicStore(fifo.CPReadPointer, readPtr);
|
||||||
Common::AtomicAdd(fifo.CPReadWriteDistance, -32);
|
Common::AtomicAdd(fifo.CPReadWriteDistance, -32);
|
||||||
if((GetVideoBufferEndPtr() - g_pVideoData) == 0)
|
if((GetVideoBufferEndPtr() - g_pVideoData) == 0)
|
||||||
Common::AtomicStore(fifo.SafeCPReadPointer, fifo.CPReadPointer);
|
Common::AtomicStore(fifo.SafeCPReadPointer, fifo.CPReadPointer);
|
||||||
|
}
|
||||||
|
|
||||||
CommandProcessor::SetCpStatus();
|
CommandProcessor::SetCpStatus();
|
||||||
|
|
||||||
// This call is pretty important in DualCore mode and must be called in the FIFO Loop.
|
// This call is pretty important in DualCore mode and must be called in the FIFO Loop.
|
||||||
@ -225,7 +238,7 @@ void RunGpu()
|
|||||||
FPURoundMode::SaveSIMDState();
|
FPURoundMode::SaveSIMDState();
|
||||||
FPURoundMode::LoadDefaultSIMDState();
|
FPURoundMode::LoadDefaultSIMDState();
|
||||||
ReadDataFromFifo(uData, 32);
|
ReadDataFromFifo(uData, 32);
|
||||||
OpcodeDecoder_Run(g_bSkipCurrentFrame);
|
u32 count = OpcodeDecoder_Run(g_bSkipCurrentFrame);
|
||||||
FPURoundMode::LoadSIMDState();
|
FPURoundMode::LoadSIMDState();
|
||||||
|
|
||||||
//DEBUG_LOG(COMMANDPROCESSOR, "Fifo wraps to base");
|
//DEBUG_LOG(COMMANDPROCESSOR, "Fifo wraps to base");
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
class PointerWrap;
|
class PointerWrap;
|
||||||
|
|
||||||
#define FIFO_SIZE (1024*1024)
|
#define FIFO_SIZE (2*1024*1024)
|
||||||
|
|
||||||
extern volatile bool g_bSkipCurrentFrame;
|
extern volatile bool g_bSkipCurrentFrame;
|
||||||
|
|
||||||
|
@ -136,29 +136,38 @@ void ExecuteDisplayList(u32 address, u32 size)
|
|||||||
InterpretDisplayList(address, size);
|
InterpretDisplayList(address, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FifoCommandRunnable()
|
u32 FifoCommandRunnable(u32 &command_size)
|
||||||
{
|
{
|
||||||
|
u32 cycleTime = 0;
|
||||||
u32 buffer_size = (u32)(GetVideoBufferEndPtr() - g_pVideoData);
|
u32 buffer_size = (u32)(GetVideoBufferEndPtr() - g_pVideoData);
|
||||||
if (buffer_size == 0)
|
if (buffer_size == 0)
|
||||||
return false; // can't peek
|
return 0; // can't peek
|
||||||
|
|
||||||
u8 cmd_byte = DataPeek8(0);
|
u8 cmd_byte = DataPeek8(0);
|
||||||
u32 command_size = 0;
|
|
||||||
|
|
||||||
switch (cmd_byte)
|
switch (cmd_byte)
|
||||||
{
|
{
|
||||||
case GX_NOP: // Hm, this means that we scan over nop streams pretty slowly...
|
case GX_NOP: // Hm, this means that we scan over nop streams pretty slowly...
|
||||||
|
command_size = 1;
|
||||||
|
cycleTime = 6;
|
||||||
|
break;
|
||||||
case GX_CMD_INVL_VC: // Invalidate Vertex Cache - no parameters
|
case GX_CMD_INVL_VC: // Invalidate Vertex Cache - no parameters
|
||||||
|
command_size = 1;
|
||||||
|
cycleTime = 6;
|
||||||
|
break;
|
||||||
case GX_CMD_UNKNOWN_METRICS: // zelda 4 swords calls it and checks the metrics registers after that
|
case GX_CMD_UNKNOWN_METRICS: // zelda 4 swords calls it and checks the metrics registers after that
|
||||||
command_size = 1;
|
command_size = 1;
|
||||||
|
cycleTime = 6;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GX_LOAD_BP_REG:
|
case GX_LOAD_BP_REG:
|
||||||
command_size = 5;
|
command_size = 5;
|
||||||
|
cycleTime = 12;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GX_LOAD_CP_REG:
|
case GX_LOAD_CP_REG:
|
||||||
command_size = 6;
|
command_size = 6;
|
||||||
|
cycleTime = 12;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GX_LOAD_INDX_A:
|
case GX_LOAD_INDX_A:
|
||||||
@ -166,10 +175,39 @@ bool FifoCommandRunnable()
|
|||||||
case GX_LOAD_INDX_C:
|
case GX_LOAD_INDX_C:
|
||||||
case GX_LOAD_INDX_D:
|
case GX_LOAD_INDX_D:
|
||||||
command_size = 5;
|
command_size = 5;
|
||||||
|
cycleTime = 6; // TODO
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GX_CMD_CALL_DL:
|
case GX_CMD_CALL_DL:
|
||||||
|
{
|
||||||
|
// FIXME: Calculate the cycle time of the display list.
|
||||||
|
//u32 address = DataPeek32(1);
|
||||||
|
//u32 size = DataPeek32(5);
|
||||||
|
//u8* old_pVideoData = g_pVideoData;
|
||||||
|
//u8* startAddress = Memory::GetPointer(address);
|
||||||
|
|
||||||
|
//// Avoid the crash if Memory::GetPointer failed ..
|
||||||
|
//if (startAddress != 0)
|
||||||
|
//{
|
||||||
|
// g_pVideoData = startAddress;
|
||||||
|
// u8 *end = g_pVideoData + size;
|
||||||
|
// u32 step = 0;
|
||||||
|
// while (g_pVideoData < end)
|
||||||
|
// {
|
||||||
|
// cycleTime += FifoCommandRunnable(step);
|
||||||
|
// g_pVideoData += step;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
//else
|
||||||
|
//{
|
||||||
|
// cycleTime = 45;
|
||||||
|
//}
|
||||||
|
|
||||||
|
//// reset to the old pointer
|
||||||
|
//g_pVideoData = old_pVideoData;
|
||||||
command_size = 9;
|
command_size = 9;
|
||||||
|
cycleTime = 45; // This is unverified
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case GX_LOAD_XF_REG:
|
case GX_LOAD_XF_REG:
|
||||||
@ -181,10 +219,11 @@ bool FifoCommandRunnable()
|
|||||||
u32 Cmd2 = DataPeek32(1);
|
u32 Cmd2 = DataPeek32(1);
|
||||||
int transfer_size = ((Cmd2 >> 16) & 15) + 1;
|
int transfer_size = ((Cmd2 >> 16) & 15) + 1;
|
||||||
command_size += transfer_size * 4;
|
command_size += transfer_size * 4;
|
||||||
|
cycleTime = 18 + 6 * transfer_size;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@ -198,10 +237,11 @@ bool FifoCommandRunnable()
|
|||||||
command_size = 1 + 2;
|
command_size = 1 + 2;
|
||||||
u16 numVertices = DataPeek16(1);
|
u16 numVertices = DataPeek16(1);
|
||||||
command_size += numVertices * VertexLoaderManager::GetVertexSize(cmd_byte & GX_VAT_MASK);
|
command_size += numVertices * VertexLoaderManager::GetVertexSize(cmd_byte & GX_VAT_MASK);
|
||||||
|
cycleTime = 1600; // This depends on the number of pixels rendered
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return false;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -248,11 +288,19 @@ bool FifoCommandRunnable()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (command_size > buffer_size)
|
if (command_size > buffer_size)
|
||||||
return false;
|
return 0;
|
||||||
|
|
||||||
// INFO_LOG("OP detected: cmd_byte 0x%x size %i buffer %i",cmd_byte, command_size, buffer_size);
|
// INFO_LOG("OP detected: cmd_byte 0x%x size %i buffer %i",cmd_byte, command_size, buffer_size);
|
||||||
|
if (cycleTime == 0)
|
||||||
|
cycleTime = 6;
|
||||||
|
|
||||||
return true;
|
return cycleTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 FifoCommandRunnable()
|
||||||
|
{
|
||||||
|
u32 command_size = 0;
|
||||||
|
return FifoCommandRunnable(command_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Decode()
|
static void Decode()
|
||||||
@ -461,16 +509,15 @@ void OpcodeDecoder_Shutdown()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpcodeDecoder_Run(bool skipped_frame)
|
u32 OpcodeDecoder_Run(bool skipped_frame)
|
||||||
{
|
{
|
||||||
if (!skipped_frame)
|
u32 totalCycles = 0;
|
||||||
|
u32 cycles = FifoCommandRunnable();
|
||||||
|
while (cycles > 0)
|
||||||
{
|
{
|
||||||
while (FifoCommandRunnable())
|
skipped_frame ? DecodeSemiNop() : Decode();
|
||||||
Decode();
|
totalCycles += cycles;
|
||||||
}
|
cycles = FifoCommandRunnable();
|
||||||
else
|
|
||||||
{
|
|
||||||
while (FifoCommandRunnable())
|
|
||||||
DecodeSemiNop();
|
|
||||||
}
|
}
|
||||||
|
return totalCycles;
|
||||||
}
|
}
|
||||||
|
@ -50,6 +50,6 @@ extern bool g_bRecordFifoData;
|
|||||||
|
|
||||||
void OpcodeDecoder_Init();
|
void OpcodeDecoder_Init();
|
||||||
void OpcodeDecoder_Shutdown();
|
void OpcodeDecoder_Shutdown();
|
||||||
void OpcodeDecoder_Run(bool skipped_frame);
|
u32 OpcodeDecoder_Run(bool skipped_frame);
|
||||||
void ExecuteDisplayList(u32 address, u32 size);
|
void ExecuteDisplayList(u32 address, u32 size);
|
||||||
#endif // _OPCODE_DECODING_H
|
#endif // _OPCODE_DECODING_H
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
#include "HW/ProcessorInterface.h"
|
#include "HW/ProcessorInterface.h"
|
||||||
#include "DLCache.h"
|
#include "DLCache.h"
|
||||||
#include "State.h"
|
#include "State.h"
|
||||||
#include "PerfQueryBase.h"
|
|
||||||
|
|
||||||
namespace PixelEngine
|
namespace PixelEngine
|
||||||
{
|
{
|
||||||
@ -113,14 +112,14 @@ static UPEAlphaReadReg m_AlphaRead;
|
|||||||
static UPECtrlReg m_Control;
|
static UPECtrlReg m_Control;
|
||||||
//static u16 m_Token; // token value most recently encountered
|
//static u16 m_Token; // token value most recently encountered
|
||||||
|
|
||||||
static bool g_bSignalTokenInterrupt;
|
volatile u32 g_bSignalTokenInterrupt;
|
||||||
static bool g_bSignalFinishInterrupt;
|
volatile u32 g_bSignalFinishInterrupt;
|
||||||
|
|
||||||
static int et_SetTokenOnMainThread;
|
static int et_SetTokenOnMainThread;
|
||||||
static int et_SetFinishOnMainThread;
|
static int et_SetFinishOnMainThread;
|
||||||
|
|
||||||
volatile bool interruptSetToken = false;
|
volatile u32 interruptSetToken = 0;
|
||||||
volatile bool interruptSetFinish = false;
|
volatile u32 interruptSetFinish = 0;
|
||||||
|
|
||||||
u16 bbox[4];
|
u16 bbox[4];
|
||||||
bool bbox_active;
|
bool bbox_active;
|
||||||
@ -164,10 +163,10 @@ void Init()
|
|||||||
m_AlphaModeConf.Hex = 0;
|
m_AlphaModeConf.Hex = 0;
|
||||||
m_AlphaRead.Hex = 0;
|
m_AlphaRead.Hex = 0;
|
||||||
|
|
||||||
g_bSignalTokenInterrupt = false;
|
g_bSignalTokenInterrupt = 0;
|
||||||
g_bSignalFinishInterrupt = false;
|
g_bSignalFinishInterrupt = 0;
|
||||||
interruptSetToken = false;
|
interruptSetToken = 0;
|
||||||
interruptSetFinish = false;
|
interruptSetFinish = 0;
|
||||||
|
|
||||||
et_SetTokenOnMainThread = CoreTiming::RegisterEvent("SetToken", SetToken_OnMainThread);
|
et_SetTokenOnMainThread = CoreTiming::RegisterEvent("SetToken", SetToken_OnMainThread);
|
||||||
et_SetFinishOnMainThread = CoreTiming::RegisterEvent("SetFinish", SetFinish_OnMainThread);
|
et_SetFinishOnMainThread = CoreTiming::RegisterEvent("SetFinish", SetFinish_OnMainThread);
|
||||||
@ -214,7 +213,7 @@ void Read16(u16& _uReturnValue, const u32 _iAddress)
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PE_TOKEN_REG:
|
case PE_TOKEN_REG:
|
||||||
_uReturnValue = CommandProcessor::fifo.PEToken;
|
_uReturnValue = Common::AtomicLoad(*(volatile u32*)&CommandProcessor::fifo.PEToken);
|
||||||
INFO_LOG(PIXELENGINE, "(r16) TOKEN_REG : %04x", _uReturnValue);
|
INFO_LOG(PIXELENGINE, "(r16) TOKEN_REG : %04x", _uReturnValue);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -351,8 +350,8 @@ void Write16(const u16 _iValue, const u32 _iAddress)
|
|||||||
{
|
{
|
||||||
UPECtrlReg tmpCtrl(_iValue);
|
UPECtrlReg tmpCtrl(_iValue);
|
||||||
|
|
||||||
if (tmpCtrl.PEToken) g_bSignalTokenInterrupt = false;
|
if (tmpCtrl.PEToken) g_bSignalTokenInterrupt = 0;
|
||||||
if (tmpCtrl.PEFinish) g_bSignalFinishInterrupt = false;
|
if (tmpCtrl.PEFinish) g_bSignalFinishInterrupt = 0;
|
||||||
|
|
||||||
m_Control.PETokenEnable = tmpCtrl.PETokenEnable;
|
m_Control.PETokenEnable = tmpCtrl.PETokenEnable;
|
||||||
m_Control.PEFinishEnable = tmpCtrl.PEFinishEnable;
|
m_Control.PEFinishEnable = tmpCtrl.PEFinishEnable;
|
||||||
@ -399,13 +398,13 @@ void UpdateInterrupts()
|
|||||||
void UpdateTokenInterrupt(bool active)
|
void UpdateTokenInterrupt(bool active)
|
||||||
{
|
{
|
||||||
ProcessorInterface::SetInterrupt(INT_CAUSE_PE_TOKEN, active);
|
ProcessorInterface::SetInterrupt(INT_CAUSE_PE_TOKEN, active);
|
||||||
interruptSetToken = active;
|
Common::AtomicStore(interruptSetToken, active ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateFinishInterrupt(bool active)
|
void UpdateFinishInterrupt(bool active)
|
||||||
{
|
{
|
||||||
ProcessorInterface::SetInterrupt(INT_CAUSE_PE_FINISH, active);
|
ProcessorInterface::SetInterrupt(INT_CAUSE_PE_FINISH, active);
|
||||||
interruptSetFinish = active;
|
Common::AtomicStore(interruptSetFinish, active ? 1 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(mb2): Refactor SetTokenINT_OnMainThread(u64 userdata, int cyclesLate).
|
// TODO(mb2): Refactor SetTokenINT_OnMainThread(u64 userdata, int cyclesLate).
|
||||||
@ -415,20 +414,23 @@ void UpdateFinishInterrupt(bool active)
|
|||||||
// Called only if BPMEM_PE_TOKEN_INT_ID is ack by GP
|
// Called only if BPMEM_PE_TOKEN_INT_ID is ack by GP
|
||||||
void SetToken_OnMainThread(u64 userdata, int cyclesLate)
|
void SetToken_OnMainThread(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
//if (userdata >> 16)
|
// XXX: No 16-bit atomic store available, so cheat and use 32-bit.
|
||||||
//{
|
// That's what we've always done. We're counting on fifo.PEToken to be
|
||||||
g_bSignalTokenInterrupt = true;
|
// 4-byte padded.
|
||||||
//_dbg_assert_msg_(PIXELENGINE, (CommandProcessor::fifo.PEToken == (userdata&0xFFFF)), "WTF? BPMEM_PE_TOKEN_INT_ID's token != BPMEM_PE_TOKEN_ID's token" );
|
Common::AtomicStore(*(volatile u32*)&CommandProcessor::fifo.PEToken, userdata & 0xffff);
|
||||||
INFO_LOG(PIXELENGINE, "VIDEO Backend raises INT_CAUSE_PE_TOKEN (btw, token: %04x)", CommandProcessor::fifo.PEToken);
|
INFO_LOG(PIXELENGINE, "VIDEO Backend raises INT_CAUSE_PE_TOKEN (btw, token: %04x)", CommandProcessor::fifo.PEToken);
|
||||||
|
if (userdata >> 16)
|
||||||
|
{
|
||||||
|
Common::AtomicStore(*(volatile u32*)&g_bSignalTokenInterrupt, 1);
|
||||||
UpdateInterrupts();
|
UpdateInterrupts();
|
||||||
|
}
|
||||||
CommandProcessor::interruptTokenWaiting = false;
|
CommandProcessor::interruptTokenWaiting = false;
|
||||||
IncrementCheckContextId();
|
IncrementCheckContextId();
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetFinish_OnMainThread(u64 userdata, int cyclesLate)
|
void SetFinish_OnMainThread(u64 userdata, int cyclesLate)
|
||||||
{
|
{
|
||||||
g_bSignalFinishInterrupt = 1;
|
Common::AtomicStore(*(volatile u32*)&g_bSignalFinishInterrupt, 1);
|
||||||
UpdateInterrupts();
|
UpdateInterrupts();
|
||||||
CommandProcessor::interruptFinishWaiting = false;
|
CommandProcessor::interruptFinishWaiting = false;
|
||||||
CommandProcessor::isPossibleWaitingSetDrawDone = false;
|
CommandProcessor::isPossibleWaitingSetDrawDone = false;
|
||||||
@ -438,23 +440,13 @@ void SetFinish_OnMainThread(u64 userdata, int cyclesLate)
|
|||||||
// THIS IS EXECUTED FROM VIDEO THREAD
|
// THIS IS EXECUTED FROM VIDEO THREAD
|
||||||
void SetToken(const u16 _token, const int _bSetTokenAcknowledge)
|
void SetToken(const u16 _token, const int _bSetTokenAcknowledge)
|
||||||
{
|
{
|
||||||
// TODO?: set-token-value and set-token-INT could be merged since set-token-INT own the token value.
|
|
||||||
if (_bSetTokenAcknowledge) // set token INT
|
if (_bSetTokenAcknowledge) // set token INT
|
||||||
{
|
{
|
||||||
|
Common::AtomicStore(*(volatile u32*)&g_bSignalTokenInterrupt, 1);
|
||||||
|
}
|
||||||
|
|
||||||
Common::AtomicStore(*(volatile u32*)&CommandProcessor::fifo.PEToken, _token);
|
|
||||||
CommandProcessor::interruptTokenWaiting = true;
|
CommandProcessor::interruptTokenWaiting = true;
|
||||||
CoreTiming::ScheduleEvent_Threadsafe(0, et_SetTokenOnMainThread, _token | (_bSetTokenAcknowledge << 16));
|
CoreTiming::ScheduleEvent_Threadsafe(0, et_SetTokenOnMainThread, _token | (_bSetTokenAcknowledge << 16));
|
||||||
}
|
|
||||||
else // set token value
|
|
||||||
{
|
|
||||||
// we do it directly from videoThread because of
|
|
||||||
// Super Monkey Ball
|
|
||||||
// XXX: No 16-bit atomic store available, so cheat and use 32-bit.
|
|
||||||
// That's what we've always done. We're counting on fifo.PEToken to be
|
|
||||||
// 4-byte padded.
|
|
||||||
Common::AtomicStore(*(volatile u32*)&CommandProcessor::fifo.PEToken, _token);
|
|
||||||
}
|
|
||||||
IncrementCheckContextId();
|
IncrementCheckContextId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -477,7 +469,6 @@ void ResetSetFinish()
|
|||||||
{
|
{
|
||||||
UpdateFinishInterrupt(false);
|
UpdateFinishInterrupt(false);
|
||||||
g_bSignalFinishInterrupt = false;
|
g_bSignalFinishInterrupt = false;
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -491,8 +482,7 @@ void ResetSetToken()
|
|||||||
if (g_bSignalTokenInterrupt)
|
if (g_bSignalTokenInterrupt)
|
||||||
{
|
{
|
||||||
UpdateTokenInterrupt(false);
|
UpdateTokenInterrupt(false);
|
||||||
g_bSignalTokenInterrupt = false;
|
g_bSignalTokenInterrupt = 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -500,17 +490,4 @@ void ResetSetToken()
|
|||||||
}
|
}
|
||||||
CommandProcessor::interruptTokenWaiting = false;
|
CommandProcessor::interruptTokenWaiting = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WaitingForPEInterrupt()
|
|
||||||
{
|
|
||||||
return !CommandProcessor::waitingForPEInterruptDisable && (CommandProcessor::interruptFinishWaiting || CommandProcessor::interruptTokenWaiting || interruptSetFinish || interruptSetToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ResumeWaitingForPEInterrupt()
|
|
||||||
{
|
|
||||||
interruptSetFinish = false;
|
|
||||||
interruptSetToken = false;
|
|
||||||
CommandProcessor::interruptFinishWaiting = false;
|
|
||||||
CommandProcessor::interruptTokenWaiting = false;
|
|
||||||
}
|
|
||||||
} // end of namespace PixelEngine
|
} // end of namespace PixelEngine
|
||||||
|
@ -81,8 +81,6 @@ void SetToken(const u16 _token, const int _bSetTokenAcknowledge);
|
|||||||
void SetFinish(void);
|
void SetFinish(void);
|
||||||
void ResetSetFinish(void);
|
void ResetSetFinish(void);
|
||||||
void ResetSetToken(void);
|
void ResetSetToken(void);
|
||||||
bool WaitingForPEInterrupt();
|
|
||||||
void ResumeWaitingForPEInterrupt();
|
|
||||||
|
|
||||||
// Bounding box functionality. Paper Mario (both) are a couple of the few games that use it.
|
// Bounding box functionality. Paper Mario (both) are a couple of the few games that use it.
|
||||||
extern u16 bbox[4];
|
extern u16 bbox[4];
|
||||||
|
Loading…
x
Reference in New Issue
Block a user