Эмулятор чипа-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). Если это не доступно, вы всегда можете вернуться к своей занятой петле:)

Другие вопросы по тегам