落地每网格双缓冲图层 + 后台绘制架构:全局宽缓冲区重构为每网格信号图层(宽x3、只含基底),m_layerFront/m_layerBack 双缓冲 + m_layerWorker 后台线程,前台只做位图坐标偏移贴图不再绘制波形;前台图层就绪即用、内容稍旧也照用并交后台补画后交换;含不收敛保护、前台优先让路、固定加锁顺序、渲染状态完整还原;新增 BackgroundLayerRenderCount 验证后台绘制;深放大区间各交互 0.7~8.7ms/帧

This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-12 03:17:31 +08:00
1 parent e22749b57a
commit f97c4cf289
3 files changed
+667 -338

No files matched your search

+38 -8
View File
@@ -449,15 +449,44 @@ Documents/ 项目文档(含本文件)
- 剩余的几百通道差异集中在**密网格/密波形**上,是"宽基底按整数像素平移(≤0.5px 亚像素误差)"
的必然结果,肉眼不可辨;视界停下后的"结算"帧会回到**像素精确**。
#### 尚未完成(如实记录)
#### 「每网格双缓冲 + 后台绘制」架构(已落地,本轮同批提交)
用户要求的「每个网格独立双缓冲 + **后台线程绘制**」这一层**本轮未实现**,原因:
`PaintLane` 及其调用链直接读写大量 `PlotView` 成员(`m_lanes` / `m_area` / `m_plotRect` / 视界 /
多重缓存 / `m_decorRects` / `m_legendHits` …),把它搬到后台线程会与 UI 线程的 `paintOverlays`
**数据竞争**。要做需要把渲染器改造成「不依赖成员状态的独立渲染器」(把上述状态显式参数化、
缓存改为每线程私有),属于大重构,风险高。
**本轮先把"正确性"与"帧时间"问题解决**:现在平移/缩放期间的帧时间已降到 4~6ms(原 9~19ms),
且不再有拖影/残留/错位;后续可按用户方案逐步把单网格绘制搬到后台。
把原来**全局一张**「宽缓冲区」重构为用户提出的方案:
- **每网格一块「信号图层」**(`LaneLayer`):宽度 = 视界宽 ×3(左右各多画半屏),只含该网格基底
(底色 + 网格线 + 波形 + 派生曲线),**不含装饰**(Y 轴刻度 / 图例,它们锚定网格左缘、不随时间视界移动)。
- **双缓冲**:`m_layerFront`(前台只读)/ `m_layerBack`(后台填充)。后台画完整块后
`std::swap` 整体交换;**前台永远能拿到立即可用的像素**,后台正在绘制也不影响交互。
- **后台绘制**:`m_layerWorker` 线程(BELOW_NORMAL 优先级)按任务队列逐网格重画。
前台每帧只做:① 判断各网格图层是否就绪;② `BitsBlt` 把**就绪且跨度一致**的图层按坐标偏移贴到画面;
③ 画时间轴 / 派生叠加层 / 装饰 / 叠加层。**前台不再绘制任何波形**。
- **"未就绪就用旧数据应付"**:只要图层**跨度一致、能覆盖当前视界**,即使内容稍旧也照贴
(`LayersReadyForView()` 不比较内容指纹),内容由后台补画后交换 —— 于是样式/参数变化、
平移、滚动都不会退化成整幅重绘。只有"跨度变了 / 图层还没建"才落到整幅重绘一次以保证正确性。
- **不收敛保护**:若同一状态反复排任务仍与前台不一致(说明某参与指纹的状态没被正确还原),
超过 90 次即改为同步重建一次强行收敛,避免死循环。
- **前台优先**:后台在"前台最近 30ms 内绘制过"时主动让路(把任务放回队首,8ms 后再试),
避免与前台抢 `m_renderMutex` 反而更卡;交互停下后后台从容补画。
- **加锁顺序**:一律 `m_renderMutex` → `m_layerMutex`(后台与前台一致),避免 AB-BA 死锁。
- **渲染状态还原**:图层渲染会临时改写 `m_area` / `m_plotRect` / 视界 / 网格矩形,
结束后必须调用 `BuildLaneVisuals()` 重算 `m_laneContentHeight` 并还原 `LaneScroll` 与各网格幅值范围
—— 否则内容指纹与前台永远不一致 → 图层被判定"永远过期" → 死循环(本轮踩过这个坑)。
验证(离屏,`perf.exe ... 6`,视界 ≈ **2.045ms 一大格**):
| 交互 | 帧平均 | 像素差(对比从零整幅渲染) |
| --- | --- | --- |
| 键盘左右方向键平移 | **3.5ms** | 3705 通道(≤0.5px 亚像素) |
| 键盘上下方向键滚动网格 | 0.7ms | **0** |
| 中键水平拖动 / 左键水平拖动 | 3.3 / 1.8ms | 357 / **0** 通道 |
| 中键纵向 / 左键纵向拖动 | 5.7 / 5.5ms | 72 通道 |
| 滚轮滚动网格 | 1.9ms | **0** |
| 缩放后再平移 | 4.6ms(缩放帧 5.0ms) | 549 通道 |
| 弱波增强预览 + 平移 | 8.7ms | 3590 通道(异步曲线发布时序) |
- 「背景图层绘制次数」计数可直接证明**单网格基底的绘制确实发生在后台线程**:一轮诊断累计 **18~45 次**;
前台只在做位图搬运。
- 跨进程验证:`BackgroundLayerRenderCount()` 为公开只读接口,`perf.exe ... 6` 结束时会打印总量。
---
@@ -477,4 +506,5 @@ Documents/ 项目文档(含本文件)
| 2026-10-11 | DeepSeek-V4.1-Flash | 宽缓冲改为**滚动式**(自贴图平移 + 只补画新暴露细条),消除"移出边界整幅重建"的周期性尖峰;缓冲只覆盖绘图区、时间轴前台绘制、亚像素误差用锚点限制在 ≤0.5px → 持续拖动平移约 6ms/帧、无尖峰(长距最大 10.5ms);静止帧逐像素 0 差异。残留:每次开始拖拽的首帧需新建缓冲(一次启动尖峰)。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 消除"开始拖拽"的首帧尖峰:平移首帧用**静态场景缓存初始化宽基底**(`InitWideBufferFromScene`,贴图 + 细条,不再从零重画 2×视界);启用带预览的实验性功能时**预建基底**。实测:平移拖动最大 8.5ms、弱波增强滑块最大 10.8ms(原分别 35/37.6ms),用户全部鼠标交互在该放大区间**全程 ≥60fps、无尖峰**;静止帧逐像素 0 差异。残留:非拖拽视界变化(键盘/工具栏)仍整幅重绘。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 消除 9.13 残留:**非拖拽视界变化(键盘/工具栏)也走宽基底快路径**(124.6ms→**8.6ms**),并用 `wideKey` 相等判定"视界已停止变化"触发一次**结算**整幅全质量帧以恢复像素精确;分支顺序固定为"静态贴图 → 宽基底 → 样式补丁 → 整幅",宽基底强制全质量抗锯齿且竖向刻度用前台跨度。新增像素对比模式验证:底座管线忠实(meanAbs 0.68/255),平移期仅 ≤0.5px 亚像素差;7 张静态/结算帧与 HEAD **字节一致**;平移中途帧不再有图例/刻度重影。 |
| 2026-10-12 | DeepSeek-V4.1-Flash | 落地用户提出的「各网格独立双缓冲区 + 后台线程绘制」架构:把全局宽缓冲区重构为**每网格信号图层**(宽×3,只含基底不含装饰)、`m_layerFront/m_layerBack` **双缓冲** + `m_layerWorker` **后台线程**(前台只做 BitBlt 坐标偏移,不再绘制波形);前台"图层就绪即用现成像素、内容稍旧也照用,交后台补画后交换";加不收敛保护、前台优先让路、固定加锁顺序(render→layer)、渲染状态完整还原(含 `m_laneContentHeight`/`LaneScroll`)。新增 `BackgroundLayerRenderCount()` 公开计数用于验证后台绘制确实发生(一轮诊断 18~45 次)。实测深放大区间(2.045ms 一大格)各交互 0.7~8.7ms/帧,像素差 ≤0.017(除亚像素平移),静态/结算帧像素精确。 |
| 2026-10-12 | DeepSeek-V4.1-Flash | 修复用户报告的「缩放后平移出现拖影/残留/错误元素」与「2ms 一大格区间仍然卡顿」。根因:①网格纵向滚动量未参与任何内容指纹→滚动时把旧位置像素贴回且每两帧触发一次整幅重绘(≈8fps);②`FindStyleOnlyLanes` 未校验视界→视界变化后仍走局部补丁;③宽基底用含装饰的整幅画面初始化→装饰随平移重影;④装饰缓存的矩形列表与坐标原点两帧不同源→装饰消失/黑块/错位。修复后交互帧 9~19ms → **4.5~6ms**,纵向拖动残留 2 万+ 通道 → 72 通道(亚像素量化),静态/结算帧像素精确。新增 `perf.exe ... 6` 诊断模式(逐帧计时 + 逐像素对比 + 差异包围盒)。未实现:每网格双缓冲后台渲染(需先把 `PaintLane` 改造成无状态渲染器,已在文档 9.15 说明)。 |