从零手写 C++ UI 库:布局渲染优化与 AI 编程实践
掐指一算,这是 kwik-ui 这个系列进度记录的第三篇了。半年前我的第一篇还在画大饼,第二篇总算把窗口、渲染循环和基本的控件树跑通了,今天这篇主要聊两件事:一是这几个月里 kwik-ui 到底把哪些硬骨头啃下来了,二是这段时间高密度用 AI 写 C++ 之后的真实感受。对,就是热搜话题里天天被刷屏的 AI 编程、vibe coding、Cursor 那一整套东西。
先给没看过前文的朋友交代背景:kwik-ui 是一个从零手写的 C++ UI 库,目标是轻量、跨平台、能直接嵌进游戏引擎或者工具类项目里。不像 Qt 那样全家桶体积爆炸,也不像 ImGui 那样渲染风格一眼工具化,我想要的界面最终能像 Figma 稿一样干净,同时代码量还能控制在能看懂、能改、能被一个人维护的程度。
如果你最近也在折腾 C++ 界面、UI 卡顿问题,或者正纠结要不要让 AI 大规模进入你的 C++ 代码库,这篇应该能给你一些参考。文章会比较长,但每段都是我实际跑过、摔过、又爬起来改过的记录,不是网上那种抄来抄去的概念科普。
1. 项目回顾与定位:kwik-ui 到底想解决什么问题
1.1 kwik-ui 的定位:轻量、可控、可嵌入
刚开始做 kwik-ui 时,我给自己定的目标是:不依赖 Qt、不依赖 Electron、不依赖 WebView,纯 C++ 实现一个保留模式的 UI 框架。所谓保留模式,就是界面元素以节点树的方式常驻内存,你改属性它就重绘,而不是像 ImGui 那样每帧从代码里重新声明界面。保留模式的好处是适合动态界面、状态复杂的产品级 UI,代价是实现复杂度高很多。
为什么不用现成的?Qt 功能确实强,但发布一个几十 MB 的动态库就为了画几个按钮,对嵌入式工具和游戏编辑器太笨重;ImGui 又完全是另一套思路,它做调试工具很爽,但真要做出设计稿里那种圆角卡片、阴影、渐变、弹性布局,你得一层一层自己画,画完还要处理状态一致性和事件命中,基本等于把 UI 框架重新发明一遍。
kwik-ui 走的是中间路线:底层的窗口抽象和渲染后端自己写,核心数据结构(节点树、布局引擎、事件系统)也自己写,但把字体、图片解码、文本整形等场景直接接成熟库。这样可控的部分完全可控,复杂的部分不必重复造轮子。
1.2 这一轮选择的几个关键前提
在架构上,这一轮我给自己定了几条硬约束。第一,渲染后端必须基于 GPU 批量绘制,不能像早期原型那样一个控件一个 draw call,否则控件一多卡顿马上来。第二,布局引擎必须单独存在,不能把布局逻辑散落在每个控件的绘制代码里,否则后面做响应式就是灾难。第三,事件系统要跟 Web 的 DOM 事件冒泡机制对齐,团队成员一看就懂。
这些选择决定了后文的所有细节。如果你也想做一个类似的 C++ UI 库,或者你正在用渲染引擎做编辑器工具面板,这几条前提值得抄作业。它们不是最炫的,但能保证后续迭代不翻车。
1.3 本轮范围:不再"能跑",而是"敢用"
上一个版本就是"能跑",按钮能点、文字能画、窗口能缩能放,但字体渲染一上中英文混排就乱,滚动条滚动时整帧重绘,GPU 上每帧要重新提交两三千个绘制指令。这轮迭代的目标很明确:布局系统重写、渲染路径重写、字体系统重写、事件系统重写,外加一套基准性能测试。
听起来像重写四次,其实每次都是渐进替换,控制在一个分支里,每天都能编译运行。我个人的习惯是:UI 框架这种代码最忌讳"憋大招",一星期没跑过 == 一星期后跑不起来。下一节我把每块重写的核心细节拆开讲。
2. 核心架构与实现细节:UI 卡顿这个坑是怎么填的
2.1 节点树与事件模型:先把地基打好
kwik-ui 的界面由三棵结构组成:Widget 树、Render 树、Layout 树。Widget 树是你写业务代码时操作的对象,Render 树保存绘制所需的全部状态,Layout 树只保留参与布局的属性。三棵树之间通过一个 Element 中间对象关联。这个设计借鉴了 Flutter 的三棵树思想,好处是:业务层改了状态,不需要立刻触发重建整棵树,只有真正需要绘制和布局的节点才同步。
事件模型我直接选择了 Web 风格的冒泡和捕获双阶段。事件从根节点开始捕获,经过路径上的每个节点,再到目标节点,然后反向冒泡回根。这个模型看起来简单,但真正实现时坑很多。比如 StopPropagation 要同时处理捕获阶段和冒泡阶段;又比如节点从事件分发的第一秒开始,就要正确判断自己是否还挂在树上,否则一个删除节点的回调会让整棵事件链悬空。
我踩过最痛的一个 bug:滚动条拖动时释放鼠标,鼠标事件先到达旧的滚动条状态,再到达新位置,导致滚动条在 dragging 状态里卡住。最后解决办法是事件目标在 PointerDown 时加上"捕获指针"逻辑,后续的 Move 和 Up 事件强制派发给捕获者,直到 Up 或取消事件发生。这在桌面 UI 里是基本要求,但一开始最容易漏。
2.2 布局系统:FlexBox 与锚点在 C++ 里的落地
上一版布局是每个控件自己算坐标,写到后面到处是 position += offset,维护起来像盘丝洞。这版直接重写为 FlexBox + 锚点混合布局。核心思路是:每个元素有 style,布局引擎根据 style 构建一棵专门的布局树,然后按次序计算尺寸和位置。
计算流程分四步:先将树中 display:none 的节点剔除;然后对每个节点解析 min-width、max-width、flex-grow 等约束;接着进行文本测量,将所有文本节点的理想尺寸计算出来;最后按深度优先顺序做 flex 分配,生成最终 BoxGeometry。关键一点是,文本测量结果必须缓存,否则布局一帧要反复调用很久。
我贴一段布局计算的核心伪代码,方便大家理解,实际实现比这个复杂得多,但骨架就是这个:
布局引擎是 UI 库的心智负担重灾区,但也是收益最明显的地方。重写完成之后,以前那种手动 addWidget(pos) 的方式全部废弃,界面结构写在初始化里,直接可读可预测,调试效率翻倍。不过 FlexBox 也不是银弹,你做一个滚动列表、弹窗居中、底栏吸底这种场景时,还是需要一个 position 为 absolute + 锚点对齐的组合手段。所以我的布局系统里 FlexBox 只管"流式排列",锚点只管"相对定位",两者互不干扰。
2.3 渲染后端:把 Draw Call 降一个数量级
UI 卡顿最典型的表现就是:鼠标滚动时掉帧,窗口缩放时界面像 PPT。造成这种问题的核心原因无非三点:每帧重建全部几何数据、绘制指令太多、纹理上传太频繁。
kwik-ui 早期版本就是这样,每个控件独立提交一条绘制指令,当时画一百个卡片就产生两千多条命令,连我自己的集成显卡都能感到压力。这轮我直接改成 GPU 实例化 + 顶点预打包方案。具体做法是:每帧把界面里所有可见的矩形、文字、位图、边框,合并成若干个大的顶点缓冲,按照图集把纹理打包上传,然后通过若干次带纹理绑定的 Draw 调用来输出。
说这么抽象你可能没概念,举个数据。改完渲染路径后,同样是 100 个圆角卡片带阴影的场景,绘制命令从大约 2100 条降到 15 条,帧耗时在集成显卡上从 16 毫秒左右降到 3 毫秒左右。这个改动对 UI 库是质的飞跃。
当然,实例化也不是没有代价,代价就是:你需要自己维护一个 batcher,负责将动态 UI 和静态 UI 分层。我的做法是:动画中、悬停中、被滚动影响的控件进入"动态层",每帧重新打包;背景、静态容器、静态文字进入"静态层",只在属性变化时更新。静态层叠加动态层输出,兼顾流畅度和 CPU 占用。
2.4 卡顿消消乐:脏矩形与字体缓存
Draw Call 降下来之后,另一个隐藏刺客是文字测量和重绘范围。布局阶段不可能避免测量文本,但不缓存的话,一个文本控件测量一次就是一次 FreeType/字体 shaping 调用。界面一多,布局耗时立刻被拖垮。
我的方案是两级缓存:一级缓存 text measurement,以 (fontKey, string, fontSize, maxWidth) 为 key,保存测量结果;第二级是 shaping 结果缓存,以 (fontKey, utf8String, fontSize) 为 key,保存 glyph 位置、缩放、advance。两级缓存让同一段文本的反复布局,从毫秒级降到了微秒级,这个优化不做,布局引擎再多花哨都没用。
重绘范围方面,我实现了脏矩形 + 局部合并。每个节点在属性变更时被标记为 dirty,渲染循环收集所有 dirty 节点的并集,只重绘那个区域。这里最容易踩的坑是阴影和溢出:一个按钮在可见边界外绘制了阴影,但它的 Geometry 判断在边界内,导致阴影被裁剪。这个问题我调了整整一个周六,最后方案是脏矩形计算时把每个节点的装饰盒向外扩展一个固定的阴影安全距离。别小看这个距离,它直接决定了你滚动时阴影会不会闪烁。
3. 实际踩坑实录:中英文混排、命中测试与三平台崩溃
3.1 中英文混排的字体回退问题
Unicode 字符集里有超过十四万个字符,而一款字体通常只覆盖其中一部分。UI 显示中文时最容易遇到的问题是:默认字体是拉丁字体,中文全变豆腐块;或者你指定了中文字体,但英文和数字又显得大一圈。
kwik-ui 的字体系统是用 HarfBuzz + FreeType 搭的,shaping 交给 HarfBuzz,光栅化交给 FreeType。最开始我用一套固定字体链,先扫系统字体列表,找到包含对应字符的字体就切换。这个思路方向对,但实现时漏了一个细节:shaping 必须基于整段文本跑,你不能把中英文字符拆开各自 shaping 再拼起来,否则阿拉伯文、泰文这类有复杂连接和重排规则的文字会直接碎掉。
当前实现是:把文本按 bidi 方向分好段,再对每一段做 shaping。中英文混排时的处理是:先检查当前字体有没有那个 glyph,没有就去字体回退表里找候选字体,找到了直接向 HarfBuzz 喂新字体句柄。效果就是同样一行"按钮 Button 点击 12345",中文、英文、数字的字号和基线都能对齐,视觉上不再跳跃。
这里必须提醒一句:字体回退表虽然看起来是小事,但在 Windows / macOS / Linux 上差异极大。Windows 用 DirectWrite 可以拿到系统字体回退规则,但你要自己解析多语言回退链;macOS 用 CoreText 的 CTFontCopyDefaultCascadeList 会方便很多;Linux 就得遍历 fontconfig 拿 pattern 匹配。我的建议是:做跨平台 UI 库,千万别想当然认为"反正都是 FreeType",平台层的字体规则差异比渲染 API 还要大。
3.2 事件命中测试从 O(n) 到四叉树
事件要落到正确的控件上,就得做命中测试。之前是直接遍历所有可见控件,判断鼠标点是否在控件矩形内。界面控件少还好,控件一旦上百,这个函数就成了性能热点。尤其滚动区域里,事件每帧都要测多次,浪费明显。
这轮我把命中测试改成了双阶段策略:先对静态区域构建四叉树,把树深度控制在 5 层左右,每个叶子最多挂 8 个控件;动态区域(拖拽、弹窗、动画中)不参与四叉树,直接线性测试。查询时先测试四叉树,再叠加动态区域,得到最终候选控件列表。
实际测试里,一个含 1200 个控件的复杂表单页,命中测试单次耗时从 2.1 毫秒降到 0.07 毫秒,这个数字带来的体感变化就是你拖拽滑块时不再有"黏滞感"。核心优化就是:UI 里绝大多数控件是不动的,没必要每帧为它们付线性的遍历成本。
3.3 Windows、macOS、Linux 三端:同一份代码,三处不同的崩溃
跨平台 UI 库最常见的问题不是 API 不会调,而是每个平台都有各自的"小性格"。举三个我实际遇到的例子,每个都耗费半天以上。
Windows 上,DPICHANGED 消息处理只更新了逻辑尺寸,忘了同步字体渲染的缩放系数,结果窗口从 96 DPI 拖到 144 DPI 时,所有文字模糊,按钮位置错位。后来统一到"物理像素 + DIP 比例"两层坐标,所有布局只在 DIP 坐标系算,只有在最终绘制阶段才乘上缩放系数,才彻底解决。
macOS 上,Retina 屏幕的视图坐标系和窗口坐标系不一致,我一开始直接在 NSView 的 drawRect 里拿 bounds.size 当像素尺寸,导致窗口内容只覆盖了左上角四分之一。答案是把视图的 wantsLayer 设为 YES,再通过 convertRectToBacking 拿真实的物理像素尺寸。
Linux 上则是 Wayland 和 X11 的窗口尺寸上报机制不同,Wayland 窗口在打开后要先等一个 configure 事件才能知道真实尺寸,如果你在创建阶段立刻布局,首次布局几乎必然跑在错误尺寸上。解决办法是把首次布局强制推迟到第一个尺寸事件到达之后。
这三类问题本质上都是"平台窗口系统对尺寸的定义不同",如果你也做跨平台框架,建议第一天就把"逻辑坐标、窗口坐标、物理像素坐标"三种坐标系统分开,不然后面改造成本奇高。
3.4 性能基线:从数字说起
这轮迭代接近结束时,我跑了一组基准数据,放在这方便大家代入:
| 场景 | 上一版 | 当前版 | 说明 |
|---|---|---|---|
| 100 个圆角卡片渲染 | 约 16ms / 帧 | 约 3.2ms / 帧 | 渲染路径重写 + 批处理 |
| 1200 控件表单命中测试 | 约 2.1ms / 次 | 约 0.07ms / 次 | 四叉树加速 |
| 中英文混排文本测量 | 首次约 9ms | 约 0.003ms / 次 | 两级缓存 |
| 启动加载 500 控件 | 约 920ms | 约 210ms | 布局树延迟构建 |
从数据可以直观看到,UI 卡顿从来不是单点问题,而是渲染、布局、命中测试、字体处理四个环节的合力。哪个环节掉链子,帧率都会被拖垮。优化这类系统,先建立基准测试账本,比凭感觉乱调重要得多。
4. 高密度使用 AI 编程的真实感受:到底提效还是添乱
4.1 AI 确实帮我省下最多时间的三类代码
先说 AI 在 C++ UI 库里真帮忙的地方。我自己实际用了大概小半年 Cursor + Claude,最爽的是这三类场景。
第一类是纯样板代码。UI 框架里每个控件都要实现 GetTypeName、ToString、属性序列化、反序列化。这类代码高度重复,思路清晰,AI 生成得非常准,我给一个控件的类定义,它能给我补全序列化函数,几乎没有 bug。
第二类是 CMake 和构建脚本。我自己写 CMake 属于能用但不愿意深究的水平,AI 对多平台宏、编译选项、依赖查找非常熟悉。比如让 AI 配置 Windows 静态库的运行时选项、macOS 的 framework 链接、Linux 的 pkg-config 挂接,比我自己翻文档快五倍。注意,它生成的 CMake 第一次不一定跑得通,但错误信息给回去,它能很快自我修正,这比人肉 debug 舒服多了。
第三类是单元测试骨架。让它基于函数签名生成测试用例的骨架,我再填测试数据和断言边界,确实比从空文件写起快很多。后面第 5 节我会专门展开讲 AI 测试开发的实际效果和局限。
4.2 AI 在 C++ 里反复翻车的四个场景
AI 帮手也有翻车翻到我血压升高的时候,而且全都集中在 C++ 最典型的几个难点上。
第一,模板元编程。C++ 模板本来就是一门"函数式 + 编译期求值"的语言,AI 生成的 SFINAE、constexpr 分支,看起来头头是道,编译也能过,但运行时行为可能完全错误。有一次我让它实现 constexpr 字符串哈希,它写出的版本在我测试长度超过 16 的字符串时静默返回错误结果,连警告都没有,这种 bug 在生产环境很难排查。
第二,生命周期与指针。C++ 里最容易被 AI 坑的就是裸指针、shared_ptr、weak_ptr 的混合使用。AI 处理 this 指针逃逸、回调生命周期延长、悬空引用这些问题时表现很差,它会自信地写一个成员函数捕获 this 给回调,完全不考虑调用时对象是否还存活。这种代码 review 不仔细,线上崩溃当天就能到。
第三,API 幻觉。AI 特别擅长一本正经地虚构接口。我问它某个标准库有没有 std::string::insert_range_at,它斩钉截铁说存在,还写好了示例。实际上 C++23 标准里根本没有这个 API。凡是 AI 生成的代码里出现了我没见过的函数名,我现在第一反应不是去查文档,而是直接把那段代码高亮,用编译器再验一遍。
第四,并发与状态一致性。C++ 的线程模型本身就很微妙,再加上 UI 这种对线程亲和性极度敏感的系统,AI 生成的并发代码经常有数据竞争。比如它建议我把 UI 更新丢到另一个线程,却没处理好与主线程的同步,界面闪烁和崩溃纷至沓来。
4.3 vibe coding 的诱惑:一次失控的"结构体优化"
Vibe coding 这个词最近特别火,意思是你把需求丢给 AI,说一句"大概的意思你能不能帮我实现",AI 唰唰写完,你连看都不看就提交。我在 kwik-ui 上就栽过一次。
当时我让 AI 优化布局节点的内存占用,它自作主张把一个存储宽高的 struct 拆成 union,理由是"反正 Flex 和 Anchor 不会同时使用"。从纯内存角度看它没错。但问题是布局过程中有很多地方同时访问 width 和 anchor.width 做中间计算,union 之后这些逻辑全乱套,得到一批莫名其妙偏移的控件,最终排查了两天才找到根因。
这个案例给我的教训非常直接:UI 代码本质上是确定性系统,任何"聪明的共享内存"都会引入隐性耦合。AI 只看到了局部的内存优化,看不到整个布局引擎对宽高的双重依赖。所以在 UI 领域,AI 生成的代码更要警惕它那些"看起来优雅"的微优化。
4.4 一套我自己在用、还算好用的 AI 提示词模板
我不想给你那种网上抄来抄去的万能提示词。给你一个我实际在 kwik-ui 里用了很多次的模板,核心思路是"给约束、给接口、给验证方式",把 AI 从"自由发挥者"降级成"按规格实现者"。
这种提示词让 AI 从"写代码"变成"做作业",输出质量明显稳定很多。它还是会错,但至少不会跑题,也不会对着一个不存在的标准库接口大展拳脚。如果你想用 AI 写 C++,强烈建议按这个思路设计你的提示词:给接口边界,给约束,给验证路径,这三样比任何花哨的提示技巧都管用。
5. AI 辅助 C++ 开发的正确姿势与边界思考
5.1 给 AI 装上安全带的四个要求
用 AI 写 C++ 不是不行,但必须立规矩。我现在给团队定的四条要求很简单:第一,AI 生成的代码必须逐行 review 后才能提交;第二,涉及内存生命周期、并发、模板元编程的 AI 代码必须加对应单元测试;第三,AI 如果写了不经意的"优化",默认拒绝,除非有基准测试证明收益;第四,AI 提示词里给出的约束必须可验证,不接受"我觉得这样应该可以"的口头理由。
有人会觉得这四条太严,拖慢节奏。但实际上,用 AI 写代码最快的路径不是让 AI 一次写好,而是"AI 出初稿 + 人类快速修正边界条件",因为 C++ 的错误大量集中在边界条件里。我做了一个小统计:AI 帮我生成的布局代码,第一版能直接编译运行的占比不超过四成;一旦我加上边界检查和单元测试,这个比例能到九成。这跟很多人想象中"AI 一次写对"的体验差别很大,但确实是硬碰硬的经验。
5.2 AI 测试开发的实际效果:有惊喜,但要盯紧
热搜词里频繁出现"AI 测试开发"这个词,我就拿 kwik-ui 的布局引擎来当案例说说。
第一次让 AI 写布局引擎的单测,我预期它会输出几百行用例。它确实输出了很多,还带随机生成参数的测试,我一度以为捡到宝。跑完才发现,它的随机参数范围设置有问题,坐标值集中在正数小范围,根本没有覆盖负 margin 和超大 width 的场景。我改掉了生成器之后,它才产出几个有价值的崩溃用例。
更有价值的用法是反过来的:把失败场景喂给 AI,让它解释可能原因。有一次布局递归爆栈,我贴了崩溃栈,AI 很快指出是 flex-grow 在递归布局时可以被子节点的子节点无限放大,导致栈溢出。这个推理我花了一个小时没看出来,它 30 秒就分析出来了。所以我现在对 AI 测试开发的态度是:AI 适合当"测试问题分析器",不太适合当"测试生成器";让它分析一个已定位的 bug,比让它凭空造测试更靠谱。
5.3 什么人适合用 AI 编程:门槛警告
我必须说点泼冷水的话。AI 编程对本来就是熟练程序员的人是放大器,对刚入门的初学者却是陷阱。为什么?因为 AI 生成的 C++ 代码最大的问题不是它编译不过,而是它编译过了、结果还错了,并且它的错误非常隐蔽。如果你没有足够经验去判断这个结果是否符合预期,你根本不知道它错在哪里。
举个例子,让 AI 写一个"冒泡排序算法 C++ 版本",大概率是对的,怎么写都不会出事。但让它写"带自定义比较器的 flex-wrap 算法"这类 UI 逻辑,它可能把排序稳定性、间距一致性都搞混。初学者拿到这样的代码,第一反应是"能用就行",等到 UI 某些边界情况跑坏,才意识到问题,但那时候已经不知道是哪一层出了问题。
所以我的建议是:如果你还在学习 C++ 的初级阶段,AI 可以用来解释概念、生成练习题,但不要让你写的每行代码都从 AI 来。你要是让 AI 帮你"写作业"、你来"交作业",迟早有一天会在面试或项目里被极限拷打。
5.4 建立"AI 只能当副驾"的工作流
我这几个月摸索下来的工作流可以总结成七个字:AI 草稿,我来定稿。具体的循环是这样的:
每次改动先描述清楚问题、接口、约束,让 AI 生成初始实现;然后我逐行读代码,重点关注函数签名是否存在、生命周期是否安全、是否引入了隐藏依赖;接着我补测试,尤其是边界输入测试;最后我合并代码,并标记哪些区域"由 AI 生成,后续小心"。这套流程一跑起来,AI 的效率才能真正转化为我的效率,而不是制造一堆让我半夜爬起来修的定时炸弹。
6. 下阶段路线图与给同行的建议
6.1 接下来 kwik-ui 要做的三件事
这轮迭代完成之后,我准备让 kwik-ui 进入"可用状态"的门槛。最近最想推进的是三件事:响应式布局、动画系统、无障碍支持。
响应式布局跟 FlexBox 不一样,FlexBox 解决的是同一屏内元素的分布,而响应式布局关心的是不同窗口尺寸下布局如何切换。kwik-ui 计划引入断点机制,类似 CSS 媒体查询的最小实现。你设一个 breakpoints 映射,布局引擎在窗口尺寸变化时自动切换布局参数,不需要业务代码手动监听。
动画系统目前是 UI 库最薄弱的环节。我准备结合重绘机制,做出 keyframe + easing 曲线的基本框架。注意 UI 动画和游戏动画不一样,UI 动画必须和输入事件可中断、可反转,否则弹窗开合就会显得很死板。为了让动画不掉帧,动画节点必须放到动态渲染层,避免脏矩形区域外的静态节点反复重绘。
无障碍支持在国内技术博客里聊得少,但作为 UI 库这是绕不开的。键盘焦点遍历、读屏软件需要的控件角色描述、焦点可见性,都要在一开始就设计进来,不然后面返工成本高到离谱。这算是我给自己挖的一个大坑吧。
6.2 一份给 C++ UI 开发者的资料清单
很多人问我 C++ UI 怎么系统学,我每次都推荐这几份资料。它们不一定直接教你怎么做 UI 库,但能把底子打扎实。
第一,Dear ImGui 的绘图函数实现。它的顶点缓冲、索引缓冲、纹理图集组织方式,是我见过最容易理解的 GPU 绘制范例,直接啃代码比看抽象文章快得多。第二,The Cherno 的 C++ 系列视频。这个系列对现代 C++ 的工程化细节讲得非常细,尤其是区分左值、右值、移动语义的那几集,对写 UI 框架里的状态更新特别有用。第三,Figma 的布局文档。Figma 的 auto layout 基本就是 FlexBox 思想的优秀产品化实现,理解它的设计逻辑,再去实现 FlexBox 会顺手很多。第四,Harfbuzz 官方文档加上 FreeType 的图文教程,这两个是字体渲染绕不开的宝库。最后,Chrome 团队的 rendering 工程博客,他们讲解从 HTML/CSS 到最终像素的完整管线,对理解 UI 渲染的性能边界非常关键。
6.3 做 UI 库这两年,我对"确定性"的一些体会
如果你问我这轮迭代最大的收获是什么,我会说不是某个优化算法,而是更深刻地理解了 UI 系统对"确定性"的执着。UI 代码和业务代码不同,用户看到的永远是状态变化的结果,一点不确定都会变成视觉上的闪烁、错位、卡顿。AI 生成的代码擅长的恰恰是连通性与生成性,它能在海量组合里找到一种看似合理的路径,但 UI 系统要求的是"在任何状态、任何输入下都表现正确",这恰恰是 AI 最不擅长、最容易被它"自信一笔带过"的地方。
我自己现在用 AI 写 C++ 的状态是:宏观设计我坚持手写,微观样板我大胆交给 AI。架构、生命周期、状态机这类决定系统稳定性的骨架,我一定会亲自盯着;序列化、CMake、测试用例骨架这类边界明确的部分,AI 能做我的得力助手。这个比例关系,可以说是我踩了无数坑之后总结出来的最舒适工作方式。
最后分享一个实操小技巧。每次合并 AI 生成的 C++ 代码前,我都会先 grep 一遍这些危险符号:裸 delete、reinterpret_cast、new 后不跟 shared_ptr、以及任何我没见过的标准库接口。一次 grep 花费半分钟,能过滤掉 80% 的坑。如果你也在用 AI 写 C++,不妨从这个小习惯开始。