Почему безопасность потоков так важна для графических API?

21

Как Vulkan, так и DirectX12 заявлены как пригодные для многопоточного использования. Люди, кажется, взволнованы этим.

Почему это считается такой огромной особенностью? В любом случае, «настоящая» обработка перебрасывается через мост памяти на отдельный процессор.

Кроме того, если он настолько большой, почему до сих пор не вышел потокобезопасный графический API?

чокнутый урод
источник
Эта статья гораздо более "ориентирована на геймеров", но может дать вам некоторое представление ... pcgamer.com/what-directx-12-means-for-gamers-and-developers
glampert

Ответы:

13

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

Причина, по которой это не было сделано до сих пор, вероятно, заключается в том, что directx и opengl были созданы во времена, когда многопоточность не была действительно очевидна. Также доска Khronos очень консервативна в изменении API. Их мнение о Vulkan также заключается в том, что он будет сосуществовать рядом с OpenGL, потому что оба служат различным целям. Вероятно, до недавнего времени парализм стал настолько важным, что потребители получают доступ к все большему числу процессоров.

РЕДАКТИРОВАТЬ: Я не имею в виду, что при работе на нескольких процессорах не достигается никакой производительности, бесполезно разбивать ваши вызовы на несколько потоков, чтобы быстрее создавать текстуры / шейдеры. Скорее производительность достигается за счет того, что больше процессоров занято, а процессор занят работой.

Морис Лаво
источник
1
В качестве дополнительной заметки OpenGL, как правило, работает только в одном потоке, поэтому приложение, интенсивно использующее графику, может максимально использовать одно ядро. Что-то вроде Vulkan позволяет нескольким потокам отправлять команды в очередь, что означает, что из нескольких потоков можно сделать много графических вызовов.
Мыльный
9

Требуется много работы с ЦП для настройки фрейма для ГП, и большая часть этой работы находится внутри графического драйвера. До появления DX12 / Vulkan работа этого графического драйвера была по существу вынуждена быть однопоточной из-за разработки API.

Надежда состоит в том, что DX12 / Vulkan снимает это ограничение, позволяя параллельно выполнять работу драйвера на нескольких процессорных потоках в кадре. Это позволит более эффективно использовать многоядерные процессоры, позволяя игровым движкам выдвигать более сложные сцены, не привязываясь к процессору. Это надежда - будет ли она реализована на практике - это то, что нам придется ждать в течение следующих нескольких лет.

Для уточнения: вывод рендерера игрового движка представляет собой поток вызовов API DX / GL, которые описывают последовательность операций для рендеринга фрейма. Однако между потоком вызовов API и реальными двоичными буферами команд, которые потребляет оборудование графического процессора, существует большое расстояние. Драйвер должен, так сказать, «скомпилировать» вызовы API в машинный язык графического процессора. Это не тривиальный процесс - он включает в себя большой перевод концепций API в низкоуровневые аппаратные реалии, проверку на предмет того, чтобы убедиться, что графический процессор никогда не переходит в недопустимое состояние, резкое распределение памяти и данных, отслеживание изменений состояния для выдачи исправлять низкоуровневые команды и так далее и тому подобное. Графический драйвер отвечает за все это.

В DX11 / GL4 и более ранних API эта работа обычно выполняется одним потоком драйвера. Даже если вы вызываете API из нескольких потоков (что можно сделать, например, с помощью списков отложенных команд DX11), он просто добавляет некоторую работу в очередь, которую поток драйвера будет просматривать позже. Одна из главных причин этого - отслеживание состояния, о котором я упоминал ранее. Многие детали конфигурации графического процессора на аппаратном уровне требуют знания текущего состояния графического конвейера, поэтому нет хорошего способа разбить список команд на блоки, которые могут обрабатываться параллельно - каждый блок должен точно знать, в каком состоянии он должен запускаться с, хотя предыдущий фрагмент еще не обработан.

Это одна из самых важных вещей, которые изменились в DX12 / Vulkan. С одной стороны, они объединяют почти все состояния графического конвейера в один объект, а с другой (по крайней мере, в DX12), когда вы начинаете создавать список команд, вы должны предоставить начальное состояние конвейера; состояние не наследуется от одного списка команд к следующему. В принципе, это позволяет драйверу не знать ничего о предыдущих списках команд, прежде чем он сможет начать компиляцию, а это, в свою очередь, позволяет приложению разбивать его рендеринг на распараллеливаемые фрагменты, создавая полностью скомпилированные списки команд, которые затем можно соединены вместе и отправлены в GPU с минимумом суеты.

Конечно, есть много других изменений в новых API, но с точки зрения многопоточности это самая важная часть.

Натан Рид
источник
5

Современные графические процессоры обычно имеют единую секцию внешнего интерфейса, которая обрабатывает полностью линейный поток команд от процессора. Является ли это естественный дизайн оборудования или если он просто эволюционировали из тех дней, когда был одноядерный процессор генерации команды для GPU спорно, но это реальность сейчас. Поэтому, если вы генерируете один линейный поток команд с сохранением состояния, конечно, имеет смысл генерировать этот поток линейно в одном потоке на CPU! Правильно?

Что ж, современные графические процессоры также обычно имеют очень гибкий унифицированный бэкэнд, который может работать одновременно с множеством разных вещей. Вообще говоря, графический процессор работает с вершинами и пикселями с довольно тонкой детализацией. Существует небольшая разница между графическим процессором, обрабатывающим 1024 вершины в одном рисовании и 512 + 512 вершин в двух разных рисованиях.

Это предполагает довольно естественный способ выполнять меньше работы: вместо того, чтобы бросать огромное количество вершин в GPU за один вызов отрисовки, разбивать вашу модель на секции, делать дешевый грубый отбор этих секций и отправлять каждый кусок по отдельности, если он проходит тест выбраковки. Если вы сделаете это с правильной детализацией, вы должны получить хорошее ускорение!

К сожалению, в современной реальности графического API вызовы отрисовки чрезвычайно дороги на процессоре. Упрощенное объяснение того, почему: изменения состояния в графическом процессоре могут не соответствовать напрямую вызовам графического API, поэтому многие вызовы графического API просто устанавливают некоторое состояние внутри драйвера, и вызов отрисовки, который будет зависеть от этого нового состояния, выполняется и просматривает все состояние, помеченное как изменившееся с момента последней отрисовки, записывает его в поток команд для графического процессора, а затем фактически инициирует отрисовку. Это все работа, выполняемая в попытке получить скудный и средний поток команд для внешнего модуля графического процессора.

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

Есть и другие причины (например, асинхронное изменение времени для улучшений задержки VR), но это большая причина для игр с привязкой к графике и другого программного обеспечения с большим количеством вызовов (например, пакетов 3D-моделирования).

Джон Калсбек
источник