完善异步架构:后台线程池 + 预览曲线上层绘制 + 快速帧只用于改主体的拖拽

1) 后台派生曲线计算改为多线程池(线程数 = clamp(硬件并发-1,1,4)),多条预览曲线并行计算、更快出现(原单线程顺序处理 5 条整段弱波增强约 1.8s,用户长时间看不到曲线)。
2) PaintLane 分两遍:先画全部原信号,再画全部派生预览曲线,保证预览曲线在本网格全部原信号之上、不被覆盖(用户要求)。
3) 「快速帧(关抗锯齿)」只用于会改变画面主体的拖拽(PAN/SCROLL/LANE_REORDER/MOVE_SIGNAL/BOOST_*);拖时间光标、选区框选、拖面板这类只改叠加层的交互改为复用静态场景缓存(贴图约 1ms),实测拖光标从整幅重画降到约 1-4ms。
小跨度渲染与旧实现逐像素 0 差异。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-11 14:15:26 +08:00
1 parent 99247622c1
commit 226943921b
3 files changed
+58 -17

No files matched your search

+1 -1
View File
@@ -1746,7 +1746,7 @@ namespace jdi
// 缩放等交互的反馈始终跟手,不会被计算拖住,同时把重计算放到后台充分利用多核。
mutable std::mutex m_curveMutex; /**< 保护任务队列与已算好条目。 */
mutable std::condition_variable m_curveCv; /**< 唤醒后台线程。 */
mutable std::thread m_curveWorker; /**< 后台计算线程(惰性启动)。 */
mutable std::vector<std::thread> m_curveWorkers; /**< 后台计算线程池(惰性启动,多核并行)。 */
mutable bool m_curveWorkerStop = false; /**< 请求后台线程退出。 */
mutable std::uint64_t m_curveGeneration = 0; /**< 数据代次(结构变化时自增)。 */
mutable std::uint64_t m_curveSerial = 0; /**< 已发布条目序号。 */
+48 -16
View File
@@ -2353,17 +2353,27 @@ namespace jdi
void PlotView::EnsureCurveWorker() const
{
if (m_curveWorker.joinable())
if (!m_curveWorkers.empty())
{
return;
}
// **多核并行**:后台线程池并发计算多条派生曲线(5 个网格各一条时,
// 串行要 5×368ms,并行只要约 1×368ms),预览曲线因此能很快出现。
// 线程数 = 硬件并发 - 1(给 UI/绘制线程留一个核),夹在 1~4。
unsigned int hardware = std::thread::hardware_concurrency();
int workers = static_cast<int>(hardware == 0 ? 2u : hardware) - 1;
workers = std::max(1, std::min(workers, 4));
m_curveWorkerStop = false;
m_curveWorker = std::thread([this]() { CurveWorkerLoop(); });
m_curveWorkers.reserve(static_cast<std::size_t>(workers));
for (int index = 0; index < workers; ++index)
{
m_curveWorkers.emplace_back([this]() { CurveWorkerLoop(); });
}
}
void PlotView::StopCurveWorker() const
{
if (!m_curveWorker.joinable())
if (m_curveWorkers.empty())
{
return;
}
@@ -2373,7 +2383,14 @@ namespace jdi
m_curveJobs.clear();
}
m_curveCv.notify_all();
m_curveWorker.join();
for (std::thread& worker : m_curveWorkers)
{
if (worker.joinable())
{
worker.join();
}
}
m_curveWorkers.clear();
}
void PlotView::CurveWorkerLoop() const
@@ -5461,11 +5478,18 @@ namespace jdi
return;
}
// 交互期间(拖拽 / 拖动滑块)改用「快速绘制」:关闭波形与派生曲线的抗锯齿。
// 用户明确要求"先用低分辨率,绘制完成后再用高分辨率替换"——
// 抗锯齿是单帧最贵的一项(同一条包络带实测 27.6ms → 3.2ms),几何完全不变;
// 松手时宿主会整帧失效,因此全质量版本随即补上。
m_fastFrame = (m_dragMode != PlotDragMode::NONE);
// 「快速绘制(关闭抗锯齿)」只在**会改变画面主体(波形/网格几何)**的拖拽期间启用:
// 这些拖拽每帧都要重画波形,关掉抗锯齿能显著加速(同一条包络带 27.6ms → 3.2ms)。
// ⚠️ 像「拖时间光标」「选区框选」「拖浮动面板」这类**只改叠加层、画面主体不变**的交互,
// 一旦启用快速帧就会给静态场景缓存打上「已降级」标记 → 缓存失效 → 每帧整幅重画
//(实测拖光标从约 1ms 骤升到十几毫秒)。因此这里**不**把它们算作快速帧,
// 让它们直接复用静态场景缓存(贴图 ~1ms)+ 叠加层,做到真正跟手。
const bool sceneChangingDrag =
(m_dragMode == PlotDragMode::PAN) || (m_dragMode == PlotDragMode::SCROLL_LANES) ||
(m_dragMode == PlotDragMode::LANE_REORDER) || (m_dragMode == PlotDragMode::MOVE_SIGNAL) ||
(m_dragMode == PlotDragMode::BOOST_LEVEL) ||
(m_dragMode == PlotDragMode::BOOST_ITERATIONS);
m_fastFrame = sceneChangingDrag;
// 贴图之后、静态场景之外的全部内容(随鼠标变化或位于最上层)。
// 三个分支(完全命中 / 局部重画 / 整幅重绘)共用同一段,保证观感完全一致。
@@ -6857,10 +6881,20 @@ namespace jdi
g.FillPath(&brush, &scatterPath);
}
}
}
// 中心轴均值滤波曲线(「实验性功能 → 边沿计数(中心轴)」)叠加绘制在原信号之上。
// 它只是叠加显示的派生曲线,**不会**加入左侧信号列表;
// **只有该信号被勾选时**才绘制(参数与「均值滤波」同源,曲线逐位一致)。
// 第二遍:把所有**派生预览曲线**统一绘制在**本网格全部原信号之上**。
// 若在同一个信号循环里就地叠加,后绘制的原信号会把先前信号的预览曲线盖住
//(用户明确要求"预览信号处于原信号上层不要被覆盖")。
for (const SignalRef& ref : laneSignals)
{
const ResolvedSignal resolved = ResolveSignal(ref);
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
{
continue;
}
// 中心轴均值滤波曲线(「实验性功能 → 边沿计数(中心轴)」):与「均值滤波」同源,
// 只有被勾选的信号才绘制。
if (centerOn)
{
const Lane::EdgeCenterItem* centerItem =
@@ -6870,8 +6904,7 @@ namespace jdi
PaintMeanFilterCurve(g, resolved, lane, centerItem->points, centerItem->iterations);
}
}
// 均值滤波曲线(「实验性功能 → 均值滤波」):同样是仅用于预览的叠加曲线,
// 但**只有该信号被勾选时**才绘制(参数取自它自己的条目)。
// 均值滤波预览曲线:只有被勾选的信号才绘制(参数取自它自己的条目)。
if (meanFilterOn)
{
const Lane::MeanFilterItem* item =
@@ -6881,8 +6914,7 @@ namespace jdi
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations);
}
}
// 弱波增强曲线:同样是仅用于预览的叠加曲线,只在被勾选时绘制
//(增强等级取自整个网格)。
// 弱波增强预览曲线:只有被勾选的信号才绘制(增强等级取自整个网格)。
if (weakBoostOn)
{
const Lane::WeakBoostItem* item =
+9
View File
@@ -228,6 +228,14 @@ Documents/ 项目文档(含本文件)
- **显示效果**:小窗口(同步)路径与旧实现**逐像素 0 差异**;大窗口(异步)路径的曲线像素与旧实现一致(差异仅出现在合成测试中一个与缓存相关的**旧实现的时轴陈旧**现象上,新实现按设定视图正确绘制)。后台完成后的全质量帧与同步结果相同。
- **代价**:大窗口缓存条目可占用较多内存(整段弱波增强单条 ≈104MB/信号),由 `kCurveSpanCacheMaxBytes`(768MB)+ 条目数上限(32)兜底,最旧优先淘汰。
### 9.7 完善异步架构 + 预览曲线层级修正(本仓库当前状态)
在 9.6 基础上做了三项关键完善:
1. **后台线程池(真·多线程)**:原先是**单线程**顺序计算,5 个网格各一条整段弱波增强要 5×368ms≈1.8s,用户会长时间看不到预览曲线(看起来"完全没画出来")。改为 **`m_curveWorkers` 线程池**(`EnsureCurveWorker`,线程数 = `clamp(硬件并发-1, 1, 4)`,给 UI 留一个核),多条曲线**并行**计算,预览曲线因此很快出现。`StopCurveWorker` 会 join 全部线程。
2. **预览曲线绘制在「本网格全部原信号之上」**:原先在**同一个信号循环里**就地叠加派生曲线,导致网格内有多条信号时,后绘制的原信号会把先前信号的预览曲线盖住(用户明确要求"预览信号处于原信号上层不要被覆盖")。现在 `PaintLane` 分**两遍**:第一遍画全部原信号(线/散点/光晕),第二遍画全部派生预览曲线。
3. **快速帧(关抗锯齿)只用于「会改变画面主体」的拖拽**:原先 `m_fastFrame = (拖拽中)`,会给静态场景缓存打上「已降级」标记 → 缓存失效 → 每帧整幅重画。像**拖时间光标 / 选区框选 / 拖浮动面板**这些**只改叠加层、主体不变**的交互因此被拖慢(实测拖光标从约 1ms 升到十几毫秒)。现在只有 `PAN / SCROLL_LANES / LANE_REORDER / MOVE_SIGNAL / BOOST_LEVEL / BOOST_ITERATIONS` 才算快速帧;其余拖拽直接复用静态场景缓存(贴图 ~1ms)+ 叠加层。实测拖光标 **全程命中缓存、约 1–4ms**。
- **平移(尤其"一大格=2ms")的固有下限**:平移会让视图每帧变化 → 静态场景缓存必然失效 → 每帧都要重画波形(**这部分无法异步**,因为 GDI+ 绘制必须在 UI 线程;异步只覆盖"派生预览曲线"的计算)。在 5 个网格、含 2576 万采样编码器方波的 `VERY_BIG_DATA` 上,实测**拖拽平移**约 6–15ms/帧(机器空闲/繁忙分别为下限/上限),已无尖峰。若日后要进一步降低,只能减少波形绘制本身(受"不牺牲显示效果"约束)或把整帧渲染也搬到离屏线程(改动很大、风险高)。
- **预览曲线"看起来没画出来"的真相**:对**编码器方波这类强信号**,弱波增强几乎不改动波形,所以预览曲线与原信号**几乎重合**(读数胶囊里两者数值只差末位),视觉上"看不见"——这是**正确行为**。用 `mode=1` 导出可确认曲线确实被绘制(G1–G5 均有浅色叠加 +「(弱波增强)」读数)。小跨度渲染与旧实现**逐像素 0 差异**。
---
## 10. 变更日志(本文件维护记录)
@@ -238,3 +246,4 @@ Documents/ 项目文档(含本文件)
| 2026-10-11 | DeepSeek-V4.1-Flash | 修复右键网格菜单后悬停光晕/提示气泡残留;修复「启用带预览曲线的实验性功能后平移/拖光标 300ms+ 卡顿」(`paintDerivedCapsule` 巨窗每帧重算,收窄请求窗口,显示效果零变化)。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 深度修复「实验性功能预览曲线导致的交互卡顿」:大窗口改入缓存(静止重绘 2247ms→0.25ms)、交互期间延迟重算(弱波增强拖滑块 2303ms→18ms)、计算缓冲复用;显示逐像素不变。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 架构升级:**派生曲线计算与 UI 绘制分离**(后台线程 + 双缓冲)。绘制线程永不等计算,弱波增强拖滑块/平移从 2303/2247ms 降到 ~15ms;结构变化前 `PauseDerivedAsync` 保证线程安全;小窗口同步路径与旧实现逐像素 0 差异。 |
| 2026-10-11 | DeepSeek-V4.1-Flash | 完善异步:后台改**多线程池**(预览曲线并行计算、更快出现);`PaintLane` 分两遍把**预览曲线画在全部原信号之上**(不再被覆盖);**快速帧只用于改主体的拖拽**(拖光标/框选改为命中场景缓存,约 1–4ms)。小跨度逐像素 0 差异。 |