网格底衬永不空白 + 动画流畅性根治 + 光标随首个信号重现 + 「放大自动点线」新判据
一、网格底衬消失(半屏/局部/全屏,且要等一会才刷新好)—— 两个机制叠加 1) BlitWideBuffer 对「跨度已过期」的图层原用 StretchBlt 放大占位:深度放大时把一小段像素放大 几十倍 ⇒ 整块网格变成一片无网格的模糊色块(用户看到的就是「网格叠底消失、没有任何内容」)。 修法:删除占位拉伸;对拿不到(完整)图层像素的网格【就地补画网格底衬】 (新增 PaintLaneGridBacking + m_renderGridOnly,复用 PaintLane 的网格部分,跳过波形/散点/ 派生曲线/Y 轴刻度/图例)。底衬代价极低,可每帧执行 ⇒ 网格永不空白/闪烁。 ⚠️ 顺序:先画底衬(GDI+)再 g.GetHDC() 做 BitBlt;只有全部网格都贴不出像素时才交给整幅重绘。 2) 待重画队列按下标顺序消费(一次 1 个)⇒ 上百个网格时可视网格要等上百帧。 修法:RenderPendingLaneRebuilds 里做稳定划分,【可视网格优先】(LaneVisuallyRelevant); 「结算」判据由 LanesAllReadyForView() 改为 LanesVisiblyReadyForView()。 二、过渡动画卡顿/不平顺/直接闪到终态 —— 三处根因 1) 「m_layerRebuildPending 非空就每帧请求再送一帧」:上百个网格时 700ms 内挤出 124 次重绘, 而 WM_TIMER 优先级低于投递消息 ⇒ 动画定时器被饿死(实测 frames=124 / timerTicks=1)。 修法:新增 AnyVisibleLanePending(),只用「还有没有可视网格待补」决定是否请求再送一帧; 离屏网格改为机会式预热(实测 frames=25 / timerTicks=16)。 2) SetTimer 的间隔会被量化到系统 tick(默认 15.6ms):请求 16ms 实际等到下一个 tick ⇒ 实测帧距 ~26ms(只有约 38fps)。修法:kScrollTimerPeriodMs 16 → 10(实测 ~15.5ms ≈ 64fps; 真实消息循环帧距 ~25ms → ~16ms)。实测映射:8→15.8 / 10→15.5 / 12→16.5 / 16→26.4 / 31→37.9。 ⚠️ timeBeginPeriod(1) 在本机无效(已 A/B 验证)。 3) 网格滚动联动 / 「回到最上方」每帧都改 LaneScroll(),而它原本参与「宽基底可用性」判据 (SceneGeometryHash)⇒ 每一帧都退化成整幅重绘。修法:SceneContentHash 新增 includeLaneScroll 形参, 宽基底可用性判据不计网格滚动量/各网格屏幕纵向位置(它们只是图层的贴图落点); 静态场景缓存的指纹仍然计入。实测网格滚动动画单帧 18.36ms → 1.19ms。 三、光标工具激活且没有任何信号绘制时,首个信号出现后按规则重新出现(用户要求) 新增 PlotView::ResetCursorPlacementOnFreshContent()(规则与 CycleCursorMode() 的「首次出现」同源: 单光标置于视图中心;双光标第二条紧邻第一条且落在可见范围内),由 Application::SynchronizeView 的「首次出现可见信号」分支在 FitAll() 之后调用。 实测:单光标 stale=4.60 → 新的视图中心 20.92;双光标 stale=(2.89,7.18) → (20.92,26.07)。 四、「放大自动点线」规则改为:任一小格内数据点少于 5 个即自动切换(用户重新定义) 新增 PlotView::AutoPointLineNeeded()(推翻此前「一屏内数据点 < N×4」):按 GridMinorCellCount() 切成横向小格,只要存在任何一个小格内有效数据点 < kAutoPointLineMinPointsPerCell(=5) 就切「点线」。 实现:先「总有效点 < N×5」鸽巢 O(1) 充分判定;单调时间轴逐小格两次二分取区间(单格累计到 5 个即早退, 另给 kAutoPointLineCellScanCap=512 封顶);非单调时间轴一次有界扫描分桶。保留「视界内至少一个可见点」保护。 ⚠️ 光标吸附仍用旧的整屏判据 SignalSnapEnabled(用户明确要求「其它规则不变」)。 验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/gridanim_main.cpp(gridanim.exe)55/55: [A] 缩放/适应视图/「预热未完成再滚动」等每个用例逐帧断言「每个可视网格都有网格底衬 (有竖直锐边、无模糊占位、无未绘制带)」;[B] 直接驱动 + 真实消息循环两条路径统计每帧耗时、 帧距均值/最大与进度单调性;[C] 光标重现;[D] 自动点线新判据(稠密不切、稀疏切换、恢复原样式)。 - 既有回归:verify 49/49、panelanim 156/156、datacheck 69/69、edgecheck 46/46、errcheck 19/19。 - make release 零错误零警告(使用 TARGET_FILE_NAME=TargetCheck 以避免与用户正在运行的实例冲突)。 文档:Documents/agent_mem.md 新增 §9.42、§11.2 第 117–124 条与变更日志一行。
This commit is contained in:
1 parent
f845616938
commit
34375481de
7 files changed
+702
-168
No files matched your search
@@ -310,11 +310,12 @@ namespace jdi
|
||||
void ReapplyGlobalAutoFitIfEnabled();
|
||||
|
||||
/**
|
||||
* @brief 依据「光标吸附判定」自动启用/恢复「点线」绘制方式。
|
||||
* @brief 依据「每小格数据点数」自动启用/恢复「点线」绘制方式(即“放大自动点线”)。
|
||||
*
|
||||
* 用户要求(2026-10-15):**仅在满足光标吸附判定的信号上**生效 ——
|
||||
* 即“一屏内数据点少于 N×4”(见 `SignalSnapEnabled`)时自动切到「点线」提示
|
||||
* 用户数据点的位置;一旦不再满足(适应视图、或把视界从稀疏区移到稠密区)
|
||||
* 用户 2026-10-17 **重新定义判据**(推翻此前的“一屏内数据点少于 N×4”):
|
||||
* 按**缩放后的横向时间轴网格**把小格切出来,**只要存在任何一个小格内的数据点数
|
||||
* 少于 `kAutoPointLineMinPointsPerCell`(=5)** 就自动切到「点线」提示用户数据点的位置
|
||||
*(见 `AutoPointLineNeeded`);一旦不再满足(适应视图、或把视界从稀疏区移到稠密区)
|
||||
* 自动恢复**原来的**绘制方式。
|
||||
*
|
||||
* ⚠️ 自动启用期间若用户**手动**改了绘制方式(`SignalStyle::autoScatterUserOverride`
|
||||
@@ -354,6 +355,23 @@ namespace jdi
|
||||
*/
|
||||
bool SignalSnapEnabled(const TimeAxis& axis, const SignalData& signal, double timeShift) const;
|
||||
|
||||
/**
|
||||
* @brief 判断某信号在当前视界下是否需要**自动切换到「点线」**(放大自动点线)。
|
||||
*
|
||||
* 用户 2026-10-17 **重新定义**的判据(推翻此前的“一屏内数据点 < N×4”):
|
||||
* 按缩放后的**横向时间轴网格**把小格切出来,**只要存在任何一个小格内的数据点数
|
||||
* 少于 `kAutoPointLineMinPointsPerCell`(=5)**,就返回 true。
|
||||
*
|
||||
* 其余规则不变:不再满足时恢复原绘制方式;用户手动改过绘制方式时不恢复
|
||||
*(见 `UpdateAutoScatterStyles`)。光标吸附**仍用** `SignalSnapEnabled` 的整屏判据。
|
||||
*
|
||||
* @param [IN] axis 该信号使用的时间轴。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] timeShift 该信号的显示时间偏移(`offsetTime`)。
|
||||
* @return 需要自动切换到「点线」返回 true。
|
||||
*/
|
||||
bool AutoPointLineNeeded(const TimeAxis& axis, const SignalData& signal, double timeShift) const;
|
||||
|
||||
/**
|
||||
* @brief 判断某信号在给定可见下标区间内是否存在**落在视界内**的有效采样点。
|
||||
* @param [IN] axis 该信号使用的时间轴。
|
||||
@@ -609,6 +627,20 @@ namespace jdi
|
||||
*/
|
||||
bool CycleCursorMode();
|
||||
|
||||
/**
|
||||
* @brief 当绘图区从「没有任何可见信号」变为「有信号」时,按当前光标模式**重新按规则摆好光标**。
|
||||
*
|
||||
* 用户 2026-10-17 要求:光标工具处于激活状态(单 / 双光标)且当前**没有任何信号被勾选绘制**时,
|
||||
* 下一个信号首次出现时应按“光标出现的规则”重新出现一次,而不是停留在之前的
|
||||
* 残留位置(那时的视界与数据都是空的,残留位置已无意义)。
|
||||
*
|
||||
* 规则与 `CycleCursorMode()` 的“首次出现”完全同源:单光标置于视图中心;
|
||||
* 双光标的第一条置于视图中心、第二条紧邻第一条并保证落在可见范围内。
|
||||
*
|
||||
* @return 确实重新摆放了光标返回 true(光标工具未激活时返回 false)。
|
||||
*/
|
||||
bool ResetCursorPlacementOnFreshContent();
|
||||
|
||||
/**
|
||||
* @brief 判断时间光标是否已启用(单/双光标均为启用)。
|
||||
* @return 启用返回 true。
|
||||
@@ -2574,10 +2606,18 @@ namespace jdi
|
||||
* “宽基底可用性”判据,每一帧都会退化成整幅重绘(质量下降 + 卡顿)。
|
||||
* 因此宽基底判据用 `includeLaneRange = false` 的几何指纹,
|
||||
* 幅值变化改由**逐网格图层自己的指纹**处理(只有该网格需要重画)。
|
||||
* @return 指纹值。
|
||||
*/
|
||||
std::uint64_t SceneContentHash(bool includeStyles = true, bool includeDerived = true,
|
||||
bool includeLaneRange = true) const;
|
||||
* @param [IN] includeLaneScroll 是否把**网格纵向滚动量**与各网格的屏幕纵向位置
|
||||
* (`LaneScroll()` / `lane.rect.Y`)计入指纹。
|
||||
* ⚠️ 静态场景缓存**必须**计入(缓存位图的内容取决于它们);
|
||||
* 但“宽基底可用性”判据**不能**计入:网格滚动只改变各网格的**贴图落点**,
|
||||
* 图层内容完全不变(`BlitWideBuffer` 本来就按 `lane.rect` 逐网格贴图)——
|
||||
* 若计入,则“网格滚动到可视区 / 回到最上方”的每一帧都会退化成整幅重绘
|
||||
* (用户 2026-10-17 报告:滚动动画卡顿)。
|
||||
* @return 指纹值。
|
||||
*/
|
||||
std::uint64_t SceneContentHash(bool includeStyles = true, bool includeDerived = true,
|
||||
bool includeLaneRange = true,
|
||||
bool includeLaneScroll = true) const;
|
||||
|
||||
/**
|
||||
* @brief 场景内容中**不含逐信号外观样式**的指纹。
|
||||
@@ -3033,7 +3073,20 @@ namespace jdi
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @return 至少贴了若干像素返回 true。
|
||||
*/
|
||||
bool BlitWideBuffer(gd::Graphics& g) const;
|
||||
bool BlitWideBuffer(gd::Graphics& g, const std::vector<double>& timeTicks);
|
||||
|
||||
/**
|
||||
* @brief 为单个网格绘制**底衬**(网格底色 + 横向/竖向网格线 + 网格分界线)。
|
||||
*
|
||||
* 不含波形、派生预览曲线、Y 轴刻度与图例 —— 这些要么代价高、要么属于装饰层。
|
||||
* 用途:宽基底快路径中本帧拿不到图层像素的网格用它兜底,保证**网格底衬这种
|
||||
* 廉价绘制永远不会空白/闪烁**(用户 2026-10-17 明确要求)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] timeTicks 当前视界的时间主刻度。
|
||||
* @return 绘制了内容返回 true。
|
||||
*/
|
||||
bool PaintLaneGridBacking(gd::Graphics& g, int laneIndex, const std::vector<double>& timeTicks);
|
||||
/**
|
||||
* @brief 全部图层是否**结构可用**(数量 / 位图尺寸 / 视界跨度都与当前状态一致)。
|
||||
*
|
||||
@@ -3058,6 +3111,29 @@ namespace jdi
|
||||
bool LaneLayerReady(int laneIndex) const;
|
||||
/** @brief 全部网格图层是否都完全就绪(用于判断能否做"结算"整幅精确帧)。 */
|
||||
bool LanesAllReadyForView() const;
|
||||
/**
|
||||
* @brief 该网格是否**在屏幕可视区内**(其纵向带与绘图区相交)。
|
||||
*
|
||||
* ⚠️ 只有可视网格的图层才影响画面。整批重建时必须**优先补画可视网格**,
|
||||
* 否则(上百个网格时)队列按下标顺序推进,可视网格可能要等上百帧才轮到,
|
||||
* 期间画面一直停留在旧的兜底状态(用户报告:需要等一会才正确加载)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 可视返回 true。
|
||||
*/
|
||||
bool LaneVisuallyRelevant(int laneIndex) const;
|
||||
/** @brief 全部**可视**网格图层是否都已就绪(用于判断能否做结算的整幅精确帧)。 */
|
||||
bool LanesVisiblyReadyForView() const;
|
||||
/**
|
||||
* @brief 待重画队列里是否还有**可视**网格。
|
||||
*
|
||||
* ⚠️⚠️ 用它(而不是“队列非空”)来决定“是否要请求宿主再送一帧”:
|
||||
* 上百个网格时队列里会长期残留大量**离屏**网格,若按“队列非空”每帧都请求重绘,
|
||||
* 就会形成持续 1 秒以上、每帧一次的高频重绘(用户在“显示全部信号 → 折起分组”
|
||||
* 时正好会撞上),把动画的 `WM_TIMER`(优先级低于投递消息)饿死 ⇒ 动画卡住 / 闪烁。
|
||||
* 离屏网格改为**机会式**预热:任何一帧(悬停、动画、滚动…)顺手补一个即可。
|
||||
* @return 仍有可视网格待重画返回 true。
|
||||
*/
|
||||
bool AnyVisibleLanePending() const;
|
||||
/** @brief 把某网格排进"待重画"队列(去重;视界/跨度后续变化时会在渲染时再次校验)。 */
|
||||
void QueueLaneRebuild(int laneIndex);
|
||||
/**
|
||||
@@ -3128,6 +3204,15 @@ namespace jdi
|
||||
|
||||
bool m_renderWide = false; /**< 正在渲染信号图层(重入 `PaintLane` 时跳过装饰)。 */
|
||||
|
||||
/**
|
||||
* @brief 本次 `PaintLane` 只绘制「网格底衬」(底色 + 网格线 + 网格分界线)。
|
||||
*
|
||||
* 用于宽基底快路径的兜底:本帧拿不到图层像素的网格必须**立刻**画出正确的网格底衬,
|
||||
* 否则该网格会显示成一块空白(用户 2026-10-17 报告「网格叠底消失」)。
|
||||
* 底色/网格线的绘制代价极低,因此这条兜底路径可以**每帧**执行。
|
||||
*/
|
||||
bool m_renderGridOnly = false;
|
||||
|
||||
/**
|
||||
* @brief 渲染信号图层时,横向网格线(幅值刻度)的「参考跨度 / 参考高度」。
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user