Экзистенциальный вопрос: зачем использовать 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, эта тема будет рассмотрена в другой части :)