滚动动画根治(巨型信号不再影响滚动)+「N 项」计数点击语义 + 浮动面板首现右上角 + 开关/关闭面板卡顿
一、滚动过渡动画在巨型信号处卡顿 —— 真因是「退化成整幅重绘」 滚动到**从未构建过图层的区域**时,可见网格一个都贴不出图层像素 ⇒ BlitWideBuffer() 返回 false ⇒ 落到 Paint() 末尾的整幅重绘 ⇒ 把本帧全部可见网格的波形(含千万级采样的巨型信号)每帧重画一遍。 实测(MF4 124 网格 + VERY_BIG_DATA 5 网格,点选最后一个信号):单帧 20~35ms,且「可见的巨型信号 网格数」越多越慢(0 个 ≈20ms、5 个 ≈34ms);对照手动拖滚动条只要 ~6ms。 修法:BlitWideBuffer 只要还能画网格底衬(backing 非空)就返回 true, 只有 blits 与 backing 都为空时才交给整幅重绘 —— 这正是「没来得及画信号时就按只有网格叠底的状态继续滚动」。 实测:动画均值 23.0→11.2ms、最坏 35.6→13.1ms;含 5 个巨型信号网格的帧 33→10.7~12.3ms。 二、面板动画帧只重绘面板(额外 −2.7ms/帧) 新增 Application::m_panelDirty:滚动定时器在只有面板动画在跑时置位,Render 走 else if (panelOnly) 只重绘面板 + 分隔条(SignalPanel::Paint 会铺满自己的区域)。m_chromeDirty(真内容变化)仍走完整装饰重绘。 ⚠️ 回送仍是 if (fullFrame) {整窗} else {绘图区} 的无条件分支(遵守 §9.41)。 实测帧耗时拆解:plot 4.8ms / plot+panel 11.4ms / plot+全装饰 13.9ms。 三、分组行「N 项」计数:只折叠展开,绝不进入改名 新增 RowPart::GROUP_COUNT 与 SignalPanel::GroupStatsRect(row)(绘制与命中的唯一几何来源,命中矩形在 文本左右各外扩 3 逻辑像素)。RowHitPart 在名称区域之前判定计数区: - OnLButtonDown:折叠/展开;OnDoubleClick:只折叠/展开,任何点击频率都不进入改名; - DoubleClickIsPlainPress 返回 true ⇒ 快速连点每一击都生效。 ⚠️「快速双击进改名」的计时只对 RowPart::BODY 记录(否则“点计数、紧接着点名称”会被误判成双击)。 四、浮动面板首次出现的位置:右上角 旧默认位置 top = plotTop + 16 + laneIndex*28:上百个网格时 laneIndex 很大 ⇒ 被夹到右下角 (实测 plotH=1081 时 topMargin=964)。改为按「当前真正显示出来的面板中排在本面板之前的个数」错开 (固定遍历顺序:网格下标 → 面板种类),并把错开量夹到“面板下沿不超过绘图区 45% 高度”以内: 第一个面板错开 0 ⇒ 正好贴在上右角(实测 topMargin=24、rightMargin=24),6 个面板时最坏 topMargin=234。 ⚠️ 判定「面板是否显示」不能只看 *Panel.open —— LanePanelState::open 默认就是 true,必须同时看功能启用。 五、开关实验性功能 / 关闭面板卡顿 —— RebuildWideBuffer() 整批重建 四个 SetLane* 末尾都调 RebuildWideBuffer()(注释说是“预建宽缓冲基底”),而它同步重渲染全部网格的图层: 129 个网格(含 5 个千万级采样的巨型信号)整批重建要几百毫秒 ⇒ 点一下功能开关 / 关闭浮动面板就会卡住。 修法:新增 PlotView::EnsureLaneLayersPrefetched() —— 只有「图层数组与网格数量不符」(首次绘制 / 增删网格) 才同步整批建立;其余只 RollWideBuffer() 登记缺失/过期网格,由既有预算(每帧最多一个)逐帧补齐。 实测:几百毫秒 → ≤22ms;巨型信号网格上开关均值滤波同样 15ms 量级。 验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/lanescroll_main.cpp(lanescroll.exe)23/23:逐帧耗时 + 「可见巨型信号网格数」关联、 「N 项」计数热区与双击不进改名、面板首现位置与多面板错开、开着巨型信号的开关/关闭面板耗时。 - 既有回归:verify 49/49、panelanim 158/158、datacheck 69/69、edgecheck 46/46、errcheck 19/19、 gridanim 93/93。 - make release / make debug 零错误零警告(用 TARGET_FILE_NAME=TargetCheck/TargetCheckD 避免与运行中实例冲突)。 文档:Documents/agent_mem.md 新增 §9.44、§11.2 第 134–140 条与变更日志一行。
This commit is contained in:
1 parent
436dcbfd52
commit
6b15575829
10 files changed
+352
-38
No files matched your search
@@ -468,6 +468,19 @@ namespace jdi
|
||||
* false,从而每帧只重绘、只回送绘图区所在的矩形,避免整窗重绘的开销。
|
||||
*/
|
||||
bool m_chromeDirty = true;
|
||||
/**
|
||||
* @brief 「只有信号面板(与分隔条)需要重绘」。
|
||||
*
|
||||
* 面板滚动动画(「滚动到目标信号」)每帧只改变面板像素,而 `m_chromeDirty` 会
|
||||
* 连**标签栏 / 工具栏 / 整窗底色**一起重绘 —— 实测 129 个网格时这一份装饰重绘
|
||||
* 约 **3~4ms/帧**,会把动画挤出 15.6ms 的预算(用户 2026-10-17 报告:滚动过渡动画卡顿)。
|
||||
*
|
||||
* ✓ 由滚动定时器在**只有面板动画在跑**时置位:`Render` 只重绘面板 + 分隔条
|
||||
*(标签栏/工具栏/整窗底色与上一帧逐像素相同,后备缓冲区里的旧像素本来就是对的)。
|
||||
* ⚠️ 回送仍是 `if (fullFrame) {整窗} else {绘图区}` 的**无条件分支**,
|
||||
* 与 §9.41 的教训一致(**不**按更新区域决定回送范围)。
|
||||
*/
|
||||
bool m_panelDirty = false;
|
||||
bool m_mouseOverPlot = false; /**< 上一次鼠标移动是否位于绘图区内。 */
|
||||
};
|
||||
|
||||
|
||||
@@ -409,8 +409,16 @@ namespace jdi
|
||||
// ✓ 把动画状态直接纳入重绘条件(**只增加重绘条件,不改变任何绘制/回送路径**)。
|
||||
const bool panelAnimating = m_panel.ScrollAnimationActive() || m_panel.GroupAnimationActive();
|
||||
const bool overlaysVisible = m_menu.IsOpen() || m_colorPicker.IsOpen();
|
||||
const bool chromeDirty = m_chromeDirty || m_loading || overlaysVisible || panelAnimating;
|
||||
// ⚠️ 两部分分开:
|
||||
// * `fullChrome` —— 标签栏 / 工具栏 / 整窗底色 / 面板都要重绘(有真内容变化);
|
||||
// * `panelOnly` —— **只有面板像素变了**(面板滚动动画),标签栏/工具栏/整窗底色
|
||||
// 与上一帧逐像素相同,没必要重绘(实测 129 网格时约 3~4ms/帧)。
|
||||
const bool fullChrome = m_chromeDirty || m_loading || overlaysVisible;
|
||||
const bool panelOnly = !fullChrome && (panelAnimating || m_panelDirty);
|
||||
m_chromeDirty = false;
|
||||
m_panelDirty = false;
|
||||
// 仍需要“重绘装饰区域”这个判定(决定回送整窗还是只回送绘图区)。
|
||||
const bool chromeDirty = fullChrome || panelOnly;
|
||||
// 提示浮层淡出由定时器驱动,其绘制范围局限在绘图区,但为稳妥起见整帧回送。
|
||||
const bool fullFrame = chromeDirty || m_plot.HintNeedsAnimation();
|
||||
|
||||
@@ -426,7 +434,7 @@ namespace jdi
|
||||
// 鼠标离开窗口(WM_MOUSELEAVE)、数据/行结构变化、切换标签页等。
|
||||
m_plot.SetExternalHoverSignal(m_panel.HoveredSignal());
|
||||
|
||||
if (chromeDirty)
|
||||
if (fullChrome)
|
||||
{
|
||||
UiFillRect(g, gd::RectF(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)),
|
||||
m_theme.windowBg);
|
||||
@@ -436,6 +444,12 @@ namespace jdi
|
||||
m_panel.Paint(g);
|
||||
UiFillRect(g, m_splitterRect, m_theme.border);
|
||||
}
|
||||
else if (panelOnly)
|
||||
{
|
||||
// 只有面板像素变了 ⇒ 只重画面板与分隔条(`SignalPanel::Paint` 会铺满自己的区域)。
|
||||
m_panel.Paint(g);
|
||||
UiFillRect(g, m_splitterRect, m_theme.border);
|
||||
}
|
||||
|
||||
m_plot.Paint(g);
|
||||
|
||||
@@ -1514,6 +1528,12 @@ namespace jdi
|
||||
m_plot.AdvanceBackToTopAnimation();
|
||||
if (panelAnimating)
|
||||
{
|
||||
// 面板动画:只有面板像素会变(且通常绘图区动画同时也在跑)⇒
|
||||
// 用 `m_panelDirty` 让 `Render` 只重画面板 + 分隔条,而**不是**把
|
||||
// 标签栏/工具栏/整窗底色也重画一遍(约 3~4ms/帧)。
|
||||
// ⚠️ 用**推进之前**的状态判定:动画结束那一帧仍需整窗重绘,
|
||||
// 保证面板的最终位置被完整重绘并回送。
|
||||
m_panelDirty = !m_chromeDirty;
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
else
|
||||
|
||||
Reference in new issue
Block a user