Windows Game Loop 50% ЦП на двухъядерном - PullRequest
4 голосов
/ 02 марта 2010

Только в игровом цикле используется 50% загрузки ЦП, я еще не выполнял рендеринга. Что я здесь делаю?

        while(true)
        {
            if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))
            {
                    if(msg.message == WM_QUIT || 
                           msg.message == WM_CLOSE || 
                           msg.message == WM_DESTROY)
                            break;

                    TranslateMessage(&msg);
                    DispatchMessage(&msg);                   
            }
            else
            {
                    //Run game code, break out of loop when the game is over

            }
        }

Ответы [ 12 ]

8 голосов
/ 02 марта 2010

Классический цикл занятости / ожидания. Ваш процессор занят проверкой (и перепроверкой до бесконечности) сообщений. Вам нужно ждать сообщений блокирующим способом или, более вероятно, использовать таймер, который периодически пробуждает ваш игровой поток, чтобы он мог выполнять свою работу. Затем игровая нить исчезнет до следующего пробуждения.

5 голосов
/ 02 марта 2010

Вы создали цикл ожидания ожидания. Вероятно, вы используете 100% одного ядра, то есть 50% двухъядерного.

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

3 голосов
/ 02 марта 2010

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

1 голос
/ 26 ноября 2011

Это потому, что функция PeekMessage не удаляет сообщение WM_PAINT, поэтому оно всегда возвращает TRUE. MSDN говорит:

Функция PeekMessage обычно не удаляет сообщения WM_PAINT из очереди. Сообщения WM_PAINT остаются в очереди, пока не будут обработаны. Однако, если сообщение WM_PAINT имеет область обновления NULL, PeekMessage действительно удаляет его из очереди.

1 голос
/ 14 января 2011

У меня была такая же проблема, и я получил ответ здесь: Игровые циклы

В моей программе я использовал последний цикл из статьи выше «Постоянная скорость игры с максимальным FPS»

1 голос
/ 04 марта 2010

Вам нужно отказаться от процессора, когда у вас нет сообщений для обработки и кода игры для выполнения. Один из способов сделать это - использовать MsgWaitForMultipleObjects, чтобы дождаться появления сообщения в вашей очереди или через некоторое время, которое истечет.

Как то так

   DWORD g_msNextGameCall;
   DWORD g_msGameTickTime = 1000/75;

   while (true)
      {
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD))
         {
         if (WM_QUIT == msg.message)
            break;

         TranslateMessage(&msg);
         DispatchMessage(&msg);  
         }
      else
         {
         DWORD ms = GetTickCount();
         DWORD msNext = g_msNextGameCall;
         LONG  lWait = 0;
         DWORD dwRet = WAIT_TIMEOUT;

         if (ms < msNext)
            lWait = min((LONG)g_msGameTickTime, (LONG)(msNext - ms));

         if (lWait <= 1)
            {
            g_msNextGameCall = ms + g_msGameTickTime;
            DoGameStuff();
            }
         else
            {
            if (WAIT_TIMEOUT == MsgWaitForMultipleObjects (0, NULL, FALSE, lWait, QS_ALLEVENTS))
               {
               g_msNextGameCall = GetTickCount() + g_msGameTickTime;
               DoGameStuff();
               }
            }
         }
      }
1 голос
/ 02 марта 2010

Я думаю, что такое поведение ожидается. Всякий раз, когда код вашей игры ничего не делает, приложение яростно проверяет очередь сообщений, используя PeekMessage - это непрерывный цикл, поэтому используется всего 1 ядро.

Когда вы добавите логику в свой блок else{...}, вы обнаружите, что он остается на 100% занятым на одном ядре, но время затрачивается на выполнение игровой логики - при вызове PeekMessage используются только неиспользованные циклы ЦП, но сейчас 100% циклов не используются.

Обычно игра максимально загружает процессор, когда он виден, если он работает в полноэкранном режиме. Но вы, вероятно, должны рассмотреть возможность использования GetMessage вместо PeekMessage.

Обратите внимание, что игры обычно не работают так же, как обычные приложения. Обычные приложения, как правило, ничего не делают, если только они не получают сообщение, в котором говорится, что они что-то делают. Игры обычно делают вещи постоянно, потому что они хотят рендерить как можно больше кадров / секунду. Кража all Процессор немного жадный в оконном режиме.

1 голос
/ 02 марта 2010

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

0 голосов
/ 09 августа 2016

Вопрос старый, но я считаю, что мой отзыв может помочь новым читателям.

Здесь я столкнулся с той же проблемой (50% ЦП при простое приложения) с чистым приложением Win32, использующим Delphi (без VCL), работающим с 32-битной Win7, Core Duo.

Я пробовал Sleep (0), Sleep (1) и т. Д. В цикле сообщений, но ни один из них не уменьшил загрузку ЦП до 0% (в режиме ожидания приложения). В конце концов мне удалось использовать тот же Sleep (), больше не в каждом цикле цикла, а только в том случае, если PeekMessage () возвращает False. Теперь я получаю 0% загрузки процессора, когда приложение бездействует.

В упрощенном коде я сейчас и делаю:

AppIsDone := False;  // turned on after wm_Close (not shown below)

repeat
  if not PeekMessage(...) then
  begin
    Sleep(1);
    Continue;  {repeat}
  end;

  if GetMessage(...) then
  begin
    TranslateMessage(...);
    DispatchMessage(...);
  end;
until AppIsDone;
0 голосов
/ 10 мая 2010

Ваша игра работает максимально быстро в одном ядре. Это нормально в зависимости от того, что вы сделали.

Я не знаю, хотите ли вы получить БОЛЬШУ мощность (таким образом, достичь 100%) или МЕНЬШЕ мощности (и использовать меньше из-за низкого расхода энергии пользователя ...)

Если вы хотите получить БОЛЬШЕ мощности, вам нужно как-то использовать многопоточность, чтобы использовать оба ядра. Поскольку я не сильно увлекаюсь потоками, я даже не буду пытаться объяснить, это совершенно не моя сфера.

Если вы хотите использовать МЕНЬШЕ питания, есть также два варианта ...

Одним из них является «выход», как называют его некоторые API-интерфейсы библиотек игр (например, Allegro), он состоит из создания счетчика FPS и передачи управления процессору каждому кадру на достаточное время. Например, если ваша игра хочет работать со скоростью 120 FPS, а вы хотите, чтобы она работала со скоростью 60, вы можете дать ему примерно столько же времени, сколько требуется для расчета фрейма (около 8,3333 ... мс).

Другой способ - использовать код на основе событий вместо кодирования. В этой форме вы помещаете свой код в функцию с именем «Update», которая принимает в качестве аргумента количество времени, прошедшее с момента последнего вызова (очень важно это ...). Это «обновление» может быть либо для всего приложения (более классического), либо для каждого объекта, имеющего свой собственный (популярный после изобретения Flash, который использует это, как «OnEnterFrame», также Unity и этот, и некоторые другие движки). И вы создаете код, который генерирует прерывание, и вызываете это обновление, обычно это просто таймер, который работает в обычное время (16,6 .... мс для 60FPS, 33,33333 ... мс для 30FPS).

Очевидно, что есть много других способов, но я не буду объяснять их все, потому что это достаточно информации для маленькой книги ...

Я сам использовал различные подходы, мне нравится просто использовать полную загрузку ЦП (без многопоточности) и использовать все больше и больше оскорбительных системных эффектов, когда доступно энергопотребление. Но для более простых игр я обычно делаю цикл, который «дает», самый простой способ, обычно подсчитывать, сколько времени потребовалось, чтобы вычислить все, вычесть это из 16,6 мс и вызвать функцию сна (которая дает контроль ОС на это время) на результат ... Например, если для вычисления кадра потребовалось 3 мс, я вызываю режим сна (16-3). И несколько движков заставляют меня работать с «стилем событий» (то есть: ввод поступает от прерываний клавиатуры, мыши и джойстика, а таймер прерывает и вызывает «Обновить (шаг)»), мне это не нравится, но мне пришлось учиться. ..

И последнее замечание: переменная «step», как я ее назвал (аргумент Update), обычно используется в математике логики игры, например: «position.x = position.x + speed * step» для создания объекта двигаться с фактической постоянной скоростью ... (очевидно, используемая операция зависит от того, какой "шаг" представляет).

...