Экзистенциальный вопрос: зачем использовать CUDA в Unreal Engine?
Ответ: Почему бы и нет?
Введение
Cuda позволяет реализовать высокопроизводительные вычисления (GPGPU). Некоторые распараллеливаемые задачи можно выполнять прямо в CUDA. Мы также могли бы подумать об использовании вычислительных шейдеров для параллельных вычислений в Unreal, но это не тема сегодняшнего дня (может быть, следующая статья).
Цель этой статьи НЕ показать, как оптимизировать вычисления на Unreal Engine, а показать, как использовать CUDA в Unreal. Никакой оптимизации расчетов не будет. Простая функция сложения векторов Unreal (FVector) будет реализована с помощью CUDA, а затем использована в Unreal.
Будут обсуждаться разные вещи:
- Создание общей библиотеки (dll) с использованием функций CUDA
- Добавление этой библиотеки в Unreal
- Использование этой библиотеки во время выполнения
Среда разработки
1. CUDA 11.7
2. Visual Studio 2022
Архитектура
Архитектура, реализованная в этой истории, следующая. Мы подробно рассмотрим логику этой архитектуры и выбор, который я сделал.
Dll
Чтобы скомпилировать пользовательскую библиотеку с кодом CUDA, я решил использовать Cmake в Visual Studio (2022).
Эта библиотека содержит:
- kernel.cuh: заголовочный файл (определяющий, какие функции экспортируются в dll)
- kernel.cu: исходный файл, содержащий реализацию функций CUDA
Приложение Unreal Engine
Приложение Unreal Engine — простое приложение. Это приложение содержит весь ваш код и логику (контент, источники).
Он также содержит плагин под названием «Cuda4Unreal», в котором есть основной модуль, отвечающий за импорт функций Cuda и связывание dll. Этот модуль также содержит функции для предоставления функций CUDA в Blueprint.
Чтобы связать dll и импортировать функции, код можно было найти непосредственно в исходниках проекта. Однако может быть интересно сделать эту интеграцию непосредственно в плагине, чтобы облегчить повторное использование функций в разных проектах.
Каковы входы и выходы нашей функции CUDA и как она будет интегрирована в Unreal?
Вот схема, показывающая, как это работает.
Давайте представим Blueprint, который содержит функцию с двумя параметрами «FVector a» и «FVector b», мы должны сначала преобразовать эти типы в типы, совместимые с CUDA (простые массивы C++). Затем мы вызываем функцию CUDA с этими двумя массивами, и она возвращает результат, который представляет собой массив размера 3 (размер FVector). Затем мы «конвертируем» этот выходной массив в FVector, чтобы иметь возможность использовать его в Unreal Engine (например, для отображения).
Настройка среды разработки
Скачать Cuda: https://developer.nvidia.com/cuda-downloads
Скачать Visual Studio: https://visualstudio.microsoft.com
При установке Visual Studio необходимо установить необходимые пакеты для компиляции C++.
1 — Разработка для универсальной платформы Windows
2 — Разработка рабочего стола C++
2.1 — Инструменты Cmake для Windows
3 - Разработка игр C++
3.1 - Установщик Unreal Engine
Создайте общую библиотеку
Для начала вы можете просто создать проект Cmake, используя окно конфигурации Visual Studio (и дать ему красивое имя, например: CudaExempleLib)
Теперь, когда у вас есть проект, он должен выглядеть так! Мы подробно рассмотрим, что было автоматически сгенерировано Visual Studio.
В корне проекта у вас есть:
- CMakePreset.json: глобальный файл конфигурации
- CMakeLists.txt: глобальный файл Cmake
- папка «out»
— папка «CudaExempleLib ”
CMakePreset.json : файл, позволяющий указать параметры конфигурации и поделиться ими с другими пользователями.
CMakeLists.txt: файл содержит набор директив и инструкций, описывающих исходные и целевые файлы проекта (исполняемый файл, библиотека или и то, и другое).
Папка «out»: содержит файлы, которые будут созданы при компиляции (исполняемый файл, библиотека, временный файл…). Код здесь не ставится.
Папка «CudaExempleLib»: содержит исходные файлы нашей библиотеки!
Мы не будем изменять CmakeLists.txt или CMakePreset.json в корне проекта. Нас будут интересовать только файлы в папке «CudaExempleLib».
Создание функций CUDA
Создать заголовочный файл CUH
Прежде всего, мы создадим специальную папку для файлов CUDA, которую назовем «ядра».
Затем мы добавим файл cuh, в котором будут определены функции для реализации и экспорта в нашу библиотеку.
__declspec(dllexport) :мы указываем, что хотим экспортировать функцию в dll, чтобы позже получить к ней доступ в Unreal.
Примечание. Здесь мы указываем длину вектора с помощью параметра «размер», поскольку мы хотим добавить FVector, который всегда имеет размер 3 (a.x, a.y, a.z), мы могли бы поместить этот аргумент в #define.
Реализовать функцию добавления
Теперь нам нужно реализовать функцию добавления на GPU. Для этого мы должны сначала создать наш файл .cu и создать функцию, которая будет выполняться на графическом процессоре.
Функция kernel_add — это не та функция, которую мы определили в заголовке, это просто функция, которая будет использоваться функцией «добавить» для выполнения операции на графическом процессоре. Мы называем это «ядром».
Теперь мы можем реализовать функцию, определенную в заголовке.
Реализация функции CUDA не является предметом этой статьи, но вкратце процесс выглядит следующим образом:
- выделить память для различных параметров (входных и выходных)
- скопировать входные данные в GPU буферы
— вызов функции ядра
— копирование вывода в память хоста
— освобождение памяти
Теперь у нас есть функция CUDA, которая добавляет два вектора, но теперь нам нужно скомпилировать ее как dll. Для этого изменим файл CMakeLists.txt.
Создайте DLL
Чтобы создать dll, мы изменим CMakeLists.txt, присутствующий в папке «CudaExempleLib».
Во-первых, мы указываем компилятору использовать язык CUDA. Затем мы добавляем библиотеку как разделяемую библиотеку и добавляем файлы kernel.cuh и kernel.cu. Затем мы указываем, что хотим включить компиляцию CUDA отдельно для нашей библиотеки.
Если мы скомпилируем решение и перейдем в папку out/build/x64-debug/CudaProject, то должны найти несколько файлов (/pbd, .exp ..). Нас интересуют файлы CudaLib.dll и CudaLib.lib. Это те, которые мы будем интегрировать в Unreal.
Эта первая часть закончена, теперь необходимо интегрировать эту библиотеку в Unreal, эта тема будет рассмотрена в другой части :)