You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

D3D11CreateDeviceAndSwapChain无法在第二显示器切换至全屏的问题

问题拆解与解决方案

我来帮你一步步捋清楚这个Direct3D全屏的坑,结合DXGI的工作机制和你遇到的现象,咱们逐个解决:

核心问题分析

首先得明确两个关键现象背后的原因:

  1. 直接用D3D11CreateDeviceAndSwapChain创建全屏交换链时,第二显示器只显示窗口化但返回S_OK:这是DXGI的“隐性容错”机制——当它发现无法在指定输出设备上创建全屏交换链时,会悄悄回退到窗口化模式,但仍然返回成功,这很容易误导开发者。
  2. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.28 07:08:28