GLX Vsync事件

时间:2016-04-27 11:21:06

标签: c x11 framebuffer epoll glx

我想知道我是否可以通过任何文件描述符捕获屏幕vsync事件并[select |民意调查| epoll]它。

通常情况下,如果我是对的,glXSwapBuffers()不会阻止进程,所以我可以这样做:

int init() {
    create epollfd;
    add Xconnection number to it;
    add some other fd like socket timer tty etc...
    possibly add a vsync fd like dri/card0 or fb0 or other???
    return epollfd;
}

main() {
    int run = 1;
    int epollfd = init();

    while(run) {
        epoll_wait(epollfd, ...) {

        if(trigedfd = socket) {
            do network computing;
        }

        if(trigedfd = timer) {
            do physics computing;
        }

        if(trigedfd = tty) {
            do electronic communications;
        }

        if(trigedfd = Xconnection number) {
            switch(Xevent) {
                case key event:
                    do key computing;
                case mouse event:
                    do mouse computing;
                case vsync???:
                    do GL computings;
                    glXSwapBuffers();
            }
        }

        if(trigedfd = dri/card0 or fb0 or other???) {
            do GL computings;
            glXSwapBuffers();
        }
    }
}

因此,无论何时发生vsync事件,我都可以触发任何事件,并且在我仅使用X绘图函数和可能GL for vsync的情况下避免同时撕裂效果。

libdrm可以帮帮我吗?更普遍的问题是:

那么我必须使用什么来捕获vsync事件以及如何在这个fd上设置shure,发生的事件是vsync事件?

2 个答案:

答案 0 :(得分:1)

看起来您可以使用libdrm API查看vsync事件。请参阅this blog entry,特别是this example code。代码中的注释解释了它的工作原理:

/* (...)
 * The DRM_MODE_PAGE_FLIP_EVENT flag tells drmModePageFlip() to send us a
 * page-flip event on the DRM-fd when the page-flip happened. The last argument
 * is a data-pointer that is returned with this event.
 * (...)
 */

您需要设置一个页面翻转事件处理程序,以便在发生vsync时收到通知,这将由drmHandleEvent方法(来自libdrm)调用,当drm文件中存在活动时,您可以调用该方法描述符。

但是,将所有这些映射到X客户端可能很难或不可能。 可能是你可以自己打开drm设备,只是监听vsync事件(不试图设置模式等),但这也可能是不可能的。相关代码:

drmEventContext ev;
memset(&ev, 0, sizeof(ev));
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = modeset_page_flip_event;

// When file descriptor input is available:
drmHandleEvent(fd, &ev);
// If above works, "modeset_page_flip_event" will be called on vertical refresh.

问题是,如果您实际发出了页面翻转(缓冲交换)请求,则似乎只会生成页面翻转事件。可能是X服务器发出了这样的请求,但它甚至没有标记它想要在vsync实际发生时得到通知(即使用DRM_MODE_PAGE_FLIP_EVENT标志)。

还有打开正确驱动设备的难度(/dev/dri/card0/dev/dri/card1或......?)

鉴于上述所有问题的难度/不可靠性/一般不可行性,最简单的解决方案可能是:

  1. 使用单独的线程等待使用标准GL调用的vsync。根据{{​​3}},您应该使用glXSwapIntervalEXT(1)启用vsync,然后使用glXSwapBuffersglFinish来确保实际发生垂直回溯。
  2. 然后,通知主线程发生垂直回扫。您可以通过将数据发送到管道来实现此目的,或者在Linux上使用eventfd
  3. <强>更新

    您可以使用this page on the OpenGL wiki

      

    接受glXSelectEvent参数并在glXGetSelectedEvent参数中返回:

         

    GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000

         

    返回&#34;交换完成&#34;事件:

         

    GLX_EXCHANGE_COMPLETE_INTEL 0x8180
      GLX_COPY_COMPLETE_INTEL 0x8181
      GLX_FLIP_COMPLETE_INTEL 0x8182

    ...

      

    客户端可以要求在窗口上接收交换完整的GLX事件。   收到事件后,调用者可以通过检查event_type字段来确定发生了什么类型的交换。

    这意味着,如果支持扩展,您可以通过常规X事件接收交换通知(如果启用了vsync,则对应于垂直回扫),您将在X连接文件描述符上看到。

答案 1 :(得分:0)

不幸的是,从网上寻找扩展和接口以轮询vsync [1] ,我发现最好的(最少资源密集型)方法是以下:

  1. 查询vtrace速率(对于60Hz的显示器,每16.666ms)。
  2. nanosleep的部分延迟(例如15ms)取决于查询精度和垂直同步脉冲的预期方差。
  3. clock_gettime我们剩余的时间配额,并且(如果可以)(可选)进行一些实时计算。通常,计算应在第7步完成。
  4. 使用以下其中一种方法忙于等待vsync。添加pause指令。
  5. 立即clock_gettime以确保下一个周期的准确睡眠延迟。
  6. 进行绘图或像素查询。
  7. 进行任何其他计算,使用线程,看门狗或实时计算。
  8. 重复步骤2。

如果您信任自己的代码,则可以动态地增加延迟时间(但过于激进并开始丢帧)。 如果您确实需要可靠的民意测验并且根本无法自旋锁,则可以在另一个线程中进行所有这些操作。 nanosleep应该确保内核在整个生命周期中都不会调度您的线程。

您必须校准延迟,但您可能可以接受它。 15ms非常保守,几乎可以保证正常工作。 我使用的查询方法的结果大约在16.665-16.667之间,因此任何低于该值的延迟都应足够。 这完全取决于您对nanosleep实现和线程调度程序的信任程度。如果您的内核支持,请使用实时调度策略(或者不用担心任何其他问题,像其他人一样轻松地走出去并忙于等待)。

这是大多数屏幕录像机使用的方法。

查询Vblank频率

如果您的驱动程序支持,则可以重复调用glXGetVideoSyncSGI(来自GLX_SGI_video_sync分机),并与clock_gettime进行时间查询空白速率(例如16.666ms)。 查找垂直同步速率的替代方法是glXGetSwapIntervalMESAglXQueryDrawable(dpy, drawable, GLX_SWAP_INTERVAL_EXT, &interval)(分别来自GLX_MESA_swap_controlGLX_EXT_swap_control)。 GLX_SGI_video_sync的好处是您只需要一个扩展名(请参阅下一节)。

来自glXGetSyncValuesOML扩展名的

GLX_OML_sync_control返回相同的计数器,但效率可能较低,因为它必须获取其他不相关的值。它可能会返回更准确的垂直同步速率。它在我的系统上也不可用,所以我不知道它的便携性。

等待Vsync

您可以在当前帧开始后的每帧之后设置一个定时器,例如〜15ms(如果可以,请使用clock_nanosleep和非相对时间),然后忙于等待其余的时间,直到{{ 1}}计数器更改值。那么将计时器连接到轮询/事件循环应该很简单。

如果您足够勇敢,则可以在繁忙循环中进行一些实时计算。

glXGetVideoSyncSGI中的

glXWaitForMscOML做同样的事情,可能有一些优化,但是它在我的系统上不可用,如果它像其他所有东西一样忙于等待,我也不会感到惊讶。我也不知道它有多可靠。当然,还有其他一些方法也可以等待vsync(MESA / DRM / KMS),但是它们比GLX_OML_sync_control的可移植性更差,而且它们通常又忙于等待。

即使没有可用的帧计数器,您也可以假设GLX_SGI_video_sync会阻止并且仍然执行类似的操作(请注意,在我所知道的所有驱动程序上,用户都可以手动关闭阻止行为。请检查{{1 }}尝试覆盖此行为)。不过,此时最好在另一个线程中循环调用glXSwapBuffers

如果其他所有方法均失败,则可以改为在GLX_EXT_swap_control上等待。 glXSwapBuffers曾经这样做,但如今您必须自己做。如果您使用的是这种方法,则可能应该对著名的vsync值进行硬编码,而不要依赖于查询(睡眠延迟的任何差异都可能导致丢帧,由于硬件的异步特性,该vsync仍然具有自然差异,因此没有驱动程序支持的可靠vsync可能是不可能的。

“ compton”开源合成器管理器提到了以下等待clock_gettime的方法:

  • nanosleep

特定于Linux。 等待非常可靠,也不需要OpenGL。 适当地挂起线程,而不是忙于等待。 不幸的是,vsync未实现并且DRM_IOCTL_WAIT_VBLANK被破坏,因此阻塞等待仍然是唯一有效的用例。 DRM实际上为手动交换缓冲区的应用程序提供了准确的vsync脉冲,但这很难称为_DRM_VBLANK_SIGNAL。另外,如果仍在交换缓冲区,则最好使用_DRM_VBLANK_EVENT。 如果您需要高性能并且不关心可移植性,则推荐使用。

  • lightweight

跨平台。几乎普遍可用。这就是我的建议。

  • glXSwapBuffers

推荐(如果有)。

  • SGI_video_sync
  • OML_sync_control
  • EXT_swap_control

我不建议这样做。这是SGI_swap_control方法。大多数驱动程序都支持此功能,但是它也会占用大量资源,并且如果驱动程序设置发生更改,则容易中断。这些实现大多数都是自旋锁。

结论

不幸的是,似乎没有一种资源友好的方式可以在不久的将来轮询vsync。视频驱动程序显然必须跟踪vsync才能知道何时发送下一帧,因此没有技术上的原因,为什么没有简单的接口可以在任何地方轮询vsync脉冲,但是不幸的是,Linux,nvidia和khronos的共识似乎是“没人需要这个”。

通常,vtrace脉冲对于像素查询非常重要(案例要点:实际上,每个合成器管理器都有关于vsync的错误报告或邮件列表线程),但它也可用于实时计算。

[1] vsync a.k.a. vtrace a.k.a.缓冲区交换a.k.a.帧计数器增量a.k.a.帧往返