0

编辑:我在下面发布了一个很好的解决整个渲染器分离问题的方法。

我最近在多线程 X11 环境中使用 OpenGL。我找到了以下教程,它可以编译、链接并运行良好。

但是,在尝试根据自己的需要调整代码后,我遇到了一个奇怪的问题。

教程中XCreateWindow、glXCreateContext、XSelectInput、XSetWMProtocols的调用顺序如下:

param[i].win = XCreateWindow(param[i].d_, root, 200,200, 
                   300,200, 0, visInfo->depth, InputOutput, visInfo->visual,
                   CWColormap,
                   &windowAttr);
param[i].ctx = glXCreateContext(param[i].d_, visInfo,  NULL, True);
XSelectInput(d, param[i].win, StructureNotifyMask);
XSetWMProtocols(d, param[i].win, &(delMsg), 1);

请注意,XCreateWindow 和 XSelectInput/XSetWMProtocols 使用不同的显示连接。

但是,当更改调用顺序时

param[i].win = XCreateWindow(param[i].d_, root, 200,200, 
                   300,200, 0, visInfo->depth, InputOutput, visInfo->visual,
                   CWColormap,
                   &windowAttr);
XSelectInput(d, param[i].win, StructureNotifyMask);
XSetWMProtocols(d, param[i].win, &(delMsg), 1);
param[i].ctx = glXCreateContext(param[i].d_, visInfo,  NULL, True);

该程序失败

X Error of failed request: BadWindow (invalid Window parameter)
Major opcode of failed request: 2 (X_ChangeWindowAttributes)
Resource id in failed request: 0x5000002 Serial number of failed request: 17 Current serial number in output stream: 18

这似乎是由 XSetWMProtocols 引起的。

由于使用了不同的显示连接,如果整个事情一开始就不起作用,我不会感到惊讶。但不知何故,在调用 glXCreateContext 之后,一切似乎都神奇地好起来了。

我对 X11/GLX 编程比较陌生,我错过了什么吗?glXCreateContext 执行了什么样的魔法?还是发生了其他事情?或者也许我应该继续前进,因为 OpenGL 和多线程似乎总是会导致问题。

我的解决方案:

我很懒,只是使用教程中的方法。这一直有效,直到将 freetype 添加到我的项目中,这突然又给了我一个 BadWindow 崩溃。所以,即使一切看起来都很好,当你在不同的线程中工作时,X11 会在你不在的时候认真地处理一些内存。(不是我,我用 valgrind 检查过)

我目前的解决方案是 nm 评论的:我将所有内容都放入一个 GUI 线程(X11 和 GL/GLX 调用)中,其资源永远不可用于其他线程。但是,必须记住两件事,因为它可能会减慢渲染循环:

  • 缓慢的消息处理延迟渲染(如下 ilmale 所述)
  • 缓慢的渲染延迟消息处理(我担心)

第一个问题很容易解决。创建一个 stl deque 或列表或任何容器,您可以在其中为您的应用程序逻辑排队相关的 XEvent,然后从另一个线程中获取它们。只要确保您的 STL 是线程安全的,并且毫无疑问地实现您自己的队列。通过在容器大小上设置等待条件,您甚至可以模拟 XNextEvent 之类的阻塞调用。

第二个问题很棘手。您可能会争辩说,如果渲染器的速度为 1 fps 或更慢,那么游戏或应用程序无论如何都是无用的。那是真实的。但是,如果您能够处理一些终止信号(例如销毁窗口原子),即使您的速度为 0.1 fps,那将会很整洁。我能想到的唯一解决方案是在渲染每千个左右的精灵后检查新消息。将它们发送到您的容器并继续渲染。当然,在这种情况下,您永远不能让渲染线程随时运行用户脚本或其他未知代码。但我想,无论如何,这将使将渲染与其他线程分开的想法毫无意义。

希望这可以帮助。

4

2 回答 2

1

我基本上在跨平台项目中经历了多线程 X11 和 Win32 的相同试验。

我注意到的一件事是 X11 并没有像上面的帖子那样修改内存。当然,各种命令的顺序有些奇怪,但是一旦你做对了,它似乎就相当稳定了。

具体来说,几乎让我认输的一项是后台 GPU 处理!正是这种非常奇怪且难以捕捉的运行时竞争条件让我认为应该归咎于操作系统。

在将纹理发送到显示列表中的卡片后(咳,在实现 freetype 时也是如此),立即绘制资源有时会导致字体显示列表轻微损坏,即使是以后的绘制也是如此。显示列表本身已损坏,我什至求助于实现全局 OpenGL 锁定只是为了证明线程不是罪魁祸首。但为什么它会被破坏?操作系统?不,GPU。

我相信共享的 GLX 上下文会在某些卡上强制执行不同的行为,尤其是我系统上的 nvidia。不是其他线程导致了我的麻烦,而是 createContext 调用上的共享标志加上在使用资源之前缺少 glFinish() 。这和我将在下面解释的一些最佳实践。

在 99% 的运行中,即使使用多线程,它也可以在没有glFinish() 的情况下正常工作。只有在加载时才会发生这种情况,因此不断停止/重新启动应用程序最终会暴露它。如果它毫无问题地加载了所有内容,则该应用程序将从那里正常运行。如果有问题,图像会一直损坏,直到我重新加载它。

通过遵守这些简单的规则,所有问题都得到了解决。

  1. 在非 main() 线程中创建第二个 GLContext。(不要在同一个线程中创建两个上下文并给第二个线程指针,这样不稳定)
  2. 在第二个线程中加载资源时,在将结果放入要使用的队列之前添加一个 glFinish() 。(简单地说,glFinish() 在使用资源之前)
  3. 在第二个线程内的第二个上下文中调用 makeCurrent() 后,调用 getCurrentContext() 函数并等待它为非 NULL,然后再让一线程执行其他 OpenGL 资源加载或调用。有时在第二个线程它(makeCurrent)返回但getCurrentContext()在某些视频卡上可能仍为NULL一会儿。不确定驱动程序为什么或如何让这种情况发生,但检查可以防止应用程序崩溃。

我在我的 6-Thread+ 应用程序中实施了这些做法,奇怪的 1-off 腐败问题消失了,再也没有回来。

事实证明,从我的经验来看,X11 并不是那么卑鄙……显卡是,但它真的比什么都挑剔。在我的情况下,我什至使用 typedefs 使用非特定函数编写 Linux/Windows 代码,这会使事情进一步复杂化,如果采取适当的预防措施,这仍然是一个易于管理的野兽:)。

如果您问我,这很古怪,但不是“不惜一切代价避免”的问题。我希望这篇文章有帮助,祝你好运!

于 2012-09-19T04:51:11.400 回答
0

我同意 nm,我是编写教程的人。:D 我试图解决的问题是将事件循环与渲染循环分离,这样我就可以在不影响渲染的情况下重播事件,反之亦然。我正在编写一个 LUA 框架,而我的“processMessage(event)”函数可能会调用用户定义的 Lua 函数。

在编写事件循环时,我遇到了很多问题,就像您遇到的那样,我还尝试了在 Fedora 上工作但在 Ubuntu 上崩溃的 XCB,经过一番头疼后,我找到了具有不同显示的解决方案(对于 X 服务器是像服务不同的进程)与共享的 GlContext 和另一个用于加载的线程(纹理和网格)。

回到你的问题:

XSetWMProtocols(...)

想要创建窗口的相同显示,但仅在某些版本的 X 上。这就是我现在使用 Qt 的原因。

于 2012-07-02T21:58:07.823 回答