Создание структур 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 в своей программе, и каждый поток может иметь свой собственный контекст активным (в то время как другие потоки используют разные контексты).