- Nik's new decoupled audio code with fixes to minimize overruns.

- VBlank timing: increased to 20% of a frame.
This commit is contained in:
Bart Trzynadlowski 2011-09-12 05:43:37 +00:00
parent 4c18925679
commit cf73207c98
9 changed files with 1322 additions and 983 deletions

View file

@ -509,6 +509,10 @@ void CDSB1::SaveState(CBlockFile *StateFile)
// Z80 CPU state
Z80.SaveState(StateFile, "DSB1 Z80");
//DEBUG
//printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
}
void CDSB1::LoadState(CBlockFile *StateFile)
@ -554,6 +558,10 @@ void CDSB1::LoadState(CBlockFile *StateFile)
}
else
MPEG_StopPlaying();
//DEBUG
//printf("usingMPEGStart=%X usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
}
// Offsets of memory regions within DSB1's pool
@ -1065,6 +1073,11 @@ void CDSB2::SaveState(CBlockFile *StateFile)
// 68K CPU state
M68KSetContext(&M68K);
M68KSaveState(StateFile, "DSB2 68K");
//DEBUG
//printf("mpegStart=%X, mpegEnd=%X\n", mpegStart, mpegEnd);
//printf("usingMPEGStart=%X, usingMPEGEnd=%X\n", usingMPEGStart, usingMPEGEnd);
//printf("usingLoopStart=%X, usingLoopEnd=%X\n", usingLoopStart, usingLoopEnd);
}
void CDSB2::LoadState(CBlockFile *StateFile)

View file

@ -1917,8 +1917,8 @@ void CModel3::RunFrame(void)
if (!StartThreads())
goto ThreadError;
// Wake sound board and drive board threads so they can process a frame
if (!sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post())
// Wake threads for sound board (if sync'd) and drive board (if attached) so they can process a frame
if (syncSndBrdThread && !sndBrdThreadSync->Post() || DriveBoard.IsAttached() && !drvBrdThreadSync->Post())
goto ThreadError;
// At the same time, process a single frame for main board (PPC) in this thread
@ -1929,7 +1929,7 @@ void CModel3::RunFrame(void)
goto ThreadError;
// Wait for sound board and drive board threads to finish their work (if they haven't done so already)
while (!sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone)
while (syncSndBrdThread && !sndBrdThreadDone || DriveBoard.IsAttached() && !drvBrdThreadDone)
{
if (!notifySync->Wait(notifyLock))
goto ThreadError;
@ -1957,7 +1957,7 @@ ThreadError:
g_Config.multiThreaded = false;
}
bool CModel3::StartThreads()
bool CModel3::StartThreads(void)
{
if (startedThreads)
return true;
@ -1966,6 +1966,12 @@ bool CModel3::StartThreads()
sndBrdThreadSync = CThread::CreateSemaphore(1);
if (sndBrdThreadSync == NULL)
goto ThreadError;
sndBrdNotifyLock = CThread::CreateMutex();
if (sndBrdNotifyLock == NULL)
goto ThreadError;
sndBrdNotifySync = CThread::CreateCondVar();
if (sndBrdNotifySync == NULL)
goto ThreadError;
if (DriveBoard.IsAttached())
{
drvBrdThreadSync = CThread::CreateSemaphore(1);
@ -1979,19 +1985,26 @@ bool CModel3::StartThreads()
if (notifySync == NULL)
goto ThreadError;
// Create sound board thread
// Create sound board thread (sync'd or unsync'd)
if (syncSndBrdThread)
sndBrdThread = CThread::CreateThread(StartSoundBoardThreadSyncd, this);
else
sndBrdThread = CThread::CreateThread(StartSoundBoardThread, this);
if (sndBrdThread == NULL)
goto ThreadError;
// Create drive board thread, if drive board is attached
// Create drive board thread (sync'd), if drive board is attached
if (DriveBoard.IsAttached())
{
drvBrdThread = CThread::CreateThread(StartDriveBoardThread, this);
drvBrdThread = CThread::CreateThread(StartDriveBoardThreadSyncd, this);
if (drvBrdThread == NULL)
goto ThreadError;
}
// Set audio callback if unsync'd
if (!syncSndBrdThread)
SetAudioCallback(AudioCallback, this);
startedThreads = true;
return true;
@ -2002,16 +2015,55 @@ ThreadError:
return false;
}
void CModel3::StopThreads()
bool CModel3::PauseThreads(void)
{
if (!startedThreads)
return true;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Wait for all threads to finish their processing
pausedThreads = true;
while (sndBrdThreadRunning || drvBrdThreadRunning)
{
if (!notifySync->Wait(notifyLock))
goto ThreadError;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
return true;
ThreadError:
ErrorLog("Threading error in CModel3::PauseThreads: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
return false;
}
void CModel3::ResumeThreads(void)
{
// No need to use any locking here
pausedThreads = false;
return;
}
void CModel3::StopThreads(void)
{
if (!startedThreads)
return;
// Remove callback
if (!syncSndBrdThread)
SetAudioCallback(NULL, NULL);
DeleteThreadObjects();
startedThreads = false;
}
void CModel3::DeleteThreadObjects()
void CModel3::DeleteThreadObjects(void)
{
// Delete (which in turn kills) sound board and drive board threads
// Note that can do so here safely because threads will always be waiting on their semaphores when this method is called
@ -2037,6 +2089,16 @@ void CModel3::DeleteThreadObjects()
delete drvBrdThreadSync;
drvBrdThreadSync = NULL;
}
if (sndBrdNotifyLock != NULL)
{
delete sndBrdNotifyLock;
sndBrdNotifyLock = NULL;
}
if (sndBrdNotifySync != NULL)
{
delete sndBrdNotifySync;
sndBrdNotifySync = NULL;
}
if (notifyLock != NULL)
{
delete notifyLock;
@ -2051,28 +2113,145 @@ void CModel3::DeleteThreadObjects()
int CModel3::StartSoundBoardThread(void *data)
{
// Call sound board thread method on CModel3
// Call method on CModel3 to run unsync'd sound board thread
CModel3 *model3 = (CModel3*)data;
model3->RunSoundBoardThread();
return 0;
}
int CModel3::StartDriveBoardThread(void *data)
int CModel3::StartSoundBoardThreadSyncd(void *data)
{
// Call drive board thread method on CModel3
// Call method on CModel3 to run sync'd sound board thread
CModel3 *model3 = (CModel3*)data;
model3->RunDriveBoardThread();
model3->RunSoundBoardThreadSyncd();
return 0;
}
void CModel3::RunSoundBoardThread()
int CModel3::StartDriveBoardThreadSyncd(void *data)
{
// Call method on CModel3 to run sync'd drive board thread
CModel3 *model3 = (CModel3*)data;
model3->RunDriveBoardThreadSyncd();
return 0;
}
void CModel3::AudioCallback(void *data)
{
// Call method on CModel3 to wake sound board thread
CModel3 *model3 = (CModel3*)data;
model3->WakeSoundBoardThread();
}
void CModel3::WakeSoundBoardThread(void)
{
// Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock())
goto ThreadError;
// Signal to sound board that it should start processing again
if (!sndBrdNotifySync->Signal())
goto ThreadError;
// Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock())
goto ThreadError;
return;
ThreadError:
ErrorLog("Threading error in WakeSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunSoundBoardThread(void)
{
for (;;)
{
bool wait = true;
while (wait)
{
// Enter sound board notify critical section
if (!sndBrdNotifyLock->Lock())
goto ThreadError;
// Wait for notification from audio callback
if (!sndBrdNotifySync->Wait(sndBrdNotifyLock))
goto ThreadError;
// Exit sound board notify critical section
if (!sndBrdNotifyLock->Unlock())
goto ThreadError;
// Enter main notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
sndBrdThreadRunning = true;
}
// Leave main notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Keep processing frames until audio buffer is half full
bool repeat = true;
// NOTE - performs an unlocked read of pausedThreads here, but this is okay
while (!pausedThreads && !SoundBoard.RunFrame())
{
//printf("Rerunning sound board\n");
}
// Enter main notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Let other threads know processing has finished
sndBrdThreadRunning = false;
sndBrdThreadDone = true;
if (!notifySync->SignalAll())
goto ThreadError;
// Leave main notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
ThreadError:
ErrorLog("Threading error in RunSoundBoardThread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunSoundBoardThreadSyncd(void)
{
for (;;)
{
bool wait = true;
while (wait)
{
// Wait on sound board thread semaphore
if (!sndBrdThreadSync->Wait())
goto ThreadError;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
sndBrdThreadRunning = true;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Process a single frame for sound board
SoundBoard.RunFrame();
@ -2080,9 +2259,10 @@ void CModel3::RunSoundBoardThread()
if (!notifyLock->Lock())
goto ThreadError;
// Let main thread know processing has finished
// Let other threads know processing has finished
sndBrdThreadRunning = false;
sndBrdThreadDone = true;
if (!notifySync->Signal())
if (!notifySync->SignalAll())
goto ThreadError;
// Leave notify critical section
@ -2091,18 +2271,37 @@ void CModel3::RunSoundBoardThread()
}
ThreadError:
ErrorLog("Threading error in sound board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
ErrorLog("Threading error in RunSoundBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunDriveBoardThread()
void CModel3::RunDriveBoardThreadSyncd(void)
{
for (;;)
{
bool wait = true;
while (wait)
{
// Wait on drive board thread semaphore
if (!drvBrdThreadSync->Wait())
goto ThreadError;
// Enter notify critical section
if (!notifyLock->Lock())
goto ThreadError;
// Check threads not paused
if (!pausedThreads)
{
wait = false;
drvBrdThreadRunning = true;
}
// Leave notify critical section
if (!notifyLock->Unlock())
goto ThreadError;
}
// Process a single frame for drive board
DriveBoard.RunFrame();
@ -2110,9 +2309,10 @@ void CModel3::RunDriveBoardThread()
if (!notifyLock->Lock())
goto ThreadError;
// Let main thread know processing has finished
// Let other threads know processing has finished
drvBrdThreadRunning = false;
drvBrdThreadDone = true;
if (!notifySync->Signal())
if (!notifySync->SignalAll())
goto ThreadError;
// Leave notify critical section
@ -2121,13 +2321,19 @@ void CModel3::RunDriveBoardThread()
}
ThreadError:
ErrorLog("Threading error in drive board thread: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
ErrorLog("Threading error in RunDriveBoardThreadSyncd: %s\nSwitching back to single-threaded mode.\n", CThread::GetLastError());
g_Config.multiThreaded = false;
}
void CModel3::RunMainBoardFrame(void)
{
// Run the PowerPC for a frame
ppc_execute(g_Config.GetPowerPCFrequency()*1000000/60-10000);
// Compute display and VBlank timings
unsigned frameCycles = g_Config.GetPowerPCFrequency()*1000000/60;
unsigned vblCycles = (unsigned) ((float) frameCycles * 20.0f/100.0f); // 20% vblank (just a guess; probably too long)
unsigned dispCycles = frameCycles - vblCycles;
// Run the PowerPC for the active display part of the frame
ppc_execute(dispCycles);
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
// VBlank
@ -2135,7 +2341,7 @@ void CModel3::RunMainBoardFrame(void)
GPU.BeginFrame();
GPU.RenderFrame();
IRQ.Assert(0x02);
ppc_execute(10000); // TO-DO: Vblank probably needs to be longer. Maybe that's why some games run too fast/slow
ppc_execute(vblCycles);
//printf("PC=%08X LR=%08X\n", ppc_get_pc(), ppc_get_lr());
/*
@ -2736,10 +2942,14 @@ CModel3::CModel3(void)
securityPtr = 0;
startedThreads = false;
pausedThreads = false;
sndBrdThread = NULL;
drvBrdThread = NULL;
sndBrdThreadRunning = false;
sndBrdThreadDone = false;
drvBrdThreadRunning = false;
drvBrdThreadDone = false;
syncSndBrdThread = false;
sndBrdThreadSync = NULL;
drvBrdThreadSync = NULL;
notifyLock = NULL;

View file

@ -291,6 +291,9 @@ public:
CModel3(void);
~CModel3(void);
bool PauseThreads(void);
void ResumeThreads(void);
/*
* Private Property.
* Tresspassers will be shot! ;)
@ -306,16 +309,21 @@ private:
void WriteSystemRegister(unsigned reg, UINT8 data);
void Patch(void);
void RunMainBoardFrame(); // Runs the main board (PPC) for a frame
bool StartThreads(); // Starts all threads
void StopThreads(); // Stops all threads
void DeleteThreadObjects(); // Deletes all threads and synchronization objects
void RunMainBoardFrame(void); // Runs the main board (PPC) for a frame
bool StartThreads(void); // Starts all threads
void StopThreads(void); // Stops all threads
void DeleteThreadObjects(void); // Deletes all threads and synchronization objects
static int StartSoundBoardThread(void *data); // Callback to start sound board thread
static int StartDriveBoardThread(void *data); // Callback to start drive board thread
static int StartSoundBoardThread(void *data); // Callback to start unsync'd sound board thread
static int StartSoundBoardThreadSyncd(void *data); // Callback to start sync'd sound board thread
static int StartDriveBoardThreadSyncd(void *data); // Callback to start sync'd drive board thread
void RunSoundBoardThread(); // Runs sound board thread
void RunDriveBoardThread(); // Runs drive board thread
static void AudioCallback(void *data); // Audio buffer callback
void WakeSoundBoardThread(void); // Used by audio callback to wake sound board thread when not sync'd with PPC thread
void RunSoundBoardThread(void); // Runs sound board thread unsync'd with PPC thread, ie at full speed
void RunSoundBoardThreadSyncd(void); // Runs sound board thread sync'd in step with PPC thread
void RunDriveBoardThreadSyncd(void); // Runs drive board thread sync'd in step with PPC thread
// Game and hardware information
const struct GameInfo *Game;
@ -357,13 +365,19 @@ private:
// Multiple threading
bool startedThreads; // True if threads have been created and started
bool pausedThreads; // True if threads are currently paused
CThread *sndBrdThread; // Sound board thread
CThread *drvBrdThread; // Drive board thread
bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing for current frame
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing for current frame
bool sndBrdThreadRunning; // Flag to indicate sound board thread is currently processing
bool sndBrdThreadDone; // Flag to indicate sound board thread has finished processing
bool drvBrdThreadRunning; // Flag to indicate drive board thread is currently processing
bool drvBrdThreadDone; // Flag to indicate drive board thread has finished processing
// Thread synchronization objects
bool syncSndBrdThread;
CSemaphore *sndBrdThreadSync;
CMutex *sndBrdNotifyLock;
CCondVar *sndBrdNotifySync;
CSemaphore *drvBrdThreadSync;
CMutex *notifyLock;
CCondVar *notifySync;

View file

@ -1,4 +1,3 @@
//TODO: before release, comment out printf()'s
/**
** Supermodel
** A Sega Model 3 Arcade Emulator.
@ -358,7 +357,7 @@ void CSoundBoard::WriteMIDIPort(UINT8 data)
DSB->SendCommand(data);
}
void CSoundBoard::RunFrame(void)
bool CSoundBoard::RunFrame(void)
{
#ifdef SUPERMODEL_SOUND
// Run sound board first to generate SCSP audio
@ -379,7 +378,7 @@ void CSoundBoard::RunFrame(void)
DSB->RunFrame(audioL, audioR);
// Output the audio buffers
OutputAudio(44100/60, audioL, audioR);
bool bufferFull = OutputAudio(44100/60, audioL, audioR);
#ifdef SUPERMODEL_LOG_AUDIO
// Output to binary file
@ -391,8 +390,10 @@ void CSoundBoard::RunFrame(void)
s = audioR[i];
fwrite(&s, sizeof(INT16), 1, soundFP); // right channel
}
#endif
#endif
#endif // SUPERMODEL_LOG_AUDIO
#endif // SUPERMODEL_SOUND
return bufferFull;
}
void CSoundBoard::Reset(void)

View file

@ -126,7 +126,7 @@ public:
*
* Runs the sound board for one frame, updating sound in the process.
*/
void RunFrame(void);
bool RunFrame(void);
/*
* Reset(void):

View file

@ -7,6 +7,12 @@
* Function-based interface for audio output.
*/
typedef void (*AudioCallbackFPtr)(void *data);
extern void SetAudioCallback(AudioCallbackFPtr callback, void *data);
extern void SetAudioEnabled(bool enabled);
/*
* OpenAudio()
*
@ -19,7 +25,7 @@ extern bool OpenAudio();
*
* Sends a chunk of two-channel audio with the given number of samples to the audio system.
*/
extern void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer);
extern bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer);
/*
* CloseAudio()

View file

@ -38,6 +38,25 @@ static bool writeWrapped = false; // True if write position has wrapped around
static unsigned underRuns = 0; // Number of buffer under-runs that have occured
static unsigned overRuns = 0; // Number of buffer over-runs that have occured
static AudioCallbackFPtr callback = NULL;
static void *callbackData = NULL;
void SetAudioCallback(AudioCallbackFPtr newCallback, void *newData)
{
// Lock audio whilst changing callback pointers
SDL_LockAudio();
callback = newCallback;
callbackData = newData;
SDL_UnlockAudio();
}
void SetAudioEnabled(bool newEnabled)
{
enabled = newEnabled;
}
static void PlayCallback(void *data, Uint8 *stream, int len)
{
//printf("PlayCallback(%d) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n",
@ -128,6 +147,8 @@ static void PlayCallback(void *data, Uint8 *stream, int len)
// Move play position forward for next time
playPos += len;
bool halfEmpty = adjWritePos + audioBufferSize / 2 - BYTES_PER_FRAME / 2 < playPos + audioBufferSize;
// Check if play position has moved past end of buffer
if (playPos >= audioBufferSize)
{
@ -135,6 +156,10 @@ static void PlayCallback(void *data, Uint8 *stream, int len)
playPos -= audioBufferSize;
writeWrapped = false;
}
// If buffer is more than half empty then call callback
if (callback && halfEmpty)
callback(callbackData);
}
static void MixChannels(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer, void *dest)
@ -227,11 +252,13 @@ bool OpenAudio()
return OKAY;
}
void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
bool OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
{
//printf("OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u]\n",
// numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize);
bool halfFull = false;
UINT32 bytesRemaining;
UINT32 bytesToCopy;
INT16 *src;
@ -296,6 +323,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
// Check if write position has caught up with play region and now overlaps it (ie buffer over-run)
bool overRun = writePos + numBytes > playPos + audioBufferSize;
if (writePos + audioBufferSize / 2 + BYTES_PER_FRAME / 2 > playPos + audioBufferSize)
halfFull = true;
// Move write position back to within buffer
if (writePos >= audioBufferSize)
writePos -= audioBufferSize;
@ -308,6 +338,8 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
//printf("Audio buffer over-run #%u in OutputAudio(%u) [writePos = %u, writeWrapped = %s, playPos = %u, audioBufferSize = %u, numBytes = %u]\n",
// overRuns, numSamples, writePos, (writeWrapped ? "true" : "false"), playPos, audioBufferSize, numBytes);
halfFull = true;
// Discard current chunk of data
goto Finish;
}
@ -366,6 +398,9 @@ void OutputAudio(unsigned numSamples, INT16 *leftBuffer, INT16 *rightBuffer)
Finish:
// Unlock SDL audio callback
SDL_UnlockAudio();
// Return whether buffer is half full
return halfFull;
}
void CloseAudio()

View file

@ -762,16 +762,24 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
quit = 1;
#ifdef SUPERMODEL_DEBUGGER
bool processUI = true;
if (Debugger != NULL)
{
Debugger->Poll();
// Check if debugger requests exit or pause
if (Debugger->CheckExit())
{
quit = 1;
processUI = false;
}
else if (Debugger->CheckPause())
{
paused = 1;
else
processUI = false;
}
}
if (processUI)
{
#endif // SUPERMODEL_DEBUGGER
@ -783,6 +791,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
}
else if (Inputs->uiReset->Pressed())
{
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Reset emulator
Model3->Reset();
@ -792,17 +806,46 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
Debugger->Reset();
#endif // SUPERMODEL_DEBUGGER
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
puts("Model 3 reset.");
}
else if (Inputs->uiPause->Pressed())
{
// Toggle emulator paused flag
paused = !paused;
if (paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
else
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
}
else if (Inputs->uiSaveState->Pressed())
{
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Save game state
SaveState(Model3);
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
}
else if (Inputs->uiChangeSlot->Pressed())
{
@ -813,6 +856,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
}
else if (Inputs->uiLoadState->Pressed())
{
if (!paused)
{
Model3->PauseThreads();
SetAudioEnabled(false);
}
// Load game state
LoadState(Model3);
@ -821,6 +870,12 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
if (Debugger != NULL)
Debugger->Reset();
#endif // SUPERMODEL_DEBUGGER
if (!paused)
{
Model3->ResumeThreads();
SetAudioEnabled(true);
}
}
else if (Inputs->uiMusicVolUp->Pressed())
{
@ -904,7 +959,7 @@ int Supermodel(const char *zipFile, CInputs *Inputs, CINIFile *CmdLine)
printf("Frame limiting: %s\n", g_Config.throttle?"On":"Off");
}
#ifdef SUPERMODEL_DEBUGGER
else if (Inputs->uiEnterDebugger->Pressed())
else if (Debugger != NULL && Inputs->uiEnterDebugger->Pressed())
{
// Break execution and enter debugger
Debugger->ForceBreak(true);

View file

@ -122,6 +122,11 @@ bool CCondVar::Signal()
return SDL_CondSignal((SDL_cond*)m_impl) == 0;
}
bool CCondVar::SignalAll()
{
return SDL_CondBroadcast((SDL_cond*)m_impl) == 0;
}
CMutex::CMutex(void *impl) : m_impl(impl)
{
//