Требуется много работы с ЦП для настройки фрейма для ГП, и большая часть этой работы находится внутри графического драйвера. До появления DX12 / Vulkan работа этого графического драйвера была по существу вынуждена быть однопоточной из-за разработки API.
Надежда состоит в том, что DX12 / Vulkan снимает это ограничение, позволяя параллельно выполнять работу драйвера на нескольких процессорных потоках в кадре. Это позволит более эффективно использовать многоядерные процессоры, позволяя игровым движкам выдвигать более сложные сцены, не привязываясь к процессору. Это надежда - будет ли она реализована на практике - это то, что нам придется ждать в течение следующих нескольких лет.
Для уточнения: вывод рендерера игрового движка представляет собой поток вызовов API DX / GL, которые описывают последовательность операций для рендеринга фрейма. Однако между потоком вызовов API и реальными двоичными буферами команд, которые потребляет оборудование графического процессора, существует большое расстояние. Драйвер должен, так сказать, «скомпилировать» вызовы API в машинный язык графического процессора. Это не тривиальный процесс - он включает в себя большой перевод концепций API в низкоуровневые аппаратные реалии, проверку на предмет того, чтобы убедиться, что графический процессор никогда не переходит в недопустимое состояние, резкое распределение памяти и данных, отслеживание изменений состояния для выдачи исправлять низкоуровневые команды и так далее и тому подобное. Графический драйвер отвечает за все это.
В DX11 / GL4 и более ранних API эта работа обычно выполняется одним потоком драйвера. Даже если вы вызываете API из нескольких потоков (что можно сделать, например, с помощью списков отложенных команд DX11), он просто добавляет некоторую работу в очередь, которую поток драйвера будет просматривать позже. Одна из главных причин этого - отслеживание состояния, о котором я упоминал ранее. Многие детали конфигурации графического процессора на аппаратном уровне требуют знания текущего состояния графического конвейера, поэтому нет хорошего способа разбить список команд на блоки, которые могут обрабатываться параллельно - каждый блок должен точно знать, в каком состоянии он должен запускаться с, хотя предыдущий фрагмент еще не обработан.
Это одна из самых важных вещей, которые изменились в DX12 / Vulkan. С одной стороны, они объединяют почти все состояния графического конвейера в один объект, а с другой (по крайней мере, в DX12), когда вы начинаете создавать список команд, вы должны предоставить начальное состояние конвейера; состояние не наследуется от одного списка команд к следующему. В принципе, это позволяет драйверу не знать ничего о предыдущих списках команд, прежде чем он сможет начать компиляцию, а это, в свою очередь, позволяет приложению разбивать его рендеринг на распараллеливаемые фрагменты, создавая полностью скомпилированные списки команд, которые затем можно соединены вместе и отправлены в GPU с минимумом суеты.
Конечно, есть много других изменений в новых API, но с точки зрения многопоточности это самая важная часть.