Подтвердить что ты не робот

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) Основной поток больше не блокируется, поэтому он блокирует и разблокирует мьютекс до завершения этапа инициализации.

Обязательно прочитайте ответы и комментарии, там есть много полезной информации.

4b9b3361

Ответ 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;"