1. 渲染系统概述
WPF 采用保留模式渲染系统 (Retained Mode Rendering System),该系统可分为 UI 线程和复合线程两个主要部分,两者协作完成 WPF 应用程序的渲染工作。
1.1 立即模式GUI和保持模式GUI#
图形 API 可分为保留模式API 和即时模式API。 Direct2D 是一种即时模式 API。 WPF 是保留模式 API 的一个示例。
1.1.1. 立即模式GUI#
保留模式 API 是声明性的。 应用程序从图形基元(如形状和线条)构造场景。 图形库将场景的模型存储在内存中。 为了绘制帧,图形库将场景转换为一组绘图命令。 在帧之间,图形库将场景保留在内存中。 若要更改呈现的内容,应用程序会发出命令来更新场景,例如添加或删除形状。 然后,该库负责重绘场景。
每渲染一帧时(很多机器都可以做到1秒钟60帧),渲染库需要执行渲染每个元素的指令,所以这种库无需记住你已经渲染了什么东西,反正每次都会全部重绘。会持续消耗你的CPU和GPU资源。
即时模式的GUI库常用于更动态的元素表现,比如实时图表,动画,特效,游戏等,任何一个像素的改变,都会快速的呈现给你的用户。
1.1.2. 保持模式GUI#
即时模式 API 是过程性的。 每次绘制新帧时,应用程序都会直接发出绘图命令。 图形库不会在帧之间存储场景模型。 相反,应用程序会跟踪场景。
由开发者调用渲染库的API,适时的重绘需需要改变的元素。这就需要渲染库有能力记住之前都渲染了什么东西,所以也会占用更多的内存。
保留模式的GUI库通常更容易使用,使开发更快,但它们也通常也会需要更多的开销,比如要记住元素的位置、层级、遮盖情况等等。
保留模式 API 可能更易于使用,因为 API 会为你执行更多工作,例如初始化、状态维护和清理。 另一方面,它们通常不太灵活,因为 API 施加了自己的场景模型。 此外,保留模式 API 可能具有更高的内存要求,因为它需要提供通用场景模型。 使用即时模式 API,可以实现有针对性的优化。
2. 线程模型
2.1. 概述#
WPF 应用程序有两类线程负责渲染:一个用于管理 UI 叫 UI 线程,另一个用于处理渲染叫复合线程,也叫呈现线程。 当 UI 线程接收输入、处理事件、绘制屏幕和运行应用程序代码时,复合线程通过隐藏方式在后台高效运行。
2.1.1. UI线程#
UI 线程是 WPF 应用程序最重要的线程。它负责处理所有用户交互事件,如按钮单击、菜单选择以及键盘和鼠标输入。它还负责计算 UI 元素的布局、处理数据绑定、触发属性更改等工作。UI 线程是单线程的,这意味着在同一时间只能有一个操作在 UI 线程上运行。
UI 线程在称为 Dispatcher 的对象内对工作项进行排队。 Dispatcher 基于优先级选择工作项,并运行每一个工作项直到完成。 每个 UI 线程必须具有至少一个 Dispatcher,且每个 Dispatcher 都可精确地在一个线程中执行工作项。
UI 线程也可以启用多个,由于多线程程序既复杂又难以调试,因此当存在单线程解决方案时,应避免使用多线程程序。
UI 线程的主要职责是:
- 计算布局和测量视觉(Visual)对象。
- 实现数据绑定和依赖属性系统。
- 处理用户输入和事件。
- 安排和分派渲染工作项给复合线程。
总的来说,UI 线程的工作是计算最终结果,并安排复合线程执行渲染工作。
2.1.2. 复合线程#
复合线程负责 WPF 视觉层次结构的实际渲染工作。当应用程序的 UI 需要在屏幕上重新绘制时,复合线程就会介入,包括窗口大小调整、动画以及任何影响 UI 外观的操作。
复合线程与 UI 线程紧密协作。UI 线程计算布局并安排 Visual 对象,复合线程会渲染 Visual 对象,并将其发送给桌面窗口管理器以在屏幕上显示。
复合线程是一个线程池,包含若干个工作线程,线程数量通常与系统的 CPU 内核数量相同,可以在多个内核上并行工作,从而提高 WPF 应用程序 UI 操作的性能。
复合线程的主要职责是:
- 生成可视化树中元素的位图。
- 应用效果(如3D变换、混合模式等)。
- 合成最终的渲染内容发送给桌面窗口管理器。
2.2. UI线程和复合线程协作#
UI 线程和复合线程是 WPF 渲染系统的两个重要部分,它们相互协作完成渲染工作:
- UI线程负责计算布局、测量元素大小、响应用户交互等。
- UI线程通过 VisualTarget.Render 方法将渲染工作项分派给复合线程线程池。
- 复合线程中的工作线程并行执行渲染工作,生成位图数据。
- 复合线程将最终的渲染结果提交给桌面窗口管理器显示。
3. 性能优化
若要生成响应迅速、用户友好的应用程序,诀窍在于通过保持工作项小型化来最大化 Dispatcher 吞吐量。 这样一来,工作项就不会停滞在 Dispatcher 队列中,因等待处理而过时。 输入和响应间任何可察觉的延迟都会让界面卡顿无响应,带来糟糕体验。
需要注意的是,UI线程不应该执行长时间的操作,否则会导致UI无响应。相反,应该在后台线程上执行这些任务。
WPF 应用程序在处理大型操作时,如涉及大型计算,或需要查询某些远程服务器上的数据库。通常情况下,解决方法是在单独的线程中处理大型操作,让 UI 线程更多倾向于处理 Dispatcher 队列中的工作项。大型操作完成后,再通过 Dispatcher 将结果发送到 UI 线程进行安全渲染。
总的来说,UI 线程专注于用户交互和布局,而复合线程专注于高效呈现 UI。通过正确地利用这两个线程,可以构建响应灵敏且高效的 WPF 应用程序。
参考引用:
https://learn.microsoft.com/zh-cn/windows/win32/learnwin32/retained-mode-versus-immediate-mode