检查我们是否需要在glfwSwapBuffers方法中休眠

时间:2014-05-12 15:10:49

标签: opengl glfw vsync

我正在尝试使用glew和glfw创建示例应用。主循环很简单,看起来像:

 while (running) {
    someUsefullMathHere();
    renderer->render(timeDelta);

    glfwSwapBuffers(window);
    glfwPollEvents();

    running = running & (!glfwWindowShouldClose(window));
}

问题在于,由于vsync当前线程休眠了一些时间glfwSwapBuffers执行(fps限制为60 fps)。我正在寻找一种方法来利用这段时间来进行一些连续的方法连续执行。理想情况下,代码必须类似于:

while (running) {
    while (!timeToRenderAndSwap()) {
        someUsefullMathHere();
    }
    renderer->render(timeDelta);

    glfwSwapBuffers(window);
    glfwPollEvents();

    running = running & (!glfwWindowShouldClose(window));
}

有没有办法解决这个问题?

3 个答案:

答案 0 :(得分:1)

现代GL实现通常不会阻止SwapBuffers,即使VSync已启用。它们会将GL命令排队等候几个帧,并且只有在达到排队帧数量的特定于实现的限制后才会在SwapBuffers处阻塞(nvidia的Windows驱动程序有明确的设置, BTW)。在您绘制的未修改的渲染循环中,这导致通常循环的前两到五次迭代不会阻塞的情况,并且每个后续循环都会阻塞。

使用相当现代的GL,您可以利用sync objects来改善情况。在伪代码中,这可能如下所示:

GLsync fence=NULL;
while (running) {
    if (fence)
        while (glClientWaitSync(fence, GL_SYNC_FLUSH_COMMANDS_BIT, 0)==GL_TIMEOUT_EXPIRED) {
            someUsefullMathHere();
        }
        glDeleteSync(fence);
        fence=NULL;
    }

    renderer->render(timeDelta);

    glfwSwapBuffers(window);
    fence=glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
    glfwPollEvents();

    running = running & (!glfwWindowShouldClose(window));
}

if (fence)
    glDeleteSync(fence);
}

这利用了SwapBuffers不会立即阻塞的事实,并将一直使用,直到实际执行缓冲区交换以进行有用的数学运算。

这也具有将延迟限制为一帧的副作用,这可能是好事或坏事,具体取决于您的场景。但原则上,您还可以交错多个fence同步对象,并等待倒数第二个缓冲区交换而不是最后一个,依此类推。

答案 1 :(得分:0)

唯一可行的方法是为someUsefulMathHere()功能使用thread。您可以使用常规线程同步方法来回读任何结果(例如mutex,仅列举其中一种可能),或者排队新作业。这样,您不仅可以在主线程真正休眠时获得备用CPU周期,而且还可以在多核CPU上获得额外的性能。

答案 2 :(得分:0)

首先我要指出,即使没有vsync glfwSwapBuffers(window)也可能会阻塞一段时间。 OpenGL的许多功能只是将命令放入队列并返回而不等待完成。这意味着当您拨打glfwSwapBuffers(window)时,显卡仍然可能会有很多工作。在交换缓冲区并返回之前,此函数将一直等到所有工作完成。

我不确定someUsefullMathHere()的工作类型,所以我有两个可能的答案:

如果您需要每帧进行一定数量的计算:
除了渲染之外,您还必须每帧更新场景。要更新场景,您需要每帧进行一些计算。您只需将此代码放在glfwSwapBuffers(window)之前,而不是将其放在循环的开头或结尾。所以你可以在不使用线程的情况下实现更高的帧速率。我认为如果程序只是在等待vsync,那应该没什么区别,但是如果你必须等待显卡它会有所作为:

while (running) {
    renderer->render(timeDelta);
    someUsefullMathHere();

    glfwSwapBuffers(window);
    glfwPollEvents();

    running = running & (!glfwWindowShouldClose(window));
}

如果您希望将时间用于跨框架的某些工作:
有时你必须做一些需要太多时间才能完成一个框架的事情。像加载关卡或下载数据之类的东西。要使用交换缓冲区之前的时间,可以使用第二个线程。我认为你可以在运行剩下的循环时暂停线程以避免竞争条件:

while (running) {
    renderer->render(timeDelta);

    unpauseThread();
    glfwSwapBuffers(window);
    pauseThreadWhenItIsSave();

    glfwPollEvents();

    running = running & (!glfwWindowShouldClose(window));
}