我使用Direct3D9
在ID3DXSprite
中创建了简单的,与帧无关的,可变时间步长,线性移动。大多数用户都无法注意到它,但在某些(包括我的)计算机上,它经常发生,有时它会很多口吃。
在VSync
启用和禁用时发生口吃。
我发现在OpenGL
渲染器中也是如此。
它不是浮点问题。
似乎问题仅存在于AERO Transparent Glass
窗口模式中(在全屏,无边框全屏窗口或禁用航空器时很好或至少不太明显),更糟糕的是当窗口失去焦点时。
修改
即使发生口吃,帧增量时间也不会离开16 .. 17 ms。
好像我的帧增量时间测量日志代码被窃听了。我现在修好了。
(我只在应用程序退出时转储一次日志,而不是在运行时转储,渲染,因此它不会影响性能)
device->Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_ARGB(255, 255, 255, 255), 0, 0);
device->BeginScene();
sprite->Begin(D3DXSPRITE_ALPHABLEND);
QueryPerformanceCounter(&counter);
float time = counter.QuadPart / (float) frequency.QuadPart;
float deltaTime = time - currentTime;
currentTime = time;
position.x += velocity * deltaTime;
if (position.x > 640)
velocity = -250;
else if (position.x < 0)
velocity = 250;
position.x = (int) position.x;
sprite->Draw(texture, 0, 0, &position, D3DCOLOR_ARGB(255, 255, 255, 255));
sprite->End();
device->EndScene();
device->Present(0, 0, 0, 0);
由于Eduard Wirch和Ben Voigt的固定计时器(尽管它没有解决初始问题)
float time()
{
static LARGE_INTEGER start = {0};
static LARGE_INTEGER frequency;
if (start.QuadPart == 0)
{
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&start);
}
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return (float) ((counter.QuadPart - start.QuadPart) / (double) frequency.QuadPart);
}
编辑#2:
到目前为止,我尝试了三种更新方法:
1)可变时间步
x += velocity * deltaTime;
2)固定时间步
x += 4;
3)固定时间步长+插值
accumulator += deltaTime;
float updateTime = 0.001f;
while (accumulator > updateTime)
{
previousX = x;
x += velocity * updateTime;
accumulator -= updateTime;
}
float alpha = accumulator / updateTime;
float interpolatedX = x * alpha + previousX * (1 - alpha);
所有方法都工作得非常相似,固定时间步长看起来更好,但它不是一个完全依赖帧速率的选项,它不能完全解决问题(仍然很少跳跃(断断续续))。
到目前为止,禁用AERO Transparent Glass
或全屏显示只是重大的积极变化。
我正在使用NVIDIA
最新驱动程序GeForce 332.21 Driver
和Windows 7 x64 Ultimate
。
答案 0 :(得分:7)
部分解决方案是简单的精确数据类型问题。用常数交换速度计算,你会看到一个非常平滑的运动。分析计算表明,您将结果从QueryPerformanceCounter()
存储在浮点内。 QueryPerformanceCounter()
会在我的计算机上返回一个如下所示的数字:724032629776
。此数字至少需要存储5个字节。然而,float
使用4个字节(实际数字只有24位)来存储该值。因此,将QueryPerformanceCounter()
的结果转换为float
时,精度会丢失。有时,这导致deltaTime
零导致口吃。
这部分解释了为什么有些用户没有遇到这个问题。这完全取决于QueryPerformanceCounter()
的结果是否适合float
。
问题的这一部分的解决方案是:使用double
(或者作为Ben Voigt建议:存储初始性能计数器,并在转换为float
之前从新值中减去此值。这将给出当你的应用程序运行很长时间(取决于性能计数器的增长速度)时,你至少会有更多的头部空间,但最终可能再次达到float
分辨率限制。)
解决这个问题后,口吃的情况要少得多,但并没有完全消失。分析运行时行为表明偶尔会跳过一个帧。应用程序GPU命令缓冲区由Present
刷新,但是当前命令保留在应用程序上下文队列中,直到下一个vsync(即使在vsync(14ms)之前很久就调用了Present
)。进一步的分析表明,背景过程(f.lux)告诉系统偶尔设置伽马斜坡。此命令要求完整的GPU队列在执行之前运行。可能是为了避免副作用。这个GPU刷新是在'present'命令移动到GPU队列之前启动的。系统阻止视频调度直到GPU干涸。这直到下一个vsync。因此,在下一帧之前,当前数据包不会移动到GPU队列。这可见效果:口吃。
您也不太可能在计算机上运行f.lux。但是你可能正在经历类似的背景介入。您需要自己在系统上查找问题的根源。我写了一篇关于如何诊断框架跳过的博客文章:Diagnose frame skips and stutter in DirectX applications。你也会发现将f.lux诊断为罪魁祸首的整个故事。
但即使您发现帧的来源跳过,我怀疑在启用dwm窗口组合时您将获得稳定的60fps。原因是,你没有直接画到屏幕上。但相反,你绘制到dwm的共享表面。由于它是共享资源,因此可以被其他人锁定一段任意时间,使您无法保持帧速率对您的应用程序稳定。如果您确实需要稳定的帧速率,请全屏显示,或禁用窗口组合(在Windows 7上.Windows 8不允许禁用窗口组合):
#include <dwmapi.h>
...
HRESULT hr = DwmEnableComposition(DWM_EC_DISABLECOMPOSITION);
if (!SUCCEEDED(hr)) {
// log message or react in a different way
}
答案 1 :(得分:2)
我查看了您的源代码,发现每帧只处理一个窗口消息。对我而言,这在过去引起了口吃。
我建议循环PeekMessage
,直到它返回零表示消息队列已用尽。之后渲染一个框架。
所以改变:
if (PeekMessageW(&message, 0, 0, 0, PM_REMOVE))
到
while (PeekMessageW(&message, 0, 0, 0, PM_REMOVE))
编辑:
我编译并运行了代码(使用另一个纹理),它为我顺利显示了运动。我没有aero(Windows 8)。
我注意到的一件事:你设置了D3DCREATE_SOFTWARE_VERTEXPROCESSING
。您是否尝试将其设置为D3DCREATE_HARDWARE_VERTEXPROCESSING
?