Clean up multithreading code and offer it via command line options.
Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
parent
ef114f9e64
commit
f13816262d
5 changed files with 114 additions and 99 deletions
|
@ -13,23 +13,26 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & vers
|
|||
desc.add_options()
|
||||
("help,h", "Print this help message")
|
||||
("conf", "Save configuration on exit")
|
||||
("qt-ini", "Use Qt ini file (read only)");
|
||||
("multi-threaded,m", "Multi threaded")
|
||||
("loose-mutex,l", "Loose mutex")
|
||||
("timer-interval,i", po::value<int>()->default_value(16), "Timer interval in ms")
|
||||
("qt-ini,q", "Use Qt ini file (read only)");
|
||||
|
||||
po::options_description diskDesc("Disk");
|
||||
diskDesc.add_options()
|
||||
("d1,1", po::value<std::string>(), "Mount disk image in first drive")
|
||||
("d2,2", po::value<std::string>(), "Mount disk image in second drive")
|
||||
("d1,1", po::value<std::string>(), "Disk in 1st drive")
|
||||
("d2,2", po::value<std::string>(), "Disk in 2nd drive")
|
||||
("create,c", "Create missing disks");
|
||||
desc.add(diskDesc);
|
||||
|
||||
po::options_description snapshotDesc("Snapshot");
|
||||
snapshotDesc.add_options()
|
||||
("load-state,ls", po::value<std::string>(), "Load snapshot from file");
|
||||
("load-state,s", po::value<std::string>(), "Load snapshot from file");
|
||||
desc.add(snapshotDesc);
|
||||
|
||||
po::options_description memoryDesc("Memory");
|
||||
memoryDesc.add_options()
|
||||
("memclear,m", po::value<int>(), "Memory initialization pattern [0..7]");
|
||||
("memclear", po::value<int>(), "Memory initialization pattern [0..7]");
|
||||
desc.add(memoryDesc);
|
||||
|
||||
po::options_description emulatorDesc("Emulator");
|
||||
|
@ -54,6 +57,9 @@ bool getEmulatorOptions(int argc, const char * argv [], const std::string & vers
|
|||
|
||||
options.saveConfigurationOnExit = vm.count("conf");
|
||||
options.useQtIni = vm.count("qt-ini");
|
||||
options.multiThreaded = vm.count("multi-threaded");
|
||||
options.looseMutex = vm.count("loose-mutex");
|
||||
options.timerInterval = vm["timer-interval"].as<int>();
|
||||
|
||||
if (vm.count("d1"))
|
||||
{
|
||||
|
|
|
@ -25,6 +25,11 @@ struct EmulatorOptions
|
|||
bool useQtIni; // use Qt .ini file (read only)
|
||||
|
||||
bool run; // false if options include "-h"
|
||||
|
||||
bool multiThreaded;
|
||||
bool looseMutex; // whether SDL_UpdateTexture is mutex protected (from CPU)
|
||||
int timerInterval; // only when multithreaded
|
||||
|
||||
};
|
||||
|
||||
bool getEmulatorOptions(int argc, const char * argv [], const std::string & version, EmulatorOptions & options);
|
||||
|
|
|
@ -21,32 +21,6 @@
|
|||
namespace
|
||||
{
|
||||
|
||||
SDL_Rect refreshTexture(const std::shared_ptr<SDL_Texture> & tex)
|
||||
{
|
||||
uint8_t * data;
|
||||
int width;
|
||||
int height;
|
||||
int sx, sy;
|
||||
int sw, sh;
|
||||
|
||||
getScreenData(data, width, height, sx, sy, sw, sh);
|
||||
SDL_UpdateTexture(tex.get(), nullptr, data, width * 4);
|
||||
|
||||
SDL_Rect srect;
|
||||
srect.x = sx;
|
||||
srect.y = sy;
|
||||
srect.w = sw;
|
||||
srect.h = sh;
|
||||
|
||||
return srect;
|
||||
}
|
||||
|
||||
void renderScreen(const std::shared_ptr<SDL_Renderer> & ren, const std::shared_ptr<SDL_Texture> & tex, const SDL_Rect & srect)
|
||||
{
|
||||
SDL_RenderCopyEx(ren.get(), tex.get(), &srect, nullptr, 0.0, nullptr, SDL_FLIP_VERTICAL);
|
||||
SDL_RenderPresent(ren.get());
|
||||
}
|
||||
|
||||
void cycleVideoType(const std::shared_ptr<SDL_Window> & win)
|
||||
{
|
||||
g_eVideoType++;
|
||||
|
@ -76,20 +50,6 @@ namespace
|
|||
VideoRedrawScreen();
|
||||
}
|
||||
|
||||
int getFPS()
|
||||
{
|
||||
SDL_DisplayMode current;
|
||||
|
||||
int should_be_zero = SDL_GetCurrentDisplayMode(0, ¤t);
|
||||
|
||||
if (should_be_zero)
|
||||
{
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
return current.refresh_rate;
|
||||
}
|
||||
|
||||
void processAppleKey(const SDL_KeyboardEvent & key)
|
||||
{
|
||||
// using keycode (or scan code) one takes a physical view of the keyboard
|
||||
|
@ -179,47 +139,51 @@ Emulator::Emulator(
|
|||
: myWindow(window)
|
||||
, myRenderer(renderer)
|
||||
, myTexture(texture)
|
||||
, myFPS(getFPS())
|
||||
, myMultiplier(1)
|
||||
, myFullscreen(false)
|
||||
, myExtraCycles(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Emulator::executeOneFrame()
|
||||
void Emulator::executeCycles(const int targetCycles)
|
||||
{
|
||||
const bool bVideoUpdate = true;
|
||||
const int nExecutionPeriodUsec = 1000; // 1.0ms
|
||||
const double fUsecPerSec = 1.e6;
|
||||
const double fExecutionPeriodClks = int(g_fCurrentCLK6502 * (nExecutionPeriodUsec / fUsecPerSec));
|
||||
|
||||
int totalCyclesExecuted = 0;
|
||||
const DWORD uCyclesToExecute = int(g_fCurrentCLK6502 / myFPS) + myExtraCycles;
|
||||
const UINT dwClksPerFrame = NTSC_GetCyclesPerFrame();
|
||||
|
||||
while (totalCyclesExecuted < uCyclesToExecute)
|
||||
{
|
||||
// go in small steps of 1ms
|
||||
const DWORD uActualCyclesExecuted = CpuExecute(fExecutionPeriodClks, bVideoUpdate);
|
||||
totalCyclesExecuted += uActualCyclesExecuted;
|
||||
g_dwCyclesThisFrame = (g_dwCyclesThisFrame + uActualCyclesExecuted) % dwClksPerFrame;
|
||||
const DWORD executedCycles = CpuExecute(targetCycles + myExtraCycles, bVideoUpdate);
|
||||
|
||||
GetCardMgr().GetDisk2CardMgr().UpdateDriveState(uActualCyclesExecuted);
|
||||
MB_PeriodicUpdate(uActualCyclesExecuted);
|
||||
SpkrUpdate(uActualCyclesExecuted);
|
||||
}
|
||||
g_dwCyclesThisFrame = (g_dwCyclesThisFrame + executedCycles) % dwClksPerFrame;
|
||||
GetCardMgr().GetDisk2CardMgr().UpdateDriveState(executedCycles);
|
||||
MB_PeriodicUpdate(executedCycles);
|
||||
SpkrUpdate(executedCycles);
|
||||
|
||||
myExtraCycles = uCyclesToExecute - totalCyclesExecuted;
|
||||
myExtraCycles = targetCycles - executedCycles;
|
||||
}
|
||||
|
||||
SDL_Rect Emulator::updateTexture()
|
||||
{
|
||||
return refreshTexture(myTexture);
|
||||
uint8_t * data;
|
||||
int width;
|
||||
int height;
|
||||
int sx, sy;
|
||||
int sw, sh;
|
||||
|
||||
getScreenData(data, width, height, sx, sy, sw, sh);
|
||||
SDL_UpdateTexture(myTexture.get(), nullptr, data, width * 4);
|
||||
|
||||
SDL_Rect srect;
|
||||
srect.x = sx;
|
||||
srect.y = sy;
|
||||
srect.w = sw;
|
||||
srect.h = sh;
|
||||
|
||||
return srect;
|
||||
}
|
||||
|
||||
void Emulator::refreshVideo(const SDL_Rect & rect)
|
||||
{
|
||||
renderScreen(myRenderer, myTexture, rect);
|
||||
SDL_RenderCopyEx(myRenderer.get(), myTexture.get(), &rect, nullptr, 0.0, nullptr, SDL_FLIP_VERTICAL);
|
||||
SDL_RenderPresent(myRenderer.get());
|
||||
}
|
||||
|
||||
void Emulator::processEvents(bool & quit)
|
||||
|
|
|
@ -12,7 +12,7 @@ public:
|
|||
const std::shared_ptr<SDL_Texture> & texture
|
||||
);
|
||||
|
||||
void executeOneFrame();
|
||||
void executeCycles(const int cycles);
|
||||
void refreshVideo();
|
||||
SDL_Rect updateTexture();
|
||||
void refreshVideo(const SDL_Rect & rect);
|
||||
|
@ -27,7 +27,6 @@ private:
|
|||
const std::shared_ptr<SDL_Window> myWindow;
|
||||
const std::shared_ptr<SDL_Renderer> myRenderer;
|
||||
const std::shared_ptr<SDL_Texture> myTexture;
|
||||
const int myFPS;
|
||||
|
||||
int myMultiplier;
|
||||
bool myFullscreen;
|
||||
|
|
|
@ -123,22 +123,33 @@ namespace
|
|||
RiffFinishWriteFile();
|
||||
}
|
||||
|
||||
int getFPS()
|
||||
{
|
||||
SDL_DisplayMode current;
|
||||
|
||||
const int should_be_zero = SDL_GetCurrentDisplayMode(0, ¤t);
|
||||
|
||||
if (should_be_zero)
|
||||
{
|
||||
throw std::runtime_error(SDL_GetError());
|
||||
}
|
||||
|
||||
return current.refresh_rate;
|
||||
}
|
||||
|
||||
struct Data
|
||||
{
|
||||
Emulator * emulator;
|
||||
SDL_mutex * mutex;
|
||||
bool quit;
|
||||
};
|
||||
|
||||
Uint32 my_callbackfunc(Uint32 interval, void *param)
|
||||
Uint32 emulator_callback(Uint32 interval, void *param)
|
||||
{
|
||||
Data * data = static_cast<Data *>(param);
|
||||
if (!data->quit)
|
||||
{
|
||||
SDL_LockMutex(data->mutex);
|
||||
data->emulator->executeOneFrame();
|
||||
SDL_UnlockMutex(data->mutex);
|
||||
}
|
||||
SDL_LockMutex(data->mutex);
|
||||
const int uCyclesToExecute = int(g_fCurrentCLK6502 * interval * 0.001);
|
||||
data->emulator->executeCycles(uCyclesToExecute);
|
||||
SDL_UnlockMutex(data->mutex);
|
||||
return interval;
|
||||
}
|
||||
|
||||
|
@ -171,7 +182,6 @@ void run_sdl(int argc, const char * argv [])
|
|||
if (!run)
|
||||
return;
|
||||
|
||||
|
||||
if (options.log)
|
||||
{
|
||||
LogInit();
|
||||
|
@ -230,30 +240,61 @@ void run_sdl(int argc, const char * argv [])
|
|||
|
||||
Emulator emulator(win, ren, tex);
|
||||
|
||||
std::shared_ptr<SDL_mutex> mutex(SDL_CreateMutex(), SDL_DestroyMutex);
|
||||
|
||||
Data data;
|
||||
data.quit = false;
|
||||
data.mutex = mutex.get();
|
||||
data.emulator = &emulator;
|
||||
|
||||
SDL_TimerID timer = SDL_AddTimer(1000 / 60, my_callbackfunc, &data);
|
||||
|
||||
do
|
||||
if (options.multiThreaded)
|
||||
{
|
||||
SDL_LockMutex(data.mutex);
|
||||
SDirectSound::writeAudio();
|
||||
emulator.processEvents(data.quit);
|
||||
SDL_UnlockMutex(data.mutex);
|
||||
const SDL_Rect rect = emulator.updateTexture();
|
||||
emulator.refreshVideo(rect);
|
||||
} while (!data.quit);
|
||||
std::shared_ptr<SDL_mutex> mutex(SDL_CreateMutex(), SDL_DestroyMutex);
|
||||
|
||||
SDL_RemoveTimer(timer);
|
||||
// if the following enough to make sure the timer has finished
|
||||
// and wont be called again?
|
||||
SDL_LockMutex(data.mutex);
|
||||
SDL_UnlockMutex(data.mutex);
|
||||
Data data;
|
||||
data.mutex = mutex.get();
|
||||
data.emulator = &emulator;
|
||||
|
||||
const SDL_TimerID timer = SDL_AddTimer(options.timerInterval, emulator_callback, &data);
|
||||
|
||||
bool quit = false;
|
||||
do
|
||||
{
|
||||
SDL_LockMutex(data.mutex);
|
||||
SDirectSound::writeAudio();
|
||||
emulator.processEvents(quit);
|
||||
if (options.looseMutex)
|
||||
{
|
||||
// loose mutex
|
||||
// unlock early and let CPU run again in the timer callback
|
||||
SDL_UnlockMutex(data.mutex);
|
||||
// but the texture will be updated concurrently with the CPU updating the video buffer
|
||||
// pixels are not atomic, so a pixel error could happen (if pixel changes while being read)
|
||||
// on the positive side this will release pressure from CPU and allow for more parallelism
|
||||
}
|
||||
const SDL_Rect rect = emulator.updateTexture();
|
||||
if (!options.looseMutex)
|
||||
{
|
||||
// safe mutex, only unlock after texture has been updated
|
||||
// this will stop the CPU for longer
|
||||
SDL_UnlockMutex(data.mutex);
|
||||
}
|
||||
emulator.refreshVideo(rect);
|
||||
} while (!quit);
|
||||
|
||||
SDL_RemoveTimer(timer);
|
||||
// if the following enough to make sure the timer has finished
|
||||
// and wont be called again?
|
||||
SDL_LockMutex(data.mutex);
|
||||
SDL_UnlockMutex(data.mutex);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int fps = getFPS();
|
||||
bool quit = false;
|
||||
const int uCyclesToExecute = int(g_fCurrentCLK6502 / fps);
|
||||
do
|
||||
{
|
||||
SDirectSound::writeAudio();
|
||||
emulator.processEvents(quit);
|
||||
emulator.executeCycles(uCyclesToExecute);
|
||||
const SDL_Rect rect = emulator.updateTexture();
|
||||
emulator.refreshVideo(rect);
|
||||
} while (!quit);
|
||||
}
|
||||
|
||||
SDirectSound::stop();
|
||||
stopEmulator();
|
||||
|
|
Loading…
Add table
Reference in a new issue