平移不再阻塞前台刷新;弱峰增强逐信号参数;浮动面板加入层序
一、平移:前台刷新线程永不阻塞(解决"按住键拖拽时质量下降/断线/周期性卡顿")
- RollWideBuffer 不再整块重画:逐网格整数像素平移 + 只补细条;内容指纹过期或锚点离视界
过远的网格只登记到 m_layerRebuildPending 并仍按现有像素平移(位置照样跟随视界)。
- 新增 RenderPendingLaneRebuilds(budget):Paint 每帧最多重画 1 个网格,其余留到后续帧;
队列非空时 PostMessage(kMessagePlotDerivedReady) 请宿主再送一帧,直到补齐。
- BlitWideBuffer 改为局部贴图:把 BitBlt 夹到图层可用区间,图层没覆盖到的部分留空由底色
填充(旧实现直接跳过整块,导致网格瞬间变空 = "断线")。
- "结算"(整幅全质量精确帧)触发条件收紧为三者同时成立:内容与视界同上一条快路径帧、
m_dragMode == NONE、全部图层就绪。拖拽期间不再结算,消除"宽基底快路径 / 整幅重绘(关AA)"
每两帧振荡造成的质量下降与周期性卡顿。
- 新增 LaneLayerReady(i) / LanesAllReadyForView();原 LayersReadyForView() 更名
LayersStructurallyReady()(只看结构,用于"跨度/布局变化必须整批重建一次")。
- 实测(.preflight/panperf,VERY_BIG_DATA 5 网格深放大,每帧平移 60px):平移 40 帧
中位 6.2ms / p90 7.2ms / max 7.6ms;按住键且视界停在边界 12 帧中位 0.29ms(无整幅重绘)。
二、弱峰增强:改为逐信号参数(对齐均值滤波)
- Lane::WeakPeakItem 由 using = MeanFilterItem 改为独立结构体
{ ref, levelPercent(0-200, 默认 150), iterations(1-10, 默认 1) };删除网格级
Lane::weakPeakLevel / weakPeakIterations。
- 面板改为与均值滤波完全同构:每个被勾选信号一行"名称 + 复选框",第二排两个输入框
(增强等级 % / 迭代次数 N),底部"构建为新信号";删除网格级两个滑块及其常量、
WeakPeakSliderHit、PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS、m_peakDragLane、
EdgePanelLayout::slider/sliderIterations。
- 新增 PlotEditTarget::PEAK_LEVEL_PERCENT / PEAK_ITERATIONS,EndEdit 中分别夹到 0-200 / 1-10。
- 指纹/快照/预览曲线/光标胶囊/构建新信号全部改为逐信号参数(同一网格内不同信号可用不同强度,
且"预览"与"构建"逐位一致);WeakPeakItem 不再是 MeanFilterItem,purgeRefs 改为泛型 lambda。
三、浮动面板:层序(z-order)与命中修复
- 新增 m_panelOrder(队尾 = 最上层):SyncPanelOrder 保留已有序、新面板追加到最上层;
TopPanelAtPoint 从最上层往下取第一个命中;BringPanelToFront 在点击面板时把它置顶。
- 绘制改为按 m_panelOrder 自下而上;PanelGlobalKey 混合层序(层序变化即整层重画);
QueryCursor / OnMouseMove 的"是否在面板上"也走 TopPanelAtPoint。
- 修复"上层面板的 ✕ / 标题栏被下层参数输入框抢走,导致上层面板关不掉/拖不动"。
四、测试
- uitest 新增 [AG]:层预算(每帧只重画 1 个网格、剩余排队、最终补齐并全部就绪)、
局部贴图(只覆盖视界一半时左侧被贴、右侧保持哨兵色)、面板层序(上层 ✕ 命中上层、
下层不受影响、点击下层置顶、TopPanelAtPoint 随之改变、新开面板在最上层)。
- [X] 改为逐信号参数(含两个输入框就地编辑:写入该信号自己的条目、越界夹取),
移除已不存在的滑块断言。
- 新增离屏探针 .preflight/panperf(平移帧耗时 + 按住键静止帧耗时)。
- make all 零警告;uitest 388/388 通过;panperf PASS;layercheck 保持像素一致。
This commit is contained in:
1 parent
da83dc2987
commit
1ddbc37b78
5 files changed
+706
-544
No files matched your search
+95
-8
@@ -510,6 +510,7 @@ Documents/ 项目文档(含本文件)
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash | 修复用户报告的「缩放后平移出现拖影/残留/错误元素」与「2ms 一大格区间仍然卡顿」。根因:①网格纵向滚动量未参与任何内容指纹→滚动时把旧位置像素贴回且每两帧触发一次整幅重绘(≈8fps);②`FindStyleOnlyLanes` 未校验视界→视界变化后仍走局部补丁;③宽基底用含装饰的整幅画面初始化→装饰随平移重影;④装饰缓存的矩形列表与坐标原点两帧不同源→装饰消失/黑块/错位。修复后交互帧 9~19ms → **4.5~6ms**,纵向拖动残留 2 万+ 通道 → 72 通道(亚像素量化),静态/结算帧像素精确。新增 `perf.exe ... 6` 诊断模式(逐帧计时 + 逐像素对比 + 差异包围盒)。未实现:每网格双缓冲后台渲染(需先把 `PaintLane` 改造成无状态渲染器,已在文档 9.15 说明)。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **跨设备记忆合流**:把起源设备(本机)的详细工程记忆**并入本文件**(同一记忆文件,不再另建 `agent_mem_origin.md` 等旁支文件),新增 §11(跨设备协作须知)与 §12(起源设备详细记忆原文)。远端 `gitea/main` 已快进合并到本机(`7608e85` → `f97c4cf`,**16** 个提交)。本机 MinGW-w64 GCC 15.2.0 (UCRT) 下 `make release` 通过。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复「缩放后平移出现严重绘制错误/波形抖动/前台被拖慢」**:根因是后台图层线程会改写共享渲染状态(保存→改写→恢复`m_area`/`m_plotRect`/视界/网格矩形),与 UI 线程无锁的平移/命中测试相冲 → 视界被弹回、网格错位;叠加"全局锚点 + 逐网格内容"导致各网格时间错位。修法:**移除后台图层线程**(改为 UI 线程同步维护)、**逐网格独立锚点与滚动**(补细条用该图层自己的 `wideMin`)、跨度变化时同步 `RebuildWideBuffer()`。同时把「弱波增强」改名「**弱峰增强**」并改为 **0~200% 分段插值**(0% 完全不动 / 100% 到较温和一侧 / 200% 完全拉平;新 1.5 = 旧 0.5)。离屏验证:居中图层 vs 整幅重绘 **5 网格逐像素 0 差异**、平移帧对齐 ≤1px、24 次竞态检测 0 覆盖;测试套件 **380/380 通过**。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复"按住键拖拽平移时波形质量下降 / 断线 / 周期性卡顿"与"浮动面板互相覆盖时交互错位"**,并把弱峰增强改为**逐信号参数**。① 平移:`RollWideBuffer` 不再整块重画(只平移+补细条,内容过期/锚点过远只入队),新增 `RenderPendingLaneRebuilds(budget)`(**每帧最多重画 1 个网格**,其余下帧)+ `BlitWideBuffer` **局部贴图**(图层没覆盖的部分留空,不再整块跳过);"结算"整幅精确帧**仅限非拖拽且全部图层就绪**(消除拖拽中每两帧的整幅重绘/质量振荡)。实测 `VERY_BIG_DATA` 5 网格深放大平移:中位 **6.2ms**、max 7.6ms(无尖峰);按住键且视界停在边界时 **0.29ms/帧**。② 弱峰增强:`WeakPeakItem` 改为携带 `levelPercent(0–200)+iterations`,面板改为与均值滤波同构的**逐信号两个输入框**(删除网格级两滑块),哈希/快照/构建/预览全部改为逐信号。③ 浮动面板:新增 `m_panelOrder` 层序,**命中从最上层往下**、点击 `BringPanelToFront`、新面板置顶;修复"上层 ✕ / 标题栏被下层参数框抢走"。离屏测试 **388/388 通过**。 |
|
||||
|
||||
---
|
||||
|
||||
@@ -542,14 +543,15 @@ Documents/ 项目文档(含本文件)
|
||||
5. ⚠️ **图层渲染一律在 UI 线程内同步完成**(`Paint` 全程持有 `m_renderMutex`)。**绝不要把图层渲染搬到后台线程**:它会临时改写 `m_area`/`m_plotRect`/`Tab().ViewMin()/ViewMax()`/各网格矩形,而这些状态被 UI 线程的消息处理**无锁读/写**(平移读视界、命中测试读网格矩形),后台的"保存→改写→恢复"会**覆盖用户正在进行的平移**(视界弹回)并让命中测试错位 —— 这就是 2026-10-12 移除后台图层线程的原因(详见 §12 本轮)。
|
||||
6. 每个图层记录**自己的**时间映射 `LaneLayer::wideMin`;滚动与贴图必须**逐网格**按各自 `wideMin` 计算,**绝不共用一个全局锚点**(否则各网格被平移不同的真实距离 → 网格间时间错位)。补细条时必须传 `layer.wideMin`(不能用"当前视界"的映射,否则有 ≤0.5px 接缝与抖动)。
|
||||
7. 加锁顺序**恒定** `m_renderMutex`(recursive)→ `m_layerMutex`(图层不再有跨线程访问,但顺序约定保留)。
|
||||
6. **GDI 绘制不受 GDI+ `SetClip` 约束**:`UiText`(`DrawTextW` / `ETO_CLIPPED`)与 `UiFillRect` 的 GDI 快路径(不透明且面积 ≥ 20000px²)都会画出裁剪区;宽矩形 / 文本必须**自己夹取**,GDI 批绘必须显式传 clip。
|
||||
7. `SceneContentHash` 必须覆盖 `Tab().LaneScroll()`(网格纵向滚动量)与 `SelectedRef()`;`FindStyleOnlyLanes()` 必须**逐项比较 `SceneCacheKey`**(`SceneGeometryHash()` 刻意不含视界,只比它不足以判定"视图没变")。
|
||||
8. 随鼠标变化的绘制(悬停高亮 / 光晕 / 图例高亮 / 光标读数 / 选区 / 提示 / 面板悬停)**必须同时**加在"缓存贴图快路径"与"整幅重绘路径"两侧。
|
||||
9. **不要为波形构建 / 绘制引入多线程**;已落地的多线程只有:每网格图层后台线程(`m_layerWorker`,`BELOW_NORMAL`)、派生曲线线程池(`m_curveWorkers`)。任何"移动 / 释放采样"的操作前必须 `PauseDerivedAsync()`。
|
||||
10. MinGW 的 `swprintf` 按 C99:宽格式串里的 `%s` = **窄**字符串,宽字符串必须用 `%ls`(否则 "KB" 会被截成 "K")。
|
||||
11. 源文件名必须**全局唯一**(Makefile 按 basename 去重 + vpath 查找);新增目录要同时登记进 `CXX_INCLUDES_PATHS` / `CXX_SOURCES_PATHS`。
|
||||
12. `GdiplusStartup` 之后创建的 GDI+ 成员(如 `ColorPickerPopup` 的色域缓存 `gd::Bitmap`)必须在 `GdiplusShutdown` **之前**释放,否则退出时访问违例(实测退出码 `0xC0000005`)。
|
||||
13. 主题化弹出层禁止 `CreatePopupMenu` / `TrackPopupMenu` / `AppendMenuW`;一律用自绘 `PopupMenu` / `ColorPickerPopup`,且 `Open()` 后必须 `InvalidateRect`。
|
||||
8. ⚠️ **前台刷新线程绝不做“整批图层重绘”**:平移/拖动期间 `RollWideBuffer()` **只**做逐网格整数像素平移 + 补细条;内容过期 / 锚点过远的网格只登记到 `m_layerRebuildPending`,由 `RenderPendingLaneRebuilds(budget)` **本帧至多重画 1 个**,其余下帧继续(并 `PostMessage(kMessagePlotDerivedReady)` 请求重绘)。图层没覆盖到的部分 **`BlitWideBuffer` 只贴有像素的那一段**(其余留给底色,下帧补齐),**不得整块跳过**(跳过 = 网格变空 / “断线”)。
|
||||
9. ⚠️ **“结算”(整幅全质量精确帧)只能在 `m_dragMode == NONE` 且全部图层就绪时发生**:否则按住键拖拽时会在“宽基底快路径”与“整幅重绘(拖拽时关 AA)”之间每两帧振荡 → **波形质量轮流变差 / 抖动 / 周期性卡顿**(用户报告的“按住拖拽时显示效果下降、出现断线,松手才恢复”)。
|
||||
10. **GDI 绘制不受 GDI+ `SetClip` 约束**:`UiText`(`DrawTextW` / `ETO_CLIPPED`)与 `UiFillRect` 的 GDI 快路径(不透明且面积 ≥ 20000px²)都会画出裁剪区;宽矩形 / 文本必须**自己夹取**,GDI 批绘必须显式传 clip。
|
||||
11. `SceneContentHash` 必须覆盖 `Tab().LaneScroll()`(网格纵向滚动量)与 `SelectedRef()`;`FindStyleOnlyLanes()` 必须**逐项比较 `SceneCacheKey`**(`SceneGeometryHash()` 刻意不含视界,只比它不足以判定"视图没变")。
|
||||
12. 随鼠标变化的绘制(悬停高亮 / 光晕 / 图例高亮 / 光标读数 / 选区 / 提示 / 面板悬停)**必须同时**加在"缓存贴图快路径"与"整幅重绘路径"两侧。
|
||||
13. 浮动面板必须按**层序**(`m_panelOrder`,队尾 = 最上层)绘制与命中:命中从后往前取第一个命中者,点击面板 `BringPanelToFront()`,新开面板追加到队尾。**不要再按 `kEdgePanelKinds` 固定顺序命中再 break**(否则上层 ✕ / 标题栏会被下层参数输入框抢走 → “上层面板关不掉 / 拖不动”)。
|
||||
14. **不要为波形构建 / 绘制引入多线程**;已落地的多线程只有派生曲线线程池(`m_curveWorkers`)。任何"移动 / 释放采样"的操作前必须 `PauseDerivedAsync()`。
|
||||
15. MinGW 的 `swprintf` 按 C99:宽格式串里的 `%s` = **窄**字符串,宽字符串必须用 `%ls`(否则 "KB" 会被截成 "K")。
|
||||
16. 源文件名必须**全局唯一**(Makefile 按 basename 去重 + vpath 查找);新增目录要同时登记进 `CXX_INCLUDES_PATHS` / `CXX_SOURCES_PATHS`。
|
||||
|
||||
### 11.3 离屏验证方法学(本机偏好,强烈建议沿用)
|
||||
|
||||
@@ -2397,3 +2399,88 @@ Documents/ 项目文档(含本文件)
|
||||
→ 容差由 32 提到 **96**(真正“面板内容过期”的差异是百级)。
|
||||
- `[X]` 预览曲线像素计数的颜色容差 6 → **40**(真实信号上曲线多与波形重合,纯色像素本就少)。
|
||||
- **验证**:`make release` 零警告;离屏测试套件 **380/380 通过**(exit=0)。
|
||||
|
||||
---
|
||||
|
||||
## 本轮(2026-10-12,起源设备):平移不再阻塞前台 + 弱峰增强逐信号参数 + 浮动面板层序
|
||||
|
||||
### A. 平移:前台刷新线程**永不阻塞**(用户猜测的评估与落地)
|
||||
|
||||
**用户的猜测与评估**:用户推测“网格自己的前后台缓冲区没准备好时,前台会等待它完成,于是退化到
|
||||
未优化之前”—— **机制描述与当前代码不完全一致**(`f97c4cf` 的后台图层线程已在上一轮被删除,
|
||||
因为后台改写共享渲染状态会覆盖用户的平移),但**问题类别与补救方向完全正确**:
|
||||
平移时前台确实会因为“某个网格的图层没准备好(内容指纹过期 / 锚点离视界太远)”而**同步重画整块图层**
|
||||
(旧代码里 `RollWideBuffer` 直接 `RenderLaneLayerRegion(..., 0, 0, defaultWideMin)`),
|
||||
5 个网格就会在一帧里做完整批重画 → 卡顿;而“结算”帧在**拖拽期间**也会触发,
|
||||
于是按住鼠标不动时在“宽基底快路径”与“整幅重绘(拖拽 → 关抗锯齿)”之间**每两帧振荡**,
|
||||
表现为“波形质量轮流变差 / 断线 / 周期性卡顿,松手才恢复”。
|
||||
|
||||
**本轮改动**:
|
||||
1. `RollWideBuffer()` **不再做任何整块重画**:逐网格整数像素平移 + 补细条;
|
||||
内容过期 / 锚点过远(`|shift| >= bitmapWidth-4`)的网格只 `QueueLaneRebuild()`,
|
||||
本帧仍按现有像素平移(位置照样跟随)。
|
||||
2. 新增 `RenderPendingLaneRebuilds(budget)`:**每帧最多重画 `budget` 个网格**(`Paint` 传 1)。
|
||||
队列非空时 `PostMessageW(kMessagePlotDerivedReady)` 请宿主再送一帧,直到补齐。
|
||||
3. `BlitWideBuffer()` 改为**局部贴图**:图层只覆盖“视界 ± 半屏”,视界落在图层之外时
|
||||
**只贴有像素的那一段**(`srcX/destOffset/blitWidth` 三个量把 BlitBlt 夹到可用区间),
|
||||
其余留由底色填充 —— 即“未绘制的部分先暂时不管,下个循环就绪后再用新缓冲”。
|
||||
旧行为是 `subX < -0.5 || subX + width > bitmapWidth + 0.5` 就 `continue` → **整个网格瞬间变空**。
|
||||
4. **“结算”(整幅全质量精确帧)的触发条件收紧为三者同时成立**:
|
||||
内容+视界与上一条快路径帧相同(`viewStopped`)**且** `m_dragMode == NONE` **且**
|
||||
`LanesAllReadyForView()`。拖拽期间绝不结算 → 不再有质量振荡与周期性整幅重绘。
|
||||
新增 `LaneLayerReady(i)` / `LanesAllReadyForView()`(结构 + 内容指纹 + 跨度全一致);
|
||||
原 `LayersReadyForView()` 改名 `LayersStructurallyReady()`(只看结构,用于“跨度/布局变了
|
||||
必须整批重建一次”这一种情形)。
|
||||
5. 跨度 / 尺寸变化(缩放、布局变化、首帧)仍然走**同步整批** `RebuildWideBuffer()`:
|
||||
这是“一次性”代价(不是平移路径),不做预算分摊,否则会出现整屏空白的中间帧。
|
||||
|
||||
**实测(离屏 `.preflight/panperf`,`VERY_BIG_DATA` 5 网格,深放大 ≈2ms/大格,每帧平移 60px)**:
|
||||
- 平移 40 帧:**中位 6.2ms / p90 7.2ms / max 7.6ms**(旧实现会周期性出现数十~上百毫秒的尖峰)。
|
||||
- **按住键且视界停在数据边界(视界不再变化)12 帧:中位 0.29ms / max 1.29ms**
|
||||
—— 证明拖拽期间不再有整幅重绘(旧实现这里会每两帧穿插一次整幅重绘 + 关抗锯齿)。
|
||||
|
||||
### B. 弱峰增强:改为**逐信号参数**(对齐均值滤波)
|
||||
|
||||
- 数据层:`Lane::WeakPeakItem` 从 `using WeakPeakItem = MeanFilterItem`(只有 `ref` 有效)
|
||||
改为**独立结构体** `{ ref, levelPercent = 150, iterations = 1 }`;
|
||||
删除 `Lane::weakPeakLevel` / `Lane::weakPeakIterations`(网格级)。
|
||||
- 因 `WeakPeakItem` 不再是 `MeanFilterItem`,`TabConfig::RemapAfterSignalRemoval()` 里原本
|
||||
绑定到 `std::vector<Lane::MeanFilterItem>&` 的 `purgeRefs` lambda 改为**泛型 lambda + `std::decay_t`**。
|
||||
- `PlotView`:删除 `LaneWeakPeakLevel/LaneWeakPeakIterations/SetLaneWeakPeakLevel/SetLaneWeakPeakIterations`、
|
||||
`PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS`、`m_peakDragLane`、`EdgePanelLayout::slider/sliderIterations`、
|
||||
`WeakPeakSliderHit()` 与三个滑块常量。
|
||||
- 面板:弱峰增强**与均值滤波完全同构**(逐条目「名称 + 复选框」+ 第二排两个输入框
|
||||
「增强等级 %」/「迭代次数 N」+ 底部「构建为新信号」);`EdgePanelLayout.itemHasParams`
|
||||
统一了 MEAN/CENTER/PEAK 的参数行占位。新增 `PlotEditTarget::PEAK_LEVEL_PERCENT / PEAK_ITERATIONS`,
|
||||
在 `EndEdit` 里分别夹到 0–200 / 1–10 并清 `m_weakPeakCurveCache`。
|
||||
- 指纹与快照:`SceneContentHash` 混合每条 `WeakPeakItem` 的 `levelPercent + iterations`;
|
||||
`CollectSceneDerived` 的 `flatten` 增加 `valueOf` 参数(MEAN/CENTER 取 `points`,PEAK 取 `levelPercent`);
|
||||
`PanelSlotKey` 混合每条目的等级/迭代(面板数字随输入实时刷新)。
|
||||
- 预览曲线 / 光标胶囊 / 「构建为新信号」全部改为**读取该信号自己的条目参数**,
|
||||
因此「预览」与「构建」逐位一致,且同一网格内不同信号可用不同强度。
|
||||
|
||||
### C. 浮动面板:层序(z-order)与命中修复
|
||||
|
||||
**问题**(用户报告):两个面板重叠时,对**上层**面板的 ✕ / 标题栏操作会落到**下层**面板
|
||||
(因为命中按固定的 `kEdgePanelKinds` 顺序遍历、先命中先 break),于是“上层 ✕ 关不掉 / 标题栏拖不动”。
|
||||
|
||||
**修法**:
|
||||
- 新增层序 `m_panelOrder`(`std::vector<PanelRef{laneIndex, kind}>`,**队尾 = 最上层**,`mutable`)。
|
||||
- `SyncPanelOrder()`:按现有顺序保留仍可见的面板,把**新出现**的面板**追加到队尾**(新面板永不被盖住)。
|
||||
- `TopPanelAtPoint(x, y, lane, kind)`:**从队尾往前**找第一个命中的面板。
|
||||
- `BringPanelToFront(lane, kind)`:点击面板(任意部位,含 ✕/标题/输入框)时把它移到队尾 = 置顶。
|
||||
- 绘制循环改为**按 `m_panelOrder` 自下而上**绘制(与命中顺序、像素缓存级联一致);
|
||||
`PanelGlobalKey()` 混合层序 → 层序变化时整层面板缓存作废并按新层序重画。
|
||||
- `QueryCursor` 与 `OnMouseMove` 的“是否在面板上”判断也改用 `TopPanelAtPoint`(与点击一致)。
|
||||
|
||||
### D. 测试与脚本
|
||||
|
||||
- `uitest` 新增 `[AG]`:层预算(`RenderPendingLaneRebuilds(1)` 一帧只重画 1 个网格、剩余留在队列、
|
||||
最终补齐且全部就绪)、**局部贴图**(把某层 `wideMin` 推到只覆盖视界左半 → 断言左侧被贴、
|
||||
右侧保持哨兵色 = “未被绘制就不管”)、面板层序(上层面板 ✕ 命中上层、下层面板不受影响、
|
||||
点击下层把它置顶、`TopPanelAtPoint` 随之改变、新开面板在最上层)。
|
||||
- `uitest` 的 `[X]` 改为逐信号参数(含两个输入框的就地编辑:按下进入编辑、输入 175 回车写入
|
||||
**该信号自己的条目**、999 被夹到 200、迭代次数写入、上下限夹取),并删除已不存在的滑块断言。
|
||||
- 新增离屏探针 `.preflight/panperf`(平移帧耗时 + 按住键静止帧耗时)。
|
||||
- **验证**:`make all` 零警告;`uitest` **388/388 通过**;`panperf` PASS;
|
||||
`.preflight/layercheck` 保持“居中图层 vs 整幅重绘逐像素 0 差异 / 平移对齐 ≤1px / 0 次视界覆盖”。
|
||||
Reference in new issue
Block a user