Как следует правильно использовать промежуточный буфер с точки зрения производительности?

#memory-management #buffer #vulkan

#управление памятью #буфер #vulkan

Вопрос:

Как следует правильно использовать промежуточный буфер Vulkan API? Поскольку сохранение данных ваших вершин в промежуточном буфере, а не копирование в буфер вершин в графическом процессоре, кажется, требует большего времени, чем просто прямая отправка ваших вершин в буфер вершин. Это программа-клон Minecraft, поэтому будет много вершинных данных (с индексными данными тоже) и динамической загрузкой блоков, так есть ли какие-либо другие виды буфера или метод буферизации, которые могли бы извлечь выгоду из этого?

Даже тогда использование отдельных потоков устройств или даже потоков между устройствами кажется медленнее, чем просто прямая отправка ваших вершин в буфер вершин на лету. И в настоящее время я не совсем понимаю плюсы и минусы использования традиционного прямого вершинного буфера по сравнению с промежуточным буфером.

В руководстве, которое я сейчас просматриваю, промежуточный буфер используется один раз перед чертежами и презентацией. Похоже, что не хватает форумов или статей, в которых обсуждается проблема, точно описанная выше.

Комментарии:

1. » просто отправляйте свои вершины в буфер вершин на лету». Что именно вы имеете в виду под этим? Вы не можете «отправлять вершины» в буфер из ЦП, если этот буфер не находится в памяти, доступной для записи ЦП. И если он доступен для записи в ЦП… почему вы используете промежуточный буфер?

2. Я действительно не знаю правильного слова для «отправки» вершин на устройство. Но я говорю, что когда вы записываете командный буфер, вы помещаете вершинный буфер в vkCmdBindVertexBuffers и запускается в каждом кадре??

Ответ №1:

Точная механика, которую можно использовать для достижения высокой производительности, будет сильно зависеть как от деталей оборудования, так и от ожидаемой частоты обновлений данных.

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

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

Но локальная память устройства (обычно) недоступна напрямую из ЦП; отсюда и необходимость промежуточного использования.

Однако память, которая не является локальной для устройства, может использоваться для задач графического процессора. То есть графический процессор может иметь возможность считывать данные из доступной для процессора памяти напрямую для определенных видов операций. Вы можете спросить, можно ли использовать определенный тип памяти в качестве исходной памяти для данных вершин.

Если доступная для процессора память, не относящаяся к локальному устройству, может использоваться для вершинных данных, то теперь у вас есть реальный выбор: из какой кучи читать? Считываете ли вы данные вершин по шине PCI-e? Или вы переносите данные вершин в более быструю память, а затем считываете из нее?

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

И если бы вы выполняли какую-то потоковую передачу данных, когда вы загружаете мировые данные по мере перемещения камеры по сцене, тогда я бы сказал, что лучше всего было бы перенести их в локальную память устройства. Вы используете данные гораздо чаще, чем выполняете операции передачи, и вы должны иметь возможность сбрасывать целые куски данных за несколько вызовов передачи.

Но ваш вариант использования касается прерывистой генерации данных. Даже если проигрыватель постоянно размещает блоки, вы, вероятно, будете повторно использовать одни и те же данные для рендеринга нескольких кадров. И даже когда блок размещен, вы изменяете только один подраздел данных. Каждая операция передачи сопряжена с определенными накладными расходами, поэтому выполнение нескольких небольших передач может замедлить работу.

Таким образом, трудно сказать, что лучше; вы должны профилировать его на различных аппаратных средствах, чтобы увидеть, какова производительность.

Кроме того, имейте в виду, что некоторые дискретные графические процессоры будут иметь специальную кучу размером около 256 МБ. Он особенный, потому что он доступен как для процессора, так и для локального устройства. Предположительно, существует какой-то быстрый канал для ЦП для записи своих данных в память этого устройства. Эта куча памяти предназначена для потокового использования, поэтому она будет довольно хороша для ваших нужд (при условии, что ее размер достаточен; размер обычно составляет около 256 МБ независимо от общей памяти графического процессора).

Комментарии:

1. Если быть более точным, я имею в виду, что между каждым кадром не обязательно будет генерироваться данных, но более условно с разным размером. Например, когда игроки перемещаются, загружая новый фрагмент и генерируя большой фрагмент данных, или когда игрок уничтожает или помещает один блок, таким образом, генерируя небольшое количество новых данных. Итак, я подумал, можно ли использовать буфер этапа и использовать буфер, доступный для процессора, условно. Однако это выглядит действительно излишне сложным. Я работаю на дискретном графическом процессоре NVidia, если это поможет.

2. Я полагаю, что вы используете слово «пул» вместо термина «куча», который используется в спецификации Vulkan, я прав? Вы упомянули, что AMD HW, как правило, имеет видимый «пул» устройства-локального хоста. По-видимому, у Nvidia это тоже есть: vulkan.gpuinfo.org/displayreport.php?id=16387#memory

3. @tuket: Хорошая мысль; Я скорректировал формулировку.

Ответ №2:

Коротко и просто:

  1. Промежуточный буфер — это просто способ копирования данных из CPU в память GPU

  2. Если данные находятся в памяти процессора, они считываются через PCI-e и кэшируются графическим процессором во время доступа

  3. PCI-e имеет относительно ограниченную пропускную способность и некоторую задержку

  4. Бессмысленное копирование всего этого в каждом кадре бесполезно и добавит накладных расходов.

Итак:

  1. Если память часто изменяется процессором (например, матрицы камеры, положение игрока) — тогда пусть она будет в памяти процессора

  2. Если данные редко / только небольшие их части модифицируются процессором — затем перенесите их на GPU

В вашем случае:

  1. Буфер с воксельными данными изменяется: во время загрузки фрагмента и когда игрок изменяет мир. Итак, первый случай редок — используйте асинхронное копирование, когда это происходит, второй случай — маленький и редкий — если вы записываете свои командные буферы каждый кадр, вы даже можете незаметно скопировать эту копию туда…

У Никола Боласа есть более подробный ответ

Кстати, если это клон minecraft, зачем использовать много индексов? Просто запишите несколько фигур для разных блоков, а затем используйте instancing для хранения мировых позиций и идентификаторов текстур для них. Используйте команду рисования для каждой другой фигуры, которую вы рисуете. Создание экземпляра тривиально для реализации в Vulkan. Вы даже можете использовать вычислительные шейдеры для создания мировых данных практически мгновенно!

Комментарии:

1. Я использовал индексы, потому что думал, что смогу сохранить некоторый размер вершинного буфера, используя индексы меньшего размера. Для квадрата требуется два треугольника: таким образом, будет 6 вершин, в 6 раз превышающих размер (вероятно, больше, чем размер каждого индекса). Итак, я подумал, что использование индексов может сэкономить 2 вершины размером n при значительно меньшем размере индекса (u32). Там будет много вершин, поэтому я подумал, что это может сильно помочь в использовании памяти.

2. Я имел в виду, что вы сказали, что будет много данных о вершинах и индексах, в то время как при использовании экземпляра вы могли бы хранить только один куб вершин и индексов (8v 36i немного) и использовать данные экземпляра для их различения. Если у вас нет какой-то сложной оптимизации, для которой требуются отдельные данные вершин?

3. Извините, я имею в виду много вершин и, возможно , много индексных данных. Итак, если я использую инстансинг, единственное, что мне нужно передать, кроме куба и самих индексов, — это позиция. И поэтому данные об освещении, текстурах и всех других данных куба могут храниться в основном буфере вершин?

4. Запеченные огни… В динамической среде? Вы уверены, что это хорошая идея? Если это так, то вам, вероятно, понадобятся отдельные вершины. Координаты текстуры проще, поскольку в одной и той же геометрии они практически одинаковы, и для выбора разных текстур вы можете просто смещать или накладывать на них данные, передаваемые через данные экземпляра, поскольку каждый блок одного и того же типа имеет одинаковую текстуру.

5. О, кстати, вы могли бы поместить команду copy buffer в свою команду рисования, а затем барьер памяти. Каждый раз, когда вы его записываете, вы указываете, какие части памяти копировать. Но имейте в виду, что это может и, скорее всего, вызовет заикание. Также vkBindVertexBuffers ничего не копирует — он просто сообщает следующим командам рисования использовать этот буфер, чтобы вы могли переключаться между буферами вершин, используемыми внутри одного командного буфера