Форум программистов «Весельчак У»
  *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

  • Рекомендуем проверить настройки временной зоны в вашем профиле (страница "Внешний вид форума", пункт "Часовой пояс:").
  • У нас больше нет рассылок. Если вам приходят письма от наших бывших рассылок mail.ru и subscribe.ru, то знайте, что это не мы рассылаем.
   Начало  
Наши сайты
Помощь Поиск Календарь Почта Войти Регистрация  
 
Страниц: [1]   Вниз
  Печать  
Автор Тема: Мое решение проблемы потоков в OpenGL  (Прочитано 2103 раз)
0 Пользователей и 1 Гость смотрят эту тему.
jur
Помогающий

lt
Offline Offline

« : 16-10-2013 19:24 » 

Привет!

Интернет полнится вопросами: "Как работать с OpenGL из потоков?". Невооруженным глазом видно, что проблема серьезная, касающаяся многих пользователей этой замечательной графической библиотеки. Коснулась она и меня. В моей программе графическими вопросами занимается, естественно, отдельная DLL-ка. ( В скобках следует отметить, что сейчас у нас графический движек реализован в DirectX, но я с удовольствием двигаюсь в сторону OpenGL :-) ) Вот пример тестовой программы, которая иллюстрирует, что мне нужно получить:


Здесь в левой части отображается ультразвуковое изображение с датчика (не реальное, конечно, это тестовый сигнал). Эти кадры формируются с периодом следования порядка 20-50 fps. В правой части - лента, которая едет справа налево и показывает выбранный срез (или луч) из левого кадра (этот сигнал тоже не реальный, берется из тестового массива). Таким образом можно выбрать интересующее место на левом кадре (с помощью маркера-линии; сейчас он не показан) и наблюдать изменение по времени в этом месте, например, работу сердечного клапана и т.п. Частота поступления этих лучей составляет от 4 до более сотни миллисекунд (это зависит от того, какое время на всю ленту выбрал доктор).

Исходная ультразвуковая информация поступает из железа и совершенно не синхронна между собой. Ультразвуковые кадры поступают целиком и помещаются в свою текстуру. Отдельные M-лучи (что в ленте) - в свою. Причем, если лучи следуют слишком быстро, то они собираются в пачки по несколько штук, чтобы результирующий fps был порядка 30-50.

Режимов работы прибора - дофига :-) Отображательных потоков - тоже. Получается такая петрушка: при старте программы инициализируется OpenGL со всеми нужными объектами. Затем, в зависимости от выбранного режима работы, запускаются один или несколько потоков отображения ультразвука конкретного режима работы.

Сложность тут в том, что контекст рендеринга привязан к потоку. Поэтому просто так залезать, например, в текстуру с новыми данными из некоторого потока отображения - не катит. В Интернете дают советы, что де мол, нужно как инициализацию OpenGL, так и работу с ним организовать в одном потоке. Но в моем случае это невозможно! На момент инициализации приборной программы потоков отображения вообще не существует. Это раз. Второе, таких потоков может быть более одного. Городить какой-то один единственный поток, который был бы "на все руки мастер" - можно, но как-то это некошерно...

Что же делать? Гугл подсказывает нам, что есть такая замечательная функция: wglShareLists. Она действительно работает, но радости мне она не принесла... Если однопотоковый рендер этого кадра на моей видеокарте занимает порядка 200 микросекунд, то с этой функцией - примерно 3.7 миллисекунд. Это в 15-18 раз медленнее! Причем, такое время возникает даже в том случае, когда второй поток вообще никакого рендеринга не производит! ( Понятно, ведь драйвер - не Господь Бог, он не знает, будет-ли производиться рендеринг в следующую секунду. Но мне-то от этого не легче! :-) )

В этот момент стала страдать моя репа... От усиленного почесывания... :-) Я подумал, что можно ведь захватить контекст и в другом потоке, только он должен быть свободен, т.е. не должно быть активного контекста. Ну-ка, ну-ка! Я измерил время, которое занимает пара вызовов wglMakeCurrent(m_hDC, m_hRC)/wglMakeCurrent(NULL, NULL). Оказалось, что это время исчезающе мало: всего лишь около 40 микросекунд! Прекрасно!

Тогда я сделал следующее. Я сотворил две функции RC_open()/RC_close(), которые включают мой контекст и выключают его. И "обернул" ими все те внешние функции моей графической DLL-ки, которые имеют дело с OpenGL-ресурсами. Получилось что-то вроде такого кода:

Код:
GLENGINE_API void GLEngine_PaneM_SetViewp(int x,int y,int width,int height,int num_of_beams_to_plot)
{
  GLEngine_Locker.Lock();
  if( RC_open() ) { // RC_open checks Plotter.m_Ready
    Plotter.m_PaneM->SetViewport(x, y, width, height, num_of_beams_to_plot);
    RC_close();
  }
  GLEngine_Locker.Release();
}

Это образец функции для задания вьюпорта той самой ленты (правая часть скриншота). Locker - это примитивнейший класс, инкапсулирующий функционал критической секции, как самого быстрого механизма упорядочивания доступа к разделяемым ресурсам Винды (мьютексы заметно медленнее).

Испытывая радостное возбуждение, я запустил свой тестовый пример и обнаружил, что рендеринг из отдельного потока практически нисколько не замедлился! Ура!! Эти 40 микросекунд - пустяки, в пределах погрешности измерения (Винда ведь, чай, не RTOS). Как было 200 с небольшим микросекунд, так и осталось!

Вот таков мой опыт в этом вопросе. Какие будут замечания, вопросы, критика?

« Последнее редактирование: 16-10-2013 19:30 от jur » Записан

MPEG-4 - в массы!
Алексей++
кот глобальный и пушистый
Глобальный модератор

ru
Offline Offline
Сообщений: 13


« Ответ #1 : 25-10-2013 05:54 » 

ну, вообще-то, синхронизация - это классика при многопоточности

можно ещё попробовать следующую оптимизацию: в каждом потоке создать свою пару объектов HDC+HBITMAP и именно на них рисовать, а затем в критической секции копировать на рабочий контекст. Должно работать ещё шустрее
Записан

Страниц: [1]   Вверх
  Печать  
 

Powered by SMF 1.1.21 | SMF © 2015, Simple Machines