evzhy / ParticleGen

Multithreaded particle generation engine.

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Реализация генератора эффектов частиц.
Для разработки используется Visual Studio 2017.

Описание:

 Рендеринг происходит в основном потоке, обработка частиц - в 1-N дополнительных потоков.

 Каждый поток обработки имеет два одинаковых вектора: пассивный с координатами вершин прошлого расчёта и активный, в который пишутся данные нового расчёта, а также atomic<bool> флаг, указывающий, какой из векторов - пассивный, какой - активный (aBuf).

 Основной поток перед рендерингом выставляет atomic<bool> о процессе рендеринга в true (aR), перебирает потоки, проверяя у каждого атомик, какой из буферов пассивный (aBuf), отправляет используемую часть пассивного буфера на рендеринг, по завершению вызовов отрисовки вершин выставляет атомик рендеринга (aR) в false и оповещает conditional variable о конце рендеринга (cvR).

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

 В конце цикла расчёта каждый поток обработки синхронизирует новые эффекты между потоками обработки через мьютекс (mTh) (никак не затрагивая основной поток), затем проверяет атомик процесса рендеринга (aR), при true ожидает по условной переменной окончания рендеринга (cvR) и только затем в конце цикла переключает пассивный и активный векторы своим атомиком (aBuf).

Преимущества:
 1) основной поток никогда не блокируется
 2) поддержка многопоточной обработки эффектов с равномерным распределением нагрузки
 3) нет memcpy, уменьшение числа cache miss
 4) нет выделений памяти в процессе работы
 5) рендеринг за количество команд на отрисовку, равное числу потоков обработки частиц (при одном потоке - один батч)

Недостатоки:
 1) удвоенный расход памяти в куче из-за дублирования буферов с полным возможным набором данных на отрисовку
 2) наверное что-то ещё

Аббревиатуры:
 aR - atomic<bool> рендеринга, выставляется в true перед отправкой вершин на рендеринг, в false после draw calls.
 cvR - conditional_variable, оповещение всех ожидающих её происходит в основном потоке после draw calls и смены aR на false. Используется для избежания проблем при идеальном совпадении скоростей основного потока рендеринга и потока обработки, при котором без ожидания (и без смены назначения буферов) возможно продолжительное устаревание данных для рендеринга.
 aBuf - atomic<bool> направления буферов, хранится и меняется в каждом потоке обработки, читается при рендеринге в основном потоке. Меняется на противоположное значение в конце каждого цикла обработки после проверки процесса рендеринга (aR, cvR).
 mTh - мьютекс синхронизации данных между потоками обработки для распределения расчёта частиц (новых эффектов) между потоками. Не затрагивает основной поток.
 пассивный буфер - хранится в каждом потоке обработки, имеет фиксированный размер (максимум_частиц / число_потоков). Используется для чтения рендерером основного потока и расчётом частиц с нулевого элемента по число_используемых_частиц-1.
 активный буфер - хранится в каждом потоке обработки и аналогичен по размеру пассивному, используется только на запись и только своим потоком обработки.
 
Идеи, на которых основана реализация:

- стремиться к отсутствию блокировок основного потока при любых стечениях обстоятельств для избежания фризов и потенциальных deadlock
- не выделять память в процессе работы, что означает использование как минимум одного массива под все возможные вершины частиц эффектов размером: float * 3 координаты * 64 частицы * 2048 эффектов, полтора мегабайта, что избыточно для хранения на стеке
- бороться с cache miss и последующими фризами и плавающим фпс путём минимизацией копирования памяти: кэши процессора L1/L2 (особенно мобильных) небольшие, объём данных 1.5 МБ превышает размеры кеша верхнего уровня и на порядки превышает типичный размер страниц памяти ОС
- задача похожа на классический consumer-producer, но обычно это решается тем или иным видом реализации многопоточной очереди, которая выделяет и освобождает память в процессе работы, что не подходит под пожелания
- запрет посторонних библиотек исключает boost::lockfree или C++React
- независимая работа рендеринга и расчёта эффектов требует дополнительной памяти под запись промежуточных результатов расчёта частиц в случае, когда потоки обработки работают более чем в два раза быстрее основного потока рендеринга, что требует или очень частых блокировок для частичной записи, или полного дублирования данных с редкими блокировками
- при очень высокой частоте смены кадров любой вариант неполного дублирования данных вершин будет требовать блокировки основного потока рендеринга и memcpy, ни то, ни другое нежелательно

Опробованные варианты:

Вариант 1 - в лоб с одним буфером и мьютексом.
Вариант 2 - с двумя буферами и memcpy между ними с использованием mutex.try_lock из главного потока и потоков обработки.
Вариант 3 - с атомиками и условной переменной. Будет компактнее, надёжнее и удобнее в С++20 с atomic<bool>.wait()

About

Multithreaded particle generation engine.

License:MIT License


Languages

Language:C 62.0%Language:C++ 38.0%