GLX 垂直同步事件
GLX Vsync event
我想知道是否可以通过任何文件描述符和 [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 事件?
看来您可以使用 libdrm API 查看 vsync 事件。参见 this blog entry, and in particular 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.
* (...)
*/
您需要设置一个页面翻转事件处理程序,以便在发生垂直同步时得到通知,它将由 drmHandleEvent
方法(来自 libdrm)调用,当有 activity 在 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
或...?)
鉴于上述所有 difficulty/unreliability/general 不可行,最简单的解决方案可能是:
- 使用单独的线程等待使用标准 GL 调用的垂直同步。根据 this page on the OpenGL wiki 你应该使用
glXSwapIntervalEXT(1)
启用垂直同步,然后 glXSwapBuffers
和 glFinish
确保垂直回扫实际发生。
- 然后,通知主线程发生了垂直回溯。您可以通过将数据发送到管道或在 Linux 上使用
eventfd
. 来完成此操作
更新:
您可以使用 GLX_INTEL_swap_event extension:
Accepted by the parameter of glXSelectEvent and returned in the parameter of glXGetSelectedEvent:
GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000
Returned in the field of a "swap complete" event:
GLX_EXCHANGE_COMPLETE_INTEL 0x8180
GLX_COPY_COMPLETE_INTEL 0x8181
GLX_FLIP_COMPLETE_INTEL 0x8182
...
A client can ask to receive swap complete GLX events on a window.
When an event is received, the caller can determine what kind of swap occurred by checking the event_type field.
这意味着,如果支持扩展,您可以通过常规 X 事件接收交换通知(如果启用了垂直同步,则对应于垂直回溯),您将在 X 连接文件描述符上看到这些通知。
不幸的是,通过在网络上寻找扩展和接口来轮询 vsync [1],我找到了可能的最佳(最少资源密集型)方法可用的是:
- 查询 vtrace 速率(对于 60hz 显示器每 16.666ms)。
nanosleep
该延迟的一部分(例如 15 毫秒)取决于查询精度和 vsync 脉冲的预期方差。
clock_gettime
我们的剩余时间配额和(可选)如果可以的话进行一些实时计算。通常计算应该在第 7 步完成。
- 使用下述方法之一忙等待 vsync。添加
pause
说明。
- 立即
clock_gettime
以确保下一个周期的睡眠延迟准确。
- 进行绘图或像素查询。
- 做任何其他计算,使用线程、看门狗或实时计算。
- 重复步骤 2。
如果您相信自己的代码,您可以动态地增加延迟时间(但过于激进会开始丢帧)。
如果你真的需要一个可靠的轮询并且根本不能自旋锁,你可以在另一个线程中完成所有这些。 nanosleep
应该确保内核不会在其生命周期的大部分时间里安排您的线程。
您必须校准延迟,但您可能可以观察到它。 15ms 是非常保守的并且几乎可以保证工作。
我使用的查询方法产生大约 16.665-16.667 的方差,因此低于该值的任何延迟都应该足够了。
这完全取决于您对 nanosleep
实现和线程调度程序的信任程度。如果您的内核支持,请使用实时调度策略(或者不要担心任何这些,像其他人一样采取简单的方法并忙等待)。
这是大多数屏幕录像机使用的方法。
查询Vblank频率
如果您的驱动程序支持,您可以重复调用glXGetVideoSyncSGI
(从GLX_SGI_video_sync
扩展名)并使用clock_gettime
计时以查询vblank率(例如16.666ms)。
查找 vsync 率的替代方法是 glXGetSwapIntervalMESA
和 glXQueryDrawable(dpy, drawable, GLX_SWAP_INTERVAL_EXT, &interval)
(分别来自 GLX_MESA_swap_control
和 GLX_EXT_swap_control
)。 GLX_SGI_video_sync
的好处是您只需要一个扩展名(见下一节)。
glXGetSyncValuesOML
来自 GLX_OML_sync_control
扩展 return 是相同的计数器,但可能效率较低,因为它必须获取其他不相关的值。它可能 return 更准确的垂直同步率。它在我的系统上也不可用,所以我不知道它的便携性如何。
等待垂直同步
你可以在每一帧之后设置一个计时器,比如在当前帧开始后约 15 毫秒(如果可以的话,使用 clock_nanosleep
和非相对时间),然后忙等待剩下的直到glXGetVideoSyncSGI
计数器更改值。然后将计时器连接到 poll/event 循环应该是微不足道的。
如果你足够勇敢,你也许可以在繁忙的循环中做一些实时计算。
glXWaitForMscOML
from GLX_OML_sync_control
做同样的事情并且可能有一些优化,但它在我的系统上不可用,如果它像其他所有东西一样只是忙等待我也不会感到惊讶.也不知道可信度如何。当然还有其他等待 vsync 的方法 (MESA/DRM/KMS),但它们的可移植性甚至不如 GLX_SGI_video_sync
,而且它们通常都是忙等待。
即使没有可用的帧计数器,您也可以假设 glXSwapBuffers
阻塞并仍然做类似的事情(请注意,在我所知道的所有驱动程序上,用户可以手动关闭阻塞行为。查看 GLX_EXT_swap_control
尝试覆盖此行为)。不过,此时最好在另一个线程中循环调用 glXSwapBuffers
。
如果一切都失败了,您可以在 clock_gettime
上忙等待。 nanosleep
以前是这样做的,但是现在你必须自己做。如果您使用这种方法,您可能应该对众所周知的 vsync 值进行硬编码,而不是依赖查询(睡眠延迟的任何变化都可能导致丢帧,也就是说由于硬件的异步性质,vsync 无论如何都有自然变化,所以没有驱动程序支持的可靠垂直同步可能是不可能的)。
"compton"开源合成器管理器提到了以下等待vsync
的方法:
DRM_IOCTL_WAIT_VBLANK
Linux-特定。
等待非常可靠,也不需要 OpenGL。
正确挂起线程而不是忙等待。
不幸的是,_DRM_VBLANK_SIGNAL
没有实现并且 _DRM_VBLANK_EVENT
被破坏了,所以阻塞等待仍然是唯一有效的用例。
DRM 实际上 确实 为手动交换缓冲区的应用程序提供准确的垂直同步脉冲,但这很难被称为 lightweight
。另外,如果你无论如何都要交换缓冲区,你也可以只使用 glXSwapBuffers
。
如果您需要高性能且不关心便携性,则推荐使用。
SGI_video_sync
跨平台。几乎普遍可用。这是我推荐的。
OML_sync_control
如果有的话推荐。
EXT_swap_control
SGI_swap_control
MESA_swap_control
我不推荐这个。这是 glXSwapBuffers
方法。大多数驱动程序都支持这一点,但它也是资源密集型的,并且如果驱动程序设置发生变化,则很容易中断。大多数这些实现自旋锁。
结论
不幸的是,似乎不会很快有一种资源友好的方式来轮询 vsync。视频驱动程序显然必须跟踪 vsync 以知道何时发送下一帧,因此没有技术原因没有简单的接口可以在任何地方轮询 vsync 脉冲,但不幸的是,linux、nvidia 和khronos 似乎是 "nobody needs this".
通常 vtrace 脉冲对于像素查询非常重要(案例点:几乎每个合成器管理器都有关于 vsync 的错误报告或邮件列表线程),但它也可用于实时计算。
[1] 垂直同步 a.k.a。 vtrace a.k.a。缓冲区交换 a.k.a。帧计数器增量 a.k.a。帧往返
我想知道是否可以通过任何文件描述符和 [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 事件?
看来您可以使用 libdrm API 查看 vsync 事件。参见 this blog entry, and in particular 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.
* (...)
*/
您需要设置一个页面翻转事件处理程序,以便在发生垂直同步时得到通知,它将由 drmHandleEvent
方法(来自 libdrm)调用,当有 activity 在 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
或...?)
鉴于上述所有 difficulty/unreliability/general 不可行,最简单的解决方案可能是:
- 使用单独的线程等待使用标准 GL 调用的垂直同步。根据 this page on the OpenGL wiki 你应该使用
glXSwapIntervalEXT(1)
启用垂直同步,然后glXSwapBuffers
和glFinish
确保垂直回扫实际发生。 - 然后,通知主线程发生了垂直回溯。您可以通过将数据发送到管道或在 Linux 上使用
eventfd
. 来完成此操作
更新:
您可以使用 GLX_INTEL_swap_event extension:
Accepted by the parameter of glXSelectEvent and returned in the parameter of glXGetSelectedEvent:
GLX_BUFFER_SWAP_COMPLETE_INTEL_MASK 0x04000000
Returned in the field of a "swap complete" event:
GLX_EXCHANGE_COMPLETE_INTEL 0x8180
GLX_COPY_COMPLETE_INTEL 0x8181
GLX_FLIP_COMPLETE_INTEL 0x8182
...
A client can ask to receive swap complete GLX events on a window. When an event is received, the caller can determine what kind of swap occurred by checking the event_type field.
这意味着,如果支持扩展,您可以通过常规 X 事件接收交换通知(如果启用了垂直同步,则对应于垂直回溯),您将在 X 连接文件描述符上看到这些通知。
不幸的是,通过在网络上寻找扩展和接口来轮询 vsync [1],我找到了可能的最佳(最少资源密集型)方法可用的是:
- 查询 vtrace 速率(对于 60hz 显示器每 16.666ms)。
nanosleep
该延迟的一部分(例如 15 毫秒)取决于查询精度和 vsync 脉冲的预期方差。clock_gettime
我们的剩余时间配额和(可选)如果可以的话进行一些实时计算。通常计算应该在第 7 步完成。- 使用下述方法之一忙等待 vsync。添加
pause
说明。 - 立即
clock_gettime
以确保下一个周期的睡眠延迟准确。 - 进行绘图或像素查询。
- 做任何其他计算,使用线程、看门狗或实时计算。
- 重复步骤 2。
如果您相信自己的代码,您可以动态地增加延迟时间(但过于激进会开始丢帧)。
如果你真的需要一个可靠的轮询并且根本不能自旋锁,你可以在另一个线程中完成所有这些。 nanosleep
应该确保内核不会在其生命周期的大部分时间里安排您的线程。
您必须校准延迟,但您可能可以观察到它。 15ms 是非常保守的并且几乎可以保证工作。
我使用的查询方法产生大约 16.665-16.667 的方差,因此低于该值的任何延迟都应该足够了。
这完全取决于您对 nanosleep
实现和线程调度程序的信任程度。如果您的内核支持,请使用实时调度策略(或者不要担心任何这些,像其他人一样采取简单的方法并忙等待)。
这是大多数屏幕录像机使用的方法。
查询Vblank频率
如果您的驱动程序支持,您可以重复调用glXGetVideoSyncSGI
(从GLX_SGI_video_sync
扩展名)并使用clock_gettime
计时以查询vblank率(例如16.666ms)。
查找 vsync 率的替代方法是 glXGetSwapIntervalMESA
和 glXQueryDrawable(dpy, drawable, GLX_SWAP_INTERVAL_EXT, &interval)
(分别来自 GLX_MESA_swap_control
和 GLX_EXT_swap_control
)。 GLX_SGI_video_sync
的好处是您只需要一个扩展名(见下一节)。
glXGetSyncValuesOML
来自 GLX_OML_sync_control
扩展 return 是相同的计数器,但可能效率较低,因为它必须获取其他不相关的值。它可能 return 更准确的垂直同步率。它在我的系统上也不可用,所以我不知道它的便携性如何。
等待垂直同步
你可以在每一帧之后设置一个计时器,比如在当前帧开始后约 15 毫秒(如果可以的话,使用 clock_nanosleep
和非相对时间),然后忙等待剩下的直到glXGetVideoSyncSGI
计数器更改值。然后将计时器连接到 poll/event 循环应该是微不足道的。
如果你足够勇敢,你也许可以在繁忙的循环中做一些实时计算。
glXWaitForMscOML
from GLX_OML_sync_control
做同样的事情并且可能有一些优化,但它在我的系统上不可用,如果它像其他所有东西一样只是忙等待我也不会感到惊讶.也不知道可信度如何。当然还有其他等待 vsync 的方法 (MESA/DRM/KMS),但它们的可移植性甚至不如 GLX_SGI_video_sync
,而且它们通常都是忙等待。
即使没有可用的帧计数器,您也可以假设 glXSwapBuffers
阻塞并仍然做类似的事情(请注意,在我所知道的所有驱动程序上,用户可以手动关闭阻塞行为。查看 GLX_EXT_swap_control
尝试覆盖此行为)。不过,此时最好在另一个线程中循环调用 glXSwapBuffers
。
如果一切都失败了,您可以在 clock_gettime
上忙等待。 nanosleep
以前是这样做的,但是现在你必须自己做。如果您使用这种方法,您可能应该对众所周知的 vsync 值进行硬编码,而不是依赖查询(睡眠延迟的任何变化都可能导致丢帧,也就是说由于硬件的异步性质,vsync 无论如何都有自然变化,所以没有驱动程序支持的可靠垂直同步可能是不可能的)。
"compton"开源合成器管理器提到了以下等待vsync
的方法:
DRM_IOCTL_WAIT_VBLANK
Linux-特定。
等待非常可靠,也不需要 OpenGL。
正确挂起线程而不是忙等待。
不幸的是,_DRM_VBLANK_SIGNAL
没有实现并且 _DRM_VBLANK_EVENT
被破坏了,所以阻塞等待仍然是唯一有效的用例。
DRM 实际上 确实 为手动交换缓冲区的应用程序提供准确的垂直同步脉冲,但这很难被称为 lightweight
。另外,如果你无论如何都要交换缓冲区,你也可以只使用 glXSwapBuffers
。
如果您需要高性能且不关心便携性,则推荐使用。
SGI_video_sync
跨平台。几乎普遍可用。这是我推荐的。
OML_sync_control
如果有的话推荐。
EXT_swap_control
SGI_swap_control
MESA_swap_control
我不推荐这个。这是 glXSwapBuffers
方法。大多数驱动程序都支持这一点,但它也是资源密集型的,并且如果驱动程序设置发生变化,则很容易中断。大多数这些实现自旋锁。
结论
不幸的是,似乎不会很快有一种资源友好的方式来轮询 vsync。视频驱动程序显然必须跟踪 vsync 以知道何时发送下一帧,因此没有技术原因没有简单的接口可以在任何地方轮询 vsync 脉冲,但不幸的是,linux、nvidia 和khronos 似乎是 "nobody needs this".
通常 vtrace 脉冲对于像素查询非常重要(案例点:几乎每个合成器管理器都有关于 vsync 的错误报告或邮件列表线程),但它也可用于实时计算。
[1] 垂直同步 a.k.a。 vtrace a.k.a。缓冲区交换 a.k.a。帧计数器增量 a.k.a。帧往返