为什么同一支手写笔,在 WPF 和 WinUI 3 里的跟手感差这么多?

为什么同一支笔,在 WPF 和 WinUI 3 里的跟手感差这么多?

之前产品经理总是觉得我特别菜,很多很多年了,总是觉得我跟她讲不清楚为什么我切换了winrt 的winui3延迟就降低了。原理是什么,和WPF相比到底怎么回事,怎么提升的,延迟怎么就从60ms 下降到20ms了。我总是给她讲不清楚,就只能说是winrt针对笔做了优化。不走WPF这套渲染了,有独立的优化。但是讲不清楚细节,就导致了干任何事情,她都觉得我很菜。因为很多winui3的源码,我也没有看过,太耗时间了,有些东西还需要反复看,然后还要梳理、消化。现在有AI了。上下文又很长,这个让我产生了写这篇博客的想法。当前这套里也没有讲清楚,因为有一些windows 不开放的API。 当然还有更多的问题没有解释清楚,为什么WPF的字体美化不能继续迁移到winui3了。为什么让你改个笔头你都改不了了。当前这些在其他的文章里写了。但是由于设计到很多windows 不开放的API。我觉得在他开源之前,这些事情没有价值。所以我不想深入去写了。就这这篇延迟的。价值比较高。

这篇文章只讲两个核心差异:

  1. 墨迹走的是哪一棵视觉树
  2. 用户最终感受到的延迟是怎样产生和被补偿的

一、先说结论

同样一支笔、同一块触控屏、同一块 60 Hz 屏幕,WPF 和 WinUI 3 的硬件输入速度不会因为应用框架不同而改变。

真正发生变化的是:

驱动拿到笔坐标以后,
墨迹通过什么路径进入屏幕。

可以先把两种方案理解成两条路:

WPF:
笔点 → WPF自己生成墨迹画面 → WPF渲染系统 → 屏幕

WinUI 3 + Windows InkPresenter:
笔点 → Windows系统墨迹引擎 → 系统合成树 → 屏幕

两条路都能画出湿墨迹,也都有后台处理能力。

主要差异不是“有没有湿墨迹”,而是:

  • WPF 的湿墨迹先成为 WPF Visual,再经过 WPF 的渲染体系。
  • WinUI 3 可以直接托管 Windows InkPresenter,让系统湿墨迹进入 Composition 合成树。
  • Windows InkPresenter 还可以预测未来最多 20 ms 的笔尖位置,让墨迹在视觉上更靠近笔尖。

一句话总结:

WPF 是应用框架自己把墨迹画进窗口;UWP&WinUI 3 是把 Windows 系统墨迹通道直接接进窗口。


二、笔写延迟到底是什么

用户写字时,现实中的笔尖已经移动,但屏幕上的墨迹通常还停留在之前的位置:

屏幕墨迹末端             当前笔尖
      ●----------------------●
              距离

这段距离越长,用户越觉得“不跟手”。

距离产生的原因是整条链路都需要时间:

笔尖移动
→ 触控屏扫描
→ 芯片计算坐标
→ 降噪和平滑
→ 驱动收到坐标
→ 软件生成墨迹
→ 画面进入显示系统
→ 屏幕刷新

两种需要区分的延迟

1. 真实延迟

从笔尖真实移动,到对应真实坐标变成屏幕像素的时间。

2. 视觉延迟

当前笔尖与屏幕墨迹末端之间的距离,换算成时间。

轨迹预测可以明显降低视觉延迟,但不会让硬件真的提前完成工作。

例如:

真实系统延迟       35 ms
向前预测补偿      -20 ms
------------------------
用户看到的跟手差   15 ms

因此,WinUI 3 开启 Prediction 后测得的 10~20 ms,更适合理解为预测后的视觉等效延迟


三、两种方案前面的硬件完全一样

如果使用同一支笔和同一台设备,那么下面这段基本相同:

笔尖真实移动
    ↓
触控面板扫描笔的位置
    ↓
Touch IC 计算 X/Y、压力和倾角
    ↓
底层滤波,减少抖动和噪声
    ↓
Windows 驱动收到坐标

换成 WinUI 3 不会自动消除:

  • 触控屏扫描时间
  • 硬件滤波时间
  • 坐标传输时间
  • 显示器刷新时间

所以,WPF 和 WinUI 3 的主要差异从这里开始:

驱动已经有了笔坐标,接下来谁负责把坐标变成屏幕上的墨迹?


四、先理解“视觉树”是什么

“视觉树”可以把它理解成应用交给显示系统的一张分层画面清单。

例如一个笔记应用的画面可能是:

窗口
├─ 工具栏
├─ 页面背景
├─ 图片
├─ 已完成的笔迹
└─ 当前正在书写的湿墨迹

每个图形元素都在视觉树中占一个位置。显示系统根据这棵树决定:

  • 什么内容在上面
  • 什么内容在下面
  • 内容位于哪里
  • 内容多大
  • 内容是否透明
  • 哪些区域需要重新显示

因此,笔数据到达以后,并不是“有坐标就能立刻上屏”。软件还必须把墨迹变成视觉树中的可显示内容。

这正是 WPF 和 WinUI 3 方案最重要的差异。


五、WPF:先变成 WPF Visual,再进入 WPF 渲染系统

第一步:Pen Thread 接收笔点

WPF 有专门的 Pen Thread 接收高频笔数据。

这些数据包括:

坐标
压力
倾角
时间戳
按钮状态

所以 WPF 并不是完全依赖应用主线程接收每一个笔点。

第二步:DynamicRenderer 生成湿墨迹

WPF 中的 DynamicRenderer 是一个专门处理实时墨迹的组件。

DynamicRenderer 会在独立的 Dynamic Rendering Thread 上根据笔点生成临时湿墨迹。

可以把它想象成一个“临时画笔助手”:

Pen Thread 报告新位置
        ↓
DynamicRenderer 立即补画一小段
        ↓
用户先看到临时墨迹

这说明 WPF 本身也有:

  • 独立输入处理
  • 独立动态渲染线程
  • 湿墨迹

所以不能简单说 WinUI 3 快,是因为 WinUI 3 有湿墨迹而 WPF 没有。

第三步:湿墨迹成为 WPF Visual

DynamicRenderer 生成的湿墨迹不是直接交给屏幕,而是生成一棵 WPF 动态视觉树。

DynamicRenderer
    ↓
DynamicRenderer.RootVisual
    ↓
WPF InkPresenter

这里的 RootVisual 可以理解为:

当前正在书写的临时墨迹,在 WPF 视觉树中的入口。

第四步:进入 WPF 的渲染管线

湿墨迹成为 WPF Visual 后,还要继续经过:

WPF Visual
    ↓
PresentationCore
    ↓
MIL / milcore
    ↓
窗口画面
    ↓
DWM 桌面合成
    ↓
屏幕

这条路径的意义是,WPF 可以把墨迹和应用里的按钮、图片、页面、缩放等内容统一管理。

代价是,实时墨迹仍然属于 WPF 的整套视觉和渲染体系。

第五步:抬笔后换成正式笔迹

WPF 在书写过程中显示的是临时动态墨迹,但最终还需要创建正式 Stroke

书写时:动态湿墨迹
        ↓
抬笔后:UI Thread 创建正式 Stroke
        ↓
正式 Stroke 完成渲染
        ↓
删除临时湿墨迹

所以 WPF 同时维护两份内容:

临时动态 Visual
最终静态 Stroke

两者需要完成一次交接。

WPF 的核心特点

笔点
→ WPF DynamicRenderer
→ WPF 动态 Visual
→ WPF 渲染系统
→ 窗口
→ DWM
→ 屏幕

通俗地说:

WPF 的实时墨迹虽然由专门线程生成,但墨迹仍然要先加入 WPF 自己的画面体系,再通过 WPF 的渲染管线上屏。


六、WinUI 3:直接把 Windows 系统墨迹接入合成树

WinUI 3 实验版 InkCanvas 的核心变化,是直接托管 Windows 系统提供的 InkPresenter

第一步:Windows InkPresenter 接管实时墨迹

InkPresenter 不只是一个显示控件。InkPresenter 统一负责:

接收墨迹输入
处理笔点
建立笔迹模型
生成湿墨迹
管理最终 Stroke

因此,应用不需要为每个笔点执行:

收到 PointerMoving
→ 应用计算曲线
→ 创建图形
→ 修改 XAML 元素
→ 等待页面重新渲染

第二步:建立独立的墨迹宿主

在桌面程序中,Windows Ink 使用专门的桌面托管机制。

可以把 IInkDesktopHost 理解成一个“系统墨迹工作区”:

应用 UI Thread
负责页面、按钮、业务逻辑

Ink Host Thread
负责系统墨迹相关工作

这不是简单地多创建一个线程。

真正重要的是:

墨迹宿主线程、InkPresenter 和系统合成树属于同一套 Windows 墨迹管线。

实时墨迹不需要先回到应用 UI Thread,再由应用把笔点变成普通 XAML 图形。

第三步:系统湿墨迹已经是一棵可合成的 Visual

InkPresenter 会生成系统管理的 Wet Ink Visual。

通过桌面承载和 Visual Link,这棵墨迹 Visual 可以接入 WinUI 3 的 Composition Tree。

Windows InkPresenter
    ↓
系统 Wet Ink Visual
    ↓
WinUI Composition Tree

通俗地说:

Windows 系统已经把实时墨迹做好了,WinUI 3 InkCanvas 只需要把这层系统墨迹“挂”到自己的窗口中。

而不是每一个笔点都重新走:

应用事件
→ 应用计算
→ XAML元素更新
→ 页面渲染

第四步:直接提交给合成系统

Wet Ink Visual 更新以后,可以直接提交 DirectComposition 命令:

新笔点到达
    ↓
InkPresenter 更新 Wet Ink Visual
    ↓
Composition Commit
    ↓
DWM 获得最新墨迹
    ↓
屏幕显示

这意味着墨迹更新不一定要等待普通 WinUI 页面完成一整轮 UI 更新。

WinUI 3 的核心特点

笔点
→ Windows InkPresenter
→ 系统 Wet Ink Visual
→ Composition Tree
→ DWM
→ 屏幕

通俗地说:

WinUI 3 不是自己重新画一遍墨迹,而是把 Windows 已经准备好的系统墨迹层直接接入窗口。


七、两棵视觉树的差异

这是整篇文章最重要的一张概念图。

WPF

笔点
  ↓
DynamicRenderer
  ↓
WPF 动态墨迹 Visual
  ↓
WPF InkPresenter
  ↓
WPF PresentationCore / MIL
  ↓
窗口内容
  ↓
DWM
  ↓
屏幕

WinUI 3 + Windows InkPresenter

笔点
  ↓
Windows InkPresenter
  ↓
系统 Wet Ink Visual
  ↓
DirectComposition / WinUI Composition Tree
  ↓
DWM
  ↓
屏幕

可以这样理解

WPF 像“把急件交给公司内部完整流程”

急件
→ 部门处理
→ 进入公司格式
→ 进入公司审批和发布流程
→ 对外发布

WinUI 3 像“给急件开了一条系统专线”

急件
→ 专线处理
→ 直接进入发布系统

这里不是说 WPF 的路径一定慢,也不是说 WinUI 3 完全没有中间步骤。

真正的区别是:

WPF 湿墨迹属于 WPF Visual;Windows Ink 湿墨迹属于系统 Ink Visual,并直接接入 Composition。


八、Prediction 为什么还能继续降低延迟

即使使用更直接的系统墨迹路径,硬件扫描、数据传输和屏幕刷新仍然需要时间。

因此,Windows InkPresenter 还提供一个视觉补偿机制:轨迹预测。

预测做了什么

当前已知的真实位置      预测的未来位置
         ●------------------●
                 最多20 ms

系统根据:

  • 最近的坐标
  • 速度
  • 方向
  • 加速度

预测笔尖在未来几毫秒的位置,并把湿墨迹先延伸过去。

默认预测时间是 15 ms,最大支持 20 ms。

预测没有做什么

预测没有让下面这些物理过程消失:

  • 触控屏扫描
  • 硬件降噪
  • 数据传输
  • 屏幕刷新
  • 像素响应

预测只是提前猜测:

“按照现在的速度和方向,笔尖接下来大概率会到这里。”

为什么用户觉得明显更跟手

假设真实可见延迟为 35 ms,向前预测 20 ms:

真实延迟           35 ms
预测补偿          -20 ms
------------------------
视觉跟手差         15 ms

因此,WinUI 3 的低延迟体验通常来自两部分:

更直接的系统墨迹视觉链路
+
最多20 ms的轨迹预测补偿

九、为什么 60 Hz 屏幕也可能测到 10~20 ms

60 Hz 表示屏幕约每 16.67 ms 刷新一轮,但并不表示所有延迟只能是 16.67 ms 的整数倍。

原因包括:

  • 笔数据可能在刷新周期中的任意时刻到达
  • 屏幕采用逐行扫描,不是所有像素同时变化
  • 墨迹位置可能通过预测向前补偿
  • 测量常根据笔尖和墨迹之间的距离换算时间

因此,在 60 Hz 屏幕上测到 10~20 ms 的视觉等效延迟是可能的,但不能据此认为真实硬件因果延迟只有 10 ms。


十、对产品价值意味着什么

1. 用户最先感知的是笔尖附近

用户不会在书写时持续分析整条笔迹。用户最敏感的是:

笔尖旁边有没有墨迹
墨迹是否紧跟笔尖
急转弯时是否甩尾
停笔时是否继续滑动

所以,低延迟系统应优先优化实时湿墨迹和笔尖末端。

2. 更低延迟不等于更好的所有体验

预测越激进,笔迹越靠近笔尖,但也可能出现:

  • 急转弯时的修正
  • 停笔时的回拉
  • 预测轨迹与真实轨迹不一致

因此产品设计要在两件事之间平衡:

更跟手
与
更稳定、更准确

3. 技术指标应该拆开

建议产品规格同时记录:

真实端到端延迟
预测后的视觉等效延迟
直线跟手距离
急转弯修正幅度
停笔回拉距离

只写一个“笔延迟 15 ms”,很容易掩盖测量口径差异。


十一、最终总结

给产品经理的一段话

同样的笔和屏幕,WPF 与 WinUI 3 的前半段硬件延迟基本相同。差异主要发生在坐标进入软件以后。WPF 的实时墨迹由 DynamicRenderer 生成,再作为 WPF Visual 进入 WPF 自己的渲染体系;WinUI 3 实验版 InkCanvas 则直接托管 Windows InkPresenter,把系统已经生成的 Wet Ink Visual 接入 Composition 合成树。这样,实时墨迹不需要由应用逐点转成普通 XAML 图形。同时,Windows InkPresenter 还可以预测未来最多 20 ms 的笔尖位置,让墨迹末端在视觉上更靠近笔尖。因此,WinUI 3 的优势不是“有湿墨迹,而 WPF 没有”,而是“系统墨迹视觉链路更直接,并且自带短时预测补偿”。

一句话版本

WPF 是把笔迹画进框架自己的视觉树,再交给显示系统;WinUI 3 是把 Windows 系统墨迹层直接挂到合成树,并用预测让墨迹提前追上笔尖。

三句话版本

  1. 硬件相同,所以扫描、滤波和传输延迟基本相同。
  2. WPF 和 WinUI 3 的主要差异,是湿墨迹进入屏幕时走的视觉树不同。
  3. WinUI 3 再通过最多 20 ms 的预测,进一步降低用户看到的笔尖与墨迹距离。

技术边界

本文可以确认的事实:

  • WPF 有 Pen Thread、UI Thread、Dynamic Rendering Thread 和湿墨迹。
  • WPF DynamicRenderer 会生成动态 WPF Visual,并通过 WPF 渲染体系显示。
  • Windows InkPresenter 负责墨迹输入、处理和呈现。
  • Windows Ink 桌面承载可以把墨迹 Visual 接入 DirectComposition Visual Tree。
  • PredictionTime 默认 15 ms,有效范围 0~20 ms。

仍需通过测试确认的内容:

  • Composition 路径本身具体节省多少毫秒。
  • WinUI 3 关闭 Prediction 后,相对 WPF 还快多少。
  • 当前项目 10~20 ms 的结果中,多少来自管线优化,多少来自预测。

建议保留三组对比:

WPF InkCanvas
WinUI 3 InkCanvas,PredictionTime = 0 ms
WinUI 3 InkCanvas,PredictionTime = 20 ms

这样产品和研发才能分别看到:

系统墨迹管线带来的收益
预测补偿带来的收益

参考资料

posted @ 2026-07-17 09:58  杜文龙  阅读(126)  评论(3)    收藏  举报