Clean up multithreading code and offer it via command line options.

Signed-off-by: Andrea Odetti <mariofutire@gmail.com>
This commit is contained in:
Andrea Odetti 2020-11-13 11:50:42 +00:00
parent ef114f9e64
commit f13816262d
5 changed files with 114 additions and 99 deletions

View file

@ -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"))
{

View file

@ -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);

View file

@ -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, &current);
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)

View file

@ -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;

View file

@ -123,22 +123,33 @@ namespace
RiffFinishWriteFile();
}
int getFPS()
{
SDL_DisplayMode current;
const int should_be_zero = SDL_GetCurrentDisplayMode(0, &current);
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();