Динамический рендеринг ландшафта с буферами аргументов: понимание того, почему буфер частиц не перезаписывается графическим процессором на полете

#objective-c #metal #wwdc

#цель-c #Металлические #wwdc

Вопрос:

Я просматриваю демонстрационный проект Apple, связанный с видеороликом WWDC 2017 года под названием «Представляем Metal 2», где разработчики демонстрируют использование буферов аргументов. Ссылка на проект приведена здесь, на странице «Динамический рендеринг ландшафта с буферами аргументов» на веб-сайте разработчика Apple. Здесь они синхронизируют запись ресурсов процессором для предотвращения условий гонки с a dispatch_semaphore_t , сигнализируя об этом, когда буфер команд завершает выполнение на графическом процессоре и ожидает его, если процессор записывает данные на несколько кадров раньше графического процессора. Это согласуется с тем, что было показано на предыдущем WWDC 2014 года «Работа с металлом: основы».

Я заметил, что, похоже APPLParticleRenderer , он отправляет данные, которые должны быть записаны графическим процессором во время вычислительного прохода, прежде чем он завершит чтение из того же буфера из фрагментного шейдера из предыдущего прохода рендеринга. Режим хранения ресурсов буфера MTLResourceStorageModePrivate . Мой вопрос: автоматически ли Metal синхронизирует доступ к частным id<MTLBuffer> сайтам, доступным только графическому процессору? Имеют ли проходы render, compute и blit, вызываемые из new id<MTLCommandEncoder> , доступ к буферу только после того, как другие проходы записали и прочитали из него (эксклюзивный доступ)? Я видел, что в тайловых шейдерах существуют гарантированные барьеры, когда доступ к тайловой памяти осуществляется исключительно ядром, прежде чем последующие фрагментные шейдеры получат доступ к памяти.

Наконец, в WWDC 2016 «Что нового в металле, часть 2» первый докладчик Чарльз Бриссар в 16:44 упоминает, что чтение и запись функций фрагмента и вершины из одного и того же буфера должны быть помещены в два кодировщика команд рендеринга, но для вычислительных ядер достаточно одного кодировщика вычислительных команд. Это согласуется с тем, что видно в программе визуализации частиц.

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

1. И сразу после фиксации _frameAllocator->switchToNextBufferInRing(); и появления в APPLParticleRenderer меняет параметры друг под другом для своего ядра.

2. Да, я это видел. Я также, вероятно, должен был указать objective-c (я уроженец Swift и мало работал над objective-c). Однако, похоже, что _frameAllocator выделяет только _uniforms_gpu и переключает буфер, на который ссылается там, записывая в него каждый кадр. Существует частный MTLBuffer объект, управляемый средством визуализации частиц _particleDataPool (with MTLResourceStorageModePrivate ), который повторно используется каждый кадр, который, по-видимому, перезаписывается каждый кадр, пока графический процессор все еще считывает из него.

3. Аппаратное обеспечение уровня 2

4. @OlSen Возможно, я нашел свой ответ. Согласно документации Apple, a MTLResource имеет режим отслеживания опасности по умолчанию ( hazardTrackingMode ) of MTLHazardTrackingModeTracked (или MTLHazardTrackingMode.tracked в Swift). Это означает, что Metal отслеживает зависимости между командами, которые изменяют ресурс, как в случае с ядром particle, и задерживает выполнение до завершения предыдущих команд, обращающихся к ресурсу. Следовательно, система тройной буферизации не требуется для ресурса, доступ к которому осуществляется исключительно графическим процессором (если он может быть записан процессором, это другое дело).

5. @OlSen Я добавил больше деталей после дальнейшего изучения синхронизации металлов, если вам интересно

Ответ №1:

Краткую версию этого ответа смотрите в моем комментарии к первоначальному вопросу.

Оказывается, Metal отслеживает зависимости между командами, запланированными для GPU по умолчанию для MTLResource типов. hazardTrackingMode Свойство a MTLResource по умолчанию MTLHazardTrackingModeTracked имеет значение ( MTLHazardTrackingMode.tracked в Swift) в соответствии с документацией Metal. Это означает, что Metal отслеживает зависимости между командами, которые изменяют ресурс, как в случае с ядром particle, и задерживает выполнение до завершения предыдущих команд, обращающихся к ресурсу. Следовательно, поскольку _particleDataPool буфер имеет режим хранения MTLResourceStorageModePrivate ( storageModePrivate в Swift), он может быть записан только графическим процессором; следовательно, синхронизация CPU / GPU с семафором для этого буфера не требуется, и, следовательно, для ресурса не требуется мультибуферная система. Только когда ресурс может быть записан процессором, в то время как графический процессор все еще читает из него, нам нужно несколько буферов, чтобы процессор не простаивал.

Обратите внимание, что режим отслеживания опасности по умолчанию для a MTLHeap MTLHazardTrackingModeUntracked ( MTLHazardTrackingMode.untracked в Swift), и в этом случае вы несете ответственность за синхронизацию записи ресурсов графическим процессором

Редактировать

После прочтения синхронизации ресурсов в Metal я хотел бы сделать несколько дополнительных замечаний, которые, я думаю, еще больше прояснят, что происходит. Обратите внимание, что оставшаяся часть находится в Swift. Чтобы узнать больше подробностей, я рекомендую прочитать раздел «Синхронизация» в документации Metal здесь.

MTLFence

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

Важно отметить, что автоматическое управление, о котором я упоминаю в ответе, происходит только в пределах одного командного буфера между проходами кодирования. Но это не означает, что нам нужно синхронизировать все командные буферы, запланированные в одной очереди команд, поскольку командный буфер не запланирован для немедленного выполнения. Фактически, согласно документации по addScheduledHandler(_:) методу MTLCommandBuffer протокола, найденной здесь

Объект устройства планирует командный буфер после того, как он идентифицирует какие-либо зависимости с рабочими задачами, представленными другими командными буферами или другими API в системе.

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

MTLEvent

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

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

MTLSharedEvent

В случае (без каламбура), если у вас несколько процессоров (разные процессорные ядра, CPU и GPU или несколько GPU), необходима синхронизация ресурсов. Здесь вы создаете a MTLSharedEvent вместо a MTLEvent , который можно использовать для синхронизации между устройствами и процессами. Это, по сути, тот же API, что и у the MTLEvent , но включает очереди команд на разных устройствах.