强制使用硬件加速渲染

15

我有一个用C++编写的OpenGL库,使用C++/CLI适配器从C#应用程序中调用。我的问题是,如果在使用了Nvidia Optimus技术的笔记本电脑上使用该应用程序,应用程序将无法使用硬件加速并失败。

我已经尝试使用Nvidia文档中提供的信息http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf,尝试将链接库用于我的C++-dll,并从我的OpenGL库中导出NvOptimusEnablement,但这失败了。 我想我必须对.exe文件而不是与.exe链接的.dll文件进行一些操作。

对我们来说,使用配置文件并不是一个好选择,因为我们需要确保使用了NVIDIA硬件。

是否有办法让C#应用程序强制使用Nvidia芯片组而不是集成的Intel芯片组?


1
你可以在这里询问:http://gamedev.stackexchange.com/ - Tigran
前往NVIDIA面板并切换到独立显卡。 - Michael IV
使用Nvidia面板不是我想要的方式。我想在代码中完成它。 - JohanR
@JohanR:我们最近也有类似的问题,但很抱歉告诉你,目前还没有标准API可以选择要使用的GPU。你要么向NVidia和Microsoft施压引入这样的API,要么直接复制驱动程序控制面板对系统产生的影响,即投掷正确的注册表开关(你必须进行逆向工程)。 - datenwolf
6个回答

8
一个可行的解决方案。其实所有这些都已经被提到了,但是我花了一些时间才明白如何让它工作...
[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
}

重要提示 - 在创建任何窗口之前请调用InitializeDedicatedGraphics()


谢谢,它在我的Lenovo Z510电脑上Windows 10操作系统上运行良好。 - mrbm
1
谢谢您提供的解决方案,它在NVidia GPU上运行良好。您知道如何在AMD GPU上实现相同的功能吗? - Walt D
@WaltD,不幸的是没有。 - igorushi
2
使用.NET Core 3,您现在可以直接调用NativeLibrary.Load("nvapi64.dll")或等效的32位版本。当然,您仍需要使用try catch来处理错误,但这比定义虚拟P/Invoke存根来加载库要简短得多。 - Pieter-Jan Briers

3

我尝试了the swine提供的两个选项,但它们本身都不起作用。我发现需要尝试调用已导入的函数。

using System.Runtime.InteropServices;

class OptimusEnabler
{
    [DllImport("nvapi.dll")]
    public static extern int NvAPI_Initialize();
};

然后在我的应用启动时:

try
{
    ///Ignore any System.EntryPointNotFoundException
    ///or System.DllNotFoundException exceptions here
    OptimusEnabler.NvAPI_Initialize();
}
catch
{ }

在nVidia Optimus系统上,我遇到了一个System.EntryPointNotFoundException的问题,但仍然可以让应用程序使用nVidia硬件。在具有ATI卡的系统上进行测试,我得到了一个System.DllNotFoundException。无论哪种方式,尝试调用此函数并忽略任何异常都似乎运行良好。

1
我无法在我的电脑上使其工作。问题是我的应用程序作为64位进程运行(我已经在应用程序生成设置中禁用了“首选32位”),因此无法找到和加载nvapi.dll。我的解决方案是创建2个分别封装NvAPI_Initialize方法的类,但使用不同的dll(nvapi.dll和nvapi64.dll),并根据Environment.Is64BitProcess属性在运行时选择要尝试使用哪一个。现在似乎可以正常工作了。 - wiesener

2
如果你的软件在英特尔上失败,那么你将不能在50%的笔记本电脑上运行它。因此,我建议你先解决这个问题。
话虽如此,你完全可以通过代码创建配置文件。只需使用NvAPI即可。这段代码正是这样做的,但要注意,你可能不应该操纵全局配置文件,而是创建自己的配置文件:
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;

在启动时,检测您的配置文件是否存在。如果不存在,则创建它(并且您可能需要重新启动自己)。 NvAPI是一个静态库,在非NVIDIA硬件上会优雅地返回错误代码,因此您可以安全地使用它。
编辑:看起来有一种更简单的方法。来自GLFW 3源代码:
// Applications exporting this symbol with this value will be automatically
// directed to the high-performance GPU on nVidia Optimus systems
//
GLFWAPI DWORD NvOptimusEnablement = 0x00000001;

我们没有问题指定我们的应用程序不会在某些硬件上运行,比如英特尔图形。看起来我们将不得不采用“配置文件设置”方式,并使用NvAPI代码创建一个配置文件,可以直接在应用程序中使用,也可以作为独立应用程序,在安装程序安装应用程序时运行。 - JohanR

1

NvPatch x EditBinPE

我面临使用NVIDIA和AMD GPU的需求。在我的网络冒险中,我发现了nvpatch,这是一个应用程序,可以添加必要的标头来使用专用GPU。然而,这个应用程序仅适用于x64,因此我最终创建了EditBinPE,通过它可以编辑PE文件的头部,在其中添加必要的标头以使用AmdPowerXpressRequestHighPerformanceNvOptimusEnablement。为了启用AmdPowerXpressRequestHighPerformanceNvOptimusEnablement,您必须使用以下两个应用程序之一,nvpatchEditBinPE。这两个应用程序用于编辑PE32文件的头部(只有editbinpe可以处理此类型的文件)和PE32+。

使用 EditBinPE

EditBinPE 命令: editbinpe --enable-gpu filename.exe

仅限 AMD 的命令: editbinpe --enable filename.exe AmdPowerXpressRequestHighPerformance

仅限 NVIDIA 的命令: editbinpe --enable filename.exe NvOptimusEnablement

使用 NvPatch

nvpatch 命令: nvpatch --enable filename.exe

NVIDIA

如果只想使用 NVIDIA,请使用此功能:

static void InitializeDedicatedGraphics()
{
     if (Environment.Is64BitProcess)
         NativeLibrary.Load("nvapi64.dll");
     else
         NativeLibrary.Load("nvapi.dll");
}

1

我的解决方案适用于NVidia和AMD,不需要使用DllExport(也不需要NuGet包UnmanagedExports),基于导出函数NvOptimusEnablementAmdPowerXpressRequestHighPerformance

在VS2019上,前面提到的UnmanagedExports包似乎无法正常工作。我找不到另一个为.NET框架提供DllExport功能的工作包。这就是为什么我使用直接的MSIL代码开发了自己的解决方案,在Visual Studio上编写IL代码IL Support给了我很大的帮助。

按照以下步骤操作:

  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.

.NET 5 允许非托管导出(请参见:https://devblogs.microsoft.com/dotnet/improvements-in-native-code-interop-in-net-5-0/ 中的 UnmanagedCallersOnly)。我还没有尝试过,但看起来很有前途 - 所有 MSIL 的反编译/编译在 .NET 5 中都将不再必要。 - Dariusz Wasacz

0

从文档中看起来相当简单。你有多种选项可以这样做。不幸的是,需要执行exe而不是dll来完成。根据this tutorial,可能可以尝试像这样做:

class OptimusEnabler {
    [DllExport("NvOptimusEnablement")]
    public static int NvOptimusEnablement = 1;
};

然后需要将其包含在您的C++库接口中,以便使用它的任何C#应用程序都必须导出此内容。或者,您可以尝试链接到nvapi.dll

class OptimusEnabler {
    [DllImport("nvapi.dll")]
    public static extern int NvAPI_Initialize();
};

根据文档,这应该足以将您的应用程序识别为NV-enabled。导入的函数甚至不需要被调用。

DllExport属性需要一个名为UnmanagedExports的NuGet包。它不是.NET框架的本地支持。 - linleno
3
此外,似乎 UnmanagedExports NuGet 包仅支持导出方法而不是变量,如此答案所示。尝试使用上面展示的 DllExport 属性构建不起作用,因为它期望的是方法而不是变量。 - Nerdtron

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接