How use CUDA in Unreal Engine ? Pt1
Existential question: Why use CUDA in Unreal Engine?
Answer : Why not?
Introduction
Cuda allows the realization of High Performance Computing (GPGPU). Some parallelizable tasks can be done directly on CUDA. We could also think about using compute shaders for parallelized computations on Unreal but it’s not the subject of today (maybe a next article).
The purpose of this article is NOT to show how to optimize calculations on Unreal Engine, but to show how to use CUDA in Unreal. No optimization of calculations will be done. A simple Unreal vector addition function (FVector) will be implemented with CUDA and then used in Unreal.
Different things will be discussed:
- Building a shared library (dll) using CUDA functions
- Adding this library in Unreal
- Using this library in runtime
Development Environment
1. CUDA 11.7
2. Visual Studio 2022
Architecture
The architecture implemented in this story is the following. We will see in detail the logic of this architecture, and the choices I made.
Dll
In order to compile the custom library hosting CUDA code, I chose to use Cmake on Visual Studio (2022).
This library contains :
- kernel.cuh : the header file (defining which functions are exported in the dll)
- kernel.cu : the source file containing the implementation of the CUDA functions
App Unreal Engine
The Unreal Engine application is a simple application. This application contains all your code and logic (Content, Sources).
It also contains a plugin named “Cuda4Unreal” which has a main module in charge of importing the Cuda functions and linking the dll. This module also contains functions to expose the CUDA functions to the Blueprint.
To link the dll, and import the functions, the code could have been found directly in the project sources. However, it can be interesting to make this integration directly in a plugin to facilitate the reuse of functions on different projects.
What are the inputs and outputs of our CUDA function, and how will it be integrated into Unreal?
Here is a diagram showing how it works.
Let’s imagine a Blueprint, which contains a function that has two parameters “FVector a” and “FVector b”, we must first convert these types into CUDA compatible types (simple C++ arrays). Then we call the CUDA function with these two arrays, and it returns an output which is an array of size 3 (the size of an FVector). Then we “convert” this output array into an FVector to be able to use it in Unreal Engine (to display it for example).
Configuration of the development environment
Download Cuda : https://developer.nvidia.com/cuda-downloads
Download Visual Studio : https://visualstudio.microsoft.com
When installing visual studio, you have to install the necessary packages for the C++ compilation.
1 - Development for the universal windows platform
2 - C++ desktop development
2.1 - Cmake tools for windows
3 - Game development C++
3.1 - Unreal Engine installer
Create the shared library
To start, you can simply create a Cmake project using the Visual Studio configuration window (and give it a beautiful name ex: CudaExempleLib)
Now that you have your project, it should look like this! We will see in detail what was automatically generated by Visual Studio.
In the root of the project you have :
- CMakePreset.json : Global Configuration File
- CMakeLists.txt : Global Cmake File
- Folder “out”
- Folder “CudaExempleLib”
CMakePreset.json : File allowing to specify configuration options, and to share them with other users.
CMakeLists.txt : file contains a set of directives and instructions describing the project’s source files and targets (executable, library, or both)
Folder “out” : Contains the files that will be generated by the compilation (executable, library, temporary file…). No code is put here.
Folder “CudaExempleLib” : Contains the source files of our library !
We will not modify the CmakeLists.txt or the CMakePreset.json at the root of the project. We will only be interested in the files in the “CudaExempleLib” folder.
Create CUDA functions
Create CUH header file
First of all, we will create a specific folder for CUDA files that we will name “kernels”.
Then, we will add a cuh file which will define the functions to implement and export in our library.
__declspec(dllexport) : we specify that we want to export the function to the dll to access it later in Unreal.
Note : Here we specify the length of the vector with the parameter “size”, since we want to add an FVector which is always of size 3 (a.x, a.y, a.z), we could have put this argument in a #define.
Implement the add function
Now we need to implement the add function on GPU. To do this, we must first create our .cu file and create the function that will be executed on the GPU.
The kernel_add function, is not the function we defined in the header, it is just the function that will be used by the “add” function to execute the operation on GPU. We call it a “kernel”.
Now we can implement the function defined in the header.
The implementation of a CUDA function is not the subject of this article, but quickly the process is as follows:
- allocate the memory of the different parameters (input and output)
- copy the inputs in the GPU buffers
- invoke the kernel function
- copy the output to the host memory
- free the memory
We now have a CUDA function that adds two vectors but we now need to compile it as a dll. To do this we will modify the file CMakeLists.txt.
Create the dll
To create the dll, we will modify the CMakeLists.txt present in the “CudaExempleLib” folder.
First, we specify to the compiler to use the CUDA language. Then we add the library as a shared library, and we add the files kernel.cuh and kernel.cu. Then we specify that we want to enable CUDA compilation separately for our library.
If we compile the solution, and we go to the folder out/build/x64-debug/CudaProject we should find several files (/pbd, .exp ..). The files that interest us here are the CudaLib.dll and CudaLib.lib. These are the ones we will integrate into Unreal.
This first part being finished, it is now necessary to integrate this library to Unreal, this subject will be treated in another part :)