tgoop.com/random_rust_dev/45
Last Update:
Ничего давно не постил. Потихоньку делаю work-graph для работы с GPU в движке и эдиторе.
Сначала я хотел просто отображать графи рендерпассов, без возможности менять его в UI, так как он задается кодом.
Сейчас рядом с рендерграфом почти вырос более обобщенный WorkGraph
, который можно собрать из джобов, которые плагины будут экспортировать. Джобы будут инстанциироваться и соединятся как угодно, в пределах типизации пинов.
В первом черновике входы и выходы типизировались растовыми типами и соотвественно в данных лежал TypeId
, но так как плагины могут в рантайме перезагружаться, то TypeId
может меняться.
Сейчас за неимением сильно лучшей альтернативы в нодах лежит kind: String
. А в дженерик методах, где есть тип T: Target
проверяется T::kind() == pin.kind
. Если у вас есть идеи что будет лучше, милости прошу, оставьте в комментариях.
Но граф существует как граф только в редакторе.
В джижке он тут же превращается в тыкву очередь.
В самом деле, зачем нам хранить граф как граф, если нас интересует только его обход в dependencies-first или dependencies-last порядке. Можно просто сделать очередь и ходить по ней в двух направлениях.
В конструкторе WorkGraph
на вход подаются джобы и их связи.
Портам, которые соединены с другим(и) назначаются target IDшки, так что бы у соединенных был одинаковый ID, а так же у update пар внутри каждой джобы.
Еще всем входам прописываются их джобы-зависимости. Почему портам а не джобам? Сейчас мы к этому придем.
WorkGraph в движке получается полу-статичный, при изменении набора нод или связей нужно пересобрать движковый WorkGraph. Это совершенно нормально, потому что меняется граф только когда разработчик руками его меняет, то есть очень не часто в разработке, а в проде вообще никогда.
Но все же не совсем статичный.
В граф можно добавить к выходам внешние таргеты, так результат работы графа и экстрактится из него.
Например можно захватить следующую картинку из Swapchain
и установить в качестве таргета у вообще любого выхода подходящего типа. Это даже вполне ок делать каждый кадр, если их количество не зашкаливает.
Дальше то что идет каждый кадр.
1. Планирование. Берем все внешние таргеты и добавляем в сет selected
все джобы ноды от этих выходов.
2. Потом идем с конца, пропуская джобы не из selected
и собираем дескрипшны со всех входов, так мы узнаем например какого разрешение должен быть таргет картинка или размер таргета буфера.
В этот момент так же добавляются в selected
джобы-зависимости каждого входного порта. Да, вот они где используются.
В конце получаем сет всех джоб, которые нужно запустить, что бы заполнить все внешние таргеты. А еще у каждого таргета, который нужно будет создать, собирается Target::Info
для инициализации.
3. Наконец идем в прямом порядке и запускаем выполнение всех selected
джоб. В этот момент создаются все нужные таргеты (берутся из кэша, если предыдущий Target::Info
совпадает).
Кроме таргетов джоба аллоцирует нужное количество CommandEncoder
-ов которые сразу кладутся в очередь, а джобе достается только ссылка. Так джоба не забудет сделать Queue::submit
, потому что она и не должна.
К тому же перед тем как отдать первый CommandBuffer
джобе, туда ловко вставляется синхронизация (в коде еще нет, но такой план).
Так же джоба вольна пользоваться Device
для создания внутренних ресурсов и чего только ей не захочется.
4. В последний CommandEncoder
вписывается синхронизация между джобами и внешним кодом для внешних ресурсов. Туда же добавится CommandEncoder::present
. После чего все они сабмитятся в порядке аллокации.
Вот так примерно оно должно будет работать.
Кто досюда дочитал - молодец и умница, возьми угощение на полочке и поставь лайк.
BY Random Rust Dev
Share with your friend now:
tgoop.com/random_rust_dev/45