带有多视频适配器 (GPU) PC 的 DirectX11
DirectX11 with a multiple video adapter (GPU) PC
通常 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);
然后你为你的 window:
创建一个交换链
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 设备,但这对我没有帮助,因为我不知道哪个显示器显示我的 window。
- 如何找到哪个显示器显示我的window?我必须使用那个监视器视频适配器还是可以使用任何适配器?
- 如果用户将 window 从 Monitor1 移动到 Monitor2 会怎样?另一个适配器是否开始显示 window?
- 总体而言,DirectX11 如何处理此类问题?
如果你想创建一个匹配适配器的设备,你需要在调用中做一些更改:
D3D11CreateDevice
思路是自己提供Adapter参数,所以过程是:
1/自己创建 DXGI 工厂,使用 CreateDXGIFactory
2/枚举此工厂的适配器,为此您使用 factory->EnumAdapters
3/对于每个适配器,您可以枚举连接到它们的显示器,adapter->EnumOutputs
4/然后您可以使用 output->GetDesc
获取监视器描述
5/根据该描述,您可以访问屏幕边界,例如:
output_desc.DesktopCoordinates
现在您可以使用 window 的边界并进行比较(最大面积,包含...)
当您找到最合适的显示器后,您可以将其用于设备创建功能,例如:
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(如果您提供特定的适配器,这是唯一有效的参数,因为驱动程序类型是从中推断出来的)。
现在如果用户将 window 移动到第二个显示器(连接到另一个显卡)会发生什么情况?
最初它会“神奇地工作”,因为桌面 Window 管理器 (DWM) 会检测到您的 window 中的内容需要显示在另一张显卡上。
主要问题是,这将对性能产生很大影响(因为 DWM 需要从适配器 1“下载”交换链内容,并将其上传到适配器 2,然后在那里呈现。
因此,虽然它有效,但它涉及 GPU 往返(您还会注意到 DWM 进程突然增加 CPU 使用率,根据您的系统,可能会以一种潜在的剧烈方式增加)。
请注意,它还可以(将)增加延迟(我们有几个设置,多个 windows 连接到不同的显示器,每个显示器使用一个设备连接到不同的 GPU,它有效,但内容严重不同步(连接到另一张卡的显示器滞后),因此我们必须确保使用 2 台设备,每张卡一个。
所以如果你想确保始终使用最相关的适配器,你需要在每个帧中获取 windows 边界,如果 window 在不同的适配器上,你需要而是使用该适配器创建一个新设备(当然,这涉及重新创建在前一个适配器上创建的每个资源)。
边界检查过程非常快,因此检查基本上没有性能影响(如果只有一个适配器存在,您也可以优化并跳过该检查)。
注:
还有一个函数叫做:IDXGISwapChain::GetContainingOutput
哪个应该给你包含最多的显示监视器window。
从那里您可以执行 GetParent 来检索适配器,并检查适配器是否与设备当前使用的适配器相同。
我从未尝试过该解决方案,但它也可以工作(问题是你已经需要一个交换链,所以在创建时这是不可用的)。
通常 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);
然后你为你的 window:
创建一个交换链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 设备,但这对我没有帮助,因为我不知道哪个显示器显示我的 window。
- 如何找到哪个显示器显示我的window?我必须使用那个监视器视频适配器还是可以使用任何适配器?
- 如果用户将 window 从 Monitor1 移动到 Monitor2 会怎样?另一个适配器是否开始显示 window?
- 总体而言,DirectX11 如何处理此类问题?
如果你想创建一个匹配适配器的设备,你需要在调用中做一些更改:
D3D11CreateDevice
思路是自己提供Adapter参数,所以过程是:
1/自己创建 DXGI 工厂,使用 CreateDXGIFactory
2/枚举此工厂的适配器,为此您使用 factory->EnumAdapters
3/对于每个适配器,您可以枚举连接到它们的显示器,adapter->EnumOutputs
4/然后您可以使用 output->GetDesc
获取监视器描述5/根据该描述,您可以访问屏幕边界,例如:
output_desc.DesktopCoordinates
现在您可以使用 window 的边界并进行比较(最大面积,包含...) 当您找到最合适的显示器后,您可以将其用于设备创建功能,例如:
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(如果您提供特定的适配器,这是唯一有效的参数,因为驱动程序类型是从中推断出来的)。
现在如果用户将 window 移动到第二个显示器(连接到另一个显卡)会发生什么情况?
最初它会“神奇地工作”,因为桌面 Window 管理器 (DWM) 会检测到您的 window 中的内容需要显示在另一张显卡上。 主要问题是,这将对性能产生很大影响(因为 DWM 需要从适配器 1“下载”交换链内容,并将其上传到适配器 2,然后在那里呈现。 因此,虽然它有效,但它涉及 GPU 往返(您还会注意到 DWM 进程突然增加 CPU 使用率,根据您的系统,可能会以一种潜在的剧烈方式增加)。
请注意,它还可以(将)增加延迟(我们有几个设置,多个 windows 连接到不同的显示器,每个显示器使用一个设备连接到不同的 GPU,它有效,但内容严重不同步(连接到另一张卡的显示器滞后),因此我们必须确保使用 2 台设备,每张卡一个。
所以如果你想确保始终使用最相关的适配器,你需要在每个帧中获取 windows 边界,如果 window 在不同的适配器上,你需要而是使用该适配器创建一个新设备(当然,这涉及重新创建在前一个适配器上创建的每个资源)。 边界检查过程非常快,因此检查基本上没有性能影响(如果只有一个适配器存在,您也可以优化并跳过该检查)。
注:
还有一个函数叫做:IDXGISwapChain::GetContainingOutput 哪个应该给你包含最多的显示监视器window。 从那里您可以执行 GetParent 来检索适配器,并检查适配器是否与设备当前使用的适配器相同。 我从未尝试过该解决方案,但它也可以工作(问题是你已经需要一个交换链,所以在创建时这是不可用的)。