Search code examples
c++visual-studio-2017directx-11

How to ensure DirectX 11 app use the discrete GPU on a dual-gpu laptop with C++?


I'm currently working on a game engine project with DirectX 11, which will be compiled to a dynamically linked library, and be called by an exe file. My engine is working perfectly fine. However, I noticed a weird problem.

I have three game projects that use this engine. They are created and set in the same manner, the linker/compiler options are completely the same, and they are linked with the same libraries. But, when I open the executables, only one of them will use the dGPU on my laptop, while the other two use the Intel GPU.

I'm using Visual Studio 2017 and Windows 10 SDK 17763. I'm not traversing all GPU devices in my code but using the default video adapter. And since they are using the same code, I assume that the behavior should be the same.

Here is how I create my ID3D11Device.

    DXGI_SWAP_CHAIN_DESC swapDesc = {};
    swapDesc.BufferCount = 2;
    swapDesc.BufferDesc.Width = width;
    swapDesc.BufferDesc.Height = height;
    swapDesc.BufferDesc.RefreshRate.Numerator = 0;
    swapDesc.BufferDesc.RefreshRate.Denominator = 1;
    swapDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swapDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
    swapDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    swapDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swapDesc.Flags = 0;
    swapDesc.OutputWindow = hWnd;
    swapDesc.SampleDesc.Count = 1;
    swapDesc.SampleDesc.Quality = 0;
    swapDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
    swapDesc.Windowed = true;

    // Attempt to initialize DirectX
    return D3D11CreateDeviceAndSwapChain(
        nullptr,                // Video adapter (physical GPU) to use, or null for default
        D3D_DRIVER_TYPE_HARDWARE,   // We want to use the hardware (GPU)
        nullptr,                // Used when doing software rendering
        deviceFlags,                // Any special options
        nullptr,        // Optional array of possible versions we want as fallback
        0,              // The number of fallback in the above param
        D3D11_SDK_VERSION,          // Current version of the SDK
        &swapDesc,                  // Address of swap chain options
        &swapChain,                 // Pointer to our Swap Chain pointer
        &device,                    // Pointer to our Device pointer
        &dxFeatureLevel,            // This will hold the actual feature level the app will use
        &context);                  // Pointer to our Device Context pointer

Here's the result of two EXEs.

Screenshot of the app that is using NVIDIA GPU Screenshot of the app that is using NVIDIA GPU

Screenshot of the app that is not using NVIDIA GPU Screenshot of the app that is not using NVIDIA GPU

You can see from the screenshots that, on the top left corner of the first screenshot there is an FPS counter generated by NVIDIA GeForce Experience, and to the right there is a notification that indicates the GeForce Overlay is turned on, which means that the app is using the NVIDIA GPU. However, neither of these two is shown on the second pic, which means that the app is using the Intel GPU.


Solution

  • For these 'hybrid' systems, it's largely up to the vendor's driver to 'pick the right GPU' with the user managing it through their proprietary settings UI.

    Prior to Windows 10 (17134), the only programmatic hint you can provide is for a Win32 desktop app by using these 'known' exports in your source code:

    // Indicates to hybrid graphics systems to prefer the discrete part by default
    extern "C" 
    {
        __declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001;
        __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
    }
    

    With DXGI 1.6 when running on Windows 10 (17134) or later, you can use the IDXGIFactory6 to do an adapter enumeration via EnumAdapterByGpuPreference.

    ComPtr<IDXGIFactory6> factory6;
    HRESULT hr = m_dxgiFactory.As(&factory6);
    if (SUCCEEDED(hr))
    {
        for (UINT adapterIndex = 0;
            DXGI_ERROR_NOT_FOUND != factory6->EnumAdapterByGpuPreference(
                adapterIndex,
                DXGI_GPU_PREFERENCE_HIGH_PERFORMANCE,
                IID_PPV_ARGS(adapter.ReleaseAndGetAddressOf()));
            adapterIndex++)
        {
    …
        }
    }
    else for (UINT adapterIndex = 0;
        DXGI_ERROR_NOT_FOUND != m_dxgiFactory->EnumAdapters1(
            adapterIndex,
            adapter.ReleaseAndGetAddressOf());
        adapterIndex++)
    {
    …
    }