The session essentially focuses on case study of porting Divinity Original Sin 2 engine to Apple Metal API. Real-life examples are provided as well as a demo of frame capture, dissection and explanations of techniques used. The case study is followed by best practices section with advises for transitioning generic titles to Metal. The session concludes with ‘future tech’ section where advanced samples of Metal rendering technologies are displayed and briefly explained.
5. С ❤️ от Elverils
• DevGamm 2019 Minsk активно демонстрирует самые последние
графические технологии Apple - много сессий посвящены Metal для
продвинутых пользователей
• Мы надеемся это только начало и при наличии интереса всегда
готовы рассказать о нашем опыте и поделиться знаниями
• Огромное спасибо команде Larian за любовь к платформе
• Команде Apple Metal Engineering Team
• WWDR Россия
• Проекту The Forge и лично Wolfgang Engel
6. Кросс-платформенная реализация игровых проектов
• Elverils - code magic at your fingertips
• Год основания - 2007 (более 8 лет в игровой индустрии)
• Изначально занимались сетевой безопасностью
• Специализация на низкоуровневых оптимизациях
• Уникальность в изначальной ориентации на портирование
• Огромный опыт работы с macOS, iOS и консолями
• Предпочтение работы с самостоятельными (не построенными на
готовых платформах) проектами
7. Кросс-платформенность в ваших играх
• Вы хотите сделать вашу игру кросс-платформенной - что же делать?
• Не хочется покупать аппаратуру для тестирования и оптимизации
• Хочется все сделать быстро и с минимальными затратами
• Дополнительная платформа не является весомым источником
прибыли
• Вы плохо представляете себе возможности альтернативной
платформы
• В таком случае - лучше вообще ничего не делать!
8. Кросс-платформенность в ваших играх
• Сегодня мы не говорим о готовых решениях (Unity, Unreal, etc)
• Из чего состоит наша игра - узнайте свои сторонние библиотеки (и
их поддерживаемые платформы)
• Трезво оцените свой код и отделите непортабельный код
• Обдумайте начальный уровень ОС на которой будет работать ваш
проект
• Думаем на 2 шага вперед - сегодня desktop - завтра mobile
9. Кросс-платформенность в ваших играх
Чем плохи готовые решения
• Вы вряд ли захотите править их код (если это вообще возможно)
• Вы не можете объективно оценить степень их современности и
оптимизации под конкретную платформу
• Ошибки в них никто не отменял
• Все что выходит за рамки готового решения = головная боль для
вас
• Как правило ничего не работает в “один клик”
10. Кросс-платформенность в ваших играх
Чем плохи трансляторы API (MoltenVK и другие)
• Разобраться в них подчас еще сложнее чем в готовых решениях
• Их качество не позволяет задействовать всю мощь доступного
оборудования
• Ошибки, ошибки, ошибки
• Графическая отладка таких решений сплошная головная боль
• Про мобильные платформы можно забыть
11. Как оценить временные затраты на кросс-
платформенную разработку
• Анализ существующего кода / процент кода не являющегося кросс-
платформенным
• Анализ сторонних библиотек - наличие исходного кода или
необходимость оплаты лицензий
• Графика важна - но это обычно меньше 5 % кода - не
преувеличивайте ее значение!
• Проверяем как обрабатывается ввод и аудио
• Поддержка контроллеров
• Поддержка CPU инструкций SSE2, AVX/AVX2 класса
• Continuous integration- экономим время и деньги
12. Уважаем платформу под которую портируем
• Пользователи все видят и все замечают - если мы изначально
считаем их требования “несущественными” лучше и не браться
• Изучаем файловую систему и правильное расположение наших файлов
в ней
• Честные бинарные файлы - не используем сомнительные
практики
• Оптимизация для конкретных CPU
• Изучаем инструменты и их возможности - все конечно можно
сделать и в Visual Studio Code - но лучше не надо
13. Уважаем платформу под которую портируем
• Оцениваем возможности использования специфических особенности
аппаратуры на которую портируем
• Собираем сторонние библиотеки сами если это возможно
• Изучаем техники PGO/LTO и другие доступные на платформе
• Отсекаем поддержку только того, что категорически невозможно
поддержать (оценив временные затраты на поддержку)
• Разбираемся с цифровой подписью
• Изучаем особенности и возможности платформы для конкретного
магазина - Steam/GOG/Epic Store
14. App Store - это просто
• Оцениваем финансовую сторону вопроса
• Sandbox - для игр не опасен
• Желательно использование сервисов Apple - iCloud, GameCenter
• Ревью не должно вас пугать
• Весь процесс можно и нужно автоматизировать
• Launcher - это удобно для вас и для пользователей
• Инструкция к игре - желательно приложить
15. Общение с Apple - наш опыт
• При портировании игр A класса и выше этого не избежать
• WDR Россия
• Категорически избегать негатива и неконструктивной критики
• Радары - правильное заполнение ключ к успеху
• Использование DTS
• Посещение WWDC
• Использование форумов для разработчиков
• Если дела совсем плохи - info@elverils.com
16. Игровой движок Divinity 2 - теория
Обратите внимание, даже иконка сделана специально для macOS
17. Игровой движок Divinity 2 - теория
• Deferred PBR с использованием LightProbes и ClusteredLighting
• DirectX11 API взят за основу
• Делали OpenGL - знакомы с внутренностями
• Результат работы - код рендера специфический для платформы <
3% всего кода
• Результат работы - код рендера готов для начального переноса на
мобильные платформы (нужно понимать, что перенос на
мобильные платформы это гораздо более сложная задача чем
просто единое API)
18. Перенос на Metal - знакомимся с API
• Только ObjectiveC (non-ARC модель)
• Прямая интеграция с C++ кодом через использование Objective
C++ - ноль проблем на протяжении всей разработки
• Использование Metal 1.2 профиля (таргет 10.13+)
• Использование графического инструментария XCode
• Изначальное таргетирование AMD, Intel, eGPU
• Основные инструменты разработки MacBook 15” 2017, iMac 5K,
Mac Mini 2019
19. Перенос на Metal - знакомимся с API
• Современное API класса DirectX12 & Vulkan - появившееся и работающее на устройствах ранее
предшественников
• Активно развивается
• Прекэшированные состояния
• Полностью ориентирован на много-поточность
• Достаточно низкоуровневый доступ
• Возможность изначально доверить 99% синхронизационной работы драйверу
• Полный комплект инструментов для отладки
• Больше похож на OpenGL, но имеет ряд существенных отличий
• Собственный язык шейдеров (стандартный clang как компилятор)
• Унифицированный подход к шейдерам/compute коду
• Run-time Валидатор
• macOS, iOS, tvOS, etc.. и поддержка всего современного железа
• Хорошая документация и много обучащих видео на сайте developer.apple.com
20. Перенос на Metal - знакомимся с API
• Шейдеры - проще один раз сделать все правильно
• Могут компилироваться заранее
• Шейдер на С++ это удобно
• Инкапсулировать входные параметры внутри класса/структуры
• Передавать входные параметры во все необходимые функции
• Адекватная документация на сайте Apple
• Pro информация - так что же такое шейдер в металле?
21. Особенности Metal Rendering Pipeline
Сегодня мы не рассматриваем сложные конфигурации. Все будет просто - как и в нашем порте
-
Command Buffers
• Может быть один или несколько - разбиение производится по числу внутренних encoder-ов
• Всегда выполняются в том порядке как указал программист
• Могут заполняться на стороне CPU параллельно
• Ранняя отправка позволяет раньше начать работу GPU
RenderEncoders
• Основной блок рендеринга или компьюта. Их число нужно минимизировать. При начале
выполнения могут очищать или загружать RT.
• Порядок выполнения определяется драйвером при помощи указаний программиста или
полностью автоматически
22. Особенности Metal Rendering Pipeline
Процесс создания/завершения MTLRenderCommandEncoder
• Требуется при любых изменениях указателей на RT & Depth/Stencil
• Требуется при необходимости выполнить операции Compute или Blit
(MTLComputeCommandEncoder, MTLBlitCommandEncoder)
• Не требуется при переключении:
• Шейдеров
• Формул Depth/Stencil
• Viewport & Scissor
• Разнообразных буферов для вертексного, фрагментного и других
шейдеров
• И т.д.
Особенности Metal Rendering Pipeline
23. Особенности Metal Rendering Pipeline
MTLRenderPipelineDescriptor - шейдер + кое-что еще
• Сами функции вертекса и фрагмента
• Vertex descriptor
• Спецификация на выходные текстуры, depth/stencil
• Спецификация параметров смешивания
• Тесселяция, растеризация, etc
• Идея состоит в том чтобы либо изначально знать все возможные
состояния шейдера (и потенциальных выводов), либо “лениво”
создавать их по ходу программы
24. Особенности Metal Rendering Pipeline
Очистка и сохранение - ставим правильно и постоянно проверяем
• В Metal нельзя в произвольный момент времени очистить текстуру
или буфер глубины
• Драйвер может крайне агрессивно применять состояния особенно на
Intel GPU
• Неверные параметры могут приводить к сбоям GPU
• Нельзя проверить валидатором так как полностью в компетенции
разработчка
• Хорошо видны на диаграмме зависимостей
• Установка “в лоб” ведет к серьезной потере производительности
25. Особенности Metal Rendering Pipeline
Depth/Stencil & текстурирование
• В Intel GPU нет Depth24S8
• В Metal нет RGB8 текстур
• В Metal есть все современные BCx компрессии
• На iOS нет BCx зато есть ASTC
• Формат DDS не поддерживается напрямую - нативный формат
для Metal это KTX
• В Metal есть все для поддержки HDR
26. Особенности Metal Rendering Pipeline
В какой момент все это надо делать?
• При портировании приложений с DirectX11/OpenGL возникает
проблема внутреннего состояния рендера которого у нас нет
• Одним из разумных путей является создание собственного
легковесного объекта состояния
• В него входят RT,Depth/Stencil, установленные
текстуры/сэмплеры и т.д
• Непосредственно решения о создании/переключении encoder-ов
можно принимать перед вызовом drawcall
27. Особенности Metal Rendering Pipeline
Переключение шейдера
• Проблема состоит в том что любой v/f шейдер может:
• Иметь разные входные вертекс форматы
• Осуществлять вывод в разный формат текстур
• Использовать разные blend формулы
• Каждый такой случай требует отдельного MTLRenderPipelineState
• Мы использовали хэш в который попадали - BlendState, VertexFormat,
формат RT[массив], формат Depth, формат Stencil
• В хэше лежали пары ключ/MTLRenderPipelineState
28. Особенности Metal Rendering Pipeline
Кэширование состояний - что нужно и можно кэшировать
MTLDevice/MTLCommandQueue
MTLLibrary
MTLRenderPipelineState/MTLComputePipelineState
MTLSamplerState
MTLDepthStencilState
MTLBuffer
MTLTexture
• В большинстве случаев хранить сущности *Desc не требуется
• Внутри рендера всегда хранятся текущий MTLCommandBuffer и encoder-ы
• MTLFunction хранить не рекомендуется
29. Особенности Metal Rendering Pipeline
Триплицирование данных
• CPU & GPU в идеале работают параллельно. Вполне возможна ситуация при
которой CPU захочет формировать новые кадры в то время как GPU еще не
закончило работу с предыдущим кадром
• Четко отделите все буфера которые не требуют триплицирования
• Возможно в вашем случае триплицирование можно заменить на rolling
buffer
• Используйте типовые подходы dispatch_semaphore из примеров для
гарантии безопасной записи
• Всегда обрабатывайте ситуации нехватки памяти
30. Особенности Metal Rendering Pipeline
Как поддержать идеологию всем знакомых Uniform-ов
• При наличии сотен шейдеров и тысячи drawcall держать отдельный буфер для каждого
шейдера очень накладно
• Особенно если шейдер используется многократно с разными uniform значениями - в
таком случае необходимо будет делать массив буферов - еще более сложное решение
• Решением является большой (~10Mb) триплицированный буфер для хранения uniform
значений
• При установке движком uniform-а его данные копируются в буфер
• Процессом выделения памяти в буфере является инкрементирование смещения
• Помните про выравнивание
• Константы малых размеров можно ставить напрямую без буфера (не увлекайтесь
этим!)
31. Особенности Metal Rendering Pipeline
Управление памятью
• При знакомстве с Metal не используйте MTLHeap (который является очень мощным
средством и категорически необходим для некоторых случаев)
• Помните что создание буфера/текстуры во время формирования кадра - это плохая
идея - необходимо выполнять такие операции заранее или использовать MTLHeap
• Всегда ставьте константные текстуры и буфера в MTLResourceStorageModePrivate
• На macOS старайтесь использовать MTLResourceStorageModeManaged для ресурсов
большого размера, но помните что это дублирует память для дискретных видеокарт
• Для небольших буферов которые часто обновляются со стороны CPU можно
использовать MTLStorageModeShared
• На iOS MTLStorageModeMemoryless позволяет экономить память но имеет ряд
серьезных ограничений
32. Особенности Metal Rendering Pipeline
Синхронизация (когда за вас все сделают и так)
• Не используйте MTLHeap если вы не уверены, что он вам нужен.
(На будущее) Разберитесь как он работает и что изменяется в
синхронизации в таких случаях
• Расставьте правильно Load/Store операции
• Драйвер самостоятельно распараллелит операции внутри
CommandBuffer
• (На будущее) При работе в iOS вам нужно будет использовать и
MTLHeap и синхронизационные примитивы для экономии памяти
и убыстрения работы
33. Особенности Metal Rendering Pipeline
Особенности видеокарт
• Старайтесь адаптировать проект так, чтобы он работал на всех видеокартах
• В iGPU Общая память (но использование Private памяти абсолютно
оправдано)
• В iGPU нет D24S8
• Вариативное количество потоков в Compute
• Дайте пользователю возможность переключить устройство вывода
(MTLDevice)
• Используйте информацию внутри MTLDevice для адаптации вашего движка
под устройство
34. Особенности Metal Rendering Pipeline
eGPU поддержка
• При написании правильного кода работает сразу
• Возможно добавление функционала динамического переключения GPU при
отключении eGPU
• Возможно использование нескольких GPU одновременно
• Следует обратить внимание на корректную идентификацию монитора,
подключенного к eGPU
• Помните что eGPU может выводить на любой монитор, но для получения
полной мощности необходим вывод на монитор подключенный к eGPU
• Для работы с мониторами используйте NSScreen
35. Особенности Metal Rendering Pipeline
Помогаем драйверу улучшить производительность
• Минимизируем вызовы Metal API
Не ставим одинаковые состояния RenderEncoder в те-же значения
То-же самое с текстурами и буферами - не дублируем их установку
• Правильно ставим Load/Store/DontCare
• Не очищаем без надобности
• Не подключаем лишние текстуры (особенно Depth/Stencil) когда они реально не требуются
• Правильно выбираем тип памяти
• Не создаем объекты в цикле рисования
• Изучаем Event Graph в Xcode
• (Pro tip) setPurgeableState:MTLPurgeableStateEmpty перед удалением текстур/буфера
• (Pro tip) А так ли вам нужен ARC? Используйте ручное управление памятью
36. Особенности Metal Rendering Pipeline
Помогаем драйверу улучшить производительность
• Правильно выставить флажки доступа для текстур - они могут
иметь огромные последствия для мобильных устройств например
MTLTextureUsageShaderWrite
• (Pro tip) OBJC объекты можно включать в C++ хедеры для простой
интеграции через макросы - например так:
#ifdef __OBJC__
# include <Metal/Metal.h>.
# define OBJC_I(type) type*
# define OBJC_ID(type) id<type>
#else
# define OBJC_I(type) void*.
# define OBJC_ID(type) void*.
#endif
37. Особенности Metal Rendering Pipeline
Завершающие советы
• Постарайтесь не использовать MTKView и напишите свой класс инкапсулирующий
CAMetalDrawable
• VSync можно отключать через CAMetalDrawable
• При создании окон учитывайте Retina scale
• При создании полноэкранного окна выставляйте правильные стили это позволит
вам рендерить напрямую исключая композитор
• Не бойтесь вызывать CGDisplaySetDisplayMode при режиме ‘true fullscreen’
• SDL не панацея - там много устаревшего кода - на практике все проще написать
самому
• Изучите вопросы цветокоррекции и их особенности на macOS/iOS
38. Советы и рекомендации
• Начните с простого - не экономьте на памяти - создавайте состояния
динамически если это необходимо на начальных этапах. Не используйте
DontCare состояние.
• Ограничьте себя Metal 1.2 уровнем на начальном этапе
• Изучите примеры Apple и по желанию примеры The Forge
• Переключайте состояния только когда нужно - (чем позже тем лучше)
• Старайтесь уменьшить количество encoder-ов
• Старайтесь держать количество CommandBuffer оптимальным либо
попробуйте работать только с одним основным буфером
• Помните про триплицирование данных
39. Советы и рекомендации
• Оцените возможность нехватки памяти в буфере для uniform - корректно обработайте
такую ситуацию
• Делайте шейдеры понятными себе и компилятору - старайтесь не использовать Uber
шейдеры - Metal предлагает собственное решение этой проблемы - function constants -
исследуйте этот вопрос и делайте как вам удобнее
• Всегда компилируйте шейдеры заранее
• Библиотека в 6000 шейдеров это нормально, а вот 6000 одновременно загруженных
шейдеров - нет
• Сразу отбрасывайте те объекты и/или дескрипторы которые не должны кэшироваться.
Особенно это важно для объектов MTLFunction
• Четко понимайте на что расходуется память - для iGPU это критически важно
• Не пренебрегайте пониманием - что вас ждет на мобильных устройствах с текущим
кодом
40. Советы и рекомендации
• Изучите best practices в документации Apple - всегда выделяйте drawable как
можно позднее и рисуйте в него как в RT на последнем шаге избегая лишнего blit
прохода.
• Всегда делайте прогоны с включенным валидатором
• Всегда делайте прогоны без валидатора
• Всегда делайте чистые прогоны без валидатора и capture
• Изучите Instruments/Allocations и оцените цифры затрат на Metal API объекты
которые вы кэшируете. Обязательно проверяйте проект на утечки.
• Обращайте внимание на проблемы указанные в Event Graph .. и не бойтесь тех
проблем которых в реальности нет
• Обратитесь к ядру - изучите инструменты vmmap и IOAccelMemory для полного
понимания распределения памяти
41. Советы и рекомендации
• Располагайте буфера и текстуры в правильной памяти с правильным доступом - это
ключ к оптимизациям со стороны драйвера
• Всегда старайтесь использовать сжатие текстур если это возможно
• Помните - Metal не является single-threaded API вы можете кодировать из нескольких
потоков одновременно
• Освойте capture определенных кусков CommandBuffer - это просто и поможет вам
сэкономить кучу времени
• Избегайте апдейтить большие >=4K текстуры каждый кадр - это точно повредит
производительности на eGPU
• Потратьте время на изучение переведения алгоритмов пост-процессинга на compute
• Если вы нашли проблему выделите ее в пример и сообщите об этом в Apple приложив
sysdiagnose
42. Советы и рекомендации
• Изучайте performance метрики в инструментах или frame capture и обращайте
внимание на то чем в действительности занят шейдер
• Изучите подходы с работой в FP16 – если его использование возможно в ваших
алгоритмах
• Если есть критическая необходимость уменьшить потребление памяти и/или увеличить
производительность:
Heaps
Aliasing
ICB
Argument Buffers
….
58. Tracked and untracked resources
Tracked - Metal сам построит синхронизацию для ресурса
Untracked - необходимо в ручную синхронизировать (barriers +
MTLFence). Больше гибкости и производительность = больше
ответственность.
Все что в heap - untacked если не выставлен hazardTrackingMode в
MTLHazardTrackingModeTracked (удобен для отладки если не ясно
в чем проблема)
59. IndirectCommandBuffer (ICB)
В Metal нет аналога glMultiDraw*()
Позволяет создавать command buffer
на GPU с помощью compute шейдера.
Draw calls и изменение render pipeline
state (например объединить opaque и
alpha)
render_command cmd(cmdBuffer, commandId);
if (args.indexCount) {
cmd.set_render_pipeline_state(pipelineState);
cmd.set_vertex_buffer(vertexPosition, UNIT_VBPASS_POSITION);
cmd.set_fragment_buffer(texturesArgBuffer,UNIT_VBPASS_TEXTURES);
… // pass more buffers
cmd.draw_indexed_primitives(
primitive_type::triangle,
indexCount,
startIndex,
instanceCount,
vertexOffset,
startInstance);
} else {
cmd.reset()
}