1

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

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

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 以进行渲染。

你能解释一下吗?

4

3 回答 3

4

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

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

于 2013-05-06T15:01:34.113 回答
2

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

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

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

可以在此处找到改进此示例的良好信息来源。

于 2015-01-03T20:41:40.543 回答
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调用一次。

于 2015-01-04T00:10:48.263 回答