修复悬停高亮变长方形色块/联动失效 + 信号列表动画卡顿真因(行位图缓存)
一、悬停高亮恢复"整条曲线发光"(上一轮裁成鼠标附近一段 ⇒ 满幅方波上就是个长方形色块) 光晕的语义是"整条曲线一起发光",对编码器这种满幅方波,一列包络本来就跨满网格高, 裁成 144px 宽 × 满高的窗口在屏幕上就是一个像选区框的色块(用户 2026-10-18 第二轮报告)。 修法(PlotViewPaint.cpp): - PaintSignalHaloFor 恢复**整条**几何(包络带 FillPath / 折线 DrawLines); - 新增 PaintSignalHaloCached:光晕几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标无关** ⇒ 渲染一次进离屏位图,之后每帧只做一次 AlphaBlend(源位图预乘 alpha,用 256×256 查表); - 缓存键与 AcquirePolyline 同源,**不含网格屏幕位置**(滚动网格只按新位置贴同一张位图)。 210ms/帧(整条描边)→ 一次性 15ms 渲染 + 每帧 ~2ms 贴图。 halocheck 校验:悬停必命中(60/60 位置)、光晕贴合带宽(outsideBand=0)、不越出网格、 bbox 覆盖整条曲线(不是中段方块)、列表行悬停联动仍生效。 二、信号列表动画卡顿的三个真因(探针 panelstutter:129 个可见网格 + 反复"展开→切组复选框→收起") 1) 面板每帧重画 ~36 行 × ~0.35ms(十来个 GDI+ 原语)⇒ panel.Paint 24~35ms; 2) 动画帧里仍在预热网格图层:每帧重建 1 个网格图层,2500 万采样信号 10~30ms ⇒ 帧被拉长; 3) 勾选分组会把上百个**离屏**网格排进待重画队列,随后每帧补 1 个 ⇒ 数秒持续掉帧。 修法: 1) 行位图缓存(SignalPanel::PaintRowCached):按"行的可见内容"做键,整行渲染进离屏 DIB, 命中时一次 BitBlt。panel.Paint 24~35ms → 5~6ms。 ⚠️ 必须 DIB + 内存 DC(gd::Bitmap 上 GDI 会关闭 ClearType,整列文字退化); ⚠️ 行内几何在 RowRect 里平移(GDI 文本不认 GDI+ 世界变换,否则文字画到小位图之外); ⚠️ 渲染到行位图时不按列表矩形裁剪,裁剪放到贴图时的 DC(GDI 不认 GDI+ 裁剪, 必须自己 SaveDC + IntersectClipRect,折起动画时行不许画到组名上); ⚠️ 缓存上限 192 行(≈10MB)。静态画面逐像素一致(面板区域 54/275200 不同)。 2) 新增 PlotView::SetAnimationBusy:UI 动画期间不预热网格图层(队列保留,动画结束后继续)。 3) 待重画队列只保留可视 + 预取带内的网格;RollWideBuffer 不再登记离屏网格。 实测:展开 p50 35ms → 13ms、收起 33ms → 13ms、动画帧内图层重建 56 次 → 0 次。 三、试过并放弃(避免以后重走) - ExtTextOutW 替代 DrawTextW:快 2~4 倍但光栅化不同(面板 10% 像素差异)⇒ 观感变化,放弃; - 用 gd::Bitmap 做行位图:文字丢失 ClearType,放弃。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/panelstutter_main.cpp(8/8)、halocheck_main.cpp(16/16); - 回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、gridanim 93/93、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、panflash 19/19、stress 6/6; - make release / make debug 零警告(TARGET_FILE_NAME=TargetCheck/TargetCheckD)。 文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:高亮语义不得为性能让步、 GDI 文本的三个隐形陷阱、面板卡顿先量"每行成本×可见行数"与"本帧是否顺带做绘图区重活"、 动画帧内绝不做可延迟的整帧级工作)。
This commit is contained in:
1 parent
0efaa8c0ca
commit
24f0bf849d
11 files changed
+853
-197
No files matched your search
@@ -6112,3 +6112,85 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
|
||||
2. **任何"每帧随鼠标变化"的绘制都必须 O(光标附近)**,不能 O(整条曲线);否则一次悬停就能把 60fps 打成 5fps。
|
||||
3. **性能与正确性判据必须是"同一次运行内的相对量"**:绝对 ms / 绝对墨迹都会随机器状态和视界内容变化而误报。
|
||||
4. 平移正确性的唯一可信判据是"与同一视界的整幅重绘对比",而不是"与某个固定基准帧对比"。
|
||||
|
||||
---
|
||||
|
||||
## 本轮(2026-10-18 第二轮):悬停光晕恢复"整条发光" + 信号列表卡顿真因(行位图缓存)
|
||||
|
||||
用户报告:① 上一轮的光晕修复把高亮变成了"像选区放大的长方形色块"、联动也失效;② 信号选择区
|
||||
(**只有文字**的面板)滚动/展开动画依然卡顿,反复"展开→切组复选框→收起→切回"必现。
|
||||
|
||||
### A. ① 的真因:上一轮把光晕裁到鼠标附近 ±72px
|
||||
|
||||
上一轮为了让"鼠标划过巨型信号"不卡死,把光晕塞进 `kHaloWindowHalfWidth=72` 的窗口。
|
||||
但光晕的**语义是"整条曲线一起发光"**:对满幅方波(编码器)来说,一列包络本来就跨满网格高,
|
||||
裁成一个 144px 宽 × 满高的窗口 ⇒ 屏幕上就是一个**长方形色块**,看起来像选区框。
|
||||
|
||||
本轮:`PaintSignalHaloFor` 恢复**整条**几何(包络带 `FillPath` / 折线 `DrawLines`),
|
||||
代价改由 **`PaintSignalHaloCached`** 承担:
|
||||
- 光晕的几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标位置无关** ⇒ 渲染一次进离屏位图;
|
||||
- 每次贴图只做一次 `AlphaBlend`(源位图**预乘 alpha**,见下);
|
||||
- 键与 `AcquirePolyline` 同源(`PolylineCacheEntry` 的键字段 + 样式字段),
|
||||
**不含网格屏幕位置**(滚动网格只需按新位置贴同一张位图)。
|
||||
|
||||
性能:210ms/帧(描边整条)→ **整条包络带填充一次 15ms + 每帧贴图 ~2ms**;
|
||||
`halocheck` 校验 `outsideBand=0`、`outsideLane=0`、bbox 覆盖整条曲线(不是中段方块)。
|
||||
|
||||
### B. ② 的真因有三个,都不是"面板没优化"
|
||||
|
||||
用新增探针 `panelstutter`(129 个可见网格 + 反复"展开→切组复选框→收起")量出来的:
|
||||
|
||||
1. **面板每帧重画 ~36 行,每行 ~0.35ms**(十来个 GDI+ 原语)⇒ `panel.Paint` **24~35ms**。
|
||||
一行内容在动画期间**一个像素都没变**,变的只是 y 坐标。
|
||||
2. **动画帧里还在预热网格图层**:`WarmLaneLayersStep()` 每帧重建 1 个网格图层,
|
||||
单个 2500 万采样信号 **10~30ms** ⇒ 动画帧被硬生生拉长(用户看到的"跳帧")。
|
||||
3. **勾选一个分组会把上百个离屏网格排进待重画队列**,随后每帧补 1 个 × 10~30ms ⇒
|
||||
**数秒的持续掉帧**(`RollWideBuffer` 对离屏网格也 `QueueLaneRebuild`)。
|
||||
|
||||
修法:
|
||||
1. **行位图缓存**(`SignalPanel::PaintRowCached`):按"行的可见内容"做键,把整行渲染进
|
||||
离屏 DIB,命中时一次 `BitBlt` 贴出。`panel.Paint` **24~35ms → 5~6ms**。
|
||||
- ⚠️ 位图必须是 **DIB + 内存 DC**,不能用 `gd::Bitmap`:GDI 在 GDI+ 位图的临时 HDC 上
|
||||
**会关闭 ClearType**(整列文字退化成灰度抗锯齿、观感变细)。
|
||||
- ⚠️ 行内几何必须**在 `RowRect` 里平移**(`m_rowRenderActive`),不能用
|
||||
`TranslateTransform`:GDI 文本不认 GDI+ 世界变换 ⇒ 文字会画到小位图之外(实测:只剩复选框与色块)。
|
||||
- ⚠️ 渲染到行位图时**不能按列表矩形裁剪**(`m_rowClipRender`),裁剪留到贴图时的 DC 上:
|
||||
`GetHDC()` 出来的 DC **不继承 GDI+ 裁剪**,必须自己 `SaveDC + IntersectClipRect`(折起动画时
|
||||
行要滑进组名栏、不许画到组名上)。
|
||||
- 缓存数量上限 192 行(≈10MB),超过即整体作废。
|
||||
- 静态画面**逐像素一致**(面板区域 54/275200 像素不同,最大差 75 —— 滚动条滑块抗锯齿)。
|
||||
2. **UI 动画期间不预热网格图层**(`PlotView::SetAnimationBusy`,由 `Application::Render` 传入):
|
||||
预热被"推迟"而不是取消(队列保留),动画结束后按既有预算继续。
|
||||
3. **待重画队列只保留可视 + 预取带内的网格**(`RenderPendingLaneRebuilds` 丢弃离屏项);
|
||||
`RollWideBuffer` 也不再登记离屏网格 —— 它们真正进入视区时会按新视界重新登记。
|
||||
|
||||
实测(`panelstutter`,同一台同一次运行):展开动画 **p50 35ms → 13ms**、收起 **33ms → 13ms**、
|
||||
面板绘制 **24~35ms → 5~6ms**、动画帧里"重建网格图层"次数 **56 → 0**。
|
||||
|
||||
### C. 试过但放弃的两条路(避免以后重走)
|
||||
|
||||
- **`ExtTextOutW` 替代 `DrawTextW`**:约快 2~4 倍,但**光栅化结果不同**(面板区域 10% 像素差异、
|
||||
亚像素边缘差 1px)⇒ 全体界面文字观感变化,放弃。
|
||||
- **用 `gd::Bitmap` 做行位图**:文字丢失 ClearType(见 B.1)。
|
||||
|
||||
### D. 验证(全部离屏,不占用鼠标/键盘/屏幕)
|
||||
|
||||
- 新增 `panelstutter`(8/8):展开/收起动画帧不重建网格图层、动画 p50 ≤ idle+12ms、
|
||||
行悬停仍会重绘该行(行缓存随悬停失效)。
|
||||
- 新增 `halocheck`(16/16):悬停必命中(60/60 采样位置)、光晕沿整条曲线且贴合带宽、
|
||||
列表行悬停联动、单信号满高场景耗时受控。
|
||||
- 回归:`verify` 49/49、`panelanim` 158/158、`hoverdrag` 20/20、`gridanim` 93/93、
|
||||
`lanescroll` 23/23、`datacheck` 69/69、`edgecheck` 46/46、`errcheck` 19/19、
|
||||
`panflash` 19/19、`stress` 6/6。
|
||||
- `make release` / `make debug` 零警告(`TARGET_FILE_NAME=TargetCheck/TargetCheckD`)。
|
||||
|
||||
### E. 教训(新增规则)
|
||||
|
||||
1. **"高亮提示"的语义是"整条曲线发光"**:任何把高亮裁成"鼠标附近一段"的做法,在满幅信号上
|
||||
都会退化成"长方形色块"(用户一眼就看出不对)。性能要用**缓存**解决,不能改变语义。
|
||||
2. **GDI 文本的三种"隐形陷阱"**:① 不认 GDI+ 世界变换(要平移几何);② 不认 GDI+ 裁剪区
|
||||
(要自己 `IntersectClipRect`);③ 在 GDI+ 位图的临时 HDC 上**关闭 ClearType**
|
||||
(要改用 DIB + 内存 DC)。
|
||||
3. **"面板只有文字却卡顿"多半不是文字本身的锅**:先量"每行成本 × 可见行数",
|
||||
再量"这一帧是否顺带在做绘图区的重活"(图层预热 / 队列排空)。
|
||||
4. **动画帧里绝不做可延迟的整帧级工作**(图层重建、队列排空);这类工作一律推迟到动画之后。
|
||||
Reference in new issue
Block a user