Плавная анимация с использованием Win32 API - без управления насосом сообщений - PullRequest
0 голосов
/ 25 февраля 2009

В настоящее время я пытаюсь интегрировать некоторый мой код рисования анимации в стороннее приложение в виде внешнего плагина.

Этот код анимации в режиме реального времени 3d основан на OpenGL и должен отображаться с максимальной скоростью, обычно со скоростью 60 кадров в секунду.

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

for (;;)
{
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
    do
    {
      if (msg.message == WM_QUIT) break;
      TranslateMessage(&msg);
      DispatchMessage(&msg);
    } 
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE));
  }

  draw();
}

Теперь, когда я больше не король в мире, я должен хорошо играть с сообщениями приложения, чтобы оно продолжало реагировать. Насколько я знаю, поскольку я плагин, я не могу похитить весь насос сообщений приложения; поэтому я пробовал разные вещи, рисуя в обработчике сообщений WM_PAINT:

Используйте WM_TIMER, что не работает: я заранее не знаю, какой временной шаг мне нужен (часто не фиксированный) и время не точное. Позвоните InvalidateRect, как только я закончу рисовать, не работает: полностью предотвращает остальную часть приложения, реагирующую и выполняющую собственное обновление. Создать «рабочий» поток, единственная задача которого - публиковать пользовательское сообщение в окне плагина. Это сообщение публикуется, как только рисунок заканчивается (сигнализируется событием). Обработчик сообщений пользователя, в свою очередь, вызывает InvalidateRect (см. там ).

Пока что моя последняя попытка лучше, а иногда и работает нормально.
DWORD WINAPI PaintCommandThreadProc(LPVOID lpParameter)
{
  Plugin* plugin = static_cast<Plugin*>(lpParameter);
  HANDLE updateEvent = plugin->updateEvent();

  while (updateEvent == plugin->updateEvent())
  {
    ::WaitForSingleObject(updateEvent, 100);
    ::Sleep(0);
    if (updateEvent == plugin->updateEvent())
    {
      ::PostMessage(plugin->hwnd(), WM_USER+0x10, 0, 0);
    }
  }
  return 0;
}

...

LRESULT CALLBACK PluginWinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  bool processDefault = true;
  LRESULT result = 0;

  Plugin* plugin = reinterpret_cast<Plugin*>( GetWindowLong(hWnd, GWL_USERDATA) );

  switch (msg) {
    ...
    case WM_GL_MESSAGE:
      {
        ::InvalidateRect( hWnd, NULL, FALSE );
        processDefault = false;
        result = TRUE;
      }
      break;

    case WM_PAINT:
      {
        draw(hWnd);
        ::SetEvent( plugin->updateEvent() );
        processDefault = false;
        result = TRUE;
      }
      break;
    ...
  }

  if (processDefault && plugin && plugin->m_wndOldProc)
    result = ::CallWindowProc(plugin->m_wndOldProc, hWnd, msg, wParam, lParam);

  return result;
}

В некоторых случаях хост-приложение по-прежнему пропускает сообщения. Основными характеристиками проблемы является то, что я должен нажать клавишу «Alt» для отображения модальных диалогов; и я должен переместить мышь, чтобы дать некоторое время обработки хост-приложению! ...

Существует ли какое-либо «стандартное» решение для такой проблемы перерисовки анимации, как часто бывает? *

1 Ответ

1 голос
/ 25 февраля 2009

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

Альтернативное решение (что лучше imho) - это визуализация OpenGL только в отдельном потоке. Все вызовы OpenGL должны происходить в потоке, который создал контекст OpenGL. Однако вы можете создать окно в одном потоке (основной поток вашего приложения), но создать контекст OpenGL в другом потоке. Таким образом, исходные сообщения приложения остаются неизменными, и в потоке рендеринга вы можете зацикливаться, выполняя рендеринг (с вызовами SwapBuffers и vsync).

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

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