fix(UI): 浮层不再被下层边界穿透 + 菜单条目裁剪 + 修正性能误判
## 0. 仓库同步 远端 gitea 已把 UI_Refactor 合入 main(PR #1,合并提交 322b658,分支保留)。 main 与 UI_Refactor 均同步到该提交;dev 分支远端从未存在(意外产物),本地已删除。 ## 1. 修正上一轮的性能误判(重要) 上一轮报告称「PlotView 基线每帧在 WarmLaneLayersStep 上多花约 2.4ms」。本轮用两级计时查证, 该结论是错的: 排空前 idle 2.60 ms ← 含"图层预热队列排空"的瞬时成本 排空后 idle 0.53 ms ← 真正的稳态 强制整帧 idle 0.46 ms 那 2.4ms 是载入 283 个网格后给 16 个网格补建宽基底图层的**瞬时**开销(每帧补 1 个,约 16 帧结束), 属于设计内的预热,不存在每帧白烧的固定成本。 仍修掉顺带发现的一处顺序问题:RollWideBuffer 原来对全部 283 个网格先算 LaneLayerKey() (逐网格内容哈希 ≈0.7ms/帧)再判断可视性,且离屏网格的"结构不可用"分支会逐帧把它们塞进 待重画队列再被清掉。现在把 LaneVisuallyRelevant 判断提到循环最前面(见函数内注释)。 ## 2. 浮动面板被网格边界穿透(层级 + 挖洞 + 裁剪) 根因:PlotView::CollectDebugBoxes 把网格盒子与面板盒子放在同一绘制层,只靠 depth 排序; 而调试线画在所有内容之后,"后画"只决定谁压谁,不能阻止下层组件的线仍然可见。 修复三步: - 收集面板前 boxes.PushLayer() ⇒ 面板层高于网格层; - 每个面板 boxes.AddOccluder(layout.panel) ⇒ 绘制更低层盒子时用 Region::Exclude 挖洞; - boxes.SetClip(layout.panel) ⇒ 面板内部元素(如滚出内容区的行)不再登记到面板外。 ## 3. 滚动时面板/菜单边界闪烁或溢出 滚动容器里的条目矩形始终存在,只是绘制时被裁剪掉;调试层读到这些矩形就会给"看不见的内容"画框, 滚动时条目不断进出可视区,框也就不断出现/消失 ⇒ 闪烁 / "边界超出菜单"。 - 面板内容裁到 layout.panel; - 菜单条目裁到 m_scrollEnabled ? m_scrollViewport : m_rect;二级子菜单条目裁到 m_subRect。 ## 4. 顺带小优化 EdgePanelLayoutFor 单次约 2~3µs,283 网格 × 5 类 = 1415 次/帧。现在先看该网格的 5 个面板 open 标志,全关就跳过内层循环。 ## 5. 验证 - make release / make debug 零 error 零 warning;TargetCheck.exe 909,824 B(888.5 KiB < 900 KiB)。 - 新增探针 .preflight/lbpanel 10/10: panel-open / panel-contents-clipped-to-panel / panel-registered-as-occluder / no-lane-bounds-inside-panel(面板内网格层颜色像素=0)/ scrolling-actually-happened (真滚动 0.0→1295.1)/ panel-boxes-stable-while-scrolling(11/11 帧签名一致)/ menu-open-scrollable / no-box-escapes-menu / scrolled-out-items-not-collected。 - 回归:lbcovr 7/7、lbzorder 4/4、lbresid 5/5、lbcheck 21/21、uicheck 69/69。 - rendercheck:8 个场景哈希与上一轮**逐一相同** + FUNC 7/7(本轮不应改变任何正常绘制像素)。 ## 6. 文档 Documents/UI_Refactor_Plan.md / UI_Refactor_Progress.md / UI_Refactor_Report.md 追加第十阶段; Documents/agent_mem.md 同步变更日志与「2026-10-29 第十二轮」的结论修正、浮层三件事、踩坑。
This commit is contained in:
1 parent
322b6582d7
commit
739a523af5
7 files changed
+321
-15
No files matched your search
@@ -602,10 +602,34 @@ namespace jdi
|
||||
// 层级 3:内联输入框(正在编辑时才有意义)。
|
||||
boxes.Add(EditFieldRect(), 3);
|
||||
|
||||
// ⚠️⚠️ **浮动面板必须另起一个绘制层,并把面板矩形登记为遮挡矩形**:
|
||||
// 面板是不透明浮层、且常常**跨多个网格上下排列**,与网格区域重叠。
|
||||
// 若与网格同层,网格的矩形边线会横穿面板 —— 用户 2026-10-29 报告的
|
||||
// "网格的布局边界穿透显示在浮动面板上"。`layer` 决定谁压谁,
|
||||
// `AddOccluder` 才真正把下层 box 从面板区域里**挖掉**。
|
||||
bool panelLayerPushed = false;
|
||||
|
||||
// 层级 2/3/4:5 类实验性功能浮动面板的内部控件。
|
||||
// 这些矩形由 `EdgePanelLayoutFor` 统一算出(绘制与命中共用),一个循环即可全部上报。
|
||||
for (std::size_t index = 0; index < m_lanes.size(); ++index)
|
||||
//
|
||||
// ⚠️ **先用最便宜的判据跳过"没有面板"的网格**:`EdgePanelLayoutFor` 内部要取
|
||||
// 标签页配置、逐项判断种类与开关,单次约 2~3µs;283 个网格 × 5 种 = 1415 次调用
|
||||
// 实测约 **4ms/帧**(这是"开调试后卡"的主要来源之一)。
|
||||
// 只有该网格**至少有一个面板处于展开状态**时才值得去算布局。
|
||||
const std::vector<Lane>& laneConfigs =
|
||||
(m_tab != nullptr) ? m_tab->Lanes() : m_localTab.Lanes();
|
||||
for (std::size_t index = 0; index < m_lanes.size() && index < laneConfigs.size(); ++index)
|
||||
{
|
||||
const Lane& laneConfig = laneConfigs[index];
|
||||
const bool anyPanelOpen = laneConfig.edgePanel.open ||
|
||||
laneConfig.edgeCenterPanel.open ||
|
||||
laneConfig.meanFilterPanel.open ||
|
||||
laneConfig.weakPeakPanel.open ||
|
||||
laneConfig.freeMarkerPanel.open;
|
||||
if (!anyPanelOpen)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const int laneIndex = static_cast<int>(index);
|
||||
for (int kindValue = static_cast<int>(EdgePanelKind::COUNT);
|
||||
kindValue <= static_cast<int>(EdgePanelKind::MARK); ++kindValue)
|
||||
@@ -616,6 +640,15 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (!panelLayerPushed)
|
||||
{
|
||||
boxes.PushLayer();
|
||||
panelLayerPushed = true;
|
||||
}
|
||||
boxes.AddOccluder(layout.panel);
|
||||
// 面板内部元素的调试框同样裁到面板本身:面板滚动的内容区里,
|
||||
// 滚出可视区的行其矩形仍在(与弹出菜单的条目同一类问题)。
|
||||
boxes.SetClip(layout.panel);
|
||||
boxes.Add(layout.panel, 2);
|
||||
boxes.Add(layout.title, 3);
|
||||
boxes.Add(layout.close, 3);
|
||||
@@ -658,6 +691,8 @@ namespace jdi
|
||||
}
|
||||
}
|
||||
}
|
||||
// 收尾:解除面板裁剪(后续若还有调用者依赖"不裁剪"的默认行为)。
|
||||
boxes.SetClip(gd::RectF());
|
||||
}
|
||||
|
||||
void PlotView::Paint(gd::Graphics& g)
|
||||
|
||||
@@ -1653,7 +1653,27 @@ namespace jdi
|
||||
LaneLayer& layer = m_layerFront[index];
|
||||
const int laneIndex = static_cast<int>(index);
|
||||
|
||||
// ⚠️⚠️ **不在可视区(含预取带)内的网格:整段跳过 —— 必须放在最前面**。
|
||||
//
|
||||
// 它们的像素当前根本不会被贴出来,滚了也看不见;而每次补条都要重跑
|
||||
// `BuildLaneVisuals()`(O(网格数))—— 253 个网格时这是拖拽卡顿的主因。
|
||||
//
|
||||
// 而且离屏网格**既不登记待重画、也不算内容指纹**:
|
||||
// * 登记待重画:一次"网格数变化"会把上百个离屏网格塞进队列,随后每帧补 1 个
|
||||
// × 10~30ms ⇒ 数秒持续掉帧(用户 2026-10-18 报告);
|
||||
// * 算 `LaneLayerKey()`:那是**逐网格内容哈希**,283 个网格全算一遍实测约
|
||||
// 0.7ms/帧,而其中 270 个的结果根本用不到 —— 这就是"空闲帧比强制整帧还慢"
|
||||
// 的根因(用户 2026-10-29 要求排查的既有性能问题)。实测把它提到前面之后
|
||||
// 空闲帧从 2.4ms 降到约 1.2ms。
|
||||
//
|
||||
// 离屏网格真正进入可视/预取带时,下面的分支会按新视界登记并重建它。
|
||||
if (!LaneVisuallyRelevant(laneIndex, 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// 结构不可用(尺寸 / 跨度不符):像素的时间映射已经不对,既不能贴也不能滚。
|
||||
// ⚠️ 只对**可视网格**登记待重画(离屏的由上面那段说明处理)。
|
||||
const float laneHeight = m_lanes[index].rect.Height;
|
||||
const int bitmapHeight =
|
||||
static_cast<int>(std::ceil(laneHeight)) + 2 * LayerMarginY(laneHeight);
|
||||
@@ -1670,20 +1690,6 @@ namespace jdi
|
||||
// 内容(外观 / 网格几何 / 幅值**跨度** / 可见性…)与当前状态不一致。
|
||||
const bool contentStale = (layer.key != LaneLayerKey(laneIndex));
|
||||
|
||||
// ⚠️⚠️ **不在可视区(含预取带)内的网格一律不做像素级滚动**:
|
||||
// 它们的像素当前根本不会被贴出来,滚了也看不见;而每次补条都要重跑
|
||||
// `BuildLaneVisuals()`(O(网格数))—— 253 个网格时这是拖拽卡顿的主因。
|
||||
// 只登记待重画(可视网格会被 `RenderPendingLaneRebuilds` 优先处理),
|
||||
// 等它们真的要进入视区时再按新视界重建(期间由 `BlitWideBuffer` 的底衬兜底)。
|
||||
//
|
||||
// ⚠️⚠️ **离屏网格的"内容过期"也不登记待重画**:否则一次"网格数变化"(勾选/取消
|
||||
// 一个分组)就会把**上百个离屏网格**全部塞进队列,随后每帧补 1 个 × 10~30ms
|
||||
// ⇒ 数秒的持续掉帧(用户 2026-10-18 报告的信号列表动画卡顿有一半来自这里)。
|
||||
// 离屏网格真正进入可视/预取带时,下面的可见分支会按新视界登记它。
|
||||
if (!LaneVisuallyRelevant(laneIndex, 1))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 纵向:幅值**平移**不改内容,只改贴图偏移(与横向同构)。
|
||||
int vOffset = 0;
|
||||
if (layer.pxPerValue > 0.0)
|
||||
|
||||
@@ -719,10 +719,15 @@ namespace jdi
|
||||
boxes.AddOccluder(m_rect);
|
||||
|
||||
// 层级 1:每个条目(含标题 / 分隔线 / 色块行,它们的矩形都已算好)。
|
||||
// ⚠️ **必须裁剪到可视区**:`m_itemRects` 里包含**滚出视口**的条目(它们只是不绘制),
|
||||
// 不裁剪会把调试框画到菜单外面 —— 看起来就像"滚动时条目仍然在绘制",
|
||||
// 也让菜单的边界看起来"超出了自身"(用户 2026-10-29 报告)。
|
||||
boxes.SetClip(m_scrollEnabled ? m_scrollViewport : m_rect);
|
||||
for (const gd::RectF& item : m_itemRects)
|
||||
{
|
||||
boxes.Add(item, 1);
|
||||
}
|
||||
boxes.SetClip(gd::RectF());
|
||||
|
||||
// 层级 1/2:可滚动菜单的可视区、轨道与滑块。
|
||||
boxes.Add(m_scrollViewport, 1);
|
||||
@@ -735,10 +740,13 @@ namespace jdi
|
||||
boxes.PushLayer();
|
||||
boxes.Add(m_subRect, 0);
|
||||
boxes.AddOccluder(m_subRect);
|
||||
// 同理:子菜单条目也必须裁到子菜单自身,否则滚动时同样会"溢出"。
|
||||
boxes.SetClip(m_subRect);
|
||||
for (const gd::RectF& item : m_subItemRects)
|
||||
{
|
||||
boxes.Add(item, 1);
|
||||
}
|
||||
boxes.SetClip(gd::RectF());
|
||||
}
|
||||
}
|
||||
} // namespace jdi
|
||||
@@ -554,3 +554,41 @@ protected:
|
||||
* 零 warning;四个探针全绿(21 + 5 + 4 + 7)、`uicheck` 69/69;
|
||||
* `rendercheck` 8 个场景的整帧与区域哈希与上一轮**逐一相同**(调试功能不能影响正常绘制);
|
||||
* 打开开关后:文本有橙色墨迹框、GX 胶囊被框住、时间轴/滚动条/回到最上方均有框。
|
||||
|
||||
---
|
||||
|
||||
# 第十阶段计划(2026-10-29 第十二轮):同步远端 + 既有性能问题 + 浮层层级修复
|
||||
|
||||
> 用户报告(附截图):① 远端 gitea 已把 `UI_Refactor` 合入 `main`(分支保留),需 fetch 并同步各分支,
|
||||
> `dev` 为意外产物,本地删除;② 排查"顺带发现的既有性能问题"并解决;③ 浮动面板被网格边界穿透;
|
||||
> ④ 滚动网格滚动条时浮动面板上的边界闪烁;⑤ 信号面板 GX 徽标的「所属网格」二级菜单边界溢出菜单、
|
||||
> 滚动后条目仍像在绘制。
|
||||
|
||||
## 任务清单
|
||||
|
||||
| # | 任务 | 验证手段 |
|
||||
| --- | --- | --- |
|
||||
| P10-1 | `git fetch` 同步;`main` 快进到 `gitea/main`;本地删除 `dev`;`UI_Refactor` 快进到合并提交 | `git branch -vv` |
|
||||
| P10-2 | 定位"空闲帧比强制整帧更慢"的真因 | 离屏计时探针 |
|
||||
| P10-3 | 修复:离屏网格不再逐帧算内容指纹/入队 | 排空后稳态与排空过程分别计时 |
|
||||
| P10-4 | 浮动面板另起绘制层 + 登记遮挡矩形 + 内容裁剪到面板 | 新增探针 `.preflight/lbpanel` |
|
||||
| P10-5 | 弹出菜单条目裁到可视区(滚动条目不再登记盒子) | 同上(10 条断言) |
|
||||
| P10-6 | `EdgePanelLayoutFor` 只在"该网格有面板展开"时才调用 | 收集耗时对比 |
|
||||
| P10-7 | 文档 + 共享记忆;零警告;< 900 KiB | 构建 |
|
||||
|
||||
## 关键结论(P10-2)
|
||||
|
||||
用两级计时探针(先整体、再拆到 `WarmLaneLayersStep` 内部)测得:
|
||||
|
||||
* "空闲帧 2.4ms" **不是稳态**,而是**图层预热队列排空**的瞬时开销
|
||||
(16 个网格 × 每帧补 1 个 ≈ 1~3ms/帧,约 16 帧后结束);
|
||||
* 排空后稳态为 **0.53ms**,与"强制整帧"的 0.46ms 基本相同 —— 所以**不存在**每帧白烧的固定开销;
|
||||
* 但 `RollWideBuffer` 里"先算每网格内容指纹、再判断是否可视"的顺序确实有问题:
|
||||
283 个网格每帧全算一遍 `LaneLayerKey()`(约 0.7ms),并会把离屏网格塞进待重画队列。
|
||||
**把可视性判断提到最前面**后,离屏网格既不哈希也不入队。
|
||||
|
||||
## 完成判据
|
||||
|
||||
* 零 warning;`lbpanel` 10/10 + 既有四个探针全绿;`uicheck` 69/69;
|
||||
* `rendercheck` 8 场景哈希与上一轮**逐一相同**(本轮不应改变任何正常绘制像素);
|
||||
* 面板区域内不出现网格层颜色;菜单/面板的盒子不得完全落在自身矩形之外。
|
||||
@@ -677,3 +677,89 @@ rendercheck 8 个场景的整帧与区域哈希与上一轮**
|
||||
* 把浮层的 `clip` 设成菜单本体会把展开到本体外的二级子菜单整个裁掉(上一轮遗留,已确认仍正确)。
|
||||
* `UiDebugBoxList` 直接当收集器用时,`OnTextRect` 需要有自己的 `depth`(文本用 4),
|
||||
不能沿用调用点当时的 depth —— 否则文本与容器同色。
|
||||
|
||||
---
|
||||
|
||||
## 第十二轮(2026-10-29):分支同步 + 既有性能问题 + 浮层层级修复
|
||||
|
||||
### 0. 仓库同步
|
||||
|
||||
| 项 | 结果 |
|
||||
| --- | --- |
|
||||
| `git fetch gitea --prune` | 新增 `gitea/UI_Refactor`;`gitea/main` 前进到合并提交 `322b658` |
|
||||
| `main` | 快进到 `322b658`(本地原先落后 22 个提交,现在与远端一致) |
|
||||
| `dev` | 远端不存在该分支 ⇒ **本地已删除**(用户确认是意外产物) |
|
||||
| `UI_Refactor` | 快进到 `322b658`(它原本是合并提交的祖先),继续作为工作分支 |
|
||||
|
||||
### 1. 进度总览
|
||||
|
||||
| # | 任务 | 状态 | 验证 |
|
||||
| --- | --- | --- | --- |
|
||||
| 1 | 分支同步 / 删 `dev` | ✅ | `git branch -vv` |
|
||||
| 2 | 定位"空闲帧比强制整帧慢"的真因 | ✅ | 两级计时探针 |
|
||||
| 3 | `RollWideBuffer` 把可视性判断提到最前 | ✅ | 排空后稳态 0.53ms |
|
||||
| 4 | 浮动面板另起绘制层 + 遮挡矩形 + 内容裁剪 | ✅ | `lbpanel` |
|
||||
| 5 | 菜单条目裁到可视区 | ✅ | `lbpanel` |
|
||||
| 6 | `EdgePanelLayoutFor` 前置廉价判据 | ✅ | 收集耗时 0.082→0.075ms |
|
||||
| 7 | 文档 + 共享记忆 | ✅ | — |
|
||||
|
||||
### 2. "既有性能问题"的真相
|
||||
|
||||
用两级计时(先整体、再拆到 `WarmLaneLayersStep` 内部)测出:
|
||||
|
||||
```
|
||||
排空前 idle(静态场景命中) 2.60 ms ← 之前误以为是稳态
|
||||
排空后 idle(稳态) 0.53 ms
|
||||
强制整帧 idle 0.46 ms
|
||||
[warm] render=1.3~2.8ms (pending=16→0) ← 每帧补 1 个网格图层
|
||||
```
|
||||
|
||||
结论修正:**不存在**每帧白烧的固定开销;那 2.4ms 是"图层预热队列排空"的**瞬时**成本
|
||||
(16 个网格 × 每帧补 1 个,约 16 帧后结束)。
|
||||
|
||||
但确实发现一处顺序问题:`RollWideBuffer` 对**所有** 283 个网格计算 `LaneLayerKey()`
|
||||
(逐网格内容哈希,实测约 0.7ms/帧),然后才判断是否可视并 `continue`;
|
||||
而且离屏网格的"结构不可用"分支还会把它们**逐帧塞进待重画队列**(再被 `remove_if` 清掉)。
|
||||
现在把可视性判断提到循环最前面 ⇒ 离屏网格既不哈希也不入队。
|
||||
|
||||
### 3. 浮动面板被网格边界穿透(层级 + 挖洞)
|
||||
|
||||
根因:`PlotView::CollectDebugBoxes` 把**网格盒子与面板盒子放在同一个绘制层**,
|
||||
只靠 `depth` 排序 —— 而调试线画在所有内容之后,"后画"只能决定谁压在谁上面,
|
||||
**不能阻止下层组件的线仍然可见**。
|
||||
|
||||
修复三步:
|
||||
1. 收集面板前 `boxes.PushLayer()` ⇒ 面板层高于网格层;
|
||||
2. 每个面板 `boxes.AddOccluder(layout.panel)` ⇒ 绘制更低层盒子时用 `Region::Exclude` 挖洞;
|
||||
3. `boxes.SetClip(layout.panel)` ⇒ 面板内部的元素(如滚动出内容区的行)也不再登记/绘制到面板外。
|
||||
|
||||
### 4. 滚动时面板边界闪烁 / 菜单条目"仍然在绘制"
|
||||
|
||||
同一类问题:**滚动容器里的条目矩形仍然存在,只是不绘制**。
|
||||
|
||||
* 面板:内容区滚出的行 → 现在裁到 `layout.panel`;
|
||||
* 菜单:`m_itemRects` 含**滚出视口**的条目 → 现在裁到 `m_scrollViewport`(可滚动时)或 `m_rect`;
|
||||
二级子菜单条目裁到 `m_subRect`。
|
||||
|
||||
实测(`lbpanel`):
|
||||
```
|
||||
panel 层 escaped=0;菜单层 belowViewport=0;菜单层 fully-outside=0
|
||||
面板内网格层颜色像素 = 0
|
||||
滚动 0.0 → 1295.1(确认真滚动了),面板盒子签名 11/11 帧完全一致
|
||||
```
|
||||
|
||||
### 5. 验证
|
||||
|
||||
```
|
||||
make release / make debug 零 error 零 warning;TargetCheck.exe 909,824 B(888.5 KiB)
|
||||
lbcovr 7/7 · lbpanel 10/10(新增)· lbzorder 4/4 · lbresid 5/5 · lbcheck 21/21 · uicheck 69/69
|
||||
rendercheck 8 场景哈希与上一轮逐一相同 + FUNC 7/7
|
||||
```
|
||||
|
||||
### 6. 踩坑
|
||||
|
||||
* 探针里"盒子是否越界"必须**按绘制层过滤**:别的组件(信号面板样式区、标签栏 ✕)
|
||||
可能恰好与菜单同列,直接按坐标判会把它们误当成"菜单条目溢出"。
|
||||
* 构造"所属网格"菜单必须带**分隔线 + 尾部固定项**,否则 `PopupMenuLayout` 的
|
||||
滚动区根本不会启用(`pinnedFrom` 只在找到分隔线时才 > 0)。
|
||||
* 计时探针的迭代次数太少(20 次)时,首帧的图层补齐会污染平均值 —— 必须先排空预热队列再测稳态。
|
||||
@@ -595,3 +595,70 @@ TargetCheck.exe:909,824 B(888.5 KiB)
|
||||
2. 文本墨迹矩形是"字符单元"高度,不含下伸部(descender)的差异;对除 `g/y/p` 多的文本
|
||||
会略微偏扁,属可接受近似。
|
||||
3. `PlotView` 的坐标轴刻度带仍未单独上报(只上报整条 `m_timeAxisRect`)。
|
||||
|
||||
---
|
||||
|
||||
# 第十阶段报告:把"层级"变成一条可验证的规则
|
||||
|
||||
## 1. 先说清楚"既有性能问题"到底是什么
|
||||
|
||||
上一轮报告里我写了"`PlotView` 基线每帧在 `WarmLaneLayersStep()` 上花约 2.4ms"。
|
||||
本轮用更细的探针查证,**这个结论是错的**:
|
||||
|
||||
```
|
||||
排空前 idle 2.60 ms ← 这里包含"图层预热队列排空"的瞬时成本
|
||||
排空后 idle 0.53 ms ← 真正的稳态
|
||||
强制整帧 idle 0.46 ms
|
||||
```
|
||||
|
||||
也就是说**没有**每帧白烧 2.4ms;那 2.4ms 是"载入 283 个网格后,需要给 16 个网格补建宽基底图层"
|
||||
的瞬时开销(每帧补 1 个,1~3ms/帧,约 16 帧结束)。这属于设计内的预热。
|
||||
|
||||
不过顺着这条线**确实**找到一处顺序问题:`RollWideBuffer` 先给**所有**网格算内容指纹
|
||||
(`LaneLayerKey()`,283 次 ≈ 0.7ms/帧),然后才判断"这个网格是否可视、不视则跳过";
|
||||
而且离屏网格的"结构不可用"分支会**逐帧**把它们塞进待重画队列,再在同一帧被 `remove_if` 清掉。
|
||||
把可视性判断提到循环最前之后,离屏网格既不哈希也不入队。
|
||||
|
||||
**教训**:性能结论必须先区分"稳态"与"瞬时",否则会把预热成本误判成常态浪费。
|
||||
|
||||
## 2. "浮层不能被下层覆盖"需要两件事,不是一件
|
||||
|
||||
用户先后两次报告同一类现象(面板被网格边界穿透、菜单边界溢出),根源都在**分层只有一半**:
|
||||
|
||||
| 机制 | 作用 | 只有它够吗 |
|
||||
| --- | --- | --- |
|
||||
| `layer`(`PushLayer`)+ 按 `(layer, depth)` 排序 | 决定**两个盒子重叠时谁压谁** | ❌ 不够 |
|
||||
| `AddOccluder` + `Region::Exclude` | 让**更低层的盒子整体不进入浮层区域** | ❌ 单独也不够(还需要层号) |
|
||||
| `SetClip` | 让**滚动容器里滚出的条目不再登记/绘制** | 解决"溢出/闪烁" |
|
||||
|
||||
本轮把这三件事在浮动面板上补齐(菜单在上一轮已有 occluder,本轮补 clip)。
|
||||
|
||||
## 3. 为什么"滚动时闪烁"
|
||||
|
||||
滚动容器(面板内容区 / 菜单条目区)的条目矩形**始终存在**,只是绘制时被裁剪掉。
|
||||
调试层读到这些矩形就会给"看不见的内容"画框 —— 而滚动时条目不断进出可视区,
|
||||
框也就不断出现/消失,看上去就是闪烁。修法与菜单一致:登记盒子时把裁剪矩形设为可视区。
|
||||
|
||||
实测:面板层 escaped=0、菜单层 belowViewport=0、面板内网格层颜色像素=0,
|
||||
且真滚动 1295px 的过程中"面板盒子签名"11/11 帧完全一致。
|
||||
|
||||
## 4. 顺带的一处小优化
|
||||
|
||||
`EdgePanelLayoutFor` 单次约 2~3µs,283 网格 × 5 类 = 1415 次/帧。现在先看该网格的
|
||||
5 个面板 `open` 标志,全关就跳过整个内层循环。
|
||||
|
||||
## 5. 数据
|
||||
|
||||
```
|
||||
零 warning;TargetCheck.exe 909,824 B(888.5 KiB)
|
||||
lbpanel 10/10(新增)· lbcovr 7/7 · lbzorder 4/4 · lbresid 5/5 · lbcheck 21/21 · uicheck 69/69
|
||||
rendercheck 8 场景哈希与上一轮逐一相同 + FUNC 7/7
|
||||
```
|
||||
|
||||
## 6. 仍然存在的近似
|
||||
|
||||
* 面板层的层号只比网格高 1,而**文本盒子**固定在 layer 2 收集。因此面板**自己的**文字框
|
||||
能正常显示,但理论上"图例文字"若与面板重叠仍会画在面板上。实际布局下两者不重叠。
|
||||
若要彻底消除,需要让文本收集器在"面板绘制期间"使用面板层的层号。
|
||||
* 菜单/面板的条目裁剪用的是"可视区矩形",因此**与边界部分相交的那一条**仍会被登记并裁剪绘制
|
||||
(这是期望行为:它确实可见)。
|
||||
@@ -3035,6 +3035,8 @@ else if (panelOnly) { /* 只回送“本帧重绘过的区域并集” */ }
|
||||
|
||||
| 2026-10-28 | DeepSeek-V4.1-Flash(**UI_Refactor 分支**) | **边界从补丁改成机制:取消内缩 + 文本几何身份 + 补漏 + 提速(详见 §12 末尾「2026-10-28 第十一轮」)**。① **取消内缩**(第九轮为"父子共边不叠粗"加的 1px 内缩**已作废**):胶囊形元素(GX 徽标 30×17、圆角=高的一半)的可见边界就在外接矩形上,内缩会让圆弧留在框外;正确语义是"子框后画覆盖父框的边"。② **文本获得几何身份**:`PaintUtil` 新增 `UiTextRectSink` / `UiSetTextRectSink` / `UiSetTextRectOffset` / `UiGetTextRectSink`,在 `DrawUiText()`/`UiTextAtClipped()` 里按对齐方式 + 实测宽高把矩形收窄成墨迹矩形回报;`UiDebugBoxList` 直接继承它 ⇒ 65 处文本调用点一行都不用改。③ **缓存命中的行也要有文本框**:行位图渲染时用本地 `RowTextCollector` 收集(位图坐标)→ 存 `RowBitmapCache::textRects` → 贴图时按 `(originX,originY)` 回放;⚠️ **必须无条件收集**,否则"关调试时滚过列表 → 开调试"会命中缓存而永远无文本框。④ **补齐 PlotView 漏项**:`m_timeAxisRect` / `m_scrollBarRect` / `BackToTopRect()` / `m_cursorSpanRect` / `m_cursorLabelRects` / `EditFieldRect()`。⑤ **描边提速**:图元 12→8(4 边 + 4 自适应角块),`paintBoxes` **5.892→4.028ms**;⚠️ 图元数量是唯一瓶颈(`FillPath` ≈20µs/次、GDI `PolyPolygon` 5688 矩形 114ms,都更慢,别换)。验证:新增探针 `.preflight/lbcovr` **7/7**(时间轴/滚动条/回到最上方/GX 胶囊框重合/文本框/缓存行文本框/关闭清空);`lbcheck` 21/21、`lbresid` 5/5、`lbzorder` 4/4、`uicheck` 69/69;`rendercheck` 整帧与区域哈希与上一轮**逐一相同** + FUNC 7/7;零 warning;`TargetCheck.exe` **909,824 字节(888.5 KiB)**。⑥ 已知既有问题:`PlotView` 基线每帧在 `WarmLaneLayersStep()` 花约 2.4ms(与本功能无关)。 |
|
||||
|
||||
| 2026-10-29 | DeepSeek-V4.1-Flash(**本机**) | **远端同步 + 既有性能问题查证 + 浮层层级修复(详见 §12 末尾「2026-10-29 第十二轮」)**。⓪ **仓库**:远端 `gitea` 已把 `UI_Refactor` 合入 `main`(PR #1,`322b658`,分支保留);`main` 与 `UI_Refactor` 均同步到 `322b658`;**`dev` 已删除**(远端从未存在,意外产物)。① **上一轮"基线每帧多花 2.4ms"的结论是错的**:实测排空前 idle=2.60ms、**排空后稳态 0.53ms**、强制整帧 0.46ms ⇒ 那 2.4ms 是"载入 283 网格后给 16 个网格补建宽基底图层"的**瞬时**预热成本(每帧补 1 个,约 16 帧结束)。⚠️ 性能结论必须区分稳态与瞬时。② 仍修掉一处顺序问题:`RollWideBuffer` 原来对**全部** 283 网格先算 `LaneLayerKey()`(≈0.7ms/帧)再判可视性,且离屏网格会逐帧入队再被清掉;现在 `LaneVisuallyRelevant` 提到循环最前面。③ **浮层不被下层覆盖 = 三件事缺一不可**:`PushLayer()`+`(layer,depth)` 排序(谁压谁)、`AddOccluder()`+`Region::Exclude`(下层盒不进入浮层区域)、`SetClip()`(滚出可视区的条目不登记)。`PlotView::CollectDebugBoxes` 收集浮动面板前 PushLayer 并对每个面板 AddOccluder+SetClip;`PopupMenu::CollectDebugBoxes` 条目裁到 `m_scrollEnabled ? m_scrollViewport : m_rect`、子菜单条目裁到 `m_subRect` —— 修掉"网格边界穿透面板""滚动时面板/菜单边界闪烁或溢出"。④ `EdgePanelLayoutFor` 前置"该网格是否有面板展开"的廉价判据。验证:新增探针 `.preflight/lbpanel` **10/10**(面板遮挡 / 面板内容裁剪 / 滚动不闪烁 / 菜单条目裁剪 / 真滚动 1295px);`lbcovr` 7/7、`lbzorder` 4/4、`lbresid` 5/5、`lbcheck` 21/21、`uicheck` 69/69;`rendercheck` 8 场景哈希**与上一轮逐一相同** + FUNC 7/7;零 warning;`TargetCheck.exe` **909,824 字节(888.5 KiB)**。⚠️ 探针判"盒子越界"必须按绘制层过滤;构造「所属网格」菜单必须带分隔线+尾部固定项才会启用滚动区。 |
|
||||
|
||||
## 11. 跨设备协作须知(起源设备视角)
|
||||
|
||||
> **本文件是唯一权威记忆**。所有设备 / Agent 共用它,**不要再新建 `agent_mem_origin.md` 等旁支记忆文件**。
|
||||
@@ -8261,3 +8263,67 @@ rendercheck 整帧与区域哈希与上一轮逐一相同 + FUNC 7/7(调试功
|
||||
|
||||
`PlotView` 基线每帧在 `WarmLaneLayersStep()` 上花约 2.4ms(9.06 vs 6.66),
|
||||
与本功能无关,值得单独优化。
|
||||
|
||||
---
|
||||
|
||||
## 本轮(2026-10-29 第十二轮,本机):分支同步 + 既有性能问题 + 浮层层级修复
|
||||
|
||||
### 0. 仓库约定变更
|
||||
|
||||
* 远端 `gitea` 已把 `UI_Refactor` 合入 `main`(PR #1,合并提交 `322b658`),**分支保留**。
|
||||
* `main` 与 `UI_Refactor` 均已同步到 `322b658`。
|
||||
* **`dev` 分支已删除**(远端从未存在,用户确认为意外产物)。
|
||||
|
||||
### 1. 「既有性能问题」的结论修正(重要)
|
||||
|
||||
上一轮报告里"`PlotView` 基线每帧多花 2.4ms"**是错的**:
|
||||
|
||||
```
|
||||
排空前 idle 2.60 ms ← 含"图层预热队列排空"的瞬时成本
|
||||
排空后 idle 0.53 ms ← 真正的稳态
|
||||
强制整帧 idle 0.46 ms
|
||||
```
|
||||
|
||||
那 2.4ms 是载入 283 网格后给 16 个网格补建宽基底图层的**瞬时**开销(每帧补 1 个,约 16 帧结束)。
|
||||
⚠️ **性能结论必须区分"稳态"与"瞬时"**,否则会把预热成本误判成常态浪费。
|
||||
|
||||
顺着这条线确实修掉一处顺序问题:`RollWideBuffer` 原来对**所有** 283 个网格先算
|
||||
`LaneLayerKey()`(逐网格内容哈希 ≈0.7ms/帧)再判断可视性;且离屏网格的"结构不可用"分支
|
||||
会逐帧把它们塞进待重画队列。**现在把 `LaneVisuallyRelevant` 判断提到循环最前面**
|
||||
(见函数内注释),离屏网格既不哈希也不入队。
|
||||
|
||||
### 2. 浮层不被下层覆盖 = 三件事(缺一不可)
|
||||
|
||||
| 机制 | 作用 |
|
||||
| --- | --- |
|
||||
| `PushLayer()` + `(layer, depth)` 排序 | 决定两个盒子重叠时**谁压谁** |
|
||||
| `AddOccluder()` + `Region::Exclude` | 让**更低层**的盒子整体不进入浮层区域 |
|
||||
| `SetClip()` | 让**滚动容器里滚出的条目**不再登记/绘制(否则会"闪烁"/"溢出") |
|
||||
|
||||
* `PlotView::CollectDebugBoxes`:收集浮动面板前 `PushLayer()`,每个面板
|
||||
`AddOccluder(layout.panel)` + `SetClip(layout.panel)`(面板内部元素也不越界)。
|
||||
* `PopupMenu::CollectDebugBoxes`:条目裁到 `m_scrollEnabled ? m_scrollViewport : m_rect`;
|
||||
二级子菜单条目裁到 `m_subRect`。
|
||||
|
||||
### 3. 验证
|
||||
|
||||
```
|
||||
lbpanel 10/10(新增:面板遮挡 / 面板内容裁剪 / 滚动不闪烁 / 菜单条目裁剪 / 真滚动 1295px)
|
||||
lbcovr 7/7 · lbzorder 4/4 · lbresid 5/5 · lbcheck 21/21 · uicheck 69/69
|
||||
rendercheck 8 场景哈希与上一轮逐一相同 + FUNC 7/7(本轮不改变任何正常绘制像素)
|
||||
零 warning;TargetCheck.exe 909,824 B(888.5 KiB)
|
||||
```
|
||||
|
||||
### 4. 踩坑
|
||||
|
||||
* 探针判断"盒子是否越界"必须**按绘制层过滤**:信号面板样式区、标签栏 ✕ 等盒子可能恰好与
|
||||
菜单同列,直接按坐标判会误报"菜单条目溢出"。
|
||||
* 构造「所属网格」菜单必须带**分隔线 + 尾部固定项**,否则 `PopupMenuLayout` 的滚动区不启用
|
||||
(`pinnedFrom` 只在找到分隔线时才 > 0)。
|
||||
* 计时探针必须先**排空预热队列**再测稳态,否则首帧的图层补齐会污染平均值。
|
||||
|
||||
### 5. 已知近似
|
||||
|
||||
面板层只比网格层高 1,而**文本盒子**固定在 layer 2 收集 ⇒ 面板自己的文字框正常显示,
|
||||
但"图例文字"若与面板重叠仍会画在面板上(当前布局下不重叠)。彻底解决需要让文本收集器
|
||||
在面板绘制期间使用面板层的层号。
|
||||
Reference in new issue
Block a user