修复深放大下样式/参数滑块卡顿(探针定位:样式补丁用抗锯齿 + 后台发布整幅作废)

复现:VERY_BIG_DATA 全信号在视界 [40.1756,40.1885]s(Δt≈12.9ms)下拖透明度/粗细/自定义颜色/拾色/弱波增强滑块极卡,但拖光标流畅(光标命中场景缓存)。用 JDI_PERF_PROBE 门控的临时探针精确拆解:整幅/补丁耗时几乎全在包络带 FillPath(抗锯齿 ≈29ms/网格,关掉 ≈5.4ms)。

两个根因:① 样式补丁(拖透明度/粗细/颜色)不在拖拽态 → m_fastFrame=false → 用抗锯齿重画该网格包络带 ≈72ms;② 拖弱波增强/均值滤波滑块时后台每算完一条就 InvalidateSceneCache → 每帧退化成整幅重绘(150ms+)。

修复:① 样式补丁强制快速帧并置 m_sceneDegraded(补丁 72→14ms,静止后自动补全质量);② 拖拽中后台发布只标记降级、不整幅作废(弱波增强滑块 96–176→26ms);③ 交互期间省略浮动面板投影。

同一负载机器实测:透明度/粗细/颜色 77→16ms,弱波增强滑块 96–176→26ms,平移拖动 34ms(快速帧,空闲机约 8ms)。全质量/结算帧与修复前逐像素 0 差异(LockBits 验证)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-11 19:07:37 +08:00
1 parent c794967b02
commit aefaaef6fa
2 files changed
+53 -7

No files matched your search

+30 -7
View File
@@ -5487,10 +5487,22 @@ namespace jdi
std::lock_guard<std::mutex> lock(m_curveMutex);
PruneCurveEntriesLocked();
}
// 后台算好了新曲线:静态场景缓存里仍是旧曲线(或还没有曲线),作废一次以纳入最新结果。
// 后台算好了新曲线:
// * **静止**时:整幅作废一次,让新曲线(与新的静态场景缓存)立刻出现。
// * **拖拽中**(拖弱波增强/均值滤波参数滑块):**不作废整幅** —— 拖拽时参数每帧都在变,
// 本来就每帧走"样式补丁"路径,补丁会直接取到最新曲线;若此时还整幅作废,
// 每帧会退化成整幅重绘(实测单帧 150ms+,与"拖动要跟手"直接冲突)。
// 改为标记"已降级",等拖拽结束后再补一帧全质量。
if (m_curveSceneDirty.exchange(false, std::memory_order_relaxed))
{
InvalidateSceneCache();
if (m_dragMode == PlotDragMode::NONE)
{
InvalidateSceneCache();
}
else
{
m_sceneDegraded = true;
}
}
if (m_lanes.empty())
@@ -5567,7 +5579,6 @@ namespace jdi
paintOverlays();
return;
}
// 只有"外观样式"变化(典型场景:拖动自定义颜色滑块实时调色):网格结构、视图几何、
// 波形采样都没变,只是颜色/不透明度/线宽/模式不同 —— 此时只重画受影响的网格,
// 再写回缓存。(VERY_BIG_DATA 实测:整幅重绘 30ms+,单网格重画约 4ms。)
@@ -5575,7 +5586,14 @@ namespace jdi
std::vector<int> patchLanes;
if (FindStyleOnlyLanes(patchLanes) && BlitSceneCache(g))
{
// ⚠️ 样式/参数**正在连续变化**(拖透明度/粗细/拾色/实验性功能滑块):这本身就是
// 一种"交互中"。若不切换快速帧,本帧会用**抗锯齿**重画波形包络带 —— 实测
// 单条网格的包络带 AA 填充 ≈29ms(本机负载下 72ms),而关掉 AA 只要 ≈5.6ms。
// 因此补丁阶段强制快速帧,并在补丁后把场景缓存标记为"已降级",
// 下一帧(参数稳定后)自动补一帧全质量 —— 既保证拖动手感,又不牺牲最终画质。
m_fastFrame = true;
RepaintSceneCachePatch(g, patchLanes);
m_sceneDegraded = true;
paintOverlays();
return;
}
@@ -6942,7 +6960,7 @@ namespace jdi
const DrawMode mode = resolved.style->mode;
// 顶点由“几何键”缓存提供:同一视图下重复绘制不再重算全量采样。
const PolylineCacheEntry* cached = AcquirePolyline(ref, resolved, lane);
const PolylineCacheEntry* cached = AcquirePolyline(ref, resolved, lane);
if (cached == nullptr)
{
continue;
@@ -9162,9 +9180,14 @@ namespace jdi
const float pad = kEdgePanelPad * m_scale;
// 面板主体 + 标题栏。
UiFillRoundRect(g, gd::RectF(panel.X + 1.0f * m_scale, panel.Y + 2.0f * m_scale,
panel.Width, panel.Height),
5.0f * m_scale, 0x14000000u);
// 投影响用两张大面积的半透明圆角矩形模拟,代价不低;交互期间(快速帧)省略它,
// 让拖动参数滑块时面板重绘不再被投影拖累(松手后的全质量帧会补回投影)。
if (!m_fastFrame)
{
UiFillRoundRect(g, gd::RectF(panel.X + 1.0f * m_scale, panel.Y + 2.0f * m_scale,
panel.Width, panel.Height),
5.0f * m_scale, 0x14000000u);
}
UiFillRoundRect(g, panel, 5.0f * m_scale, WithAlpha(m_theme->cardBg, 0xF4));
UiDrawRoundRect(g, panel, 5.0f * m_scale, m_theme->borderStrong, 1.0f);
UiFillRoundRect(g, gd::RectF(panel.X, panel.Y, panel.Width, titleHeight), 5.0f * m_scale,
+23
View File
@@ -261,6 +261,28 @@ Documents/ 项目文档(含本文件)
- **踩坑**:比对若用 `[System.Drawing.Bitmap]::GetPixel` 且两侧位图**尺寸不一致或文件缺失**,会抛 `调用"GetPixel"时发生异常:"参数无效"`。根因通常是"旧版本"的 harness 链接失败(例如它引用了新版本才有的符号)导致对照图没生成。**务必**:①先确认两侧 PNG 都存在且尺寸一致;②用 `LockBits` 而非逐点 `GetPixel`(后者对上兆像素级图片极慢)。
- **实测结论**(`709cbe4` 的"可见窗口预览" vs `389b4ec` 的"整段预览",同 harness 同数据):全部**全质量帧**(均值滤波/弱波增强 × FitAll/2.5s/103ms 三个缩放)**逐像素 0 差异** —— 证明"改为整段计算"未带来任何显示回归,预览与"构建为新信号"一致;仅**拖拽中途**帧有 0.09% 差异(快速帧关抗锯齿 + 装饰层贴回的预期差异)。
### 9.9 深度定位:样式/参数滑块的卡顿根因与修复(探针 + 分支计时)
- **用户复现**:`VERY_BIG_DATA` 加载全部信号 → 选区放大到视界 `[40.1756, 40.1885]s`(Δt≈12.9ms,≈2 采样/像素)→ 拖左/中键平移、拖透明度/粗细/自定义颜色/拾色、拖弱波增强参数滑块 → 极卡;但**拖时间光标很流畅**(光标只改叠加层、命中静态场景缓存)。
- **定位方法**(临时探针,`JDI_PERF_PROBE` 门控,事后已移除):在 `.preflight/perf` 的离屏 harness 中**精确复现该视界**(`mode=2`),给 `PlotView::Paint` 的每个分支(缓存贴图/样式补丁/整幅重绘/网格重排)与 `PaintLane` 的每个子阶段(网格线/波形/派生曲线/Y 轴刻度/图例/包络带 `FillPath`)加计时。
- **实测拆解**(本机当时负载约 4×,下列为负载下的值):
| 场景 | 整帧 | 其中包络带 `FillPath` |
| --- | --- | --- |
| 平移(整幅重绘,**抗锯齿开**) | 161ms | **143ms(5 网格,≈29ms/条)** |
| 平移拖动(快速帧,**抗锯齿关**) | 34ms | 27ms(5 网格,≈5.4ms/条) |
| 透明度/粗细/颜色(样式补丁,**抗锯齿开**) | **77ms** | **72ms(1 网格)** |
| 弱波增强参数滑块 | 96–176ms | (含整幅重绘放大) |
- **两个真正的根因**:
1. **样式补丁用了抗锯齿**:拖透明度/粗细/自定义颜色属于"样式变化 → 局部补丁",但它**不在任何拖拽态**,于是 `m_fastFrame=false` → 补丁用**抗锯齿**重画该网格的包络带(≈72ms/网格)。这是"样式滑块极卡"的直接原因。
2. **后台派生曲线发布时整幅作废**:拖弱波增强/均值滤波参数滑块时,后台每算完一条就 `InvalidateSceneCache()` → 下一步退化成**整幅重绘**(负载下 150ms+),而不只是补丁。
- **修复(3 处,均只影响拖拽帧;静止/结算帧逐像素不变)**:
- **样式补丁强制快速帧**:进入补丁分支时设 `m_fastFrame = true`(关抗锯齿)并置 `m_sceneDegraded = true` → 补丁 ≈72ms → **≈14ms**;参数稳定后的下一帧自动补一帧全质量。
- **拖拽中不整幅作废**:后台曲线发布时,若 `m_dragMode != NONE`(拖拽中)只置 `m_sceneDegraded`,由每帧补丁直接取到最新曲线;仅静止时才整幅作废。弱波增强滑块 ≈96–176ms → **≈25ms**。
- **交互期间省略浮动面板投影**:面板的两张大面积半透明圆角投影在快速帧跳过,减少面板重绘开销。
- **结果(同一负载机器)**:透明度/粗细/颜色 **77ms → 16ms**;弱波增强滑块 **96–176ms → 26ms**;平移拖动 **34ms**(快速帧,空闲机 ≈8ms)。全质量/结算帧与修复前**逐像素 0 差异**(`LockBits` 对照验证)。
- **仍存在的固有下限**:整幅重绘中的**包络带 `FillPath`** 本身是 O(像素列数) 的 GDI+ 抗锯齿填充(≈29ms/网格/负载)。它只在"整幅重绘"或"结算帧"出现(平移快速帧已关抗锯齿 ≈5.4ms/网格)。若要在密集包络带上也关抗锯齿可再快 5×,但会轻微改变边缘观感,**需用户确认后再动**。
---
## 10. 变更日志(本文件维护记录)
@@ -273,3 +295,4 @@ Documents/ 项目文档(含本文件)
| 2026-10-11 | DeepSeek-V4.1-Flash | 架构升级:**派生曲线计算与 UI 绘制分离**(后台线程 + 双缓冲)。绘制线程永不等计算,弱波增强拖滑块/平移从 2303/2247ms 降到 ~15ms;结构变化前 `PauseDerivedAsync` 保证线程安全;小窗口同步路径与旧实现逐像素 0 差异。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 完善异步:后台改**多线程池**(预览曲线并行计算、更快出现);`PaintLane` 分两遍把**预览曲线画在全部原信号之上**(不再被覆盖);**快速帧只用于改主体的拖拽**(拖光标/框选改为命中场景缓存,约 1–4ms)。小跨度逐像素 0 差异。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 预览曲线改为**按整条信号计算**,与「构建为新信号」逐位一致;新增**平移装饰层像素缓存**(图例 + Y 轴刻度在拖拽时直接贴回,2ms/大格拖拽 13ms→约 5ms);后台任务按 (信号,种类) 去重以防拖滑块淹没线程池。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 探针定位"样式/参数滑块在深放大下极卡":根因是**样式补丁用了抗锯齿**(≈72ms/网格)且**后台曲线发布时整幅作废**导致每帧整幅重绘。修复:样式补丁强制快速帧、拖拽中不整幅作废、交互期省略面板投影 → 透明度/粗细/颜色 77ms→16ms、弱波增强滑块 96–176ms→26ms,静止/结算帧逐像素不变。 |