Рендеринг OpenGL во вторичном потоке - PullRequest
26 голосов
/ 30 мая 2011

Я пишу приложение для просмотра 3D-моделей в качестве хобби-проекта, а также в качестве тестовой платформы, чтобы опробовать различные методы рендеринга.Я использую SDL для управления окнами и событиями, а OpenGL для 3D-рендеринга.Первая итерация моей программы была однопоточной и работала достаточно хорошо.Тем не менее, я заметил, что однопоточная программа приводила к тому, что система становилась очень вялой / медленной.Мое решение состояло в том, чтобы переместить весь код рендеринга в другой поток, освобождая тем самым основной поток для обработки событий и предотвращая перехват приложения.

Это решение работало с перебоями, программа часто зависала из-за меняющегося (и, на мой взгляд, странного) набора ошибок, исходящих в основном из системы X Window.Это заставило меня усомниться в моем первоначальном предположении, что, пока все мои вызовы OpenGL происходят в потоке, в котором был создан контекст, все должно работать.Проведя большую часть дня в поисках ответа в Интернете, я полностью озадачен.

Более кратко: возможно ли выполнить 3D-рендеринг с использованием OpenGL в потоке, отличном от основного потока?Могу ли я по-прежнему использовать кроссплатформенную библиотеку окон, такую ​​как SDL или GLFW, с этой конфигурацией?Есть ли лучший способ сделать то, что я пытаюсь сделать?

До сих пор я занимался разработкой для Linux (Ubuntu 11.04) с использованием C ++, хотя я также чувствую себя комфортно с Java и Python, если естьрешение, которое работает лучше на этих языках.

ОБНОВЛЕНИЕ: В соответствии с просьбой, некоторые пояснения:

  • Когда я говорю «Система становится вялой», я имею в виду взаимодействие срабочий стол (перетаскивание окон, взаимодействие с панелью и т. д.) становится намного медленнее, чем обычно.Перемещение окна моего приложения занимает время порядка секунд, а другие взаимодействия достаточно медленные, чтобы раздражать.
  • Что касается вмешательства в оконный менеджер композитинга ... Я использую оболочку GNOME, которая поставляется с Ubuntu11.04 (оставаясь в стороне от Unity на данный момент ...), и я не смог найти никаких вариантов отключения эффектов рабочего стола, как это было в предыдущих выпусках.Я предполагаю, что это означает, что я не использую оконный менеджер композитинга ... хотя я могу быть очень неправ.
  • Я полагаю, что "X-ошибки" - это ошибки сервера из-за сообщений об ошибках, которые я получаю в терминале.Более подробная информация ниже.

Ошибки, которые я получаю с многопоточной версией моего приложения:

XIO: фатальная ошибка ввода-вывода 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) Поток рендеринга блокирует мьютекс, инициализирует видеоподсистему SDL и создает контекст OpenGL.
4) Поток рендеринга разблокирует мьютекс и входит в его «цикл рендеринга».
5) Основной поток больше не блокируется, поэтому он блокирует и разблокирует мьютекс перед завершением шага инициализации.

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

Ответы [ 6 ]

7 голосов
/ 30 мая 2011

Пока контекст OpenGL затрагивается только одним потоком за раз, у вас не должно быть никаких проблем. Вы сказали, что даже ваша однопоточная программа замедляет работу вашей системы. Означает ли это всю систему или только ваше собственное приложение? Самое худшее, что должно произойти в однопоточной программе OpenGL, это то, что обработка пользовательских входных данных для этой одной программы запаздывает, но на остальную систему это не влияет.

Если вы используете какой-либо оконный менеджер композитинга (Compiz, KDE4 kwin), пожалуйста, попробуйте, что произойдет, если вы отключите все эффекты композитинга.

Когда вы говорите X error вы имеете в виду ошибки на стороне клиента или ошибки, зарегистрированные в журнале X-сервера? Последний случай не должен произойти, потому что любой вид искаженного потока команд X, которым X-сервер должен быть в состоянии справиться и, самое большее, выдать предупреждение. Если он (X-сервер) дает сбой, это ошибка, о которой следует сообщить X.org.

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

3 голосов
/ 18 июня 2012

На всякий случай - X-Server имеет свою собственную подсистему синхронизации.При рисовании попробуйте следующее: man XInitThreads - для инициализации
man XLockDisplay/XUnlockDisplay - для рисования (не уверен в обработке событий);

3 голосов
/ 30 мая 2011

В аналогичной ситуации я делал так, чтобы мои вызовы OpenGL оставались в главном потоке, но перемещал подготовку массивов вершин в отдельный поток (или потоки).

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

Это прекрасно сработало для меня.

1 голос
/ 08 февраля 2015
  1. C ++, SDL, OpenGl :::
    1. в основном потоке: SDL_CreateWindow ();
    2. SDL_CreateSemaphore ();
    3. SDL_SemWait ();
    4. на renderThread: SDL_CreateThread (запустить, "rendererThread", (void *) this)
    5. SDL_GL_CreateContext ()
    6. "инициализировать оставшуюся часть openGl и glew"
    7. SDL_SemPost () // разблокировать ранее созданный семафор
    8. P.S: SDL_CreateThread () принимает функции только в качестве первого параметра, а не методов, если метод нужен, чем вы моделируете метод / функцию в своем классе, делая его функцией-другом. таким образом, он будет иметь черты метода, но все еще может использоваться как функтор для SDL_CreateThread ().
    9. PSS: внутри "run (void * data)", созданного для потока, "(void *)" это важно, и для того, чтобы повторно получить "this" внутри функции, эта строка необходима " ClassName * me = (ClassName *) data; "
1 голос
/ 04 марта 2013

Это половина ответа и половина вопроса.

Возможен рендеринг в 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 выключается. Может быть, кто-то может сказать мне, как безопасно освободить контекст, когда поток завершается.

1 голос
/ 02 апреля 2012

Я получил одну из ваших ошибок:

../../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.

...