从后台线程调用时 glewInit 失败

glewInit fails when called from a background thread

在我的应用程序中,我有一个隐藏的 GLFW window 用于离屏渲染。我想使用此 window 从多个后台线程进行渲染。保证一次只有一个线程使用 window。

在后台线程上的每个渲染操作之前,我执行以下操作:

glfwMakeContextCurrent((GLFWwindow *)window);
glewExperimental = withGlewExperimental ? GL_TRUE : GL_FALSE;
const auto glewInitStatus = glewInit();
if (glewInitStatus != GLEW_OK)
    LOG(ERROR) << "Could not initialize glew, error: " << glewGetErrorString(glewInitStatus);

第一次执行正常,但是当第二个线程获取上下文时 glewInit 失败。

Could not initialize glew, error: Missing GL version

当我为每个线程创建一个新的隐藏 window 时,这似乎没有重现,但是 GLFW 禁止在主线程之外创建 windows,并维护每个线程的 windows 池使实现复杂化并产生许多不必要的 windows。这就是为什么我希望所有线程都呈现相同的 window.

有个叫GLEW_MX的东西支持多上下文,但它只存在于GLEW的旧版本,2.0.0之前,以及我的GLEW版本没有这个选项。

所以,我想知道以下问题的答案:

  1. 这个想法是否可行(从多个线程渲染到单个 window)?
  2. 如果是这样,我该如何修复 GLEW
  3. 的错误
  4. 如果不是这种情况,您有什么解决方法建议?
  1. Is this idea viable at all (rendering to a single window from multiple threads)?

技术上正确答案是肯定的。

实际正确答案是否定的。

OpenGL 设计用于每个上下文的单个线程。如果您尝试同时使用多个线程,它的 API 几乎没有任何东西会正确运行,虽然完全有可能通过将上下文所有权从一个线程传递到另一个线程来让 OpenGL 自行运行,确保在一个多线程场景,一次只有一个线程与上下文交互,我无法想象这样一个场景你实际上会通过这样做获得显着的性能提升。

一般来说,我处理多线程渲染的方式是构建一个消息队列,多个线程可以写入,但只有渲染线程可以读取和执行。

class Renderer {
    //Roll your own or find a good implementation somewhere online
    concurrent::queue<std::function<void()>> rendering_queue; 
    std::thread rendering_thread;
    GLFWwindow * window;
    /*...*/
public:
    Renderer(GLFWwindow * window) : window(window) {
        rendering_thread = std::thread([this]{
            glfwMakeContextCurrent(window);
            glewInit(); //Check for error if necessary
            while(!glfwWindowShouldClose(window)) {
                draw();
                glfwSwapBuffers(window);
            }
        });
    }

    Renderer(Renderer const&) = delete;

    ~Renderer() noexcept {
        glfwSetWindowShouldClose(window, GLFW_TRUE);
        rendering_thread.join();
    }

    void push_task(std::function<void()> func) {
        rendering_queue.push(std::move(func));
    }

    void draw() {
        std::function<void()> func;
        while(rendering_queue.try_pop(func)) func();
        /*Normal Rendering Tasks*/
    }
};

int main() {
    glfwInit();
    GLFWwindow * window = glfwCreateWindow(800, 600, "Hello World!", nullptr, nullptr);
    Renderer renderer(window);

    std::thread circle_drawer{[&renderer]{
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
        renderer.push_task([]{/*Draw a Circle*/});
    }};
    circle_drawer.detach();

    std::thread square_drawer{[&renderer]{
        renderer.push_task([]{/*Draw a Square*/});
        renderer.push_task([]{/*Draw a Square*/});
        renderer.push_task([]{/*Draw a Square*/});
        renderer.push_task([]{/*Draw a Square*/});
    }};
    square_drawer.detach();

    /*Etc...*/

    while(!glfwWindowShouldClose(window)) {
        glfwPollEvents();
    }
}

显然我抽象掉了很多细节,但这主要是因为您的问题非常广泛。对于至少在表面上需要多线程渲染能力的大多数应用程序,该模型应该适用并且具有可塑性。

IMO 你不需要每个胎面 glewInit。据我所知,每个应用程序只需要调用一次。

我从未尝试过,但我认为您需要这样的工作流程。

  1. 第一个线程:创建 window、glewInit、其他 openGL 代码以初始化渲染并渲染您的内容,wglMakeCurrent( NULL, NULL ) 完成后。

  2. 其他线程:wglMakeCurrent( dc, glrc ),其他 openGL 代码渲染您的内容,wglMakeCurrent( NULL, NULL ) 完成后。

只要确保您永远不会在不同线程上同时

使用您的 OpenGL 上下文或 Window' HDC

这个主意好……我不太喜欢。 OpenGL 和 GPU 驱动程序的用户模式部分很大,L1-L2 缓存是每个内核的,当内核之间共享缓存行时,L3 缓存会慢 ~2 倍。

如您所见,您只能同时从一个线程使用 GL 上下文 + Window 的 HDC。

如果我要设计那种东西,我会创建一个专用的离屏渲染线程。并为渲染作业实施作业队列。

如果您想让这些其他线程休眠以等待作业结果,一个简单的方法是 SendMessage 带有自定义 window 消息的 WinAPI,在消息参数中传递指向作业的指针[s ].渲染线程无论如何都需要 Windows 消息泵。

如果您希望其他线程轮询渲染结果,您可以使用 PostMessage 来提交作业,例如(std::queue 受临界区保护)每个线程轮询结果。