平移不再阻塞前台刷新;弱峰增强逐信号参数;浮动面板加入层序
一、平移:前台刷新线程永不阻塞(解决"按住键拖拽时质量下降/断线/周期性卡顿")
- RollWideBuffer 不再整块重画:逐网格整数像素平移 + 只补细条;内容指纹过期或锚点离视界
过远的网格只登记到 m_layerRebuildPending 并仍按现有像素平移(位置照样跟随视界)。
- 新增 RenderPendingLaneRebuilds(budget):Paint 每帧最多重画 1 个网格,其余留到后续帧;
队列非空时 PostMessage(kMessagePlotDerivedReady) 请宿主再送一帧,直到补齐。
- BlitWideBuffer 改为局部贴图:把 BitBlt 夹到图层可用区间,图层没覆盖到的部分留空由底色
填充(旧实现直接跳过整块,导致网格瞬间变空 = "断线")。
- "结算"(整幅全质量精确帧)触发条件收紧为三者同时成立:内容与视界同上一条快路径帧、
m_dragMode == NONE、全部图层就绪。拖拽期间不再结算,消除"宽基底快路径 / 整幅重绘(关AA)"
每两帧振荡造成的质量下降与周期性卡顿。
- 新增 LaneLayerReady(i) / LanesAllReadyForView();原 LayersReadyForView() 更名
LayersStructurallyReady()(只看结构,用于"跨度/布局变化必须整批重建一次")。
- 实测(.preflight/panperf,VERY_BIG_DATA 5 网格深放大,每帧平移 60px):平移 40 帧
中位 6.2ms / p90 7.2ms / max 7.6ms;按住键且视界停在边界 12 帧中位 0.29ms(无整幅重绘)。
二、弱峰增强:改为逐信号参数(对齐均值滤波)
- Lane::WeakPeakItem 由 using = MeanFilterItem 改为独立结构体
{ ref, levelPercent(0-200, 默认 150), iterations(1-10, 默认 1) };删除网格级
Lane::weakPeakLevel / weakPeakIterations。
- 面板改为与均值滤波完全同构:每个被勾选信号一行"名称 + 复选框",第二排两个输入框
(增强等级 % / 迭代次数 N),底部"构建为新信号";删除网格级两个滑块及其常量、
WeakPeakSliderHit、PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS、m_peakDragLane、
EdgePanelLayout::slider/sliderIterations。
- 新增 PlotEditTarget::PEAK_LEVEL_PERCENT / PEAK_ITERATIONS,EndEdit 中分别夹到 0-200 / 1-10。
- 指纹/快照/预览曲线/光标胶囊/构建新信号全部改为逐信号参数(同一网格内不同信号可用不同强度,
且"预览"与"构建"逐位一致);WeakPeakItem 不再是 MeanFilterItem,purgeRefs 改为泛型 lambda。
三、浮动面板:层序(z-order)与命中修复
- 新增 m_panelOrder(队尾 = 最上层):SyncPanelOrder 保留已有序、新面板追加到最上层;
TopPanelAtPoint 从最上层往下取第一个命中;BringPanelToFront 在点击面板时把它置顶。
- 绘制改为按 m_panelOrder 自下而上;PanelGlobalKey 混合层序(层序变化即整层重画);
QueryCursor / OnMouseMove 的"是否在面板上"也走 TopPanelAtPoint。
- 修复"上层面板的 ✕ / 标题栏被下层参数输入框抢走,导致上层面板关不掉/拖不动"。
四、测试
- uitest 新增 [AG]:层预算(每帧只重画 1 个网格、剩余排队、最终补齐并全部就绪)、
局部贴图(只覆盖视界一半时左侧被贴、右侧保持哨兵色)、面板层序(上层 ✕ 命中上层、
下层不受影响、点击下层置顶、TopPanelAtPoint 随之改变、新开面板在最上层)。
- [X] 改为逐信号参数(含两个输入框就地编辑:写入该信号自己的条目、越界夹取),
移除已不存在的滑块断言。
- 新增离屏探针 .preflight/panperf(平移帧耗时 + 按住键静止帧耗时)。
- make all 零警告;uitest 388/388 通过;panperf PASS;layercheck 保持像素一致。
This commit is contained in:
1 parent
da83dc2987
commit
1ddbc37b78
5 files changed
+706
-544
No files matched your search
@@ -43,8 +43,6 @@ namespace jdi
|
||||
MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */
|
||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||
PEAK_LEVEL, /**< 拖动「弱峰增强」面板中的等级滑块。 */
|
||||
PEAK_ITERATIONS, /**< 拖动「弱峰增强」面板中的迭代次数 N 滑块。 */
|
||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||
};
|
||||
|
||||
@@ -72,7 +70,9 @@ namespace jdi
|
||||
EDGE_CENTER_POINTS, /**< 编辑「中心轴边沿计数」的均值点数 X。 */
|
||||
EDGE_CENTER_ITERATIONS, /**< 编辑「中心轴边沿计数」的迭代次数 N。 */
|
||||
MEAN_FILTER_POINTS, /**< 编辑「均值滤波」的均值点数 X。 */
|
||||
MEAN_FILTER_ITERATIONS /**< 编辑「均值滤波」的迭代次数 N。 */
|
||||
MEAN_FILTER_ITERATIONS, /**< 编辑「均值滤波」的迭代次数 N。 */
|
||||
PEAK_LEVEL_PERCENT, /**< 编辑「弱峰增强」某信号的增强等级(0–200%)。 */
|
||||
PEAK_ITERATIONS /**< 编辑「弱峰增强」某信号的迭代次数 N。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -570,7 +570,7 @@ namespace jdi
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 弱峰增强」的「构建为新信号」单个请求
|
||||
* (一个被勾选的信号 + 整个网格共用的增强等级)。
|
||||
* (一个被勾选的信号 + **它自己的**增强等级与迭代次数,与均值滤波同构)。
|
||||
*/
|
||||
struct WeakPeakBuild
|
||||
{
|
||||
@@ -610,36 +610,6 @@ namespace jdi
|
||||
*/
|
||||
bool ToggleLaneWeakPeak(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 读取某网格的弱峰增强等级(0–2)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 增强等级;网格不存在时返回 0.5。
|
||||
*/
|
||||
double LaneWeakPeakLevel(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 读取某网格的弱峰增强迭代次数 N(1–10)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 迭代次数;网格不存在时返回 1。
|
||||
*/
|
||||
int LaneWeakPeakIterations(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱峰增强迭代次数 N(1–10,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] iterations 迭代次数。
|
||||
* @return 迭代次数发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakPeakIterations(int laneIndex, int iterations);
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱峰增强等级(0–2,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] level 增强等级。
|
||||
* @return 等级发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakPeakLevel(int laneIndex, double level);
|
||||
|
||||
/**
|
||||
* @brief 获取弱峰增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。
|
||||
* @param [IN] signal 目标信号。
|
||||
@@ -1158,14 +1128,13 @@ namespace jdi
|
||||
gd::RectF close; /**< ✕ 关闭按钮。 */
|
||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||
gd::RectF slider; /**< 「弱峰增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF sliderIterations; /**< 「弱峰增强」迭代次数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||
/**
|
||||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||||
* @brief 「逐条目」面板的矩形(MEAN / CENTER / PEAK 面板共用同一套排布)。
|
||||
*
|
||||
* 索引与 `rowRefs` 一一对应(即网格内可见信号的顺序):
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为第二排的两个输入框;
|
||||
* `rowChecks` 为第一排右侧的复选框;`rowPoints` / `rowIterations` 为第二排的两个输入框
|
||||
*(PEEK 信号分别为「增强等级 %」与「迭代次数 N」);
|
||||
* `rowStats` 为第三排的统计结果(中心轴边沿计数专用)。
|
||||
* 未勾选的条目对应的矩形均为空。
|
||||
*/
|
||||
@@ -1688,7 +1657,6 @@ namespace jdi
|
||||
int m_pendingWeakPeakLane = -1; /**< 待构建的弱峰增强信号所属网格下标(-1 表示无请求)。 */
|
||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<WeakPeakBuild> m_pendingWeakPeakItems; /**< 待构建的弱峰增强信号及其参数。 */
|
||||
int m_peakDragLane = -1; /**< 正在拖动滑块的面板所属网格。 */
|
||||
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||||
/**
|
||||
* @brief 本帧是否使用「快速绘制」(交互期间关闭波形与派生曲线的抗锯齿)。
|
||||
@@ -1979,9 +1947,7 @@ namespace jdi
|
||||
int edgeCenter = 0; /**< 中心轴边沿计数开关。 */
|
||||
int meanFilter = 0; /**< 均值滤波开关。 */
|
||||
int weakPeak = 0; /**< 弱峰增强开关。 */
|
||||
int weakPeakLevelPercent = 0; /**< 弱峰增强等级(0–200)。 */
|
||||
int weakPeakIterations = 1; /**< 弱峰增强迭代次数 N。 */
|
||||
/** 各条目展开为 [文件, 信号, 均值点数 X, 迭代次数 N],按原顺序(稳定)。 */
|
||||
/** 各条目展开为 [文件, 信号, 参数…],按原顺序(稳定)。 */
|
||||
std::vector<int> edgeCenterItems;
|
||||
std::vector<int> meanFilterItems;
|
||||
std::vector<int> weakPeakItems;
|
||||
@@ -2287,32 +2253,55 @@ namespace jdi
|
||||
/**
|
||||
* @brief 让各图层"滚动"以跟随视界:逐网格整数像素自贴图平移 + 只补画新暴露的细条。
|
||||
*
|
||||
* 每个网格按**自己的** `wideMin` 计算位移;位移过大(锚点离当前视界太远)或内容指纹
|
||||
* 与当前状态不一致的网格直接整块重画。任一网格图层缺失(尺寸 / 跨度不符)返回 false,
|
||||
* 由调用方先 `RebuildWideBuffer()`。
|
||||
* @return 全部网格都能用现成像素跟随视界返回 true。
|
||||
* ⚠️ **本函数绝不做"整块重画"**:内容指纹过期 / 锚点离视界太远的网格只被排进
|
||||
* `m_layerRebuildPending`(随后由 `RenderPendingLaneRebuilds` 按预算补画),本帧仍按
|
||||
* 现有像素做整数像素平移,因此**前台线程永远不会因为"图层没准备好"而卡顿**。
|
||||
*/
|
||||
bool RollWideBuffer();
|
||||
void RollWideBuffer();
|
||||
/**
|
||||
* @brief 逐个网格截取图层子矩形贴图到后备缓冲区(O(1) 位图搬运)。
|
||||
*
|
||||
* 图层只覆盖"视界 ± 半屏":若当前视界有一部分落在图层之外,这里**只贴有像素的那一段**
|
||||
* (其余留由底色填充,下一帧补齐),而不是跳过整块 —— 这正是"未绘制的部分先暂时不管"。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @return 至少贴了若干网格返回 true。
|
||||
* @return 至少贴了若干像素返回 true。
|
||||
*/
|
||||
bool BlitWideBuffer(gd::Graphics& g) const;
|
||||
/**
|
||||
* @brief 全部图层是否已就绪且跨度与当前视界一致。
|
||||
* @brief 全部图层是否**结构可用**(数量 / 位图尺寸 / 视界跨度都与当前状态一致)。
|
||||
*
|
||||
* 只检查"位图尺寸 / 网格数量 / 视界跨度"这类**结构性**条件(内容指纹由
|
||||
* `RollWideBuffer()` 逐个网格处理);不满足时应先 `RebuildWideBuffer()`。
|
||||
* 结构不可用意味着"像素的时间映射已经不对",只能整批重建(跨度变化 / 布局变化 / 首帧);
|
||||
* 内容指纹是否最新由 `LaneLayerReady()` 逐个判断。
|
||||
* @return 可以直接滚动 + 贴图层像素返回 true。
|
||||
*/
|
||||
bool LayersReadyForView() const;
|
||||
bool LayersStructurallyReady() const;
|
||||
/**
|
||||
* @brief 某个网格的图层是否**完全就绪**(结构可用且内容指纹与当前状态一致)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 就绪返回 true。
|
||||
*/
|
||||
bool LaneLayerReady(int laneIndex) const;
|
||||
/** @brief 全部网格图层是否都完全就绪(用于判断能否做"结算"整幅精确帧)。 */
|
||||
bool LanesAllReadyForView() const;
|
||||
/** @brief 把某网格排进"待重画"队列(去重;视界/跨度后续变化时会在渲染时再次校验)。 */
|
||||
void QueueLaneRebuild(int laneIndex);
|
||||
/**
|
||||
* @brief 按预算补画"待重画"的网格图层(**本帧最多 `budget` 个**)。
|
||||
*
|
||||
* 这是"前台不做整批重绘"的关键:即使 5 个网格都需要重画,也一帧只画一个,
|
||||
* 其余留到后续帧(缺的部分留空 / 用旧像素)。调用方若发现队列非空应请求再次重绘。
|
||||
* @param [IN] budget 本次最多重画的网格数量。
|
||||
* @return 队列中仍有待重画网格返回 true。
|
||||
*/
|
||||
bool RenderPendingLaneRebuilds(int budget);
|
||||
/** @brief 使全部图层失效。 */
|
||||
void InvalidateWideBuffer();
|
||||
/** @brief 释放全部图层位图。 */
|
||||
void ReleaseWideBuffer();
|
||||
|
||||
std::vector<LaneLayer> m_layerFront; /**< 各网格的「信号图层」(与 `m_lanes` 同序)。 */
|
||||
/** 待重画的网格下标(去重;`RollWideBuffer` 登记,`RenderPendingLaneRebuilds` 消费)。 */
|
||||
std::vector<int> m_layerRebuildPending;
|
||||
/**
|
||||
* @brief 图层元数据的互斥(当前图层只在 UI 线程维护,故它实际不承担跨线程保护;
|
||||
* 保留是为了给 `BlitWideBuffer()` 这类 `const` 读取路径一个明确的一致性边界)。
|
||||
@@ -2364,6 +2353,42 @@ namespace jdi
|
||||
std::uint64_t m_panelGlobalKey = 0; /**< 上一层对应的全局键。 */
|
||||
std::vector<PanelSlot> m_panelSlots; /**< 上一层缓存的面板(本帧比对用)。 */
|
||||
|
||||
/**
|
||||
* @brief 浮动面板的**层序**(从下到上)。
|
||||
*
|
||||
* 面板允许互相重叠,因此必须有明确的层序:
|
||||
* * **绘制**按此顺序从下往上;**命中测试**从后往前(最上层优先)。
|
||||
* * 点击某个面板会把它移到队尾(置顶),因此重叠时总能操作到"用户想要"的那一个
|
||||
* (否则上层面板的 ✕ / 标题栏会被下层控件的命中判定抢走)。
|
||||
* * 新出现的面板追加到队尾(在最上层),不会被已有面板盖住。
|
||||
*/
|
||||
struct PanelRef
|
||||
{
|
||||
int laneIndex = -1; /**< 所属网格。 */
|
||||
int kind = 0; /**< `EdgePanelKind` 的整型值。 */
|
||||
|
||||
bool operator==(const PanelRef& other) const
|
||||
{
|
||||
return laneIndex == other.laneIndex && kind == other.kind;
|
||||
}
|
||||
};
|
||||
|
||||
/** 同步 `m_panelOrder` 与当前"可见的面板集合"(新增的追加到最上层;消失的移除)。 */
|
||||
void SyncPanelOrder() const;
|
||||
/** @brief 把某个面板移到最上层(点击面板时调用)。 */
|
||||
void BringPanelToFront(int laneIndex, int kind);
|
||||
/**
|
||||
* @brief 从最上层往下找出包含该点的面板。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
* @param [IN] y 客户区纵坐标。
|
||||
* @param [OUT] laneIndex 命中面板所属网格。
|
||||
* @param [OUT] kind 命中面板的种类(`EdgePanelKind`)。
|
||||
* @return 命中返回 true。
|
||||
*/
|
||||
bool TopPanelAtPoint(float x, float y, int& laneIndex, int& kind) const;
|
||||
|
||||
mutable std::vector<PanelRef> m_panelOrder; /**< 浮动面板层序(队尾 = 最上层;mutable 供 const 光标查询同步层序)。 */
|
||||
|
||||
/**
|
||||
* @brief 查询坐标命中的图例条目。
|
||||
* @param [IN] x 客户区横坐标。
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user