Sanitize and match emulation timing

-> no more perceivable audio latency
-> fewer underruns
This commit is contained in:
Christian Speckner 2018-05-05 00:47:48 +02:00
parent 9079d77de0
commit d2c930886b
20 changed files with 240 additions and 73 deletions

View File

@ -1,22 +1,13 @@
# Debugging
* Log and check how queue fills
# Bugs
* Fix timeslice size
# Refactoring
* Move timing related constants and logic to separate class
# Verify # Verify
* Verify that the base unit for chrono is seconds
* Verify that FPS are still measured correctly * Verify that FPS are still measured correctly
# Missing features # Missing features
* Reimplement target FPS mode * Reimplement target FPS mode
* Implement Lanzcos resampling * Implement Lanzcos resampling
* Fixup OpenGL sync * Fixup OpenGL sync, ensure that FB only rerenders after a frame has been generated
# Cleanup
* Document EmulationTiming

View File

@ -21,7 +21,7 @@ using std::mutex;
using std::lock_guard; using std::lock_guard;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate) AudioQueue::AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate)
: myFragmentSize(fragmentSize), : myFragmentSize(fragmentSize),
myIsStereo(isStereo), myIsStereo(isStereo),
mySampleRate(sampleRate), mySampleRate(sampleRate),
@ -34,7 +34,7 @@ AudioQueue::AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt1
myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)]; myFragmentBuffer = new Int16[myFragmentSize * sampleSize * (capacity + 2)];
for (uInt8 i = 0; i < capacity; i++) for (uInt32 i = 0; i < capacity; i++)
myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize; myFragmentQueue[i] = myAllFragments[i] = myFragmentBuffer + i * sampleSize * myFragmentSize;
myAllFragments[capacity] = myFirstFragmentForEnqueue = myAllFragments[capacity] = myFirstFragmentForEnqueue =
@ -51,13 +51,13 @@ AudioQueue::~AudioQueue()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 AudioQueue::capacity() const uInt32 AudioQueue::capacity() const
{ {
return myFragmentQueue.size(); return myFragmentQueue.size();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 AudioQueue::size() uInt32 AudioQueue::size()
{ {
lock_guard<mutex> guard(myMutex); lock_guard<mutex> guard(myMutex);

View File

@ -45,7 +45,7 @@ class AudioQueue
@param isStereo Whether samples are stereo or mono. @param isStereo Whether samples are stereo or mono.
@param sampleRate The sample rate. This is not used, but can be queried. @param sampleRate The sample rate. This is not used, but can be queried.
*/ */
AudioQueue(uInt32 fragmentSize, uInt8 capacity, bool isStereo, uInt16 sampleRate); AudioQueue(uInt32 fragmentSize, uInt32 capacity, bool isStereo, uInt16 sampleRate);
/** /**
We need a destructor to deallocate the individual fragment buffers. We need a destructor to deallocate the individual fragment buffers.
@ -55,12 +55,12 @@ class AudioQueue
/** /**
Capacity getter. Capacity getter.
*/ */
uInt8 capacity() const; uInt32 capacity() const;
/** /**
Size getter. Size getter.
*/ */
uInt8 size(); uInt32 size();
/** /**
Stereo / mono getter. Stereo / mono getter.
@ -122,10 +122,10 @@ class AudioQueue
Int16* myFragmentBuffer; Int16* myFragmentBuffer;
// The nubmer if queued fragments // The nubmer if queued fragments
uInt8 mySize; uInt32 mySize;
// The next fragment. // The next fragment.
uInt8 myNextFragment; uInt32 myNextFragment;
// We need a mutex for thread safety. // We need a mutex for thread safety.
std::mutex myMutex; std::mutex myMutex;

View File

@ -21,6 +21,8 @@
#include "bspf.hxx" #include "bspf.hxx"
#include "Sound.hxx" #include "Sound.hxx"
#include "OSystem.hxx" #include "OSystem.hxx"
#include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
/** /**
This class implements a Null sound object, where-by sound generation This class implements a Null sound object, where-by sound generation
@ -57,7 +59,7 @@ class SoundNull : public Sound
Initializes the sound device. This must be called before any Initializes the sound device. This must be called before any
calls are made to derived methods. calls are made to derived methods.
*/ */
void open() override { } void open(shared_ptr<AudioQueue>, EmulationTiming*) override { }
/** /**
Should be called to close the sound device. Once called the sound Should be called to close the sound device. Once called the sound

View File

@ -29,6 +29,7 @@
#include "Console.hxx" #include "Console.hxx"
#include "SoundSDL2.hxx" #include "SoundSDL2.hxx"
#include "AudioQueue.hxx" #include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
namespace { namespace {
inline Int16 applyVolume(Int16 sample, Int32 volumeFactor) inline Int16 applyVolume(Int16 sample, Int32 volumeFactor)
@ -116,8 +117,10 @@ void SoundSDL2::setEnabled(bool state)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue) void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulationTiming)
{ {
this->emulationTiming = emulationTiming;
myOSystem.logMessage("SoundSDL2::open started ...", 2); myOSystem.logMessage("SoundSDL2::open started ...", 2);
mute(true); mute(true);
@ -241,7 +244,7 @@ uInt32 SoundSDL2::getSampleRate() const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::processFragment(Int16* stream, uInt32 length) void SoundSDL2::processFragment(Int16* stream, uInt32 length)
{ {
if (myUnderrun && myAudioQueue->size() > 0) { if (myUnderrun && myAudioQueue->size() > emulationTiming->prebufferFragmentCount()) {
myUnderrun = false; myUnderrun = false;
myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment); myCurrentFragment = myAudioQueue->dequeue(myCurrentFragment);
myFragmentIndex = 0; myFragmentIndex = 0;
@ -273,8 +276,10 @@ void SoundSDL2::processFragment(Int16* stream, uInt32 length)
Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment); Int16* nextFragment = myAudioQueue->dequeue(myCurrentFragment);
if (nextFragment) if (nextFragment)
myCurrentFragment = nextFragment; myCurrentFragment = nextFragment;
else else {
myUnderrun = true; myUnderrun = true;
(cout << "audio underrun!\n").flush();
}
} }
if (isStereo) { if (isStereo) {

View File

@ -22,6 +22,7 @@
class OSystem; class OSystem;
class AudioQueue; class AudioQueue;
class EmulationTiming;
#include "SDL_lib.hxx" #include "SDL_lib.hxx"
@ -59,7 +60,7 @@ class SoundSDL2 : public Sound
Initializes the sound device. This must be called before any Initializes the sound device. This must be called before any
calls are made to derived methods. calls are made to derived methods.
*/ */
void open(shared_ptr<AudioQueue> audioQueue) override; void open(shared_ptr<AudioQueue> audioQueue, EmulationTiming* emulationTiming) override;
/** /**
Should be called to close the sound device. Once called the sound Should be called to close the sound device. Once called the sound
@ -124,6 +125,8 @@ class SoundSDL2 : public Sound
shared_ptr<AudioQueue> myAudioQueue; shared_ptr<AudioQueue> myAudioQueue;
EmulationTiming* emulationTiming;
Int16* myCurrentFragment; Int16* myCurrentFragment;
uInt32 myTimeIndex; uInt32 myTimeIndex;
uInt32 myFragmentIndex; uInt32 myFragmentIndex;

View File

@ -513,7 +513,7 @@ void Debugger::nextFrame(int frames)
unlockSystem(); unlockSystem();
while(frames) while(frames)
{ {
myOSystem.console().tia().update(); myOSystem.console().tia().update(myOSystem.console().emulationTiming().maxCyclesPerTimeslice());
--frames; --frames;
} }
lockSystem(); lockSystem();

View File

@ -72,7 +72,6 @@
namespace { namespace {
constexpr uInt8 YSTART_EXTRA = 2; constexpr uInt8 YSTART_EXTRA = 2;
constexpr uInt8 AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT = 1;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -358,6 +357,7 @@ void Console::toggleFormat(int direction)
setTIAProperties(); setTIAProperties();
myTIA->frameReset(); myTIA->frameReset();
initializeVideo(); // takes care of refreshing the screen initializeVideo(); // takes care of refreshing the screen
initializeAudio(); // ensure that audio synthesis is set up to match emulation speed
myOSystem.frameBuffer().showMessage(message); myOSystem.frameBuffer().showMessage(message);
@ -556,7 +556,7 @@ void Console::initializeAudio()
createAudioQueue(); createAudioQueue();
myTIA->setAudioQueue(myAudioQueue); myTIA->setAudioQueue(myAudioQueue);
myOSystem.sound().open(myAudioQueue); myOSystem.sound().open(myAudioQueue, &myEmulationTiming);
} }
/* Original frying research and code by Fred Quimby. /* Original frying research and code by Fred Quimby.
@ -699,37 +699,18 @@ void Console::setTIAProperties()
myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart); myTIA->setYStart(ystart != 0 ? ystart : myAutodetectedYstart);
myTIA->setHeight(height); myTIA->setHeight(height);
myEmulationTiming.updateFrameLayout(myTIA->frameLayout());
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Console::createAudioQueue() void Console::createAudioQueue()
{ {
uInt32 fragmentSize, sampleRate;
switch (myConsoleTiming) {
case ConsoleTiming::ntsc:
fragmentSize = 262 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT;
sampleRate = 2 * 262 * 60;
break;
case ConsoleTiming::pal:
case ConsoleTiming::secam:
fragmentSize = 312 * AUDIO_QUEUE_HALF_FRAMES_PER_FRAGMENT;
sampleRate = 2 * 312 * 50;
break;
default:
throw runtime_error("invalid console timing");
}
uInt32 queueSize =
(2 * myOSystem.sound().getFragmentSize() * sampleRate) / (fragmentSize * myOSystem.sound().getSampleRate());
myAudioQueue = make_shared<AudioQueue>( myAudioQueue = make_shared<AudioQueue>(
fragmentSize, myEmulationTiming.audioFragmentSize(),
queueSize > 0 ? queueSize : 1, myEmulationTiming.audioQueueCapacity(myOSystem.sound().getSampleRate(), myOSystem.sound().getFragmentSize()),
myProperties.get(Cartridge_Sound) == "STEREO", myProperties.get(Cartridge_Sound) == "STEREO",
sampleRate myEmulationTiming.audioSampleRate()
); );
} }

View File

@ -37,6 +37,7 @@ class AudioQueue;
#include "Serializable.hxx" #include "Serializable.hxx"
#include "EventHandlerConstants.hxx" #include "EventHandlerConstants.hxx"
#include "NTSCFilter.hxx" #include "NTSCFilter.hxx"
#include "EmulationTiming.hxx"
#include "frame-manager/AbstractFrameManager.hxx" #include "frame-manager/AbstractFrameManager.hxx"
/** /**
@ -190,6 +191,11 @@ class Console : public Serializable
*/ */
void stateChanged(EventHandlerState state); void stateChanged(EventHandlerState state);
/**
Retrieve emulation timing provider.
*/
EmulationTiming& emulationTiming() { return myEmulationTiming; }
public: public:
/** /**
Toggle between NTSC/PAL/SECAM (and variants) display format. Toggle between NTSC/PAL/SECAM (and variants) display format.
@ -416,7 +422,9 @@ class Console : public Serializable
// Contains timing information for this console // Contains timing information for this console
ConsoleTiming myConsoleTiming; ConsoleTiming myConsoleTiming;
uInt32 myFramerate; // Emulation timing provider. This ties together the timing of the core emulation loop
// and the audio synthesis parameters
EmulationTiming myEmulationTiming;
// Table of RGB values for NTSC, PAL and SECAM // Table of RGB values for NTSC, PAL and SECAM
static uInt32 ourNTSCPalette[256]; static uInt32 ourNTSCPalette[256];

View File

@ -0,0 +1,103 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include "EmulationTiming.hxx"
namespace {
constexpr uInt32 AUDIO_HALF_FRAMES_PER_FRAGMENT = 1;
constexpr uInt32 QUEUE_CAPACITY_SAFETY_FACTOR = 2;
constexpr uInt32 PREBUFFER_FRAGMENT_COUNT = 4;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
EmulationTiming::EmulationTiming(FrameLayout frameLayout) : frameLayout(frameLayout) {}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void EmulationTiming::updateFrameLayout(FrameLayout frameLayout) {
this->frameLayout = frameLayout;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::maxCyclesPerTimeslice() const {
return (3 * cyclesPerFrame()) / 2;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::minCyclesPerTimeslice() const {
return cyclesPerFrame() / 2;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::linesPerFrame() const {
switch (frameLayout) {
case FrameLayout::ntsc:
return 262;
case FrameLayout::pal:
return 312;
default:
throw runtime_error("invalid frame layout");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerFrame() const {
return 76 * linesPerFrame();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::framesPerSecond() const {
switch (frameLayout) {
case FrameLayout::ntsc:
return 60;
case FrameLayout::pal:
return 50;
default:
throw runtime_error("invalid frame layout");
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::cyclesPerSecond() const {
return cyclesPerFrame() * framesPerSecond();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioFragmentSize() const {
return AUDIO_HALF_FRAMES_PER_FRAGMENT * linesPerFrame();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioSampleRate() const {
return 2 * linesPerFrame() * framesPerSecond();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const {
uInt32 capacity = (playbackFragmentSize * audioSampleRate()) / (audioFragmentSize() * playbackRate) + 1;
uInt32 minCapacity = (maxCyclesPerTimeslice() * audioSampleRate()) / (audioFragmentSize() * cyclesPerSecond()) + 1;
return std::max(prebufferFragmentCount() + 1, QUEUE_CAPACITY_SAFETY_FACTOR * std::max(capacity, minCapacity));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 EmulationTiming::prebufferFragmentCount() const {
return PREBUFFER_FRAGMENT_COUNT;
}

View File

@ -0,0 +1,57 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2018 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef EMULATION_TIMING_HXX
#define EMULATION_TIMING_HXX
#include "bspf.hxx"
#include "FrameLayout.hxx"
class EmulationTiming {
public:
EmulationTiming(FrameLayout frameLayout = FrameLayout::ntsc);
void updateFrameLayout(FrameLayout frameLayout);
uInt32 maxCyclesPerTimeslice() const;
uInt32 minCyclesPerTimeslice() const;
uInt32 linesPerFrame() const;
uInt32 cyclesPerFrame() const;
uInt32 framesPerSecond() const;
uInt32 cyclesPerSecond() const;
uInt32 audioFragmentSize() const;
uInt32 audioSampleRate() const;
uInt32 audioQueueCapacity(uInt32 playbackRate, uInt32 playbackFragmentSize) const;
uInt32 prebufferFragmentCount() const;
private:
FrameLayout frameLayout;
};
#endif // EMULATION_TIMING_HXX

View File

@ -258,7 +258,7 @@ FBInitStatus FrameBuffer::createDisplay(const string& title,
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int64 FrameBuffer::update() Int64 FrameBuffer::update(uInt32 maxCycles)
{ {
// Determine which mode we are in (from the EventHandler) // Determine which mode we are in (from the EventHandler)
// Take care of S_EMULATE mode here, otherwise let the GUI // Take care of S_EMULATE mode here, otherwise let the GUI
@ -274,7 +274,7 @@ Int64 FrameBuffer::update()
// Run the console for one frame // Run the console for one frame
// Note that the debugger can cause a breakpoint to occur, which changes // Note that the debugger can cause a breakpoint to occur, which changes
// the EventHandler state 'behind our back' - we need to check for that // the EventHandler state 'behind our back' - we need to check for that
cycles = myOSystem.console().tia().update(); cycles = myOSystem.console().tia().update(maxCycles);
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break; if(myOSystem.eventHandler().state() != EventHandlerState::EMULATION) break;
#endif #endif

View File

@ -116,7 +116,7 @@ class FrameBuffer
drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles drawing the TIA, any pending menus, etc. Returns the numbers of CPU cycles
spent during emulation, or -1 if not applicable. spent during emulation, or -1 if not applicable.
*/ */
Int64 update(); Int64 update(uInt32 maxCycles = 50000);
/** /**
Shows a message onscreen. Shows a message onscreen.

View File

@ -229,7 +229,7 @@ bool M6502::execute(uInt32 number)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
inline bool M6502::_execute(uInt32 number) inline bool M6502::_execute(uInt32 cycles)
{ {
// Clear all of the execution status bits except for the fatal error bit // Clear all of the execution status bits except for the fatal error bit
myExecutionStatus &= FatalErrorBit; myExecutionStatus &= FatalErrorBit;
@ -239,10 +239,12 @@ inline bool M6502::_execute(uInt32 number)
M6532& riot = mySystem->m6532(); M6532& riot = mySystem->m6532();
#endif #endif
uInt32 currentCycles = 0;
// Loop until execution is stopped or a fatal error occurs // Loop until execution is stopped or a fatal error occurs
for(;;) for(;;)
{ {
for(; !myExecutionStatus && (number != 0); --number) for(; !myExecutionStatus && (currentCycles < cycles * SYSTEM_CYCLES_PER_CPU); currentCycles += SYSTEM_CYCLES_PER_CPU)
{ {
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag) if(myJustHitReadTrapFlag || myJustHitWriteTrapFlag)
@ -329,7 +331,7 @@ inline bool M6502::_execute(uInt32 number)
} }
// See if we've executed the specified number of instructions // See if we've executed the specified number of instructions
if(number == 0) if (currentCycles >= cycles * SYSTEM_CYCLES_PER_CPU)
{ {
// Yes, so answer that everything finished fine // Yes, so answer that everything finished fine
return true; return true;

View File

@ -110,10 +110,12 @@ class M6502 : public Serializable
is executed, someone stops execution, or an error occurs. Answers is executed, someone stops execution, or an error occurs. Answers
true iff execution stops normally. true iff execution stops normally.
@param number Indicates the number of instructions to execute @param number Indicates the number of cycles to execute. Not that the actual
granularity of the CPU is instructions, so this is only accurate up to
a couple of cycles
@return true iff execution stops normally @return true iff execution stops normally
*/ */
bool execute(uInt32 number); bool execute(uInt32 cycles);
/** /**
Tell the processor to stop executing instructions. Invoking this Tell the processor to stop executing instructions. Invoking this
@ -318,7 +320,7 @@ class M6502 : public Serializable
This is the actual dispatch function that does the grunt work. M6502::execute This is the actual dispatch function that does the grunt work. M6502::execute
wraps it and makes sure that any pending halt is processed before returning. wraps it and makes sure that any pending halt is processed before returning.
*/ */
bool _execute(uInt32 number); bool _execute(uInt32 cycles);
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
/** /**

View File

@ -646,10 +646,21 @@ void OSystem::mainLoop()
myEventHandler->poll(getTicks()); myEventHandler->poll(getTicks());
if(myQuitLoop) break; // Exit if the user wants to quit if(myQuitLoop) break; // Exit if the user wants to quit
Int64 cycles = myFrameBuffer->update(); Int64 totalCycles = 0;
const Int64 minCycles = myConsole ? myConsole->emulationTiming().minCyclesPerTimeslice() : 50000;
const Int64 maxCycles = myConsole ? myConsole->emulationTiming().maxCyclesPerTimeslice() : 0;
const uInt32 cyclesPerSecond = myConsole ? myConsole->emulationTiming().cyclesPerSecond() : 1;
do {
Int64 cycles = myFrameBuffer->update(totalCycles > 0 ? minCycles - totalCycles : maxCycles);
if (cycles < 0) break;
totalCycles += cycles;
} while (totalCycles < minCycles);
duration<double> timeslice ( duration<double> timeslice (
(cycles >= 0) ? (totalCycles > 0) ?
static_cast<double>(cycles) / static_cast<double>(76 * ((myConsole->timing() == ConsoleTiming::ntsc) ? (262 * 60) : (312 * 50))) : static_cast<double>(totalCycles) / static_cast<double>(cyclesPerSecond) :
1. / 30. 1. / 30.
); );
@ -659,7 +670,7 @@ void OSystem::mainLoop()
if (duration_cast<duration<double>>(now - virtualTime).count() > 0) if (duration_cast<duration<double>>(now - virtualTime).count() > 0)
virtualTime = now; virtualTime = now;
else if (virtualTime > now) { else if (virtualTime > now) {
if (busyWait && cycles >= 0) { if (busyWait && totalCycles > 0) {
while (high_resolution_clock::now() < virtualTime); while (high_resolution_clock::now() < virtualTime);
} }
else std::this_thread::sleep_until(virtualTime); else std::this_thread::sleep_until(virtualTime);

View File

@ -20,6 +20,7 @@
class OSystem; class OSystem;
class AudioQueue; class AudioQueue;
class EmulationTiming;
#include "bspf.hxx" #include "bspf.hxx"
@ -51,7 +52,7 @@ class Sound
Start the sound system, initializing it if necessary. This must be Start the sound system, initializing it if necessary. This must be
called before any calls are made to derived methods. called before any calls are made to derived methods.
*/ */
virtual void open(shared_ptr<AudioQueue> audioQueue) = 0; virtual void open(shared_ptr<AudioQueue>, EmulationTiming*) = 0;
/** /**
Should be called to stop the sound system. Once called the sound Should be called to stop the sound system. Once called the sound

View File

@ -53,6 +53,7 @@ MODULE_OBJS := \
src/emucore/Control.o \ src/emucore/Control.o \
src/emucore/Driving.o \ src/emucore/Driving.o \
src/emucore/EventHandler.o \ src/emucore/EventHandler.o \
src/emucore/EmulationTiming.o \
src/emucore/FrameBuffer.o \ src/emucore/FrameBuffer.o \
src/emucore/FBSurface.o \ src/emucore/FBSurface.o \
src/emucore/FSNode.o \ src/emucore/FSNode.o \

View File

@ -805,11 +805,11 @@ bool TIA::loadDisplay(Serializer& in)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt64 TIA::update() uInt64 TIA::update(uInt32 maxCycles)
{ {
uInt64 timestampOld = myTimestamp; uInt64 timestampOld = myTimestamp;
mySystem->m6502().execute(25000); mySystem->m6502().execute(maxCycles);
updateEmulation(); updateEmulation();
return (myTimestamp - timestampOld) / 3; return (myTimestamp - timestampOld) / 3;

View File

@ -199,7 +199,7 @@ class TIA : public Device
desired frame rate to update the TIA. Invoking this method will update desired frame rate to update the TIA. Invoking this method will update
the graphics buffer and generate the corresponding audio samples. the graphics buffer and generate the corresponding audio samples.
*/ */
uInt64 update(); uInt64 update(uInt32 maxCycles = 50000);
/** /**
Returns a pointer to the internal frame buffer. Returns a pointer to the internal frame buffer.