使用Win32 API平滑动画 - 无需控制消息泵

时间:2009-02-25 14:56:08

标签: c winapi graphics animation

我目前正在尝试将我的一些动画绘制代码集成到外部插件形式的第三方应用程序中。

此动画代码基于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:不起作用:完全阻止应用程序的其余部分响应并进行自己的刷新。
  • 创建一个'worker'线程,其唯一的工作是将用户消息发布到插件窗口。一旦绘图完成,就会发布此消息(由事件发出信号)。反过来,用户消息处理程序会调用InvalidateRect(请参阅there)。

    到目前为止,我的最后一次尝试是更好的,有时还可以正常工作。

    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 个答案:

    答案 0 :(得分:1)

    每个线程都有自己的消息队列,发送到窗口的消息到达创建窗口的线程的队列。如果您自己创建插件窗口,则可以在单独的线程中创建它,这样您就可以完全控制其消息泵。

    另一种解决方案(imho更好),只是在一个单独的线程中进行OpenGL渲染。所有OpenGL调用必须在创建OpenGL上下文的线程中进行。但是,您可以在一个线程(应用程序主线程)中创建一个窗口,但在另一个线程中创建OpenGL上下文。这样原始应用程序消息泵保持不变,并且在渲染线程中,您可以永远循环执行渲染(调用SwapBuffers到vsync)。

    第二个解决方案的主要问题是插件WindowProc和渲染循环之间的通信必须考虑线程(即访问共享内存时使用锁)。但是,由于消息泵与呈现是分开的,因此它可以是同时发生的,并且您的消息处理能够尽可能地响应。