From 1c9c43233d0535ccafed5d7f1ad2e5269e4aa34c Mon Sep 17 00:00:00 2001 From: "DeepSeek V4.1 Flash" Date: Mon, 12 Oct 2026 00:04:01 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9D=9E=E6=8B=96=E6=8B=BD=E8=A7=86=E7=95=8C?= =?UTF-8?q?=E5=8F=98=E5=8C=96=EF=BC=88=E9=94=AE=E7=9B=98/=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E6=A0=8F=EF=BC=89=E4=B9=9F=E8=B5=B0=E5=AE=BD=E5=9F=BA?= =?UTF-8?q?=E5=BA=95=E5=BF=AB=E8=B7=AF=E5=BE=84=EF=BC=9A124.6ms=E2=86=928.?= =?UTF-8?q?6ms=EF=BC=9B=E6=96=B0=E5=A2=9E=20wideKey=20=E7=BB=93=E7=AE=97?= =?UTF-8?q?=E5=B8=A7=E6=81=A2=E5=A4=8D=E5=83=8F=E7=B4=A0=E7=B2=BE=E7=A1=AE?= =?UTF-8?q?=EF=BC=9B=E5=88=86=E6=94=AF=E9=A1=BA=E5=BA=8F=E5=9B=BA=E5=AE=9A?= =?UTF-8?q?=E4=B8=BA=E9=9D=99=E6=80=81=E8=B4=B4=E5=9B=BE=E2=86=92=E5=AE=BD?= =?UTF-8?q?=E5=9F=BA=E5=BA=95=E2=86=92=E6=A0=B7=E5=BC=8F=E8=A1=A5=E4=B8=81?= =?UTF-8?q?=EF=BC=9B=E5=AE=BD=E5=9F=BA=E5=BA=95=E5=BC=BA=E5=88=B6=E5=85=A8?= =?UTF-8?q?=E8=B4=A8=E9=87=8F=E6=8A=97=E9=94=AF=E9=BD=BF=E4=B8=94=E7=AB=96?= =?UTF-8?q?=E5=90=91=E5=88=BB=E5=BA=A6=E6=94=B9=E7=94=A8=E5=89=8D=E5=8F=B0?= =?UTF-8?q?=E8=B7=A8=E5=BA=A6=EF=BC=9B=E5=83=8F=E7=B4=A0=E5=AF=B9=E6=AF=94?= =?UTF-8?q?=E9=AA=8C=E8=AF=81=207=20=E5=BC=A0=E9=9D=99=E6=80=81/=E7=BB=93?= =?UTF-8?q?=E7=AE=97=E5=B8=A7=E4=B8=8E=20HEAD=20=E5=AD=97=E8=8A=82?= =?UTF-8?q?=E4=B8=80=E8=87=B4=E3=80=81=E5=BA=95=E5=BA=A7=E7=AE=A1=E7=BA=BF?= =?UTF-8?q?=E5=BF=A0=E5=AE=9E=EF=BC=88meanAbs=200.68/255=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Components/PlotView/Inc/PlotView.hpp | 4 + Components/PlotView/Src/PlotView.cpp | 157 ++++++++++++++++----------- Documents/agent_mem.md | 30 ++++- 3 files changed, 128 insertions(+), 63 deletions(-) diff --git a/Components/PlotView/Inc/PlotView.hpp b/Components/PlotView/Inc/PlotView.hpp index b2f72a5..8316679 100644 --- a/Components/PlotView/Inc/PlotView.hpp +++ b/Components/PlotView/Inc/PlotView.hpp @@ -2258,6 +2258,10 @@ namespace jdi bool m_wideValid = false; /**< 宽缓冲区内容是否有效。 */ std::uint64_t m_wideKey = 0; /**< 宽缓冲区对应的内容指纹(外观/派生参数等)。 */ bool m_renderWide = false; /**< 正在渲染宽缓冲区(重入 `PaintLane` 时跳过装饰)。 */ + /** 上一次走"宽缓冲快路径"时的内容指纹(含视界)。当前帧指纹与之相同 → 视界已停止变化, + * 此时改走一次整幅重绘("结算"),保证最终呈现的是**像素精确**的全质量帧。 */ + std::uint64_t m_wideLastKey = 0; + bool m_wideLastValid = false; /** * @brief 实验性功能浮动面板的像素图层项。 diff --git a/Components/PlotView/Src/PlotView.cpp b/Components/PlotView/Src/PlotView.cpp index b7df407..4f6234f 100644 --- a/Components/PlotView/Src/PlotView.cpp +++ b/Components/PlotView/Src/PlotView.cpp @@ -5568,58 +5568,6 @@ namespace jdi } }; - // 视界平移 / 派生预览曲线参数变化:画面**基底**(网格/波形)不变,只是横向平移或 - // 叠加的派生曲线变化。直接复用「宽缓冲区」已渲染的**基底**像素,只把派生曲线作为 - // 叠加层重画 —— 前台 O(1),不重画波形,因此无论放大到多深都保持跟手。 - // 判定:网格结构未变(几何指纹一致)且**逐信号外观样式未变** → 属视图/派生变化,可用宽基底; - // 外观样式(颜色/粗细/透明度/模式)变化 → 交给下方"样式补丁"分支局部重画。 - bool wideUsable = false; - if (m_fastFrame && m_decorValid && HasDrawableContent() && m_sceneValid && - m_sceneGeometryHash == SceneGeometryHash()) - { - std::vector> currentStyles; - CollectSceneStyles(currentStyles); - wideUsable = (currentStyles == m_sceneStyles); - } - if (wideUsable) - { - // 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰); - // 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画 - // 2×视界造成的一次尖峰),再不行才整幅重建。 - bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false; - if (!ready && InitWideBufferFromScene()) - { - ready = RollWideBuffer(); - } - if (!ready) - { - ready = RebuildWideBuffer(); - } - if (ready) - { - // 绘图区底色(覆盖时间轴与侧边留白区域)。 - UiFillRect(g, m_area, m_theme->plotBg); - // 贴基底(绘图区:网格 + 波形)。 - BlitWideBuffer(g); - // 时间轴:宽缓冲不含它,前台按当前视图绘制。 - const double viewMin = Tab().ViewMin(); - const double viewMax = Tab().ViewMax(); - const double spanNow = viewMax - viewMin; - const double timeStep = NiceStep((spanNow > 0.0) ? spanNow : 1.0, kTimeTickTarget); - std::vector timeTicks; - BuildTicks(viewMin, viewMax, timeStep, timeTicks); - PaintTimeAxis(g, timeTicks, timeStep); - // 派生预览曲线作为叠加层重画(很轻);随后装饰层与其它叠加层。 - for (std::size_t i = 0; i < m_lanes.size(); ++i) - { - PaintLaneDerivedOverlays(g, static_cast(i), m_lanes[i]); - } - BlitDecorCache(g); - paintOverlays(); - return; - } - } - // 网格次序拖拽:静态场景缓存仍然有效,但各网格要按位移重新"摆放"。 // 这里逐网格把缓存中的横向条带平移贴回(几十微秒), // 因此拖动百万级采样的网格时依然流畅,且被拖网格的波形随鼠标自由移动。 @@ -5640,6 +5588,82 @@ namespace jdi paintOverlays(); return; } + + // 视界平移 / 派生预览曲线参数变化:画面**基底**(网格/波形)不变,只是横向平移或 + // 叠加的派生曲线变化。直接复用「宽缓冲区」已渲染的**基底**像素,只把派生曲线作为 + // 叠加层重画 —— 前台 O(1),不重画波形,因此无论放大到多深都保持跟手。 + // 判定:网格结构未变(几何指纹一致)且**逐信号外观样式未变** → 属视图/派生变化,可用宽基底; + // 外观样式(颜色/粗细/透明度/模式)变化 → 交给下方"样式补丁"分支局部重画。 + // ⚠️ 位置必须在"静态场景贴图"**之后**:视界与样式都没变时(典型:鼠标悬停移动) + // 应由静态场景贴图命中,绝不能落到这里 —— 否则每两帧就会触发一次"结算整幅重绘", + // 悬停反而变卡。而它又必须在"样式补丁"**之前**:派生曲线参数变化(拖弱波增强 / + // 均值滤波滑块)同样只需叠加重画派生曲线,走本分支比"逐网格补丁"更快且质量更高。 + // ⚠️ **不要求"正在拖拽"**:非拖拽的视界变化(键盘方向键 / 工具栏)同样走此快路径, + // 避免该放大区间"整幅重绘约 124ms"的卡顿。 + bool wideUsable = false; + if (m_decorValid && HasDrawableContent() && m_sceneValid && + m_sceneGeometryHash == SceneGeometryHash()) + { + std::vector> currentStyles; + CollectSceneStyles(currentStyles); + wideUsable = (currentStyles == m_sceneStyles); + } + if (wideUsable) + { + // 内容(含视界)与本帧上一条快路径帧相同 → 视界已停止变化 → 走一次整幅"结算" + // 全质量帧(保证最终呈现**像素精确**;此后命中静态场景缓存,不再回到本分支)。 + std::uint64_t wideKey = SceneContentHash(true); + wideKey ^= static_cast(std::llround(Tab().ViewMin() * 1.0e6)); + wideKey *= 1099511628211ull; + wideKey ^= static_cast(std::llround(Tab().ViewMax() * 1.0e6)); + + if (m_wideLastValid && m_wideLastKey == wideKey) + { + m_wideLastValid = false; // 结算:落到下方整幅重绘 + } + else + { + // 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰); + // 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画 + // 2×视界造成的一次尖峰),再不行才整幅重建。 + bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false; + if (!ready && InitWideBufferFromScene()) + { + ready = RollWideBuffer(); + } + if (!ready) + { + ready = RebuildWideBuffer(); + } + if (ready) + { + m_wideLastKey = wideKey; + m_wideLastValid = true; + // 绘图区底色(覆盖时间轴与侧边留白区域)。 + UiFillRect(g, m_area, m_theme->plotBg); + // 贴基底(绘图区:网格 + 波形)。 + BlitWideBuffer(g); + // 时间轴:宽缓冲不含它,前台按当前视图绘制。 + const double wideViewMin = Tab().ViewMin(); + const double wideViewMax = Tab().ViewMax(); + const double wideSpanNow = wideViewMax - wideViewMin; + const double wideTimeStep = + NiceStep((wideSpanNow > 0.0) ? wideSpanNow : 1.0, kTimeTickTarget); + std::vector wideTimeTicks; + BuildTicks(wideViewMin, wideViewMax, wideTimeStep, wideTimeTicks); + PaintTimeAxis(g, wideTimeTicks, wideTimeStep); + // 派生预览曲线作为叠加层重画(很轻);随后装饰层与其它叠加层。 + for (std::size_t i = 0; i < m_lanes.size(); ++i) + { + PaintLaneDerivedOverlays(g, static_cast(i), m_lanes[i]); + } + BlitDecorCache(g); + paintOverlays(); + return; + } + } + } + // 只有"外观样式"变化(典型场景:拖动自定义颜色滑块实时调色):网格结构、视图几何、 // 波形采样都没变,只是颜色/不透明度/线宽/模式不同 —— 此时只重画受影响的网格, // 再写回缓存。(VERY_BIG_DATA 实测:整幅重绘 30ms+,单网格重画约 4ms。) @@ -5647,9 +5671,9 @@ namespace jdi std::vector patchLanes; if (FindStyleOnlyLanes(patchLanes) && BlitSceneCache(g)) { - // ⚠️ 样式/参数**正在连续变化**(拖透明度/粗细/拾色/实验性功能滑块):这本身就是 - // 一种"交互中"。若不切换快速帧,本帧会用**抗锯齿**重画波形包络带 —— 实测 - // 单条网格的包络带 AA 填充 ≈29ms(本机负载下 72ms),而关掉 AA 只要 ≈5.6ms。 + // ⚠️ 样式**正在连续变化**(拖透明度 / 粗细 / 拾色滑块):这本身就是一种"交互中"。 + // 若不切换快速帧,本帧会用**抗锯齿**重画波形包络带 —— 实测单条网格的包络带 + // AA 填充 ≈29ms(本机负载下 72ms),而关掉 AA 只要 ≈5.6ms。 // 因此补丁阶段强制快速帧,并在补丁后把场景缓存标记为"已降级", // 下一帧(参数稳定后)自动补一帧全质量 —— 既保证拖动手感,又不牺牲最终画质。 m_fastFrame = true; @@ -6865,6 +6889,11 @@ namespace jdi Tab().ViewMax() = wideMin + static_cast(bitmapWidth) / scale; m_renderWide = true; + // 宽缓冲必须与"同一视界的整幅渲染"**逐像素同源**,否则平移后网格线数量 / 锯齿会不同: + // * 强制全质量抗锯齿(不受"拖拽快速帧"影响,绝不降级); + // * 网格竖向刻度沿用**前台视界跨度**(而非 2× 的宽窗口跨度),否则竖网格线会减半。 + const bool savedFastFrame = m_fastFrame; + m_fastFrame = false; BuildLaneVisuals(); // 宽缓冲必须沿用**当前视界**的 Y 轴范围,否则幅值映射与前台不一致。 for (std::size_t i = 0; i < m_lanes.size() && i < savedLanes.size(); ++i) @@ -6881,17 +6910,16 @@ namespace jdi UiFillRect(g, m_area, m_theme->plotBg); UiFillRect(g, m_plotRect, m_theme->cardBg); - const double viewMin = Tab().ViewMin(); - const double viewMax = Tab().ViewMax(); - const double sp = viewMax - viewMin; - const double timeStep = NiceStep((sp > 0.0) ? sp : 1.0, kTimeTickTarget); + const double fgSpan = savedViewMax - savedViewMin; + const double timeStep = NiceStep((fgSpan > 0.0) ? fgSpan : 1.0, kTimeTickTarget); std::vector timeTicks; - BuildTicks(viewMin, viewMax, timeStep, timeTicks); + BuildTicks(Tab().ViewMin(), Tab().ViewMax(), timeStep, timeTicks); for (std::size_t i = 0; i < m_lanes.size(); ++i) { PaintLane(g, static_cast(i), m_lanes[i], timeTicks); } } + m_fastFrame = savedFastFrame; m_renderWide = false; // 恢复布局 / 视图 / 网格。 @@ -7066,6 +7094,10 @@ namespace jdi Tab().ViewMax() = newWideMin + static_cast(bitmapWidth) / scale; m_renderWide = true; + // 与 `RebuildWideBuffer` 保持完全一致:细条补画也必须**全质量抗锯齿**,且网格竖向刻度 + // 沿用**前台视界跨度**,否则滚动后新露出的细条会与既有基底的网格 / 锯齿不一致。 + const bool savedFastFrame = m_fastFrame; + m_fastFrame = false; BuildLaneVisuals(); for (std::size_t i = 0; i < m_lanes.size() && i < savedLanes.size(); ++i) { @@ -7079,8 +7111,8 @@ namespace jdi g.SetTextRenderingHint(gd::TextRenderingHintClearTypeGridFit); UiFillRect(g, m_area, m_theme->plotBg); UiFillRect(g, m_area, m_theme->cardBg); - const double sp = Tab().ViewMax() - Tab().ViewMin(); - const double timeStep = NiceStep((sp > 0.0) ? sp : 1.0, kTimeTickTarget); + const double fgSpan = savedViewMax - savedViewMin; + const double timeStep = NiceStep((fgSpan > 0.0) ? fgSpan : 1.0, kTimeTickTarget); std::vector timeTicks; BuildTicks(Tab().ViewMin(), Tab().ViewMax(), timeStep, timeTicks); for (std::size_t i = 0; i < m_lanes.size(); ++i) @@ -7088,6 +7120,7 @@ namespace jdi PaintLane(g, static_cast(i), m_lanes[i], timeTicks); } } + m_fastFrame = savedFastFrame; m_renderWide = false; m_area = savedArea; diff --git a/Documents/agent_mem.md b/Documents/agent_mem.md index b08efb3..c80c248 100644 --- a/Documents/agent_mem.md +++ b/Documents/agent_mem.md @@ -354,7 +354,34 @@ Documents/ 项目文档(含本文件) | 透明度/粗细/颜色 | 8–16ms | 8–15ms | - **结论**:用户报告的全部**鼠标**交互(左/中键拖动平移、样式滑块/拾色、弱波增强参数滑块)在该放大区间**全程 ≥60fps、无尖峰**;静止/结算帧与 HEAD **逐像素 0 差异**。 -- **仍存在的残留**:**非拖拽的视界变化**(键盘方向键 / 工具栏,非用户报告的鼠标场景)仍走整幅重绘(约 125ms)—— 因其没有"拖拽结束"事件可用来触发一次精确的全质量重绘,若走宽缓冲快路径会留下 ≤0.5px 的亚像素偏差且无法"结算"回精确帧,故保持整幅重绘以保证像素精确。 +- **当时仍存在的残留**:**非拖拽的视界变化**(键盘方向键 / 工具栏,非用户报告的鼠标场景)仍走整幅重绘(约 125ms)—— 因其没有"拖拽结束"事件可用来触发一次精确的全质量重绘,若走宽缓冲快路径会留下 ≤0.5px 的亚像素偏差且无法"结算"回精确帧,故当时保持整幅重绘以保证像素精确。**该残留已在 9.14 中解决。** + +--- + +### 9.14 非拖拽视界变化(键盘 / 工具栏)也走宽基底快路径 + "结算"帧保证像素精确 +9.13 的残留(非拖拽视界变化 ≈125ms / 8fps)已消除: +- **宽基底判定去掉"必须在拖拽中"**:只要"网格几何指纹 + 逐信号外观样式 + 装饰层"未变(派生曲线参数变化也算),任何**视界变化**都可用宽基底,前台只贴图 + 画时间轴/派生叠加/装饰层 —— 键盘方向键、工具栏缩放平移帧降到 **≈8ms**。 +- **"结算"帧(保证最终像素精确)**:宽基底是整数像素平移,实际视界位置带**≤0.5px** 亚像素偏差。因此在快路径帧里把"内容指纹 ⊕ 视界"揉成一个 `wideKey`;**若本帧的 `wideKey` 与上一条快路径帧相同 → 判定"视界已停止变化" → 放弃快路径,落到整幅重绘**(一次全质量精确帧),并清空 `m_wideLastValid`。该帧同时刷新静态场景缓存,之后所有帧命中缓存(≈1ms),不会反复重绘。 +- **分支顺序有硬约束**:静态场景贴图 → **宽基底** → 样式补丁 → 整幅重绘。 + * 宽基底必须在**静态场景贴图之后**:视界与样式都没变时(鼠标悬停移动)必须命中场景贴图,否则每两帧就会触发一次"结算整幅重绘",悬停反而变卡。 + * 宽基底必须在**样式补丁之前**:派生预览曲线参数变化(拖弱波增强 / 均值滤波滑块)走宽基底比"逐网格补丁"更快且质量更高(补丁会关抗锯齿)。 +- **宽基底必须与"同一视界的整幅渲染"同源**(否则平移后网格/锯齿与前台不一致):基底渲染时**强制全质量抗锯齿**(不受拖拽快速帧影响),且网格竖向刻度沿用**前台视界跨度**而非 2× 的宽窗口跨度(后者会让竖网格线减半)。 +- **实测**(`VERY_BIG_DATA`,视界 `[40.1756, 40.1885]s`,ms/帧): + + | 场景 | 9.13 | 本次 | + | --- | --- | --- | + | **非拖拽视界变化(键盘/工具栏)** | 最大 124.6ms(≈8fps) | **最大 8.6ms** | + | 中键平移拖动(短/长距) | 8.5 / 7.4ms | 9.3 / 14.6ms | + | 透明度 / 颜色滑块 | 8–16ms | 10.6 / 12.5ms | + | 弱波增强滑块 | 最大 10.8ms | **最大 7.5ms** | + | 粗细滑块 | 8–15ms | 40 帧中仅**首帧** 127ms(其余 7.7ms) | + +- **粗细滑块那一帧 127ms 是"设计内"的**:样式连续变化期间走"样式补丁 + 已降级标记"(快),一旦样式**停止变化**(含"回到与缓存相同的值")就必须补一帧全质量 —— 该帧必然是整幅重绘。它是**用户松手后的唯一一帧**,不是持续卡顿。 +- **保真度实测**(新增 `perf.exe … 5` 像素对比模式):快路径与"同一视界的整幅全质量渲染"对比 —— + * 视界几乎不动(<0.01px,不滚动)时:`meanAbs = 0.68/255`(逐像素几乎一致,证明"底座 + 贴图"管线本身忠实)。 + * 平移约 17px 时:`meanAbs ≈ 13.5/255`(差异集中在**密波形**上,是 ≤0.5px 亚像素平移的必然结果,肉眼不可辨);停止平移后的"结算"帧回到**像素精确**。 +- **质量回退检查**:`mean_0/1/2`、`boost_0/1/2`、`panref` 七张静态/结算帧与 HEAD **字节完全一致**(SHA256 相同)。 +- **意外收获**:平移**中途**帧比 HEAD 更干净 —— HEAD 把静态场景(含**图例 / Y 轴刻度**)直接贴进宽基底,平移后这些像素随之移动,出现**重复的网格标题与刻度重影**(见对比 PNG);本次改动后同一场景不再出现重影。 --- @@ -373,3 +400,4 @@ Documents/ 项目文档(含本文件) | 2026-10-11 | DeepSeek-V4.1-Flash | 把宽缓冲推广为**基底复用**:抽出 `PaintLaneDerivedOverlays`、宽缓冲只存基底(不含派生曲线),基底键排除派生参数 → 拖弱波增强滑块 **55ms→5.2ms**;平移 1.3ms、样式 8ms 不变;静止帧逐像素 0 差异。残留:宽缓冲重建在前台(约每屏一次)、非拖拽视界变化仍整幅重绘。 | | 2026-10-11 | DeepSeek-V4.1-Flash | 宽缓冲改为**滚动式**(自贴图平移 + 只补画新暴露细条),消除"移出边界整幅重建"的周期性尖峰;缓冲只覆盖绘图区、时间轴前台绘制、亚像素误差用锚点限制在 ≤0.5px → 持续拖动平移约 6ms/帧、无尖峰(长距最大 10.5ms);静止帧逐像素 0 差异。残留:每次开始拖拽的首帧需新建缓冲(一次启动尖峰)。 | | 2026-10-11 | DeepSeek-V4.1-Flash | 消除"开始拖拽"的首帧尖峰:平移首帧用**静态场景缓存初始化宽基底**(`InitWideBufferFromScene`,贴图 + 细条,不再从零重画 2×视界);启用带预览的实验性功能时**预建基底**。实测:平移拖动最大 8.5ms、弱波增强滑块最大 10.8ms(原分别 35/37.6ms),用户全部鼠标交互在该放大区间**全程 ≥60fps、无尖峰**;静止帧逐像素 0 差异。残留:非拖拽视界变化(键盘/工具栏)仍整幅重绘。 | +| 2026-10-11 | DeepSeek-V4.1-Flash | 消除 9.13 残留:**非拖拽视界变化(键盘/工具栏)也走宽基底快路径**(124.6ms→**8.6ms**),并用 `wideKey` 相等判定"视界已停止变化"触发一次**结算**整幅全质量帧以恢复像素精确;分支顺序固定为"静态贴图 → 宽基底 → 样式补丁 → 整幅",宽基底强制全质量抗锯齿且竖向刻度用前台跨度。新增像素对比模式验证:底座管线忠实(meanAbs 0.68/255),平移期仅 ≤0.5px 亚像素差;7 张静态/结算帧与 HEAD **字节一致**;平移中途帧不再有图例/刻度重影。 |