From 226943921b1453b4b6a8fb3dbd5ff590e3f63d55 Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" <> Date: Sun, 11 Oct 2026 14:15:26 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E5=96=84=E5=BC=82=E6=AD=A5=E6=9E=B6?= =?UTF-8?q?=E6=9E=84=EF=BC=9A=E5=90=8E=E5=8F=B0=E7=BA=BF=E7=A8=8B=E6=B1=A0?= =?UTF-8?q?=20+=20=E9=A2=84=E8=A7=88=E6=9B=B2=E7=BA=BF=E4=B8=8A=E5=B1=82?= =?UTF-8?q?=E7=BB=98=E5=88=B6=20+=20=E5=BF=AB=E9=80=9F=E5=B8=A7=E5=8F=AA?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E6=94=B9=E4=B8=BB=E4=BD=93=E7=9A=84=E6=8B=96?= =?UTF-8?q?=E6=8B=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) 后台派生曲线计算改为多线程池(线程数 = clamp(硬件并发-1,1,4)),多条预览曲线并行计算、更快出现(原单线程顺序处理 5 条整段弱波增强约 1.8s,用户长时间看不到曲线)。 2) PaintLane 分两遍:先画全部原信号,再画全部派生预览曲线,保证预览曲线在本网格全部原信号之上、不被覆盖(用户要求)。 3) 「快速帧(关抗锯齿)」只用于会改变画面主体的拖拽(PAN/SCROLL/LANE_REORDER/MOVE_SIGNAL/BOOST_*);拖时间光标、选区框选、拖面板这类只改叠加层的交互改为复用静态场景缓存(贴图约 1ms),实测拖光标从整幅重画降到约 1-4ms。 小跨度渲染与旧实现逐像素 0 差异。 --- Components/PlotView/Inc/PlotView.hpp | 2 +- Components/PlotView/Src/PlotView.cpp | 64 +++++++++++++++++++++------- Documents/agent_mem.md | 9 ++++ 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index e1968bd..0c01de4 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -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 m_curveWorkers; /**< 后台计算线程池(惰性启动,多核并行)。 */ mutable bool m_curveWorkerStop = false; /**< 请求后台线程退出。 */ mutable std::uint64_t m_curveGeneration = 0; /**< 数据代次(结构变化时自增)。 */ mutable std::uint64_t m_curveSerial = 0; /**< 已发布条目序号。 */ diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index 376d087..727767c 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -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(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(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 = diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index d75b90f..60233d3 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -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 差异。 |