I have a brand new laptop (Tuxedo with RTX 3080) and as Nvidia decided to deliver all their devs in containers I decided to give a try. the laptop comes with a preinstalled Ubuntu 20.04.1 version and Nvidia drivers already installed.
To run accelerated docker containers, you need to allow the containers to detect and use the GPU. Docker make use of something call “container runtime” to get access to physical resources in the host, however, GPUs are special devices and few extra considerations and configurations are required.
Install Docker in Ubuntu is easy, only have to run the script I show here:
After getting docker installed, is suggested to check the driver’s installation in your machine, for that the simplest way is to use the nvidia-smi command:
As you can see, I have a nvidia driver and CUDA already installed on my system, however the CUDA shown there are only the binaries, so I need to install the nvidia cuda toolkit which comes with compiler, headers, and all the juicy stuff.
To check if I have CUDA installed you can run:
Now, we must tell docker to run under the nvidia runtime that allows to get access to the GPU, and to do this, we have three choices, systemd, docker config files, or CLI (console).
I like to modify the docker deamon in /etc/docker/deamon.json
Now, we will try to run something as a test, but before we want a list of containers we can get (having installed NGC):
We will download the TensorFlow image:
and let check the container:
and run it:
But, my driver is already outdated, so I need to update it from the list of tags in NGC, and I make it works with the tag 20.12 and taking care to use nvidia-docker in the command line to avoid setting up the –gpu options.
Every time I want to attach something really cool to Unity, it comes packed as DLL from some C/C++ implementation, and some integration work is required to make it works. In other opportunities, performance reasons or security, push you to package your codes in a DLL. Make DLLs for Unity has been always a mystery for many people, and generally seen as a magician works, but, truth be said, is not that hard and I’m sure many others share the same spirit to get the best performance from their projects but don’t have the experience and/or confidence to do it.
Unity is not very DLL friendly (from a developer point of view), the first time you call a DLL from a script, Unity will load that DLL and will not release it until you close your application, forcing you to relaunch it if some change has been done in the DLL. If some exception is triggered inside your DLL, forget having a nice popup window that holds your hand to guide you through your mistakes, here is just the crash. Also, there is no much information around, and whatever you find useful, is not Unity-related mostly and is not easy to understand for inexperienced people. So, in summary, this is a very hostile macho-style environment for low-level developers.
So, the first thing you need to do is to be absolutely sure that your application is working well before building the DLL, because Unity doesn’t allow inspections in an easy way, and is quite exhausting trying to catch a bug.
The recipe for success here is:
1.- Create a new solution in Visual Studio C++ (2019 in my case) with one project, that project will run the core code, testing the functionality of your implementation and defining the interface’s objects. Take some working application and modify it in such a way that you be able to isolate the core components in different files. Here, is when the developer experience bright, try to catch all the exceptions that could crash your program, use RAII much as possible, same for unique_ptr and shared_ptr as garbage collectors. If you make use of multithreading, be careful to not left some detached orphans-threads around, and please don’t abuse the memory pool because is big, I’ll be mad at you, and Unity too.
2.- Add another solution that compiles a DLL, and add to the source directory list the folder of the first application, and calls the functions from there. DO NOT COPY THE FILES, there is no need, that’s why exists the include directive.
Also, I suggest to configure the project to automatically copy the dll after compilation, this could be done in Project Properties -> Build Events -> Post-Build Events.
3- Make another project that loads the DLL and runs the codes from there, this is very important, since it will emulate the calls done by Unity. It requires typing a few code lines but is the best tool you will have to debug this.
Other thing you can add is a .NET project calling the DLL, but I guess this is enough for now.
I’ll not get into too much detail about how to make DLLs, there are a bunch of others sites that talk about that, better than me, and I’ll share an example project on GitHub, so you can reference it if you have some doubts about the implementation details. I’ll focus in the details required by Unity to pass and get information on between them.
I’ll create a simple project that will expose some functions, it will be called UnityDLLTemplate. And then add utils.h and utils.cpp to that project.
But before start coding, I’ll explain few importand details.
C# is managed code, which means that C# has control over all the objects in the program and takes care of the memory management, disposing of the memory at will, and optimizing it as possible. This creates a safe environment for developers at cost of overhead at runtime. Is not a bad deal while you be working with non-real-time applications and/or you don’t need the best performance, but if that is the case, you will see the need to jump to C/C++. That’s the reason why many libraries that deal with devices and hardware, in general, come packed in DLLs, they need good performance.
While C# is a managed code, very protected, and with a bunch of rules that ensure stability, C/C++ is the wild wild west and is called unmanaged code. C/C++ doesn’t take control of almost anything as in C#, but in change brings you the whole power of the computer. It trusts in the skills of the developer and assumes that the unmanaged code is working perfectly, without any memory leak or embarrassing mistakes.
In general, calling functions from a DLL in C# is fairly simple, you only need to declare it in a C# class in a certain way (almost like any other function). The catch, is that the types are NOT the same in both languages, i.e., boolean type in C/C++ are 8bits, and in C# are 32bits, ups !!!. Another important detail is that work with pointers in C# is never encouraged so it could be tricky, and in fact, no needed most of the time.
To have a reference in how different the types in both languages could be, we can refer to the official documentation Marshaling Data with Platform Invoke:
As you can see, some types are very different. The first and probably the most common mistake is trying to use bool as a return from some test functions, please don’t do that, is a newb trap made by Microsoft. The second mistake is when you pass an array of characters, the size of the array will depend if you are using Unicode/Ansi/ASCII characters and in some cases doesn’t keep the same lenght on between internationalization’s configurations (is a mess if you are not aware of that). And the third one is when you pass structures, in C# are not contiguous in memory, in other words, they are not sequential. Using memory that is not sequential in C/C++ guarantees an exception and depending on where are you aiming, a potential catastrophic failure (blue screen type). There are others details, but these ones are the most common, and enough for our purposes.
To handle this problem between the managed and unmanaged code, C# has the concept of Marshaling, which is defined as “the process of transforming types when they need to cross between managed and native code”, which looks like is what we need to do the job well done.
To complete the picture, there is another component called pinvoke, which is defined as “a technology that allows you to access structs, callbacks, and functions in unmanaged libraries from your managed code”.
Going deep on pinvoke and marshaling is out of the scope of this document since is a well-developed topic and there is enough information around, however, I’ll leave few links for those who wanted to have a better understanding of what are they doing. I encourage you to read these links, will take only a few minutes and are very helpful.
Ok, so back to coding. As a template, i”ll put common cases as structures, characters, arrays, etc… just to show how they work. I’ll comment on the source code but I’ll not put that here, please reference the Github project.
Notice that I use fixed types in the return functions and in the function’s parameters. This is only for personal choice, is easier for me to read code this way since you can put the same numeric types in C# saving few brain-cycles while reading the codes.
#include "utils.h"
#include <numeric>
#include <algorithm>
// Bool will be pased as uint8_t
uint8_t RBoolPvoid(void)
{
return 0;
}
int RIntPVoid(void)
{
return 0;
}
float RFloatPVoid(void)
{
return 0.0f;
}
void RVoidPArray(uint8_t* array, size_t array_size)
{
return (uint32_t)std::accumulate(array, array + array_size, 0.0f);
}
// this function will reverse the array string and return the number of returned characters
float RFloatPString(uint8_t string_array, size_t array_size, uint8_t out_string_array, size_t out_array_size)
{
return 0.0f;
}
int RIntPFloat(float *float_array, size_t array_size)
{
return (int)std::accumulate(float_array, float_array + array_size, 0.0f);
}
ST_TEST_STRUCTURE read_structure(ST_TEST_STRUCTURE* st_ptr, uint32_t str_size)
{
return 0x00;
}
Now that we have our functions, let’s move to the DLL. As I mention, I’ll not show how to make a DLL, so I’ll show the code, is just one file and we referenced the source code from the other project in our VisualStudio include directory list.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
public class DLLTemplate
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)] // --> this is to keep the structure in contiguous memory, very C/C++ style
public struct ST_TEST_STRUCTURE {
[MarshalAs(UnmanagedType.LPUTF8Str)] public string text1;
// ByValTStr pass the string in-place in the structure
[MarshalAs(UnmanagedType.ByValTStr, SizeConst =20)] public string fixed_size_char_array;
[MarshalAs(UnmanagedType.LPArray, SizeConst = 20)] public float[] array1;
public float a;
public UInt32 b;
public UInt32 c;
// Boolean values default to marshaling as a native 4-byte Win32
[MarshalAs(UnmanagedType.Bool)] public bool marshal_as_four_bytes_bool;
// Boolean values marshaling as a 1 byte, C tyle
[MarshalAs(UnmanagedType.U1)] public bool marshal_as_one_byte_bool;
}
[DllImport("DLLTemplate", EntryPoint="RBoolPvoid")]
public static extern byte RBoolPvoid();
[DllImport("DLLTemplate", EntryPoint="RIntPVoid")]
public static extern int RIntPVoid();
[DllImport("DLLTemplate", EntryPoint="RFloatPVoid")]
public static extern float RFloatPVoid();
[DllImport("DLLTemplate", EntryPoint="RVoidPArray")]
public static extern void RVoidPArray(Byte[] array, UInt32 array_size);
[DllImport("DLLTemplate", EntryPoint="RFloatPString")]
public static extern float RFloatPString([MarshalAs(UnmanagedType.LPStr)] [In] string string_array,
[In] UInt32 array_size,
[MarshalAs(UnmanagedType.LPStr)] [Out] string out_string_array,
[Out] UInt32 out_array_size);
[DllImport("DLLTemplate", EntryPoint="RIntPFloat", CharSet = CharSet.Ansi)]
public static extern Int32 RIntPFloat([MarshalAs(UnmanagedType.LPArray),In,Out]float float_array[], UInt32 array_size);
[DllImport("DLLTemplate", EntryPoint="ReadStructure")]
public static extern ST_TEST_STRUCTURE ReadStructure( [Out] ST_TEST_STRUCTURE new_structure);
}