避免等待SwapBuffers

时间:2011-04-29 08:39:06

标签: c++ winapi opengl

我发现OpenGL中的SwapBuffers只要显卡未完成或者等待V-Sync就会忙碌等待。

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

我发现回调函数如glutTimerFunc和glutIdleFunc可能对我有用,但我不想使用过剩。仍然,过剩必须以某种方式使用正常的gl函数来做到这一点,对吗?

是否有“glReadyToSwap”等功能?在那种情况下,我可以检查每毫秒左右,并确定我是否应该等待一段时间或进行交换。我也可以想象也许跳过SwapBuffers并编写我自己的类似功能,如果有人能指出我正确的方向,那就不要忙。等等。

2 个答案:

答案 0 :(得分:24)

SwapBuffers不是忙等待,它只是在驱动程序上下文中阻塞你的线程,这使得Windows错误地计算CPU使用率:Windows通过确定空闲进程获得多少CPU时间来计算CPU使用率+程序不在驱动程序上下文中花费多少时间。 SwapBuffers将阻塞驱动程序上下文,您的程序显然会从空闲进程中消耗掉CPU时间。但是你的CPU在时间上几乎没有做任何事情,调度程序很乐意等待将时间花在其他进程上。空闲进程OTOH除了立即将其时间放到系统的其余部分之外别无其他,因此调度程序会立即跳回到您的进程中,这会在驱动程序中阻止Windows计为“正在堵塞CPU”。如果您测量实际功耗或热量输出,对于简单的OpenGL程序,这将保持相当低的水平。

这种恼人的行为实际上是一个OpenGL FAQ!

只需为并行数据处理创建其他线程。将OpenGL保留在一个线程中,将数据处理保存在另一个线程中。如果你想降低报告的CPU使用率,在之后添加Sleep(0)或Sleep(1) SwapBuffers就可以了。 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阻塞而没有任何助手Sleeps,但是我给了一些其他工作线程关于那个时候通过共享上下文(比如更新纹理)在GPU上做东西我有垃圾收集器运行。准确计算它并不是真的必要,但是在SwapBuffers返回之前完成工作线程允许人们几乎立即开始渲染下一帧,因为大多数互斥锁已经解锁了。

答案 1 :(得分:0)

虽然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;
     }
 }