I have an OpenGL library written in c++ that is used from a C# application using C++/CLI adapters. My problem is that if the application is used on laptops with Nvidia Optim
I tried both options from the swine, but neither worked by themselves. I found I needed to attempt to call the imported function.
using System.Runtime.InteropServices;
class OptimusEnabler
{
[DllImport("nvapi.dll")]
public static extern int NvAPI_Initialize();
};
then in my app startup:
try
{
///Ignore any System.EntryPointNotFoundException
///or System.DllNotFoundException exceptions here
OptimusEnabler.NvAPI_Initialize();
}
catch
{ }
On an nVidia Optimus system, I am getting a System.EntryPointNotFoundException
, but it still works to make the application use the nVidia hardware. Tested on a system with an ATI card, I got a System.DllNotFoundException
. Either way, attempting to call this and ignoring any exception here seems to work fine.
If your software fails on Intel, then you won't be able to run it on 50% of the laptops. So I'd suggest fixing this instead.
Than being said, you can perfectly create profiles by code. Just use NvAPI. This code does exactly this, but beware, you probably shouldn't mess with the global profile and create your own instead :
NvAPI_Status status;
// (0) Initialize NVAPI. This must be done first of all
status = NvAPI_Initialize();
if (status != NVAPI_OK)
PrintError(status, __LINE__);
// (1) Create the session handle to access driver settings
NvDRSSessionHandle hSession = 0;
status = NvAPI_DRS_CreateSession(&hSession);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
// (2) load all the system settings into the session
status = NvAPI_DRS_LoadSettings(hSession);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
// (3) Obtain the Base profile. Any setting needs to be inside
// a profile, putting a setting on the Base Profile enforces it
// for all the processes on the system
NvDRSProfileHandle hProfile = 0;
status = NvAPI_DRS_GetBaseProfile(hSession, &hProfile);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
NVDRS_SETTING drsSetting1 = {0};
drsSetting1.version = NVDRS_SETTING_VER;
drsSetting1.settingId = SHIM_MCCOMPAT_ID;
drsSetting1.settingType = NVDRS_DWORD_TYPE;
NVDRS_SETTING drsSetting2 = {0};
drsSetting2.version = NVDRS_SETTING_VER;
drsSetting2.settingId = SHIM_RENDERING_MODE_ID;
drsSetting2.settingType = NVDRS_DWORD_TYPE;
NVDRS_SETTING drsSetting3 = {0};
drsSetting3.version = NVDRS_SETTING_VER;
drsSetting3.settingId = SHIM_RENDERING_OPTIONS_ID;
drsSetting3.settingType = NVDRS_DWORD_TYPE;
if( ForceIntegrated ){
drsSetting1.u32CurrentValue = SHIM_MCCOMPAT_INTEGRATED;
drsSetting2.u32CurrentValue = SHIM_RENDERING_MODE_INTEGRATED;
drsSetting3.u32CurrentValue = SHIM_RENDERING_OPTIONS_DEFAULT_RENDERING_MODE | SHIM_RENDERING_OPTIONS_IGPU_TRANSCODING;
}else{
drsSetting1.u32CurrentValue = SHIM_MCCOMPAT_ENABLE;
drsSetting2.u32CurrentValue = SHIM_RENDERING_MODE_ENABLE;
drsSetting3.u32CurrentValue = SHIM_RENDERING_OPTIONS_DEFAULT_RENDERING_MODE;
}
status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting1);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting2);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
status = NvAPI_DRS_SetSetting(hSession, hProfile, &drsSetting3);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
// (5) Now we apply (or save) our changes to the system
status = NvAPI_DRS_SaveSettings(hSession);
if (status != NVAPI_OK)
PrintError(status, __LINE__);
// (6) We clean up. This is analogous to doing a free()
NvAPI_DRS_DestroySession(hSession);
hSession = 0;
At startup, test if your profile exists. If not, create it (and you'll probably have to restart yourself too). NvAPI is a static lib, and will gracefully return an error code on non-NVIDIA hardware, so you can ship with it safely.
EDIT : Looks like there's an easier way. From GLFW 3 source code :
// Applications exporting this symbol with this value will be automatically
// directed to the high-performance GPU on nVidia Optimus systems
//
GLFWAPI DWORD NvOptimusEnablement = 0x00000001;
My solution working both on NVidia and AMD, without DllExport (without NuGet package UnmanagedExports), based on exported functions NvOptimusEnablement and AmdPowerXpressRequestHighPerformance:
On VS2019 the aforementioned UnmanagedExports package doesn't seem to work properly. I couldn't find another working package for .NET framework that provides DllExport capability. That's why I developed my own solution using direct MSIL code with great help from Writing IL code on Visual Studio and IL Support.
Steps to follow:
To .csproj file (.csproj for exe, not for any dll) add the following section which enables direct usage of MSIL code (you may put it generally anywhere, i.e. after <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />). For .net frameworks newer than 4.6.2 you may need to update paths to ildasm.exe:
<PropertyGroup>
<CoreCompileDependsOn>
HideILFromCoreCompile;
$(CoreCompileDependsOn);
</CoreCompileDependsOn>
<CompileDependsOn>
HideILFromCompile;
$(CompileDependsOn);
InitializeIL;
CoreDecompile;
CoreCompileIL;
</CompileDependsOn>
</PropertyGroup>
<Target Name="HideILFromCoreCompile">
<ItemGroup>
<Compile Remove="@(Compile)" Condition="'%(Extension)'=='.il'" />
</ItemGroup>
</Target>
<Target Name="HideILFromCompile">
<ItemGroup>
<IL Include="@(Compile)" Condition="'%(Extension)'=='.il'" />
<Compile Remove="@(Compile)" Condition="'%(Extension)'=='.il'" />
</ItemGroup>
</Target>
<Target Name="InitializeIL">
<PropertyGroup>
<ILFile>@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).il', ' ')</ILFile>
<ILResourceFile>@(IntermediateAssembly->'%(RootDir)%(Directory)%(Filename).res', ' ')</ILResourceFile>
</PropertyGroup>
</Target>
<Target Name="CoreDecompile" Inputs="@(IntermediateAssembly)" Outputs="$(ILFile)" Condition=" Exists ( @(IntermediateAssembly) ) ">
<GetFrameworkSdkPath>
<Output TaskParameter="Path" PropertyName="FrameworkSdkPath" />
</GetFrameworkSdkPath>
<PropertyGroup>
<ILDasm>"$(FrameworkSdkPath)bin\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)bin\NETFX 4.0 Tools\ildasm.exe' ) ">
<ILDasm>"$(FrameworkSdkPath)bin\NETFX 4.0 Tools\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)bin\NETFX 4.5.1 Tools\ildasm.exe' ) ">
<ILDasm>"$(FrameworkSdkPath)bin\NETFX 4.5.1 Tools\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)bin\NETFX 4.6 Tools\ildasm.exe' ) ">
<ILDasm>"$(FrameworkSdkPath)bin\NETFX 4.6 Tools\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)bin\NETFX 4.6.1 Tools\ildasm.exe' ) ">
<ILDasm>"$(FrameworkSdkPath)bin\NETFX 4.6.1 Tools\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)bin\NETFX 4.6.2 Tools\ildasm.exe' ) ">
<ILDasm>"$(FrameworkSdkPath)bin\NETFX 4.6.2 Tools\ildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm>
</PropertyGroup>
<Exec Command="$(ILDasm)" />
<ItemGroup>
<FileWrites Include="$(ILFile)" />
<FileWrites Include="$(ILResourceFile)" />
</ItemGroup>
<PropertyGroup>
<ILSource>$([System.IO.File]::ReadAllText($(ILFile)))</ILSource>
<Replacement>// method ${method} forwardref removed for IL import</Replacement>
<Pattern>\.method [^{}]+ cil managed forwardref[^}]+} // end of method (?<method>[^ \r\t\n]+)</Pattern>
<ILSource>$([System.Text.RegularExpressions.Regex]::Replace($(ILSource), $(Pattern), $(Replacement)))</ILSource>
<Pattern>\.method [^{}]+ cil managed[^\a]+"extern was not given a DllImport attribute"[^}]+} // end of method (?<method>[^ \r\t\n]+)</Pattern>
<ILSource>$([System.Text.RegularExpressions.Regex]::Replace($(ILSource), $(Pattern), $(Replacement)))</ILSource>
</PropertyGroup>
<WriteLinesToFile File="$(ILFile)" Lines="$(ILSource)" Overwrite="true" />
<PropertyGroup>
<ILSource />
</PropertyGroup>
<Delete Files="@(IntermediateAssembly)" />
</Target>
<Target Name="CoreCompileIL" Inputs="@(IL)" Outputs="@(IntermediateAssembly)">
<GetFrameworkPath>
<Output TaskParameter="Path" PropertyName="FrameworkPath" />
</GetFrameworkPath>
<PropertyGroup>
<ILAsm>"$(FrameworkPath)\ilasm.exe" /nologo /quiet /output:@(IntermediateAssembly->'"%(FullPath)"', ' ')</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(FileAlignment)' != '' ">
<ILAsm>$(ILAsm) /alignment=$(FileAlignment)</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(BaseAddress)' != '' ">
<ILAsm>$(ILAsm) /base=$(BaseAddress)</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(OutputType)' == 'Library' ">
<ILAsm>$(ILAsm) /dll</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(DebugType)' == 'pdbonly' ">
<ILAsm>$(ILAsm) /pdb</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(DebugType)' == 'full' ">
<ILAsm>$(ILAsm) /debug</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(Optimize)' == 'true' ">
<ILAsm>$(ILAsm) /optimize</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'x64' ">
<ILAsm>$(ILAsm) /pe64 /x64</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(Platform)' == 'Itanium' ">
<ILAsm>$(ILAsm) /pe64 /itanium</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" '$(AssemblyOriginatorKeyFile)' != '' ">
<ILAsm>$(ILAsm) /key:"$(AssemblyOriginatorKeyFile)"</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(ILResourceFile)' ) ">
<ILAsm>$(ILAsm) /resource:"$(ILResourceFile)"</ILAsm>
</PropertyGroup>
<PropertyGroup Condition=" Exists ( '$(ILFile)' ) ">
<ILAsm>$(ILAsm) "$(ILFile)"</ILAsm>
</PropertyGroup>
<Exec Command="$(ILAsm) @(IL->'"%(FullPath)"', ' ')" />
<ItemGroup>
<FileWrites Include="@(IntermediateAssembly->'%(RootDir)%(Directory)DesignTimeResolveAssemblyReferencesInput.cache', ' ')" />
</ItemGroup>
<Touch Files="$(ILFile)" />
</Target>
Paste the code below to this file (instead of 'WindowsApplication1' you may enter your namespace):
.class public WindowsApplication1.ForceDedicatedGraphicCard
{
.method public static int32 NvOptimusEnablement() cil managed
{
.export [1]
ldc.i4.1
ret
}
.method public static uint32 AmdPowerXpressRequestHighPerformance() cil managed
{
.export [2]
ldc.i4.1
ret
}
}
Check if the functions are exported using dumpbin.exe
dumpbin.exe /exports your_project.exe
From the document it seems to be rather simple. You are given multiple options how to do that. Unfortunately, the exe needs to do that, not the dll. According to this tutorial, it might be possible to do something like:
class OptimusEnabler {
[DllExport("NvOptimusEnablement")]
public static int NvOptimusEnablement = 1;
};
This then needs to be included in your C++ library interface so that any C# application that uses it would be forced to export this. Alternately, you can try linking against nvapi.dll
:
class OptimusEnabler {
[DllImport("nvapi.dll")]
public static extern int NvAPI_Initialize();
};
According to the document, this should also be enough to recognize your application as NV-enabled. The imported function should not even be required to be called.
A working solution. Actually all those already mentioned, but it took me a time to understand how to get it work...
[System.Runtime.InteropServices.DllImport("nvapi64.dll", EntryPoint = "fake")]
static extern int LoadNvApi64();
[System.Runtime.InteropServices.DllImport("nvapi.dll", EntryPoint = "fake")]
static extern int LoadNvApi32();
private void InitializeDedicatedGraphics()
{
try
{
if (Environment.Is64BitProcess)
LoadNvApi64();
else
LoadNvApi32();
}
catch { } // will always fail since 'fake' entry point doesn't exists
}
Important - call InitializeDedicatedGraphics()
before any window is created