Принудительное аппаратное ускорение рендеринга

У меня есть библиотека OpenGL, написанная на C++, которая используется из приложения C# с использованием адаптеров C++ /CLI. Моя проблема в том, что если приложение используется на ноутбуках с технологией Nvidia Optimus, приложение не будет использовать аппаратное ускорение и не выйдет из строя.

Я пытался использовать информацию, найденную в документе Nvidias http://developer.download.nvidia.com/devzone/devcenter/gamegraphics/files/OptimusRenderingPolicies.pdf о связывании библиотек с моим C++-dll и экспорте NvOptimusEnablement из моей библиотеки OpenGL но это не удается. Я думаю, что я должен сделать что-то с.exe, а не с.dll, связанные с.exe

Для нас это не очень хороший вариант использования профилей, так как мы должны убедиться, что используется оборудование nvidia.

Каким-то образом приложение C# может заставить Optimus использовать чипсет Nvidia вместо встроенного чипсета Intel?

6 ответов

Рабочий раствор. На самом деле все это уже упоминалось, но мне потребовалось время, чтобы понять, как заставить это работать...

[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, но он все еще работает, чтобы приложение использовало оборудование nVidia. Протестировано на системе с картой ATI, я получил System.DllNotFoundException, В любом случае, попытка вызвать это и игнорирование любого исключения здесь, кажется, работает хорошо.

Если ваше программное обеспечение не работает на Intel, вы не сможете запустить его на 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;

NvPatch x EditBinPE

Я столкнулся с необходимостью использования графических процессоров NVIDIA и AMD. В своем интернет-приключении я нашел — приложение, которое добавляет необходимые заголовки для использования выделенных графических процессоров. Однако это приложение работает только на x64, поэтому в итоге я создал , с помощью которого можно редактировать заголовки PE-файлов, добавляя необходимые для использования заголовки и . ВключитьAmdPowerXpressRequestHighPerformanceиNvOptimusEnablementвы должны использовать одно из двух приложений: и . Эти два приложения служат для редактирования заголовка файла PE32 (только Editbinpe делает этот тип файла) и PE32+.

Использование EditBinPE

Команда EditBinPEEditBinPE :EditBinPEeditbinpe --enable-gpu filename.exe

только команда amd:editbinpe --enable filename.exe AmdPowerXpressRequestHighPerformance

команда только nvidia:editbinpe --enable filename.exe NvOptimusEnablement

Использование NvPatch

команда nvpatchнвпатча :nvpatchnvpatch --enable filename.exe

NVIDIA

Чтобы просто использовать NVIDIA, просто используйте эту функцию здесь:

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

Мое решение, работающее как на NVidia, так и на AMD, без DllExport (без пакета NuGet UnmanagedExports), основано на экспортированных функциях NvOptimusEnablement и https://gpuopen.com/amdpowerxpressrequesthighperformance/:

На VS2019 вышеупомянутый пакет UnmanagedExports не работает должным образом. Мне не удалось найти другой рабочий пакет для.NET framework, который предоставляет возможность DllExport. Вот почему я разработал собственное решение с использованием прямого кода MSIL с большой помощью в написании кода IL в Visual Studio и поддержке IL.

Следующие шаги:

  1. Выберите платформу x86 или x64 для вашего exe (вы не можете сделать это с настройкой AnyCPU)
  2. К файлу.csproj (.csproj для exe, а не для любой dll) добавьте следующий раздел, который разрешает прямое использование кода MSIL (вы можете поместить его в любом месте, например, после ). Для.net frameworks новее 4.6.2 вам может потребоваться обновить пути к 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. Добавьте в проект файл с расширением.il (например, ForceDedicatedGraphicCard.il)
  4. Вставьте приведенный ниже код в этот файл (вместо 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
         }
     }
    
  5. Построить проект
  6. Проверьте, экспортируются ли функции с помощью dumpbin.exe

    dumpbin.exe /exports your_project.exe
    
  7. Теперь выделенная графическая карта должна выбираться автоматически. Если нет, проверьте, есть ли у вас обновленный драйвер - старые драйверы не поддерживают эти экспортированные функции.

Из документа это выглядит довольно просто. Вам дают несколько вариантов, как это сделать. К сожалению, это нужно сделать 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. Импортированная функция даже не должна вызываться.

Другие вопросы по тегам