Galactic Civilizations 3 (GC3) — это пошаговая глобальная стратегия, разработанная и выпущенная компанией Stardock Entertainment. Игра увидела свет 14 мая 2015 г. В ходе демонстрационного доступа и бета-тестирования мы собирали и анализировали информацию о производительности процессов рендеринга в этой игре. Одним из основных улучшений, которые нам удалось осуществить, стало устранение нескольких источников задержки синхронизации центрального и графического процессоров, в результате которой нарушался параллелизм в работе процессоров. В этой статье описывается выявленная проблема и найденное решение, а также рассматривается важность использования средств анализа производительности в процессе разработки с учетом преимуществ и недостатков этих средств.

Выявление проблемы


Мы начали изучение эффективности рендеринга с помощью анализаторов производительности графической подсистемы Graphics Performance Analyzers, входящих в состав пакета Intel INDE. На снимке экрана внизу представлены данные трассировки (без вертикальной синхронизации) до внедрения усовершенствований. В очереди графического процессора наблюдаются промежутки внутри кадров и между ними, причем в каждый момент времени объем отложенной нагрузки составляет менее одного кадра. Если очередь графического процессора не получает достаточного объема ресурсов от центрального процессора, возникают временные промежутки, которые не могут использоваться приложением для повышения производительности и точности рендеринга.


До: длительность кадра — около 21 мс; длина очереди — менее 1 кадра; промежутки в очереди графического процессора; слишком длительные вызовы метода Map

Кроме того, в интерфейсе GPA Platform Analyzer показывается время, затрачиваемое на обработку каждого вызова API-интерфейса Direct3D* 11 (т. е. передачи каждой команды по пути «приложение — среда выполнения — драйвер» и получения отклика). На приведенном снимке экрана показан вызов метода ID3D11DeviceContext::Map, который занимает около 15 мс вместе с получением отклика. В течение этого времени основной поток приложения простаивает.

На снимке ниже показана увеличенная временная шкала с интервалом обработки одного кадра (от начала операции, выполняемой центральным процессором, до окончания операции, выполняемой графическим процессором). Промежутки простоя отмечены розовыми прямоугольниками, их общая длительность составляет около 3,5 мс на кадр. Средство Platform Analyzer также отображает общую длительность вызовов различных API-интерфейсов в данной трассе (4,306 секунды), из которой вызовы Map занимают 4,015 секунды!



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

Поиск источника проблемы


(В разделе, посвященном ресурсам Direct3D, в конце статьи вы найдете базовые инструкции по использованию и обновлению ресурсов.)
Средство отладки драйвера выявило, что длительный вызов Map использует флаг DX11_MAP_WRITE_DISCARD (в интерфейсе Platform Analyzer не отображаются аргументы вызова Map) для обновления крупного буфера вершин, созданного с помощью флага D3D11_USAGE_DYNAMIC.

Этот способ очень часто используется при создании игр для оптимизации потоков данных при обращении к часто обновляемым ресурсам. При сопоставлении динамического ресурса с помощью признака DX11_MAP_WRITE_DISCARD функция возвращает алиас, выбранный из кучи алиасов данного ресурса. Алиас отвечает за выделение памяти для данного ресурса при каждом сопоставлении. Когда пространство для алиасов в используемой куче ресурса заканчивается, выделяется теневая куча алиасов Это продолжается до достижения максимального числа куч для данного ресурса.

Именно в этом состояла проблема в игре Galactic Civilizations 3. При каждом возникновении подобной ситуации (то есть несколько раз за время обработки каждого кадра для нескольких крупных ресурсов, которые сопоставлялись многократно) драйвер ожидал, пока метод Draw, использующий ранее назначенный алиас ресурса, завершит процесс, чтобы использо¬вать этот алиас для другого запроса. Данная проблема возникала не только с драйвером Intel. Она также возникала с драйвером NVIDIA, и в этом случае мы использовали средство GPUView, чтобы подтвердить данные, полученные с помощью анализатора Platform Analyzer.

Размер буфера вершин составлял около 560 КБ (определено с помощью драйвера), а сопоставление с буфером выполнялось примерно 50 раз за время обработки одного кадра (со сбросом). Для хранения алиасов драйвер Intel выделяет по требованию несколько куч (размером 1 МБ каждая) на ресурс. Алиасы выделяются из кучи до достижения предельного объема, после чего ресурсу назначается другая теневая куча алиасов размером 1 МБ и так далее. В случае с длительным вызовом Map куча вмещала в себя не более одного алиаса, поэтому каждый раз, когда метод Map обращался к ресурсу, создавалась новая теневая куча для нового алиаса до тех пор, пока не достигалось предельное количество куч. Это происходило при обработке каждого кадра (этим объясняется повторение конфигурации на диаграмме). При каждом обращении драйвер ожидал, пока предыдущий вызов Draw (выполняемый для того же кадра) завершит использование алиаса, чтобы использовать его повторно.

Мы изучили журнал API-интерфейса в средстве Frame Analyzer и отсортировали ресурсы, которые сопоставлялись несколько раз. Обнаружилось несколько случаев, когда сопоставление с буфером вершин выполнялось более 50 раз, причем основным источником проблемы оказалась система интерфейса пользователя. Средство отладки драйвера выявило, что при каждом сопоставлении обновлялся только небольшой фрагмент буфера.


Один и тот же ресурс (с идентификатором 2322) многократно сопоставляется за время обработки одного кадра

Решение проблемы


Мы в Stardock выполнили настройку всех систем визуализации для отображения дополнительных маркеров на временной шкале Platform Analyzer, в частности для того, чтобы убедиться, что источником слишком длительного вызова был пользовательский интерфейс, а также для создания профилей в будущем.
У нас было несколько возможных способов решить проблему.
  • Можно было заменить флаг D3D11_MAP_WRITE_DISCARD на D3D11_MAP_WRITE_NO_OVERWRITE для вызова Map. Крупный буфер вершин используется несколькими схожими элементами. Например, большинство элементов пользовательского интерфейса на экране совместно используют крупный буфер. Каждый вызов Map обновляет лишь небольшой отдельный фрагмент буфера. Космические корабли и астероиды, для которых применяется технология хранения экземпляров, также используют крупный буфер вершин (данных экземпляров). В данном случае флаг D3D11_MAP_WRITE_NO_OVERWRITE был бы идеальным решением, поскольку он обеспечил бы защиту фрагментов буфера, которые могут использоваться графическим процессором в данный момент, от перезаписи приложением.
  • Существовала возможность разделить крупный буфер вершин на несколько мелких. Поскольку причина сбоя синхронизации заключалась в выделении алиасов, благодаря значительному сокращению размера буфера вершин куча смогла бы вмещать несколько алиасов. Число вызовов Draw в приложении Galactic Civilizations 3 ограничено, поэтому сокращение размера буфера в 10 или в 100 раз (с 560 КБ до 5–50 КБ) позволяло решить проблему.
  • Еще один вариант заключался в использовании флага D3D11_MAP_FLAG_DO_NOT_WAIT. С его помощью можно определить, используется ли данный ресурс графическим процессором, и выполнить другую задачу, прежде чем ресурс освободится для нового сопоставления. Несмотря на то что в данном случае нагрузка выполняется центральным процессором, это решение было далеко не оптимальным для данной проблемы.

Мы выбрали второй вариант и заменили константу в алгоритме создания буфера. Размеры буферов вершин для каждой подсистемы были жестко запрограммированы, их требовалось только снизить. Теперь каждая куча размером 1 МБ могла вмещать несколько алиасов и, благодаря сравнительно небольшому числу вызовов Draw в приложении Galactic Civilizations 3, проблема должна была исчезнуть.
Устранение данной проблемы в одной подсистеме визуализации увеличивало ее масштабы в другой, поэтому описанные действия были выполнены во всех подсистемах. На снимке экрана ниже показана трассировка с учетом исправлений и внедрения новых средств, а также увеличенное представление одного кадра.


После: длительность кадра — около 16 мс; длина очереди — 3 кадра; отсутствие промежутков в очереди графического процессора; отсутствие длительных вызовов метода Map



Общая длительность вызовов метода Map сократилась с 4 секунд до 157 миллисекунд! Исчезли задержки в очереди графического процессора. Длительность очереди стабильно составляла 3 кадра, и по окончании обработки кадра графическим процессором следующий кадр уже ждал своей очереди! Несколько несложных изменений помогли обеспечить непрерывную работу графического процессора. Повышение быстродействия составило около 24 %: время обработки каждого кадра сократилось примерно с 21 до 16 мс.
?

Заключение


Оптимизация производительности процессов визуализации в играх представляет собой непростую задачу. Средства захвата и воспроизведения кадров и трасс предоставляют различные важные сведения о производительности игры. В этой статье были рассмотрены задержки синхронизации центрального и графического процессоров, для диагностики которых требуются такие средства трассировки, как GPA Platform Analyzer или GPUView.
?

Основные сведения о работе с ресурсами Direct3D*


В API-интерфейсе Direct3D можно выделить средства создания и удаления ресурсов, задания состояния конвейера рендеринга, привязки ресурсов к элементам конвейера, а также средства обновления определенных ресурсов. Большинство операций по созданию ресурсов выполняется во время загрузки уровней и сцен.

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

Во время создания ресурса элемент перечисления D3D11_USAGE используется для задания следующих параметров ресурса:
  1. доступ ГП для чтения и записи (DEFAULT — для объектов визуализации, элементов UAV, редко обновляемых буферов констант);
  2. доступ ГП только для чтения (IMMUTABLE — для текстур);
  3. доступ ЦП для записи + доступ ГП для чтения (DYNAMIC — для часто обновляемых буферов);
  4. доступ ЦП с возможностью для ГП копировать данные в ресурс (STAGING).

Обратите внимание, что для обеспечения вариантов использования 3 и 4 необходимо правильно установить флаг D3D11_CPU_ACCESS_FLAG для ресурса.
Для обновления данных ресурса в API-интерфейсе Direct3D 11 предусмотрено три метода, каждый из которых выполняет определенные задачи (как описано выше):
  1. Map/Unmap;
  2. UpdateSubresource;
  3. CopyResource/CopySubresourceRegion.

Существует интересный сценарий, требующий неявной синхронизации: когда ЦП имеет доступ к ресурсу для записи, а ГП — для чтения. Подобный сценарий часто встречается во время обработки кадров. В качестве примеров можно привести обновление матрицы представления (модели, проекции) или преобразование связующей матрицы модели (model bone) в анимации. Ожидание, пока графический процессор завершит использование ресурса, привело бы к неоправданному снижению быстродействия. Создание нескольких независимых ресурсов (копий ресурса) для реализации этого сценария слишком усложнило бы задачу для создателей приложения. В результате в интерфейсе Direct3D версий 9–11 эта задача передается на драйвер с помощью флага DX11_MAP_WRITE_DISCARD. Каждый раз, когда сопоставление ресурса выполняется с помощью этого флага, драйвер создает новую область памяти для ресурса, которую использует центральный процессор. Таким образом, различные вызовы Draw, которые обновляют данный ресурс, используют разные псевдонимы ресурса, что, несомненно, повышает коэффициент использования памяти ГП.
Дополнительные сведения об управлении ресурсами в Direct3D:

Комментарии (6)


  1. Warezovvv
    25.09.2015 11:30

    Не могу понять (оффтоп):
    Steam делает свою операционку и свои Steam машины. Многие игры сейчас переводятся на OpenGL ради поддержки операционки стима. Недавно я был оочень приятно удивлён тем, что на моём ArchLinux запустился Company of Heroes 2 без лагов / фризов с хорошим фпс. Так почему люди сразу не пишут на OpenGL?
    А тулзы у Интела — 5+. Очень удобные.


    1. realimba
      25.09.2015 12:16

      У большинства пользователей малобюджетные/доисторические видеокарты с кривыми драйверами OpenGL и они тоже хотят играть. Поэтому выбор для ПК очевиден. Вспомним для примера историю с Rage и ATI.


    1. creker
      25.09.2015 14:03
      +1

      Многие игры сейчас переводятся на OpenGL ради поддержки операционки стима

      Единицы переводятся, а операционка стима, грубо говоря, на рынке не представлена. Это не платформа, раз, просто еще один Linux дистриб. В отличие от убунты, ее доля вообще нулевая, два. В свете опоздания Vulkan за DX12 и ухода Apple на Metal для приемника все выглядит еще хуже.

      Так почему люди сразу не пишут на OpenGL?

      А зачем писать, когда у подавляющего большинства разработчиков связка windows+xbox. Там сейчас Dx12. В итоге, OpenGL не нужен и только мешается, потому что тут же натыкаешься на вечные проблемы с драйверами (вспоминается Rage от Кармака, которому именно качество поддержки OpenGL испортило релиз).


      1. semenyakinVS
        26.09.2015 01:41
        +1

        А вот интересно, чего с openGL такая проблема? Казалось бы, это должно быть более качественное API — ведь его разработка распределённая, реализацию могут максимально подогнать под своё железо производители видеокарт. В то время как Dx разрабатывается авторитарно майкрософтами под все видяхи которые есть и которые будут (насколько я понимаю, если ошибаюсь — было бы интересно узнать как это выглядит на самом деле) — и, следовательно, всё должно быть более громоздким и глючным.


        1. khim
          26.09.2015 14:17
          +1

          Производители и «подгоняют» реализуя спорные места (а их есть в любой спецификации, тем более такой большой) так, как им захочется. А потом они ошибки исправляют когда им об этом сообщат. Но только для последних GPU (ну действительно: не возиться же с GPU выпущенными лет 10 назад, правда?). А потом на всём этом кто-то пытается запустить игру.

          Распределённая разработка — путь куда угодо (к дешевезне за счёт конкуренции, к богатству возможностей за счёт выбора разных подходов и т.д. и т.п.), только не к качественной реализации чего-либо.

          А насчёт последствий разработки Dx только Microsoft'ом… вы неправильно понимаете ситуацию: если Microsoft скажет что где-то нужно красить по вертикали, а не пе горизонтали, а создатель GPU их не послушается, то дальше — вариантов два:
          1. Не послушался сейчас — исправит в следующей версии (пример — nVidia GeForce FX, оптимизированная под FP 16bit, в то время как Microsoft требовал 24).
          2. Не хочет исправлятьсяя — вылетит с рынка (пример — PowerVR, который «рулит» на рынке мобильной графики, но исчез с рынка настольной «не вписавшись» в DirectX видение мира).
          Это, понятно, мало нравится разработчикам железа (но куда им деваться?), зато очень помогает разработчикам игр.


        1. creker
          03.10.2015 15:43
          +1

          Именно авторитарность DX и ключ его успеха. МС это общий знаменатель. Они вводят спецификации и требования к железу и разработчики железа просто не имеют выбора кроме как подчиниться — это банально в их же интересах. Галочка «поддерживает DX12» это хорошее подспорье для покупки новой видеокарты. Кроме того это высшее благо для разработчиков игр. Нет ничего лучше, когда у тебя есть четкие спецификации и гарантии совместимости. DX их предоставляет. Разработчикам остается лишь заниматься главным — делать свой продукт, а не бороться с драйверами и железом. Разве что багрепорты отправлять, которые быстро исправляются вендорами еще на стадии разработки игры.

          МС не делают все под все видяхи. Они дают вендорам только API и спецификации. Каждый вендор сам пишет свои драйвера. Вот архитектура их модели драйверов https://msdn.microsoft.com/en-us/library/windows/hardware/ff570589(v=vs.85).aspx Серым выделены компоненты, которые пишет и подгоняет под свое железо вендор.