D3D11CreateDeviceAndSwapChain无法在第二显示器切换至全屏的问题
问题拆解与解决方案
我来帮你一步步捋清楚这个Direct3D全屏的坑,结合DXGI的工作机制和你遇到的现象,咱们逐个解决:
核心问题分析
首先得明确两个关键现象背后的原因:
- 直接用
D3D11CreateDeviceAndSwapChain创建全屏交换链时,第二显示器只显示窗口化但返回S_OK:这是DXGI的“隐性容错”机制——当它发现无法在指定输出设备上创建全屏交换链时,会悄悄回退到窗口化模式,但仍然返回成功,这很容易误导开发者。 SetFullscreenState用vOutput[0]返回E_FAIL,换成vOutput[1]就正常:说明你硬编码的输出设备索引和窗口实际所在的显示器不匹配,DXGI不允许跨显示器强制切换全屏,直接返回失败。
针对性解决方案
1. 正确匹配窗口所在的DXGI Output
不要硬编码vOutput[0]或vOutput[1],而是通过窗口位置自动匹配对应的显示器输出对象,这是解决E_FAIL的关键。示例代码:
// 假设你已经获取了IDXGIFactory指针pFactory std::vector<IDXGIOutput*> targetOutputs; IDXGIAdapter* pAdapter = nullptr; for (UINT adapterIdx = 0; pFactory->EnumAdapters(adapterIdx, &pAdapter) != DXGI_ERROR_NOT_FOUND; adapterIdx++) { IDXGIOutput* pOutput = nullptr; for (UINT outputIdx = 0; pAdapter->EnumOutputs(outputIdx, &pOutput) != DXGI_ERROR_NOT_FOUND; outputIdx++) { DXGI_OUTPUT_DESC outputDesc; pOutput->GetDesc(&outputDesc); // 获取游戏窗口的屏幕位置 RECT windowRect; GetWindowRect(key.hWnd, &windowRect); // 检查窗口是否落在当前输出设备的桌面区域内 if (PtInRect(&outputDesc.DesktopCoordinates, {windowRect.left, windowRect.top}) || PtInRect(&outputDesc.DesktopCoordinates, {windowRect.right, windowRect.bottom})) { targetOutputs.push_back(pOutput); break; } pOutput->Release(); } pAdapter->Release(); } // 用匹配到的Output切换全屏 if (!targetOutputs.empty()) { HRESULT hr = pSwapChain->SetFullscreenState(TRUE, targetOutputs[0]); // 这里就不会返回E_FAIL了 }
2. 遵循DXGI最佳实践创建交换链
按照DXGI文档建议,先创建窗口化交换链,再切换全屏,同时调整交换链描述的参数,避免硬编码分辨率和刷新率:
DXGI_SWAP_CHAIN_DESC sd = {}; sd.BufferCount = 1; sd.BufferDesc.Width = 0; // 自动匹配窗口/显示器分辨率 sd.BufferDesc.Height = 0; sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 0; // 自动匹配显示器刷新率 sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = key.hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; // 先以窗口化创建 sd.Flags = DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
这种方式让DXGI自动适配显示器的硬件能力,能避免很多兼容性问题。
3. 处理高分辨率兼容性问题
如果遇到部分分辨率下切换失败的情况(比如你提到的1680x1050以上),可以先枚举目标显示器支持的显示模式,选择最匹配的模式再切换:
// 枚举当前Output支持的所有显示模式 std::vector<DXGI_MODE_DESC> supportedModes; UINT modeCount = 0; pMatchingOutput->GetDisplayModeList(DXGI_FORMAT_B8G8R8A8_UNORM, 0, &modeCount, nullptr); supportedModes.resize(modeCount); pMatchingOutput->GetDisplayModeList(DXGI_FORMAT_B8G8R8A8_UNORM, 0, &modeCount, supportedModes.data()); // 筛选出和游戏分辨率匹配的模式 DXGI_MODE_DESC targetMode = {}; for (const auto& mode : supportedModes) { if (mode.Width == Graphics::ScreenWidth && mode.Height == Graphics::ScreenHeight) { targetMode = mode; break; } } // 设置交换链的显示模式后再切换全屏 pSwapChain->ResizeTarget(&targetMode); pSwapChain->SetFullscreenState(TRUE, pMatchingOutput);
SetFullscreenState的替代方案:伪全屏
如果以上方案都无法解决(比如显卡驱动存在bug),可以用无边框窗口模拟全屏,这是很多游戏的 fallback 方案,兼容性拉满:
// 获取目标显示器的桌面区域 DXGI_OUTPUT_DESC outputDesc; pMatchingOutput->GetDesc(&outputDesc); // 移除窗口边框和标题栏 LONG windowStyle = GetWindowLongPtr(key.hWnd, GWL_STYLE); windowStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX); SetWindowLongPtr(key.hWnd, GWL_STYLE, windowStyle); // 将窗口移动到目标显示器的全屏位置 SetWindowPos(key.hWnd, HWND_TOPMOST, outputDesc.DesktopCoordinates.left, outputDesc.DesktopCoordinates.top, outputDesc.DesktopCoordinates.right - outputDesc.DesktopCoordinates.left, outputDesc.DesktopCoordinates.bottom - outputDesc.DesktopCoordinates.top, SWP_SHOWWINDOW);
这种方式不需要依赖DXGI的全屏切换机制,完全通过窗口API实现,几乎不会有兼容性问题。
内容的提问来源于stack exchange,提问作者Mantas Kandratavičius
相关产品推荐
相关产品推荐

