How to create Custom Preprocessor?

Hi there,

I’m developing a monitoring plugin for NVIDIA and AMD GPUs. I’m using third party plugins to get these values. And I created a plugin.

NVIDIA → NVML → nvml.lib
AMD → ADL/ADLX → PerfAllMetrics.lib

However, I need to create custom preprocessor so that these libraries are only load on the correct system.

if ((Target.IsInPlatformGroup(UnrealPlatformGroup.Windows)))
			{
			
			// Add path to plugin
			PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Source", "ThirdParty", "ADL", "include"));
			PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Source", "ThirdParty", "ADL", "lib"));
			PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Source", "ThirdParty", "ADL", "ADLXHelper", "Windows", "Cpp"));
			PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Source", "ThirdParty", "NVML", "include"));
			PublicIncludePaths.Add(Path.Combine(PluginDirectory, "Source", "ThirdParty", "NVML", "lib"));

			// Uses DXGI to query GPU hardware
			// This is what will allow us to get GPU usage statistics at runtime (example: GPU VRAM values)
			PublicSystemLibraries.Add("DXGI.lib");
			
			// NVML Library is used to get NVIDIA GPU functions
			PublicAdditionalLibraries.Add("$(PluginDir)/Source/ThirdParty/NVML/lib/nvml.lib");
			
			// ADL Library is used to get AMD GPU functions
			PublicAdditionalLibraries.Add("$(PluginDir)/Source/ThirdParty/ADL/lib/PerfAllMetrics.lib");
			}

This works on the NVIDIA system fine.
However, I tested to open my project build on AMD system. And I get this following error.

image

Even project is not launch on AMD system (Compiling is done successfuly).

Thanks in advance!

You can get the gpu information from FPlatformMisc

FString GPUInfo = FPlatformMisc::GetPrimaryGPUBrand();	

Filter out if nvidia or amd is in string.

Could you get this information in the plugin StartupModule and then use it to drive further parameters or is this too far down the line of executed commands?

1 Like

If you want one plugin to be distributed to systems of both kinds, you cannot link directly against either DLL. That means, your plugin shouldn’t mention the DLLs (or their link libraries) at all in the build scripts.

Instead, your plugin needs to use manual dynamic library loading.
You can use LoadLibrary() to load the appropriate library for the platform you’re on. You then use GetProcAddress() to get the function pointers to call.

You can also get an error from that call that you can return as appropriate to the user, meaning your plugin can still “load” into the game, but it will return some “no support library present” error at runtime.

Using Run-Time Dynamic Linking - Win32 apps | Microsoft Learn

1 Like

Hi @3dRaven,

Thanks for the info! The first thing that came to my mind was filtering by string (like yours), but then I realized the existence of this code. Following code is the better way. And I already did this in my BP Library (C++).

This is perfectly work fine in the runtime but I’m not sure if this works in the Build.cs, because it is not runtime.

bool UDryreLUIEssentialsBPLibrary::IsNVIDIAGraphicsCard()
{
	const bool gpuVendorWindows = FWindowsPlatformMisc::GetGPUDriverInfo(FWindowsPlatformMisc::GetPrimaryGPUBrand()).IsNVIDIA();
	const bool gpuVendorGeneric = FGenericPlatformMisc::GetGPUDriverInfo(FGenericPlatformMisc::GetPrimaryGPUBrand()).IsNVIDIA();

	if(IsWindowsPlatform())
	{
		if(gpuVendorWindows)
		{
		return true;
		}
		return false;
	}
	else
	{
		if(gpuVendorGeneric)
		{
			return true;
		}
		return false;
	}
	return false;
}

bool UDryreLUIEssentialsBPLibrary::IsINTELGraphicsCard()
{
	const bool gpuVendorWindows = FWindowsPlatformMisc::GetGPUDriverInfo(FWindowsPlatformMisc::GetPrimaryGPUBrand()).IsIntel();
	const bool gpuVendorGeneric = FGenericPlatformMisc::GetGPUDriverInfo(FGenericPlatformMisc::GetPrimaryGPUBrand()).IsIntel();
	
	if(IsWindowsPlatform())
	{
		if(gpuVendorWindows)
		{
			return true;
		}
		return false;
	}
	else
	{
		if(gpuVendorGeneric)
		{
			return true;
		}
		return false;
	}
	return false;
}

bool UDryreLUIEssentialsBPLibrary::IsAMDGraphicsCard()
{
	const bool gpuVendorWindows = FWindowsPlatformMisc::GetGPUDriverInfo(FWindowsPlatformMisc::GetPrimaryGPUBrand()).IsAMD();
	const bool gpuVendorGeneric = FGenericPlatformMisc::GetGPUDriverInfo(FGenericPlatformMisc::GetPrimaryGPUBrand()).IsAMD();
	
	if(IsWindowsPlatform())
	{
		if(gpuVendorWindows)
		{
			return true;
		}
		return false;
	}
	else
	{
		if(gpuVendorGeneric)
		{
			return true;
		}
		return false;
	}
	return false;
}

I tried to create custom preprocessor on header file and try to use it on cpp file.

Header:

/ Include NVIDIA-specific headers
#ifdef  NVIDIA_GPU
#include "NVMLManager.h"
// Include AMD-specific headers
#elif defined(AMD_GPU)
#include "ADLManager.h"
#endif

NVMLManager.h and ADLManager.h are my custom function library derivered from the external NVIDIA and AMD libraries. And these are exposed to the blueprint in my other bp library h/cpp.

Here’s an example use of this custom preprocessor in CPP:

int UDryreLUIEssentialsBPLibrary::GetGPU_Usage_NVML(int Index)
{
#ifdef NVIDIA_GPU
	if(IsNVIDIAGraphicsCard())
	return nvGPUUsageNVML(Index);
#endif
	
	// Default case or error handling
	return -1;
}

You know what? I successfuly compile this code.
However, all values return -1! This means this preprocessor is not working.
Because it was working fine without preprocessor.

Now, I’m going to delete this preprocessor but if I want to use this both library on the same plugin but only load it when the compatible GPU driver is available on the system? Is there any other way for this? I think it should be. Because I know AAA games out there uses similar plugin to do this and they are publishing these games on every platform.

Follow the instructions in the link by jwatte.
I have successfully dynamically loaded and unloaded dll’s in the past (for a plotter device).
You can then use all of the methods needed from your dynamically linked library.

If you can just generate dll files in place of libs so that they can be changed at runtime. (should be easy if you have their source).

Thank you both. I solved the problem in a different way, but it took me days. I struggled to figure this out without sleeping, I wish there were more resources for plugins.

Solution:

First of all, I created a DLL from scratch in a Visual Studio project to learn how Third Party DLLs work. And I learned to link this DLL I created in a new Visual Studio project.
Tutorial: Walkthrough: Create and use your own Dynamic Link Library (C++) | Microsoft Learn

As for Unreal Engine;
I added a Build.cs to the source file section of the DLL files. I defined it as external module. Then I added the name “YourExternalModule” to PrivateDependencyModuleNames.

image

I used PublicDelayLoadDLLs and RuntimeDependencies. These were necessary both to load the DLL into the engine and to copy the DLL to the correct path after it was created.

// Add any include paths for the plugin
            PublicIncludePaths.Add(Path.Combine(ModuleDirectory));

            // Add any import libraries or static libraries
            PublicAdditionalLibraries.Add(Path.Combine(ModuleDirectory, "x64", "Release", "NVMLManagerLibrary.lib"));

            // Delay-load the DLL, so we can load it from the right place first
            PublicDelayLoadDLLs.Add("NVMLManagerLibrary.dll");

            string sourcePath = "$(ProjectDir)/Plugins/DryreLUIEssentialsPlugin/Source/ThirdParty/NVML/NVMLManagerLibrary/x64/Release/NVMLManagerLibrary.dll";
            string destinationPath = "$(ProjectDir)/Binaries/Win64/NVMLManagerLibrary.dll";
            string buildPath = "$(BinaryOutputDir)/NVMLManagerLibrary.dll";

Important: When building the game, make sure that the DLL files are copied to the build folder! Otherwise, even though it seems to work in the DLL engine, it will not work in the game build and you will have to copy the DLL files manually. It’s not right to copy manually, so you definitely need to use “RuntimeDependencies”.

When loading DLLs with DelayLoadDLLs, both the engine and the game build path must be common/same. Make sure you pay attention to this.

Finally, I defined a LibraryHandle (void) in YourPluginName.h private to load the appropriate DLL file according to which RHI is used.

private:
	/** Handle to the test dll we will load */
	void*	LibraryHandle;
};

StartupModule() is auto-loads the DLL for me. Here’s my code:

#include "Modules/ModuleManager.h"
#include "Misc/Paths.h"

FString myprojectDir = FPaths::ProjectDir();
	
	// DEVELOPER NOTE: Make sure to LibraryHande DLLs are in the Binaries folder!
	
	if(IsRHIDeviceNVIDIA())
	{
		// Add on the relative location of the third party dll and load it
		FString LibraryPathNVML;
#if PLATFORM_WINDOWS
		LibraryPathNVML = FPaths::Combine(*myprojectDir, TEXT("/Binaries/Win64/NVMLManagerLibrary.dll"));
#endif // PLATFORM_WINDOWS

		LibraryHandle = !LibraryPathNVML.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPathNVML) : nullptr;
	}
	else if(IsRHIDeviceAMD())
	{
		FString LibraryPathADL;
#if PLATFORM_WINDOWS
		LibraryPathADL = FPaths::Combine(*myprojectDir, TEXT("/Binaries/Win64/ADLManagerLibrary.dll"));
#endif // PLATFORM_WINDOWS
		LibraryHandle = !LibraryPathADL.IsEmpty() ? FPlatformProcess::GetDllHandle(*LibraryPathADL) : nullptr;
	}

	if (LibraryHandle)
	{
		if(IsRHIDeviceNVIDIA())
		{
			nvGPUInitializeNVML();
		}
		else if(IsRHIDeviceAMD())
		{
			adlInitializeADL();
		}
		else if(IsRHIDeviceIntel())
		{
			// Initialize Intel
		}
		else if(IsRHIDeviceApple())
		{
			// Initialize Apple
		}
		else
		{
			// Initialize Other Brand
		}
	}
	else
	{
		FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("DryreLUIEssentialsError", "Failed to load Third Party library"));
	}

Hope this helps!

1 Like