Порядок вызова XSetWMProtocols и glXCreateContext в многопоточной среде

Редактировать: ниже я разместил хорошее решение общей проблемы разделения средств визуализации.

Недавно я играл с OpenGL в многопоточной среде X11. Я нашел следующий учебник, который компилирует , ссылки и работает нормально.

Но затем я столкнулся со странной проблемой после попытки адаптировать код для своих нужд.

В учебнике порядок вызова XCreateWindow, glXCreateContext, XSelectInput и XSetWMProtocols следующий:

param[i].win = XCreateWindow(param[i].d_, root, 200,200, 
                   300,200, 0, visInfo->depth, InputOutput, visInfo->visual,
                   CWColormap,
                   &windowAttr);
param[i].ctx = glXCreateContext(param[i].d_, visInfo,  NULL, True);
XSelectInput(d, param[i].win, StructureNotifyMask);
XSetWMProtocols(d, param[i].win, &(delMsg), 1);

Обратите внимание, что XCreateWindow и XSelectInput/XSetWMProtocols используют разные подключения дисплея.

Однако при изменении порядка вызовов

param[i].win = XCreateWindow(param[i].d_, root, 200,200, 
                   300,200, 0, visInfo->depth, InputOutput, visInfo->visual,
                   CWColormap,
                   &windowAttr);
XSelectInput(d, param[i].win, StructureNotifyMask);
XSetWMProtocols(d, param[i].win, &(delMsg), 1);
param[i].ctx = glXCreateContext(param[i].d_, visInfo,  NULL, True);

программа не работает с

X Ошибка неудачного запроса: BadWindow (неверный параметр окна)
Основной код операции неудачного запроса: 2 (X_ChangeWindowAttributes)
Идентификатор ресурса в неудачном запросе: 0x5000002 Серийный номер неудачного запроса: 17 Текущий серийный номер в выходном потоке: 18

что, по-видимому, вызвано XSetWMProtocols.

Поскольку использовались разные подключения дисплея, я не удивлюсь, если все это вообще не сработает. Но каким-то образом после вызова glXCreateContext все кажется волшебным образом прекрасным.

Я относительно новичок в программировании X11/GLX, я что-то пропустил? Какую магию выполняет glXCreateContext? Или что-то еще случилось? Или, может быть, мне просто нужно двигаться дальше, потому что OpenGL и многопоточность всегда вызывают проблемы.

Мое решение:

Я был ленив и просто использовал подход из учебника. Это работало до тех пор, пока в мой проект не был добавлен фритайп, что внезапно снова привело к сбою BadWindow. Таким образом, даже если кажется, что все в порядке, когда вы работаете из разных потоков, X11 серьезно возится с памятью, пока вас нет рядом. (Это был не я, я проверял с помощью valgrind)

Мое текущее решение как н.м. прокомментировал: я поместил все в поток GUI (вызовы X11 и GL/GLX), чьи ресурсы никогда не доступны другим потокам. Однако необходимо помнить о двух вещах, поскольку это может замедлить цикл рендеринга:

  • Медленная обработка сообщений задерживает рендеринг (как указано ilmale ниже)
  • Медленный рендеринг задерживает обработку сообщений (моя проблема)

Первую проблему можно легко решить. Создайте очередь или список stl или любой другой контейнер, в котором вы ставите в очередь соответствующие XEvents для логики вашего приложения, а затем получаете их из другого потока. Просто убедитесь, что ваш STL является потокобезопасным, и, если сомневаетесь, реализуйте свою собственную очередь. С условием ожидания, установленным для размера контейнера, вы даже можете имитировать блокирующие вызовы, такие как XNextEvent.

Вторая проблема каверзная. Вы можете возразить, что если рендерер работает со скоростью 1 кадр/с или медленнее, игра или приложение в любом случае бесполезны. Это правда. Но было бы неплохо, если бы вы могли обработать какой-нибудь сигнал уничтожения (например, атом окна уничтожения), даже если у вас 0,1 кадра в секунду. Единственное решение, которое пришло мне в голову, это проверка новых сообщений после рендеринга каждой тысячи спрайтов или около того. Отправьте их в свой контейнер и продолжите рендеринг. Конечно, в этом случае вы никогда не сможете позволить потоку рендеринга запускать пользовательские скрипты или другой неизвестный код в любое время. Но я предполагаю, что это сделало бы идею отделения рендеринга от других потоков бессмысленной.

Надеюсь это поможет.

:


person user1492625    schedule 30.06.2012    source источник
comment
X11 и многопоточность всегда вызывают проблемы, не говоря уже об OpenGL. Вы не должны вызывать функции X11 или OpenGL из разных потоков. Это принесет только разочарование. Используйте один поток GUI, и пусть все остальные потоки общаются с ним. Это также дает вам хорошее разделение интересов.   -  person n. 1.8e9-where's-my-share m.    schedule 01.07.2012
comment
Спасибо за комментарий. Мне понравилась идея раздельного рендеринга. Таким образом, тяжелый рендеринг не замедлит обработку событий. Но мне действительно кажется, что это просто не стоит того, и его всегда следует избегать. Полностью согласен.   -  person user1492625    schedule 02.07.2012


Ответы (2)


Я согласен с н.м. и я парень, который написал учебник. :D Проблема, которую я пытался решить, состоит в том, чтобы отделить цикл событий от цикла рендеринга, чтобы я мог воспроизводить события, не влияя на рендеринг, и наоборот. Я писал структуру LUA, и моя функция «processMessage (событие)» потенциально могла вызывать определяемую пользователем функцию Lua.

Пока я писал цикл событий, у меня было много проблем, подобных той, что была у вас, я также пробовал XCB, который работал на Fedora, но разбился на Ubuntu, после большой головной боли я нашел решение с другим отображением (для X-сервера например, обслуживание другого процесса) с общим GlContext и другим потоком для загрузки (текстуры и сетки).

Возвращаясь к вашей проблеме:

XSetWMProtocols(...)

хочу такой же дисплей, где создаются окна, но только на какой-то версии X. Вот почему сейчас я использую Qt.

person ilmale    schedule 02.07.2012

В основном я прошел через те же испытания многопоточного X11 и Win32 в кроссплатформенном проекте.

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

В частности, одним пунктом, который почти заставил меня выбросить полотенце, была фоновая обработка GPU! Это было очень странное и трудно уловимое состояние гонки во время выполнения, из-за которого я подумал, что виновата ОС.

После отправки текстур на карту внутри списка отображения (кхм, и при реализации свободного типа) немедленное отрисовывание ресурса иногда вызывало небольшое повреждение списка отображения шрифтов, даже для более поздних отрисовок. Сам список отображения был поврежден, и я даже прибегнул к глобальной блокировке OpenGL, чтобы доказать, что потоки не виноваты. Но ПОЧЕМУ он был поврежден? ОС? Нет, ГПУ.

Я считаю, что общие контексты GLX вызывают другое поведение на некоторых картах, особенно на nvidia в моей системе. Мои беды были вызваны не другими потоками, а общим флагом в вызове createContext в сочетании с отсутствием glFinish() перед использованием ресурса. Это и несколько лучших практик, которые я объясню ниже.

В 99% запусков он будет работать нормально без glFinish() даже при многопоточности. Это состояние возникает только при загрузке, поэтому постоянная остановка / перезапуск приложения в конечном итоге приведет к его раскрытию. Если бы он загружал все без проблем, приложение с этого момента работало бы нормально. Если бы были проблемы, изображение оставалось бы поврежденным, пока я не перезагрузил бы его.

Все проблемы были устранены благодаря соблюдению этих простых правил.

  1. Создайте второй GLContext в потоке, отличном от main(). (Не создавайте оба контекста в одном и том же потоке и указывайте второй поток, это НЕ стабильно)
  2. При загрузке ресурса во втором потоке добавьте glFinish() перед помещением результата в очередь для использования. (проще говоря, glFinish() перед использованием ресурсов)
  3. Как только вы вызовете makeCurrent() во втором контексте внутри второго потока, вызовите функцию getCurrentContext() и подождите, пока она не станет ненулевой, прежде чем любой поток выполнит загрузку или вызовы других ресурсов OpenGL. Иногда во втором потоке он(makeCurrent) возвращает значение, но getCurrentContext() все еще может некоторое время иметь значение NULL на некоторых видеокартах. Не знаю, почему и как драйвер допускает это, но проверка спасает приложение от сбоя.

Я применил эти методы в своем приложении 6-Thread+, и странные разовые проблемы с повреждением исчезли и больше никогда не возвращались.

Судя по моему опыту, X11 не такой уж плохой... видеокарта такая, но она действительно просто придирчива больше всего на свете. В моем случае я даже использую typedefs для написания кода Linux/Windows с использованием неспецифических функций, что еще больше усложняет ситуацию, и все же это управляемый зверь, если принять надлежащие меры предосторожности :).

Это причудливо, но это не проблема «Избегать любой ценой», если вы спросите меня. Я надеюсь, что этот пост поможет и удачи!

person AcidTonic    schedule 19.09.2012