使用多个视频适配器(GPU)的PC上的DirectX11

7
通常,DirectX11的初始化始于创建一个DirectX11设备:
D3D_DRIVER_TYPE driverTypes[] =
{
    D3D_DRIVER_TYPE_HARDWARE,
    D3D_DRIVER_TYPE_WARP,
    D3D_DRIVER_TYPE_REFERENCE,
};

UINT nNumDriverTypes = ARRAYSIZE(driverTypes);

// Feature levels supported
D3D_FEATURE_LEVEL featureLevels[] =
{
    D3D_FEATURE_LEVEL_11_0,
    D3D_FEATURE_LEVEL_10_1,
    D3D_FEATURE_LEVEL_10_0,
    D3D_FEATURE_LEVEL_9_1
};

UINT nNumFeatureLevels = ARRAYSIZE(featureLevels);
D3D_FEATURE_LEVEL featureLevel;

// Create device
for (UINT n = 0; n < nNumDriverTypes; ++n)
{
    hr = D3D11CreateDevice(nullptr,driverTypes[n],nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,featureLevels,nNumFeatureLevels,
        D3D11_SDK_VERSION,&m_pDevice,&featureLevel,&m_pDeviceContext);

然后您为窗口创建一个交换链:
IDXGIDevice* pDXGIDevice = nullptr;
HRESULT hr = m_pDevice->QueryInterface(__uuidof(IDXGIDevice),
    reinterpret_cast<void**>(&pDXGIDevice));
if (SUCCEEDED(hr))
{
    IDXGIAdapter* pDXGIAdapter = nullptr;
    hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter),
        reinterpret_cast<void**>(&pDXGIAdapter));   
    if (SUCCEEDED(hr))
    {
        IDXGIFactory2* pDXGIFactory = nullptr;
        hr = pDXGIAdapter->GetParent(__uuidof(IDXGIFactory2),
            reinterpret_cast<void**>(&pDXGIFactory));
        if (SUCCEEDED(hr))
        {
            DXGI_SWAP_CHAIN_DESC1 desc = {};
            desc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
            desc.BufferCount = 2;
            desc.Width = nWindowWidth;
            desc.Height = nWindowHeight;
            desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
            desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
            desc.SampleDesc.Count = 1;
            desc.SampleDesc.Quality = 0;        

            hr = pDXGIFactory->CreateSwapChainForHwnd(m_pDevice,hWnd,
                &desc,nullptr,nullptr,&m_pSwapChain);

我的电脑连接了两个视频适配器和两个显示器。 适配器1 连接到 显示器1 上。 适配器2 连接到 显示器2 上。我知道我可以枚举 DXGI 适配器并使用特定的适配器为 D3D11CreateDevice 创建一个 DirectX11 设备,但这对我没有帮助,因为我不知道哪个显示器显示我的窗口。

  • 我如何找到显示我的窗口的显示器?我必须使用该显示器的视频适配器还是可以使用任何适配器?
  • 如果用户将窗口从显示器1移动到显示器2,会发生什么?另一个适配器会开始显示窗口吗?
  • 总体而言,DirectX11 如何处理这样的问题?

4
DXGI会选择包含窗口大部分客户区域的输出端口。每个DXGI适配器都有0到N个输出端口,每个DXGI输出端口都与一个监视器相关联(可以从IDXGIOutput::GetDesc获取的DXGI_OUTPUT_DESC结构体的HMONITOR字段中获得)。每个HWND也与一个HMONITOR相关联,可以使用MonitorFromWindow函数获取。 - Simon Mourier
1
谢谢您的回答!假设我创建了一个DirectX11设备,然后使用显示器(Monitor1)的DXGI适配器创建了一个窗口交换链来显示窗口。用户将窗口移动到另一个显示器(Monitor2)。DXGI会开始使用另一个适配器(Monitor2),即使我为Monitor1创建了DirectX11设备吗? - Foxy
2
窗口可以移动,但您必须重新创建设备等,请参见此处的注释:https://stackoverflow.com/questions/27346871/handling-multiple-graphics-adapters-and-monitors - Simon Mourier
1个回答

2

如果你想创建一个与适配器匹配的设备,需要对以下调用进行一些更改:

D3D11CreateDevice

这个想法是要你自己提供Adapter参数,整个过程如下:

1/使用 CreateDXGIFactory 创建 DXGI 工厂。

2/从该工厂枚举适配器,使用 factory->EnumAdapters

3/对于每个适配器,您可以枚举连接到它们的显示器,使用 adapter->EnumOutputs

4/然后使用 output->GetDesc 获取监视器描述。

5/通过描述,您可以访问屏幕边界,例如:

output_desc.DesktopCoordinates

现在您可以使用窗口的边界并进行比较(最大区域,被包含在内...)。当您找到最合适的显示器后,您可以将其用于设备创建函数,例如:

IDXGIAdapter* my_requested_adapter;
D3D11CreateDevice(my_requested_adapter, D3D_DRIVER_TYPE_UNKNOWN , nullptr, D3D11_CREATE_DEVICE_BGRA_SUPPORT,featureLevels,nNumFeatureLevels,
    D3D11_SDK_VERSION,&m_pDevice,&featureLevel,&m_pDeviceContext);

所以在调用中的唯一两个不同之处在于您需要在函数的第一个参数中指定所请求的适配器,并且驱动程序类型变为D3D_DRIVER_TYPE_UNKNOWN(如果提供特定的适配器,则这是唯一有效的参数,因为该驱动程序类型是从它推断出来的)。
那么如果用户将窗口移动到第二个监视器上会发生什么呢?
最初它会“神奇地工作”,因为桌面窗口管理器(DWM)将检测到您窗口中的内容需要在另一个图形卡上呈现。 主要问题是,这将对性能产生很大影响(因为DWM需要“下载”来自adapter1的交换链内容,然后将其上传到adapter2并在那里呈现它。 因此,虽然它可以工作,但它涉及GPU往返(您还会注意到DWM进程突然增加CPU使用率,可能会严重影响您的系统)。
请注意,它也会增加延迟(我们有几个设置,多个窗口附加到不同的监视器上,每个窗口都使用单个设备连接到不同的GPU,它可以工作,但内容严重失同步(连接到其他卡的显示器落后于当前卡),因此我们必须确保使用2个设备,每个设备使用一张卡)。
因此,如果您想始终使用最相关的适配器,则需要在每帧获取窗口边界,如果窗口位于另一个适配器上,则需要创建一个新设备来使用该适配器(当然,这涉及重新创建在先前设备上创建的每个资源)。 边界检查过程非常快,因此检查时基本上没有性能影响(如果只有一个适配器存在,则还可以进行优化并跳过该检查)。
注意:
还有一个名为:IDXGISwapChain::GetContainingOutput的函数 它应该为您提供包含大部分窗口的显示监视器。 从那里,您可以执行GetParent以检索适配器,并检查适配器是否与当前由设备使用的适配器相同。 我从未尝试过这种解决方案,但也可能有效(问题是您已经需要一个Swapchain,因此在创建时无法使用)。

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