mirror of https://github.com/PCSX2/pcsx2.git
Improved MTGS (added better suspend/resume support), and work on savestates a bit (still not working tho)
git-svn-id: http://pcsx2.googlecode.com/svn/trunk@1908 96395faa-99c1-11dd-bbfe-3dabce05a288
This commit is contained in:
parent
2046b8e7b4
commit
aaa3b773c6
|
@ -120,6 +120,8 @@ struct u128
|
|||
u128( u32 src ) :
|
||||
lo( src )
|
||||
, hi( 0 ) {}
|
||||
|
||||
u128() {}
|
||||
};
|
||||
|
||||
struct s128
|
||||
|
@ -136,6 +138,8 @@ struct s128
|
|||
s128( s32 src ) :
|
||||
lo( src )
|
||||
, hi( 0 ) {}
|
||||
|
||||
s128() {}
|
||||
};
|
||||
|
||||
#else
|
||||
|
|
|
@ -20,10 +20,11 @@
|
|||
// --------------------------------------------------------------------------------------
|
||||
// <<< Important Notes to Plugin Authors >>>
|
||||
// --------------------------------------------------------------------------------------
|
||||
// * Exceptions thrown by plugins may not be handled correctly if allowed to escape the
|
||||
// scope of the plugin, and could result in odd crashes. For C++ plugins this means
|
||||
// ensuring that any code that uses 'new' or STL containers (string, list, vector, etc)
|
||||
// are contained within a try{} block, since the STL can throw std::bad_alloc.
|
||||
// * C++ only: Exceptions thrown by plugins may not be handled correctly if allowed to
|
||||
// escape the scope of the plugin, and could result in unexpected behavior or odd crashes.
|
||||
// For C++ plugins this means ensuring that any code that uses 'new' or STL containers
|
||||
// (string, list, vector, etc) are contained within a try{} block, since the STL can
|
||||
// throw std::bad_alloc.
|
||||
//
|
||||
// * Many callbacks are optional, and have been marked as such. Any optional callback can be
|
||||
// left NULL. Any callback not marked optional and left NULL will cause the emulator to
|
||||
|
@ -39,9 +40,9 @@
|
|||
// allocated by a plugin must be freed by that plugin.
|
||||
//
|
||||
// * C++ exception handling cannot be used by either plugin callbacks or emulator callbacks.
|
||||
// This includes the Console callbacks, for example, since the nature of C++ RTTI could
|
||||
// cause a C++ plugin wth its own catch handlers to catch exceptions of mismatched types
|
||||
// from the emulator.
|
||||
// This includes the emulator's Console callbacks, for example, since the nature of C++
|
||||
// ID-based RTTI could cause a C++ plugin with its own catch handlers to catch exceptions
|
||||
// of mismatched types from the emulator.
|
||||
//
|
||||
|
||||
|
||||
|
@ -700,9 +701,6 @@ typedef struct _PS2E_ComponentAPI_Mcd
|
|||
// Returns:
|
||||
// 0 if the card is not available, or 1 if it is available.
|
||||
//
|
||||
// Exceptions:
|
||||
// None. This function should not throw.
|
||||
//
|
||||
BOOL (PS2E_CALLBACK* McdIsPresent)( PS2E_THISPTR thisptr, uint port, uint slot );
|
||||
|
||||
// McdRead
|
||||
|
@ -802,9 +800,6 @@ typedef struct _PS2E_ComponentAPI_Pad
|
|||
// Returns:
|
||||
// 0 if the card is not available, or 1 if it is available.
|
||||
//
|
||||
// Exceptions:
|
||||
// None. This function should not throw.
|
||||
//
|
||||
BOOL (PS2E_CALLBACK* PadIsPresent)( PS2E_THISPTR thisptr, uint port, uint slot );
|
||||
|
||||
// PadStartPoll
|
||||
|
@ -813,9 +808,6 @@ typedef struct _PS2E_ComponentAPI_Pad
|
|||
// Returns:
|
||||
// First byte in response to the poll (Typically 0xff).
|
||||
//
|
||||
// Exceptions:
|
||||
// None. This function should not throw.
|
||||
//
|
||||
u8 (PS2E_CALLBACK* PadStartPoll)( PS2E_THISPTR thisptr, uint port, uint slot );
|
||||
|
||||
// PadPoll
|
||||
|
@ -824,9 +816,6 @@ typedef struct _PS2E_ComponentAPI_Pad
|
|||
// Returns:
|
||||
// Next byte in response to the poll.
|
||||
//
|
||||
// Exceptions:
|
||||
// None. This function should not throw.
|
||||
//
|
||||
u8 (PS2E_CALLBACK* PadPoll)( PS2E_THISPTR thisptr, u8 value );
|
||||
|
||||
// PadKeyEvent
|
||||
|
@ -836,9 +825,6 @@ typedef struct _PS2E_ComponentAPI_Pad
|
|||
// PS2E_KeyEvent: Key being pressed or released. Should stay valid until next call to
|
||||
// PadKeyEvent or plugin is closed with EmuClose.
|
||||
//
|
||||
// Exceptions:
|
||||
// None. This function should not throw.
|
||||
//
|
||||
typedef PS2E_KeyEvent* (CALLBACK* PadKeyEvent)();
|
||||
|
||||
void* reserved[8];
|
||||
|
|
|
@ -549,9 +549,9 @@ public:
|
|||
template< class Key, class T >
|
||||
class HashMap : public google::dense_hash_map<Key, T, CommonHashClass>
|
||||
{
|
||||
public:
|
||||
typedef typename google::dense_hash_map<Key, T, CommonHashClass> _parent;
|
||||
|
||||
public:
|
||||
using _parent::operator[];
|
||||
using _parent::end;
|
||||
typedef typename _parent::const_iterator const_iterator;
|
||||
|
|
|
@ -53,18 +53,20 @@ wxString GetTranslation( const char* msg )
|
|||
// LogicErrors enabled as First-Chance exceptions regardless, so do it now. :)
|
||||
//
|
||||
// Returns:
|
||||
// FALSE if the assertion succeeded (condition is valid), or true if the assertion
|
||||
// TRUE if the assertion succeeded (condition is valid), or FALSE if the assertion
|
||||
// failed. The true clause is only reachable in release builds, and can be used by code
|
||||
// to provide a "stable" escape clause for unexpected behavior.
|
||||
//
|
||||
DEVASSERT_INLINE bool DevAssert( bool condition, const char* msg )
|
||||
{
|
||||
if( condition ) return false;
|
||||
if( IsDevBuild )
|
||||
throw Exception::LogicError( msg );
|
||||
if( condition ) return true;
|
||||
|
||||
wxASSERT_MSG_A( false, msg );
|
||||
return true;
|
||||
|
||||
if( IsDevBuild && !IsDebugBuild )
|
||||
throw Exception::LogicError( msg );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
@ -123,8 +123,6 @@ namespace Threading
|
|||
}
|
||||
else
|
||||
pthread_detach( m_thread );
|
||||
|
||||
m_running = false;
|
||||
}
|
||||
|
||||
// Blocks execution of the calling thread until this thread completes its task. The
|
||||
|
@ -136,19 +134,10 @@ namespace Threading
|
|||
// This method is roughly the equivalent of pthread_join().
|
||||
//
|
||||
sptr PersistentThread::Block()
|
||||
{
|
||||
if( _InterlockedExchange( &m_detached, true ) )
|
||||
{
|
||||
// already detached: if we're still running then its an invalid operation
|
||||
if( m_running )
|
||||
throw Exception::InvalidOperation( "Blocking on detached threads requires manual semaphore implementation." );
|
||||
|
||||
return m_returncode;
|
||||
}
|
||||
else
|
||||
{
|
||||
DevAssert( !IsSelf(), "Thread deadlock detected; Block() should never be called by the owner thread." );
|
||||
|
||||
if( m_running )
|
||||
#if wxUSE_GUI
|
||||
m_sem_finished.WaitGui();
|
||||
#else
|
||||
|
@ -156,7 +145,6 @@ namespace Threading
|
|||
#endif
|
||||
return m_returncode;
|
||||
}
|
||||
}
|
||||
|
||||
bool PersistentThread::IsSelf() const
|
||||
{
|
||||
|
|
|
@ -377,7 +377,7 @@ void cdvdReadKey(u8 arg0, u16 arg1, u32 arg2, u8* key) {
|
|||
{
|
||||
ElfCRC = loadElfCRC( str );
|
||||
ElfApplyPatches();
|
||||
GSsetGameCRC( ElfCRC, 0 );
|
||||
mtgsThread.SendGameCRC( ElfCRC );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -510,7 +510,7 @@ void cdvdDetectDisk()
|
|||
{
|
||||
ElfCRC = loadElfCRC( str.ToAscii().data() );
|
||||
ElfApplyPatches();
|
||||
GSsetGameCRC( ElfCRC, 0 );
|
||||
mtgsThread.SendGameCRC( ElfCRC );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -443,6 +443,7 @@ __forceinline void rcntUpdate_vSync()
|
|||
if (vsyncCounter.Mode == MODE_VSYNC)
|
||||
{
|
||||
eeRecIsReset = false;
|
||||
mtgsThread.PollStatus();
|
||||
SysCoreThread::Get().StateCheck();
|
||||
if( eeRecIsReset )
|
||||
{
|
||||
|
|
|
@ -175,11 +175,11 @@ void __fastcall WriteFIFO_page_6(u32 mem, const mem128_t *value)
|
|||
psHu64(0x6008) = value[1];
|
||||
|
||||
FreezeRegs(1);
|
||||
mtgsThread->PrepDataPacket(GIF_PATH_3, nloop0_packet, 1);
|
||||
u64* data = (u64*)mtgsThread->GetDataPacketPtr();
|
||||
mtgsThread.PrepDataPacket(GIF_PATH_3, nloop0_packet, 1);
|
||||
u64* data = (u64*)mtgsThread.GetDataPacketPtr();
|
||||
data[0] = value[0];
|
||||
data[1] = value[1];
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
FreezeRegs(0);
|
||||
}
|
||||
|
||||
|
|
80
pcsx2/GS.cpp
80
pcsx2/GS.cpp
|
@ -67,7 +67,7 @@ void _gs_ChangeTimings( u32 framerate, u32 iTicks )
|
|||
|
||||
void gsOnModeChanged( u32 framerate, u32 newTickrate )
|
||||
{
|
||||
mtgsThread->SendSimplePacket( GS_RINGTYPE_MODECHANGE, framerate, newTickrate, 0 );
|
||||
mtgsThread.SendSimplePacket( GS_RINGTYPE_MODECHANGE, framerate, newTickrate, 0 );
|
||||
}
|
||||
|
||||
static bool gsIsInterlaced = false;
|
||||
|
@ -92,9 +92,7 @@ void gsInit()
|
|||
|
||||
void gsReset()
|
||||
{
|
||||
// Sanity check in case the plugin hasn't been initialized...
|
||||
if( mtgsThread == NULL ) return;
|
||||
mtgsThread->Reset();
|
||||
mtgsThread.ResetGS();
|
||||
|
||||
gsOnModeChanged(
|
||||
(gsRegionMode == Region_NTSC) ? FRAMERATE_NTSC : FRAMERATE_PAL,
|
||||
|
@ -110,36 +108,8 @@ void gsReset()
|
|||
psHu32(GIF_MODE) = 0;
|
||||
}
|
||||
|
||||
bool gsGIFSoftReset( int mask )
|
||||
{
|
||||
if( GSgifSoftReset == NULL )
|
||||
{
|
||||
static bool warned = false;
|
||||
if( !warned )
|
||||
{
|
||||
Console::Notice( "GIF Warning > Soft reset requested, but the GS plugin doesn't support it!" );
|
||||
//warned = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
mtgsThread->GIFSoftReset( mask );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void gsGIFReset()
|
||||
{
|
||||
// fixme - should this be here? (air)
|
||||
//memzero_obj(g_RealGSMem);
|
||||
// none of this should be here, its a GIF reset, not GS, only the dma side of it is reset. (Refraction)
|
||||
|
||||
// perform a soft reset (but do not do a full reset if the soft reset API is unavailable)
|
||||
//gsGIFSoftReset( 7 );
|
||||
|
||||
|
||||
//GSCSRr = 0x551B4000; // Set the FINISH bit to 1 for now
|
||||
//GSIMR = 0x7f00;
|
||||
psHu32(GIF_STAT) = 0;
|
||||
psHu32(GIF_CTRL) = 0;
|
||||
psHu32(GIF_MODE) = 0;
|
||||
|
@ -147,20 +117,21 @@ void gsGIFReset()
|
|||
|
||||
void gsCSRwrite(u32 value)
|
||||
{
|
||||
|
||||
|
||||
// Our emulated GS has no FIFO...
|
||||
/*if( value & 0x100 ) { // FLUSH
|
||||
//Console::WriteLn("GS_CSR FLUSH GS fifo: %x (CSRr=%x)", value, GSCSRr);
|
||||
}*/
|
||||
|
||||
if (value & 0x200) { // resetGS
|
||||
|
||||
// perform a soft reset -- and fall back to doing a full reset if the plugin doesn't
|
||||
// support soft resets.
|
||||
// perform a soft reset -- which is a clearing of all GIFpaths -- and fall back to doing
|
||||
// a full reset if the plugin doesn't support soft resets.
|
||||
|
||||
if( !gsGIFSoftReset( 7 ) )
|
||||
mtgsThread->SendSimplePacket( GS_RINGTYPE_RESET, 0, 0, 0 );
|
||||
if( GSgifSoftReset != NULL )
|
||||
{
|
||||
GIFPath_Clear( GIF_PATH_1 );
|
||||
GIFPath_Clear( GIF_PATH_2 );
|
||||
GIFPath_Clear( GIF_PATH_3 );
|
||||
}
|
||||
else
|
||||
{
|
||||
mtgsThread.SendSimplePacket( GS_RINGTYPE_RESET, 0, 0, 0 );
|
||||
}
|
||||
|
||||
CSRw |= 0x1f;
|
||||
GSCSRr = 0x551B4000; // Set the FINISH bit to 1 - GS is always at a finish state as we don't have a FIFO(saqib)
|
||||
|
@ -168,12 +139,13 @@ void gsCSRwrite(u32 value)
|
|||
}
|
||||
else if( value & 0x100 ) // FLUSH
|
||||
{
|
||||
// Our emulated GS has no FIFO, but if it did, it would flush it here...
|
||||
//Console::WriteLn("GS_CSR FLUSH GS fifo: %x (CSRr=%x)", value, GSCSRr);
|
||||
}
|
||||
else
|
||||
{
|
||||
CSRw |= value & 0x1f;
|
||||
mtgsThread->SendSimplePacket( GS_RINGTYPE_WRITECSR, CSRw, 0, 0 );
|
||||
mtgsThread.SendSimplePacket( GS_RINGTYPE_WRITECSR, CSRw, 0, 0 );
|
||||
GSCSRr = ((GSCSRr&~value)&0x1f)|(GSCSRr&~0x1f);
|
||||
}
|
||||
|
||||
|
@ -204,7 +176,7 @@ __forceinline void gsWrite8(u32 mem, u8 value)
|
|||
gsCSRwrite((CSRw & ~0xff000000) | (value << 24)); break;
|
||||
default:
|
||||
*PS2GS_BASE(mem) = value;
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE8, mem&0x13ff, value, 0);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE8, mem&0x13ff, value, 0);
|
||||
}
|
||||
GIF_LOG("GS write 8 at %8.8lx with data %8.8lx", mem, value);
|
||||
}
|
||||
|
@ -248,7 +220,7 @@ __forceinline void gsWrite16(u32 mem, u16 value)
|
|||
}
|
||||
|
||||
*(u16*)PS2GS_BASE(mem) = value;
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE16, mem&0x13ff, value, 0);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE16, mem&0x13ff, value, 0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -273,7 +245,7 @@ __forceinline void gsWrite32(u32 mem, u32 value)
|
|||
}
|
||||
|
||||
*(u32*)PS2GS_BASE(mem) = value;
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE32, mem&0x13ff, value, 0);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE32, mem&0x13ff, value, 0);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -309,7 +281,7 @@ void __fastcall gsWrite64_generic( u32 mem, const mem64_t* value )
|
|||
GIF_LOG("GS Write64 at %8.8lx with data %8.8x_%8.8x", mem, srcval32[1], srcval32[0]);
|
||||
|
||||
*(u64*)PS2GS_BASE(mem) = *value;
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE64, mem&0x13ff, srcval32[0], srcval32[1]);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE64, mem&0x13ff, srcval32[0], srcval32[1]);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
@ -350,8 +322,8 @@ void __fastcall gsWrite128_generic( u32 mem, const mem128_t* value )
|
|||
writeTo[0] = value[0];
|
||||
writeTo[1] = value[1];
|
||||
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE64, masked_mem, srcval32[0], srcval32[1]);
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_MEMWRITE64, masked_mem+8, srcval32[2], srcval32[3]);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE64, masked_mem, srcval32[0], srcval32[1]);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_MEMWRITE64, masked_mem+8, srcval32[2], srcval32[3]);
|
||||
}
|
||||
|
||||
__forceinline u8 gsRead8(u32 mem)
|
||||
|
@ -393,7 +365,7 @@ void gsSyncLimiterLostTime( s32 deltaTime )
|
|||
|
||||
//Console::WriteLn("LostTime on the EE!");
|
||||
|
||||
mtgsThread->SendSimplePacket(
|
||||
mtgsThread.SendSimplePacket(
|
||||
GS_RINGTYPE_STARTTIME,
|
||||
deltaTime,
|
||||
0,
|
||||
|
@ -537,7 +509,7 @@ __forceinline void gsFrameSkip( bool forceskip )
|
|||
void gsPostVsyncEnd( bool updategs )
|
||||
{
|
||||
*(u32*)(PS2MEM_GS+0x1000) ^= 0x2000; // swap the vsync field
|
||||
mtgsThread->PostVsyncEnd( updategs );
|
||||
mtgsThread.PostVsyncEnd( updategs );
|
||||
}
|
||||
|
||||
void _gs_ResetFrameskip()
|
||||
|
@ -548,7 +520,7 @@ void _gs_ResetFrameskip()
|
|||
// Disables the GS Frameskip at runtime without any racy mess...
|
||||
void gsResetFrameSkip()
|
||||
{
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_FRAMESKIP, 0, 0, 0);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_FRAMESKIP, 0, 0, 0);
|
||||
}
|
||||
|
||||
void gsDynamicSkipEnable()
|
||||
|
@ -564,5 +536,5 @@ void SaveStateBase::gsFreeze()
|
|||
{
|
||||
FreezeMem(PS2MEM_GS, 0x2000);
|
||||
Freeze(CSRw);
|
||||
mtgsFreeze();
|
||||
gifPathFreeze();
|
||||
}
|
||||
|
|
192
pcsx2/GS.h
192
pcsx2/GS.h
|
@ -16,12 +16,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "Common.h"
|
||||
#include "Utilities/Threading.h"
|
||||
#include "SysThreads.h"
|
||||
|
||||
PCSX2_ALIGNED16( extern u8 g_RealGSMem[0x2000] );
|
||||
#define GSCSRr *((u64*)(g_RealGSMem+0x1000))
|
||||
#define GSIMR *((u32*)(g_RealGSMem+0x1010))
|
||||
#define GSSIGLBLID ((GSRegSIGBLID*)(g_RealGSMem+0x1080))
|
||||
PCSX2_ALIGNED16( extern u8 g_RealGSMem[Ps2MemSize::GSregs] );
|
||||
|
||||
#define PS2MEM_GS g_RealGSMem
|
||||
#define PS2GS_BASE(mem) (g_RealGSMem+(mem&0x13ff))
|
||||
|
||||
#define GSCSRr ((u64&)*(g_RealGSMem+0x1000))
|
||||
#define GSIMR ((u32&)*(g_RealGSMem+0x1010))
|
||||
#define GSSIGLBLID ((GSRegSIGBLID&)*(g_RealGSMem+0x1080))
|
||||
|
||||
enum GS_RegionMode
|
||||
{
|
||||
|
@ -29,105 +33,6 @@ enum GS_RegionMode
|
|||
Region_PAL
|
||||
};
|
||||
|
||||
extern GS_RegionMode gsRegionMode;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////
|
||||
// MTGS GIFtag Parser - Declaration
|
||||
//
|
||||
// The MTGS needs a dummy "GS plugin" for processing SIGNAL, FINISH, and LABEL
|
||||
// commands. These commands trigger gsIRQs, which need to be handled accurately
|
||||
// in synch with the EE (which can be running several frames ahead of the MTGS)
|
||||
//
|
||||
// Yeah, it's a lot of work, but the performance gains are huge, even on HT cpus.
|
||||
|
||||
struct GSRegSIGBLID
|
||||
{
|
||||
u32 SIGID;
|
||||
u32 LBLID;
|
||||
};
|
||||
|
||||
enum GIF_FLG
|
||||
{
|
||||
GIF_FLG_PACKED = 0,
|
||||
GIF_FLG_REGLIST = 1,
|
||||
GIF_FLG_IMAGE = 2,
|
||||
GIF_FLG_IMAGE2 = 3
|
||||
};
|
||||
|
||||
enum GIF_REG
|
||||
{
|
||||
GIF_REG_PRIM = 0x00,
|
||||
GIF_REG_RGBA = 0x01,
|
||||
GIF_REG_STQ = 0x02,
|
||||
GIF_REG_UV = 0x03,
|
||||
GIF_REG_XYZF2 = 0x04,
|
||||
GIF_REG_XYZ2 = 0x05,
|
||||
GIF_REG_TEX0_1 = 0x06,
|
||||
GIF_REG_TEX0_2 = 0x07,
|
||||
GIF_REG_CLAMP_1 = 0x08,
|
||||
GIF_REG_CLAMP_2 = 0x09,
|
||||
GIF_REG_FOG = 0x0a,
|
||||
GIF_REG_XYZF3 = 0x0c,
|
||||
GIF_REG_XYZ3 = 0x0d,
|
||||
GIF_REG_A_D = 0x0e,
|
||||
GIF_REG_NOP = 0x0f,
|
||||
};
|
||||
|
||||
// GIFTAG
|
||||
// Members of this structure are in CAPS to help visually denote that they are representative
|
||||
// of actual hw register states of the GIF, unlike the internal tracking vars in GIFPath, which
|
||||
// are modified during the GIFtag unpacking process.
|
||||
struct GIFTAG
|
||||
{
|
||||
u32 NLOOP : 15;
|
||||
u32 EOP : 1;
|
||||
u32 dummy0 : 16;
|
||||
u32 dummy1 : 14;
|
||||
u32 PRE : 1;
|
||||
u32 PRIM : 11;
|
||||
u32 FLG : 2;
|
||||
u32 NREG : 4;
|
||||
u32 REGS[2];
|
||||
|
||||
GIFTAG() {}
|
||||
};
|
||||
|
||||
struct GIFPath
|
||||
{
|
||||
const GIFTAG tag; // The "original tag -- modification allowed only by SetTag(), so let's make it const.
|
||||
u8 regs[16]; // positioned after tag ensures 16-bit aligned (in case we SSE optimize later)
|
||||
|
||||
u32 nloop; // local copy nloop counts toward zero, and leaves the tag copy unmodified.
|
||||
u32 curreg; // reg we left of on (for traversing through loops)
|
||||
u32 numregs; // number of regs (when NREG is 0, numregs is 16)
|
||||
u8 hasADreg; // has an A+D reg, if it doesn't have one, then it no need to check for gs interrupts
|
||||
|
||||
GIFPath();
|
||||
|
||||
__forceinline void PrepPackedRegs();
|
||||
__forceinline void SetTag(const void* mem);
|
||||
__forceinline bool StepReg() {
|
||||
if ((++curreg & 0xf) == tag.NREG) {
|
||||
curreg = 0;
|
||||
if (--nloop == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
__forceinline u8 GetReg() {
|
||||
return regs[curreg&0xf];
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// MTGS Threaded Class Declaration
|
||||
|
||||
// Uncomment this to enable the MTGS debug stack, which tracks to ensure reads
|
||||
// and writes stay synchronized. Warning: the debug stack is VERY slow.
|
||||
//#define RINGBUF_DEBUG_STACK
|
||||
|
||||
enum GIF_PATH
|
||||
{
|
||||
GIF_PATH_1 = 0,
|
||||
|
@ -135,6 +40,18 @@ enum GIF_PATH
|
|||
GIF_PATH_3,
|
||||
};
|
||||
|
||||
extern int GIFPath_ParseTag(GIF_PATH pathidx, const u8* pMem, u32 size);
|
||||
extern void GIFPath_Reset();
|
||||
extern void GIFPath_Clear( GIF_PATH pathidx );
|
||||
|
||||
extern GS_RegionMode gsRegionMode;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// MTGS Threaded Class Declaration
|
||||
|
||||
// Uncomment this to enable the MTGS debug stack, which tracks to ensure reads
|
||||
// and writes stay synchronized. Warning: the debug stack is VERY slow.
|
||||
//#define RINGBUF_DEBUG_STACK
|
||||
|
||||
enum GS_RINGTYPE
|
||||
{
|
||||
|
@ -154,8 +71,8 @@ enum GS_RINGTYPE
|
|||
, GS_RINGTYPE_SOFTRESET // issues a soft reset for the GIF
|
||||
, GS_RINGTYPE_WRITECSR
|
||||
, GS_RINGTYPE_MODECHANGE // for issued mode changes.
|
||||
, GS_RINGTYPE_CRC
|
||||
, GS_RINGTYPE_STARTTIME // special case for min==max fps frameskip settings
|
||||
, GS_RINGTYPE_QUIT
|
||||
};
|
||||
|
||||
|
||||
|
@ -165,42 +82,27 @@ struct MTGS_FreezeData
|
|||
s32 retval; // value returned from the call, valid only after an mtgsWaitGS()
|
||||
};
|
||||
|
||||
class mtgsThreadObject : public Threading::PersistentThread
|
||||
class mtgsThreadObject : public SysSuspendableThread
|
||||
{
|
||||
friend class SaveStateBase;
|
||||
|
||||
protected:
|
||||
// Size of the ringbuffer as a power of 2 -- size is a multiple of simd128s.
|
||||
// (actual size is 1<<m_RingBufferSizeFactor simd vectors [128-bit values])
|
||||
// A value of 19 is a 8meg ring buffer. 18 would be 4 megs, and 20 would be 16 megs.
|
||||
// Default was 2mb, but some games with lots of MTGS activity want 8mb to run fast (rama)
|
||||
static const uint m_RingBufferSizeFactor = 19;
|
||||
|
||||
// size of the ringbuffer in simd128's.
|
||||
static const uint m_RingBufferSize = 1<<m_RingBufferSizeFactor;
|
||||
|
||||
// Mask to apply to ring buffer indices to wrap the pointer from end to
|
||||
// start (the wrapping is what makes it a ringbuffer, yo!)
|
||||
static const uint m_RingBufferMask = m_RingBufferSize - 1;
|
||||
typedef SysSuspendableThread _parent;
|
||||
|
||||
protected:
|
||||
// note: when g_pGSRingPos == g_pGSWritePos, the fifo is empty
|
||||
uint m_RingPos; // cur pos gs is reading from
|
||||
uint m_WritePos; // cur pos ee thread is writing to
|
||||
|
||||
// used to regulate thread startup and gsInit
|
||||
Threading::Semaphore m_sem_InitDone;
|
||||
|
||||
Threading::MutexLock m_lock_RingRestart;
|
||||
Semaphore m_sem_OpenDone;
|
||||
MutexLock m_lock_RingRestart;
|
||||
|
||||
// used to keep multiple threads from sending packets to the ringbuffer concurrently.
|
||||
Threading::MutexLock m_PacketLocker;
|
||||
MutexLock m_PacketLocker;
|
||||
|
||||
// Used to delay the sending of events. Performance is better if the ringbuffer
|
||||
// has more than one command in it when the thread is kicked.
|
||||
int m_CopyCommandTally;
|
||||
int m_CopyDataTally;
|
||||
volatile u32 m_RingBufferIsBusy;
|
||||
volatile bool m_RingBufferIsBusy;
|
||||
volatile bool m_LoadState;
|
||||
|
||||
// Counts the number of vsync frames queued in the MTGS ringbuffer. This is used to
|
||||
// throttle the number of frames allowed to be rendered ahead of time for games that
|
||||
|
@ -209,7 +111,7 @@ protected:
|
|||
|
||||
// Protection lock for the frame queue counter -- needed because we can't safely
|
||||
// AtomicExchange from two threads.
|
||||
Threading::MutexLock m_lock_FrameQueueCounter;
|
||||
MutexLock m_lock_FrameQueueCounter;
|
||||
|
||||
// These vars maintain instance data for sending Data Packets.
|
||||
// Only one data packet can be constructed and uploaded at a time.
|
||||
|
@ -221,40 +123,39 @@ protected:
|
|||
Threading::MutexLock m_lock_Stack;
|
||||
#endif
|
||||
|
||||
// contains aligned memory allocations for gs and Ringbuffer.
|
||||
SafeAlignedArray<u128,16> m_RingBuffer;
|
||||
|
||||
// mtgs needs its own memory space separate from the PS2. The PS2 memory is in
|
||||
// sync with the EE while this stays in sync with the GS (ie, it lags behind)
|
||||
u8* const m_gsMem;
|
||||
|
||||
public:
|
||||
mtgsThreadObject();
|
||||
virtual ~mtgsThreadObject() throw();
|
||||
|
||||
void Start();
|
||||
void Cancel();
|
||||
void Reset();
|
||||
void GIFSoftReset( int mask );
|
||||
void PollStatus();
|
||||
|
||||
// Waits for the GS to empty out the entire ring buffer contents.
|
||||
// Used primarily for plugin startup/shutdown.
|
||||
void WaitGS();
|
||||
void ResetGS();
|
||||
|
||||
int PrepDataPacket( GIF_PATH pathidx, const u8* srcdata, u32 size );
|
||||
int PrepDataPacket( GIF_PATH pathidx, const u32* srcdata, u32 size );
|
||||
void SendDataPacket();
|
||||
void SendGameCRC( u32 crc );
|
||||
void WaitForOpen();
|
||||
void Freeze( int mode, MTGS_FreezeData& data );
|
||||
|
||||
void SendSimplePacket( GS_RINGTYPE type, int data0, int data1, int data2 );
|
||||
void SendPointerPacket( GS_RINGTYPE type, u32 data0, void* data1 );
|
||||
|
||||
u8* GetDataPacketPtr() const;
|
||||
void Freeze( SaveStateBase& state );
|
||||
void SetEvent();
|
||||
|
||||
void PostVsyncEnd( bool updategs );
|
||||
|
||||
protected:
|
||||
void OpenPlugin();
|
||||
void OnSuspendInThread();
|
||||
void OnResumeInThread();
|
||||
|
||||
void OnResumeReady();
|
||||
|
||||
// Saves MMX/XMM REGS, posts an event to the mtgsThread flag and releases a timeslice.
|
||||
// For use in surrounding loops that wait on the mtgs.
|
||||
void PrepEventWait();
|
||||
|
@ -262,23 +163,15 @@ protected:
|
|||
// Restores MMX/XMM REGS. For use in surrounding loops that wait on the mtgs.
|
||||
void PostEventWait() const;
|
||||
|
||||
// Processes a GIFtag & packet, and throws out some gsIRQs as needed.
|
||||
// Used to keep interrupts in sync with the EE, while the GS itself
|
||||
// runs potentially several frames behind.
|
||||
int gifTransferDummy(GIF_PATH pathidx, const u8 *pMem, u32 size);
|
||||
int _gifTransferDummy(GIF_PATH pathidx, const u8 *pMem, u32 size);
|
||||
|
||||
// Used internally by SendSimplePacket type functions
|
||||
uint _PrepForSimplePacket();
|
||||
void _FinishSimplePacket( uint future_writepos );
|
||||
void _RingbufferLoop();
|
||||
sptr ExecuteTask();
|
||||
};
|
||||
|
||||
extern mtgsThreadObject* mtgsThread;
|
||||
PCSX2_ALIGNED16_EXTERN( mtgsThreadObject mtgsThread );
|
||||
|
||||
void mtgsWaitGS();
|
||||
void mtgsOpen();
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Generalized GS Functions and Stuff
|
||||
|
@ -301,7 +194,6 @@ extern void _gs_ChangeTimings( u32 framerate, u32 iTicks );
|
|||
|
||||
|
||||
// used for resetting GIF fifo
|
||||
bool gsGIFSoftReset( int mask );
|
||||
void gsGIFReset();
|
||||
void gsCSRwrite(u32 value);
|
||||
|
||||
|
|
|
@ -98,12 +98,12 @@ static u32 WRITERING_DMA(u32 *pMem, u32 qwc)
|
|||
{
|
||||
psHu32(GIF_STAT) |= GIF_STAT_APATH3 | GIF_STAT_OPH;
|
||||
|
||||
int size = mtgsThread->PrepDataPacket(GIF_PATH_3, pMem, qwc);
|
||||
u8* pgsmem = mtgsThread->GetDataPacketPtr();
|
||||
int size = mtgsThread.PrepDataPacket(GIF_PATH_3, pMem, qwc);
|
||||
u8* pgsmem = mtgsThread.GetDataPacketPtr();
|
||||
|
||||
memcpy_aligned(pgsmem, pMem, size<<4);
|
||||
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
return size;
|
||||
}
|
||||
|
||||
|
@ -116,7 +116,7 @@ int _GIFchain()
|
|||
if (pMem == NULL)
|
||||
{
|
||||
// reset path3, fixes dark cloud 2
|
||||
gsGIFSoftReset(4);
|
||||
GIFPath_Clear( GIF_PATH_3 );
|
||||
|
||||
//must increment madr and clear qwc, else it loops
|
||||
gif->madr += gif->qwc * 16;
|
||||
|
|
600
pcsx2/MTGS.cpp
600
pcsx2/MTGS.cpp
|
@ -13,21 +13,19 @@
|
|||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
#include <wx/datetime.h>
|
||||
|
||||
#include "Common.h"
|
||||
#include "VU.h"
|
||||
#include "GS.h"
|
||||
|
||||
#include "VU.h"
|
||||
#include "iR5900.h"
|
||||
#include "VifDma.h"
|
||||
|
||||
#include "SamplProf.h"
|
||||
|
||||
#include <list>
|
||||
#include <wx/datetime.h>
|
||||
|
||||
// Uncomment this to enable profiling of the GS RingBufferCopy function.
|
||||
//#define PCSX2_GSRING_SAMPLING_STATS
|
||||
|
||||
|
@ -46,152 +44,60 @@ using namespace std;
|
|||
|
||||
#define volatize(x) (*reinterpret_cast<volatile uint*>(&(x)))
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// BEGIN -- MTGS GIFtag Parse Implementation
|
||||
//
|
||||
// The MTGS needs a dummy "GS plugin" for processing SIGNAL, FINISH, and LABEL
|
||||
// commands. These commands trigger gsIRQs, which need to be handled accurately
|
||||
// in synch with the EE (which can be running several frames ahead of the MTGS)
|
||||
//
|
||||
// Yeah, it's a lot of work, but the performance gains are huge, even on HT cpus.
|
||||
|
||||
|
||||
// the MTGS "dummy" GIFtag info!
|
||||
// fixme: The real PS2 has a single internal PATH and 3 logical sources, not 3 entirely
|
||||
// separate paths. But for that to work properly we need also interlocked path sources.
|
||||
// That is, when the GIF selects a source, it sticks to that source until an EOP. Currently
|
||||
// this is not emulated!
|
||||
PCSX2_ALIGNED16( static GIFPath s_path[3] );
|
||||
|
||||
GIFPath::GIFPath() :
|
||||
tag()
|
||||
{
|
||||
memzero_obj( *this );
|
||||
}
|
||||
|
||||
// unpack the registers - registers are stored as a sequence of 4 bit values in the
|
||||
// upper 64 bits of the GIFTAG. That sucks for us when handling partialized GIF packets
|
||||
// coming in from paths 2 and 3, so we unpack them into an 8 bit array here.
|
||||
//
|
||||
__forceinline void GIFPath::PrepPackedRegs()
|
||||
{
|
||||
// Only unpack registers if we're starting a new pack. Otherwise the unpacked
|
||||
// array should have already been initialized by a previous partial transfer.
|
||||
|
||||
if (curreg != 0) return;
|
||||
|
||||
u32 tempreg = tag.REGS[0];
|
||||
numregs = ((tag.NREG-1)&0xf) + 1;
|
||||
|
||||
for (u32 i = 0; i < numregs; i++) {
|
||||
if (i == 8) tempreg = tag.REGS[1];
|
||||
regs[i] = tempreg & 0xf;
|
||||
tempreg >>= 4;
|
||||
}
|
||||
}
|
||||
|
||||
__forceinline void GIFPath::SetTag(const void* mem)
|
||||
{
|
||||
const_cast<GIFTAG&>(tag) = *((GIFTAG*)mem);
|
||||
|
||||
nloop = tag.NLOOP;
|
||||
curreg = 0;
|
||||
}
|
||||
|
||||
static void _mtgsFreezeGIF( SaveStateBase& state, GIFPath (&paths)[3] )
|
||||
{
|
||||
for(int i=0; i<3; i++ )
|
||||
{
|
||||
state.Freeze( paths[i].tag );
|
||||
state.Freeze( paths[i].nloop );
|
||||
state.Freeze( paths[i].curreg );
|
||||
state.Freeze( paths[i].numregs );
|
||||
}
|
||||
|
||||
for(int i=0; i<3; i++ )
|
||||
{
|
||||
state.Freeze( paths[i].regs );
|
||||
}
|
||||
}
|
||||
|
||||
void SaveStateBase::mtgsFreeze()
|
||||
{
|
||||
FreezeTag( "mtgs" );
|
||||
mtgsThread->Freeze( *this );
|
||||
}
|
||||
|
||||
|
||||
static void RegHandlerSIGNAL(const u32* data)
|
||||
{
|
||||
MTGS_LOG("MTGS SIGNAL data %x_%x CSRw %x IMR %x CSRr\n",data[0], data[1], CSRw, GSIMR, GSCSRr);
|
||||
|
||||
GSSIGLBLID->SIGID = (GSSIGLBLID->SIGID&~data[1])|(data[0]&data[1]);
|
||||
|
||||
if ((CSRw & 0x1))
|
||||
{
|
||||
if (!(GSIMR&0x100) )
|
||||
{
|
||||
gsIrq();
|
||||
}
|
||||
|
||||
GSCSRr |= 1; // signal
|
||||
}
|
||||
}
|
||||
|
||||
static void RegHandlerFINISH(const u32* data)
|
||||
{
|
||||
MTGS_LOG("MTGS FINISH data %x_%x CSRw %x\n", data[0], data[1], CSRw);
|
||||
|
||||
if ((CSRw & 0x2))
|
||||
{
|
||||
if (!(GSIMR&0x200))
|
||||
gsIrq();
|
||||
|
||||
GSCSRr |= 2; // finish
|
||||
}
|
||||
}
|
||||
|
||||
static void RegHandlerLABEL(const u32* data)
|
||||
{
|
||||
GSSIGLBLID->LBLID = (GSSIGLBLID->LBLID&~data[1])|(data[0]&data[1]);
|
||||
}
|
||||
|
||||
// END -- MTGS GIFtag Parse Implementation
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// =====================================================================================================
|
||||
// MTGS Threaded Class Implementation
|
||||
// =====================================================================================================
|
||||
|
||||
// Size of the ringbuffer as a power of 2 -- size is a multiple of simd128s.
|
||||
// (actual size is 1<<m_RingBufferSizeFactor simd vectors [128-bit values])
|
||||
// A value of 19 is a 8meg ring buffer. 18 would be 4 megs, and 20 would be 16 megs.
|
||||
// Default was 2mb, but some games with lots of MTGS activity want 8mb to run fast (rama)
|
||||
static const uint RingBufferSizeFactor = 19;
|
||||
|
||||
// size of the ringbuffer in simd128's.
|
||||
static const uint RingBufferSize = 1<<RingBufferSizeFactor;
|
||||
|
||||
// Mask to apply to ring buffer indices to wrap the pointer from end to
|
||||
// start (the wrapping is what makes it a ringbuffer, yo!)
|
||||
static const uint RingBufferMask = RingBufferSize - 1;
|
||||
|
||||
PCSX2_ALIGNED16( mtgsThreadObject mtgsThread );
|
||||
|
||||
struct MTGS_BufferedData
|
||||
{
|
||||
u128 m_Ring[RingBufferSize];
|
||||
u8 Regs[Ps2MemSize::GSregs];
|
||||
|
||||
MTGS_BufferedData() {}
|
||||
|
||||
u128& operator[]( uint idx )
|
||||
{
|
||||
jASSUME( idx < RingBufferSize );
|
||||
return m_Ring[idx];
|
||||
}
|
||||
};
|
||||
|
||||
PCSX2_ALIGNED( 32, static MTGS_BufferedData RingBuffer );
|
||||
|
||||
mtgsThreadObject* mtgsThread = NULL;
|
||||
|
||||
#ifdef RINGBUF_DEBUG_STACK
|
||||
#include <list>
|
||||
std::list<uint> ringposStack;
|
||||
#endif
|
||||
|
||||
#ifdef PCSX2_DEBUG
|
||||
// debug variable used to check for bad code bits where copies are started
|
||||
// but never closed, or closed without having been started. (GSRingBufCopy calls
|
||||
// should always be followed by a call to GSRINGBUF_DONECOPY)
|
||||
// And it's not even used in the debug code.
|
||||
//static int copyLock = 0;
|
||||
#endif
|
||||
|
||||
typedef void (*GIFRegHandler)(const u32* data);
|
||||
static GIFRegHandler s_GSHandlers[3] = { RegHandlerSIGNAL, RegHandlerFINISH, RegHandlerLABEL };
|
||||
|
||||
mtgsThreadObject::mtgsThreadObject() :
|
||||
PersistentThread()
|
||||
SysSuspendableThread()
|
||||
, m_RingPos( 0 )
|
||||
, m_WritePos( 0 )
|
||||
|
||||
, m_sem_InitDone()
|
||||
, m_lock_RingRestart()
|
||||
, m_PacketLocker( true ) // true - makes it a recursive lock
|
||||
|
||||
, m_CopyCommandTally( 0 )
|
||||
, m_CopyDataTally( 0 )
|
||||
, m_RingBufferIsBusy( 0 )
|
||||
, m_RingBufferIsBusy( false )
|
||||
, m_LoadState( false )
|
||||
, m_QueuedFrames( 0 )
|
||||
, m_lock_FrameQueueCounter()
|
||||
, m_packet_size( 0 )
|
||||
|
@ -200,41 +106,47 @@ mtgsThreadObject::mtgsThreadObject() :
|
|||
#ifdef RINGBUF_DEBUG_STACK
|
||||
, m_lock_Stack()
|
||||
#endif
|
||||
, m_RingBuffer( m_RingBufferSize + (Ps2MemSize::GSregs/sizeof(u128)) )
|
||||
, m_gsMem( (u8*)m_RingBuffer.GetPtr( m_RingBufferSize ) )
|
||||
{
|
||||
}
|
||||
|
||||
void mtgsThreadObject::Start()
|
||||
{
|
||||
m_sem_InitDone.Reset();
|
||||
PersistentThread::Start();
|
||||
m_returncode = 0;
|
||||
m_RingPos = 0;
|
||||
m_WritePos = 0;
|
||||
|
||||
// Wait for the thread to finish initialization (it runs GSopen, which can take
|
||||
// some time since it's creating a new window and all), and then check for errors.
|
||||
m_RingBufferIsBusy = false;
|
||||
m_LoadState = false;
|
||||
|
||||
m_sem_InitDone.WaitGui();
|
||||
m_QueuedFrames = 0;
|
||||
m_packet_size = 0;
|
||||
m_packet_ringpos = 0;
|
||||
|
||||
_parent::Start();
|
||||
m_ExecMode = ExecMode_Suspending;
|
||||
SetEvent();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::PollStatus()
|
||||
{
|
||||
if( m_ExecMode == ExecMode_NoThreadYet )
|
||||
{
|
||||
if( m_returncode != 0 ) // means the thread failed to init the GS plugin
|
||||
throw Exception::PluginOpenError( PluginId_GS );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
mtgsThreadObject::~mtgsThreadObject() throw()
|
||||
{
|
||||
mtgsThreadObject::Cancel();
|
||||
}
|
||||
|
||||
// Closes the GS "forcefully" without waiting for it to finish rendering it's pending
|
||||
// queue of GS data.
|
||||
void mtgsThreadObject::Cancel()
|
||||
void mtgsThreadObject::OnResumeReady()
|
||||
{
|
||||
//SendSimplePacket( GS_RINGTYPE_QUIT, 0, 0, 0 );
|
||||
//SetEvent();
|
||||
//m_sem_finished.WaitGui();
|
||||
PersistentThread::Cancel();
|
||||
m_sem_OpenDone.Reset();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::Reset()
|
||||
void mtgsThreadObject::ResetGS()
|
||||
{
|
||||
// MTGS Reset process:
|
||||
// * clear the ringbuffer.
|
||||
|
@ -246,133 +158,9 @@ void mtgsThreadObject::Reset()
|
|||
MTGS_LOG( "MTGS: Sending Reset..." );
|
||||
SendSimplePacket( GS_RINGTYPE_RESET, 0, 0, 0 );
|
||||
SendSimplePacket( GS_RINGTYPE_FRAMESKIP, 0, 0, 0 );
|
||||
SetEvent();
|
||||
|
||||
memzero_obj( s_path );
|
||||
}
|
||||
|
||||
#define incTag(x, y) do { \
|
||||
pMem += (x); \
|
||||
size -= (y); \
|
||||
if ((pathidx==GIF_PATH_1)&&(pMem>=vuMemEnd)) pMem -= 0x4000; \
|
||||
} while(false)
|
||||
|
||||
#define aMin(x, y) ((x < y) ? (x) : (y))
|
||||
#define subVal(x, y) ((x > y) ? (x-y) : 0 )
|
||||
#define optPrint(x, y) { if (x > y) DevCon::Status("Loops Optimized = %d", x); }
|
||||
|
||||
__forceinline void gsHandler(const u8* pMem) {
|
||||
const int handler = pMem[8];
|
||||
if (handler >= 0x60 && handler < 0x63) {
|
||||
//DevCon::Status("GIF Tag Interrupt");
|
||||
s_GSHandlers[handler&0x3]((const u32*)pMem);
|
||||
}
|
||||
}
|
||||
|
||||
// Parameters:
|
||||
// size (path1) - difference between the end of VU memory and pMem.
|
||||
// size (path2/3) - max size of incoming data stream, in qwc (simd128)
|
||||
__forceinline int mtgsThreadObject::_gifTransferDummy(GIF_PATH pathidx, const u8* pMem, u32 size)
|
||||
{
|
||||
GIFPath& path = s_path[pathidx]; // Current Path
|
||||
const u8* vuMemEnd = pMem + (size<<4); // End of VU1 Mem
|
||||
if (pathidx==GIF_PATH_1) size = 0x400; // VU1 mem size
|
||||
u32 startSize = size; // Start Size
|
||||
|
||||
while (size > 0) {
|
||||
if (!path.nloop) {
|
||||
|
||||
path.SetTag(pMem);
|
||||
incTag(16, 1);
|
||||
|
||||
if (pathidx == GIF_PATH_3) {
|
||||
if (path.tag.FLG&2) Path3progress = IMAGE_MODE;
|
||||
else Path3progress = TRANSFER_MODE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch(path.tag.FLG) {
|
||||
case GIF_FLG_PACKED:
|
||||
path.PrepPackedRegs();
|
||||
do {
|
||||
if (path.GetReg() == 0xe) {
|
||||
gsHandler(pMem);
|
||||
}
|
||||
incTag(16, 1);
|
||||
} while(path.StepReg() && size > 0);
|
||||
break;
|
||||
case GIF_FLG_REGLIST:
|
||||
{
|
||||
size *= 2;
|
||||
|
||||
do { incTag(8, 1); }
|
||||
while(path.StepReg() && size > 0);
|
||||
|
||||
if (size & 1) { incTag(8, 1); }
|
||||
size /= 2;
|
||||
}
|
||||
break;
|
||||
case GIF_FLG_IMAGE:
|
||||
case GIF_FLG_IMAGE2:
|
||||
{
|
||||
int len = aMin(size, path.nloop);
|
||||
incTag((len * 16), len);
|
||||
path.nloop -= len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.tag.EOP && !path.nloop) {
|
||||
if (pathidx != GIF_PATH_2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size = (startSize - size);
|
||||
|
||||
if (pathidx == GIF_PATH_3) {
|
||||
if (path.tag.EOP && !path.nloop) {
|
||||
Path3progress = STOPPED_MODE;
|
||||
}
|
||||
gif->madr += size * 16;
|
||||
gif->qwc -= size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Processes a GIFtag & packet, and throws out some gsIRQs as needed.
|
||||
// Used to keep interrupts in sync with the EE, while the GS itself
|
||||
// runs potentially several frames behind.
|
||||
// Parameters:
|
||||
// size - max size of incoming data stream, in qwc (simd128)
|
||||
__forceinline int mtgsThreadObject::gifTransferDummy(GIF_PATH pathidx, const u8* pMem, u32 size)
|
||||
{
|
||||
#ifdef PCSX2_GSRING_SAMPLING_STATS
|
||||
static uptr profStartPtr = 0;
|
||||
static uptr profEndPtr = 0;
|
||||
if (profStartPtr == 0) {
|
||||
__asm
|
||||
{
|
||||
__beginfunc:
|
||||
mov profStartPtr, offset __beginfunc;
|
||||
mov profEndPtr, offset __endfunc;
|
||||
}
|
||||
ProfilerRegisterSource( "GSRingBufCopy", (void*)profStartPtr, profEndPtr - profStartPtr );
|
||||
}
|
||||
#endif
|
||||
|
||||
int retSize = _gifTransferDummy(pathidx, pMem, size);
|
||||
|
||||
#ifdef PCSX2_GSRING_SAMPLING_STATS
|
||||
__asm
|
||||
{
|
||||
__endfunc:
|
||||
nop;
|
||||
}
|
||||
#endif
|
||||
return retSize;
|
||||
GIFPath_Reset();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::PostVsyncEnd( bool updategs )
|
||||
|
@ -410,29 +198,74 @@ struct PacketTagType
|
|||
};
|
||||
|
||||
extern bool renderswitch;
|
||||
static volatile long gsIsOpened = 0;
|
||||
|
||||
static void _clean_close_gs( void* obj )
|
||||
{
|
||||
if( g_plugins != NULL )
|
||||
int result = InterlockedExchange( &gsIsOpened, 0 );
|
||||
if( result && (g_plugins != NULL) )
|
||||
g_plugins->m_info[PluginId_GS].CommonBindings.Close();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::_RingbufferLoop()
|
||||
static void dummyIrqCallback()
|
||||
{
|
||||
// dummy, because MTGS doesn't need this mess!
|
||||
// (and zerogs does >_<)
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OpenPlugin()
|
||||
{
|
||||
if( gsIsOpened ) return;
|
||||
|
||||
memcpy_aligned( RingBuffer.Regs, PS2MEM_GS, sizeof(PS2MEM_GS) );
|
||||
GSsetBaseMem( RingBuffer.Regs );
|
||||
GSirqCallback( dummyIrqCallback );
|
||||
|
||||
if( renderswitch )
|
||||
Console::WriteLn( "\t\tForced software switch enabled." );
|
||||
|
||||
if( GSopen2 != NULL )
|
||||
m_returncode = GSopen2( (void*)&pDsp, 1 | (renderswitch ? 4 : 0) );
|
||||
else
|
||||
m_returncode = GSopen( (void*)&pDsp, "PCSX2", renderswitch ? 2 : 1 );
|
||||
|
||||
gsIsOpened = 1;
|
||||
m_sem_OpenDone.Post();
|
||||
|
||||
if( m_returncode != 0 )
|
||||
{
|
||||
DevCon::WriteLn( "MTGS: GSopen Finished, return code: 0x%x", m_returncode );
|
||||
pthread_exit( (void*)m_returncode );
|
||||
}
|
||||
|
||||
GSCSRr = 0x551B4000; // 0x55190000
|
||||
GSsetGameCRC( ElfCRC, 0 );
|
||||
}
|
||||
|
||||
sptr mtgsThreadObject::ExecuteTask()
|
||||
{
|
||||
SetName( "MTGS" );
|
||||
|
||||
#ifdef RINGBUF_DEBUG_STACK
|
||||
PacketTagType prevCmd;
|
||||
#endif
|
||||
|
||||
gsIsOpened = false;
|
||||
pthread_cleanup_push( _clean_close_gs, this );
|
||||
while( true )
|
||||
{
|
||||
m_sem_event.Wait();
|
||||
m_sem_event.Wait(); // ... because this does a cancel test itself..
|
||||
StateCheck( false ); // false disables cancel test here!
|
||||
|
||||
AtomicExchange( m_RingBufferIsBusy, 1 );
|
||||
m_RingBufferIsBusy = true;
|
||||
|
||||
// note: m_RingPos is intentionally not volatile, because it should only
|
||||
// ever be modified by this thread.
|
||||
while( m_RingPos != volatize(m_WritePos))
|
||||
{
|
||||
wxASSERT( m_RingPos < m_RingBufferSize );
|
||||
wxASSERT( m_RingPos < RingBufferSize );
|
||||
|
||||
const PacketTagType& tag = (PacketTagType&)m_RingBuffer[m_RingPos];
|
||||
const PacketTagType& tag = (PacketTagType&)RingBuffer[m_RingPos];
|
||||
u32 ringposinc = 1;
|
||||
|
||||
#ifdef RINGBUF_DEBUG_STACK
|
||||
|
@ -458,12 +291,14 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
// stall for a bit to let the MainThread have time to update the g_pGSWritePos.
|
||||
m_lock_RingRestart.Lock();
|
||||
m_lock_RingRestart.Unlock();
|
||||
|
||||
StateCheck( false ); // disable cancel since the above locks are cancelable already
|
||||
continue;
|
||||
|
||||
case GS_RINGTYPE_P1:
|
||||
{
|
||||
const int qsize = tag.data[0];
|
||||
const u128* data = m_RingBuffer.GetPtr( m_RingPos+1 );
|
||||
const u128* data = &RingBuffer[m_RingPos+1];
|
||||
|
||||
// make sure that tag>>16 is the MAX size readable
|
||||
GSgifTransfer1((u32*)(data - 0x400 + qsize), 0x4000-qsize*16);
|
||||
|
@ -475,7 +310,7 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
case GS_RINGTYPE_P2:
|
||||
{
|
||||
const int qsize = tag.data[0];
|
||||
const u128* data = m_RingBuffer.GetPtr( m_RingPos+1 );
|
||||
const u128* data = &RingBuffer[m_RingPos+1];
|
||||
GSgifTransfer2((u32*)data, qsize);
|
||||
ringposinc += qsize;
|
||||
}
|
||||
|
@ -484,7 +319,7 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
case GS_RINGTYPE_P3:
|
||||
{
|
||||
const int qsize = tag.data[0];
|
||||
const u128* data = m_RingBuffer.GetPtr( m_RingPos+1 );
|
||||
const u128* data = &RingBuffer[m_RingPos+1];
|
||||
GSgifTransfer3((u32*)data, qsize);
|
||||
ringposinc += qsize;
|
||||
}
|
||||
|
@ -514,16 +349,16 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
break;
|
||||
|
||||
case GS_RINGTYPE_MEMWRITE8:
|
||||
m_gsMem[tag.data[0]] = (u8)tag.data[1];
|
||||
RingBuffer.Regs[tag.data[0]] = (u8)tag.data[1];
|
||||
break;
|
||||
case GS_RINGTYPE_MEMWRITE16:
|
||||
*(u16*)(m_gsMem+tag.data[0]) = (u16)tag.data[1];
|
||||
*(u16*)(RingBuffer.Regs+tag.data[0]) = (u16)tag.data[1];
|
||||
break;
|
||||
case GS_RINGTYPE_MEMWRITE32:
|
||||
*(u32*)(m_gsMem+tag.data[0]) = tag.data[1];
|
||||
*(u32*)(RingBuffer.Regs+tag.data[0]) = tag.data[1];
|
||||
break;
|
||||
case GS_RINGTYPE_MEMWRITE64:
|
||||
*(u64*)(m_gsMem+tag.data[0]) = *(u64*)&tag.data[1];
|
||||
*(u64*)(RingBuffer.Regs+tag.data[0]) = *(u64*)&tag.data[1];
|
||||
break;
|
||||
|
||||
case GS_RINGTYPE_FREEZE:
|
||||
|
@ -563,15 +398,14 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
_gs_ChangeTimings( tag.data[0], tag.data[1] );
|
||||
break;
|
||||
|
||||
case GS_RINGTYPE_CRC:
|
||||
GSsetGameCRC( tag.data[0], 0 );
|
||||
break;
|
||||
|
||||
case GS_RINGTYPE_STARTTIME:
|
||||
m_iSlowStart += tag.data[0];
|
||||
break;
|
||||
|
||||
case GS_RINGTYPE_QUIT:
|
||||
// have to use some low level code, because all the standard Close api does is
|
||||
// trigger this very ringbuffer message!
|
||||
return;
|
||||
|
||||
#ifdef PCSX2_DEVBUILD
|
||||
default:
|
||||
Console::Error("GSThreadProc, bad packet (%x) at m_RingPos: %x, m_WritePos: %x", tag.command, m_RingPos, m_WritePos);
|
||||
|
@ -585,62 +419,39 @@ void mtgsThreadObject::_RingbufferLoop()
|
|||
}
|
||||
|
||||
uint newringpos = m_RingPos + ringposinc;
|
||||
wxASSERT( newringpos <= m_RingBufferSize );
|
||||
newringpos &= m_RingBufferMask;
|
||||
wxASSERT( newringpos <= RingBufferSize );
|
||||
newringpos &= RingBufferMask;
|
||||
AtomicExchange( m_RingPos, newringpos );
|
||||
}
|
||||
AtomicExchange( m_RingBufferIsBusy, 0 );
|
||||
m_RingBufferIsBusy = false;
|
||||
}
|
||||
pthread_cleanup_pop( true );
|
||||
}
|
||||
|
||||
static void dummyIrqCallback()
|
||||
{
|
||||
// dummy, because MTGS doesn't need this mess!
|
||||
// (and zerogs does >_<)
|
||||
}
|
||||
|
||||
sptr mtgsThreadObject::ExecuteTask()
|
||||
{
|
||||
SetName( "MTGS" );
|
||||
|
||||
memcpy_aligned( m_gsMem, PS2MEM_GS, sizeof(PS2MEM_GS) );
|
||||
GSsetBaseMem( m_gsMem );
|
||||
GSirqCallback( dummyIrqCallback );
|
||||
|
||||
if( renderswitch )
|
||||
Console::WriteLn( "\t\tForced software switch enabled." );
|
||||
|
||||
if( GSopen2 != NULL )
|
||||
m_returncode = GSopen2( (void*)&pDsp, 1 | (renderswitch ? 4 : 0) );
|
||||
else
|
||||
m_returncode = GSopen( (void*)&pDsp, "PCSX2", renderswitch ? 2 : 1 );
|
||||
|
||||
DevCon::WriteLn( "MTGS: GSopen Finished, return code: 0x%x", m_returncode );
|
||||
|
||||
GSCSRr = 0x551B4000; // 0x55190000
|
||||
m_sem_InitDone.Post();
|
||||
if (m_returncode != 0) { return m_returncode; } // error msg will be issued to the user by Plugins.c
|
||||
|
||||
#ifdef RINGBUF_DEBUG_STACK
|
||||
PacketTagType prevCmd;
|
||||
#endif
|
||||
|
||||
_RingbufferLoop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OnSuspendInThread()
|
||||
{
|
||||
_clean_close_gs( NULL );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::OnResumeInThread()
|
||||
{
|
||||
if( !m_LoadState )
|
||||
OpenPlugin();
|
||||
}
|
||||
|
||||
// Waits for the GS to empty out the entire ring buffer contents.
|
||||
// Used primarily for plugin startup/shutdown.
|
||||
void mtgsThreadObject::WaitGS()
|
||||
{
|
||||
// Freeze registers because some kernel code likes to destroy them
|
||||
DevAssert( !IsSelf(), "This method is only allowed from threads *not* named MTGS." );
|
||||
|
||||
if( IsSuspended() ) return;
|
||||
|
||||
// FIXME : Use semaphores instead of spinwaits.
|
||||
SetEvent();
|
||||
while( volatize(m_RingPos) != volatize(m_WritePos) )
|
||||
{
|
||||
Timeslice();
|
||||
//SpinWait();
|
||||
}
|
||||
while( volatize(m_RingPos) != volatize(m_WritePos) ) Timeslice();
|
||||
}
|
||||
|
||||
// Sets the gsEvent flag and releases a timeslice.
|
||||
|
@ -665,7 +476,7 @@ void mtgsThreadObject::PostEventWait() const
|
|||
|
||||
u8* mtgsThreadObject::GetDataPacketPtr() const
|
||||
{
|
||||
return (u8*)m_RingBuffer.GetPtr( m_packet_ringpos );
|
||||
return (u8*)&RingBuffer[m_packet_ringpos];
|
||||
}
|
||||
|
||||
// Closes the data packet send command, and initiates the gs thread (if needed).
|
||||
|
@ -675,12 +486,12 @@ void mtgsThreadObject::SendDataPacket()
|
|||
jASSUME( m_packet_size != 0 );
|
||||
|
||||
uint temp = m_packet_ringpos + m_packet_size;
|
||||
jASSUME( temp <= m_RingBufferSize );
|
||||
temp &= m_RingBufferMask;
|
||||
jASSUME( temp <= RingBufferSize );
|
||||
temp &= RingBufferMask;
|
||||
|
||||
if( IsDebugBuild )
|
||||
{
|
||||
if( m_packet_ringpos + m_packet_size < m_RingBufferSize )
|
||||
if( m_packet_ringpos + m_packet_size < RingBufferSize )
|
||||
{
|
||||
uint readpos = volatize(m_RingPos);
|
||||
if( readpos != m_WritePos )
|
||||
|
@ -742,10 +553,6 @@ static u32 ringtx_inf[32][32];
|
|||
static u32 ringtx_inf_s[32];
|
||||
#endif
|
||||
|
||||
#ifdef PCSX2_GSRING_SAMPLING_STATS
|
||||
static u32 GSRingBufCopySz = 0;
|
||||
#endif
|
||||
|
||||
// returns the amount of giftag data not processed (in simd128 values).
|
||||
// Return value is used by VU1 XGKICK to hack-fix data packets which are too
|
||||
// large for VU1 memory.
|
||||
|
@ -810,13 +617,13 @@ int mtgsThreadObject::PrepDataPacket( GIF_PATH pathidx, const u8* srcdata, u32 s
|
|||
jASSUME( m_packet_size == 0 );
|
||||
|
||||
// Sanity checks! (within the confines of our ringbuffer please!)
|
||||
jASSUME( size < m_RingBufferSize );
|
||||
jASSUME( writepos < m_RingBufferSize );
|
||||
jASSUME( size < RingBufferSize );
|
||||
jASSUME( writepos < RingBufferSize );
|
||||
|
||||
m_packet_size = gifTransferDummy(pathidx, srcdata, size);
|
||||
m_packet_size = GIFPath_ParseTag(pathidx, srcdata, size);
|
||||
size = m_packet_size + 1; // takes into account our command qword.
|
||||
|
||||
if( writepos + size < m_RingBufferSize )
|
||||
if( writepos + size < RingBufferSize )
|
||||
{
|
||||
// generic gs wait/stall.
|
||||
// if the writepos is past the readpos then we're safe.
|
||||
|
@ -840,7 +647,7 @@ int mtgsThreadObject::PrepDataPacket( GIF_PATH pathidx, const u8* srcdata, u32 s
|
|||
PostEventWait();
|
||||
}
|
||||
}
|
||||
else if( writepos + size > m_RingBufferSize )
|
||||
else if( writepos + size > RingBufferSize )
|
||||
{
|
||||
// If the incoming packet doesn't fit, then start over from
|
||||
// the start of the ring buffer (it's a lot easier than trying
|
||||
|
@ -918,7 +725,7 @@ int mtgsThreadObject::PrepDataPacket( GIF_PATH pathidx, const u8* srcdata, u32 s
|
|||
// Command qword: Low word is the command, and the high word is the packet
|
||||
// length in SIMDs (128 bits).
|
||||
|
||||
PacketTagType& tag = (PacketTagType&)m_RingBuffer[m_WritePos];
|
||||
PacketTagType& tag = (PacketTagType&)RingBuffer[m_WritePos];
|
||||
tag.command = pathidx+1;
|
||||
tag.data[0] = m_packet_size;
|
||||
m_packet_ringpos = m_WritePos + 1;
|
||||
|
@ -935,9 +742,9 @@ __forceinline uint mtgsThreadObject::_PrepForSimplePacket()
|
|||
#endif
|
||||
|
||||
uint future_writepos = m_WritePos+1;
|
||||
jASSUME( future_writepos <= m_RingBufferSize );
|
||||
jASSUME( future_writepos <= RingBufferSize );
|
||||
|
||||
future_writepos &= m_RingBufferMask;
|
||||
future_writepos &= RingBufferMask;
|
||||
|
||||
if( future_writepos == volatize(m_RingPos) )
|
||||
{
|
||||
|
@ -963,7 +770,7 @@ void mtgsThreadObject::SendSimplePacket( GS_RINGTYPE type, int data0, int data1,
|
|||
//ScopedLock locker( m_PacketLocker );
|
||||
|
||||
const uint thefuture = _PrepForSimplePacket();
|
||||
PacketTagType& tag = (PacketTagType&)m_RingBuffer[m_WritePos];
|
||||
PacketTagType& tag = (PacketTagType&)RingBuffer[m_WritePos];
|
||||
|
||||
tag.command = type;
|
||||
tag.data[0] = data0;
|
||||
|
@ -978,7 +785,7 @@ void mtgsThreadObject::SendPointerPacket( GS_RINGTYPE type, u32 data0, void* dat
|
|||
//ScopedLock locker( m_PacketLocker );
|
||||
|
||||
const uint thefuture = _PrepForSimplePacket();
|
||||
PacketTagType& tag = (PacketTagType&)m_RingBuffer[m_WritePos];
|
||||
PacketTagType& tag = (PacketTagType&)RingBuffer[m_WritePos];
|
||||
|
||||
tag.command = type;
|
||||
tag.data[0] = data0;
|
||||
|
@ -987,60 +794,39 @@ void mtgsThreadObject::SendPointerPacket( GS_RINGTYPE type, u32 data0, void* dat
|
|||
_FinishSimplePacket( thefuture );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::SendGameCRC( u32 crc )
|
||||
{
|
||||
SendSimplePacket( GS_RINGTYPE_CRC, crc, 0, 0 );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::WaitForOpen()
|
||||
{
|
||||
if( !gsIsOpened )
|
||||
m_sem_OpenDone.WaitGui();
|
||||
m_sem_OpenDone.Reset();
|
||||
}
|
||||
|
||||
void mtgsThreadObject::Freeze( int mode, MTGS_FreezeData& data )
|
||||
{
|
||||
if( mode == FREEZE_LOAD )
|
||||
{
|
||||
AtomicExchange( m_RingPos, m_WritePos );
|
||||
SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data );
|
||||
m_LoadState = true;
|
||||
SetEvent();
|
||||
Resume();
|
||||
}
|
||||
else
|
||||
SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &data );
|
||||
|
||||
mtgsWaitGS();
|
||||
m_LoadState = false;
|
||||
}
|
||||
|
||||
// Waits for the GS to empty out the entire ring buffer contents.
|
||||
// Used primarily for plugin startup/shutdown.
|
||||
void mtgsWaitGS()
|
||||
{
|
||||
if( mtgsThread == NULL ) return;
|
||||
mtgsThread->WaitGS();
|
||||
mtgsThread.WaitGS();
|
||||
}
|
||||
|
||||
// Exceptions:
|
||||
// ThreadCreationError - Thready could not be created (indicates OS resource limitations)
|
||||
// PluginFailure - GS plugin's "GSopen" call failed.
|
||||
//
|
||||
void mtgsOpen()
|
||||
{
|
||||
// better not be a thread already running, yo!
|
||||
if( mtgsThread != NULL ) return;
|
||||
|
||||
mtgsThread = new mtgsThreadObject();
|
||||
|
||||
try
|
||||
{
|
||||
mtgsThread->Start();
|
||||
}
|
||||
catch( ... )
|
||||
{
|
||||
// if the thread start fails for any reason then set the handle to null.
|
||||
// The handle is used as a NULL test of thread running status, which is why
|
||||
// we really need to do this. :)
|
||||
safe_delete( mtgsThread );
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void mtgsThreadObject::GIFSoftReset( int mask )
|
||||
{
|
||||
if(mask & 1) memzero_obj(s_path[0]);
|
||||
if(mask & 2) memzero_obj(s_path[1]);
|
||||
if(mask & 4) memzero_obj(s_path[2]);
|
||||
|
||||
if( GSgifSoftReset == NULL ) return;
|
||||
|
||||
MTGS_LOG( "MTGS: Sending GIF Soft Reset (mask: %d)", mask );
|
||||
mtgsThread->SendSimplePacket( GS_RINGTYPE_SOFTRESET, mask, 0, 0 );
|
||||
}
|
||||
|
||||
void mtgsThreadObject::Freeze( SaveStateBase& state )
|
||||
{
|
||||
_mtgsFreezeGIF( state, s_path );
|
||||
}
|
||||
|
||||
// this function is needed because of recompiled calls from iGS.cpp
|
||||
// (currently used in GCC only)
|
||||
//void mtgsRingBufSimplePacket( s32 command, u32 data0, u32 data1, u32 data2 )
|
||||
//{
|
||||
// mtgsThread->SendSimplePacket( (GS_RINGTYPE)command, data0, data1, data2 );
|
||||
//}
|
||||
|
|
|
@ -38,9 +38,6 @@ extern u8 *psS; //0.015 mb, scratch pad
|
|||
#define PS2MEM_EROM psER
|
||||
#define PS2MEM_SCRATCH psS
|
||||
|
||||
#define PS2MEM_GS g_RealGSMem
|
||||
#define PS2GS_BASE(mem) (g_RealGSMem+(mem&0x13ff))
|
||||
|
||||
// Various useful locations
|
||||
#define spr0 ((DMACh*)&PS2MEM_HW[0xD000])
|
||||
#define spr1 ((DMACh*)&PS2MEM_HW[0xD400])
|
||||
|
@ -57,11 +54,6 @@ extern u8 *psS; //0.015 mb, scratch pad
|
|||
#define ipu0dma ((DMACh *)&PS2MEM_HW[0xb000])
|
||||
#define ipu1dma ((DMACh *)&PS2MEM_HW[0xb400])
|
||||
|
||||
// From Gif.h
|
||||
#define GSCSRr *((u64*)(g_RealGSMem+0x1000))
|
||||
#define GSIMR *((u32*)(g_RealGSMem+0x1010))
|
||||
#define GSSIGLBLID ((GSRegSIGBLID*)(g_RealGSMem+0x1080))
|
||||
|
||||
#define PSM(mem) (vtlb_GetPhyPtr((mem)&0x1fffffff)) //pcsx2 is a competition.The one with most hacks wins :D
|
||||
|
||||
#define psHs8(mem) (*(s8 *)&PS2MEM_HW[(mem) & 0xffff])
|
||||
|
|
|
@ -828,16 +828,14 @@ extern void spu2Irq();
|
|||
|
||||
static bool OpenPlugin_CDVD()
|
||||
{
|
||||
if( CDVDapi_Plugin.open( NULL ) ) return false;
|
||||
if( CDVDapi_Plugin.open(NULL) ) return false;
|
||||
CDVDapi_Plugin.newDiskCB( cdvdNewDiskCB );
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool OpenPlugin_GS()
|
||||
{
|
||||
if( mtgsThread != NULL ) return true;
|
||||
mtgsOpen(); // mtgsOpen raises its own exception on error
|
||||
GSsetGameCRC( ElfCRC, 0 );
|
||||
mtgsThread.Resume();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -848,7 +846,7 @@ static bool OpenPlugin_PAD()
|
|||
|
||||
static bool OpenPlugin_SPU2()
|
||||
{
|
||||
if( SPU2open( (void*)&pDsp ) ) return false;
|
||||
if( SPU2open((void*)&pDsp) ) return false;
|
||||
|
||||
SPU2irqCallback( spu2Irq, spu2DMA4Irq, spu2DMA7Irq );
|
||||
if( SPU2setDMABaseAddr != NULL ) SPU2setDMABaseAddr((uptr)psxM);
|
||||
|
@ -870,7 +868,7 @@ static bool OpenPlugin_USB()
|
|||
{
|
||||
usbHandler = NULL;
|
||||
|
||||
if( USBopen( (void*)&pDsp ) ) return false;
|
||||
if( USBopen((void*)&pDsp) ) return false;
|
||||
USBirqCallback( usbIrq );
|
||||
usbHandler = USBirqHandler();
|
||||
if( USBsetRAM != NULL )
|
||||
|
@ -880,7 +878,7 @@ static bool OpenPlugin_USB()
|
|||
|
||||
static bool OpenPlugin_FW()
|
||||
{
|
||||
if( FWopen( (void*)&pDsp ) ) return false;
|
||||
if( FWopen((void*)&pDsp) ) return false;
|
||||
FWirqCallback( fwIrq );
|
||||
return true;
|
||||
}
|
||||
|
@ -916,9 +914,12 @@ void PluginManager::Open()
|
|||
Console::Status( "Opening plugins..." );
|
||||
|
||||
const PluginInfo* pi = tbl_PluginInfo; do {
|
||||
g_plugins->Open( pi->id );
|
||||
Open( pi->id );
|
||||
} while( ++pi, pi->shortname != NULL );
|
||||
|
||||
mtgsThread.WaitForOpen();
|
||||
mtgsThread.PollStatus();
|
||||
|
||||
Console::Status( "Plugins opened successfully." );
|
||||
}
|
||||
|
||||
|
@ -929,11 +930,9 @@ void PluginManager::Close( PluginsEnum_t pid )
|
|||
|
||||
if( pid == PluginId_GS )
|
||||
{
|
||||
if( mtgsThread == NULL ) return;
|
||||
|
||||
// force-close PAD before GS, because the PAD depends on the GS window.
|
||||
Close( PluginId_PAD );
|
||||
safe_delete( mtgsThread );
|
||||
mtgsThread.Suspend();
|
||||
}
|
||||
else if( pid == PluginId_CDVD )
|
||||
DoCDVDclose();
|
||||
|
@ -1007,6 +1006,8 @@ void PluginManager::Init()
|
|||
//
|
||||
void PluginManager::Shutdown()
|
||||
{
|
||||
mtgsThread.Cancel(); // speedier shutdown!
|
||||
|
||||
Close();
|
||||
DbgCon::Status( "Shutting down plugins..." );
|
||||
|
||||
|
@ -1039,10 +1040,10 @@ bool PluginManager::DoFreeze( PluginsEnum_t pid, int mode, freezeData* data )
|
|||
{
|
||||
if( (pid == PluginId_GS) && wxThread::IsMain() )
|
||||
{
|
||||
MTGS_FreezeData woot = { data, 0 };
|
||||
// GS needs some thread safety love...
|
||||
mtgsThread->SendPointerPacket( GS_RINGTYPE_FREEZE, mode, &woot );
|
||||
mtgsWaitGS();
|
||||
|
||||
MTGS_FreezeData woot = { data, 0 };
|
||||
mtgsThread.Freeze( mode, woot );
|
||||
return woot.retval != -1;
|
||||
}
|
||||
else
|
||||
|
|
|
@ -18,6 +18,9 @@
|
|||
|
||||
#include "App.h"
|
||||
#include "HostGui.h"
|
||||
#include "zlib/zlib.h"
|
||||
|
||||
using namespace Threading;
|
||||
|
||||
static wxScopedPtr< SafeArray<u8> > g_RecoveryState;
|
||||
|
||||
|
@ -39,10 +42,67 @@ namespace StateRecovery {
|
|||
SysClearExecutionCache();
|
||||
}
|
||||
|
||||
SafeArray<u8> gzSavingBuffer;
|
||||
|
||||
class gzThreadClass : public PersistentThread
|
||||
{
|
||||
typedef PersistentThread _parent;
|
||||
|
||||
protected:
|
||||
gzFile m_file;
|
||||
|
||||
public:
|
||||
gzThreadClass( const wxString& file ) :
|
||||
m_file( gzopen( file.ToUTF8().data(), "wb" ) )
|
||||
{
|
||||
if( m_file == NULL )
|
||||
throw Exception::CreateStream( file, "Cannot create savestate file for writing." );
|
||||
|
||||
Start();
|
||||
}
|
||||
|
||||
virtual void DoThreadCleanup()
|
||||
{
|
||||
gzSavingBuffer.Dispose();
|
||||
if( m_file != NULL )
|
||||
{
|
||||
gzclose( m_file );
|
||||
m_file = NULL;
|
||||
}
|
||||
|
||||
_parent::DoThreadCleanup();
|
||||
}
|
||||
|
||||
virtual ~gzThreadClass() throw()
|
||||
{
|
||||
// fixme: something a little more graceful than Block, perhaps?
|
||||
Block();
|
||||
}
|
||||
|
||||
protected:
|
||||
int ExecuteTask()
|
||||
{
|
||||
if( (m_file == NULL) || (gzSavingBuffer.GetSizeInBytes() == 0) ) return 0 ;
|
||||
SetName( "Savestate::gzipper" );
|
||||
gzwrite( m_file, gzSavingBuffer.GetPtr(), gzSavingBuffer.GetSizeInBytes() );
|
||||
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
wxScopedPtr<gzThreadClass> gzThread;
|
||||
|
||||
void SaveToFile( const wxString& file )
|
||||
{
|
||||
SafeArray<u8> buf;
|
||||
memSavingState( buf ).FreezeAll();
|
||||
SysSuspend( false );
|
||||
gzThread.reset( NULL ); // blocks on any existing gzipping business.
|
||||
|
||||
memSavingState( gzSavingBuffer ).FreezeAll();
|
||||
|
||||
// start that encoding thread:
|
||||
gzThread.reset( new gzThreadClass( file ) );
|
||||
|
||||
SysResume();
|
||||
}
|
||||
|
||||
// Saves recovery state info to the given saveslot, or saves the active emulation state
|
||||
|
|
|
@ -112,7 +112,7 @@ void SaveStateBase::FreezeBios()
|
|||
|
||||
if( !m_DidBios )
|
||||
{
|
||||
if( memcmp( descin, descout, 128 ) != 0 )
|
||||
if( memcmp( descin, descout.ToAscii().data(), 128 ) != 0 )
|
||||
{
|
||||
Console::Error(
|
||||
"\n\tWarning: BIOS Version Mismatch, savestate may be unstable!\n"
|
||||
|
@ -144,8 +144,8 @@ void SaveStateBase::FreezeMainMemory()
|
|||
|
||||
void SaveStateBase::FreezeRegisters()
|
||||
{
|
||||
if( IsLoading() )
|
||||
PreLoadPrep();
|
||||
//if( IsLoading() )
|
||||
// PreLoadPrep();
|
||||
|
||||
// Second Block - Various CPU Registers and States
|
||||
// -----------------------------------------------
|
||||
|
@ -206,7 +206,7 @@ bool SaveStateBase::FreezeSection()
|
|||
FreezeTag( "BiosVersion" );
|
||||
Freeze( sectlen );
|
||||
|
||||
if( sectlen != MainMemorySizeInBytes )
|
||||
if( sectlen != 128 )
|
||||
{
|
||||
throw Exception::BadSavedState( wxEmptyString,
|
||||
L"Invalid size encountered on BiosVersion section.",
|
||||
|
@ -223,6 +223,7 @@ bool SaveStateBase::FreezeSection()
|
|||
{
|
||||
FreezeTag( "MainMemory" );
|
||||
|
||||
int seekpos = m_idx+4;
|
||||
int sectlen = MainMemorySizeInBytes;
|
||||
Freeze( sectlen );
|
||||
if( sectlen != MainMemorySizeInBytes )
|
||||
|
@ -234,6 +235,8 @@ bool SaveStateBase::FreezeSection()
|
|||
}
|
||||
|
||||
FreezeMainMemory();
|
||||
int realsectsize = m_idx - seekpos;
|
||||
wxASSERT( sectlen == realsectsize );
|
||||
m_sectid++;
|
||||
}
|
||||
break;
|
||||
|
@ -241,7 +244,7 @@ bool SaveStateBase::FreezeSection()
|
|||
case FreezeId_Registers:
|
||||
{
|
||||
FreezeTag( "HardwareRegisters" );
|
||||
int seekpos = m_idx;
|
||||
int seekpos = m_idx+4;
|
||||
int sectsize;
|
||||
Freeze( sectsize );
|
||||
|
||||
|
@ -263,13 +266,14 @@ bool SaveStateBase::FreezeSection()
|
|||
);
|
||||
}
|
||||
}
|
||||
m_sectid++;
|
||||
}
|
||||
break;
|
||||
|
||||
case FreezeId_Plugin:
|
||||
{
|
||||
FreezeTag( "Plugin" );
|
||||
int seekpos = m_idx;
|
||||
int seekpos = m_idx+4;
|
||||
int sectsize;
|
||||
Freeze( sectsize );
|
||||
|
||||
|
@ -296,17 +300,15 @@ bool SaveStateBase::FreezeSection()
|
|||
|
||||
// following increments only affect Saving mode, are ignored by Loading mode.
|
||||
m_pid++;
|
||||
if( m_pid > PluginId_Count )
|
||||
m_sectid++;
|
||||
if( m_pid >= PluginId_Count )
|
||||
m_sectid = FreezeId_End;
|
||||
}
|
||||
break;
|
||||
|
||||
case FreezeId_Unknown:
|
||||
default:
|
||||
if( IsSaving() )
|
||||
m_sectid = FreezeId_End;
|
||||
else
|
||||
{
|
||||
wxASSERT( IsSaving() );
|
||||
|
||||
// Skip unknown sections with a warning log.
|
||||
// Maybe it'll work! (haha?)
|
||||
|
||||
|
@ -320,10 +322,11 @@ bool SaveStateBase::FreezeSection()
|
|||
"\tTagname: %s, Size: %d", m_tagspace, size
|
||||
);
|
||||
m_idx += size;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
wxSafeYield( NULL );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -332,7 +335,7 @@ void SaveStateBase::FreezeAll()
|
|||
m_sectid = (int)FreezeId_End+1;
|
||||
m_pid = PluginId_GS;
|
||||
|
||||
while( FreezeSection() ) ;
|
||||
while( FreezeSection() );
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -36,13 +36,13 @@ enum FreezeSectionId
|
|||
{
|
||||
FreezeId_End,
|
||||
|
||||
FreezeId_Memory,
|
||||
FreezeId_Registers,
|
||||
|
||||
// A BIOS tag should always be saved in conjunction with Memory or Registers tags,
|
||||
// but can be skipped if the savestate has only plugins.
|
||||
FreezeId_Bios,
|
||||
|
||||
FreezeId_Memory,
|
||||
FreezeId_Registers,
|
||||
|
||||
FreezeId_Plugin,
|
||||
|
||||
// anything here and beyond we can skip, with a warning
|
||||
|
@ -159,9 +159,7 @@ protected:
|
|||
void psxRcntFreeze();
|
||||
void sio2Freeze();
|
||||
|
||||
// called by gsFreeze automatically.
|
||||
void mtgsFreeze();
|
||||
|
||||
void gifPathFreeze(); // called by gsFreeze
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
|
|
@ -263,6 +263,20 @@ void SysClearExecutionCache()
|
|||
void SysLoadState( const wxString& srcfile )
|
||||
{
|
||||
SafeArray<u8> buf;
|
||||
gzFile gzfp = gzopen( srcfile.ToUTF8().data(), "rb" );
|
||||
if( gzfp == NULL )
|
||||
throw Exception::BadSavedState( srcfile, "File not found, or permission denied!" );
|
||||
|
||||
int curidx = 0;
|
||||
do
|
||||
{
|
||||
buf.MakeRoomFor( curidx+327680 );
|
||||
gzread( gzfp, buf.GetPtr(curidx), 327680 );
|
||||
curidx += 327680;
|
||||
} while( !gzeof(gzfp) );
|
||||
|
||||
gzclose( gzfp );
|
||||
|
||||
memLoadingState joe( buf ); // this could throw n StateLoadError.
|
||||
|
||||
// we perform a full backup to memory first so that we can restore later if the
|
||||
|
@ -272,7 +286,8 @@ void SysLoadState( const wxString& srcfile )
|
|||
|
||||
SysClearExecutionCache();
|
||||
cpuReset();
|
||||
joe.FreezeAll();
|
||||
//joe.FreezeAll();
|
||||
StateRecovery::Recover();
|
||||
}
|
||||
|
||||
// Maps a block of memory for use as a recompiled code buffer, and ensures that the
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
#include "PrecompiledHeader.h"
|
||||
#include "Common.h"
|
||||
#include "System.h"
|
||||
#include "SysThreads.h"
|
||||
#include "SaveState.h"
|
||||
#include "Elfheader.h"
|
||||
#include "Plugins.h"
|
||||
#include "CoreEmuThread.h"
|
||||
|
||||
#include "R5900.h"
|
||||
#include "R3000A.h"
|
||||
|
@ -27,6 +27,248 @@
|
|||
|
||||
static __threadlocal SysCoreThread* tls_coreThread = NULL;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// SysSuspendableThread *External Thread* Implementations
|
||||
// (Called form outside the context of this thread)
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
SysSuspendableThread::SysSuspendableThread() :
|
||||
m_ExecMode( ExecMode_NoThreadYet )
|
||||
, m_lock_ExecMode()
|
||||
, m_ResumeEvent()
|
||||
, m_SuspendEvent()
|
||||
, m_ResumeProtection( false )
|
||||
{
|
||||
}
|
||||
|
||||
SysSuspendableThread::~SysSuspendableThread() throw()
|
||||
{
|
||||
}
|
||||
|
||||
void SysSuspendableThread::Start()
|
||||
{
|
||||
if( !DevAssert( m_ExecMode == ExecMode_NoThreadYet, "SysSustainableThread:Start(): Invalid execution mode" ) ) return;
|
||||
|
||||
m_ResumeEvent.Reset();
|
||||
m_SuspendEvent.Reset();
|
||||
|
||||
_parent::Start();
|
||||
}
|
||||
|
||||
|
||||
// Pauses the emulation state at the next PS2 vsync, and returns control to the calling
|
||||
// thread; or does nothing if the core is already suspended. Calling this thread from the
|
||||
// Core thread will result in deadlock.
|
||||
//
|
||||
// Parameters:
|
||||
// isNonblocking - if set to true then the function will not block for emulation suspension.
|
||||
// Defaults to false if parameter is not specified. Performing non-blocking suspension
|
||||
// is mostly useful for starting certain non-Emu related gui activities (improves gui
|
||||
// responsiveness).
|
||||
//
|
||||
void SysSuspendableThread::Suspend( bool isBlocking )
|
||||
{
|
||||
if( IsSelf() || !IsRunning() ) return;
|
||||
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
if( m_ExecMode == ExecMode_Suspended )
|
||||
return;
|
||||
|
||||
if( m_ExecMode == ExecMode_Running )
|
||||
m_ExecMode = ExecMode_Suspending;
|
||||
|
||||
DevAssert( m_ExecMode == ExecMode_Suspending, "ExecMode should be nothing other than Suspended..." );
|
||||
}
|
||||
m_sem_event.Post();
|
||||
m_SuspendEvent.WaitGui();
|
||||
}
|
||||
|
||||
// Resumes the core execution state, or does nothing is the core is already running. If
|
||||
// settings were changed, resets will be performed as needed and emulation state resumed from
|
||||
// memory savestates.
|
||||
//
|
||||
// Exceptions (can occur on first call only):
|
||||
// PluginInitError - thrown if a plugin fails init (init is performed on the current thread
|
||||
// on the first time the thread is resumed from it's initial idle state)
|
||||
// ThreadCreationError - Insufficient system resources to create thread.
|
||||
//
|
||||
void SysSuspendableThread::Resume()
|
||||
{
|
||||
if( IsSelf() ) return;
|
||||
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
switch( m_ExecMode )
|
||||
{
|
||||
case ExecMode_Running: return;
|
||||
|
||||
case ExecMode_NoThreadYet:
|
||||
Start();
|
||||
m_ExecMode = ExecMode_Suspending;
|
||||
// fall through...
|
||||
|
||||
case ExecMode_Suspending:
|
||||
// we need to make sure and wait for the emuThread to enter a fully suspended
|
||||
// state before continuing...
|
||||
|
||||
locker.Unlock(); // no deadlocks please, thanks. :)
|
||||
m_SuspendEvent.WaitGui();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
DevAssert( m_ExecMode == ExecMode_Suspended,
|
||||
"SysSuspendableThread is not in a suspended/idle state? wtf!" );
|
||||
|
||||
m_ExecMode = ExecMode_Running;
|
||||
m_ResumeProtection = true;
|
||||
OnResumeReady();
|
||||
m_ResumeProtection = false;
|
||||
m_ResumeEvent.Post();
|
||||
}
|
||||
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// SysSuspendableThread *Worker* Implementations
|
||||
// (Called from the context of this thread only)
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
void SysSuspendableThread::DoThreadCleanup()
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
m_ExecMode = ExecMode_NoThreadYet;
|
||||
_parent::DoThreadCleanup();
|
||||
}
|
||||
|
||||
void SysSuspendableThread::StateCheck( bool isCancelable )
|
||||
{
|
||||
// Shortcut for the common case, to avoid unnecessary Mutex locks:
|
||||
if( m_ExecMode == ExecMode_Running )
|
||||
{
|
||||
if( isCancelable ) pthread_testcancel();
|
||||
return;
|
||||
}
|
||||
|
||||
// Oh, seems we need a full lock, because something special is happening!
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
switch( m_ExecMode )
|
||||
{
|
||||
|
||||
#ifdef PCSX2_DEVBUILD // optimize out handlers for these cases in release builds.
|
||||
case ExecMode_NoThreadYet:
|
||||
// threads should never have this state set while the thread is in any way
|
||||
// active or alive. (for obvious reasons!!)
|
||||
DevAssert( false, "Invalid execution state detected." );
|
||||
break;
|
||||
#endif
|
||||
|
||||
case ExecMode_Running:
|
||||
// Yup, need this a second time. Variable state could have changed while we
|
||||
// were trying to acquire the lock above.
|
||||
if( isCancelable )
|
||||
pthread_testcancel();
|
||||
break;
|
||||
|
||||
case ExecMode_Suspending:
|
||||
{
|
||||
OnSuspendInThread();
|
||||
m_ExecMode = ExecMode_Suspended;
|
||||
m_SuspendEvent.Post();
|
||||
}
|
||||
// fall through...
|
||||
|
||||
case ExecMode_Suspended:
|
||||
m_lock_ExecMode.Unlock();
|
||||
while( m_ExecMode == ExecMode_Suspended )
|
||||
m_ResumeEvent.WaitGui();
|
||||
|
||||
OnResumeInThread();
|
||||
break;
|
||||
|
||||
jNO_DEFAULT;
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// EECoreThread *External Thread* Implementations
|
||||
// (Called form outside the context of this thread)
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
SysCoreThread::SysCoreThread( PluginManager& plugins ) :
|
||||
m_resetRecompilers( false )
|
||||
, m_resetProfilers( false )
|
||||
, m_shortSuspend( false )
|
||||
, m_plugins( plugins )
|
||||
{
|
||||
}
|
||||
|
||||
SysCoreThread::~SysCoreThread() throw()
|
||||
{
|
||||
_parent::Cancel();
|
||||
}
|
||||
|
||||
void SysCoreThread::Start()
|
||||
{
|
||||
m_plugins.Init();
|
||||
_parent::Start();
|
||||
}
|
||||
|
||||
// Suspends the system without closing plugins or updating GUI status.
|
||||
// Should be used for savestates or other actions which happen very quickly.
|
||||
void SysCoreThread::ShortSuspend()
|
||||
{
|
||||
m_shortSuspend = true;
|
||||
Suspend();
|
||||
m_shortSuspend = false;
|
||||
}
|
||||
|
||||
|
||||
// Resumes the core execution state, or does nothing is the core is already running. If
|
||||
// settings were changed, resets will be performed as needed and emulation state resumed from
|
||||
// memory savestates.
|
||||
//
|
||||
// Exceptions (can occur on first call only):
|
||||
// PluginInitError - thrown if a plugin fails init (init is performed on the current thread
|
||||
// on the first time the thread is resumed from it's initial idle state)
|
||||
// ThreadCreationError - Insufficient system resources to create thread.
|
||||
//
|
||||
void SysCoreThread::OnResumeReady()
|
||||
{
|
||||
if( m_shortSuspend ) return;
|
||||
|
||||
if( m_resetRecompilers || m_resetProfilers )
|
||||
{
|
||||
SysClearExecutionCache();
|
||||
m_resetRecompilers = false;
|
||||
m_resetProfilers = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Applies a full suite of new settings, which will automatically facilitate the necessary
|
||||
// resets of the core and components (including plugins, if needed). The scope of resetting
|
||||
// is determined by comparing the current settings against the new settings.
|
||||
void SysCoreThread::ApplySettings( const Pcsx2Config& src )
|
||||
{
|
||||
if( src == EmuConfig ) return;
|
||||
|
||||
const bool resumeWhenDone = !m_ResumeProtection && !IsSuspended();
|
||||
if( !m_ResumeProtection ) Suspend();
|
||||
|
||||
m_resetRecompilers = ( src.Cpu != EmuConfig.Cpu ) || ( src.Gamefixes != EmuConfig.Gamefixes ) || ( src.Speedhacks != EmuConfig.Speedhacks );
|
||||
m_resetProfilers = (src.Profiler != EmuConfig.Profiler );
|
||||
const_cast<Pcsx2Config&>(EmuConfig) = src;
|
||||
|
||||
if( resumeWhenDone ) Resume();
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// EECoreThread *Worker* Implementations
|
||||
// (Called from the context of this thread only)
|
||||
// --------------------------------------------------------------------------------------
|
||||
SysCoreThread& SysCoreThread::Get()
|
||||
{
|
||||
wxASSERT_MSG( tls_coreThread != NULL, L"This function must be called from the context of a running SysCoreThread." );
|
||||
|
@ -35,10 +277,8 @@ SysCoreThread& SysCoreThread::Get()
|
|||
|
||||
void SysCoreThread::CpuInitializeMess()
|
||||
{
|
||||
m_plugins.Open();
|
||||
cpuReset();
|
||||
SysClearExecutionCache();
|
||||
m_plugins.Open();
|
||||
|
||||
if( StateRecovery::HasState() )
|
||||
{
|
||||
|
@ -86,8 +326,6 @@ void SysCoreThread::CpuInitializeMess()
|
|||
loadElfFile( elf_file );
|
||||
}
|
||||
}
|
||||
|
||||
GSsetGameCRC( ElfCRC, 0 );
|
||||
}
|
||||
|
||||
// special macro which disables inlining on functions that require their own function stackframe.
|
||||
|
@ -119,11 +357,7 @@ sptr SysCoreThread::ExecuteTask()
|
|||
SetName( "EE Core" );
|
||||
tls_coreThread = this;
|
||||
|
||||
while( m_ExecMode != ExecMode_Running )
|
||||
{
|
||||
m_ResumeEvent.WaitGui();
|
||||
}
|
||||
|
||||
StateCheck();
|
||||
CpuInitializeMess();
|
||||
StateCheck();
|
||||
CpuExecute();
|
||||
|
@ -131,170 +365,22 @@ sptr SysCoreThread::ExecuteTask()
|
|||
return 0;
|
||||
}
|
||||
|
||||
void SysCoreThread::StateCheck()
|
||||
void SysCoreThread::OnSuspendInThread()
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
switch( m_ExecMode )
|
||||
{
|
||||
case ExecMode_NoThreadYet:
|
||||
case ExecMode_Idle:
|
||||
// threads should never have an idle execution state set while the
|
||||
// thread is in any way active or alive.
|
||||
DevAssert( false, "Invalid execution state detected." );
|
||||
break;
|
||||
|
||||
case ExecMode_Running:
|
||||
pthread_testcancel();
|
||||
break;
|
||||
|
||||
case ExecMode_Suspending:
|
||||
{
|
||||
if( !m_shortSuspend )
|
||||
m_plugins.Close();
|
||||
m_ExecMode = ExecMode_Suspended;
|
||||
m_SuspendEvent.Post();
|
||||
}
|
||||
// fall through...
|
||||
|
||||
case ExecMode_Suspended:
|
||||
m_lock_ExecMode.Unlock();
|
||||
while( m_ExecMode == ExecMode_Suspended )
|
||||
m_ResumeEvent.WaitGui();
|
||||
|
||||
m_plugins.Open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SysCoreThread::SysCoreThread( PluginManager& plugins ) :
|
||||
m_ExecMode( ExecMode_NoThreadYet )
|
||||
, m_lock_ExecMode()
|
||||
|
||||
, m_resetRecompilers( false )
|
||||
, m_resetProfilers( false )
|
||||
|
||||
, m_plugins( plugins )
|
||||
, m_ResumeEvent()
|
||||
, m_SuspendEvent()
|
||||
|
||||
void SysCoreThread::OnResumeInThread()
|
||||
{
|
||||
m_plugins.Open();
|
||||
}
|
||||
|
||||
|
||||
// Invoked by the pthread_exit or pthread_cancel
|
||||
void SysCoreThread::DoThreadCleanup()
|
||||
{
|
||||
m_plugins.Shutdown();
|
||||
PersistentThread::DoThreadCleanup();
|
||||
}
|
||||
|
||||
SysCoreThread::~SysCoreThread() throw()
|
||||
{
|
||||
PersistentThread::Cancel();
|
||||
}
|
||||
|
||||
// Resumes the core execution state, or does nothing is the core is already running. If
|
||||
// settings were changed, resets will be performed as needed and emulation state resumed from
|
||||
// memory savestates.
|
||||
//
|
||||
// Exceptions (can occur on first call only):
|
||||
// PluginInitError - thrown if a plugin fails init (init is performed on the current thread
|
||||
// on the first time the thread is resumed from it's initial idle state)
|
||||
// ThreadCreationError - Insufficient system resources to create thread.
|
||||
//
|
||||
void SysCoreThread::Resume()
|
||||
{
|
||||
if( IsSelf() ) return;
|
||||
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
if( m_ExecMode == ExecMode_NoThreadYet )
|
||||
{
|
||||
m_plugins.Init();
|
||||
Start();
|
||||
m_ExecMode = ExecMode_Idle;
|
||||
}
|
||||
|
||||
if( m_ExecMode == ExecMode_Running )
|
||||
return;
|
||||
|
||||
if( m_ExecMode == ExecMode_Suspending )
|
||||
{
|
||||
// if there are resets to be done, then we need to make sure and wait for the
|
||||
// emuThread to enter a fully suspended state before continuing...
|
||||
|
||||
if( m_resetRecompilers || m_resetProfilers )
|
||||
{
|
||||
locker.Unlock(); // no deadlocks please, thanks. :)
|
||||
m_SuspendEvent.WaitGui();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ExecMode = ExecMode_Running;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DevAssert( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Idle),
|
||||
"EmuCoreThread is not in a suspended or idle state? wtf!" );
|
||||
|
||||
OnResumeReady();
|
||||
|
||||
if( m_resetRecompilers || m_resetProfilers )
|
||||
{
|
||||
SysClearExecutionCache();
|
||||
m_resetRecompilers = false;
|
||||
m_resetProfilers = false;
|
||||
}
|
||||
|
||||
m_ExecMode = ExecMode_Running;
|
||||
m_ResumeEvent.Post();
|
||||
}
|
||||
|
||||
// Pauses the emulation state at the next PS2 vsync, and returns control to the calling
|
||||
// thread; or does nothing if the core is already suspended. Calling this thread from the
|
||||
// Core thread will result in deadlock.
|
||||
//
|
||||
// Parameters:
|
||||
// isNonblocking - if set to true then the function will not block for emulation suspension.
|
||||
// Defaults to false if parameter is not specified. Performing non-blocking suspension
|
||||
// is mostly useful for starting certain non-Emu related gui activities (improves gui
|
||||
// responsiveness).
|
||||
//
|
||||
void SysCoreThread::Suspend( bool isBlocking )
|
||||
{
|
||||
if( IsSelf() || !IsRunning() ) return;
|
||||
|
||||
{
|
||||
ScopedLock locker( m_lock_ExecMode );
|
||||
|
||||
if( (m_ExecMode == ExecMode_Suspended) || (m_ExecMode == ExecMode_Idle) )
|
||||
return;
|
||||
|
||||
if( m_ExecMode == ExecMode_Running )
|
||||
m_ExecMode = ExecMode_Suspending;
|
||||
|
||||
DevAssert( m_ExecMode == ExecMode_Suspending, "ExecMode should be nothing other than Suspended..." );
|
||||
}
|
||||
|
||||
m_SuspendEvent.WaitGui();
|
||||
}
|
||||
|
||||
// Applies a full suite of new settings, which will automatically facilitate the necessary
|
||||
// resets of the core and components (including plugins, if needed). The scope of resetting
|
||||
// is determined by comparing the current settings against the new settings.
|
||||
void SysCoreThread::ApplySettings( const Pcsx2Config& src )
|
||||
{
|
||||
if( src == EmuConfig ) return;
|
||||
|
||||
const bool isSuspended = IsSuspended();
|
||||
Suspend();
|
||||
|
||||
m_resetRecompilers = ( src.Cpu != EmuConfig.Cpu ) || ( src.Gamefixes != EmuConfig.Gamefixes ) || ( src.Speedhacks != EmuConfig.Speedhacks );
|
||||
m_resetProfilers = (src.Profiler != EmuConfig.Profiler );
|
||||
const_cast<Pcsx2Config&>(EmuConfig) = src;
|
||||
|
||||
if( !isSuspended ) Resume();
|
||||
_parent::DoThreadCleanup();
|
||||
}
|
||||
|
|
@ -2059,8 +2059,8 @@ void _vuXGKICK(VURegs * VU)
|
|||
|
||||
u8* data = ((u8*)VU->Mem + ((VU->VI[_Is_].US[0]*16) & 0x3fff));
|
||||
u32 size;
|
||||
size = mtgsThread->PrepDataPacket( GIF_PATH_1, data, (0x4000-((VU->VI[_Is_].US[0]*16) & 0x3fff)) >> 4);
|
||||
u8* pmem = mtgsThread->GetDataPacketPtr();
|
||||
size = mtgsThread.PrepDataPacket( GIF_PATH_1, data, (0x4000-((VU->VI[_Is_].US[0]*16) & 0x3fff)) >> 4);
|
||||
u8* pmem = mtgsThread.GetDataPacketPtr();
|
||||
|
||||
if((size << 4) > (u32)(0x4000-((VU->VI[_Is_].US[0]*16) & 0x3fff)))
|
||||
{
|
||||
|
@ -2074,7 +2074,7 @@ void _vuXGKICK(VURegs * VU)
|
|||
else {
|
||||
memcpy_aligned(pmem, (u8*)VU->Mem+((VU->VI[_Is_].US[0]*16) & 0x3fff), size<<4);
|
||||
}
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
}
|
||||
|
||||
void _vuXTOP(VURegs * VU) {
|
||||
|
|
|
@ -1887,12 +1887,12 @@ static int __fastcall Vif1TransDirectHL(u32 *data)
|
|||
FreezeRegs(1);
|
||||
// copy 16 bytes the fast way:
|
||||
const u64* src = (u64*)splittransfer[0];
|
||||
mtgsThread->PrepDataPacket(GIF_PATH_2, nloop0_packet, 1);
|
||||
u64* dst = (u64*)mtgsThread->GetDataPacketPtr();
|
||||
mtgsThread.PrepDataPacket(GIF_PATH_2, nloop0_packet, 1);
|
||||
u64* dst = (u64*)mtgsThread.GetDataPacketPtr();
|
||||
dst[0] = src[0];
|
||||
dst[1] = src[1];
|
||||
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
FreezeRegs(0);
|
||||
|
||||
if (vif1.tag.size == 0) vif1.cmd = 0;
|
||||
|
@ -1928,9 +1928,9 @@ static int __fastcall Vif1TransDirectHL(u32 *data)
|
|||
FreezeRegs(1);
|
||||
|
||||
// Round ret up, just in case it's not 128bit aligned.
|
||||
const uint count = mtgsThread->PrepDataPacket(GIF_PATH_2, data, (ret + 3) >> 2);
|
||||
memcpy_fast(mtgsThread->GetDataPacketPtr(), data, count << 4);
|
||||
mtgsThread->SendDataPacket();
|
||||
const uint count = mtgsThread.PrepDataPacket(GIF_PATH_2, data, (ret + 3) >> 2);
|
||||
memcpy_fast(mtgsThread.GetDataPacketPtr(), data, count << 4);
|
||||
mtgsThread.SendDataPacket();
|
||||
|
||||
FreezeRegs(0);
|
||||
|
||||
|
|
|
@ -219,10 +219,11 @@ typedef HashTools::Dictionary<const GlobalCommandDescriptor*> CommandDictionary;
|
|||
|
||||
class AcceleratorDictionary : public HashTools::HashMap<int, const GlobalCommandDescriptor*>
|
||||
{
|
||||
typedef HashTools::HashMap<int, const GlobalCommandDescriptor*> _parent;
|
||||
|
||||
protected:
|
||||
|
||||
public:
|
||||
typedef HashTools::HashMap<int, const GlobalCommandDescriptor*> _parent;
|
||||
using _parent::operator[];
|
||||
|
||||
AcceleratorDictionary();
|
||||
|
@ -438,6 +439,8 @@ protected:
|
|||
|
||||
class AppEmuThread : public SysCoreThread
|
||||
{
|
||||
typedef SysCoreThread _parent;
|
||||
|
||||
protected:
|
||||
wxKeyEvent m_kevt;
|
||||
|
||||
|
@ -446,7 +449,7 @@ public:
|
|||
virtual ~AppEmuThread() throw();
|
||||
|
||||
virtual void Suspend( bool isBlocking=true );
|
||||
virtual void StateCheck();
|
||||
virtual void StateCheck( bool isCancelable=true );
|
||||
virtual void ApplySettings( const Pcsx2Config& src );
|
||||
virtual void OnResumeReady();
|
||||
|
||||
|
@ -484,7 +487,7 @@ extern void AppSaveSettings();
|
|||
extern void AppApplySettings( const AppConfig* oldconf=NULL );
|
||||
|
||||
extern void SysStatus( const wxString& text );
|
||||
extern void SysSuspend();
|
||||
extern void SysSuspend( bool closePlugins = true );
|
||||
extern void SysResume();
|
||||
extern void SysReset();
|
||||
extern void SysExecute();
|
||||
|
|
|
@ -77,7 +77,7 @@ AppEmuThread::~AppEmuThread() throw()
|
|||
|
||||
void AppEmuThread::Suspend( bool isBlocking )
|
||||
{
|
||||
SysCoreThread::Suspend( isBlocking );
|
||||
_parent::Suspend( isBlocking );
|
||||
AppInvoke( MainFrame, ApplySettings() );
|
||||
|
||||
// Clear the sticky key statuses, because hell knows what'll change while the PAD
|
||||
|
@ -90,7 +90,9 @@ void AppEmuThread::Suspend( bool isBlocking )
|
|||
|
||||
void AppEmuThread::OnResumeReady()
|
||||
{
|
||||
DevAssert( wxThread::IsMain(), "SysCoreThread can only be resumed from the main/gui thread." );
|
||||
if( !DevAssert( wxThread::IsMain(), "SysCoreThread can only be resumed from the main/gui thread." ) ) return;
|
||||
|
||||
if( m_shortSuspend ) return;
|
||||
|
||||
ApplySettings( g_Conf->EmuOptions );
|
||||
|
||||
|
@ -109,18 +111,15 @@ static const int pxID_PadHandler_Keydown = 8030;
|
|||
extern int TranslateGDKtoWXK( u32 keysym );
|
||||
#endif
|
||||
|
||||
void AppEmuThread::StateCheck()
|
||||
void AppEmuThread::StateCheck( bool isCancelable )
|
||||
{
|
||||
SysCoreThread::StateCheck();
|
||||
_parent::StateCheck( isCancelable );
|
||||
|
||||
const keyEvent* ev = PADkeyEvent();
|
||||
|
||||
if( ev == NULL || (ev->key == 0) ) return;
|
||||
|
||||
GetPluginManager().KeyEvent( *ev );
|
||||
|
||||
m_kevt.SetEventType( ( ev->evt == KEYPRESS ) ? wxEVT_KEY_DOWN : wxEVT_KEY_UP );
|
||||
|
||||
const bool isDown = (ev->evt == KEYPRESS);
|
||||
|
||||
#ifdef __WXMSW__
|
||||
|
@ -881,9 +880,12 @@ void SysResume()
|
|||
AppInvoke( CoreThread, Resume() );
|
||||
}
|
||||
|
||||
void SysSuspend()
|
||||
void SysSuspend( bool closePlugins )
|
||||
{
|
||||
AppInvoke( CoreThread, Suspend() );
|
||||
if( closePlugins )
|
||||
AppInvoke( CoreThread, Suspend(closePlugins) );
|
||||
else
|
||||
AppInvoke( CoreThread, ShortSuspend() );
|
||||
}
|
||||
|
||||
void SysReset()
|
||||
|
|
|
@ -141,7 +141,7 @@ namespace Implementations
|
|||
{
|
||||
g_Pcsx2Recording ^= 1;
|
||||
|
||||
mtgsThread->SendSimplePacket(GS_RINGTYPE_RECORD, g_Pcsx2Recording, 0, 0);
|
||||
mtgsThread.SendSimplePacket(GS_RINGTYPE_RECORD, g_Pcsx2Recording, 0, 0);
|
||||
if( SPU2setupRecording != NULL ) SPU2setupRecording(g_Pcsx2Recording, NULL);
|
||||
}
|
||||
|
||||
|
@ -157,6 +157,10 @@ namespace Implementations
|
|||
// --------------------------------------------------------------------------------------
|
||||
// CommandDeclarations table
|
||||
// --------------------------------------------------------------------------------------
|
||||
// This is our manualized introspection/reflection table. In a cool language like C# we'd
|
||||
// have just grabbed this info from enumerating the members of a class and assigning
|
||||
// properties to each method in the class. But since this is C++, we have to do the the
|
||||
// goold old fashioned way! :)
|
||||
|
||||
static const GlobalCommandDescriptor CommandDeclarations[] =
|
||||
{
|
||||
|
|
|
@ -37,6 +37,8 @@ bool States_isSlotUsed(int num)
|
|||
// returns true if the new state was loaded, or false if nothing happened.
|
||||
void States_Load( const wxString& file )
|
||||
{
|
||||
SysSuspend();
|
||||
|
||||
try
|
||||
{
|
||||
SysLoadState( file );
|
||||
|
|
|
@ -15,58 +15,4 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "Utilities/Threading.h"
|
||||
|
||||
using namespace Threading;
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// SysCoreThread class
|
||||
// --------------------------------------------------------------------------------------
|
||||
class SysCoreThread : public PersistentThread
|
||||
{
|
||||
protected:
|
||||
enum ExecutionMode
|
||||
{
|
||||
ExecMode_NoThreadYet,
|
||||
ExecMode_Idle,
|
||||
ExecMode_Running,
|
||||
ExecMode_Suspending,
|
||||
ExecMode_Suspended
|
||||
};
|
||||
|
||||
protected:
|
||||
volatile ExecutionMode m_ExecMode;
|
||||
MutexLock m_lock_ExecMode;
|
||||
|
||||
bool m_resetRecompilers;
|
||||
bool m_resetProfilers;
|
||||
|
||||
PluginManager& m_plugins;
|
||||
Semaphore m_ResumeEvent;
|
||||
Semaphore m_SuspendEvent;
|
||||
|
||||
public:
|
||||
static SysCoreThread& Get();
|
||||
|
||||
public:
|
||||
explicit SysCoreThread( PluginManager& plugins );
|
||||
virtual ~SysCoreThread() throw();
|
||||
|
||||
bool IsSuspended() const { return (m_ExecMode == ExecMode_Suspended); }
|
||||
virtual void Suspend( bool isBlocking = true );
|
||||
virtual void Resume();
|
||||
virtual void ApplySettings( const Pcsx2Config& src );
|
||||
virtual void StateCheck();
|
||||
|
||||
virtual void DoThreadCleanup();
|
||||
|
||||
// This function is called by Resume immediately prior to releasing the suspension of
|
||||
// the core emulation thread. You should overload this rather than Resume(), since
|
||||
// Resume() has a lot of checks and balances to prevent re-entrance and race conditions.
|
||||
virtual void OnResumeReady() {};
|
||||
|
||||
protected:
|
||||
void CpuInitializeMess();
|
||||
void CpuExecute();
|
||||
virtual sptr ExecuteTask();
|
||||
};
|
||||
#include "SysThreads.h"
|
||||
|
|
|
@ -0,0 +1,377 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2009 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "Common.h"
|
||||
#include "GS.h"
|
||||
|
||||
#include "VifDma.h"
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// GIFpath -- the GIFtag Parser
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
struct GSRegSIGBLID
|
||||
{
|
||||
u32 SIGID;
|
||||
u32 LBLID;
|
||||
};
|
||||
|
||||
enum GIF_FLG
|
||||
{
|
||||
GIF_FLG_PACKED = 0,
|
||||
GIF_FLG_REGLIST = 1,
|
||||
GIF_FLG_IMAGE = 2,
|
||||
GIF_FLG_IMAGE2 = 3
|
||||
};
|
||||
|
||||
enum GIF_REG
|
||||
{
|
||||
GIF_REG_PRIM = 0x00,
|
||||
GIF_REG_RGBA = 0x01,
|
||||
GIF_REG_STQ = 0x02,
|
||||
GIF_REG_UV = 0x03,
|
||||
GIF_REG_XYZF2 = 0x04,
|
||||
GIF_REG_XYZ2 = 0x05,
|
||||
GIF_REG_TEX0_1 = 0x06,
|
||||
GIF_REG_TEX0_2 = 0x07,
|
||||
GIF_REG_CLAMP_1 = 0x08,
|
||||
GIF_REG_CLAMP_2 = 0x09,
|
||||
GIF_REG_FOG = 0x0a,
|
||||
GIF_REG_XYZF3 = 0x0c,
|
||||
GIF_REG_XYZ3 = 0x0d,
|
||||
GIF_REG_A_D = 0x0e,
|
||||
GIF_REG_NOP = 0x0f,
|
||||
};
|
||||
|
||||
// GIFTAG
|
||||
// Members of this structure are in CAPS to help visually denote that they are representative
|
||||
// of actual hw register states of the GIF, unlike the internal tracking vars in GIFPath, which
|
||||
// are modified during the GIFtag unpacking process.
|
||||
struct GIFTAG
|
||||
{
|
||||
u32 NLOOP : 15;
|
||||
u32 EOP : 1;
|
||||
u32 dummy0 : 16;
|
||||
u32 dummy1 : 14;
|
||||
u32 PRE : 1;
|
||||
u32 PRIM : 11;
|
||||
u32 FLG : 2;
|
||||
u32 NREG : 4;
|
||||
u32 REGS[2];
|
||||
|
||||
GIFTAG() {}
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// GIFPath -- PS2 GIFtag info (one for each path).
|
||||
// --------------------------------------------------------------------------------------
|
||||
// fixme: The real PS2 has a single internal PATH and 3 logical sources, not 3 entirely
|
||||
// separate paths. But for that to work properly we need also interlocked path sources.
|
||||
// That is, when the GIF selects a source, it sticks to that source until an EOP. Currently
|
||||
// this is not emulated!
|
||||
|
||||
struct GIFPath
|
||||
{
|
||||
const GIFTAG tag; // The "original tag -- modification allowed only by SetTag(), so let's make it const.
|
||||
u8 regs[16]; // positioned after tag ensures 16-bit aligned (in case we SSE optimize later)
|
||||
|
||||
u32 nloop; // local copy nloop counts toward zero, and leaves the tag copy unmodified.
|
||||
u32 curreg; // reg we left of on (for traversing through loops)
|
||||
u32 numregs; // number of regs (when NREG is 0, numregs is 16)
|
||||
|
||||
GIFPath();
|
||||
|
||||
void PrepPackedRegs();
|
||||
void SetTag(const void* mem);
|
||||
bool StepReg();
|
||||
u8 GetReg();
|
||||
|
||||
int ParseTag(GIF_PATH pathidx, const u8* pMem, u32 size);
|
||||
};
|
||||
|
||||
typedef void (*GIFRegHandler)(const u32* data);
|
||||
|
||||
struct GifPathStruct
|
||||
{
|
||||
const GIFRegHandler Handlers[3];
|
||||
GIFPath path[3];
|
||||
|
||||
__forceinline GIFPath& operator[]( int idx ) { return path[idx]; }
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// SIGNAL / FINISH / LABEL (WIP!!)
|
||||
// --------------------------------------------------------------------------------------
|
||||
// The current implementation for these is very incomplete, especially SIGNAL, which needs
|
||||
// an extra VM-state status var to be handled correctly.
|
||||
//
|
||||
|
||||
// SIGNAL : This register is a double-throw. If the SIGNAL bit in CSR is clear, set the CSR
|
||||
// and raise a gsIrq. If CSR is already *set*, then ignore all subsequent drawing operations
|
||||
// and writes to general purpose registers to the GS. (note: I'm pretty sure this includes
|
||||
// direct GS and GSreg accesses, as well as those coming through the GIFpath -- but that
|
||||
// behavior isn't confirmed yet). Privlidged writes are still active.
|
||||
//
|
||||
// Ignorance continues until the SIGNAL bit in CSR is manually cleared by the EE. And here's
|
||||
// the tricky part: the interrupt from the second SIGNAL is still pending, and should be
|
||||
// raised once the EE has reset the *IMR* mask for SIGNAL -- meaning setting the bit to 1
|
||||
// (disabled/masked) and then back to 0 (enabled/unmasked).
|
||||
//
|
||||
static void RegHandlerSIGNAL(const u32* data)
|
||||
{
|
||||
GIF_LOG("MTGS SIGNAL data %x_%x CSRw %x IMR %x CSRr\n",data[0], data[1], CSRw, GSIMR, GSCSRr);
|
||||
|
||||
GSSIGLBLID.SIGID = (GSSIGLBLID.SIGID&~data[1])|(data[0]&data[1]);
|
||||
|
||||
if ((CSRw & 0x1))
|
||||
{
|
||||
if (!(GSIMR&0x100) )
|
||||
{
|
||||
gsIrq();
|
||||
}
|
||||
|
||||
GSCSRr |= 1; // signal
|
||||
}
|
||||
}
|
||||
|
||||
// FINISH : Enables end-of-draw signaling. When FINISH is written it tells the GIF to
|
||||
// raise a gsIrq and set the FINISH bit of CSR when the current operation is finished.
|
||||
// As far as I can figure, this feature is meant for EE/GS synchronization when the EE
|
||||
// wants to utilize GS post-processing effects. We don't need to emulate that part of
|
||||
// it since we flush/interlock the GS for those specific read operations.
|
||||
//
|
||||
// However! We should properly emulate handling partial-DMA transfers on PATH2 and
|
||||
// PATH3 of the GIF, which means only signaling FINISH if nloop==0.
|
||||
//
|
||||
static void RegHandlerFINISH(const u32* data)
|
||||
{
|
||||
GIF_LOG("GIFpath FINISH data %x_%x CSRw %x\n", data[0], data[1], CSRw);
|
||||
|
||||
if ((CSRw & 0x2))
|
||||
{
|
||||
if (!(GSIMR&0x200))
|
||||
gsIrq();
|
||||
|
||||
GSCSRr |= 2; // finish
|
||||
}
|
||||
}
|
||||
|
||||
static void RegHandlerLABEL(const u32* data)
|
||||
{
|
||||
GIF_LOG( "GIFpath LABEL" );
|
||||
GSSIGLBLID.LBLID = (GSSIGLBLID.LBLID&~data[1])|(data[0]&data[1]);
|
||||
}
|
||||
|
||||
PCSX2_ALIGNED16( static GifPathStruct s_gifPath ) =
|
||||
{
|
||||
RegHandlerSIGNAL, RegHandlerFINISH, RegHandlerLABEL
|
||||
};
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// GIFPath Method Implementations
|
||||
// --------------------------------------------------------------------------------------
|
||||
|
||||
GIFPath::GIFPath() : tag()
|
||||
{
|
||||
memzero_obj( *this );
|
||||
}
|
||||
|
||||
__forceinline bool GIFPath::StepReg()
|
||||
{
|
||||
if ((++curreg & 0xf) == tag.NREG) {
|
||||
curreg = 0;
|
||||
if (--nloop == 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
__forceinline u8 GIFPath::GetReg() { return regs[curreg]; }
|
||||
|
||||
// unpack the registers - registers are stored as a sequence of 4 bit values in the
|
||||
// upper 64 bits of the GIFTAG. That sucks for us when handling partialized GIF packets
|
||||
// coming in from paths 2 and 3, so we unpack them into an 8 bit array here.
|
||||
//
|
||||
__forceinline void GIFPath::PrepPackedRegs()
|
||||
{
|
||||
// Only unpack registers if we're starting a new pack. Otherwise the unpacked
|
||||
// array should have already been initialized by a previous partial transfer.
|
||||
|
||||
if (curreg != 0) return;
|
||||
|
||||
u32 tempreg = tag.REGS[0];
|
||||
numregs = ((tag.NREG-1)&0xf) + 1;
|
||||
|
||||
for (u32 i = 0; i < numregs; i++) {
|
||||
if (i == 8) tempreg = tag.REGS[1];
|
||||
regs[i] = tempreg & 0xf;
|
||||
tempreg >>= 4;
|
||||
}
|
||||
}
|
||||
|
||||
__forceinline void GIFPath::SetTag(const void* mem)
|
||||
{
|
||||
const_cast<GIFTAG&>(tag) = *((GIFTAG*)mem);
|
||||
|
||||
nloop = tag.NLOOP;
|
||||
curreg = 0;
|
||||
}
|
||||
|
||||
void SaveStateBase::gifPathFreeze()
|
||||
{
|
||||
FreezeTag( "GIFpath" );
|
||||
Freeze( s_gifPath.path );
|
||||
}
|
||||
|
||||
|
||||
static __forceinline void gsHandler(const u8* pMem) {
|
||||
const int handler = pMem[8];
|
||||
if (handler >= 0x60 && handler < 0x63) {
|
||||
//DevCon::Status("GIF Tag Interrupt");
|
||||
s_gifPath.Handlers[handler&0x3]((const u32*)pMem);
|
||||
}
|
||||
}
|
||||
|
||||
#define incTag(x, y) do { \
|
||||
pMem += (x); \
|
||||
size -= (y); \
|
||||
if ((pathidx==GIF_PATH_1)&&(pMem>=vuMemEnd)) pMem -= 0x4000; \
|
||||
} while(false)
|
||||
|
||||
#define aMin(x, y) ((x < y) ? (x) : (y))
|
||||
#define subVal(x, y) ((x > y) ? (x-y) : 0 )
|
||||
|
||||
// Parameters:
|
||||
// size (path1) - difference between the end of VU memory and pMem.
|
||||
// size (path2/3) - max size of incoming data stream, in qwc (simd128)
|
||||
__forceinline int GIFPath::ParseTag(GIF_PATH pathidx, const u8* pMem, u32 size)
|
||||
{
|
||||
const u8* vuMemEnd = pMem + (size<<4); // End of VU1 Mem
|
||||
if (pathidx==GIF_PATH_1) size = 0x400; // VU1 mem size
|
||||
const u32 startSize = size; // Start Size
|
||||
|
||||
while (size > 0) {
|
||||
if (!nloop) {
|
||||
|
||||
SetTag(pMem);
|
||||
incTag(16, 1);
|
||||
|
||||
if (pathidx == GIF_PATH_3) {
|
||||
if (tag.FLG&2) Path3progress = IMAGE_MODE;
|
||||
else Path3progress = TRANSFER_MODE;
|
||||
}
|
||||
}
|
||||
else {
|
||||
switch(tag.FLG) {
|
||||
case GIF_FLG_PACKED:
|
||||
PrepPackedRegs();
|
||||
do {
|
||||
if (GetReg() == 0xe) {
|
||||
gsHandler(pMem);
|
||||
}
|
||||
incTag(16, 1);
|
||||
} while(StepReg() && size > 0);
|
||||
break;
|
||||
case GIF_FLG_REGLIST:
|
||||
{
|
||||
size *= 2;
|
||||
|
||||
do { incTag(8, 1); }
|
||||
while(StepReg() && size > 0);
|
||||
|
||||
if (size & 1) { incTag(8, 1); }
|
||||
size /= 2;
|
||||
}
|
||||
break;
|
||||
case GIF_FLG_IMAGE:
|
||||
case GIF_FLG_IMAGE2:
|
||||
{
|
||||
int len = aMin(size, nloop);
|
||||
incTag((len * 16), len);
|
||||
nloop -= len;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (tag.EOP && !nloop) {
|
||||
if (pathidx != GIF_PATH_2) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size = (startSize - size);
|
||||
|
||||
if (pathidx == GIF_PATH_3) {
|
||||
if (tag.EOP && !nloop) {
|
||||
Path3progress = STOPPED_MODE;
|
||||
}
|
||||
gif->madr += size * 16;
|
||||
gif->qwc -= size;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
// Processes a GIFtag & packet, and throws out some gsIRQs as needed.
|
||||
// Used to keep interrupts in sync with the EE, while the GS itself
|
||||
// runs potentially several frames behind.
|
||||
// Parameters:
|
||||
// size - max size of incoming data stream, in qwc (simd128)
|
||||
__forceinline int GIFPath_ParseTag(GIF_PATH pathidx, const u8* pMem, u32 size)
|
||||
{
|
||||
#ifdef PCSX2_GSRING_SAMPLING_STATS
|
||||
static uptr profStartPtr = 0;
|
||||
static uptr profEndPtr = 0;
|
||||
if (profStartPtr == 0) {
|
||||
__asm
|
||||
{
|
||||
__beginfunc:
|
||||
mov profStartPtr, offset __beginfunc;
|
||||
mov profEndPtr, offset __endfunc;
|
||||
}
|
||||
ProfilerRegisterSource( "GSRingBufCopy", (void*)profStartPtr, profEndPtr - profStartPtr );
|
||||
}
|
||||
#endif
|
||||
|
||||
int retSize = s_gifPath[pathidx].ParseTag(pathidx, pMem, size);
|
||||
|
||||
#ifdef PCSX2_GSRING_SAMPLING_STATS
|
||||
__asm
|
||||
{
|
||||
__endfunc:
|
||||
nop;
|
||||
}
|
||||
#endif
|
||||
return retSize;
|
||||
}
|
||||
|
||||
// Clears all GIFpath data to zero.
|
||||
void GIFPath_Reset()
|
||||
{
|
||||
memzero_obj( s_gifPath.path );
|
||||
}
|
||||
|
||||
// This is a hackfix tool provided for "canceling" the contents of the GIFpath when
|
||||
// invalid GIFdma states are encountered (tpyically needed for PATH3 only).
|
||||
__forceinline void GIFPath_Clear( GIF_PATH pathidx )
|
||||
{
|
||||
memzero_obj(s_gifPath.path[pathidx]);
|
||||
if( GSgifSoftReset == NULL ) return;
|
||||
mtgsThread.SendSimplePacket( GS_RINGTYPE_SOFTRESET, (1<<pathidx), 0, 0 );
|
||||
}
|
|
@ -492,7 +492,7 @@
|
|||
</File>
|
||||
</Filter>
|
||||
<Filter
|
||||
Name="EmuCore"
|
||||
Name="System"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\..\Dump.cpp"
|
||||
|
@ -534,6 +534,14 @@
|
|||
RelativePath="..\..\System.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\System\SysThreads.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\System\SysThreads.h"
|
||||
>
|
||||
</File>
|
||||
<Filter
|
||||
Name="ISO"
|
||||
>
|
||||
|
@ -685,10 +693,6 @@
|
|||
RelativePath="..\..\ps2\BiosTools.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\ps2\CoreEmuThread.cpp"
|
||||
>
|
||||
</File>
|
||||
<Filter
|
||||
Name="EmotionEngine"
|
||||
>
|
||||
|
@ -1448,6 +1452,10 @@
|
|||
<Filter
|
||||
Name="GS"
|
||||
>
|
||||
<File
|
||||
RelativePath="..\..\ps2\GIFpath.cpp"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\GS.cpp"
|
||||
>
|
||||
|
@ -1884,10 +1892,6 @@
|
|||
RelativePath="..\..\ps2\BiosTools.h"
|
||||
>
|
||||
</File>
|
||||
<File
|
||||
RelativePath="..\..\ps2\CoreEmuThread.h"
|
||||
>
|
||||
</File>
|
||||
</Filter>
|
||||
</Filter>
|
||||
<Filter
|
||||
|
@ -2370,7 +2374,7 @@
|
|||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)"
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)
"
|
||||
Outputs=""$(InputDir)\$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
|
@ -2380,7 +2384,7 @@
|
|||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)"
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)
"
|
||||
Outputs=""$(InputDir)\$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
|
@ -2390,7 +2394,7 @@
|
|||
<Tool
|
||||
Name="VCCustomBuildTool"
|
||||
Description=""
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)"
|
||||
CommandLine=""$(InputDir)\bin2cpp.cmd" $(InputFileName)
"
|
||||
Outputs=""$(InputDir)\$(InputName).h"
|
||||
/>
|
||||
</FileConfiguration>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
>
|
||||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
AdditionalIncludeDirectories="./;../../;../../x86;"../../x86/ix86-32";../libs;../../IPU"
|
||||
AdditionalIncludeDirectories="./;../../;../../x86;"../../x86/ix86-32";../../IPU;../../System"
|
||||
PreprocessorDefinitions="__i386__;TIXML_USE_STL;_SCL_SECURE_NO_WARNINGS"
|
||||
RuntimeTypeInfo="false"
|
||||
PrecompiledHeaderFile="$(IntDir)/pcsx2.pch"
|
||||
|
|
|
@ -1106,8 +1106,8 @@ void __fastcall mVU_XGKICK_(u32 addr) {
|
|||
addr &= 0x3ff;
|
||||
u8* data = microVU1.regs->Mem + (addr*16);
|
||||
u32 diff = 0x400 - addr;
|
||||
u32 size = mtgsThread->PrepDataPacket(GIF_PATH_1, data, diff);
|
||||
u8* pDest = mtgsThread->GetDataPacketPtr();
|
||||
u32 size = mtgsThread.PrepDataPacket(GIF_PATH_1, data, diff);
|
||||
u8* pDest = mtgsThread.GetDataPacketPtr();
|
||||
|
||||
if (size > diff) {
|
||||
// fixme: one of these days the following *16's will get cleaned up when we introduce
|
||||
|
@ -1121,7 +1121,7 @@ void __fastcall mVU_XGKICK_(u32 addr) {
|
|||
else {
|
||||
memcpy_aligned(pDest, microVU1.regs->Mem + (addr*16), size*16);
|
||||
}
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
}
|
||||
|
||||
microVUt(void) mVU_XGKICK_DELAY(mV, bool memVI) {
|
||||
|
|
|
@ -1974,10 +1974,10 @@ void VU1XGKICK_MTGSTransfer(u32 *pMem, u32 addr)
|
|||
u32 size;
|
||||
u8* data = ((u8*)pMem + (addr&0x3fff));
|
||||
|
||||
size = mtgsThread->PrepDataPacket(GIF_PATH_1, data, (0x4000-(addr&0x3fff)) / 16);
|
||||
size = mtgsThread.PrepDataPacket(GIF_PATH_1, data, (0x4000-(addr&0x3fff)) / 16);
|
||||
jASSUME( size > 0 );
|
||||
|
||||
u8* pmem = mtgsThread->GetDataPacketPtr();
|
||||
u8* pmem = mtgsThread.GetDataPacketPtr();
|
||||
|
||||
if((size << 4) > (0x4000-(addr&0x3fff)))
|
||||
{
|
||||
|
@ -1992,6 +1992,6 @@ void VU1XGKICK_MTGSTransfer(u32 *pMem, u32 addr)
|
|||
memcpy_aligned(pmem, (u8*)pMem+addr, size<<4);
|
||||
}
|
||||
|
||||
mtgsThread->SendDataPacket();
|
||||
mtgsThread.SendDataPacket();
|
||||
}
|
||||
//------------------------------------------------------------------
|
||||
|
|
Loading…
Reference in New Issue