我有一个用c++编写的OpenGL库,它是从使用c++/CLI适配器的c#应用程序中使用的。我的问题是,如果该应用程序在采用Nvidia Optimus技术的笔记本电脑上使用,该应用程序将不会使用硬件加速并失败。
我已尝试使用Nvidias文档中的信息http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf关于将库链接到我的C++-dll并从OpenGL库导出NvOptimusEnablement,但失败了。我想我必须用.exe做点什么,而不是用链接到.exe 的.dlls
对我们来说,使用配置文件不是一个好的选择,因为我们需要确保使用英伟达硬件。
C#应用程序有没有办法迫使Optimus使用英伟达芯片组而不是集成的英特尔芯片组?
一个有效的解决方案。事实上,所有这些都已经提到了,但我花了一段时间才明白如何让它发挥作用。。。
[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()
我尝试了猪的两个选项,但都不起作用。我发现我需要尝试调用导入的函数。
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
,但它仍然可以使应用程序使用英伟达硬件。在使用ATI卡的系统上测试,我得到了System.DllNotFoundException
。无论哪种方式,尝试调用它并忽略这里的任何异常似乎都很好。
如果您的软件在英特尔上出现故障,那么您将无法在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;
我的解决方案在NVidia和AMD上都能工作,没有DllExport(没有NuGet包UnmanagedExports),基于导出的函数NvOptimusEnablement和AmdPowerXpressRequestHighPerformance:
在VS2019上,上述UnmanagedExports包似乎无法正常工作。我找不到另一个提供DllExport功能的.NET框架工作包。这就是为什么我在Visual Studio和IL支持上编写IL代码的大力帮助下,使用直接MSIL代码开发了自己的解决方案。
要遵循的步骤:
- 为您的exe选择x86或x64平台(不能使用AnyCPU设置)
-
要.csproj文件(.csprojforexe,而不是任何dll),请添加以下部分,该部分允许直接使用MSIL代码(通常可以将其放在任何位置,即<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets"/>之后)。对于更新于4.6.2的.net框架,您可能需要更新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)binildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm> </PropertyGroup> <PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)binNETFX 4.0 Toolsildasm.exe' ) "> <ILDasm>"$(FrameworkSdkPath)binNETFX 4.0 Toolsildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm> </PropertyGroup> <PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)binNETFX 4.5.1 Toolsildasm.exe' ) "> <ILDasm>"$(FrameworkSdkPath)binNETFX 4.5.1 Toolsildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm> </PropertyGroup> <PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)binNETFX 4.6 Toolsildasm.exe' ) "> <ILDasm>"$(FrameworkSdkPath)binNETFX 4.6 Toolsildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm> </PropertyGroup> <PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)binNETFX 4.6.1 Toolsildasm.exe' ) "> <ILDasm>"$(FrameworkSdkPath)binNETFX 4.6.1 Toolsildasm.exe" /nobar /linenum /output:"$(ILFile)" @(IntermediateAssembly->'"%(FullPath)"', ' ')</ILDasm> </PropertyGroup> <PropertyGroup Condition=" Exists ( '$(FrameworkSdkPath)binNETFX 4.6.2 Toolsildasm.exe' ) "> <ILDasm>"$(FrameworkSdkPath)binNETFX 4.6.2 Toolsildasm.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>[^ rtn]+)</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>[^ rtn]+)</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>
- 在项目中添加一个扩展名为.il的文件(例如ForceDicatedGraphicCard.il)
-
将下面的代码粘贴到此文件(您可以输入命名空间,而不是"WindowsApplication1"):
.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 } }
- 生成项目
-
检查是否使用dumpbin.exe 导出函数
dumpbin.exe /exports your_project.exe
- 现在应该自动选择专用图形卡。如果没有,请检查您是否有更新的驱动程序-旧的驱动程序不支持这些导出的功能
NvPatch x EditBinPE
我面临着使用NVIDIA和AMD GPU的需要。在我的互联网冒险中,我发现了nvpatch,这是一个添加必要标头以使用专用GPU的应用程序。然而,这个应用程序只能在x64上工作,所以我最终创建了EditBinPE,使用它可以编辑PE文件的标题,添加必要的标题以使用AmdPowerXpressRequestHighPerformance
和NvOptimusEnablement
。要启用AmdPowerXpressRequestHighPerformance
和NvOptimusEnablement
,必须使用两个应用程序之一,nvpatch和EditBinPE。这两个应用程序用于编辑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");
}
从文档来看,它似乎相当简单。你有多种选择如何做到这一点。不幸的是,exe需要这样做,而不是dll。根据本教程,可以执行以下操作:
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。甚至不应该要求调用导入的函数。