Forcing hardware accelerated rendering
Asked Answered
P

6

15

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 Optimus technology the application will not use the hardware acceleration and fail.

I have tried to use the info found in Nvidias document http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf about linking libs to my C++-dll and exporting NvOptimusEnablement from my OpenGL-library but that fails. I guess I have to do something with the .exe not with the .dlls linked to the .exe

For us it is not a good option to use profiles since we need to ensure that the nvidia hardware is used.

Is there some way a C# application can force Optimus to use the Nvidia chipset instead of the integrated Intel chipset?

Pournaras answered 24/6, 2013 at 7:49 Comment(4)
You may ask also here: http://gamedev.stackexchange.com/Petaliferous
go to nvidia panel and switch to dedicated GPUHovis
Using the nvidia panel is not way I want to do here. I would like to do it in code.Pournaras
@JohanR: We had a question like that recently and – I'm sorry to break this to you – there is no standard API to select the GPU to be used, yet. You either have to brag at NVidia and Microsoft for the introduction of such an API, or you directly reproduce the effects the driver control panel has on the system, i.e. throwing the right registry switched (you'll have to reverse engineer those).Jampacked
C
9

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

Chau answered 14/10, 2017 at 20:37 Comment(4)
thank you, It worked for me (on lenovo z510, windows 10)Histopathology
Thanks for this solution, it works fine on NVidia GPUs. Do you know how to do the equivalent for AMD GPUs?Benniebenning
@WaltD, unfortunately noChau
With .NET Core 3 you can now just call NativeLibrary.Load("nvapi64.dll") or equivalent for 32 bit. Of course you'll still need a try catch for errors but shorter than defining fake P/Invoke stubs to load the libraries.Rey
L
4

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.

Luellaluelle answered 31/7, 2015 at 12:15 Comment(1)
I could not get this working on my machine. Th problem was that my app runs as a 64 bit process (I have disabled "prefer 32 bit" in the application build settings), and thus was not able to find and load the nvapi.dll. My solution was creating 2 separate classes, both wrapping the NvAPI_Initialize method but from different dlls (nvapi.dll and nvapi64.dll), and choose at runtime which one to attempt to use based on the Environment.Is64BitProcess property. Seems to work fine now.Samson
E
3

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;
Eliseoelish answered 24/6, 2013 at 13:46 Comment(1)
We have no problem specifying that our application will not run on certain hardware, in this case Intel graphics. It seems like we will have to go with the "profile settings"-way and use the NvAPI code to make a profile, either directly in the application or as a standalone application that will be run from the installer when the application is installed.Pournaras
T
3

NvPatch x EditBinPE x EditPE

I was faced with the need to use NVIDIA and AMD GPUs. In my internet adventure I found nvpatch, an application that adds the necessary headers to use the dedicated GPUs. However, this application only works on x64, so I ended up creating EditBinPE and EditPE, with which it is possible to edit the header of PE files, adding the necessary headers to use AmdPowerXpressRequestHighPerformance and NvOptimusEnablement. To enable AmdPowerXpressRequestHighPerformance and NvOptimusEnablement you must use one of three applications, nvpatch, EditBinPE or EditPE. These three applications serve to edit the header of the PE32+/PE64 file (only editbinpe and editpe does this type of file) and PE32

Using EditBinPE

EditBinPE command: editbinpe --enable-gpu filename.exe

command only amd: editbinpe --enable filename.exe AmdPowerXpressRequestHighPerformance

command only nvidia: editbinpe --enable filename.exe NvOptimusEnablement

Using EditPE/GPUPE

EditPE command: gpupe infile.exe outfile.exe

command only amd: editpe infile.exe outfile.exe "AmdPowerXpressRequestHighPerformance,1"

command only nvidia: editpe infile.exe outfile.exe "NvOptimusEnablement,1"

Using NvPatch

nvpatch command: nvpatch --enable filename.exe

NVIDIA

To just use NVIDIA give to just use this function here:

static void InitializeDedicatedGraphics()
{
     if (Environment.Is64BitProcess)
         NativeLibrary.Load("nvapi64.dll");
     else
         NativeLibrary.Load("nvapi.dll");
}
Towhead answered 1/4, 2023 at 1:40 Comment(0)
R
1

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:

  1. Choose x86 or x64 platform for your exe (you cannot do it with AnyCPU setting)
  2. 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 (?&lt;method&gt;[^ \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 (?&lt;method&gt;[^ \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->'&quot;%(FullPath)&quot;', ' ')" />
      <ItemGroup>
        <FileWrites Include="@(IntermediateAssembly->'%(RootDir)%(Directory)DesignTimeResolveAssemblyReferencesInput.cache', ' ')" />
      </ItemGroup>
      <Touch Files="$(ILFile)" />
    </Target>
    
  3. Add a file with .il extension to your project (e.g. ForceDedicatedGraphicCard.il)
  4. 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
         }
     }
    
  5. Build project
  6. Check if the functions are exported using dumpbin.exe

    dumpbin.exe /exports your_project.exe
    
  7. Now the dedicated graphic card should be chosen automatically. If not, check if you have an updated driver - old drivers don't support these exported functions.
Rawley answered 13/5, 2020 at 14:5 Comment(1)
.NET 5 enables unmanaged exports (see: UnmanagedCallersOnly in devblogs.microsoft.com/dotnet/…). I haven't tried it yet, but looks promising - all the MSIL decompiling/compiling will not be necessary in .NET 5.Rawley
G
0

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.

Gupton answered 20/5, 2014 at 11:26 Comment(2)
DllExport attribute required a NuGet package called UnmanagedExports. It is not natively supported by .NET framework.Enclosure
Also it appears the UnmanagedExports NuGet package only supports exporting methods not variables as shown in this answer. Trying to build with the DllExport attribute shown above doesn't work since its expecting a method not a variable.Sonar

© 2022 - 2024 — McMap. All rights reserved.