避免等待SwapBuffers

Avoid waiting on SwapBuffers

本文关键字:SwapBuffers 等待      更新时间:2023-10-16

我发现,只要显卡没有完成渲染,或者正在等待V-Sync, OpenGL中的SwapBuffers就会忙等待。

这对我来说是一个问题,因为我不想在等待卡完成时浪费100%的CPU核心。我不是在写游戏,所以我不能把CPU周期用于任何更高效的事情,我只想把它们交给操作系统中的其他进程。

我已经找到了回调函数,如glutTimerFunc和glutIdleFunc,可以为我工作,但我不想使用过剩。不过,glut必须以某种方式使用普通的gl函数来完成此操作,对吗?

是否有任何函数如"glReadyToSwap"或类似的?在这种情况下,我可以每毫秒检查一次,并决定是否应该等待更长时间或进行交换。我还可以想象也许跳过SwapBuffers并编写自己的类似函数,如果有人可以为我指出正确的方向,它不会很忙。

SwapBuffers不是忙等待,它只是阻塞了驱动上下文中的线程,这使得Windows计算CPU使用率错误:Windows通过确定空闲进程获得多少CPU时间+程序在驱动上下文中不花费多少时间来计算CPU使用率。SwapBuffers将在驱动程序上下文中阻塞,您的程序显然会从空闲进程中占用CPU时间。但是您的CPU在这段时间内实际上什么都不做,调度器愉快地等待将时间传递给其他进程。空闲进程OTOH除了立即将其时间分配给系统的其他部分之外什么都不做,因此调度器直接跳转到您的进程中,这阻塞了驱动程序,Windows将其视为"阻塞CPU"。如果你测量实际的功耗或热量输出,对于一个简单的OpenGL程序,这将保持相当低。

这个恼人的行为实际上是一个OpenGL常见问题解答!

为并行数据处理创建额外的线程。将OpenGL放在一个线程中,数据处理放在另一个线程中。如果您想降低报告的CPU使用率,在 SwapBuffers之后添加一个Sleep(0)或Sleep(1) 就可以了。Sleep(1)将使您的进程在用户上下文中花费一点阻塞时间,因此空闲进程获得更多时间,这将使数字平衡。如果你不想睡觉,你可以这样做:

const float time_margin = ... // some margin
float display_refresh_period; // something like 1./60. or so.
void render(){
    float rendertime_start = get_time();
    render_scene();
    glFinish();
    float rendertime_finish = get_time();
    float time_to_finish = rendertime_finish - rendertime_start;
    float time_rest = fmod(render_finish - time_margin, display_refresh_period);
    sleep(time_rest);
    SwapBuffers();
 }

在我的程序中,我使用这种定时,但出于另一个原因:我让SwapBuffers块没有任何助手睡眠,但是我给了一些其他工作线程的时间做的东西在GPU上通过共享上下文(如更新纹理),我有垃圾收集器运行。没有必要精确计时,但是在SwapBuffers返回之前完成的工作线程允许几乎立即开始渲染下一帧,因为大多数互斥锁那时已经解锁了。

虽然eglSwapBuffers没有忙碌等待,但非阻塞eglSwapBuffers的合法用途是拥有一个响应更快的GUI线程,可以侦听用户输入或退出信号,而不是等待OpenGL完成交换缓冲区。我对这个问题有一半的解决办法。首先在主循环中缓冲OpenGL命令,以便在交换出的缓冲区上执行。然后轮询同步对象,查看命令是否已经在交换出的缓冲区上执行完毕。然后,如果命令已经执行完毕,您可以交换缓冲区。不幸的是,此解决方案只能异步等待命令在交换出的缓冲区上完成执行,而不能异步等待vsync。下面是代码:

 void process_gpu_stuff(struct gpu_context *gpu_context)
 {
     int errnum = 0;
     switch (gpu_context->state) {
     case BUFFER_COMMANDS:
         glDeleteSync(gpu_context->sync_object);
         gpu_context->sync_object = 0;
         real_draw(gpu_context);
         glFlush();
         gpu_context->sync_object = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0);
         if (0 == gpu_context->sync_object) {
             errnum = get_gl_error();
             break;
         }
         gpu_context->state = SWAP_BUFFERS;
         break;
     case SWAP_BUFFERS:
         /* Poll to see if the buffer is ready for swapping, if
          * it is not in ready we can listen for updates in the
          * meanwhile. */
         switch (glClientWaitSync(gpu_context->sync_object, 0, 1000U)) {
         case GL_ALREADY_SIGNALED:
         case GL_CONDITION_SATISFIED:
             if (EGL_FALSE == eglSwapBuffers(display, surface)) {
                 errnum = get_egl_error();
                 break;
             }
             gpu_context->state = BUFFER_COMMANDS;
             break;
         case GL_TIMEOUT_EXPIRED:
             /* Do nothing. */
             break;
         case GL_WAIT_FAILED:
             errnum = get_gl_error();
             break;
         }
         break;
     }
 }

普遍的答案是错误的。Windows没有"错误地"报告CPU使用情况,哈哈。Opengl,与垂直同步,即使在渲染空白屏幕实际上是燃烧100%的一个线程的cpu。(你可以检查你的CPU温度)

,但解决方案很简单。在SwapBuffers之前或之后调用DwmFlush();