Обработка игрового цикла с помощью таймеров ОС - PullRequest
1 голос
/ 06 апреля 2011

Простой игровой цикл работает примерно так:

MSG msg;
while (running){
    if (PeekMessage(&msg, hWnd, 0, 0, PM_REMOVE)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    else
        try{
            onIdle();
        }
        catch(std::exception& e){
            onError(e.what());
            close();
        }
}

(взято из этого вопроса )

Для примера я использую Windows в качестве примера, это может быть любая платформа. Поправьте меня, если я ошибаюсь, но такой цикл будет использовать 100% процессора / ядра (использует 50% одного ядра на моем компьютере), так как он всегда проверяет состояние переменной running.

Мой вопрос заключается в том, было бы лучше (с точки зрения производительности) реализовать игровой цикл, используя функции таймера ОС (в примере Windows), устанавливая требуемый интервал в соответствии с желаемым числом тактов игровой логики, требуемой в соответствии с второй? Я спрашиваю об этом, потому что я предполагаю, что функции таймера используют прерывания RTC процессора.

Ответы [ 4 ]

3 голосов
/ 06 апреля 2011

Как правило, игра будет продолжать рисовать новые кадры все время, даже когда пользователь ничего не делает.Обычно это происходит там, где у вас есть вызов onIdle ().Если ваша игра обновляет окно / экран только тогда, когда пользователь нажимает кнопку или что-то подобное, или время от времени между ними, то MSGWaitForMultipleObjects является хорошим вариантом.

Однако в игре с непрерывной анимацией вы обычно вообще не хочет блокировать или спать в потоке рендеринга , если вы можете помочь, вместо этого вы хотите рендериться на максимальной скорости и полагаться на вертикальную синхронизацию в качестве газа.Причина в том, что синхронизация, блокировка и спящий режим в лучшем случае неточны, а в худшем - ненадежны, и это вполне возможно добавит мешающие артефакты к вашей анимации.графическому API (скорее всего, OpenGL, поскольку вы сказали «любая платформа») как можно быстрее, подайте сигнал рабочему потоку, чтобы он начал выполнять игровую логику, физику и т. д., а затем заблокируйте SwapBuffers.

Все таймерыТайм-ауты и время ожидания ограничены разрешением планировщика, которое в Windows составляет 15 мс (можно установить 1 мс с помощью timeBeginPeriod).При 60 кадрах в секунду кадр составляет 16,666 мс, поэтому блокировка на 15 мс катастрофична, но даже 1 мс - все еще значительное время.Вы ничего не можете сделать, чтобы получить лучшее разрешение (это значительно лучше в Linux).

Sleep, или любая функция блокировки, которая имеет тайм-аут, гарантирует, что ваш процесс спит по крайней мере столько, сколько вы просили (на самом деле, в системах Posix, оно может спать меньше , если произошло прерывание).Это не дает вам гарантии того, что ваш поток будет запущен, как только истечет время, или каких-либо других гарантий.
Sleep (0) под Windows еще хуже.В документации сказано «поток освободит оставшуюся часть своего временного интервала, но останется готовым. Обратите внимание, что готовый поток не гарантированно запустится немедленно» .Реальность такова, что в большинстве случаев работает нормально, блокируя где-то от «совсем нет» до 5–10 мс, но в некоторых случаях я видел блок Sleep (0) и в течение 100 мс, что является неприятным случаем, когда это происходит.

Использование QueryPerformanceCounter вызывает проблемы - просто не делайте этого .В некоторых системах (Windows 7 с недавним процессором) он будет работать просто отлично, поскольку использует надежный HPET, но во многих других системах вы увидите все виды странных эффектов «путешествия во времени» или «обратного времени», которые никто не может объяснить илиотладки.Это потому, что в этих системах результат QPC считывает счетчик TSC, который зависит от частоты процессора.Частота ЦП не является постоянной, и значения TSC в многоядерных / многопроцессорных системах не обязательно должны быть согласованными.

Тогда возникает проблема синхронизации.Два отдельных таймера, даже если тиканье на одной частоте, обязательно станут несинхронизированными (потому что не существует такого понятия, как «один и тот же»).В вашей видеокарте уже есть таймер, который выполняет вертикальную синхронизацию.Использование этого для синхронизации будет означать, что все будет работать, использование другого означает, что в конечном итоге все будет не синхронизировано.Нарушение синхронизации может вообще не иметь никакого эффекта, может нарисовать наполовину законченный кадр или может блокировать один полный кадр, или что-то в этом роде.
Хотя пропуск кадра обычно не так уж и важен (если он всего одини случается редко), в этом случае это то, чего вы можете полностью избежать.

1 голос
/ 06 апреля 2011
  1. MSGWaitForMultipleObjects следует использовать в цикле сообщений Windows, так как вы можете автоматически остановить ожидание сообщений в соответствии с заданным временем ожидания. Это может сильно ограничить ваши вызовы OnIdle, одновременно обеспечивая быстрое реагирование окна на сообщения.

  2. Если ваше окно не полноэкранное, то SetTimer , вызывающий таймер или отправляющий сообщения WM_TIMER в WindowProc вашей игры, обязательно . Если вы не возражаете против остановки анимации всякий раз, когда пользователь, например, нажимает на строку заголовка вашего окна. Или вы видите модальное диалоговое окно какого-то вида.

  3. В Windows нет способа получить доступ к фактическим прерываниям по таймеру. Этот материал скрыт под многими уровнями аппаратной абстракции. прерывания таймера обрабатываются драйверами для сигнализации объектов ядра, которые код пользовательского режима может использовать при вызовах WaitForMultipleObjects. Это означает, что ядро ​​разбудит ваш поток для обработки таймера ядра, связанного с вашим потоком, когда вы вызвали SetTimer (), и в этот момент GetMessage / PeekMessage, если очередь сообщений пуста, синтезирует сообщение WM_TIMER.

1 голос
/ 06 апреля 2011

Зависит от того, для кого вы хотите оптимизировать производительность.Если вы хотите самую быструю частоту кадров для своей игры, используйте цикл, который вы опубликовали.Если ваша игра работает в полноэкранном режиме, это, вероятно, правильная вещь.

Если вы хотите ограничить частоту кадров и позволить настольным компьютерам и другим приложениям получать некоторые циклы, тогда подойдет таймер.В Windows вы просто используете скрытое окно, вызов SetTimer и меняете PeekMessage на GetMessage.Просто помните, что интервалы WM_TIMER не точны.Когда вам нужно выяснить, сколько реального времени прошло с момента последнего кадра, вызовите GetTickCount, вместо того чтобы предполагать, что вы проснулись точно на временном интервале.

Гибридный подход, который хорошо работает в отношении игр и настольных ПКприложения должны использовать цикл, который вы опубликовали выше, но вставьте Sleep (0) после возврата из функции OnIdle.

0 голосов
/ 06 апреля 2011

Никогда не используйте GetTickCount или WM_TIMER для определения времени в игре, у них ужасное разрешение. Вместо этого используйте QueryPerformanceCounter.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...