Лучший способ добавить новый 3D-объект во время выполнения - PullRequest
0 голосов
/ 18 февраля 2019

Все время до сих пор у меня были 3D-объекты, созданные во время запуска.Но теперь мне нужно добавить их динамически.Что может быть проще, подумал я ...

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

Вот мойsetup:

  • Я использую библиотеку выделителя памяти vulkan , так что я свободен от бремени управления памятью.
  • Я планирую использоватьОтдельный VkBuffer для каждого объекта - таким образом, мне не нужно управлять смещениями, выравниванием, и было бы проще добавлять / удалять объекты.

И вот мои мысли / вопросы:

  1. Как загрузить данные?Я хочу, чтобы буфер был только видимым для gpu, это означает, что мне нужен промежуточный буфер.
  2. Если я использую промежуточный буфер, мне нужно знать, когда данные будут готовы для использования в gpu.Я не хочу промывать трубопровод и ждать.Единственный способ, которым я вижу, - это использовать забор для каждого объекта и вызывать команду draw только тогда, когда этот забор готов.
  3. Если я использую промежуточный буфер и хочу загрузить несколько объектов в течение короткого кадра, янужно как-то быть уверенным, что части этого промежуточного буфера не переопределяются разными объектами.Для этого мне нужно держать это большим, обрабатывать выравнивание для смещений.Но насколько большой?

Я почти уверен, что слишком усложняю.Я считаю, что должно быть гораздо проще.Как бы вы это сделали?

1 Ответ

0 голосов
/ 18 февраля 2019

Я полагаю, что должна быть намного более простая модель.

Это Вулкан;это явный низкоуровневый API.«Простой» не является его целью.

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

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

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

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

Если вывозможность использовать отдельную очередь для передачи (будь то настоящая очередь передачи или просто отдельная очередь вычислений / графики), тогда вы можете поэкспериментировать с семафорами.Пакет, который передает данные, должен сигнализировать семафор, когда он завершается;это часть пакета в вызове vkQueueSubmit.Пакет в главной очереди, который использует переданные данные для какого-либо процесса, должен ждать на этом семафоре.Таким образом, оба потока должны использовать один и тот же объект VkSemaphore.И ожидание семафора должно просто иметь глобальный барьер памяти, чтобы сделать память видимой.

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

Если вы не используете вторую очередь, то все немного проще.

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

Самый простой и гибкий способ сделать это - создать CB передачи таким образом, чтобыпоследняя команда - это команда vkCmdSetEvent (а первая команда - vkCmdResetEvent, чтобы сбросить ее с предыдущих кадров).В этом случае потоку подачи необходимо только создать небольшой CB, содержащий только команду vkCmdWaitEvenets, которая ожидает установленного события передачи.Эта команда должна создать полный барьер памяти, и этот CB должен выполняться между CB передачи и любыми CB рендеринга, которые читают из переданных данных.

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

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

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

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

Но насколько велики?

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

Добро пожаловать в использование явных низкоуровневых API.

...