【问题标题】:GLX Vsync eventGLX 垂直同步事件
【发布时间】:2016-04-27 11:21:06
【问题描述】:

我想知道是否可以通过任何文件描述符捕获屏幕垂直同步事件并 [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 进行 vsync 的情况下同时出现撕裂效应。

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

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

【问题讨论】:

  • 请注意,我想让它成为单线程的。我可以创建一个单独的进程,它就像一个服务器,我可以连接到它并发送一个请求“当 vsync 发生时唤醒我”,这个进程可以阻止 glFinish,然后通过套接字或管道或其他方式发送一些东西,我会避免它也是。
  • 你到底为什么想要这个?这可能是the XY problem 的情况吗?
  • 我正在创建自己的库/api,我希望进程的所有传入事件只有一个同步点。

标签: c x11 framebuffer epoll glx


【解决方案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 标记)。

打开正确的 dri 设备也很困难(/dev/dri/card0/dev/dri/card1 或 ...?)

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

  1. 使用单独的线程来等待使用标准 GL 调用的 vsync。根据this page on the OpenGL wiki,您应该使用glXSwapIntervalEXT(1) 启用垂直同步,然后使用glXSwapBuffersglFinish 以确保垂直回溯确实发生。
  2. 然后,通知主线程发生了垂直回溯。您可以通过将数据发送到管道来完成此操作,或者在 Linux 上使用 eventfd

更新

您也许可以使用GLX_INTEL_swap_event extension

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

GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000

在“交换完成”事件的字段中返回:

GLX_EXCHANGE_COMPLETE_INTEL 0x8180
GLX_COPY_COMPLETE_INTEL 0x8181
GLX_FLIP_COMPLETE_INTEL 0x8182

...

客户端可以请求在窗口上接收交换完成 GLX 事件。 当接收到一个事件时,调用者可以通过检查 event_type 字段来确定发生了什么样的交换。

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

【讨论】:

  • 我已经看过这个博客和代码,我尝试按原样启动 modeset,它告诉我“drm 设备 /dev/dri/card0 不支持哑缓冲区”。我只有 /dev/dri/card0。我暂时没有在 drm 方面尝试更多,我想知道这可以通过 X11 扩展来实现......
  • @LewisAnesa 你不需要哑缓冲区。您根本不需要映射缓冲区;你只需要接收事件(翻页事件)。唯一悬而未决的问题是这些事件是否真的会交付。
  • 是的,我会在下班后(法国时区)继续这些调查,也许通过 glXSwapIntervalEXT(1) 激活垂直同步会导致这些事件由 dri 设备传递..?我还没有看到任何有关 libdrm 的文档,例如 xlib 文档,如果您看到了什么,请随时分享。
  • 噢...我使用fossies.org/dox/mesa-demos-8.3.0/glxgears_8c_source.html 进行测试并在 glXSwapBuffers(dpy, win);第 342 行,然后,惊喜!!! glXSwapBuffers 在大约 16 毫秒内阻塞。注意 glXQueryExtensionsString(dpy, scrnum);第 585 行不返回任何交换扩展。所以我想我不能双缓冲或获得 vsync ......
  • 嗯。事实证明,唯一真正的选择是使用线程(即在单独的线程中调用 glXSwapBuffers 并在完成时以某种方式向主线程发出信号)。
【解决方案2】:

不幸的是,从在网上寻找扩展和接口到轮询 vsync [1],我发现可能可用的最佳(资源最少)方法是以下:

  1. 查询 vtrace 速率(对于 60hz 监视器,每 16.666 毫秒)。
  2. nanosleep 延迟的一部分(比如 15 毫秒)取决于查询精度和垂直同步脉冲的预期方差。
  3. clock_gettime 我们剩余的时间配额和(可选)如果可以的话做一些实时计算。通常计算应在第 7 步完成。
  4. 使用下面提到的方法之一忙于等待垂直同步。添加pause说明。
  5. 立即clock_gettime 以确保下一个周期的准确睡眠延迟。
  6. 进行绘图或像素查询。
  7. 执行任何其他计算,使用线程、看门狗或实时计算。
  8. 重复第 2 步。

如果您信任自己的代码,您可以动态增加延迟时间(但过于激进,您会开始丢帧)。 如果您真的需要可靠的轮询并且根本无法自旋锁,您可以在另一个线程中完成所有这些操作。 nanosleep 应该确保内核不会在其生命周期的大部分时间里调度您的线程。

您必须校准延迟,但您可能会注意到它。 15ms 非常保守,几乎可以保证工作。 我使用的查询方法会产生大约 16.665-16.667 的差异,因此任何低于此的延迟都应该足够了。 这完全取决于您对nanosleep 实现和线程调度程序的信任程度。如果您的内核支持,请使用实时调度策略(或者不要担心任何这些,采取简单的方法并像其他人一样忙碌等待)。

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

查询Vblank频率

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

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

等待垂直同步

您可以在每帧之后设置一个计时器,例如在当前帧开始后约 15 毫秒(如果可以,使用 clock_nanosleep 和非相对时间),然后忙等待其余的直到 glXGetVideoSyncSGI 计数器改变价值。将计时器连接到轮询/事件循环应该很简单。

如果你足够勇敢的话,你也许可以在繁忙的循环中做一些实时计算。

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

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

如果一切都失败了,您可以在clock_gettime 上忙着等待。 nanosleep 曾经这样做,但现在你必须自己做。如果您使用此方法,您可能应该对众所周知的 vsync 值进行硬编码,而不是依赖查询(睡眠延迟的任何变化都可能导致丢帧,因为硬件的异步特性,所以说 vsync 无论如何都有自然变化,所以没有驱动程序支持的可靠 vsync 可能是不可能的)。

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

  • DRM_IOCTL_WAIT_VBLANK

Linux 特定的。 等待非常可靠,也不需要OpenGL。 正确挂起线程而不是忙于等待。 不幸的是,_DRM_VBLANK_SIGNAL 未实现,_DRM_VBLANK_EVENT 已损坏,因此阻塞等待仍然是唯一有效的用例。 DRM 实际上确实为手动交换缓冲区的应用程序提供了准确的 vsync 脉冲,但这几乎不能称为lightweight。此外,如果您无论如何都在交换缓冲区,您不妨使用glXSwapBuffers。 如果您需要高性能且不关心可移植性,建议您使用。

  • SGI_video_sync

跨平台。几乎普遍可用。这是我推荐的。

  • OML_sync_control

如果有的话推荐。

  • EXT_swap_control
  • SGI_swap_control
  • MESA_swap_control

我不会推荐这个。这是glXSwapBuffers 方法。大多数驱动程序都支持这一点,但如果驱动程序设置发生变化,它也是资源密集型的并且容易中断。大多数这些实现自旋锁。

结论

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

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

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

【讨论】:

    猜你喜欢
    • 2018-05-11
    • 2013-06-19
    • 1970-01-01
    • 1970-01-01
    • 2020-04-10
    • 1970-01-01
    • 2013-04-23
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多