结论:DXGI全屏切换必须同步协调窗口状态、交换链、RTV和视口四者,仅调用SetWindowLongPtrW+ShowWindow无效;必须使用SetFullscreenState触发DXGI层级协商,并在ResizeBuffers成功后重建RTV/DSV、更新Viewport,Present前确保垂直同步就绪。

直接说结论:不能靠“重设交换链尺寸”或“改窗口样式”单点操作实现无缝切换,必须同步协调窗口状态、DXGI交换链、渲染目标视图(RTV)和视口(Viewport)四者,且Present调用前要确保ResizeBuffers已成功完成并等待垂直同步就绪。
SetWindowLongPtrW + ShowWindow 不足以触发窗口/全屏切换
很多人试过在运行时调用SetWindowLongPtrW改WS_POPUP/WS_OVERLAPPEDWINDOW,再ShowWindow刷新,结果画面卡死、拉伸、黑屏——因为这只是改了窗口外观,DXGI完全不知道你“想切模式”。它还在用旧的缓冲区尺寸、旧的显示模式、旧的前台/后台缓冲绑定关系渲染。
- 全屏切换本质是 DXGI 层级行为,必须走
IDXGISwapChain::SetFullscreenState - 窗口模式下,交换链必须绑定到有效窗口句柄(
hWnd),且客户区尺寸需与缓冲区匹配 - 直接修改窗口样式不触发
WM_SIZE或WM_DISPLAYCHANGE,DXGI 无法感知尺寸变更
IDXGISwapChain::SetFullscreenState 的真实约束条件
SetFullscreenState看着简单,但失败率极高。它不是“开关”,而是一次状态协商:DXGI会检查显卡驱动是否支持、当前显示器是否允许独占、桌面是否处于DWM合成状态、甚至当前线程是否拥有窗口所有权。
- 必须在设备上下文空闲时调用(即刚
Present完、尚未开始下一帧绘制) - 全屏模式下,
hWnd参数可为NULL,但窗口模式下必须传入有效句柄,且该窗口不能是WS_CHILD - 若返回
DXGI_ERROR_NOT_CURRENTLY_AVAILABLE,说明DWM正在接管(如Win10/11多虚拟桌面),此时应降级为“无边框窗口+最大化”模拟全屏 - 调用后必须立刻调用
ResizeBuffers,否则后续Present会返回DXGI_ERROR_INVALID_CALL
ResizeBuffers 后必须重建 RTV 和 DSV
很多代码只调ResizeBuffers就以为万事大吉,结果切换后清屏正常,但一画三角形就崩溃。这是因为ResizeBuffers会销毁并重建后台缓冲区(ID3D11Texture2D),但原来的ID3D11RenderTargetView仍指向已释放内存。
立即学习“C++免费学习笔记(深入)”;
- 每次
ResizeBuffers成功后,必须重新调用CreateRenderTargetView获取新RTV - 如果用了深度模板缓冲(DSV),同样要
CreateDepthStencilView重建 - 别忘了更新
D3D11_VIEWPORT:宽度/高度要取客户区尺寸(GetClientRect),而非窗口矩形(GetWindowRect) - 若启用了DPI感知(
SetProcessDpiAwarenessContext),客户区尺寸还需乘以缩放因子
窗口模式下要主动处理 Alt+Tab 和焦点丢失
全屏模式下系统会自动暂停渲染;窗口模式下不会。用户Alt+Tab切走再切回,你的Present可能仍在调用,但后台缓冲已失效(DXGI_ERROR_DEVICE_REMOVED或DXGI_ERROR_ACCESS_LOST)。
- 监听
WM_ACTIVATE:WA_INACTIVE时暂停渲染循环,WA_ACTIVE时恢复 - 每次
Present前检查返回值,遇到DXGI_ERROR_DEVICE_REMOVED必须重建整个设备链(不只是交换链) - 避免在
WM_SIZE中立即调ResizeBuffers——窗口拖拽过程中会高频触发,应加防抖(如PostMessage延后执行)
最易被忽略的一点:切换前后,IDXGISwapChain的BufferCount和Usage必须保持一致。比如你初始化时用了双缓冲(BufferCount=2),全屏切换后又设成三缓冲,ResizeBuffers会静默失败,错误码还是S_OK,但后续Present必然崩。这个细节连微软文档都没强调,只能靠调试器看DXGI_SWAP_CHAIN_DESC1实际值。


















