OpenGL рендеринг во вторичном потоке
Я пишу приложение для просмотра 3D-моделей в качестве хобби-проекта, а также как тестовую платформу для тестирования различных методов рендеринга. Я использую SDL для обработки управления окнами и событий и OpenGL для 3D-рендеринга. Первая итерация моей программы была однопоточной и работала достаточно хорошо. Тем не менее, я заметил, что однопоточная программа заставила систему стать очень медлительной/отсталой. Мое решение состояло в том, чтобы переместить весь код рендеринга в другой поток, освободив тем самым основной поток для обработки событий и не позволяя приложению перестать отвечать на запросы.
Это решение работало с перерывами, программа часто терпела крах из-за изменения (и, на мой взгляд, странного) набора ошибок, поступающих в основном из системы окон X. Это заставило меня подвергнуть сомнению мое первоначальное предположение, что до тех пор, пока все мои вызовы OpenGL имели место в потоке, где был создан контекст, все должно быть выполнено. Проведя большую часть дня, ища в интернете ответ, я полностью запятнана.
Более кратко: возможно ли выполнить 3D-рендеринг с использованием OpenGL в потоке, отличном от основного потока? Могу ли я по-прежнему использовать кросс-платформенную библиотеку окон, такую как SDL или GLFW с этой конфигурацией? Есть ли лучший способ сделать то, что я пытаюсь сделать?
До сих пор я развивался в Linux (Ubuntu 11.04) с использованием С++, хотя мне также нравится Java и Python, если есть решение, которое лучше работает на этих языках.
ОБНОВЛЕНИЕ: В соответствии с запросом, некоторые пояснения:
- Когда я говорю "Система становится вялой", я имею в виду, что взаимодействие с рабочим столом (перетаскивание окон, взаимодействие с панелью и т.д.) становится намного медленнее, чем обычно. Перемещение моего окна приложения занимает время порядка секунд, а другие взаимодействия достаточно медленны, чтобы раздражать.
- Что касается помех в диспетчере окон компоновки... Я использую оболочку GNOME, которая поставляется с Ubuntu 11.04 (теперь отстоит от Unity...), и я не мог найти никаких параметров для отключения эффектов рабочего стола, таких как в предыдущих распределениях. Я предполагаю, что это означает, что я не использую диспетчер окон компоновки... хотя я мог бы ошибаться.
- Я считаю, что "ошибки X" являются ошибками сервера из-за сообщений об ошибках, которые я получаю на терминале. Подробнее см. Ниже.
Ошибки, которые я получаю с многопоточной версией моего приложения:
XIO: фатальная ошибка IO 11 (ресурс временно недоступен) на сервере X ": 0.0" после 73 запросов (73 известных обработанных) с оставшимися 0 событиями.
X Ошибка неудачного запроса: BadColor (недопустимый параметр Colormap) Основной служебный код отказавшего запроса: 79 (X_FreeColormap) Идентификатор ресурса в неудавшемся запросе: 0x4600001 Серийный номер отказавшего запроса: 72 Текущий серийный номер в потоке вывода: 73
Игра:../../src/xcb_io.c:140: dequeue_pending_request: Ошибка утверждения `req == dpy- > xcb- > pending_requests '. Отменено
Я всегда получаю одну из трех ошибок, которые я получаю, по-видимому, случайным образом, что (на мой взгляд), похоже, подтвердит, что моя проблема действительно связана с моим использованием потоков. Имейте в виду, что я учусь, когда я иду, так что есть очень хороший шанс, что по моему невежеству у меня есть что-то довольно глупое на этом пути.
РЕШЕНИЕ: Для тех, кто имеет подобную проблему, я решил проблему, переместив мой вызов на SDL_Init(SDL_INIT_VIDEO)
в поток рендеринга и заблокировав инициализацию контекста с помощью мьютекса. Это гарантирует, что контекст создается в потоке, который будет его использовать, и предотвращает запуск основного цикла до завершения задач инициализации. Упрощенная схема процедуры запуска:
1) Основной поток инициализирует struct
, который будет разделяться между двумя потоками и который содержит мьютекс.
2) Основная нить порождает поток рендеринга и засыпает на короткий период (1-5 мс), давая время рендеринга для блокировки мьютекса. После этой паузы основной поток блокируется при попытке блокировки мьютекса.
3) Render thread блокирует мьютекс, инициализирует подсистему видео SDL и создает контекст OpenGL.
4) Render thread разблокирует мьютекс и входит в его "цикл рендеринга".
5) Основной поток больше не блокируется, поэтому он блокирует и разблокирует мьютекс до завершения этапа инициализации.
Обязательно прочитайте ответы и комментарии, там есть много полезной информации.
Ответы
Ответ 1
Пока контекст OpenGL затрагивается только из одного потока за раз, вы не должны сталкиваться с какими-либо проблемами. Вы сказали, что даже ваша однопоточная программа сделала вашу систему вялой. Означает ли это всю систему или только ваше собственное приложение? Самое худшее, что должно произойти в однопоточной программе OpenGL, заключается в том, что обработка пользовательских входов для этой одной программы становится неактивной, но остальная часть системы не затрагивается.
Если вы используете какой-то диспетчер окон компоновки (Compiz, KDE4 kwin), попробуйте выяснить, что произойдет, если вы отключите все эффекты компоновки.
Когда вы говорите об ошибках X, вы имеете в виду ошибки на стороне клиента или ошибки, о которых сообщается в журнале X-сервера? Последний случай не должен происходить, потому что любой вид некорректного потока команд X, который X-сервер должен иметь возможность справиться и не вызывать предупреждения. Если это (сервер X) сбой, это ошибка, и она должна быть отправлена на X.org.
Если ваша программа выйдет из строя, то что-то не так в ее взаимодействии с X; в этом случае, пожалуйста, предоставьте нам ошибку в своих вариантах.
Ответ 2
То, что я делал в подобной ситуации, заключалось в том, чтобы сохранить мои вызовы OpenGL в основном потоке, но переместить подготовку массивов вершин к отдельному потоку (или потокам).
В принципе, если вам удастся отделить интенсивный материал процессора от вызовов OpenGL, вам не нужно беспокоиться о к сожалению сомнительной многопоточности OpenGL.
Это получилось красиво для меня.
Ответ 3
На всякий случай - X-Server имеет свою собственную подсистему синхронизации.
Попробуйте выполнить рисунок:
man XInitThreads
- для инициализации
man XLockDisplay/XUnlockDisplay
- для рисования (не уверен в обработке событий);
Ответ 4
Я получил одну из ваших ошибок:
../../src/xcb_io.c:140: dequeue_pending_request: Assertion `req ==
dpy->xcb->pending_requests' failed. Aborted
и целый ряд других. Оказывается, для SDL_PollEvent нужен указатель с инициализированной памятью. Так что это не удается:
SDL_Event *event;
SDL_PollEvent(event);
пока это работает:
SDL_Event event;
SDL_PollEvent(&event);
В случае, если кто-то еще сталкивается с этим из google.
Ответ 5
Это половина ответа и половина вопроса.
Возможно рендеринг в SDL в отдельном потоке. Он работает обычно на любой ОС. Что вам нужно сделать, так это убедиться, что вы делаете контекст GL текущим, когда поток рендеринга берет верх. В то же время, прежде чем вы это сделаете, вам нужно освободить его из основного потока, например:
Вызывается из основного потока:
void Renderer::Init()
{
#ifdef _WIN32
m_CurrentContext = wglGetCurrentContext();
m_CurrentDC = wglGetCurrentDC();
// release current context
wglMakeCurrent( nullptr, nullptr );
#endif
#ifdef __linux__
if (!XInitThreads())
{
THROW( "XLib is not thread safe." );
}
SDL_SysWMinfo wm_info;
SDL_VERSION( &wm_info.version );
if ( SDL_GetWMInfo( &wm_info ) ) {
Display *display = wm_info.info.x11.gfxdisplay;
m_CurrentContext = glXGetCurrentContext();
ASSERT( m_CurrentContext, "Error! No current GL context!" );
glXMakeCurrent( display, None, nullptr );
XSync( display, false );
}
#endif
}
Вызывается из потока рендеринга:
void Renderer::InitGL()
{
// This is important! Our renderer runs its own render thread
// All
#ifdef _WIN32
wglMakeCurrent(m_CurrentDC,m_CurrentContext);
#endif
#ifdef __linux__
SDL_SysWMinfo wm_info;
SDL_VERSION( &wm_info.version );
if ( SDL_GetWMInfo( &wm_info ) ) {
Display *display = wm_info.info.x11.gfxdisplay;
Window window = wm_info.info.x11.window;
glXMakeCurrent( display, window, m_CurrentContext );
XSync( display, false );
}
#endif
// Init GLEW - we need this to use OGL extensions (e.g. for VBOs)
GLenum err = glewInit();
ASSERT( GLEW_OK == err, "Error: %s\n", glewGetErrorString(err) );
Риски здесь в том, что SDL не имеет собственной функции MakeCurrent(), к сожалению. Таким образом, мы должны немного окунуться в внутренности SDL (1.2, 1.3, возможно, решили это к настоящему времени).
И остается одна проблема, по какой-то причине я сталкиваюсь с проблемой, когда SDL закрывается. Может кто-то может сказать мне, как безопасно освободить контекст, когда поток завершается.
Ответ 6
- С++, SDL, OpenGl:
- в главном потоке: SDL_CreateWindow();
- SDL_CreateSemaphore();
- SDL_SemWait();
- on renderThread: SDL_CreateThread (run, "rendererThread", (void *) this)
- SDL_GL_CreateContext()
- "инициализировать остальную часть openGl и glew"
- SDL_SemPost()//разблокировать ранее созданный семафор
- P.S: SDL_CreateThread() использует только функции в качестве своего первого параметра, а не методы, если метод нужен, чем вы имитируете метод/функцию в своем классе, создавая функцию друга. таким образом, он будет иметь свойства метода, хотя он еще может использоваться как функтор для SDL_CreateThread().
- PSS: внутри "run (void * data)", созданного для потока, "(void *)" это важно и для повторного получения "this" внутри функции эта строка нужна "ClassName * me = (ClassName *) data;"