Эмулятор чипа-8: замедление тактовой частоты
Я планирую написать эмулятор NES. Но сначала, чтобы понять, как работает эмуляция, я напишу эмулятор Chip-8.
Эмулятор почти закончен. У меня есть некоторые ошибки в играх, но это будет исправлено в ближайшее время. Моя задача № 1 - синхронизировать эмулятор с тактовой частотой чипа-8. В интернете я часто читал, что общая тактовая частота должна быть ~ 540 Гц. Таймеры чипа должны быть отмечены с частотой 60 Гц.
Для синхронизации моего эмулятора с Chip-8 я написал следующую логику:
private void GameTick()
{
Stopwatch watch = new Stopwatch();
var instructionCount = 0;
_gameIsRunning = true;
while (_gameIsRunning)
{
watch.Restart();
EmulateCycle();
//Updates the internal timer at a 60hz frequenz
//540hz (game tick) divided by 9 equals 60hz (timer tick)
instructionCount++;
if(instructionCount == 9)
{
UpdateSoundAndDelay();
instructionCount = 0;
}
if (_readyToDraw)
{
DrawGraphics();
_readyToDraw = false;
}
SetKeys();
//Pause the game to get a virtual clock speed of ca. 540mhz
var elapsedMicroseconds = watch.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
while(elapsedMicroseconds < 1852)
{
elapsedMicroseconds = watch.ElapsedTicks / (Stopwatch.Frequency / (1000L * 1000L));
}
}
}
Для более подробной информации смотрите мое репо: https://github.com/Marcel-Hoffmann/Chip-8-Emulator
Как видите, для каждого цикла процессора я буду ждать 1852 микросекунды. Результат будет ~ 540 циклов в секунду, что равно 540 Гц. Но я не очень доволен этой логикой.
Есть у кого идея получше, как синхронизировать тактовую частоту?
1 ответ
Это типичный подход, имеющий много недостатков, в частности, излишнее использование ЦП и проблемы с планированием (ваше приложение будет восприниматься как 100% ЦП, так что другие приложения могут получить свои потоки до загрузки под нагрузкой).
В лучшем подходе вместо этого использовался бы режим сна - однако по умолчанию системный таймер не располагал близко к частоте, чтобы выдержать ожидание менее 2 мс. Поэтому, если вы хотите использовать сон, вам нужно изменить системный таймер. Это немного сложнее в старых Windows (это общесистемная настройка и оказывает заметное влияние на другие приложения и общее использование процессора), но даже в этом случае это лучше, чем "занятый цикл" - до тех пор, пока вы восстанавливаете систему настройки потом. В Windows 8 (и в некоторой степени 7 и Vista) таймер является асинхронным и больше не требует занятого цикла, поэтому намного проще иметь более высокое разрешение таймера.
API системного таймера не предоставляются.NET, поэтому вам нужно использовать P/Invokes (timeBeginPeriod
а также timeEndPeriod
для старого стиля API). Если это не доступно, вы всегда можете вернуться к своей занятой петле:)