Windows 的窗口显示可以理解为:程序负责绘制画面,Windows 显示系统负责将画面呈现在屏幕上。理解这条路径,关键是分清窗口、绘图资源和呈现请求各自的职责。

一、几个核心概念

  • HWND(窗口句柄):Windows 用于标识和管理窗口的句柄。窗口的位置、大小、层叠关系等信息由窗口系统管理。

  • HDC(设备上下文):GDI 绘图操作使用的上下文,可以通过 GetDC(hwnd) 获取,用于绘制文字、矩形、位图等。

  • Window Surface(窗口表面):承载窗口画面的图像资源,可以类比为 HTML Canvas 背后的画布;具体实现取决于呈现路径。

  • DXGI SwapChain(交换链):管理一组用于画面呈现的缓冲区,常见的窗口化用法会将它与 HWND 关联。

  • Back Buffer(后台缓冲区):SwapChain 管理的图像缓冲区,GPU 将渲染结果写入其中。

  • DWM(桌面窗口管理器):负责桌面合成,将不同窗口的内容组合成桌面画面。

二、GDI 与 D3D11 的显示流程

下面用常见的窗口化路径说明各部分如何协作。实际是否经过完整的 DWM 合成,还会受到呈现模式和系统优化的影响。

传统 GDI:由 Windows 管理窗口绘图表面

flowchart LR A["创建 HWND"] --> B["GetDC(hwnd)<br/>获取 HDC"] --> C["GDI 绘制"] C --> D["Windows 更新<br/>窗口绘图表面"] --> E["DWM 合成"] --> F["显示器"]

GDI 无需程序自行创建 SwapChain,也不需要主动调用 Present()。窗口表面由 Windows 图形系统管理,实际存储和加速方式取决于系统与驱动。

现代 D3D11:显式管理绘制与呈现

flowchart TB subgraph setup["初始化:建立窗口与缓冲区"] direction LR A["创建 HWND"] --> B["创建 D3D11 设备"] --> C["DXGI 创建 SwapChain<br/>分配 GPU 缓冲区"] end subgraph frame["每帧:绘制并提交"] direction LR D["CPU 提交绘制命令"] --> E["GPU 绘制 Back Buffer"] --> F["CPU 调用 Present()"] end subgraph output["Windows 显示系统"] direction LR G["DXGI / DWM 安排呈现"] --> H["显示器"] end setup --> frame --> output

这里需要区分三个动作:

  • CPU 提交命令:CPU 主要负责调用 API、组织资源并提交 GPU 命令。

  • GPU 写入缓冲区:Back Buffer 通常是 GPU 图形资源,并非 CPU 可随意读写的普通内存。

  • 调用 Present():提交呈现请求,不代表画面已经立即显示到屏幕上。

三、Flip Model 为什么更高效?

DXGI 有两种主要的窗口呈现模型:

比较项

BitBlt Model

Flip Model

画面提交

通常复制到 DWM 重定向表面

直接与 DWM 共享缓冲区

额外复制

通常需要

可避免传统路径的额外复制

同一 HWND 的 GDI 混合绘图

支持

不支持普通 GDI 自动混合

使用建议

主要用于兼容旧程序

现代程序优先

在 Flip Model 下,SwapChain 的后台缓冲区可以直接提供给 DWM 用于合成,从而减少 GPU 内部的额外复制。

同一个 HWND,不代表同一块画布

同一个 HWND 可以通过 GetDC() 使用 GDI,也能关联 DXGI SwapChain,但这不代表两种绘图方式共享同一块画布。

当 Flip Model 开始呈现后,普通 GDI 对同一 HWND 的更新不会自动叠加到 SwapChain 画面上。即使 GDI 最后执行,也不能靠绘制顺序覆盖 GPU 画面。

这并不是 DWM 在两个缓冲区之间反复切换,而是该窗口的呈现路径由 Flip Model 决定。

四、与 FFmpeg 视频渲染的关系

使用 FFmpeg 的 D3D11VA 硬件解码时,一条常见的视频显示路径如下:

flowchart TB subgraph decode["解码:压缩数据变成视频纹理"] direction LR A["H.264 压缩数据"] --> B["GPU 硬件解码"] --> C["D3D11 视频纹理<br/>以 NV12 为例"] end subgraph render["呈现:纹理变成窗口画面"] direction LR D["GPU 色彩转换、缩放"] --> E["SwapChain<br/>Back Buffer"] --> F["Present()"] --> G["显示器"] end decode --> render

只要解码、纹理处理和渲染链路支持 GPU 资源互操作,解码后的视频帧就可以始终保留在 GPU 可访问的内存中,无须下载到 CPU 内存再上传。

UI 框架已有 SwapChain 时怎么办?

如果 UI 框架(例如 GPUI)已经管理了窗口的 SwapChain,就不能简单地再创建第二个 SwapChain,期待两个渲染器自动合成画面。

通常有两种接入方式:

  • 将视频纹理接入原有渲染器:由同一条渲染路径完成视频与 UI 的组合。

  • 使用独立的子 HWND 显示视频:需要额外处理窗口层叠、位置同步和裁剪问题。

总结

理解 Windows 显示机制,最关键的是区分三个对象:

HWND 决定画面属于哪个窗口;Back Buffer 保存实际绘制结果;Present() 将完成的画面提交给 Windows 显示系统。

GDI 与 D3D11 的主要区别,在于应用如何绘图、管理缓冲区,以及提交画面。

参考:Microsoft — DXGI Flip Model

文章作者: Lien
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 彼方!
喜欢就支持一下吧