@Zeux: GDC Report – D3D11 Deferred Contexts: Primer & Best Practices – Bryan Dudash (NVIDIA)

Кажется, наконец последняя трансляция @Zeux‘a с GDC. Там еще несколько моих, и все, завершим программу.
Эта тоже из разряда репортажей башни из слоновой кости – хай-тек, D3D11, многолетняя война за запуск рендера в нескольких тредах. Я, кстати, еще в бытность свою в Microsoft получал сводки с полей – и тогда драйвер распараллеливался плохо, и сейчас (Брайан, привет!). Что же у них там такое страшное внутри…

Вот кстати, show of hands – кто-то из читающих пишет для D3D11? У нас, подозреваю, только когда/если припрет Metro App делать. Причем все больше похоже на “если”, а не на “когда”.

People in this conversation:
Boris Batkin Add
i press buttons for living. u can call me god
Arseny Kapoulkine (Zeux) Add
CREAT Studios, Saber, Sperasoft (EA Sports – FIFA). Currently making kids happy at ROBLOX. http://zeuxcg.org/
Peter Popov (IronPeter) Add
Gamedev betrayer. Now web searcher.
МГУ -> Нивал -> Яндекс
Mike Samsonov (MuxaHuk) Add
Граф. программер в Havok.
Ранее работал: 1С, Mail.ru.
Раньше был ник: CybeRUS

Zeux: D3D: Primer & Best Practices – Bryan Dudash (NVIDIA)
Последний доклад понедельника от devrel NVidia про D3D. Несколько больше конкретики чем обычно и некоторые удивительные детали.
Значит, все знают что такое Dесть специальный интерфейс в d3d11, можно создать новый контекст на том же девайсе, в него отдать пачку render команд, а потом забрать из контекста command list
В котором будут все эти команды, и который можно будет выполнить на основном (immediate) контексте.
Был некий rationale, примеры как использовать, как не использовать итп

Значит, в той же самой структурке D3D11_FEATURE_DATA_THREADING есть еще один флажок, который говорит – умеет драйвер нативно поддерживать те deferred contexts или нет.
Если нет – то все эмулирует рантайм, выдавая вам контекст, все вызовы которого складывают токен с вызовом и его параметры в буфер
А при выполнении на девайсе команды воспроизводятся.
Такую же штуку вы можете сделать сами без помощи рантайма, была презентация от Gamebryo на GDC 2008, где такое же делали для D3D9 – http://beautifulpixels.blogspot.com/2008/07/parallel-rendering-with-directx-command.html
Там есть хитрый ньюанс по скорости – типичная реализация такого replay будет обладать высоким branch misprediction в условии которое делает dispatch, это традиционная для интерпретаторов проблема.
Собственно, это emulation path – в ней, оказывается, все работает, кроме некоторых параметров в UpdateSubresource…

Теперь про собственно реальный path который поддерживается в драйвере.
Значит, его целью является экономия производительности в следующем:
1. Во-первых, “дешевый” метод для рендера из нескольких потоков способствует тому, что весь ваш рендер код из движка, плюс какой-то обход всяких сценграфов будет тоже распараллелен.
Т.е. позволяя делать рендер из нескольких потоков, мы провоцируем параллелить рендер который вне рантайма, в движке.
2. Во-вторых, вся работа рантайма ведется в нескольких потоках – т.е. runtime overhead параллелится
3. В-третьих, при обычной работе команды, отданные рантайму в главном рендер треде, высылаются драйверу для обработки в треде драйвера; при использовании deferred context как я понял это можно делать только один раз при Execute, снижая накладные расходы на синхронизацию
4. В-четвертых, buffer update тоже параллелится. Buffer update – это выделение всяких временных буферов при Map с Discard плюс копирование данных, оно при поддержке драйвера тоже будет из нескольких потоков
При этом, оверхед драйвера на обработку команд все еще есть, и он сам по себе особо не параллелится. Я пытался попросить автора прояснить ситуацию, внятного ответа я не получил – у меня создалось ощущение, что в драйвере все еще большой кусок кода, генерирующий финальный command buffer, работает в одном потоке.

Значит, про скорость.
Во-первых, простые известные рекомендации:
1. При начале выполнения в Deferred context стейт чистый – не надо его очищать еще раз
2. ExecuteCommandList имеет параметр RestoreContextState, который должен восстановить стейт до Execute
Он восстанавливает сразу весь стейт какой есть без фильтрации, не надо использовать этот параметр – трекайте стейт сами
Во-вторых, рекомендации по размеру:
1. Слишком маленькие command lists неэффективные – константный оверхед на command list, оверхед на синхронизацию, лишний state reset, etc.
2. Слишком большие command lists неэффективные – т.е. использовать один command list очевидно смысла нет, т.к. нет параллелизма.
Рекомендуются десятки-сотни API команд в 1 списке, несколько десятков-сотня command lists на фрейм.

Device context хранит память на все command lists, которые еще не были обработаны GPU но были сгенерированы с него – в PC реалиях это 2-3 кадра.
Говорят, в среднем при рекомендуемом размере в сотню API вызовов (я не понял, таки draw calls или вызовов, надеюсь что draw calls), command list может занимать 10на каждый кадр. Я два раза переспросил, мне подтвердили число, я в легком недоумении.

В-третьих, есть как обычно разные варианты как в те command lists группировать draw calls
Немного зависит от того как у вас устроен рендер с т.з. тредов, jobs/threads/etc. итп
В реальном мире использовали по-разному, приводились референсы на Civ 5 про которую был отдельный доклад на GDC, сейчас найду ссылку
И на AC3
На AC3 говорят от 25% прироста производительности (и больше, в более CPU-intensive сценах), в Civ5 будет написано в оригинальной презентации
Вот презентация про Civ 5: http://www.gdcvault.com/play/1012192/Firaxis-Civilization-V-A-Case
От NVidia будет новый семпл с анализом разных методов батчинга и использования тех контекстов, можно будет сравнить.
А, про Civ 5 говорили, что изначальный прирост был скажем 50% от тех deferred context, но потом драйвер подоптимизировали и прирост (относительный) уменьшился.

Значит, общий message как я понял такой:
1. Deferred context не решает всех проблем CPU dispatch performance т.к. не параллелит CPU dispatch целиком, а только части
2. В целом можно сообразить свою реализацию которая будет быстро накапливать команды на нескольких потоках, а потом быстро их раздавать с одного
Такая реализация может быть даже быстрее чем deferred context, но ее во-первых нужно делать, а во-вторых она из существенного не сможет распараллелить buffer uploads
3. Поскольку deferred context прост в применении то надо просто начать с него, а там видно будет

В качестве неочевидного поведения -
Было упомянуто, что если сделать issue query на deferred context
а потом на immediate context сделать asynchronous GetData
то несмотря на то что GetData asynchronous, он подождет GPU.
То же самое произойдет если на deferred context сделать UpdateSubresource
а потом на immediate context сделать Map с discard или nooverwrite.
В общем, будьте аккуратны.

Zeux: В дополнение про память и те 10.
Во-первых, разумеется рекомендовано было иметь пул из device context-ов
Во-вторых, было сказано что при некоторых использованиях из-за этого повышенного расхода памяти заканчивалось 32-битное адресное пространство, приходилось урезать количество одновременно используемых контекстов.
Вот теперь вроде все, ура!

Boris Batkin: 30 мб наов это win.
это 1 мсек только память засирать хорошим кодом

IronPeter: на 100 *, не?

Zeux: Вот я два раза переспросил
Сказали, не per frame
А per list per frame.

MuxaHuk: Очень интересно, спасибо
У меня про все это спрашивали на интервью (YCbCr, Forward+, Deferred Contexts), а я как то не четко отвечал, потом дома про все это прочитал что бы запомнить. И тут тоже похожие вопросы, видимо волнуют людей одинаковые вещи.

Zeux: Из аудитории были несколько осмысленных вопросов, видно что некоторые люди пробовали.
Спросили, сколько стоит command list в котором ничего нет (ни одной команды) – из-за особенностей их thread scheduling основанной на TBB такие иногда выходили… Ответа не прозвучало, такое никто не мерял.
Спросили, как весь этот ад отлаживать, ссылаясь на неадекватную поддержку в PIX (PIX, как сейчас помню, не позволяет step делать на draw calls внутри deferred context, толькона весь execute) и на неадекватную поддержку в Nsight
Автор пообещал что в Nsight уже все давно работает, не знаю как на самом деле.
Ну и из привычного – автор рекомендовал держать code path без deferred contexts рабочим, чтобы можно было сравнивать скорость и поведение, любые серьезные изменения тестировать на обоих вендорах на последних драйверах. Говорил что очень много оптимизаций в драйверах было по фидбеку от Civ5 (в которой были десятки тысяч дипов) и AC3, и типа пишите вашим контактам в NVidia чтобы вам тоже стало хорошо