Общие сведения о Direct3D* 12
By: Michael Coppock
Аннотация
Microsoft Direct3D* 12 — важный шаг в развитии технологий игр на ПК. В новой версии разработчики получают более мощные средства контроля над своими играми, могут эффективнее использовать ресурсы ЦП.
Содержание
2.0. Объект состояния конвейера. 5
3.1. Опасности, связанные с ресурсами. 10
3.2. Управление резидентностью ресурсов. 11
3.3. Зеркальное копирование состояния. 11
4.1. Привязка избыточных ресурсов. 12
4.5. Эффективность и работа без привязки. 15
4.6. Обзор контекста отрисовки. 15
5.1. Избыточные команды отрисовки. 17
6.1. Параллельное создание команд. 21
8.0. Параллельная работа ЦП.. 25
Ссылки и полезные материалы.. 29
Введение
На конференции GDC 2014 корпорация Microsoft объявила важную новость для всего рынка игр для ПК в 2015 году — выпуск новой версии Direct3D, а именно версии 12. В D3D 12 создатели вернулись к низкоуровневому программированию: оно дает разработчикам игр более полный контроль и много новых интересных возможностей. Группа разработки D3D 12 старается снизить издержки при задействовании ЦП и повысить масштабируемость, чтобы полнее нагружать ядра ЦП. Цель состоит в повышении эффективности и производительности консольных API, чтобы игры могли эффективнее использовать ресурсы ЦП/ГП. В мире игр для ПК большую часть работы, а то и всю работу часто выполняет один-единственный поток ЦП. Другие потоки заняты только операционной системой и другими системными задачами. Существует совсем немного действительно многопоточных игр для ПК. Microsoft стремится изменить эту ситуацию в наборе D3D 12, который является надстройкой над функциональностью рендеринга D3D 11. Это означает, что на всех современных ГП можно запускать D3D 12, поскольку при этом будут эффективнее задействованы современные многоядерные ЦП и ГП. Для использования всех преимуществ D3D 12 не нужно покупать новый графический процессор. Действительно, у игр на ПК с процессором Intel® очень яркое будущее.
1.0 Хорошо забытое старое
Низкоуровневое программирование широко применяется в отрасли консолей, поскольку характеристики и устройство каждой модели консолей неизменны. Разработчики игр могут подолгу отлаживать свои игры, чтобы выжать всю возможную производительность из Xbox One* или PlayStation* 4. С другой стороны, ПК по своей природе — это гибкая платформа с бесчисленным множеством разновидностей и вариаций. При планировании разработки новой игры для ПК требуется учитывать очень много факторов. Высокоуровневые API, такие как OpenGL* и Direct3D*, помогают упростить разработку. Эти API выполняют всю «черную работу», поэтому разработчики могут сосредоточиться собственно на играх. Проблема же заключается в том, что и API, и (в несколько меньшей степени) драйверы достигли уже такого уровня сложности, что они могут увеличить объем потребляемых ресурсов при рендеринге кадров, что приводит к снижению производительности. Именно здесь на сцену выходит низкоуровневое программирование.
Первая эпоха низкоуровневого программирования на ПК окончилась вместе с прекращением использования MS-DOS*. На смену пришли API разных поставщиков. После 3Dglide* компании 3DFX* появились такие API, как Direct3D. ПК проиграли в производительности, получив взамен столь необходимую гибкость и удобство. Рынок оборудования стал крайне сложным, с огромным множеством доступных аппаратных решений. Время разработки увеличилось, поскольку разработчики, естественно, стремились к тому, чтобы в их игры могли бы играть все пользователи. При этом перемены произошли не только на стороне программ, но и в оборудовании: эффективность ЦП с точки зрения потребления электроэнергии стала важнее, чем их производительность. Теперь вместо того, чтобы гнаться за увеличением тактовой частоты, больше внимания уделяется использованию нескольких ядер и потоков ЦП, параллельному рендерингу на современных ГП. Настала пора применить в области игр для ПК некоторые методики, используемые в консольных играх. Пора эффективнее, полнее использовать все доступные ядра и потоки. Словом, пора перейти в XXI век в мире игр для ПК.
1.1 Ближе к железу
Чтобы приблизить игры к «железу», нужно снизить сложность и размер API и драйвера. Между оборудованием и самой игрой должно быть меньше промежуточных уровней. Сейчас API и драйвер тратят слишком много времени на преобразование команд и вызовов. Некоторые или даже многие эти процедуры будут снова отданы разработчикам игр. За счет снижения издержек в D3D 12 повысится производительность, а за счет уменьшения количества промежуточных уровней обработки между игрой и оборудованием ГП игры будут быстрее работать и лучше выглядеть. Разумеется, у медали есть и обратная сторона: некоторые разработчики могут не желать заниматься областями, которые ранее были под контролем API, например программировать управление памятью ГП. Пожалуй, в этой области слово за разработчиками игровых движков, но, впрочем, только время расставит все на свои места. Поскольку выпуск D3D 12 состоится еще не скоро, имеется предостаточно времени для размышлений на эту тему. Итак, как же будут выполнены все эти заманчивые обещания? Главным образом с помощью новых возможностей. Это объекты состояния конвейера, списки команд, наборы и кучи.
2.0 Объект состояния конвейера
Прежде чем рассказывать об объекте состояния конвейера (PSO), сначала рассмотрим контекст рендеринга D3D 11, а потом перейдем к изменениям в D3D 12. На рис. 1 показан контекст рендеринга D3D 11 в том виде, в котором его представил Макс Мак-Маллен (Max McMullen), руководитель по разработке D3D, на конференции BUILD 2014 в апреле 2014 года.
Контекст отрисовки: Direct3D 11
Рисунок 1. Контекст отрисовки D3D 11 [Воспроизводится с разрешения корпорации Майкрософт.]
Крупные толстые стрелки указывают отдельные состояния конвейера. В соответствии с потребностями игры каждое такое состояние можно получить или задать. Прочие состояния в нижней части - фиксированные состояния функций, такие как поле зрения или прямоугольник обрезки. Другие важные компоненты этой схемы будут пояснены в дальнейших разделах данной статьи. При обсуждении PSO нас интересует только левая часть схемы. В D3D 11 удалось снизить издержки ЦП по сравнению с D3D 9 благодаря малым объектам состояния, но по-прежнему требовалась дополнительная работа драйвера, который должен был брать эти малые объекты состояния и сочетать их с кодом ГП во время рендеринга. Назовем эту проблему издержками аппаратного несоответствия. Теперь посмотрим еще на одну схему с конференции BUILD 2014, показанную на рис. 2.
Direct3D 11 — издержки состояния конвейера
Малые объекты состояния à издержки аппаратного несоответствия
Рисунок 2. В конвейере D3D 11 с малыми объектами состояния часто возникают издержки аппаратного несоответствия
На левой стороне показан конвейер в стиле D3D 9: здесь показано, что использует приложение для выполнения своей работы. Оборудование, показанное на правой стороне схемы на рис. 2, необходимо программировать. Состояние 1 представляет код шейдера. Состояние 2 — это сочетание растеризатора и потока управления, связывающего растеризатор с шейдерами. Состояние 3 — связь между смешением и пиксельным шейдером. Шейдер вертексов D3D влияет на аппаратные состояния 1 и 2, растеризатор — на состояние 2, пиксельный шейдер — на состояния с 1 по 3 и так далее. Драйверы в большинстве случаев не отправляют вызовы одновременно с приложением. Они предпочитают записывать вызовы и дожидаться выполнения работы, чтобы можно было определить, что на самом деле нужно приложению. Это означает дополнительные издержки для ЦП, поскольку старые и устаревшие данные помечаются как «непригодные». Поток управления драйвера проверяет состояние каждого объекта во время рендеринга и программирует оборудование так, чтобы соответствовать заданному игрой состоянию. При этой дополнительной работе возможно исчерпание ресурсов и возникновение затруднений. В идеале, как только игра задает состояние конвейера, драйвер тут же «знает», что нужно игре, и сразу программирует оборудование. На рис. 3 показан конвейер D3D 12, который делает именно это с помощью так называемого объекта состояния конвейера (PSO).
Direct3D 12 — оптимизациясостояния конвейера
Группировка конвейера в один объект
Копирование из PSO в аппаратное состояние
Рисунок 3. Оптимизация состояния конвейера в D3D 12 упорядочивает процесс.
На рис. 3 показан упорядоченный процесс с уменьшенными издержками. Один PSO с информацией о состоянии для каждого шейдера может за одну копию задать все аппаратные состояния. Вспомните, что некоторые состояния были помечены как «прочие» в контексте рендеринга D3D 11. Разработчики D3D 12 осознали важность уменьшения размера PSO и предоставления игре возможности смены цели рендеринга, не затрагивая скомпилированный PSO. Такие вещи, как поле зрения и прямоугольник обрезки, остались отдельными, они программируются вне остального конвейера (рис. 4).
Контекст рендеринга:
объект состояния конвейера (PSO)
Рисунок 4. Слева показан новый PSO D3D 12 с увеличенным состоянием для повышения эффективности
Вместо того чтобы задавать и прочитывать каждое состояние по отдельности, мы получили единую точку, тем самым полностью избавившись от издержек аппаратного несоответствия. Приложение задает PSO нужным образом, а драйвер получает команды API и преобразует их в код ГП без дополнительных издержек, связанных с управлением потоком. Такой подход (более близкий к «железу») означает, что для обработки команд рендеринга требуется меньше циклов, производительность возрастает.
3.0 Привязка ресурсов
Перед рассмотрением изменений в привязке ресурсов вспомним, какая модель привязки ресурсов использовалась в D3D 11. На рис. 5 снова показана схема контекста рендеринга: слева — объект состояния конвейера D3D 12, а справа — модель привязки ресурсов D3D 11.
Контекст отрисовки:
объект состояния конвейера (PSO)
Рисунок 5. Слева — объект состояния конвейера D3D 12, справа — модель привязки ресурсов D3D 11.
На рис. 5 принудительные точки привязки находятся справа от каждого шейдера. Принудительная модель привязки означает, что у каждого этапа в конвейере есть определенные ресурсы, на которые можно сослаться. Эти точки привязки ссылаются на ресурсы в памяти ГП. Это могут быть текстуры, цели рендеринга, неупорядоченные представления доступа (UAV) и так далее. Привязки ресурсов используются уже давно, они появились даже раньше, чем D3D. Цель в том, чтобы «за кадром» обработать множество свойств и помочь игре в эффективной отправке команд рендеринга. При этом системе необходимо выполнить анализ множества привязок в трех основных областях. В следующем разделе рассматриваются эти области и их оптимизация в D3D 12.
3.1 Опасности, связанные с ресурсами
Опасности обычно связаны с переходами, например с перемещением от цели рендеринга к текстуре. Игра может отрисовать кадр, который должен стать картой среды для сцены. Игра завершает рендеринг карты среды и теперь хочет использовать ее в качестве текстуры. В ходе этого процесса и среда выполнения, и драйвер отслеживают все ресурсы, привязанные как цели рендеринга или как текстуры. Если среда выполнения или драйвер видят какой-либо ресурс, привязанный одновременно и как цель рендеринга, и как текстура, они отменяют более старую по времени привязку и сохраняют более новую. За счет этого игра может переключаться нужным образом, а набор программного обеспечения управляет этим переключением «за кадром». Драйвер также должен очистить конвейер ГП, чтобы можно было использовать цель рендеринга в качестве текстуры. В противном случае пиксели будут прочитаны до того, как они будут удалены из ЦП, и полученный результат будет несогласованным. Собственно говоря, опасность — это все, для чего требуется дополнительная работа ГП с целью получения согласованных данных.
Как и в других усовершенствованиях в D3D 12, решение здесь состоит в том, чтобы предоставить игре больше возможностей управления. Почему API и драйвер должны выполнять всю работу и отслеживание, когда это всего лишь один момент в обработке кадра? Для переключения от одного ресурса к другому требуется около 1/60 секунды. Можно снизить издержки, передав управление игре, тогда дополнительное время будет израсходовано только один раз, когда игра осуществляет переключение ресурсов (рис. 6).
D3D12_RESOURCE_BARRIER_DESC Desc; Desc.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION; Desc.Transition.pResource = pRTTexture; Desc.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES; Desc.Transition.StateBefore = D3D12_RESOURCE_USAGE_RENDER_TARGET; Desc.Transition.StateAfter = D3D12_RESOURCE_USAGE_PIXEL_SHADER_RESOURCE; pContext->ResourceBarrier( 1, &Desc );Рисунок 6. API ограничителя ресурсов, добавленный в D3D 12
API ограничителя ресурсов, показанный на рис. 6, объявляет ресурс, его начальное использование и целевое использование, после чего следует вызов, чтобы сообщить выполняемой среде и драйверу о переключении. Переключение становится явным вместо отслеживаемого в рендеринге кадра со множеством условной логики, оно осуществляется один раз за кадр или с такой частотой, с которой оно требуется в игре.
3.2 Управление резидентностью ресурсов
D3D 11 (и более старые версии) работают так, как если бы все вызовы были в очереди. Игра считает, что API немедленно выполняет вызов. Но на самом деле это не так. ГП поддерживает очередь команд, в которой все команды откладываются и выполняются позднее. При этом обеспечивается распараллеливание вычислений и более эффективное задействование ГП и ЦП, но требуется отслеживание ссылок и их учет. На учет и отслеживание расходуется достаточно много ресурсов ЦП.
Чтобы решить эту проблему, игра получает явное управление жизненным циклом ресурсов. В D3D 12 больше не скрывается очередь ГП. Для отслеживания выполнения на ГП добавлен API Fence. Игра может в заданный момент (например, один раз за кадр) проверить, какие ресурсы больше не нужны, и затем высвободить занимаемую ими память. Больше не требуется отслеживать ресурсы в течение всего рендеринга кадра с помощью дополнительной логики, чтобы высвобождать ресурсы и память.
3.3 Зеркальное копирование состояния
После оптимизации трех описанных выше областей был обнаружен дополнительный элемент, способный обеспечить прирост производительности, хотя и не столь заметный. Когда задана точка привязки, среда выполнения отслеживает эту точку, чтобы игра могла позднее вызвать Getи узнать, что привязано к конвейеру. Создается зеркальная копия точки привязки. Эта функция выполняет промежуточную работу, чтобы разделенное на компоненты программное обеспечение могло определить текущее состояние контекста рендеринга. После оптимизации привязки ресурсов зеркальные копии состояний больше не нужны. Помимо упразднения управления потоком из трех описанных выше областей также удалены операции Get для зеркальных копий.
4.0 Кучи и таблицы
Осталось рассмотреть еще одно важное изменение привязки ресурсов. В конце раздела 4 будет показан весь контекст рендеринга D3D 12. Новый контекст рендеринга D3D 12 — первый шаг на пути к повышению эффективности использования ЦП в API.
4.1 Привязка избыточных ресурсов
Проведя анализ нескольких игр, разработчики D3D заметили, что обычно в играх в множестве кадров используется одна и та же последовательность команд. Не только команды, но и привязки остаются точно такими же из кадра в кадр. ЦП создает последовательность привязок (скажем, 12) для рисования объекта в кадре. Зачастую ЦП приходится создавать эти же 12 привязок еще раз для следующего кадра. Почему же не поместить эти привязки в кэш? Разработчикам игр можно предоставить команду, указывающую на кэш, чтобы можно было многократно использовать одинаковые привязки.
Вразделе 3 мы обсудили очереди. Когда осуществляется вызов, игра исходит из того, что API немедленно выполняет этот вызов. Но на самом деле это не так. Команды помещаются в очередь, все содержимое которой откладывается и выполняется позднее в ГП. Поэтому если изменить одну из этих 12 привязок, о которых мы говорили ранее, то драйвер скопирует все 12 привязок в новое место, изменит копию, затем даст графическому процессору команду начать использовать скопированные привязки. Обычно у большинства из этих 12 привязок значения бывают статическими, а обновление требуется лишь для нескольких динамических значений. Когда игре нужно внести частичные изменения в эти привязки, она копирует все 12 штук, из-за чего наблюдается чрезмерный расход ресурсов ЦП при незначительных изменениях.
4.2 Дескрипторы
Что такое дескриптор? Коротко говоря, это фрагмент данных, определяющий параметры ресурсов. По сути, это то, из чего состоит объект представления D3D 11. Управление жизненным циклом на уровне операционной системы отсутствует. Это просто данные в памяти ГП. Здесь содержится информация о типе и формате, счетчик MIP для текстур и указатель на пиксельные данные. Дескрипторы находятся в центре новой модели привязки ресурсов.
Дескриптор
Рисунок 7. Дескриптор D3D 12 — небольшой фрагмент данных, определяющий параметр ресурса
4.3 Кучи
Когда представление задано в D3D 11, оно копирует дескриптор в текущее расположение в памяти ГП, откуда прочитываются дескрипторы. Если задать новое представление в этом же расположении, в D3D 11 дескрипторы будут скопированы в новое расположение в памяти, а ГП в следующем вызове команды рендеринга получит указание читать дескрипторы из этого нового расположения. В D3D 12 игра или приложение получают явное управление созданием дескрипторов, их копированием и пр.
Кучи дескрипторов
Рисунок 8. Куча дескрипторов является массивом дескрипторов
Кучи (рис. 8) являются просто очень большим массивом дескрипторов. Можно повторно использовать дескрипторы из предыдущих вызовов рендеринга и кадров. Можно создавать новые при необходимости. Вся разметка находится под управлением игры, поэтому при управлении кучей почти не возникают издержки. Размер кучи зависит от архитектуры ГП. В устаревших маломощных ГП размер может быть ограничен 65 тысячами, а в более мощных ГП ограничение будет определяться объемом памяти. В менее мощных ГП возможно превышение размера кучи. В D3D 12 поддерживается несколько куч и переключение от одной кучи дескрипторов к другой. Тем не менее при переключении между кучами в некоторых ГП происходит сброс данных, поэтому использованием этой функции лучше не злоупотреблять.
Итак, как сопоставить код шейдеров с определенными дескрипторами или наборами дескрипторов? Ответ – с помощью таблиц.
4.4 Таблицы
Таблицы содержат индекс начала и размер в куче. Они являются точками контекста, но не объектами API. При необходимости у каждого этапа шейдера может быть одна или несколько таблиц. Например, шейдер вертекса для вызова рендеринга может содержать таблицу, указывающую на дескрипторы со смещением с 20 по 32 в куче. Когда начнется работа над следующим вызовом рендеринга, смещение может измениться на 32—40.
Таблицы дескрипторов
Рисунок 9. Таблицы дескрипторов содержат индекс начала и размер в куче дескрипторов
Используя существующее оборудование, D3D 12 может обрабатывать несколько таблиц на каждое состояние шейдера в PSO. Можно поддерживать одну таблицу лишь с теми данными, которые часто изменяются между вызовами, а вторую таблицу — со статическими данными, неизменными для нескольких вызовов и для нескольких кадров. Это позволит избежать копирования дескрипторов из одного вызова в следующий. Тем не менее у старых ГП действует ограничение в одну таблицу на каждый этап шейдера. Поддержка нескольких таблиц возможна в современном и в перспективном оборудовании.
4.5 Эффективность и работа без привязки
Кучи дескрипторов и таблицы применяются в D3D для рендеринга без привязки, причем с возможностью задействования всех аппаратных ресурсов ПК. В D3D 12 поддерживаются любые устройства — от маломощных «систем на кристалле» до высокопроизводительных дискретных графических адаптеров. Благодаря такому универсальному подходу разработчики игр получают разнообразные возможности управления привязками. Кроме того, новая модель включает множественные обновления частоты. Поддерживаются кэшированные таблицы статических привязок с возможностью многократного использования и динамические таблицы для данных, изменяющихся в каждом вызове рендеринга. Таким образом, необходимость копировать все привязки для каждого нового вызова рендерингаотпадает.
4.6 Обзор контекста отрисовки
На рис. 10 показан контекст рендеринга с изменениями D3D 12, которые мы уже успели обсудить. Также показан новый объект состояния конвейера и упразднение вызовов Get, но сохранились явные точки привязки D3D 11.
Контекст отрисовки
Рисунок 10. Контекст отрисовки D3D 12 с изменениями, о которых мы уже успели поговорить.
Давайте удалим последние остатки контекста рендеринга D3D 11 и добавим таблицы дескрипторов и кучи. Теперь у нас появились таблицы для каждого этапа шейдера (или несколько таблиц, как показано для пиксельного шейдера).
Контекст отрисовки: Direct3D 12
Рисунок 11. Полный контекст рендеринга D3D 12
Тонко настраиваемые объекты состояния упразднены, их заменил объект состояния конвейера. Удалено отслеживание опасностей и зеркальное копирование состояния. Принудительные точки привязки заменены на управляемые приложением или игрой объекты памяти. ЦП используется более эффективно, издержки снижены, упразднены поток управления и логика и в API, и в драйвере.
5.0 Наборы
Мы завершили рассмотрение нового контекста рендеринга в D3D 12 и увидели, каким образом D3D 12 передает управление игре, приближая ее к «железу». Но этим возможности D3D 12 по повышению эффективности API не ограничиваются. В API по-прежнему существуют издержки, влияющие на производительность, и существуют дополнительные способы повысить эффективность использования ЦП. Как насчет последовательностей команд? Сколько существует повторяющихся последовательностей и как сделать их более эффективными?
5.1 Избыточные команды рендеринга
При изучении команд рендеринга в каждом кадре разработчики D3D в Microsoft обнаружили, что при переходе от одного кадра к другому происходит добавление или удаление только 5—10 % последовательностей команд. Остальные последовательности команд используются во множестве кадров. Итак, ЦП в течение 90—95 % времени своей работы повторяет одни и те же последовательности команд!
Как здесь повысить эффективность? И почему в D3D это не было сделано до сих пор? На конференции BUILD 2014 Макс Мак-Маллен сказал: «Очень сложно создать универсальный и надежный способ записывать команды. Такой способ, чтобы он работал всегда одинаково для разных ГП, с разными драйверами и при этом работал бы быстро». Игре требуется, чтобы все записанные последовательности команд выполнялись так же быстро, как отдельные команды. Что изменилось? D3D. Благодаря новым объектам состояния конвейера, кучам дескрипторов и таблицам состояние, необходимое для записи и воспроизведения команд, значительно упростилось.
5.2 Что такое наборы?
Наборы — это небольшие списки команд, которые один раз записываются, после чего их можно многократно использовать без каких-либо ограничений в одном кадре или в нескольких кадрах. Наборы можно создавать в любом потоке и использовать сколько угодно раз. Наборы не привязываются к состоянию объектов PSO. Это означает, объекты PSO могут обновлять таблицу дескрипторов, а при запуске набора для разных привязок игра будет получать разные результаты. Как и в случае с формулами в электронных таблицах Excel*, математика всегда одинаковая, а результат зависит от исходных данных. Существуют определенные ограничения, чтобы гарантировать эффективную реализацию наборов драйвером. Одно из таких ограничений состоит в том, что никакая команда не может сменить цель рендеринга. Но остается еще множество команд, которые можно записать и воспроизводить.
Наборы
Рисунок 12. Наборы — это часто повторяемые команды, записанные и воспроизводящиеся по мере необходимости
Слева на рис. 12 — пример контекста рендеринга, последовательность команд, созданных в ЦП и переданных на ГП для выполнения. Справа — два пакета, содержащих записанную последовательность команд для многократного использования в разных потоках. По мере выполнения команд ГП достигает команды на выполнение набора. После этого воспроизводится записанный набор. По завершении ГП возвращается к последовательности команд, продолжает и находит следующую команду выполнения набора. После этого прочитывается и воспроизводится второй набор, после чего выполнение продолжается.
5.3 Эффективность кода
Мы рассмотрели управление потоком в ГП. Теперь посмотрим, каким образом наборы упрощают код.
Пример кода без наборов
Перед нами страница настройки, задающая состояние конвейера и таблицы дескрипторов. Затем идут два вызова рендеринга объектов. В обоих случаях используется одинаковая последовательность команд, различаются только константы. Это типичный код D3D 11.
// Настройка pContext->SetPipelineState(pPSO); pContext->SetRenderTargetViewTable(0, 1, FALSE, 0); pContext->SetVertexBufferTable(0, 1); pContext->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);Рисунок 14. Настройка этапа в типичном коде D3D 11
// Рисунок 1 pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1); pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1); pContext->DrawInstanced(6, 1, 0, 0); pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1); pContext->DrawInstanced(6, 1, 6, 0);Рисунок 15. Рендеринг в типичном коде D3D 11
// Рисунок 2 pContext->SetConstantBufferViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1); pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1); pContext->DrawInstanced(6, 1, 0, 0); pContext->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1); pContext->DrawInstanced(6, 1, 6, 0);Рисунок 16. Рендеринг в типичном коде D3D 11
Пример кода с наборами
Теперь рассмотрим эту же последовательность команд с пакетами в D3D 12. Первый вызов, показанный ниже, создает набор. Это может быть сделано в любом потоке. На следующем этапе создается последовательность команд. Это такие же команды, как и в предыдущем примере
// Создание набора pDevice->CreateCommandList(D3D12_COMMAND_LIST_TYPE_BUNDLE, pBundleAllocator, pPSO, pDescriptorHeap, &pBundle);Рисунок 17. Образец кода с созданием набора
// Запись команд pBundle->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); pBundle->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 0, 1); pBundle->DrawInstanced(6, 1, 0, 0); pBundle->SetShaderResourceViewTable(D3D12_SHADER_STAGE_PIXEL, 1, 1); pBundle->DrawInstanced(6, 1, 6, 0); pBundle->Close();Рисунок 18. Образец кода с записью набора
В примерах кода на рис. 17 и 18 достигается такой же результат, как в коде без наборов на рис. 14—16. Хорошо видно, что наборы позволяют существенно сократить количество вызовов, необходимое для выполнения одной и той же задачи. ГП при этом выполняет точно такие же команды и получает такой же результат, но гораздо эффективнее.
6.0 Списки команд
Вы уже знаете, каким образом в D3D 12 повышается эффективность использования ЦП и предоставляются более широкие возможности разработчикам с помощью наборов, объектов состояния конвейера, куч дескрипторов и таблиц. Модель объектов состояния конвейера и дескрипторов поддерживает наборы, которые, в свою очередь, используются для широко распространенных и часто повторяющихся команд. Такой упрощенный и «приближенный к железу» подход снижает издержки и позволяет эффективнее использовать ЦП. Ранее мы упомянули, что в играх для ПК большую часть работы, а то и всю работу выполняет только один поток, а остальные потоки занимаются другими системными задачами и процессами ОС. Добиться эффективного использования нескольких ядер или потоков игрой для ПК не так просто. Зачастую для реализации многопоточности в игре требуются существенные затраты ресурсов и труда. Разработчики D3D собираются изменить это положение дел в D3D 12.
6.1 Параллельное создание команд
Как уже неоднократно упоминалось выше, отложенное выполнение команд — это модель, при которой создается ощущение, что каждая команда выполняется немедленно, но на самом деле команды помещаются в очередь и выполняются позднее. Эта функция сохраняется в D3D 12, но теперь она является прозрачной для игры. Не существует немедленного контекста, поскольку все откладывается. Потоки могут создавать команды параллельно для формирования списков команд, которые подаются в объект API, который называется очередью команд. ГП не будет выполнять команды, пока они не будут отправлены с помощью очереди команд. Очередь — это порядок следования команд, которые указываются в списке. Чем отличаются списки команд от наборов? Списки команд создаются и оптимизируются, поэтому несколько потоков могут одновременно создавать команды. Списки команд используются однократно, а затем удаляются из памяти; на месте удаленного списка команд записывается новый список. Наборы предназначены для многократного выполнения часто используемых команд рендеринга в одном или в нескольких кадрах.
В D3D 11 была сделала попытка распараллелить обработку команд; эта функция называлась отложенным контекстом. Но из-за издержек цели по повышению производительности тогда не были достигнуты. Подробный анализ показал множество мест с избыточной последовательной обработкой, что привело к неэффективному распределению нагрузки по ядрам ЦП. Часть издержек с последовательной обработкой была устранена в D3D 12 с помощью средств повышения эффективности использования ЦП, описанных в разделах 2—5.
6.2 Списки и очередь
Представьте, что два потока создают список команд рендеринга. Одна последовательность должна быть выполнена перед другой. При наличии опасностей один поток использует ресурс в качестве текстуры, а другой поток использует этот же ресурс в качестве цели рендеринга. Драйвер должен проанализировать использование ресурсов во время рендеринга и устранить опасности, обеспечив согласованность данных. Отслеживание опасностей — одна из областей с последовательными издержками в D3D 11. В D3D 12 за отслеживание опасностей отвечает игра, а не драйвер.
D3D 11 поддерживается несколько отложенных контекстов, но при их использовании возникает сопутствующая нагрузка. Драйвер отслеживает состояние для каждого ресурса. Поэтому, как только начата запись команд для отложенного контекста, драйвер должен выделять память для отслеживания состояния каждогоиспользуемого ресурса. Память занята, пока идет создание отложенного контекста. По завершении драйвер должен удалить из памяти все объекты отслеживания. Из-за этого возникают ненужные издержки. Игра объявляет максимальное количество списков команд, которые можно создать параллельно на уровне API. После этого драйвер упорядочивает и заранее выделяет все объекты отслеживания в одном согласованном объекте памяти.
В D3D 11 распространены динамические буферы (буфер контекста, вертексов и т. д.), но «за кадром» остается множество экземпляров удаленных буферов отслеживания памяти. Например, параллельно могут быть созданы два списка команд, и вызвана функция MapDiscard. После отправки списка драйвер должен вмешаться во второй список команд, чтобы исправить информацию об удаленном буфере. Как и в приведенном выше примере с отслеживанием опасностей, здесь возникают значительные издержки. В D3D 12 управление переименованием передано игре, динамического буфера больше нет. Игра получила полное управление. Она создает собственные распределители и может делить буфер на части по мере необходимости. Поэтому команды могут указывать на явным образом заданные точки в памяти.
Как мы говорили вразделе 3.1,среда выполнения и драйвер отслеживают жизненный цикл ресурсов в D3D 11. Для этого требуется подсчет и отслеживание ресурсов, все операции должны быть сделаны во время отправки. В D3D 12 игра управляет жизненным циклом ресурсов и обработкой опасностей, благодаря чему устраняются издержки последовательной обработки и повышается эффективность использования ЦП. Параллельное создание команд работает эффективнее в D3D 12 с учетом оптимизации в четырех описанных областях, что расширяет возможности распараллеливания нагрузки на ЦП. Кроме того, разработчики D3D создают новую модель драйверов WDDM 2.0 и планируют реализовать дополнительные меры по оптимизации, чтобы снизить нагрузку при отправке списков команд.
6.3 Поток очереди команд
Очередь команд
Рисунок 19. Очередь команд с двумя параллельно создаваемыми списками команд и двумя наборами повторяющихся команд
На рис. 19 показана схема набора израздела 5.2,но с многопоточностью. Очередь команд, показанная слева, представляет собой последовательность событий, отправленных на ГП. Списки команд находятся посередине, а справа — два набора, записанные перед началом сценария. Начиная со списков команд, создаваемых параллельно для разных фрагментов сцены, завершается запись списка команд 1, этот список отправляется в очередь команд, и ГП начинает его выполнять. Параллельно запускается процедура управления потоком очереди команд, а список команд 2 записывается в потоке 2. Пока ГП выполняет список команд 1, поток 2 завершает создание списка команд 2 и отправляет его в очередь команд. Когда очередь команд завершает выполнение списка команд 1, она последовательно переходит к списку команд 2. Очередь команд — это последовательность, в которой ГП должен выполнять команды. Хотя список команд 2 был создан и отправлен в ГП до того, как ГП завершил выполнение списка команд 1, список команд 2 будет выполнен только после завершения выполнения списка команд 1. В D3D 12 поддерживается более эффективное распараллеливание для всего процесса.
7.0 Динамические кучи
Как мы уже говорили выше, игра управляет переименованием ресурсов для распараллеливания создания команд. Кроме того, в D3D 12 упрощено переименование ресурсов. В D3D 11 буферы были разделены по типам: были буферы вертексов, констант и индексов. Разработчики игр запросили возможность использовать зарезервированную память так, как им заблагорассудится. И команда D3D выполнила эту просьбу. В D3D 12 разделение буферов по типам отсутствует. Буфер — это просто объем памяти, выделяемый игрой по мере необходимости в размере, необходимом для кадра (или нескольких кадров). Можно даже использовать распределитель кучи с делением по мере необходимости, что повышает эффективность процессов. В D3D 12 также применяется стандартное выравнивание. ГП сможет прочесть данные, если в игре используется стандартное выравнивание. Чем выше уровень стандартизации, тем проще создавать содержимое, работающее с разными моделями ЦП, ГП и другого оборудования. Распределение памяти также является постоянным, поэтому ЦП всегда знает нужный адрес. При этом также повышается эффективность параллельного использования ЦП: поток может направить ЦП на нужный адрес в памяти, после чего ЦП определяет, какие данные нужны для кадра.
Распределение и перераспределение
Рисунок 20. Распределение и перераспределение буферов
В верхней части рис. 20 показана модель D3D 11 с типизацией буферов. В нижней части показана новая модель D3D 12, где игра управляет кучей. Вместо выделения отдельной части памяти для буфера каждого типа используется единый постоянный фрагмент памяти. Размер буфера также настраивается игрой на основе потребностей рендеринга текущего кадра или даже нескольких последующих кадров.
8.0 Параллельная работа ЦП
Пора собрать все воедино и продемонстрировать, каким образом новые возможности D3D 12 позволяют создать действительно многопоточную игру для ПК. В D3D 12 поддерживается параллельное выполнение нескольких задач. Списки команд и наборы предоставляют возможность параллельного создания и выполнения команд. Наборы позволяют записывать команды и многократно запускать их в одном или в нескольких кадрах. Списки команд могут быть созданы в нескольких потоках и переданы в очередь команд для выполнения графическим процессором. И наконец, буферы с постоянным распределением параллельно создают динамические данные. Параллельная работа поддерживается и в D3D 12, и в WDDM 2.0. В D3D 12 устранены ограничения прежних версий D3D, разработчики могут распараллеливать свои игры или движки любым целесообразным способом.
Профилирование в D3D11
Рисунок 21. Типичная параллельная обработка в D3D 11. Поток 0 выполняет почти всю работу, остальные потоки используются незначительно
На схеме на рис. 21 показана типичная игровая нагрузка в D3D 11. Логика приложения, среда выполнения D3D, DXGKernel, KMD и текущая работа задействуют ЦП с четырьмя потоками. Поток 0 выполняет большую часть работы. Потоки 1—3 практически не используются: в них попадают только логика приложения и среда выполнения D3D 11, создающая команды рендеринга. Драйвер пользовательского режима вообще не создает команд для этих потоков в силу особенностей устройства D3D 11.
Профилирование в D3D12
Рисунок 22. Здесь показана такая же нагрузка, как на рис. 21, но в D3D 12. Нагрузка равномерно распределяется по всем 4 потокам, а с учетом других мер по повышению эффективности в D3D 12 скорость выполнения нагрузки значительно увеличена
Теперь рассмотрим такую же нагрузку в D3D 12 (рис. 22). Логика приложения, среда выполнения D3D, DXGKernel, KMD и текущая работа также задействуют ЦП с четырьмя потоками. Но здесь работа равномерно распределяется по всем потокам за счет оптимизации в D3D 12. Поскольку команды создаются параллельно, выполняемая среда D3D также работает параллельно. Издержки ядра значительно снижены за счет оптимизации ядра в WDDM 2.0. UMD работает во всех потоках, а не только в потоке 0, что означает настоящее распараллеливание создания команд. И наконец, наборы заменяют логику избыточного изменения состояния в D3D 11 и ускоряют работу логики приложения.
Показатели D3D11 и D3D12
Рисунок 23. Сравнение параллельной обработки в D3D 11 и D3D 12
На рис. 23 показано сравнение обеих версий. Поскольку уровень фактической параллельности достаточно высок, мы видим относительно равное использование ЦП потоком 0 и потоками 1—3. Потоки 1—3 выполняют больше работы, поэтому в столбце «Только графика» видно увеличение. Кроме того, благодаря снижению нагрузки в потоке 0 и новым мерам по повышению эффективности среды выполнения и драйвера общую нагрузку на ЦП удалось снизить примерно на 50 %. Если рассмотреть столбец «Приложение плюс графика», здесь также распределение нагрузки между потоками стало более равномерным, а использование ЦП снизилось примерно на 32 %.
9.0 Заключение
В D3D 12 повышена эффективность использования ЦП за счет более крупных объектов состояния конвейера. Вместо того чтобы задавать и прочитывать каждое состояние по отдельности, разработчики получили единую точку приложения сил, тем самым полностью избавившись от издержек аппаратного несоответствия. Приложение задает PSO, а драйвер получает команды API и преобразует их в код ГП. Новая модель привязки ресурсов не имеет издержек, вызванных логикой управления потоком, которая теперь не нужна.
За счет использования куч, таблиц и наборов D3D 12 обеспечивает более эффективное использование ЦП и более высокую масштабируемость. Вместо явных точек привязки используются управляемые приложением или игрой объекты памяти. Часто используемые команды можно записывать и многократно воспроизводить в одном кадре или в нескольких кадрах с помощью наборов. Списки команд и очередь команд позволяют параллельно создавать списки команд в нескольких потоках ЦП. Практически вся работа равномерно распределяется по всем потокам ЦП, что позволяет раскрыть весь потенциал и всю мощь процессоров Intel® Core™ 4-го и 5-го поколений.
Direct3D 12 — значительный шаг в развитии технологий игр на ПК. Разработчики игр смогут работать «ближе к железу» за счет более компактного API и драйвера с меньшим количеством промежуточных уровней. За счет этого повышается эффективность и производительность. С помощью сотрудничества группа разработки D3D создала новый API и модель драйверов, предоставляющую разработчикам более широкие возможности управления, что позволяет создавать игры в полном соответствии с замыслом, с великолепной графикой и отличной производительностью.
Ссылки и полезные материалы
- Предварительный обзор API Direct3D* 12
- Блог Microsoft DirectX* 12
- Процессоры Intel® Core™ M
- Процессоры Intel® Core™ 4-го поколения
- Процессоры Intel® Atom™
- Intel® Processor Graphics — руководство для разработчиков
Ссылки на материалы Intel
Корпоративный бренд: http://intelbrandcenter.tagworldwide.com/frames.cfm
Наименования продуктов Intel®: http://www.intel.com/products/processor number/
Уведомления и примечания
См. http://legal.intel.com/Marketing/notices+and+disclaimers.htm
Об авторе
Майкл Коппок (Michael Coppock) работает в корпорации Intel с 1994 года, он специализируется на производительности графики и игр для ПК. Он помогает компаниям, разрабатывающим игры, наиболее эффективно задействовать все возможности ГП и ЦП Intel. Занимаясь и программным обеспечением, и оборудованием, Майкл работал со множеством продуктов Intel, начиная с процессора 486DX4 Overdrive.
Примечания
ИНФОРМАЦИЯ В ДАННОМ ДОКУМЕНТЕ ПРИВЕДЕНА ТОЛЬКО В ОТНОШЕНИИ ПРОДУКТОВ INTEL. ДАННЫЙ ДОКУМЕНТ НЕ ПРЕДОСТАВЛЯЕТ ЯВНОЙ ИЛИ ПОДРАЗУМЕВАЕМОЙ ЛИЦЕНЗИИ, ЛИШЕНИЯ ПРАВА ВОЗРАЖЕНИЯ ИЛИ ИНЫХ ПРАВ НА ИНТЕЛЛЕКТУАЛЬНУЮ СОБСТВЕННОСТЬ. КРОМЕ СЛУЧАЕВ, УКАЗАННЫХ В УСЛОВИЯХ И ПРАВИЛАХ ПРОДАЖИ ТАКИХ ПРОДУКТОВ, INTEL НЕ НЕСЕТ НИКАКОЙ ОТВЕТСТВЕННОСТИ И ОТКАЗЫВАЕТСЯ ОТ ЯВНЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ ГАРАНТИЙ В ОТНОШЕНИИ ПРОДАЖИ И/ИЛИ ИСПОЛЬЗОВАНИЯ СВОИХ ПРОДУКТОВ, ВКЛЮЧАЯ ОТВЕТСТВЕННОСТЬ ИЛИ ГАРАНТИИ ОТНОСИТЕЛЬНО ИХ ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННОЙ ЦЕЛИ, ОБЕСПЕЧЕНИЯ ПРИБЫЛИ ИЛИ НАРУШЕНИЯ КАКИХ-ЛИБО ПАТЕНТОВ, АВТОРСКИХ ПРАВ ИЛИ ИНЫХ ПРАВ НА ИНТЕЛЛЕКТУАЛЬНУЮ СОБСТВЕННОСТЬ.
КРОМЕ СЛУЧАЕВ, СОГЛАСОВАННЫХ INTEL В ПИСЬМЕННОЙ ФОРМЕ, ПРОДУКТЫ INTEL НЕ ПРЕДНАЗНАЧЕНЫ ДЛЯ ИСПОЛЬЗОВАНИЯ В СИТУАЦИЯХ, КОГДА ИХ НЕИСПРАВНОСТЬ МОЖЕТ ПРИВЕСТИ К ТРАВМАМ ИЛИ ЛЕТАЛЬНОМУ ИСХОДУ.
Корпорация Intel оставляет за собой право вносить изменения в технические характеристики и описания своих продуктов без предварительного уведомления. Проектировщики не должны полагаться на отсутствующие характеристики, а также характеристики с пометками «Зарезервировано» или «Не определено». Эти характеристики резервируются Intel для будущего использования, поэтому отсутствие конфликтов совместимости для них не гарантируется. Информация в данном документе может быть изменена без предварительного уведомления. Не используйте эту информацию в окончательном варианте дизайна.
Продукты, описанные в данном документе, могут содержать ошибки и неточности, из-за чего реальные характеристики продуктов могут отличаться от приведенных здесь. Уже выявленные ошибки могут быть предоставлены по запросу.
Перед размещением заказа получите последние версии спецификаций в региональном офисе продаж Intel или у местного дистрибьютора.
Копии документов с порядковым номером, ссылки на которые содержатся в этом документе, а также другую литературу Intel можно получить, позвонив по телефону 1-800-548-47-25 либо на сайте http://www.intel.com/design/literature.htm.
Intel, эмблема Intel, Intel Atom и Intel Core являются товарными знаками корпорации Intel в США и в других странах.
* Другие наименования и торговые марки могут быть собственностью третьих лиц.
Intel, эмблема Intel, Intel Atom и Intel Core являются товарными знаками корпорации Intel в США и в других странах.
* Другие наименования и торговые марки могут быть собственностью третьих лиц.
© Корпорация Intel, 2014. Все права защищены.
Уведомление об оптимизации
Уведомление об оптимизации |
Компиляторы Intel могут не обеспечивать ту же степень оптимизации для других микропроцессоров (не корпорации Intel), даже если в них реализованы такие же возможности для оптимизации, как в микропроцессорах Intel. К ним относятся наборы команд SSE2®, SSE3 и SSSE3 и другие возможности для оптимизации. Корпорация Intel не гарантирует доступность, функциональность или эффективность какой-либо оптимизации на микропроцессорах других производителей. Микропроцессорная оптимизация, реализованная в этом продукте, предназначена только для использования с микропроцессорами Intel. Некоторые виды оптимизации, применяемые не только для микроархитектуры Intel, зарезервированы для микропроцессоров Intel. Ознакомьтесь с руководством пользователя и справочным руководством по соответствующему продукту для получения более подробной информации о конкретных наборах команд, которых касается данное уведомление. Редакция уведомления № 20110804 |
ПРИМЕЧАНИЕ.В зависимости от содержимого могут потребоваться дополнительные уведомления и примечания. Как правило, они находятся в следующих местах, и их необходимо добавить в раздел «Уведомления» соответствующих документов.
Общие уведомления:уведомления, размещаемые во всех материалах, распространяемых и выпускаемых корпорацией Intel.
Уведомления в отношении производительности и ее измерения:уведомления для материалов, используемых корпорацией Intel для измерения производительности или для заявлений о производительности.
Сопутствующие технические уведомления:уведомления, которые следует добавлять в технические материалы Intel, описывающие внешний вид, пригодность или функциональность продукции Intel.
Технические примечания:примечания к материалам Intel, когда описываются преимущества или возможности технологий и программ.
Примечание для технических уведомлений: если все описываемые продукты (например, ACER ULV) обладают определенной функцией или поддерживают определенную технологию, то можно удалить заявление о требованиях в уведомлении. При наличии нескольких технических уведомлений можно объединить все заявления «фактическая производительность может различаться» в одно общее.