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

xlpyo6sf  于 2022-11-04  发布在  其他
关注(0)|答案(3)|浏览(206)

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

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

    glfwSwapBuffers(window);
    glfwPollEvents();

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

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

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

    glfwSwapBuffers(window);
    glfwPollEvents();

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

有办法吗?

k5hmc34c

k5hmc34c1#

现代GL实现通常不会在SwapBuffers上阻塞,即使VSync打开也是如此。它们将提前几个帧对GL命令进行排队,并且仅在达到特定于实现的排队帧数限制后才在SwapBuffers上阻塞(顺便说一句,nvidia的windows驱动程序对此有一个明确的设置)。这导致这样的情况,即,典型地,循环的前两到五次迭代将不会阻塞,而每个后续迭代将阻塞。
有了相当现代的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不会立即阻塞的事实,并且会一直使用它,直到实际执行了缓冲区交换,以便执行有用的数学运算。
这也有一个副作用,就是将延迟限制在一帧,这可能是好事也可能是坏事,这取决于你的场景。但是原则上,你也可以交错几个围栏同步对象,然后等待倒数第二个缓冲区交换,而不是最后一个,等等。

xzv2uavs

xzv2uavs2#

唯一可行的方法是为someUsefulMathHere()函数使用thread。(例如mutex,这只是其中一种可能性),或者将新作业排队。这样,您不仅可以在主线程真正休眠时获得空闲的CPU周期,但您也可以在多核CPU上获得额外的性能。

0aydgbwb

0aydgbwb3#

首先,我想指出的是,即使没有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));
}

相关问题