diff --git a/common/include/Pcsx2Types.h b/common/include/Pcsx2Types.h index bdeb473150..0bfc171976 100644 --- a/common/include/Pcsx2Types.h +++ b/common/include/Pcsx2Types.h @@ -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 diff --git a/common/include/PluginCallbacks.h b/common/include/PluginCallbacks.h index f1394af6e5..7f4a0d24ed 100644 --- a/common/include/PluginCallbacks.h +++ b/common/include/PluginCallbacks.h @@ -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]; diff --git a/common/include/Utilities/HashMap.h b/common/include/Utilities/HashMap.h index 982239e7d5..9e6a846c6b 100644 --- a/common/include/Utilities/HashMap.h +++ b/common/include/Utilities/HashMap.h @@ -549,9 +549,9 @@ public: template< class Key, class T > class HashMap : public google::dense_hash_map { -public: typedef typename google::dense_hash_map _parent; +public: using _parent::operator[]; using _parent::end; typedef typename _parent::const_iterator const_iterator; diff --git a/common/src/Utilities/Exceptions.cpp b/common/src/Utilities/Exceptions.cpp index 2ba47dd428..b8881eb146 100644 --- a/common/src/Utilities/Exceptions.cpp +++ b/common/src/Utilities/Exceptions.cpp @@ -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; } // -------------------------------------------------------------------------------------- diff --git a/common/src/Utilities/ThreadTools.cpp b/common/src/Utilities/ThreadTools.cpp index 8fac8d7ebf..25882a52df 100644 --- a/common/src/Utilities/ThreadTools.cpp +++ b/common/src/Utilities/ThreadTools.cpp @@ -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 @@ -137,25 +135,15 @@ namespace Threading // 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." ); + 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 m_sem_finished.Wait(); #endif - return m_returncode; - } + return m_returncode; } bool PersistentThread::IsSelf() const diff --git a/pcsx2/CDVD/CDVD.cpp b/pcsx2/CDVD/CDVD.cpp index 1d928674ab..3791d502b3 100644 --- a/pcsx2/CDVD/CDVD.cpp +++ b/pcsx2/CDVD/CDVD.cpp @@ -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 ); } } diff --git a/pcsx2/Counters.cpp b/pcsx2/Counters.cpp index 8741dabea9..319e8c90e4 100644 --- a/pcsx2/Counters.cpp +++ b/pcsx2/Counters.cpp @@ -443,6 +443,7 @@ __forceinline void rcntUpdate_vSync() if (vsyncCounter.Mode == MODE_VSYNC) { eeRecIsReset = false; + mtgsThread.PollStatus(); SysCoreThread::Get().StateCheck(); if( eeRecIsReset ) { diff --git a/pcsx2/FiFo.cpp b/pcsx2/FiFo.cpp index 113b5288d9..a0145890fa 100644 --- a/pcsx2/FiFo.cpp +++ b/pcsx2/FiFo.cpp @@ -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); } diff --git a/pcsx2/GS.cpp b/pcsx2/GS.cpp index 5ab9570163..4a1502c152 100644 --- a/pcsx2/GS.cpp +++ b/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,33 +117,35 @@ 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. - - if( !gsGIFSoftReset( 7 ) ) - mtgsThread->SendSimplePacket( GS_RINGTYPE_RESET, 0, 0, 0 ); + // 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 != 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) GSIMR = 0x7F00; //This is bits 14-8 thats all that should be 1 } 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(); } diff --git a/pcsx2/GS.h b/pcsx2/GS.h index 6178275f4c..212e83b5a7 100644 --- a/pcsx2/GS.h +++ b/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_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); diff --git a/pcsx2/Gif.cpp b/pcsx2/Gif.cpp index 0c65449038..a6a6628a9d 100644 --- a/pcsx2/Gif.cpp +++ b/pcsx2/Gif.cpp @@ -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; diff --git a/pcsx2/MTGS.cpp b/pcsx2/MTGS.cpp index e88933a652..c4f3039be5 100644 --- a/pcsx2/MTGS.cpp +++ b/pcsx2/MTGS.cpp @@ -13,21 +13,19 @@ * If not, see . */ - #include "PrecompiledHeader.h" - -#include - -#include - #include "Common.h" -#include "VU.h" #include "GS.h" + +#include "VU.h" #include "iR5900.h" #include "VifDma.h" #include "SamplProf.h" +#include +#include + // 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(&(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(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< std::list 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 ); + } - 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: @@ -562,16 +397,15 @@ void mtgsThreadObject::_RingbufferLoop() case GS_RINGTYPE_MODECHANGE: _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 ); -//} diff --git a/pcsx2/Memory.h b/pcsx2/Memory.h index 6bd357b6c0..ea3e1bd8fe 100644 --- a/pcsx2/Memory.h +++ b/pcsx2/Memory.h @@ -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]) diff --git a/pcsx2/PluginManager.cpp b/pcsx2/PluginManager.cpp index b7601603e6..7a68f9018b 100644 --- a/pcsx2/PluginManager.cpp +++ b/pcsx2/PluginManager.cpp @@ -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 diff --git a/pcsx2/RecoverySystem.cpp b/pcsx2/RecoverySystem.cpp index 17e2049fcd..f5428d3409 100644 --- a/pcsx2/RecoverySystem.cpp +++ b/pcsx2/RecoverySystem.cpp @@ -18,6 +18,9 @@ #include "App.h" #include "HostGui.h" +#include "zlib/zlib.h" + +using namespace Threading; static wxScopedPtr< SafeArray > g_RecoveryState; @@ -38,11 +41,68 @@ namespace StateRecovery { StateRecovery::Clear(); SysClearExecutionCache(); } + + SafeArray 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 gzThread; void SaveToFile( const wxString& file ) { - SafeArray 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 diff --git a/pcsx2/SaveState.cpp b/pcsx2/SaveState.cpp index 2c5581053e..4e69c9152a 100644 --- a/pcsx2/SaveState.cpp +++ b/pcsx2/SaveState.cpp @@ -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,34 +300,33 @@ 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 - { - // Skip unknown sections with a warning log. - // Maybe it'll work! (haha?) + wxASSERT( IsSaving() ); - int size; - Freeze( m_tagspace ); - Freeze( size ); - m_tagspace[sizeof(m_tagspace)-1] = 0; + // Skip unknown sections with a warning log. + // Maybe it'll work! (haha?) - Console::Notice( - "Warning: Unknown tag encountered while loading savestate; going to ignore it!\n" - "\tTagname: %s, Size: %d", m_tagspace, size - ); - m_idx += size; - } + int size; + Freeze( m_tagspace ); + Freeze( size ); + m_tagspace[sizeof(m_tagspace)-1] = 0; + + Console::Notice( + "Warning: Unknown tag encountered while loading savestate; going to ignore it!\n" + "\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() ); } ////////////////////////////////////////////////////////////////////////////////// diff --git a/pcsx2/SaveState.h b/pcsx2/SaveState.h index c02ec0f741..ba7c74855d 100644 --- a/pcsx2/SaveState.h +++ b/pcsx2/SaveState.h @@ -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 }; // -------------------------------------------------------------------------------------- diff --git a/pcsx2/System.cpp b/pcsx2/System.cpp index 8d71c8e142..47be5654bf 100644 --- a/pcsx2/System.cpp +++ b/pcsx2/System.cpp @@ -263,6 +263,20 @@ void SysClearExecutionCache() void SysLoadState( const wxString& srcfile ) { SafeArray 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 diff --git a/pcsx2/ps2/CoreEmuThread.cpp b/pcsx2/System/SysThreads.cpp similarity index 60% rename from pcsx2/ps2/CoreEmuThread.cpp rename to pcsx2/System/SysThreads.cpp index 8c9fd32c41..437d7299ad 100644 --- a/pcsx2/ps2/CoreEmuThread.cpp +++ b/pcsx2/System/SysThreads.cpp @@ -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(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: - { - 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; - } + if( !m_shortSuspend ) + m_plugins.Close(); } -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(EmuConfig) = src; - - if( !isSuspended ) Resume(); + _parent::DoThreadCleanup(); } diff --git a/pcsx2/VUops.cpp b/pcsx2/VUops.cpp index 72d9308dfe..0d7e475cd7 100644 --- a/pcsx2/VUops.cpp +++ b/pcsx2/VUops.cpp @@ -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) { diff --git a/pcsx2/VifDma.cpp b/pcsx2/VifDma.cpp index a00ce4b2c0..d3b9c49e8a 100644 --- a/pcsx2/VifDma.cpp +++ b/pcsx2/VifDma.cpp @@ -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); diff --git a/pcsx2/gui/App.h b/pcsx2/gui/App.h index 3a85c796c2..a902f55f7e 100644 --- a/pcsx2/gui/App.h +++ b/pcsx2/gui/App.h @@ -219,10 +219,11 @@ typedef HashTools::Dictionary CommandDictionary; class AcceleratorDictionary : public HashTools::HashMap { + typedef HashTools::HashMap _parent; + protected: public: - typedef HashTools::HashMap _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(); diff --git a/pcsx2/gui/AppMain.cpp b/pcsx2/gui/AppMain.cpp index 43e9368226..da2981886c 100644 --- a/pcsx2/gui/AppMain.cpp +++ b/pcsx2/gui/AppMain.cpp @@ -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() diff --git a/pcsx2/gui/GlobalCommands.cpp b/pcsx2/gui/GlobalCommands.cpp index dbd1d2ec9f..27097e6f32 100644 --- a/pcsx2/gui/GlobalCommands.cpp +++ b/pcsx2/gui/GlobalCommands.cpp @@ -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[] = { diff --git a/pcsx2/gui/Saveslots.cpp b/pcsx2/gui/Saveslots.cpp index d6fb69c5fa..1eb4a0bc2f 100644 --- a/pcsx2/gui/Saveslots.cpp +++ b/pcsx2/gui/Saveslots.cpp @@ -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 ); diff --git a/pcsx2/ps2/CoreEmuThread.h b/pcsx2/ps2/CoreEmuThread.h index 4172eeaf05..07a1a60c24 100644 --- a/pcsx2/ps2/CoreEmuThread.h +++ b/pcsx2/ps2/CoreEmuThread.h @@ -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" diff --git a/pcsx2/ps2/GIFpath.cpp b/pcsx2/ps2/GIFpath.cpp new file mode 100644 index 0000000000..5b103d8023 --- /dev/null +++ b/pcsx2/ps2/GIFpath.cpp @@ -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 . + */ + +#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(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< + + + + @@ -685,10 +693,6 @@ RelativePath="..\..\ps2\BiosTools.cpp" > - - @@ -1448,6 +1452,10 @@ + + @@ -1884,10 +1892,6 @@ RelativePath="..\..\ps2\BiosTools.h" > - - @@ -2380,7 +2384,7 @@ @@ -2390,7 +2394,7 @@ diff --git a/pcsx2/windows/VCprojects/vsprops/common.vsprops b/pcsx2/windows/VCprojects/vsprops/common.vsprops index 3dab935384..189c5e2235 100644 --- a/pcsx2/windows/VCprojects/vsprops/common.vsprops +++ b/pcsx2/windows/VCprojects/vsprops/common.vsprops @@ -6,7 +6,7 @@ > 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) { diff --git a/pcsx2/x86/sVU_Lower.cpp b/pcsx2/x86/sVU_Lower.cpp index 913699b92c..eab8c84a65 100644 --- a/pcsx2/x86/sVU_Lower.cpp +++ b/pcsx2/x86/sVU_Lower.cpp @@ -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(); } //------------------------------------------------------------------