【问题标题】:glewInit fails when called from a background thread从后台线程调用 glewInit 失败
【发布时间】:2017-09-21 17:40:14
【问题描述】:

在我的应用程序中,我有一个隐藏的 GLFW 窗口,用于进行屏幕外渲染。我想使用这个窗口从几个后台线程进行渲染。保证一次只有一个线程在使用该窗口。

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

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

当我为每个线程创建一个新的隐藏窗口时,这似乎不会重现,但GLFW 禁止在主线程之外创建窗口,并且为每个线程维护窗口池会使实现复杂化并创建一个很多不必要的窗户。这就是为什么我希望所有线程都渲染到同一个窗口。

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

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

  1. 这个想法是否可行(从多个线程渲染到单个窗口)?
  2. 如果是这种情况,我该如何解决GLEW 的错误
  3. 如果不是这样,您会建议什么解决方法?

【问题讨论】:

  • 只是猜测,但您的描述让我想起了这个英特尔驱动程序错误What is the proper OpenGL initialisation on Intel HD 3000?
  • 亲爱的投票者,你能解释一下这个话题是怎么回事吗?我很困惑,那么 Stackoverflow 上的主题是什么?在我看来,这是一个合法的通用编程/软件开发问题。
  • @Spektre 谢谢你的建议,但我认为情况并非如此;我不是在同一个应用程序中创建多个上下文,而是使用来自多个线程的单个上下文。

标签: c++ opengl glfw glew


【解决方案1】:
  1. 这个想法是否可行(从多个线程渲染到单个窗口)?

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

基本正确答案是否定的。

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();
    }
}

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

【讨论】:

  • 谢谢,这是有道理的。我重写了应用程序,因此我从不使用来自多个线程的单个上下文,问题就消失了。
【解决方案2】:

IMO 您不需要为每个胎面使用glewInit。 AFAIK,您只需在每个应用程序中调用一次。

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

  1. 第一个线程:创建窗口,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 WinAPI 使用自定义窗口消息,在消息参数[s] 中传递指向作业的指针。无论如何,渲染线程都需要 Windows 消息泵。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-17
    相关资源
    最近更新 更多