C++ Windows上带OpenGL的可靠窗口vsync?
总结 在窗口模式下,带OpenGL的vsync似乎在Windows上被破坏了。我尝试过不同的API(SDL、glfw、SFML),所有这些都有相同的结果:虽然帧速率是有限的(根据我尝试过的多个60 Hz设置上的CPU测量,始终在16-17毫秒左右),而且CPU实际上大部分时间都在睡眠,但帧经常被跳过。根据机器和CPU在渲染以外的其他方面的使用情况,这可能与有效地将帧速率减半一样糟糕。此问题似乎与驱动程序无关 如何使用OpenGL在窗口模式下在Windows上运行vsync,或使用这些属性获得类似效果(如果我忘记了一些值得注意的内容,或者某些内容不合理,请发表评论):C++ Windows上带OpenGL的可靠窗口vsync?,c++,windows,opengl,rendering,C++,Windows,Opengl,Rendering,总结 在窗口模式下,带OpenGL的vsync似乎在Windows上被破坏了。我尝试过不同的API(SDL、glfw、SFML),所有这些都有相同的结果:虽然帧速率是有限的(根据我尝试过的多个60 Hz设置上的CPU测量,始终在16-17毫秒左右),而且CPU实际上大部分时间都在睡眠,但帧经常被跳过。根据机器和CPU在渲染以外的其他方面的使用情况,这可能与有效地将帧速率减半一样糟糕。此问题似乎与驱动程序无关 如何使用OpenGL在窗口模式下在Windows上运行vsync,或使用这些属性获得类似
- CPU可以在大部分时间睡眠
- 不撕裂
- 没有跳过的帧(假设系统没有过载)
- CPU可以知道实际显示帧的时间
细节/一些研究 当我在谷歌上搜索
opengl-vsync-stutter
或opengl-vsync-frame-drop
或类似查询时,我发现很多人都有这个问题(或非常类似的问题),但实际问题似乎没有一致的解决方案(在gamedev stackexchange上也有许多回答不充分的问题;还有许多低投入论坛的帖子)
总结一下我的研究:新版本的Windows中使用的合成窗口管理器(DWM)似乎强制使用三重缓冲,这会干扰vsync。人们建议禁用DWM、不使用vsync或全屏显示,所有这些都不是解决原始问题的方法(脚注1)。我也没有找到详细的解释,说明为什么三重缓冲会导致vsync出现此问题,或者为什么在技术上无法解决此问题
但是:我还测试了Linux上不会出现这种情况,即使在非常脆弱的PC上也是如此。因此,基于OpenGL的硬件加速必须在技术上(至少在一般情况下)能够在不跳过帧的情况下启用功能性vsync
此外,在Windows上使用D3D而不是OpenGL(启用了vsync)时,这也不是问题。因此,在Windows上运行vsync在技术上必须是可行的(我尝试过新的、旧的和非常旧的驱动程序以及不同的(旧的和新的)硬件,虽然我所有可用的硬件设置都是Intel+NVidia,所以我不知道AMD/ATI会发生什么)
最后,一定要有适用于Windows的软件,无论是游戏、多媒体应用程序、创意制作、3D建模/渲染程序还是其他任何软件,它们使用OpenGL并在窗口模式下正常工作,同时仍能准确地渲染,无需等待CPU,也无需掉帧
我注意到,当使用传统的渲染循环时
while (true)
{
poll_all_events_in_event_queue();
process_things();
render();
}
CPU在该循环中所做的工作量会影响口吃的行为。然而,这绝不是CPU过载的问题,因为这个问题也发生在一个可以编写的最简单的程序之一(见下文)和一个功能非常强大的系统上,而该系统不做任何其他事情(程序只是在每一帧上用不同的颜色清除窗口,然后显示它)
我还注意到,它似乎从来没有比每隔一帧跳过一次更糟糕(即,在我的测试中,在60赫兹的系统中,可见帧率总是在30到60之间)。当运行程序在奇数帧和偶数帧上更改2种颜色之间的背景颜色时,您可能会发现Nyquist采样定理有点违规,这使我相信某些东西没有正确同步(即Windows或其OpenGL实现中的软件错误)。同样,就CPU而言,帧速率是坚如磐石。而且,timeBeginPeriod
在我的测试中没有明显的效果
(脚注1)但应注意,由于DWM,窗口模式下不会发生撕裂(这是使用vsync的两个主要原因之一,另一个原因是使CPU在不丢失帧的情况下尽可能长时间睡眠)。因此,我可以接受在应用程序层实现vsync的解决方案 但是,我认为唯一可行的方法是,有一种方法可以显式(准确地)等待页面翻转发生(有可能超时或取消),或者查询页面翻转时设置的非粘性标志(以一种不会强制刷新整个异步渲染管道的方式,例如,
glGetError
does),而我也没有找到一种方法
下面是一些代码,可以运行一个演示此问题的快速示例(使用SFML,我发现这是最不费力的) 你应该看到同质闪烁。如果你在多个画面上看到相同的颜色(黑色或紫色),那就糟糕了 (屏幕上会闪烁显示屏的刷新率,因此可能会出现癫痫警告):
//g++TEST\u TEST\u TEST.cpp-lsfml系统-lsfml窗口-lsfml图形-lGL
#包括
#包括
#包括
#包括
#包括
int main()
{
//创建窗口
渲染窗口(sf::视频模式(800600),“OpenGL”);
window.setVerticalSyncEnabled(真);
//激活窗口
window.setActive(true);
int frame_计数器=0;
sf::矩形形状矩形;
rect.setSize(sf::Vector2f(10,10));
sf:时钟;
while(true)
{
//处理事件
sf::事件;
while(window.pollEvent(事件))
{
如果(event.type==sf::event::Closed)
{
返回0;
}
}
++帧计数器;
if(帧计数器和1)
{
glClearColor(0,0,0,1);
}
其他的
{
glClearColor(6
// g++ TEST_TEST_TEST.cpp -lsfml-system -lsfml-window -lsfml-graphics -lGL
#include <SFML/System.hpp>
#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>
#include <SFML/OpenGL.hpp>
#include <iostream>
int main()
{
// create the window
sf::RenderWindow window(sf::VideoMode(800, 600), "OpenGL");
window.setVerticalSyncEnabled(true);
// activate the window
window.setActive(true);
int frame_counter = 0;
sf::RectangleShape rect;
rect.setSize(sf::Vector2f(10, 10));
sf::Clock clock;
while (true)
{
// handle events
sf::Event event;
while (window.pollEvent(event))
{
if (event.type == sf::Event::Closed)
{
return 0;
}
}
++frame_counter;
if (frame_counter & 1)
{
glClearColor(0, 0, 0, 1);
}
else
{
glClearColor(60.0/255.0, 50.0/255.0, 75.0/255.0, 1);
}
// clear the buffers
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Enable this to display a column of rectangles on each frame
// All colors (and positions) should pop up the same amount
// This shows that apparently, 1 frame is skipped at most
#if 0
int fc_mod = frame_counter % 8;
int color_mod = fc_mod % 4;
for (int i = 0; i < 30; ++i)
{
rect.setPosition(fc_mod * 20 + 10, i * 20 + 10);
rect.setFillColor(
sf::Color(
(color_mod == 0 || color_mod == 3) ? 255 : 0,
(color_mod == 0 || color_mod == 2) ? 255 : 0,
(color_mod == 1) ? 155 : 0,
255
)
);
window.draw(rect);
}
#endif
int elapsed_ms = clock.restart().asMilliseconds();
// NOTE: These numbers are only valid for 60 Hz displays
if (elapsed_ms > 17 || elapsed_ms < 15)
{
// Ideally you should NEVER see this message, but it does tend to stutter a bit for a second or so upon program startup - doesn't matter as long as it stops eventually
std::cout << elapsed_ms << std::endl;
}
// end the current frame (internally swaps the front and back buffers)
window.display();
}
return 0;
}