mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-01-31 01:15:24 +01:00
Pcsx2 now has a gifUnit class which acts like the ps2's gif and executes a single gif transfer at a time (and performs path arbitration according to priority and path3 slicing). This new code is generally a speedup for most games. Particularly VU heavy games like GoW. This revision breaks old saved state compatibility so don't update if you wish to keep playing with your old saved states. Leave comments if this revision fixes or breaks anything... Message to GS Plugin authors: This new code now uses only 1 gif callback to transfer data to the gs plugin (GSgifTransfer). pcsx2 also is garaunteed to send full GS primitives to the plugin. So you don't have to account for partial-transfers anymore. Thanks goes out to shadowlady who tested around 500 games for me :D Note 1: The old gif code is still in this revision, and can be enabled by the USE_OLD_GIF macro. The old code will be deleted soon. Note 2: This revision also enables assertion dialogs in devel builds, and changed a lot of assume cases into assertions. git-svn-id: http://pcsx2.googlecode.com/svn/trunk@4821 96395faa-99c1-11dd-bbfe-3dabce05a288
1089 lines
32 KiB
C++
1089 lines
32 KiB
C++
/* PCSX2 - PS2 Emulator for PCs
|
|
* Copyright (C) 2002-2010 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 "MainFrame.h"
|
|
#include "GSFrame.h"
|
|
#include "GS.h"
|
|
#include "AppSaveStates.h"
|
|
#include "AppGameDatabase.h"
|
|
#include "AppAccelerators.h"
|
|
|
|
#include "Plugins.h"
|
|
#include "ps2/BiosTools.h"
|
|
|
|
#include "Dialogs/ModalPopups.h"
|
|
#include "Dialogs/ConfigurationDialog.h"
|
|
#include "Dialogs/LogOptionsDialog.h"
|
|
|
|
#include "Utilities/IniInterface.h"
|
|
|
|
#include <wx/stdpaths.h>
|
|
|
|
#ifdef __WXMSW__
|
|
# include <wx/msw/wrapwin.h> // needed to implement the app!
|
|
#endif
|
|
|
|
#ifdef __WXGTK__
|
|
// Need to tranform the GSPanel to a X11 window/display for the GS plugins
|
|
#include <wx/gtk/win_gtk.h> // GTK_PIZZA interface
|
|
#include <gdk/gdkx.h>
|
|
#include <gtk/gtk.h>
|
|
#endif
|
|
|
|
|
|
IMPLEMENT_APP(Pcsx2App)
|
|
|
|
DEFINE_EVENT_TYPE( pxEvt_LoadPluginsComplete );
|
|
DEFINE_EVENT_TYPE( pxEvt_LogicalVsync );
|
|
|
|
DEFINE_EVENT_TYPE( pxEvt_ThreadTaskTimeout_SysExec );
|
|
|
|
ScopedPtr<AppConfig> g_Conf;
|
|
|
|
template<typename DialogType>
|
|
int AppOpenModalDialog( wxWindow* parent=NULL )
|
|
{
|
|
if( wxWindow* window = wxFindWindowByName( L"Dialog:" + DialogType::GetNameStatic() ) )
|
|
{
|
|
window->SetFocus();
|
|
if( wxDialog* dialog = wxDynamicCast( window, wxDialog ) )
|
|
{
|
|
// It's legal to call ShowModal on a non-modal dialog, therefore making
|
|
// it modal in nature for the needs of whatever other thread of action wants
|
|
// to block against it:
|
|
|
|
if( !dialog->IsModal() )
|
|
{
|
|
int result = dialog->ShowModal();
|
|
dialog->Destroy();
|
|
return result;
|
|
}
|
|
}
|
|
pxFailDev( "Can only show wxDialog class windows as modal!" );
|
|
return wxID_CANCEL;
|
|
}
|
|
else
|
|
return DialogType( parent ).ShowModal();
|
|
}
|
|
|
|
static bool HandlePluginError( BaseException& ex )
|
|
{
|
|
if( !pxDialogExists( L"CoreSettings" ) )
|
|
{
|
|
if( !Msgbox::OkCancel( ex.FormatDisplayMessage() +
|
|
_("\n\nPress Ok to go to the Plugin Configuration Panel.")
|
|
) )
|
|
return false;
|
|
}
|
|
|
|
g_Conf->SysSettingsTabName = L"Plugins";
|
|
|
|
// TODO: Send a message to the panel to select the failed plugin.
|
|
|
|
return AppOpenModalDialog<Dialogs::ComponentsConfigDialog>() != wxID_CANCEL;
|
|
}
|
|
|
|
class PluginErrorEvent : public pxExceptionEvent
|
|
{
|
|
typedef pxExceptionEvent _parent;
|
|
|
|
public:
|
|
PluginErrorEvent( BaseException* ex=NULL ) : _parent( ex ) {}
|
|
PluginErrorEvent( const BaseException& ex ) : _parent( ex ) {}
|
|
|
|
virtual ~PluginErrorEvent() throw() { }
|
|
virtual PluginErrorEvent *Clone() const { return new PluginErrorEvent(*this); }
|
|
|
|
protected:
|
|
void InvokeEvent();
|
|
};
|
|
|
|
class PluginInitErrorEvent : public pxExceptionEvent
|
|
{
|
|
typedef pxExceptionEvent _parent;
|
|
|
|
public:
|
|
PluginInitErrorEvent( BaseException* ex=NULL ) : _parent( ex ) {}
|
|
PluginInitErrorEvent( const BaseException& ex ) : _parent( ex ) {}
|
|
|
|
virtual ~PluginInitErrorEvent() throw() { }
|
|
virtual PluginInitErrorEvent *Clone() const { return new PluginInitErrorEvent(*this); }
|
|
|
|
protected:
|
|
void InvokeEvent();
|
|
|
|
};
|
|
|
|
void PluginErrorEvent::InvokeEvent()
|
|
{
|
|
if( !m_except ) return;
|
|
|
|
ScopedExcept deleteMe( m_except );
|
|
m_except = NULL;
|
|
|
|
if( !HandlePluginError( *deleteMe ) )
|
|
{
|
|
Console.Error( L"User-canceled plugin configuration; Plugins not loaded!" );
|
|
Msgbox::Alert( _("Warning! System plugins have not been loaded. PCSX2 may be inoperable.") );
|
|
}
|
|
}
|
|
|
|
void PluginInitErrorEvent::InvokeEvent()
|
|
{
|
|
if( !m_except ) return;
|
|
|
|
ScopedExcept deleteMe( m_except );
|
|
m_except = NULL;
|
|
|
|
if( !HandlePluginError( *deleteMe ) )
|
|
{
|
|
Console.Error( L"User-canceled plugin configuration after plugin initialization failure. Plugins unloaded." );
|
|
Msgbox::Alert( _("Warning! System plugins have not been loaded. PCSX2 may be inoperable.") );
|
|
}
|
|
}
|
|
|
|
// Allows for activating menu actions from anywhere in PCSX2.
|
|
// And it's Thread Safe!
|
|
void Pcsx2App::PostMenuAction( MenuIdentifiers menu_id ) const
|
|
{
|
|
MainEmuFrame* mainFrame = GetMainFramePtr();
|
|
if( mainFrame == NULL ) return;
|
|
|
|
wxCommandEvent joe( wxEVT_COMMAND_MENU_SELECTED, menu_id );
|
|
if( wxThread::IsMain() )
|
|
mainFrame->GetEventHandler()->ProcessEvent( joe );
|
|
else
|
|
mainFrame->GetEventHandler()->AddPendingEvent( joe );
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// Pcsx2AppMethodEvent
|
|
// --------------------------------------------------------------------------------------
|
|
// Unlike pxPingEvent, the Semaphore belonging to this event is typically posted when the
|
|
// invoked method is completed. If the method can be executed in non-blocking fashion then
|
|
// it should leave the semaphore postback NULL.
|
|
//
|
|
class Pcsx2AppMethodEvent : public pxActionEvent
|
|
{
|
|
typedef pxActionEvent _parent;
|
|
DECLARE_DYNAMIC_CLASS_NO_ASSIGN(Pcsx2AppMethodEvent)
|
|
|
|
protected:
|
|
FnPtr_Pcsx2App m_Method;
|
|
|
|
public:
|
|
virtual ~Pcsx2AppMethodEvent() throw() { }
|
|
virtual Pcsx2AppMethodEvent *Clone() const { return new Pcsx2AppMethodEvent(*this); }
|
|
|
|
explicit Pcsx2AppMethodEvent( FnPtr_Pcsx2App method=NULL, SynchronousActionState* sema=NULL )
|
|
: pxActionEvent( sema )
|
|
{
|
|
m_Method = method;
|
|
}
|
|
|
|
explicit Pcsx2AppMethodEvent( FnPtr_Pcsx2App method, SynchronousActionState& sema )
|
|
: pxActionEvent( sema )
|
|
{
|
|
m_Method = method;
|
|
}
|
|
|
|
Pcsx2AppMethodEvent( const Pcsx2AppMethodEvent& src )
|
|
: pxActionEvent( src )
|
|
{
|
|
m_Method = src.m_Method;
|
|
}
|
|
|
|
void SetMethod( FnPtr_Pcsx2App method )
|
|
{
|
|
m_Method = method;
|
|
}
|
|
|
|
protected:
|
|
void InvokeEvent()
|
|
{
|
|
if( m_Method ) (wxGetApp().*m_Method)();
|
|
}
|
|
};
|
|
|
|
|
|
IMPLEMENT_DYNAMIC_CLASS( Pcsx2AppMethodEvent, pxActionEvent )
|
|
|
|
#ifdef __WXGTK__
|
|
extern int TranslateGDKtoWXK( u32 keysym );
|
|
#endif
|
|
|
|
void Pcsx2App::PadKeyDispatch( const keyEvent& ev )
|
|
{
|
|
m_kevt.SetEventType( ( ev.evt == KEYPRESS ) ? wxEVT_KEY_DOWN : wxEVT_KEY_UP );
|
|
const bool isDown = (ev.evt == KEYPRESS);
|
|
|
|
#ifdef __WXMSW__
|
|
const int vkey = wxCharCodeMSWToWX( ev.key );
|
|
#elif defined( __WXGTK__ )
|
|
const int vkey = TranslateGDKtoWXK( ev.key );
|
|
#else
|
|
# error Unsupported Target Platform.
|
|
#endif
|
|
|
|
switch (vkey)
|
|
{
|
|
case WXK_SHIFT: m_kevt.m_shiftDown = isDown; return;
|
|
case WXK_CONTROL: m_kevt.m_controlDown = isDown; return;
|
|
|
|
case WXK_ALT: // ALT/MENU are usually the same key? I'm confused.
|
|
case WXK_MENU: m_kevt.m_altDown = isDown; return;
|
|
}
|
|
|
|
m_kevt.m_keyCode = vkey;
|
|
|
|
if( m_kevt.GetEventType() == wxEVT_KEY_DOWN )
|
|
{
|
|
if( GSFrame* gsFrame = wxGetApp().GetGsFramePtr() )
|
|
{
|
|
gsFrame->GetViewport()->DirectKeyCommand( m_kevt );
|
|
}
|
|
else
|
|
{
|
|
m_kevt.SetId( pxID_PadHandler_Keydown );
|
|
wxGetApp().ProcessEvent( m_kevt );
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// Pcsx2AppTraits (implementations) [includes pxMessageOutputMessageBox]
|
|
// --------------------------------------------------------------------------------------
|
|
// This is here to override pxMessageOutputMessageBox behavior, which itself is ONLY used
|
|
// by wxWidgets' command line processor. The default edition is totally inadequate for
|
|
// displaying a readable --help command line list, so I replace it here with a custom one
|
|
// that formats things nicer.
|
|
//
|
|
class pxMessageOutputMessageBox : public wxMessageOutput
|
|
{
|
|
public:
|
|
pxMessageOutputMessageBox() { }
|
|
|
|
virtual void Printf(const wxChar* format, ...);
|
|
};
|
|
|
|
// EXTRAORDINARY HACK! wxWidgets does not provide a clean way of overriding the commandline options
|
|
// display dialog. The default one uses operating system built-in message/notice windows, which are
|
|
// appaling, ugly, and not at all suited to a large number of command line options. Fortunately,
|
|
// wxMessageOutputMessageBox::PrintF is only used in like two places, so we can just check for the
|
|
// commandline window using an identifier we know is contained in it, and then format our own window
|
|
// display. :D --air
|
|
void pxMessageOutputMessageBox::Printf(const wxChar* format, ...)
|
|
{
|
|
using namespace pxSizerFlags;
|
|
|
|
va_list args;
|
|
va_start(args, format);
|
|
wxString out;
|
|
out.PrintfV(format, args);
|
|
va_end(args);
|
|
|
|
FastFormatUnicode isoFormatted;
|
|
isoFormatted.Write( L"[%s]", _("IsoFile") );
|
|
int pos = out.Find( isoFormatted );
|
|
|
|
if(pos == wxNOT_FOUND)
|
|
{
|
|
Msgbox::Alert( out ); return;
|
|
}
|
|
|
|
pos += isoFormatted.Length();
|
|
|
|
wxDialogWithHelpers popup( NULL, AddAppName(_("%s Commandline Options")) );
|
|
popup.SetMinWidth( 640 );
|
|
popup += popup.Heading(out.Mid(0, pos));
|
|
//popup += ;
|
|
//popup += popup.Text(out.Mid(pos, out.Length())).Align( wxALIGN_LEFT ) | pxExpand.Border(wxALL, StdPadding*3);
|
|
|
|
wxTextCtrl* traceArea = new wxTextCtrl(
|
|
&popup, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize,
|
|
wxTE_READONLY | wxTE_MULTILINE | wxTE_RICH2 | wxHSCROLL
|
|
);
|
|
|
|
traceArea->SetDefaultStyle( wxTextAttr( wxNullColour, wxNullColour, pxGetFixedFont(9) ) );
|
|
traceArea->SetFont( pxGetFixedFont(9) );
|
|
|
|
int fonty = traceArea->GetCharHeight();
|
|
|
|
traceArea->SetMinSize( wxSize( traceArea->GetMinWidth(), (fonty+1)*18 ) );
|
|
traceArea->WriteText( pxTextWrapper(wxString(L' ', 18)).Wrap(traceArea, out.Mid(pos, out.Length()), 600).GetResult() );
|
|
traceArea->SetInsertionPoint( 0 );
|
|
traceArea->ShowPosition( 0 );
|
|
|
|
popup += traceArea | pxExpand.Border(wxALL, StdPadding*3);
|
|
|
|
pxIssueConfirmation(popup, MsgButtons().Close() );
|
|
}
|
|
|
|
wxMessageOutput* Pcsx2AppTraits::CreateMessageOutput()
|
|
{
|
|
#ifdef __UNIX__
|
|
return _parent::CreateMessageOutput();
|
|
#else
|
|
return new pxMessageOutputMessageBox;
|
|
#endif
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// Pcsx2StandardPaths
|
|
// --------------------------------------------------------------------------------------
|
|
#ifdef wxUSE_STDPATHS
|
|
class Pcsx2StandardPaths : public wxStandardPaths
|
|
{
|
|
public:
|
|
wxString GetResourcesDir() const
|
|
{
|
|
return Path::Combine( GetDataDir(), L"Langs" );
|
|
}
|
|
|
|
#ifdef __LINUX__
|
|
wxString GetUserLocalDataDir() const
|
|
{
|
|
// Note: GetUserLocalDataDir() on linux return $HOME/.pcsx2 unfortunately it does not follow the XDG standard
|
|
// So we re-implement it, to follow the standard.
|
|
wxDirName user_local_dir;
|
|
wxString xdg_home_value;
|
|
if( wxGetEnv(L"XDG_CONFIG_HOME", &xdg_home_value) ) {
|
|
if ( xdg_home_value.IsEmpty() ) {
|
|
// variable exist but it is empty. So use the default value
|
|
user_local_dir = (wxDirName)Path::Combine( GetUserConfigDir() , wxDirName( L".config/pcsx2" ));
|
|
} else {
|
|
user_local_dir = (wxDirName)Path::Combine( xdg_home_value, pxGetAppName());
|
|
}
|
|
} else {
|
|
// variable do not exist
|
|
user_local_dir = (wxDirName)Path::Combine( GetUserConfigDir() , wxDirName( L".config/pcsx2" ));
|
|
}
|
|
return user_local_dir.ToString();
|
|
}
|
|
#endif
|
|
};
|
|
|
|
wxStandardPathsBase& Pcsx2AppTraits::GetStandardPaths()
|
|
{
|
|
static Pcsx2StandardPaths stdPaths;
|
|
return stdPaths;
|
|
}
|
|
#endif
|
|
|
|
wxAppTraits* Pcsx2App::CreateTraits()
|
|
{
|
|
return new Pcsx2AppTraits;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// FramerateManager (implementations)
|
|
// --------------------------------------------------------------------------------------
|
|
void FramerateManager::Reset()
|
|
{
|
|
//memzero( m_fpsqueue );
|
|
m_initpause = FramerateQueueDepth;
|
|
m_fpsqueue_writepos = 0;
|
|
|
|
for( int i=0; i<FramerateQueueDepth; ++i )
|
|
m_fpsqueue[i] = GetCPUTicks();
|
|
|
|
Resume();
|
|
}
|
|
|
|
//
|
|
void FramerateManager::Resume()
|
|
{
|
|
}
|
|
|
|
void FramerateManager::DoFrame()
|
|
{
|
|
++m_FrameCounter;
|
|
|
|
m_fpsqueue_writepos = (m_fpsqueue_writepos + 1) % FramerateQueueDepth;
|
|
m_fpsqueue[m_fpsqueue_writepos] = GetCPUTicks();
|
|
|
|
// intentionally leave 1 on the counter here, since ultimately we want to divide the
|
|
// final result (in GetFramerate() by QueueDepth-1.
|
|
if( m_initpause > 1 ) --m_initpause;
|
|
}
|
|
|
|
double FramerateManager::GetFramerate() const
|
|
{
|
|
if( m_initpause > (FramerateQueueDepth/2) ) return 0.0;
|
|
const u64 delta = m_fpsqueue[m_fpsqueue_writepos] - m_fpsqueue[(m_fpsqueue_writepos + 1) % FramerateQueueDepth];
|
|
const u32 ticks_per_frame = (u32)(delta / (FramerateQueueDepth-m_initpause));
|
|
return (double)GetTickFrequency() / (double)ticks_per_frame;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Pcsx2App Event Handlers
|
|
// ----------------------------------------------------------------------------
|
|
|
|
// LogicalVsync - Event received from the AppCoreThread (EEcore) for each vsync,
|
|
// roughly 50/60 times a second when frame limiting is enabled, and up to 10,000
|
|
// times a second if not (ok, not quite, but you get the idea... I hope.)
|
|
void Pcsx2App::LogicalVsync()
|
|
{
|
|
if( AppRpc_TryInvokeAsync( &Pcsx2App::LogicalVsync ) ) return;
|
|
|
|
if( !SysHasValidState() ) return;
|
|
|
|
// Update / Calculate framerate!
|
|
|
|
FpsManager.DoFrame();
|
|
|
|
if (EmuConfig.GS.ManagedVsync && EmuConfig.GS.VsyncEnable)
|
|
{
|
|
static bool last_enabled = true; // Avoids locking it in some scenarios
|
|
static int too_slow = 0;
|
|
static int fast_enough = 0;
|
|
|
|
if ( g_LimiterMode == Limit_Nominal && EmuConfig.GS.VsyncEnable && g_Conf->EmuOptions.GS.FrameLimitEnable )
|
|
{
|
|
float fps = (float)FpsManager.GetFramerate();
|
|
|
|
if( gsRegionMode == Region_NTSC )
|
|
{
|
|
if (fps < 59.0f ) {
|
|
too_slow++;
|
|
fast_enough = 0;
|
|
if (too_slow > 4 && last_enabled == true)
|
|
{
|
|
last_enabled = false;
|
|
GSsetVsync( 0 );
|
|
}
|
|
}
|
|
else {
|
|
fast_enough++;
|
|
too_slow = 0;
|
|
if (fast_enough > 12 && last_enabled == false)
|
|
{
|
|
last_enabled = true;
|
|
GSsetVsync( 1 );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (fps < 49.2f ) {
|
|
too_slow++;
|
|
fast_enough = 0;
|
|
if (too_slow > 3 && last_enabled == true)
|
|
{
|
|
last_enabled = false;
|
|
GSsetVsync( 0 );
|
|
}
|
|
}
|
|
else {
|
|
fast_enough++;
|
|
too_slow = 0;
|
|
if (fast_enough > 15 && last_enabled == false)
|
|
{
|
|
last_enabled = true;
|
|
GSsetVsync( 1 );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
last_enabled = true; // Avoids locking it in some scenarios
|
|
too_slow = 0;
|
|
fast_enough = 0;
|
|
GSsetVsync( 0 );
|
|
}
|
|
}
|
|
// Only call PADupdate here if we're using GSopen2. Legacy GSopen plugins have the
|
|
// GS window belonging to the MTGS thread.
|
|
if( (PADupdate != NULL) && (GSopen2 != NULL) && (wxGetApp().GetGsFramePtr() != NULL) )
|
|
PADupdate(0);
|
|
|
|
while( const keyEvent* ev = PADkeyEvent() )
|
|
{
|
|
if( ev->key == 0 ) break;
|
|
|
|
// Give plugins first try to handle keys. If none of them handles the key, it will
|
|
// be passed to the main user interface.
|
|
|
|
if( !GetCorePlugins().KeyEvent( *ev ) )
|
|
PadKeyDispatch( *ev );
|
|
}
|
|
}
|
|
|
|
void Pcsx2App::OnEmuKeyDown( wxKeyEvent& evt )
|
|
{
|
|
const GlobalCommandDescriptor* cmd = NULL;
|
|
if( GlobalAccels ) GlobalAccels->TryGetValue( KeyAcceleratorCode( evt ).val32, cmd );
|
|
|
|
if( cmd == NULL )
|
|
{
|
|
evt.Skip();
|
|
return;
|
|
}
|
|
|
|
DbgCon.WriteLn( "(app) Invoking command: %s", cmd->Id );
|
|
cmd->Invoke();
|
|
}
|
|
|
|
// Returns a string message telling the user to consult guides for obtaining a legal BIOS.
|
|
// This message is in a function because it's used as part of several dialogs in PCSX2 (there
|
|
// are multiple variations on the BIOS and BIOS folder checks).
|
|
wxString BIOS_GetMsg_Required()
|
|
{
|
|
return pxE( "!Notice:BiosDumpRequired",
|
|
L"PCSX2 requires a PS2 BIOS in order to run. For legal reasons, you *must* obtain "
|
|
L"a BIOS from an actual PS2 unit that you own (borrowing doesn't count). "
|
|
L"Please consult the FAQs and Guides for further instructions."
|
|
);
|
|
}
|
|
|
|
|
|
void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event) const
|
|
{
|
|
const_cast<Pcsx2App*>(this)->HandleEvent( handler, func, event );
|
|
}
|
|
|
|
void Pcsx2App::HandleEvent(wxEvtHandler* handler, wxEventFunction func, wxEvent& event)
|
|
{
|
|
try {
|
|
(handler->*func)(event);
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::StartupAborted& ex ) // user-aborted, no popups needed.
|
|
{
|
|
Console.Warning( ex.FormatDiagnosticMessage() );
|
|
Exit();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::BiosLoadFailed& ex )
|
|
{
|
|
wxDialogWithHelpers dialog( NULL, _("PS2 BIOS Error") );
|
|
dialog += dialog.Heading( ex.FormatDisplayMessage() + L"\n\n" + BIOS_GetMsg_Required() + L"\n\n" + _("Press Ok to go to the BIOS Configuration Panel.") );
|
|
dialog += new ModalButtonPanel( &dialog, MsgButtons().OKCancel() );
|
|
|
|
if( dialog.ShowModal() == wxID_CANCEL )
|
|
Console.Warning( "User denied option to re-configure BIOS." );
|
|
|
|
if( AppOpenModalDialog<Dialogs::BiosSelectorDialog>() != wxID_CANCEL )
|
|
SysExecute();
|
|
else
|
|
Console.Warning( "User canceled BIOS configuration." );
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::SaveStateLoadError& ex)
|
|
{
|
|
// Saved state load failed prior to the system getting corrupted (ie, file not found
|
|
// or some zipfile error) -- so log it and resume emulation.
|
|
Console.Warning( ex.FormatDiagnosticMessage() );
|
|
CoreThread.Resume();
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::PluginOpenError& ex )
|
|
{
|
|
// Should need to do much here -- all systems should be in an inert and (sorta safe!) state.
|
|
|
|
Console.Error( ex.FormatDiagnosticMessage() );
|
|
AddIdleEvent( PluginInitErrorEvent(ex) );
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::PluginInitError& ex )
|
|
{
|
|
ShutdownPlugins();
|
|
|
|
Console.Error( ex.FormatDiagnosticMessage() );
|
|
AddIdleEvent( PluginInitErrorEvent(ex) );
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::PluginError& ex )
|
|
{
|
|
UnloadPlugins();
|
|
|
|
Console.Error( ex.FormatDiagnosticMessage() );
|
|
AddIdleEvent( PluginErrorEvent(ex) );
|
|
}
|
|
// ----------------------------------------------------------------------------
|
|
#if 0
|
|
catch( Exception::ThreadDeadlock& ex )
|
|
{
|
|
// [TODO] Bind a listener to the CoreThread status, and automatically close the dialog
|
|
// if the thread starts responding while we're waiting (not hard in fact, but I'm getting
|
|
// a little tired, so maybe later!) --air
|
|
|
|
Console.Warning( ex.FormatDiagnosticMessage() );
|
|
wxDialogWithHelpers dialog( NULL, _("PCSX2 Unresponsive Thread"), wxVERTICAL );
|
|
|
|
dialog += dialog.Heading( ex.FormatDisplayMessage() + L"\n\n" +
|
|
pxE( "!Notice Error:Thread Deadlock Actions",
|
|
L"'Ignore' to continue waiting for the thread to respond.\n"
|
|
L"'Cancel' to attempt to cancel the thread.\n"
|
|
L"'Terminate' to quit PCSX2 immediately.\n"
|
|
)
|
|
);
|
|
|
|
int result = pxIssueConfirmation( dialog, MsgButtons().Ignore().Cancel().Custom( _("Terminate") ) );
|
|
|
|
if( result == pxID_CUSTOM )
|
|
{
|
|
// fastest way to kill the process! (works in Linux and win32, thanks to windows having very
|
|
// limited Posix Signals support)
|
|
//
|
|
// (note: SIGTERM is a "handled" kill that performs shutdown stuff, which typically just crashes anyway)
|
|
wxKill( wxGetProcessId(), wxSIGKILL );
|
|
}
|
|
else if( result == wxID_CANCEL )
|
|
{
|
|
// Attempt to cancel the thread:
|
|
ex.Thread().Cancel();
|
|
}
|
|
|
|
// Ignore does nothing...
|
|
}
|
|
#endif
|
|
// ----------------------------------------------------------------------------
|
|
catch( Exception::CancelEvent& ex )
|
|
{
|
|
Console.Warning( ex.FormatDiagnosticMessage() );
|
|
}
|
|
catch( Exception::RuntimeError& ex )
|
|
{
|
|
// Runtime errors which have been unhandled should still be safe to recover from,
|
|
// so lets issue a message to the user and then continue the message pump.
|
|
|
|
Console.Error( ex.FormatDiagnosticMessage() );
|
|
Msgbox::Alert( ex.FormatDisplayMessage() );
|
|
}
|
|
}
|
|
|
|
bool Pcsx2App::HasPendingSaves() const
|
|
{
|
|
AffinityAssert_AllowFrom_MainUI();
|
|
return !!m_PendingSaves;
|
|
}
|
|
|
|
// A call to this method informs the app that there is a pending save operation that must be
|
|
// finished prior to exiting the app, or else data loss will occur. Any call to this method
|
|
// should be matched by a call to ClearPendingSave().
|
|
void Pcsx2App::StartPendingSave()
|
|
{
|
|
if( AppRpc_TryInvokeAsync(&Pcsx2App::StartPendingSave) ) return;
|
|
++m_PendingSaves;
|
|
}
|
|
|
|
// If this method is called inappropriately then the entire pending save system will become
|
|
// unreliable and data loss can occur on app exit. Devel and debug builds will assert if
|
|
// such calls are detected (though the detection is far from fool-proof).
|
|
void Pcsx2App::ClearPendingSave()
|
|
{
|
|
if( AppRpc_TryInvokeAsync(&Pcsx2App::ClearPendingSave) ) return;
|
|
|
|
--m_PendingSaves;
|
|
pxAssertDev( m_PendingSaves >= 0, "Pending saves count mismatch (pending count is less than 0)" );
|
|
|
|
if( (m_PendingSaves == 0) && m_ScheduledTermination )
|
|
{
|
|
Console.WriteLn( "App: All pending saves completed; exiting!" );
|
|
Exit();
|
|
}
|
|
}
|
|
|
|
// This method generates debug assertions if the MainFrame handle is NULL (typically
|
|
// indicating that PCSX2 is running in NoGUI mode, or that the main frame has been
|
|
// closed). In most cases you'll want to use HasMainFrame() to test for thread
|
|
// validity first, or use GetMainFramePtr() and manually check for NULL (choice
|
|
// is a matter of programmer preference).
|
|
MainEmuFrame& Pcsx2App::GetMainFrame() const
|
|
{
|
|
MainEmuFrame* mainFrame = GetMainFramePtr();
|
|
|
|
pxAssert(mainFrame != NULL);
|
|
pxAssert(((uptr)GetTopWindow()) == ((uptr)mainFrame));
|
|
return *mainFrame;
|
|
}
|
|
|
|
GSFrame& Pcsx2App::GetGsFrame() const
|
|
{
|
|
GSFrame* gsFrame = (GSFrame*)wxWindow::FindWindowById( m_id_GsFrame );
|
|
pxAssert(gsFrame != NULL);
|
|
return *gsFrame;
|
|
}
|
|
|
|
// NOTE: Plugins are *not* applied by this function. Changes to plugins need to handled
|
|
// manually. The PluginSelectorPanel does this, for example.
|
|
void AppApplySettings( const AppConfig* oldconf )
|
|
{
|
|
AffinityAssert_AllowFrom_MainUI();
|
|
|
|
ScopedCoreThreadPause paused_core;
|
|
|
|
g_Conf->Folders.ApplyDefaults();
|
|
|
|
// Ensure existence of necessary documents folders. Plugins and other parts
|
|
// of PCSX2 rely on them.
|
|
|
|
g_Conf->Folders.MemoryCards.Mkdir();
|
|
g_Conf->Folders.Savestates.Mkdir();
|
|
g_Conf->Folders.Snapshots.Mkdir();
|
|
|
|
g_Conf->EmuOptions.BiosFilename = g_Conf->FullpathToBios();
|
|
|
|
RelocateLogfile();
|
|
|
|
if( (oldconf == NULL) || (oldconf->LanguageCode.CmpNoCase(g_Conf->LanguageCode)) )
|
|
{
|
|
wxDoNotLogInThisScope please;
|
|
i18n_SetLanguage( g_Conf->LanguageId, g_Conf->LanguageCode );
|
|
}
|
|
|
|
CorePlugins.SetSettingsFolder( GetSettingsFolder().ToString() );
|
|
|
|
// Update the compression attribute on the Memcards folder.
|
|
// Memcards generally compress very well via NTFS compression.
|
|
|
|
#ifdef __WXMSW__
|
|
NTFS_CompressFile( g_Conf->Folders.MemoryCards.ToString(), g_Conf->McdCompressNTFS );
|
|
#endif
|
|
sApp.DispatchEvent( AppStatus_SettingsApplied );
|
|
|
|
paused_core.AllowResume();
|
|
}
|
|
|
|
// Invokes the specified Pcsx2App method, or posts the method to the main thread if the calling
|
|
// thread is not Main. Action is blocking. For non-blocking method execution, use
|
|
// AppRpc_TryInvokeAsync.
|
|
//
|
|
// This function works something like setjmp/longjmp, in that the return value indicates if the
|
|
// function actually executed the specified method or not.
|
|
//
|
|
// Returns:
|
|
// FALSE if the method was not posted to the main thread (meaning this IS the main thread!)
|
|
// TRUE if the method was posted.
|
|
//
|
|
bool Pcsx2App::AppRpc_TryInvoke( FnPtr_Pcsx2App method )
|
|
{
|
|
if( wxThread::IsMain() ) return false;
|
|
|
|
SynchronousActionState sync;
|
|
PostEvent( Pcsx2AppMethodEvent( method, sync ) );
|
|
sync.WaitForResult();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Invokes the specified Pcsx2App method, or posts the method to the main thread if the calling
|
|
// thread is not Main. Action is non-blocking. For blocking method execution, use
|
|
// AppRpc_TryInvoke.
|
|
//
|
|
// This function works something like setjmp/longjmp, in that the return value indicates if the
|
|
// function actually executed the specified method or not.
|
|
//
|
|
// Returns:
|
|
// FALSE if the method was not posted to the main thread (meaning this IS the main thread!)
|
|
// TRUE if the method was posted.
|
|
//
|
|
bool Pcsx2App::AppRpc_TryInvokeAsync( FnPtr_Pcsx2App method )
|
|
{
|
|
if( wxThread::IsMain() ) return false;
|
|
PostEvent( Pcsx2AppMethodEvent( method ) );
|
|
return true;
|
|
}
|
|
|
|
// Posts a method to the main thread; non-blocking. Post occurs even when called from the
|
|
// main thread.
|
|
void Pcsx2App::PostAppMethod( FnPtr_Pcsx2App method )
|
|
{
|
|
PostEvent( Pcsx2AppMethodEvent( method ) );
|
|
}
|
|
|
|
// Posts a method to the main thread; non-blocking. Post occurs even when called from the
|
|
// main thread.
|
|
void Pcsx2App::PostIdleAppMethod( FnPtr_Pcsx2App method )
|
|
{
|
|
Pcsx2AppMethodEvent evt( method );
|
|
AddIdleEvent( evt );
|
|
}
|
|
|
|
SysMainMemory& Pcsx2App::GetVmReserve()
|
|
{
|
|
if (!m_VmReserve) m_VmReserve = new SysMainMemory();
|
|
return *m_VmReserve;
|
|
}
|
|
|
|
void Pcsx2App::OpenGsPanel()
|
|
{
|
|
if( AppRpc_TryInvoke( &Pcsx2App::OpenGsPanel ) ) return;
|
|
|
|
GSFrame* gsFrame = GetGsFramePtr();
|
|
if( gsFrame == NULL )
|
|
{
|
|
gsFrame = new GSFrame( GetMainFramePtr(), GetAppName() );
|
|
m_id_GsFrame = gsFrame->GetId();
|
|
|
|
switch( wxGetApp().Overrides.GsWindowMode )
|
|
{
|
|
case GsWinMode_Windowed:
|
|
g_Conf->GSWindow.IsFullscreen = false;
|
|
break;
|
|
|
|
case GsWinMode_Fullscreen:
|
|
g_Conf->GSWindow.IsFullscreen = true;
|
|
break;
|
|
|
|
case GsWinMode_Unspecified:
|
|
g_Conf->GSWindow.IsFullscreen = g_Conf->GSWindow.DefaultToFullscreen;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// This is an attempt to hackfix a bug in nvidia's 195.xx drivers: When using
|
|
// Aero and DX10, the driver fails to update the window after the device has changed,
|
|
// until some event like a hide/show or resize event is posted to the window.
|
|
// Presumably this forces the driver to re-cache the visibility info.
|
|
// Notes:
|
|
// Doing an immediate hide/show didn't work. So now I'm trying a resize. Because
|
|
// wxWidgets is "clever" (grr!) it optimizes out just force-setting the same size
|
|
// over again, so instead I resize it to size-1 and then back to the original size.
|
|
//
|
|
// FIXME: Gsdx memory leaks in DX10 have been fixed. This code may not be needed
|
|
// anymore.
|
|
|
|
const wxSize oldsize( gsFrame->GetSize() );
|
|
wxSize newsize( oldsize );
|
|
newsize.DecBy(1);
|
|
|
|
gsFrame->SetSize( newsize );
|
|
gsFrame->SetSize( oldsize );
|
|
}
|
|
|
|
pxAssertDev( !GetCorePlugins().IsOpen( PluginId_GS ), "GS Plugin must be closed prior to opening a new Gs Panel!" );
|
|
|
|
#ifdef __WXGTK__
|
|
// The x window/display are actually very deeper in the widget. You need both display and window
|
|
// because unlike window there are unrelated. One could think it would be easier to send directly the GdkWindow.
|
|
// Unfortunately there is a race condition between gui and gs threads when you called the
|
|
// GDK_WINDOW_* macro. To be safe I think it is best to do here. It only cost a slight
|
|
// extension (fully compatible) of the plugins API. -- Gregory
|
|
GtkWidget *child_window = gtk_bin_get_child(GTK_BIN(gsFrame->GetViewport()->GetHandle()));
|
|
|
|
gtk_widget_realize(child_window); // create the widget to allow to use GDK_WINDOW_* macro
|
|
gtk_widget_set_double_buffered(child_window, false); // Disable the widget double buffer, you will use the opengl one
|
|
|
|
GdkWindow* draw_window = GTK_PIZZA(child_window)->bin_window;
|
|
Window Xwindow = GDK_WINDOW_XWINDOW(draw_window);
|
|
Display* XDisplay = GDK_WINDOW_XDISPLAY(draw_window);
|
|
|
|
pDsp[0] = (uptr)XDisplay;
|
|
pDsp[1] = (uptr)Xwindow;
|
|
#else
|
|
pDsp[0] = (uptr)gsFrame->GetViewport()->GetHandle();
|
|
pDsp[1] = NULL;
|
|
#endif
|
|
|
|
gsFrame->ShowFullScreen( g_Conf->GSWindow.IsFullscreen );
|
|
}
|
|
|
|
void Pcsx2App::CloseGsPanel()
|
|
{
|
|
if( AppRpc_TryInvoke( &Pcsx2App::CloseGsPanel ) ) return;
|
|
|
|
if (CloseViewportWithPlugins)
|
|
{
|
|
if (GSFrame* gsFrame = GetGsFramePtr())
|
|
if (GSPanel* woot = gsFrame->GetViewport())
|
|
woot->Destroy();
|
|
}
|
|
}
|
|
|
|
void Pcsx2App::OnGsFrameClosed( wxWindowID id )
|
|
{
|
|
if( (m_id_GsFrame == wxID_ANY) || (m_id_GsFrame != id) ) return;
|
|
|
|
CoreThread.Suspend();
|
|
m_id_GsFrame = wxID_ANY;
|
|
|
|
if( !m_UseGUI )
|
|
{
|
|
// [TODO] : Prompt user before exiting, k thx. :)
|
|
PrepForExit();
|
|
}
|
|
}
|
|
|
|
void Pcsx2App::OnProgramLogClosed( wxWindowID id )
|
|
{
|
|
if( (m_id_ProgramLogBox == wxID_ANY) || (m_id_ProgramLogBox != id) ) return;
|
|
|
|
ScopedLock lock( m_mtx_ProgramLog );
|
|
m_id_ProgramLogBox = wxID_ANY;
|
|
DisableWindowLogging();
|
|
}
|
|
|
|
void Pcsx2App::OnMainFrameClosed( wxWindowID id )
|
|
{
|
|
// Nothing threaded depends on the mainframe (yet) -- it all passes through the main wxApp
|
|
// message handler. But that might change in the future.
|
|
if( m_id_MainFrame != id ) return;
|
|
m_id_MainFrame = wxID_ANY;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------------
|
|
// SysExecEvent_Execute
|
|
// --------------------------------------------------------------------------------------
|
|
class SysExecEvent_Execute : public SysExecEvent
|
|
{
|
|
protected:
|
|
bool m_UseCDVDsrc;
|
|
bool m_UseELFOverride;
|
|
CDVD_SourceType m_cdvdsrc_type;
|
|
wxString m_elf_override;
|
|
|
|
public:
|
|
virtual ~SysExecEvent_Execute() throw() {}
|
|
SysExecEvent_Execute* Clone() const { return new SysExecEvent_Execute(*this); }
|
|
|
|
wxString GetEventName() const
|
|
{
|
|
return L"SysExecute";
|
|
}
|
|
|
|
wxString GetEventMessage() const
|
|
{
|
|
return _("Executing PS2 Virtual Machine...");
|
|
}
|
|
|
|
SysExecEvent_Execute()
|
|
: m_UseCDVDsrc(false)
|
|
, m_UseELFOverride(false)
|
|
{
|
|
}
|
|
|
|
SysExecEvent_Execute( CDVD_SourceType srctype, const wxString& elf_override )
|
|
: m_UseCDVDsrc(true)
|
|
, m_UseELFOverride(true)
|
|
, m_cdvdsrc_type(srctype)
|
|
, m_elf_override( elf_override )
|
|
{
|
|
}
|
|
|
|
protected:
|
|
void InvokeEvent()
|
|
{
|
|
wxGetApp().ProcessMethod( AppSaveSettings );
|
|
|
|
// if something unloaded plugins since this messages was queued then it's best to ignore
|
|
// it, because apparently too much stuff is going on and the emulation states are wonky.
|
|
if( !CorePlugins.AreLoaded() ) return;
|
|
|
|
DbgCon.WriteLn( Color_Gray, "(SysExecute) received." );
|
|
|
|
CoreThread.ResetQuick();
|
|
|
|
CDVDsys_SetFile( CDVDsrc_Iso, g_Conf->CurrentIso );
|
|
if( m_UseCDVDsrc )
|
|
CDVDsys_ChangeSource( m_cdvdsrc_type );
|
|
else if( CDVD == NULL )
|
|
CDVDsys_ChangeSource( CDVDsrc_NoDisc );
|
|
|
|
if( m_UseELFOverride && !CoreThread.HasActiveMachine() )
|
|
CoreThread.SetElfOverride( m_elf_override );
|
|
|
|
CoreThread.Resume();
|
|
}
|
|
};
|
|
|
|
// This command performs a full closure of any existing VM state and starts a
|
|
// fresh VM with the requested sources.
|
|
void Pcsx2App::SysExecute()
|
|
{
|
|
SysExecutorThread.PostEvent( new SysExecEvent_Execute() );
|
|
}
|
|
|
|
// Executes the specified cdvd source and optional elf file. This command performs a
|
|
// full closure of any existing VM state and starts a fresh VM with the requested
|
|
// sources.
|
|
void Pcsx2App::SysExecute( CDVD_SourceType cdvdsrc, const wxString& elf_override )
|
|
{
|
|
SysExecutorThread.PostEvent( new SysExecEvent_Execute(cdvdsrc, elf_override) );
|
|
}
|
|
|
|
// Returns true if there is a "valid" virtual machine state from the user's perspective. This
|
|
// means the user has started the emulator and not issued a full reset.
|
|
// Thread Safety: The state of the system can change in parallel to execution of the
|
|
// main thread. If you need to perform an extended length activity on the execution
|
|
// state (such as saving it), you *must* suspend the Corethread first!
|
|
__fi bool SysHasValidState()
|
|
{
|
|
return CoreThread.HasActiveMachine();
|
|
}
|
|
|
|
// Writes text to console and updates the window status bar and/or HUD or whateverness.
|
|
// FIXME: This probably isn't thread safe. >_<
|
|
void SysStatus( const wxString& text )
|
|
{
|
|
// mirror output to the console!
|
|
Console.WriteLn( text.c_str() );
|
|
sMainFrame.SetStatusText( text );
|
|
}
|
|
|
|
// Applies a new active iso source file
|
|
void SysUpdateIsoSrcFile( const wxString& newIsoFile )
|
|
{
|
|
g_Conf->CurrentIso = newIsoFile;
|
|
sMainFrame.UpdateIsoSrcSelection();
|
|
}
|
|
|
|
bool HasMainFrame()
|
|
{
|
|
return wxTheApp && wxGetApp().HasMainFrame();
|
|
}
|
|
|
|
// This method generates debug assertions if either the wxApp or MainFrame handles are
|
|
// NULL (typically indicating that PCSX2 is running in NoGUI mode, or that the main
|
|
// frame has been closed). In most cases you'll want to use HasMainFrame() to test
|
|
// for gui validity first, or use GetMainFramePtr() and manually check for NULL (choice
|
|
// is a matter of programmer preference).
|
|
MainEmuFrame& GetMainFrame()
|
|
{
|
|
return wxGetApp().GetMainFrame();
|
|
}
|
|
|
|
// Returns a pointer to the main frame of the GUI (frame may be hidden from view), or
|
|
// NULL if no main frame exists (NoGUI mode and/or the frame has been destroyed). If
|
|
// the wxApp is NULL then this will also return NULL.
|
|
MainEmuFrame* GetMainFramePtr()
|
|
{
|
|
return wxTheApp ? wxGetApp().GetMainFramePtr() : NULL;
|
|
}
|
|
|
|
SysMainMemory& GetVmMemory()
|
|
{
|
|
return wxGetApp().GetVmReserve();
|
|
}
|
|
|
|
SysCoreThread& GetCoreThread()
|
|
{
|
|
return CoreThread;
|
|
}
|
|
|
|
SysMtgsThread& GetMTGS()
|
|
{
|
|
return mtgsThread;
|
|
}
|
|
|
|
SysCpuProviderPack& GetCpuProviders()
|
|
{
|
|
return *wxGetApp().m_CpuProviders;
|
|
}
|