Создание структур OpenGL в многопоточной программе?

Я пытаюсь сделать следующее в физическом движке, который я создаю:

У вас есть 2 потока, один для мировой логики, один для рендеринга.

Основной поток (поток, из которого создаются другие потоки) - это поток рендеринга, а затем из него вибрирует мировой поток.

В потоке визуализации есть глобальная структура данных, называемая обработчиком рендеринга, объявленная как:

 class Renderer
{
    private:
        /*
          shader stuff
        */
        mutex busy_queue;
        vector<Render_Info*> render_queue;

    public:
        /*
           Bunch of methods
        */
        void add_data(Render_Info*);
        void render();
};

И структура Render_Info объявляется как:

struct Render_Info
{
    mutex info_lock;
    GLuint VAO;
    vector<GLuint> VBOs;
    vector<GLuint> types;
    uint layouts;
    uint render_instances;
    Mesh* geometry;
};

extern Renderer *Rendering_Handler;

Идея здесь заключалась в следующем. Любой поток, который хочет что-то сделать, должен обрабатывать свои собственные данные и помещать их в примитивы OpenGL. он затем помещает эту информацию в объект Render_Info, который действует как сообщение между потоком и потоком рендеринга.

Затем поток использует метод add_data(), чтобы отправить указатель на это сообщение данных, которое добавляется к render_queue как:

void Renderer::add_data(Render_Info* data)
{
    busy_queue.lock();
    render_queue.push_back(data);
    busy_queue.unlock();
}

И, наконец, когда поток рендеринга выберет что-нибудь, он заблокирует очередь (не добавив ничего в очередь), все сделает, а затем очистит очередь.

Теперь, конечно, необходима еще одна координация потока, но это суть идеи.

Проблема в том, что я получаю ошибки сегментации только от попытки создания OpenGL VAO и VBOs, не говоря уже о заполнении их данными.

Из того, что я прочитал, OpenGL так же далек от потокобезопасности, как Жираф - это дельфин.

И причина проблемы заключается в том, что контекст OpenGL относится к основному потоку, поэтому, когда я пытаюсь создать VAO и VBOs в мировом потоке, OpenGL просто сбой, поскольку он не знает, что происходит.

Что я могу сделать, выполнив несколько потоков программы?

Я хотел бы остановиться как можно ближе к дизайну, который я описал, если кто-то не дает хорошее обоснование того, почему это не сработает.

Ответы

Ответ 1

Требование OpenGL заключается в том, что контекст, созданный для рендеринга, должен принадлежать одному потоку в любой заданной точке, а поток, который владеет контекстом, должен делать его текущим, а затем вызывать любую связанную с gl функцию. Если вы это сделаете без владения и создания контекста, то вы получите ошибки сегментации. По умолчанию контекст будет текущим для основного потока. Поэтому, чтобы сделать вашу программу многопоточной, у вас есть два варианта.

  • Создайте два контекста и обменивайтесь ресурсами, такими как объекты текстур VAO между ними. Преимущество этого подхода заключается в том, что вы можете ссылаться в потоке 2 на любое VAO, созданное в потоке 1, и оно не будет аварийно завершено.

    Thread_1:

    glrc1=wglCreateContext(dc);
    
    glrc2=wglCreateContext(dc);
    
    BOOL error=wglShareLists(glrc1, glrc2);
    
    if(error == FALSE)
    {
    
    //Unable to share contexts so delete context and safe return 
    
    }
    
    wglMakeCurrent(dc, glrc1);
    
    DoWork();
    

    Thread_2:

    wglMakeCurrent(dc, glrc2);
    
    DoWork();
    
  • Другой вариант - сделать один контекст на поток и сделать его актуальным при запуске потока. Как и после

    Thread_1:

    wglMakeCurrent(NULL, NULL);
    
    WaitForThread2(); OrDoSomeCPUJob();
    
    wglMakeCurrent(dc, glrc);
    

    Thread_2:

    wglMakeCurrent(dc, glrc);
    
    DoSome_GL_Work();
    
    wglMakeCurrent(NULL, NULL);
    

Надеюсь, что это очистит вещь.

Ответ 2

Из того, что я прочитал, OpenGL так же далек от того, чтобы быть потокобезопасным, поскольку Жираф - это дельфин.

Тогда вы дезинформированы. OpenGL отлично защищен потоком. Вам просто нужно иметь в виду, что контексты OpenGL действуют немного как локальное хранилище потоков. То есть когда вы создаете контекст контекста OpenGL, тогда он локализуется в потоке, который делает этот вызов.

Также расширенные указатели функций OpenGL могут быть специфическими для контекста OpenGL (но не для привязки к контексту). Однако загрузчики функций OpenGL сохраняют поток → контекстный кеш. Поэтому, когда вы вызываете расширенную функцию OpenGL (т.е. Ту, которая должна быть загружена во время выполнения) из потока без привязки к контексту, скорее всего, вы вызовете недопустимый указатель на функцию и получите сбой.

Однако, несмотря на то, что он отлично защищен потоками, OpenGL не обязательно повышает производительность при использовании многопоточных приложений. Контексты zygote в контексте OpenGL очень полезны, если вам нужно обновить данные текстур и данных буфера из рабочего потока, но вы должны быть осторожны, чтобы не создавать жесткие вещи, которые могут использоваться основным потоком рендеринга. В программах, где я должен это делать, обычный подход заключается в том, что поток генерации данных создает пул объектов текстуры/буфера, обновляет данные в них и затем "передает" право собственности на объект на поток рендеринга. В конце концов поток рендеринга выполняет свою задачу с этими объектами, и после его завершения он передает право собственности обратно в поток обновлений и берет следующий объект из своего собственного пула, который заполняется потоком данных, отправленным через.

Что я могу сделать, выполнив несколько потоков программы?

Создайте контексты OpenGL в zygote и настройте их для совместного использования своих текстурных и буферных объектов с другими потоками (нитями) механизмом обмена списками отображения. Вы можете иметь произвольное количество контекстов OpenGL в своей программе, и каждый поток может иметь свой собственный контекст активным (в то время как другие потоки используют разные контексты).