【问题标题】:Basic concept of message loop in directxdirectx中消息循环的基本概念
【发布时间】:2013-05-06 14:46:49
【问题描述】:

嗯,我发现消息循环的奇怪点。

首先,将这段代码锁定在下方

MSG msg = {0};
while( WM_QUIT != msg.message )
{
    if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
    {
        TranslateMessage( &msg );
        DispatchMessage( &msg );
    }
    else
    {
        Render();  // Do some rendering
    }
}

这是一个directx的教程,这部分是消息循环的一部分。

如果我点击鼠标,它会作为消息进入队列。

所以这样的输入应该是win api的proc函数中的处理。

现在 peekMessage 返回 true,当我单击时,render() 将不会在框架中调用。

我认为代码将 if~else 更改为 if~if 以在我单击时进行渲染。

你能解释一下吗?

【问题讨论】:

    标签: directx


    【解决方案1】:

    您的理解很接近,但并不完全正确。循环不是每帧运行一次。相反,对于循环的每次迭代,要么处理单个消息,要么调用 Render。实际上,这使得渲染优先级最低,但让您的应用程序保持响应。对于绘制的每一帧,循环可能会运行多次或几次,具体取决于要做的工作量。

    Render 是否直接调用 Present?还是它使窗口无效?如果它使窗口无效,您不希望像您提到的那样更改为始终调用 Render,因为您可能不会在渲染之间重绘窗口。

    【讨论】:

    • 我主要使用Render()。同时使用window Message System 和directx 是不好的。正如你所说,Render() 的优先级低于消息。它是directx 教程,所以我认为这段代码可能对directx 无效。对此你怎么看??
    • 代码可以按原样正常工作,并且对于教程来说简单易行。如果你最终写了一个游戏,并且你发现你无法保持帧率,你可以做一些事情,比如将你的渲染切换到不同的线程或以用户优先级发布消息来进行绘图,但是为了一个教程,这将没事。
    【解决方案2】:

    本质上,这个循环将为您的窗口处理任何待处理的 Win32 消息,如果没有,它将呈现一个框架。如果它看到WM_QUIT 消息,它会退出循环以退出应用程序。

    不需要“节流”,因为如果已经有 3 帧等待渲染,DirectX Present 将阻塞线程(即暂停它)。

    此模型假设您在每次“渲染”调用时执行一帧“更新”,这对于游戏来说不太现实,但对于本教程来说很简单。使用 StepTimer 扩展教程循环看起来像:

    #include “StepTimer.h
    DX::StepTimer g_timer;
    

    ...

    MSG msg = {0};
    while( WM_QUIT != msg.message )
    {
        if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
        {
            TranslateMessage( &msg );
            DispatchMessage( &msg );
        }
        else
        {
            g_timer.Tick([&]()
            {
                Update(g_timer); // Update world/game state
            });
            Render();  // Do some rendering
        }
    }
    

    ...

    void Render();
    void Update(DX::StepTimer& timer);
    

    StepTimer 默认使用可变步长更新,这意味着每帧调用一次Update,无论时间增量如何,然后调用一次Render

    您可以像这样使用固定步长更新(比如每秒 60 次):

    g_timer.SetFixedTimeStep(true);
    g_timer.SetTargetElapsedSeconds(1.f / 60.f);
    

    在此模式下,您将处理所有待处理的 Win32 消息,然后根据需要随时调用 Update 以保持平均每秒 60 次固定步长更新,然后调用 Render一次。

    【讨论】:

      【解决方案3】:

      else 中的 Render() 基本上优先处理队列中的消息而不是渲染。将鼠标移到 directx 渲染窗口上会快速将消息添加到消息队列中,但速度不足以导致渲染延迟到您所见过的任何程度。每次迭代进行渲染没有任何优势,因为迭代发生的速度快于交换链中生成的每一帧,并且比新消息淹没队列的速度快得多。今天的大多数计算机每毫秒运行这个循环不止一次,甚至鼠标悬停事件发生的频率也比这少。每次迭代都进行渲染不会错,只是没有必要。在示例运行的情况下,尽可能快地将鼠标移到 directx 窗口上,这将导致不到 10% 的循环迭代处理消息并延迟渲染。

      此消息循环会尽快执行,并且无法检测交换链何时准备好呈现。 PeekMessage 检查队列中是否有消息。如果有,它会处理它,如果没有,它会渲染。您担心的是一系列窗口事件会导致渲染延迟,但这实际上是不可能的。无论消息发送到队列的速度有多快,交换链的渲染速度都比它需要的速度快 10 倍以上,即使是 60fps。此循环是高 CPU 利用率的原因。这样做的原因可能是为了简化教程,但因为它本质上是一个复杂的环境。如果您担心消息队列延迟帧渲染,您可以在单独的线程中修改交换链。

      为了提高示例程序的CPU效率,只需添加一个Sleep(8);在 Render() 例程的底部。这将导致消息处理程序/呈现线程在处理消息和呈现大约每秒 120 次的周期之间暂停。您可以通过在周期之间使用高分辨率计时器和基于模数的睡眠来改进这一点。

      found here 是改进此示例的良好信息来源。

      【讨论】:

      • 请注意,这不会使用 100% 的核心。使用 Direct3D 11 Present,如果有 3 帧未决,它将阻塞(即使线程进入睡眠状态)。循环尽可能快地渲染,但不会因此锁定 CPU。
      • 很奇怪。我在装有 Windows 8.1 的 i7-2600K 上看到了大约 30% 的 cpu。在渲染例程中添加 Sleep(8) 会将其降低到远低于 1%。我会调查此事。谢谢
      猜你喜欢
      • 2014-09-22
      • 2012-10-21
      • 2018-08-03
      • 2018-02-14
      • 1970-01-01
      • 2023-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多