从后台线程调用时,glewInit失败

时间:2017-09-21 17:40:14

标签: c++ opengl glfw glew

在我的应用程序中,我有一个隐藏的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. 修复错误
  4. 如果情况并非如此,您会建议什么作为解决方法?

2 个答案:

答案 0 :(得分:3)

  
      
  1. 这个想法是否可行(从多个线程渲染到一个窗口)?
  2.   

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

实际正确的答案是否定的。

OpenGL设计用于每个上下文的单个线程。如果你试图同时使用多个线程,那么它的API几乎没有任何表现,并且尽管通过从线程到线程传递上下文所有权完全可以使OpenGL表现自己,确保在多线程场景中,只有一个线程同时与上下文交互,我无法想象你实际上通过这样做会显着提高性能。

一般来说,我处理多线程渲染的方法是构建一个多线程可以写入的Message Queue,但只有渲染线程可以读取+执行。

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

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

答案 1 :(得分:-1)

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

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

  1. 第一个主题:创建窗口glewInit,其他openGL代码初始化渲染&amp;完成后渲染你的东西,wglMakeCurrent( NULL, NULL )

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

  3. 请确保您永远不会在不同的线程上同时使用您的OpenGL上下文或该窗口的HDC

    这个想法有多好......我不太喜欢它。 OpenGL和GPU驱动程序的用户模式部分非常庞大,L1-L2缓存是每个核心,当核心之间共享缓存行时,L3缓存变慢~2倍。

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

    如果我正在设计那种东西,我就创建了一个专用的屏幕外渲染线程。并实现一个作业队列来渲染作业。

    如果你希望这些其他线程等待作业结果,一个简单的方法是SendMessage WinAPI带有自定义窗口消息,将指针传递给消息参数[s]中的作业。无论如何,渲染线程都需要Windows消息泵。

    但是,如果您希望其他线程轮询渲染结果,则可以使用PostMessage来提交作业,例如(std::queue由关键部分保护)每个线程用于投票结果。