【问题标题】:Smooth animations using Win32 API - without controlling the message pump使用 Win32 API 的平滑动画 - 无需控制消息泵
【发布时间】:2010-10-09 20:01:37
【问题描述】:

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

这个基于 OpenGL 的实时 3d 动画代码,应该尽可能快地渲染,通常每秒 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(请参阅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”键才能显示模式对话框;我必须移动鼠标给主机应用程序一些处理时间!...

    是否有任何“行业标准”解决方案可以解决这种“尽可能频繁”的动画重绘问题?

  • 【问题讨论】:

      标签: c winapi graphics animation


      【解决方案1】:

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

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

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

      【讨论】:

      • 同意,在不调整主机应用程序的情况下,我想不出任何其他方式让插件尽可能快地呈现。
      • 感谢您的建议!这看起来是个好主意!我会尽快尝试的——我从没想过 OpenGL 渲染可以完全在一个自治线程中完成。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-08-24
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多