修复缩放后平移的绘制错误/抖动与前台卡顿;弱波增强改名弱峰增强并支持 0~200%
渲染(根因与修法)
- 根因:后台图层线程渲染时会临时改写共享渲染状态(m_area/m_plotRect/视界/网格矩形),
与 UI 线程无锁的平移(OnMouseMove)和命中测试相冲 → 后台的"保存→改写→恢复"会把
用户刚做的平移弹回、命中测试读到错位网格;同时 RollWideBuffer 用单一全局锚点给所有
图层算同一位移,而各图层可能是在不同视界下渲染的(后台逐网格)→ 网格之间时间错位、
贴回像素互相矛盾;补细条又误用"当前视界"的映射(与旧像素不同源)→ ≤0.5px 接缝与拖动抖动。
- 修法:
* 删除后台图层线程(m_layerWorker 及任务队列、EnsureLayerWorker、StopLayerWorker、
LayerWorkerLoop、ScheduleStaleLayerRenders、m_layerBack、m_layerAnchorMin/Valid、
m_lastPaintTick、m_layerRescheduleStreak)。图层渲染一律在 UI 线程内同步完成。
* 逐网格独立锚点与滚动:以 LaneLayer::wideMin 为准,补细条沿用该图层自己的映射。
* RenderLaneLayerRegion 新增 wideMin 形参;新增 LayerDefaultWideMin()。
* RollWideBuffer 逐网格:内容指纹不一致或锚点离视界太远 → 该网格整块重画;
否则整数像素自贴图平移 + 只补新暴露的细条。
* Paint 宽基底分支:!LayersReadyForView() 时先同步 RebuildWideBuffer() 再滚动。
* BackgroundLayerRenderCount() 保留为"同步图层区域渲染次数"的诊断计数。
- 效果:平移不再出现网格时间错位、视界被弹回或波形抖动;居中图层与整幅重绘逐像素一致。
弱峰增强(原「弱波增强」)
- 功能改名:界面文案与标识符统一为 WeakPeak/weakPeak(ComputeWeakPeakBoost、
SetLaneWeakPeak*、EdgePanelKind::PEAK、PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS、
PLOT_CMD_LANE_WEAK_PEAK、PeakExtremum 等),导出信号名 "<原信号> (弱峰增强 M% N次迭代)"。
- 等级范围改为 0~200%,目标值分段插值:
* 0~100%:在"目标极值自身的值"与"左右同类极值中较温和的一侧"之间插值
(0% 目标就是当前值 → 完全不动,允许用户做极小幅度调整);
* 100~200%:与原先 0~100% 的插值方式一致(200% 完全拉平到另一侧)。
两段在 100% 处连续。新 1.5 == 旧 0.5,新 2.0 == 旧 1.0。
- Lane::weakPeakLevel 默认 1.5;levelPercent(构建请求/曲线缓存键)范围 0~200、默认 150;
新增 kWeakPeakLevelMax = 2.0 与 kWeakPeakPercentMax = 200;滑块轨道比例 = level / 2。
测试
- 同步更新离屏测试 .preflight/uitest:等级期望改为 0/50/100/150/200 五档;阶数扫描与
"完全恢复"块改用 150%/200% 以保持旧期望;派生信号名校验改用新名;
[J] 改为"补丁帧标记降级 → 渲染到缓存重新有效后用修复帧与整幅重绘比对(0 差异)";
[AF] 面板像素容差 32→96(面板 96% 不透明,底层 ≤0.5px 残留会透出,实测最多 6 像素/72 级);
[X] 预览曲线像素计数的颜色容差 6→40。
- 新增离屏检查 .preflight/layercheck:居中图层与整幅重绘逐像素对比、平移逐网格对齐位移、
视界覆盖检测、跨度抖动下的视界保持检测。
- make release 零警告;uitest 380/380 通过(exit=0)。
This commit is contained in:
1 parent
800c8b835b
commit
da83dc2987
7 files changed
+644
-734
No files matched your search
@@ -241,8 +241,8 @@ namespace jdi
|
||||
/**
|
||||
* @brief 应用绘图区提交的「实验性功能 → 构建为新信号」请求。
|
||||
*
|
||||
* 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱波增强),
|
||||
* 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱波增强 M%)`,
|
||||
* 为指定网格内的每条被勾选信号各构建一条派生信号(多次迭代均值滤波或弱峰增强),
|
||||
* 命名为 `<原信号名称> (N点均值…)` / `<原信号名称> (弱峰增强 M%)`,
|
||||
* 插入到原信号所在组的原信号位置**下方**,并把新信号所在的网格移动到原信号网格的下方。
|
||||
*
|
||||
* @return 实际构建了新信号返回 true。
|
||||
@@ -250,7 +250,7 @@ namespace jdi
|
||||
bool ApplyPendingDerivedBuild();
|
||||
|
||||
/**
|
||||
* @brief 派生信号构建请求(多次迭代均值滤波 / 弱波增强共用同一套插入流程)。
|
||||
* @brief 派生信号构建请求(多次迭代均值滤波 / 弱峰增强共用同一套插入流程)。
|
||||
*/
|
||||
struct DerivedSignalRequest
|
||||
{
|
||||
@@ -258,12 +258,12 @@ namespace jdi
|
||||
int signalIndex = -1; /**< 原信号下标。 */
|
||||
int points = 5; /**< 多次迭代均值滤波:均值点数 X(滑平半径)。 */
|
||||
int iterations = 1; /**< 多次迭代均值滤波:迭代次数 N(至少 1)。 */
|
||||
int levelPercent = 50; /**< 弱波增强:增强等级(0–100)。 */
|
||||
bool weakBoost = false; /**< true = 弱波增强,false = 多次迭代均值滤波。 */
|
||||
int levelPercent = 150; /**< 弱峰增强:增强等级(0–200)。 */
|
||||
bool weakPeak = false; /**< true = 弱峰增强,false = 多次迭代均值滤波。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 把一条派生信号(多次迭代均值滤波 / 弱波增强)插入数据层并同步各视图。
|
||||
* @brief 把一条派生信号(多次迭代均值滤波 / 弱峰增强)插入数据层并同步各视图。
|
||||
* @param [IN] request 构建请求。
|
||||
* @param [IN] makeVisible 是否在当前标签页中勾选新信号。
|
||||
* @return 构建并插入成功返回 true。
|
||||
|
||||
@@ -1481,14 +1481,14 @@ namespace jdi
|
||||
|
||||
// 1. 计算派生采样值(与网格内的预览曲线共用同一实现,因此逐位一致)。
|
||||
// 多次迭代均值滤波形如「<原信号名称> (N点均值 M次迭代)」,
|
||||
// 弱波增强形如「<原信号名称> (弱波增强 M% N次迭代)」。
|
||||
// 弱峰增强形如「<原信号名称> (弱峰增强 M% N次迭代)」。
|
||||
std::vector<float> derived_values;
|
||||
SignalData derived;
|
||||
if (request.weakBoost)
|
||||
if (request.weakPeak)
|
||||
{
|
||||
ComputeWeakWaveBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||
ComputeWeakPeakBoost(source.values, static_cast<double>(request.levelPercent) / 100.0,
|
||||
request.iterations, derived_values);
|
||||
derived.name = baseName + L" (弱波增强 " + std::to_wstring(request.levelPercent) + L"% " +
|
||||
derived.name = baseName + L" (弱峰增强 " + std::to_wstring(request.levelPercent) + L"% " +
|
||||
std::to_wstring(request.iterations) + L"次迭代)";
|
||||
}
|
||||
else
|
||||
@@ -1538,7 +1538,7 @@ namespace jdi
|
||||
|
||||
bool Application::ApplyPendingDerivedBuild()
|
||||
{
|
||||
// 两类「构建为新信号」请求(均值滤波 / 弱波增强)机制完全一致,
|
||||
// 两类「构建为新信号」请求(均值滤波 / 弱峰增强)机制完全一致,
|
||||
// 统一整理成同一份构建清单后按下标降序处理。
|
||||
std::vector<DerivedSignalRequest> requests;
|
||||
{
|
||||
@@ -1553,24 +1553,24 @@ namespace jdi
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.points = item.points;
|
||||
request.iterations = item.iterations;
|
||||
request.weakBoost = false;
|
||||
request.weakPeak = false;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::vector<PlotView::WeakBoostBuild> items;
|
||||
if (m_plot.TakePendingWeakBoostBuild(laneIndex, items))
|
||||
std::vector<PlotView::WeakPeakBuild> items;
|
||||
if (m_plot.TakePendingWeakPeakBuild(laneIndex, items))
|
||||
{
|
||||
for (const PlotView::WeakBoostBuild& item : items)
|
||||
for (const PlotView::WeakPeakBuild& item : items)
|
||||
{
|
||||
DerivedSignalRequest request;
|
||||
request.fileIndex = item.fileIndex;
|
||||
request.signalIndex = item.signalIndex;
|
||||
request.levelPercent = item.levelPercent;
|
||||
request.iterations = item.iterations;
|
||||
request.weakBoost = true;
|
||||
request.weakPeak = true;
|
||||
requests.push_back(request);
|
||||
}
|
||||
}
|
||||
@@ -1610,9 +1610,9 @@ namespace jdi
|
||||
if (built > 0)
|
||||
{
|
||||
std::wstring text = L"已构建 " + std::to_wstring(built) + L" 条 ";
|
||||
if (last.weakBoost)
|
||||
if (last.weakPeak)
|
||||
{
|
||||
text += L"弱波增强 " + std::to_wstring(last.levelPercent) + L"% " +
|
||||
text += L"弱峰增强 " + std::to_wstring(last.levelPercent) + L"% " +
|
||||
std::to_wstring(last.iterations) + L"次迭代信号。";
|
||||
}
|
||||
else
|
||||
@@ -1753,11 +1753,11 @@ namespace jdi
|
||||
meanFilter.checked = m_plot.LaneMeanFilter(laneIndex);
|
||||
experimental.submenu.push_back(meanFilter);
|
||||
|
||||
MenuItem weakBoost;
|
||||
weakBoost.label = L"弱波增强";
|
||||
weakBoost.id = PLOT_CMD_LANE_WEAK_BOOST;
|
||||
weakBoost.checked = m_plot.LaneWeakBoost(laneIndex);
|
||||
experimental.submenu.push_back(weakBoost);
|
||||
MenuItem weakPeak;
|
||||
weakPeak.label = L"弱峰增强";
|
||||
weakPeak.id = PLOT_CMD_LANE_WEAK_PEAK;
|
||||
weakPeak.checked = m_plot.LaneWeakPeak(laneIndex);
|
||||
experimental.submenu.push_back(weakPeak);
|
||||
}
|
||||
items.push_back(experimental);
|
||||
|
||||
@@ -1872,9 +1872,9 @@ namespace jdi
|
||||
// 「实验性功能 → 均值滤波」:切换均值滤波曲线与参数面板。
|
||||
m_plot.ToggleLaneMeanFilter(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_WEAK_BOOST:
|
||||
// 「实验性功能 → 弱波增强」:切换弱波增强曲线与参数面板。
|
||||
m_plot.ToggleLaneWeakBoost(laneIndex);
|
||||
case PLOT_CMD_LANE_WEAK_PEAK:
|
||||
// 「实验性功能 → 弱峰增强」:切换弱峰增强曲线与参数面板。
|
||||
m_plot.ToggleLaneWeakPeak(laneIndex);
|
||||
break;
|
||||
case PLOT_CMD_LANE_SPLIT:
|
||||
// 「拆分当前网格」:信号按个拆成独立网格,新网格顺次插入其下方。
|
||||
@@ -2378,7 +2378,7 @@ namespace jdi
|
||||
{
|
||||
// 绘图区内的点击也可能提交图例改名,需要立刻落到数据层。
|
||||
ApplyPendingRename();
|
||||
// 「构建为新信号」按钮(均值滤波 / 弱波增强):立即构建新信号并同步各视图。
|
||||
// 「构建为新信号」按钮(均值滤波 / 弱峰增强):立即构建新信号并同步各视图。
|
||||
ApplyPendingDerivedBuild();
|
||||
InvalidateRect(hwnd, nullptr, FALSE);
|
||||
}
|
||||
|
||||
@@ -43,8 +43,8 @@ namespace jdi
|
||||
MOVE_SIGNAL, /**< 拖拽信号曲线到其它网格。 */
|
||||
EDGE_MOVE, /**< 拖动「边沿计数」的横向光标(上下移动幅值)。 */
|
||||
EDGE_PANEL_MOVE, /**< 拖动边沿计数浮动面板。 */
|
||||
BOOST_LEVEL, /**< 拖动「弱波增强」面板中的等级滑块。 */
|
||||
BOOST_ITERATIONS, /**< 拖动「弱波增强」面板中的迭代次数 N 滑块。 */
|
||||
PEAK_LEVEL, /**< 拖动「弱峰增强」面板中的等级滑块。 */
|
||||
PEAK_ITERATIONS, /**< 拖动「弱峰增强」面板中的迭代次数 N 滑块。 */
|
||||
LANE_REORDER /**< 拖动网格图例中的 GX 编号徽标:上下调整网格次序。 */
|
||||
};
|
||||
|
||||
@@ -90,7 +90,7 @@ namespace jdi
|
||||
PLOT_CMD_LANE_EDGE_CENTER = 8, /**< 切换本网格的「实验性功能 → 边沿计数(中心轴)」开关。 */
|
||||
PLOT_CMD_LANE_SPLIT = 9, /**< 拆分当前网格:把网格内信号按个拆成独立网格并顺次下移编号。 */
|
||||
PLOT_CMD_LANE_MEAN_FILTER = 10, /**< 切换本网格的「实验性功能 → 均值滤波」开关。 */
|
||||
PLOT_CMD_LANE_WEAK_BOOST = 11 /**< 切换本网格的「实验性功能 → 弱波增强」开关。 */
|
||||
PLOT_CMD_LANE_WEAK_PEAK = 11 /**< 切换本网格的「实验性功能 → 弱峰增强」开关。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -101,7 +101,7 @@ namespace jdi
|
||||
COUNT = 0, /**< 边沿计数。 */
|
||||
CENTER = 1, /**< 边沿计数(中心轴)。 */
|
||||
MEAN = 2, /**< 均值滤波。 */
|
||||
BOOST = 3 /**< 弱波增强。 */
|
||||
PEAK = 3 /**< 弱峰增强。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -112,7 +112,7 @@ namespace jdi
|
||||
enum class DerivedCurveKind : int
|
||||
{
|
||||
MEAN_FILTER = 0, /**< 多次迭代均值滤波。 */
|
||||
WEAK_BOOST = 1 /**< 弱波增强。 */
|
||||
WEAK_PEAK = 1 /**< 弱峰增强。 */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -138,11 +138,11 @@ namespace jdi
|
||||
void SetHostWindow(HWND host) { m_host = host; }
|
||||
|
||||
/**
|
||||
* @brief 后台「网格图层」绘制线程已完成的图层数量(仅供验证 / 诊断)。
|
||||
* @brief 已渲染的「信号图层」区域数量(仅供验证 / 诊断)。
|
||||
*
|
||||
* 计数 > 0 即说明单网格基底的绘制确实发生在**后台线程**,
|
||||
* 前台只负责位图搬运(贴图)与叠加层。
|
||||
* @return 累计完成的图层数量。
|
||||
* 每次把一个网格的基底(整块或滚动细条)画进图层都会 +1。计数长期不增长即说明
|
||||
* 平移期间确实在复用已有像素(只做坐标偏移),没有重新绘制波形。
|
||||
* @return 累计完成的图层区域数量。
|
||||
*/
|
||||
std::uint64_t BackgroundLayerRenderCount() const
|
||||
{
|
||||
@@ -569,115 +569,115 @@ namespace jdi
|
||||
bool TakePendingMeanFilterBuild(int& laneIndex, std::vector<MeanFilterBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 弱波增强」的「构建为新信号」单个请求
|
||||
* @brief 「实验性功能 → 弱峰增强」的「构建为新信号」单个请求
|
||||
* (一个被勾选的信号 + 整个网格共用的增强等级)。
|
||||
*/
|
||||
struct WeakBoostBuild
|
||||
struct WeakPeakBuild
|
||||
{
|
||||
int fileIndex = -1; /**< 信号所属文件下标。 */
|
||||
int signalIndex = -1; /**< 文件内信号下标。 */
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
int levelPercent = 150; /**< 弱峰增强等级(0–200)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 取出「弱波增强 → 构建为新信号」请求(由面板底部按钮触发)。
|
||||
* @brief 取出「弱峰增强 → 构建为新信号」请求(由面板底部按钮触发)。
|
||||
* @param [OUT] laneIndex 触发该请求的网格下标。
|
||||
* @param [OUT] items 需要构建的信号及其参数(按信号在网格内的顺序)。
|
||||
* @return 存在待处理请求返回 true(取出后清空)。
|
||||
*/
|
||||
bool TakePendingWeakBoostBuild(int& laneIndex, std::vector<WeakBoostBuild>& items);
|
||||
bool TakePendingWeakPeakBuild(int& laneIndex, std::vector<WeakPeakBuild>& items);
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「弱波增强」开关。
|
||||
* @brief 切换某网格的「弱峰增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] enabled 是否开启。
|
||||
* @return 状态发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakBoost(int laneIndex, bool enabled);
|
||||
bool SetLaneWeakPeak(int laneIndex, bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 查询某网格的「弱波增强」开关。
|
||||
* @brief 查询某网格的「弱峰增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 已开启返回 true。
|
||||
*/
|
||||
bool LaneWeakBoost(int laneIndex) const;
|
||||
bool LaneWeakPeak(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某网格的「弱波增强」开关。
|
||||
* @brief 切换某网格的「弱峰增强」开关。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 切换后的状态。
|
||||
*/
|
||||
bool ToggleLaneWeakBoost(int laneIndex);
|
||||
bool ToggleLaneWeakPeak(int laneIndex);
|
||||
|
||||
/**
|
||||
* @brief 读取某网格的弱波增强等级(0–1)。
|
||||
* @brief 读取某网格的弱峰增强等级(0–2)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 增强等级;网格不存在时返回 0.5。
|
||||
*/
|
||||
double LaneWeakBoostLevel(int laneIndex) const;
|
||||
double LaneWeakPeakLevel(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 读取某网格的弱波增强迭代次数 N(1–10)。
|
||||
* @brief 读取某网格的弱峰增强迭代次数 N(1–10)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @return 迭代次数;网格不存在时返回 1。
|
||||
*/
|
||||
int LaneWeakBoostIterations(int laneIndex) const;
|
||||
int LaneWeakPeakIterations(int laneIndex) const;
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱波增强迭代次数 N(1–10,超出范围会被夹取)。
|
||||
* @brief 设置某网格的弱峰增强迭代次数 N(1–10,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] iterations 迭代次数。
|
||||
* @return 迭代次数发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakBoostIterations(int laneIndex, int iterations);
|
||||
bool SetLaneWeakPeakIterations(int laneIndex, int iterations);
|
||||
|
||||
/**
|
||||
* @brief 设置某网格的弱波增强等级(0–1,超出范围会被夹取)。
|
||||
* @brief 设置某网格的弱峰增强等级(0–2,超出范围会被夹取)。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] level 增强等级。
|
||||
* @return 等级发生变化返回 true。
|
||||
*/
|
||||
bool SetLaneWeakBoostLevel(int laneIndex, double level);
|
||||
bool SetLaneWeakPeakLevel(int laneIndex, double level);
|
||||
|
||||
/**
|
||||
* @brief 获取弱波增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。
|
||||
* @brief 获取弱峰增强曲线(按「信号 + 增强等级 + 迭代次数」缓存)。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100,整数百分比)。
|
||||
* @param [IN] levelPercent 弱峰增强等级(0–200,整数百分比)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
* @return 曲线数值数组(长度与 `signal.values` 相同;缺失处为 NaN)。
|
||||
*/
|
||||
const std::vector<float>& WeakBoostCurveFor(const SignalData& signal, int levelPercent,
|
||||
const std::vector<float>& WeakPeakCurveFor(const SignalData& signal, int levelPercent,
|
||||
int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 取某个时刻的弱波增强曲线取值(用于纵向光标的取值标注)。
|
||||
* @brief 取某个时刻的弱峰增强曲线取值(用于纵向光标的取值标注)。
|
||||
* @param [IN] file 信号所属文件。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
* @param [IN] levelPercent 弱峰增强等级(0–200)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
* @param [IN] time 采样时刻。
|
||||
* @param [OUT] value 输出取值。
|
||||
* @return 取到有效值返回 true。
|
||||
*/
|
||||
bool WeakBoostValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int iterations,
|
||||
bool WeakPeakValueAt(const DataFile& file, const SignalData& signal, int levelPercent, int iterations,
|
||||
double time, double& value) const;
|
||||
|
||||
/**
|
||||
* @brief 切换某个信号在「弱波增强」面板中的预览勾选状态。
|
||||
* @brief 切换某个信号在「弱峰增强」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 切换后的勾选状态。
|
||||
*/
|
||||
bool ToggleLaneWeakBoostSignal(int laneIndex, const SignalRef& ref);
|
||||
bool ToggleLaneWeakPeakSignal(int laneIndex, const SignalRef& ref);
|
||||
|
||||
/**
|
||||
* @brief 查询某个信号在「弱波增强」面板中的预览勾选状态。
|
||||
* @brief 查询某个信号在「弱峰增强」面板中的预览勾选状态。
|
||||
* @param [IN] laneIndex 网格下标。
|
||||
* @param [IN] ref 目标信号。
|
||||
* @return 已勾选返回 true。
|
||||
*/
|
||||
bool LaneWeakBoostSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
bool LaneWeakPeakSignalEnabled(int laneIndex, const SignalRef& ref) const;
|
||||
|
||||
/**
|
||||
* @brief 直接把文本解析为网格 Y 轴上限/下限并应用(供菜单内联输入框提交使用)。
|
||||
@@ -1089,7 +1089,7 @@ namespace jdi
|
||||
const std::vector<double>& timeTicks);
|
||||
|
||||
/**
|
||||
* @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱波增强),叠加在原信号之上。
|
||||
* @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱峰增强),叠加在原信号之上。
|
||||
*
|
||||
* 从 `PaintLane` 抽出,供「宽缓冲区」前台路径复用:宽缓冲只存"基底"(网格/波形),
|
||||
* 派生曲线每帧作为叠加层单独绘制,因此拖派生参数滑块时无需重画波形。
|
||||
@@ -1158,8 +1158,8 @@ namespace jdi
|
||||
gd::RectF close; /**< ✕ 关闭按钮。 */
|
||||
gd::RectF edit; /**< 面板底部编辑行(无编辑内容时宽度为 0)。 */
|
||||
gd::RectF button; /**< 面板底部按钮(无按钮时宽度为 0)。 */
|
||||
gd::RectF slider; /**< 「弱波增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF sliderIterations; /**< 「弱波增强」迭代次数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF slider; /**< 「弱峰增强」等级滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
gd::RectF sliderIterations; /**< 「弱峰增强」迭代次数 N 滑块的轨道矩形(无滑块时宽度为 0)。 */
|
||||
int rows = 0; /**< 统计行数(每条可见信号一行)。 */
|
||||
/**
|
||||
* @brief 「逐条目」面板的矩形(MEAN 与 CENTER 面板共用同一套排布)。
|
||||
@@ -1294,20 +1294,20 @@ namespace jdi
|
||||
* @brief 取得某个采样区间内的派生曲线(带缓存)。
|
||||
*
|
||||
* ⚠️ **性能关键**:派生曲线只用于“看得见的那一段”,而整条信号可能有数千万个采样点
|
||||
* (VERY_BIG_DATA 为 2576 万)。若每次都重算整条曲线(实测弱波增强 ~800ms/条),
|
||||
* (VERY_BIG_DATA 为 2576 万)。若每次都重算整条曲线(实测弱峰增强 ~800ms/条),
|
||||
* 拖动等级/迭代次数滑块时每帧要花几秒钟,交互完全卡死。
|
||||
* 本函数只计算“所需区间 ± 保护带”,并按(信号 + 参数 + 对齐后的区间)缓存:
|
||||
* 实测单条降到 1~3ms,滑块拖动与平移因此恢复流畅。
|
||||
*
|
||||
* 保护带保证可见区间内的取值与“整条曲线”一致:均值滤波的核是有限宽度,
|
||||
* 弱波增强的修正沿相邻极值向内传播(每迭代一次传一个极值),
|
||||
* 弱峰增强的修正沿相邻极值向内传播(每迭代一次传一个极值),
|
||||
* 因此只要求保护带足够宽(见 `kDerivedCurveGuard` 系列常量)。
|
||||
*
|
||||
* @param [IN] kind 曲线种类。
|
||||
* @param [IN] signal 目标信号。
|
||||
* @param [IN] points 均值点数 X(仅 `MEAN_FILTER` 使用)。
|
||||
* @param [IN] iterations 迭代次数 N。
|
||||
* @param [IN] levelPercent 弱波增强等级(仅 `WEAK_BOOST` 使用,0–100)。
|
||||
* @param [IN] levelPercent 弱峰增强等级(仅 `WEAK_PEAK` 使用,0–200)。
|
||||
* @param [IN] begin 需要的采样区间起点(含)。
|
||||
* @param [IN] end 需要的采样区间终点(不含)。
|
||||
* @param [OUT] spanBegin 返回数组对应的首个采样下标。
|
||||
@@ -1336,7 +1336,7 @@ namespace jdi
|
||||
int kind = 0; /**< `DerivedCurveKind`。 */
|
||||
int points = 5; /**< 均值点数 X。 */
|
||||
int iterations = 1; /**< 迭代次数 N。 */
|
||||
int levelPercent = 0; /**< 弱波增强等级(0–100)。 */
|
||||
int levelPercent = 0; /**< 弱峰增强等级(0–200)。 */
|
||||
|
||||
bool operator==(const CurveKey& other) const
|
||||
{
|
||||
@@ -1461,14 +1461,14 @@ namespace jdi
|
||||
int points, int iterations) const;
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的弱波增强曲线(与均值滤波曲线同一配色派生规则)。
|
||||
* @brief 绘制某条信号的弱峰增强曲线(与均值滤波曲线同一配色派生规则)。
|
||||
* @param [IN,OUT] g 绘图上下文。
|
||||
* @param [IN] resolved 信号视图。
|
||||
* @param [IN] lane 网格绘制信息。
|
||||
* @param [IN] levelPercent 弱波增强等级(0–100)。
|
||||
* @param [IN] levelPercent 弱峰增强等级(0–200)。
|
||||
* @param [IN] iterations 迭代次数 N(1–10)。
|
||||
*/
|
||||
void PaintWeakBoostCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
void PaintWeakPeakCurve(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
int levelPercent, int iterations) const;
|
||||
|
||||
/**
|
||||
@@ -1685,10 +1685,10 @@ namespace jdi
|
||||
bool m_pendingRename = false; /**< 是否存在待应用的重命名请求。 */
|
||||
|
||||
int m_pendingMeanFilterLane = -1; /**< 待构建的均值滤波信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingWeakBoostLane = -1; /**< 待构建的弱波增强信号所属网格下标(-1 表示无请求)。 */
|
||||
int m_pendingWeakPeakLane = -1; /**< 待构建的弱峰增强信号所属网格下标(-1 表示无请求)。 */
|
||||
std::vector<MeanFilterBuild> m_pendingMeanFilterItems; /**< 待构建的信号及其参数。 */
|
||||
std::vector<WeakBoostBuild> m_pendingWeakBoostItems; /**< 待构建的弱波增强信号及其参数。 */
|
||||
int m_boostDragLane = -1; /**< 正在拖动滑块的面板所属网格。 */
|
||||
std::vector<WeakPeakBuild> m_pendingWeakPeakItems; /**< 待构建的弱峰增强信号及其参数。 */
|
||||
int m_peakDragLane = -1; /**< 正在拖动滑块的面板所属网格。 */
|
||||
bool m_mouseOverPanel = false; /**< 上一帧鼠标是否落在某个实验性功能面板上。 */
|
||||
/**
|
||||
* @brief 本帧是否使用「快速绘制」(交互期间关闭波形与派生曲线的抗锯齿)。
|
||||
@@ -1753,18 +1753,18 @@ namespace jdi
|
||||
};
|
||||
mutable std::deque<MeanCurveEntry> m_meanCurveCache;
|
||||
|
||||
/** 弱波增强曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */
|
||||
struct WeakBoostCurveEntry
|
||||
/** 弱峰增强曲线缓存项(按「信号指针 + 增强等级百分比 + 迭代次数」标识)。 */
|
||||
struct WeakPeakCurveEntry
|
||||
{
|
||||
const SignalData* signal = nullptr;
|
||||
int levelPercent = 50; /**< 弱波增强等级(0–100)。 */
|
||||
int levelPercent = 150; /**< 弱峰增强等级(0–200)。 */
|
||||
int iterations = 1; /**< 迭代次数 N(1–10)。 */
|
||||
std::vector<float> values; /**< 与 signal->values 等长;缺失处为 NaN。 */
|
||||
};
|
||||
mutable std::deque<WeakBoostCurveEntry> m_weakBoostCurveCache;
|
||||
mutable std::deque<WeakPeakCurveEntry> m_weakPeakCurveCache;
|
||||
|
||||
// ---- 异步派生曲线计算(后台线程 + 双缓冲)----
|
||||
// 设计:预览曲线(均值滤波 / 弱波增强)在窗口很大时**不在绘制线程里计算**,而是交给后台
|
||||
// 设计:预览曲线(均值滤波 / 弱峰增强)在窗口很大时**不在绘制线程里计算**,而是交给后台
|
||||
// 线程;绘制线程本帧先用上一条已算好的曲线顶替(双缓冲),后台算完后通过
|
||||
// `kMessagePlotDerivedReady` 通知宿主重绘,从而切换为最新曲线。这样滑块 / 光标 / 视界 /
|
||||
// 缩放等交互的反馈始终跟手,不会被计算拖住,同时把重计算放到后台充分利用多核。
|
||||
@@ -1941,7 +1941,7 @@ namespace jdi
|
||||
/**
|
||||
* @brief 装饰层(图例 + Y 轴刻度)的指纹:含外观样式,但**不含派生曲线参数**。
|
||||
*
|
||||
* 拖弱波增强等级/迭代次数滑块时,图例与 Y 轴刻度不变,仅网格内预览曲线变化;
|
||||
* 拖弱峰增强等级/迭代次数滑块时,图例与 Y 轴刻度不变,仅网格内预览曲线变化;
|
||||
* 因此装饰层缓存不应因参数变化而失效(否则拖滑块每帧都要重绘图例与刻度)。
|
||||
*
|
||||
* @return 指纹值。
|
||||
@@ -1971,20 +1971,20 @@ namespace jdi
|
||||
*
|
||||
* 它**只影响网格内容的绘制**(不影响网格结构/视图几何),因此不进入
|
||||
* `SceneGeometryHash`,而是与外观样式一起作为“局部重画”的判据:
|
||||
* 拖动弱波增强的等级/迭代次数滑块时,只需重画受影响的网格,
|
||||
* 拖动弱峰增强的等级/迭代次数滑块时,只需重画受影响的网格,
|
||||
* 不必整幅重绘(也不会把静态场景缓存整体作废)。
|
||||
*/
|
||||
struct SceneDerivedEntry
|
||||
{
|
||||
int edgeCenter = 0; /**< 中心轴边沿计数开关。 */
|
||||
int meanFilter = 0; /**< 均值滤波开关。 */
|
||||
int weakBoost = 0; /**< 弱波增强开关。 */
|
||||
int weakBoostLevelPercent = 0; /**< 弱波增强等级(0–100)。 */
|
||||
int weakBoostIterations = 1; /**< 弱波增强迭代次数 N。 */
|
||||
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> weakBoostItems;
|
||||
std::vector<int> weakPeakItems;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -2222,20 +2222,25 @@ namespace jdi
|
||||
mutable int m_decorOriginX = 0;
|
||||
mutable int m_decorOriginY = 0;
|
||||
|
||||
// ---- 每网格「信号图层」:双缓冲 + 后台绘制(前台只做贴图 / 坐标偏移) ----
|
||||
// ---- 每网格「信号图层」(前台同步维护;交互帧只做贴图 / 坐标偏移) ----
|
||||
//
|
||||
// 平移只改变绘制坐标、不改变画面内容。因此**每个网格各自维护一块离屏位图**
|
||||
// (宽度 = 视界宽 ×3:左右各多渲染半个视界),称为该网格的「信号图层」。
|
||||
// 图层只含基底(底色 + 网格线 + 波形 + 派生曲线),**不含装饰**(Y 轴刻度 / 图例)——
|
||||
// (宽度 = 视界宽 + 左右各半个视界的余量),称为该网格的「信号图层」。
|
||||
// 图层只含基底(底色 + 网格线 + 波形),**不含装饰**(Y 轴刻度 / 图例)——
|
||||
// 装饰锚定在网格左缘、不随时间视界移动,若烘进图层会被平移带走(重影),
|
||||
// 因此装饰由前台单独绘制(见 `m_decorCapturedRects` 那套缓存)。
|
||||
//
|
||||
// **双缓冲**:每个网格有两块图层(`m_layerFront` 前台读取 / `m_layerBack` 后台填充),
|
||||
// 后台绘制完成后再整体交换。前台**永远只读 `m_layerFront`**,因此后台正在绘制时
|
||||
// 前台仍能拿到上一份可用像素,绘制过程不影响交互响应。
|
||||
// ⚠️ **图层一律在 UI 线程内同步维护**(`Paint` 全程持有 `m_renderMutex`),
|
||||
// 因为渲染图层会临时改写 `m_area` / `m_plotRect` / `Tab().ViewMin()/ViewMax()` /
|
||||
// 各网格矩形 —— 这些状态**同时被 UI 线程的消息处理无锁读取**
|
||||
// (`OnMouseMove` 平移读视界、命中测试读网格矩形)。若把图层渲染搬到后台线程,
|
||||
// 后台的"保存 → 改写 → 恢复"会**覆盖用户正在进行的平移**(视界被弹回原位),
|
||||
// 命中测试也会读到错位的网格 → 表现为「拖动时视界跳回 / 波形抖动 / 绘制错乱」。
|
||||
// 这正是先前"后台图层线程"被移除的原因:**不要把图层渲染移回后台**。
|
||||
//
|
||||
// **后台绘制**:跨度 / 几何 / 外观变化时,后台线程池把各网格的新图层画进后台缓冲;
|
||||
// 若前台已经要刷新而后台还没画好,就继续用旧的前台缓冲「应付」交互,不解切换。
|
||||
// 每个图层记录自己的时间映射(`LaneLayer::wideMin`):不同时刻渲染出的图层锚点
|
||||
// 并不相同,滚动与贴图一律**逐网格**按各自的 `wideMin` 计算,**绝不共用一个全局锚点**
|
||||
// (共用全局锚点会让各网格被平移不同的真实距离 → 网格之间时间错位)。
|
||||
/**
|
||||
* @brief 单个网格的「信号图层」(一块只含该网格基底的离屏位图)。
|
||||
*/
|
||||
@@ -2264,18 +2269,28 @@ namespace jdi
|
||||
* @param [IN,OUT] layer 目标图层(会被写入尺寸 / 时间映射 / 指纹)。
|
||||
* @param [IN] stripX 只渲染位图的这一段 x 区间(滚动时补画细条用)。
|
||||
* @param [IN] stripW 该区间的宽度;传 0 表示渲染整块。
|
||||
* @param [IN] wideMin 位图 `x=0` 对应的时间。
|
||||
* **必须与调用方的意图一致**:整块重画传 `LayerDefaultWideMin()`(以当前视界居中);
|
||||
* 滚动补细条则传 `layer.wideMin`(与该图层已有像素同一映射,否则会留下接缝)。
|
||||
* @return 渲染成功返回 true。
|
||||
*/
|
||||
bool RenderLaneLayerRegion(int laneIndex, LaneLayer& layer, int stripX, int stripW);
|
||||
bool RenderLaneLayerRegion(int laneIndex, LaneLayer& layer, int stripX, int stripW,
|
||||
double wideMin);
|
||||
/** @brief 「以当前视界居中」的默认图层映射(位图 `x=0` 对应的时间)。 */
|
||||
double LayerDefaultWideMin() const;
|
||||
|
||||
/**
|
||||
* @brief 以当前视界为中心重建全部网格图层(同步;跨度变化 / 首帧时使用)。
|
||||
* @brief 以当前视界为中心重建全部网格图层(同步;跨度变化 / 首帧 / 图层缺失时使用)。
|
||||
* @return 全部成功返回 true。
|
||||
*/
|
||||
bool RebuildWideBuffer();
|
||||
/**
|
||||
* @brief 让全部图层"滚动"以跟随视界:整数像素自贴图平移 + 只补画新暴露的细条。
|
||||
* @return 图层仍可用返回 true。
|
||||
* @brief 让各图层"滚动"以跟随视界:逐网格整数像素自贴图平移 + 只补画新暴露的细条。
|
||||
*
|
||||
* 每个网格按**自己的** `wideMin` 计算位移;位移过大(锚点离当前视界太远)或内容指纹
|
||||
* 与当前状态不一致的网格直接整块重画。任一网格图层缺失(尺寸 / 跨度不符)返回 false,
|
||||
* 由调用方先 `RebuildWideBuffer()`。
|
||||
* @return 全部网格都能用现成像素跟随视界返回 true。
|
||||
*/
|
||||
bool RollWideBuffer();
|
||||
/**
|
||||
@@ -2285,62 +2300,26 @@ namespace jdi
|
||||
*/
|
||||
bool BlitWideBuffer(gd::Graphics& g) const;
|
||||
/**
|
||||
* @brief 全部图层是否已就绪且跨度与当前视界一致(**不要求内容也一致**)。
|
||||
* @brief 全部图层是否已就绪且跨度与当前视界一致。
|
||||
*
|
||||
* 内容一致与否由后台线程负责跟踪:只要跨度一致、图层能覆盖当前视界,
|
||||
* 前台就直接用现成像素“应付”交互(内容稍旧的一两帧由后台画好后交换),
|
||||
* 这样样式/参数变化也不会退化成整幅重绘。
|
||||
* @return 可以直接贴图层像素返回 true。
|
||||
* 只检查"位图尺寸 / 网格数量 / 视界跨度"这类**结构性**条件(内容指纹由
|
||||
* `RollWideBuffer()` 逐个网格处理);不满足时应先 `RebuildWideBuffer()`。
|
||||
* @return 可以直接滚动 + 贴图层像素返回 true。
|
||||
*/
|
||||
bool LayersReadyForView() const;
|
||||
/** @brief 使全部图层失效。 */
|
||||
void InvalidateWideBuffer();
|
||||
/** @brief 释放全部图层位图(前后台都释放)。 */
|
||||
/** @brief 释放全部图层位图。 */
|
||||
void ReleaseWideBuffer();
|
||||
|
||||
std::vector<LaneLayer> m_layerFront; /**< 各网格的「信号图层」(与 `m_lanes` 同序)。 */
|
||||
/**
|
||||
* @brief 为"内容与前台图层不一致"的网格排一次后台渲染任务(去重)。
|
||||
*
|
||||
* 后台渲染完成后会与前台缓冲交换并通知宿主重绘,前台因此始终有立即可用的像素。
|
||||
* @brief 图层元数据的互斥(当前图层只在 UI 线程维护,故它实际不承担跨线程保护;
|
||||
* 保留是为了给 `BlitWideBuffer()` 这类 `const` 读取路径一个明确的一致性边界)。
|
||||
*/
|
||||
void ScheduleStaleLayerRenders();
|
||||
|
||||
/** @brief 确保后台图层绘制线程已启动。 */
|
||||
void EnsureLayerWorker();
|
||||
/** @brief 停止后台图层绘制线程。 */
|
||||
void StopLayerWorker();
|
||||
/** @brief 后台图层绘制线程主循环。 */
|
||||
void LayerWorkerLoop();
|
||||
|
||||
/** 单个网格图层任务:只需网格下标(参数在渲染时按最新状态取)。 */
|
||||
struct LayerJob
|
||||
{
|
||||
int laneIndex = -1;
|
||||
std::uint64_t key = 0;
|
||||
};
|
||||
|
||||
std::vector<LaneLayer> m_layerFront; /**< 前台读取(后台不会就地改写)。 */
|
||||
std::vector<LaneLayer> m_layerBack; /**< 后台填充。 */
|
||||
mutable std::mutex m_layerMutex; /**< 保护前后台切换 / 元数据读取 / 锚点(const 方法也要读,故 mutable)。 */
|
||||
std::recursive_mutex m_renderMutex; /**< 串行化“改写渲染状态”的绘制(前台整帧 vs 后台单网格);可重入(整帧内部会再调图层渲染)。 */
|
||||
double m_layerAnchorMin = 0.0; /**< 图层滚动锚点:视界左端对应的时间。 */
|
||||
bool m_layerAnchorValid = false; /**< 锚点是否有效。 */
|
||||
|
||||
std::thread m_layerWorker; /**< 后台图层绘制线程。 */
|
||||
std::mutex m_layerJobMutex; /**< 任务队列互斥。 */
|
||||
std::condition_variable m_layerJobCv; /**< 任务到来 / 退出通知。 */
|
||||
std::vector<LayerJob> m_layerJobs; /**< 待渲染的网格任务。 */
|
||||
bool m_layerWorkerStop = false; /**< 请求退出后台线程。 */
|
||||
/**
|
||||
* @brief 连续排了多少次"图层重建"却始终没与前台一致。
|
||||
*
|
||||
* 正常情况下一两次就会收敛(后台画完→交换→指纹一致)。若持续增长,说明后台渲染
|
||||
* 算出的指纹与前台不一致(例如某个参与指纹的状态没被正确还原),此时改为**同步重建**
|
||||
* 一次强行收敛,避免"前台永远不等后台、后台永远画不好"的死循环。
|
||||
*/
|
||||
int m_layerRescheduleStreak = 0;
|
||||
/** 前台最近一次 `Paint` 的时刻(毫秒,`GetTickCount64`)。后台据此"让路",不与前台抢渲染锁。 */
|
||||
mutable std::atomic<unsigned long long> m_lastPaintTick{0};
|
||||
/** 后台线程已完成绘制的图层数量(原子,仅用于验证/诊断)。 */
|
||||
mutable std::mutex m_layerMutex;
|
||||
std::recursive_mutex m_renderMutex; /**< 串行化"改写渲染状态"的绘制(整帧 / 单网格图层);可重入。 */
|
||||
/** 已渲染的图层区域数量(原子,仅用于验证 / 诊断)。 */
|
||||
mutable std::atomic<std::uint64_t> m_layerRenderCount{0};
|
||||
|
||||
bool m_renderWide = false; /**< 正在渲染信号图层(重入 `PaintLane` 时跳过装饰)。 */
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+69
-3
@@ -509,6 +509,7 @@ Documents/ 项目文档(含本文件)
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash | 落地用户提出的「各网格独立双缓冲区 + 后台线程绘制」架构:把全局宽缓冲区重构为**每网格信号图层**(宽×3,只含基底不含装饰)、`m_layerFront/m_layerBack` **双缓冲** + `m_layerWorker` **后台线程**(前台只做 BitBlt 坐标偏移,不再绘制波形);前台"图层就绪即用现成像素、内容稍旧也照用,交后台补画后交换";加不收敛保护、前台优先让路、固定加锁顺序(render→layer)、渲染状态完整还原(含 `m_laneContentHeight`/`LaneScroll`)。新增 `BackgroundLayerRenderCount()` 公开计数用于验证后台绘制确实发生(一轮诊断 18~45 次)。实测深放大区间(2.045ms 一大格)各交互 0.7~8.7ms/帧,像素差 ≤0.017(除亚像素平移),静态/结算帧像素精确。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash | 修复用户报告的「缩放后平移出现拖影/残留/错误元素」与「2ms 一大格区间仍然卡顿」。根因:①网格纵向滚动量未参与任何内容指纹→滚动时把旧位置像素贴回且每两帧触发一次整幅重绘(≈8fps);②`FindStyleOnlyLanes` 未校验视界→视界变化后仍走局部补丁;③宽基底用含装饰的整幅画面初始化→装饰随平移重影;④装饰缓存的矩形列表与坐标原点两帧不同源→装饰消失/黑块/错位。修复后交互帧 9~19ms → **4.5~6ms**,纵向拖动残留 2 万+ 通道 → 72 通道(亚像素量化),静态/结算帧像素精确。新增 `perf.exe ... 6` 诊断模式(逐帧计时 + 逐像素对比 + 差异包围盒)。未实现:每网格双缓冲后台渲染(需先把 `PaintLane` 改造成无状态渲染器,已在文档 9.15 说明)。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **跨设备记忆合流**:把起源设备(本机)的详细工程记忆**并入本文件**(同一记忆文件,不再另建 `agent_mem_origin.md` 等旁支文件),新增 §11(跨设备协作须知)与 §12(起源设备详细记忆原文)。远端 `gitea/main` 已快进合并到本机(`7608e85` → `f97c4cf`,**16** 个提交)。本机 MinGW-w64 GCC 15.2.0 (UCRT) 下 `make release` 通过。 |
|
||||
| 2026-10-12 | DeepSeek-V4.1-Flash(**起源设备**) | **修复「缩放后平移出现严重绘制错误/波形抖动/前台被拖慢」**:根因是后台图层线程会改写共享渲染状态(保存→改写→恢复`m_area`/`m_plotRect`/视界/网格矩形),与 UI 线程无锁的平移/命中测试相冲 → 视界被弹回、网格错位;叠加"全局锚点 + 逐网格内容"导致各网格时间错位。修法:**移除后台图层线程**(改为 UI 线程同步维护)、**逐网格独立锚点与滚动**(补细条用该图层自己的 `wideMin`)、跨度变化时同步 `RebuildWideBuffer()`。同时把「弱波增强」改名「**弱峰增强**」并改为 **0~200% 分段插值**(0% 完全不动 / 100% 到较温和一侧 / 200% 完全拉平;新 1.5 = 旧 0.5)。离屏验证:居中图层 vs 整幅重绘 **5 网格逐像素 0 差异**、平移帧对齐 ≤1px、24 次竞态检测 0 覆盖;测试套件 **380/380 通过**。 |
|
||||
|
||||
---
|
||||
|
||||
@@ -536,9 +537,11 @@ Documents/ 项目文档(含本文件)
|
||||
- 场景贴图必须在**图层之前**(否则鼠标悬停每两帧触发一次"结算整幅重绘",反而更卡);
|
||||
- 图层必须在**样式补丁之前**(拖弱波增强 / 均值滤波参数只改派生曲线,走图层更快且质量更高)。
|
||||
2. 每网格图层**只含基底**(底色 + 网格线 + 波形 + 派生曲线),**不含装饰**(Y 轴刻度 / 图例 —— 它们锚定网格左缘、不随时间视界移动)。
|
||||
3. ⚠️ 图层位图宽度 = `width + 2*max(1, width/2)` ≈ **2 × 视界宽**(左右各半屏)。头注释与 §10 变更日志写的"**宽 ×3**"是**错的**(代码就是 ×2)。
|
||||
4. `RenderLaneLayerRegion` 结束后**必须** `BuildLaneVisuals()`(重算 `m_laneContentHeight`)**再**还原 `LaneScroll` 与各网格 `yMin/yMax`;漏还原任何一项 → 前后台指纹永不相等 → 图层永远"过期" → 每帧重复排任务 → **界面卡死**。
|
||||
5. 加锁顺序**恒定** `m_renderMutex`(recursive)→ `m_layerMutex`;反序 = AB-BA 死锁。
|
||||
3. 图层位图宽度 = `width + 2*max(1, width/2)` ≈ **2 × 视界宽**(左右各半屏,可平移 ±半屏)。
|
||||
4. `RenderLaneLayerRegion` 结束后**必须** `BuildLaneVisuals()`(重算 `m_laneContentHeight`)**再**还原 `LaneScroll` 与各网格 `yMin/yMax`;漏还原任何一项 → 算出的图层指纹与实际渲染不符 → 每帧重复重画(白耗性能;旧版有后台线程时会死循环卡死)。
|
||||
5. ⚠️ **图层渲染一律在 UI 线程内同步完成**(`Paint` 全程持有 `m_renderMutex`)。**绝不要把图层渲染搬到后台线程**:它会临时改写 `m_area`/`m_plotRect`/`Tab().ViewMin()/ViewMax()`/各网格矩形,而这些状态被 UI 线程的消息处理**无锁读/写**(平移读视界、命中测试读网格矩形),后台的"保存→改写→恢复"会**覆盖用户正在进行的平移**(视界弹回)并让命中测试错位 —— 这就是 2026-10-12 移除后台图层线程的原因(详见 §12 本轮)。
|
||||
6. 每个图层记录**自己的**时间映射 `LaneLayer::wideMin`;滚动与贴图必须**逐网格**按各自 `wideMin` 计算,**绝不共用一个全局锚点**(否则各网格被平移不同的真实距离 → 网格间时间错位)。补细条时必须传 `layer.wideMin`(不能用"当前视界"的映射,否则有 ≤0.5px 接缝与抖动)。
|
||||
7. 加锁顺序**恒定** `m_renderMutex`(recursive)→ `m_layerMutex`(图层不再有跨线程访问,但顺序约定保留)。
|
||||
6. **GDI 绘制不受 GDI+ `SetClip` 约束**:`UiText`(`DrawTextW` / `ETO_CLIPPED`)与 `UiFillRect` 的 GDI 快路径(不透明且面积 ≥ 20000px²)都会画出裁剪区;宽矩形 / 文本必须**自己夹取**,GDI 批绘必须显式传 clip。
|
||||
7. `SceneContentHash` 必须覆盖 `Tab().LaneScroll()`(网格纵向滚动量)与 `SelectedRef()`;`FindStyleOnlyLanes()` 必须**逐项比较 `SceneCacheKey`**(`SceneGeometryHash()` 刻意不含视界,只比它不足以判定"视图没变")。
|
||||
8. 随鼠标变化的绘制(悬停高亮 / 光晕 / 图例高亮 / 光标读数 / 选区 / 提示 / 面板悬停)**必须同时**加在"缓存贴图快路径"与"整幅重绘路径"两侧。
|
||||
@@ -2331,3 +2334,66 @@ Documents/ 项目文档(含本文件)
|
||||
全部与改动前一致;`make clean; make all` 零警告。
|
||||
- ⚠️ `act.ps1` 的动作参数是 **客户区坐标**(窗口钉在 (0,0)),而截图是整窗 → 换算 `客户区 = 截图 − (9,38)`。
|
||||
标签栏在客户区 y≈28(截图 y≈66 已是工具栏「导入文件」按钮,误点会弹出文件对话框)。
|
||||
|
||||
|
||||
---
|
||||
|
||||
## 本轮(2026-10-12,起源设备):图层架构缺陷修复 + 「弱峰增强」0~200%
|
||||
|
||||
### A. 渲染:移除后台图层线程(根因),改为逐网格同步滚动
|
||||
- **用户现象**(`VERY_BIG_DATA` 全信号、选区放大后用中/左键拖动移动视界):**严重绘制错误**、**波形抖动**、**前台绘制被拖慢**。
|
||||
- **根因(三个叠在一起)**:
|
||||
1. **后台线程改写共享渲染状态**:`RenderLaneLayerRegion` 为了用宽窗口映射渲染,会临时改写
|
||||
`m_area` / `m_plotRect` / `Tab().ViewMin()/ViewMax()` / 各网格矩形,完事再“恢复”。而 UI 线程的
|
||||
`OnMouseMove`(平移写视界、命中测试读网格矩形)**无锁读/写**这些状态 → 后台的“恢复”会把
|
||||
用户刚做的平移**弹回**、命中测试也会读到错位的网格。
|
||||
2. **全局锚点 + 逐网格内容**:`RollWideBuffer` 用**一个** `m_layerAnchorMin` 给所有图层算
|
||||
**同一个**位移,而各图层可能是在**不同视界**下渲染的(后台逐网格渲染、或拖拽中渲染)
|
||||
→ 各网格被平移了**不同的真实距离** → 网格之间时间错位、贴回的像素互相矛盾。
|
||||
3. **细条用错映射**:滚动补细条时用“当前视界”的映射渲染,而旧像素是另一个映射
|
||||
(`layer.wideMin` 还被覆盖)→ ≤0.5px 接缝 + 拖动时的**抖动**。
|
||||
- **修法**:
|
||||
- **删除后台图层线程**(`m_layerWorker`/任务队列/`EnsureLayerWorker`/`StopLayerWorker`/
|
||||
`LayerWorkerLoop`/`ScheduleStaleLayerRenders`/`m_layerBack`/`m_layerAnchorMin|Valid`/`m_lastPaintTick`/
|
||||
`m_layerRescheduleStreak`)。**图层渲染一律在 UI 线程内同步完成**(不要再搬回后台)。
|
||||
- **逐网格独立滚动**:每个图层以自己的 `wideMin` 为锚点算位移,细条用 `layer.wideMin` 渲染
|
||||
(与已平移的旧像素同一映射)→ 消除接缝与抖动。
|
||||
- `RenderLaneLayerRegion` 新增 `wideMin` 形参;新增 `LayerDefaultWideMin()`(当前视界居中)。
|
||||
- `RollWideBuffer()` 逐网格:内容指纹对不上(`layer.key != LaneLayerKey(i)`,例如工具其他路径改了样式而图层还是旧的)
|
||||
或锚点离视界太远 → 该网格**整块重画**;否则整数像素自贴图 + 补细条。
|
||||
- `Paint` 宽底度分支:`if (!LayersReadyForView()) RebuildWideBuffer();` → 再 `RollWideBuffer()`
|
||||
(跳度变化/首帧时**同步**重建全部图层,取代原来的后台补画)。
|
||||
- `BackgroundLayerRenderCount()` 保留为公开诊断计数(现在统计同步图层区域渲染次数)。
|
||||
- **离屏验证**(`.preflight/layercheck`,窗口 SW_HIDE、不动鼠标/键盘/屏幕):
|
||||
- **居中图层 vs 整幅重绘:5 个网格逐像素 0 差异**(证明“底座管线”本身精确,与
|
||||
f97c4cf 之前的结论一致)。
|
||||
- 平移帧:逐网格最佳对齐位移 **≤1px**、**视界不被覆盖**、无网格被跳过;
|
||||
残差为设计内 ≤0.5px 整数像素贴图残留(视界停止时的“结算帧”恢复像素精确)。
|
||||
- 竞态/视界覆盖检测:24 次“跳度抖动 + 视界变化 + 渲染”→ **0 次覆盖**。
|
||||
- 注:旧代码在该自动化场景下只能概率性复现缺陷(后台线程会被前台长期锁住而饥饿),
|
||||
因此主要证据是“结构不可能出错(没有线程再改写渲染状态)+ 居中图层逐像素精确”。
|
||||
- ⚠️ 见 §11.2 的硬约束(图层不得后台化、不得共用全局锚点)。
|
||||
|
||||
### B. 「弱波增强」→「弱峰增强」+ 等级 0~200%
|
||||
- **改名**:界面文案与标识符全部改为 `WeakPeak`/`weakPeak`(`ComputeWeakPeakBoost`、
|
||||
`SetLaneWeakPeak*`、`EdgePanelKind::PEAK`、`PlotDragMode::PEAK_LEVEL/PEAK_ITERATIONS`、
|
||||
`PLOT_CMD_LANE_WEAK_PEAK`、`PeakExtremum` 等);导出信号名 `"<原信号> (弱峰增强 M% N次迭代)"`。
|
||||
- **等级语义(0–2,面板 0%–200%)分段插值**(`ApplyWeakPeakStage`,`s` = 等级):
|
||||
- 波峰:`s<=1` → `target = cur + s*(lower-cur)`;`s>1` → `target = lower + (s-1)*(upper-lower)`
|
||||
(`lower/upper` = 左右同类极值中较小/较大者)。
|
||||
- 波谷:`s<=1` → `target = cur + s*(upper-cur)`;`s>1` → `target = upper - (s-1)*(upper-lower)`。
|
||||
- 语义:**0% 完全不动**(可做极小幅度调整)、**100% 到较温和的一侧**、**200% 完全拉平**;
|
||||
两段在 100% 处连续。**新 1.5 == 旧 0.5**、**新 2.0 == 旧 1.0**(旧行为整体平移到后 100%)。
|
||||
- `Lane::weakPeakLevel` 默认 **1.5**(= 旧默认 0.5 的效果);`levelPercent`(构建请求/缓存键)
|
||||
范围 0–200、默认 150;常量 `kWeakPeakLevelMax = 2.0`、`kWeakPeakPercentMax = 200`。
|
||||
- 滑块:轨道比例 = `level / 2`,数值显示 `level * 100`%。
|
||||
- **测试同步**(`.preflight/uitest`):`[X]` 改为 0/50/100/150/200% 五个目标值;阶数扫描与
|
||||
“完全恢复”块改用 150%/200% 以保持旧期望;`[Y]` 用 150%;派生信号名校验改用 `\u5f31\u5cf0...`。
|
||||
- **⚠️ 本轮顺带修正两处“过期的测试期望”**(它们与实现早已不一致,只是没人跑过 uitest,
|
||||
因为 f97c4cf 那轮只用 `perf.exe` 做验证):
|
||||
- `[J]`:样式补丁**故意**渲染“快速帧”(关 AA)→ 该帧与全质量整幅重绘**不可能逐像素相同**。
|
||||
现改为:断言补丁帧**标记为降级**,渲染到缓存重新有效(≤4 帧)后**用修复帧**与整幅重绘比对(0 差异)。
|
||||
- `[AF]`:面板像素层 96% 不透明 → 底下波形的 ≤0.5px 残留会透出(实测最大 72 级、仅 6 个像素)
|
||||
→ 容差由 32 提到 **96**(真正“面板内容过期”的差异是百级)。
|
||||
- `[X]` 预览曲线像素计数的颜色容差 6 → **40**(真实信号上曲线多与波形重合,纯色像素本就少)。
|
||||
- **验证**:`make release` 零警告;离屏测试套件 **380/380 通过**(exit=0)。
|
||||
+50
-41
@@ -374,13 +374,13 @@ namespace jdi
|
||||
using EdgeCenterItem = MeanFilterItem;
|
||||
|
||||
/**
|
||||
* @brief 「弱波增强」中某个信号自己的条目。
|
||||
* @brief 「弱峰增强」中某个信号自己的条目。
|
||||
*
|
||||
* 目前只用到 `ref`(增强等级属于整个网格,统一保存在 `weakBoostLevel` 中);
|
||||
* 目前只用到 `ref`(增强等级属于整个网格,统一保存在 `weakPeakLevel` 中);
|
||||
* 复用 `MeanFilterItem` 结构是为了与均值滤波/中心轴共享
|
||||
* 「条目查找 + 信号插入/删除时的引用重映射」等既有实现。
|
||||
*/
|
||||
using WeakBoostItem = MeanFilterItem;
|
||||
using WeakPeakItem = MeanFilterItem;
|
||||
|
||||
std::wstring name; /**< 网格名称。 */
|
||||
std::vector<SignalRef> signals; /**< 该网格包含的信号(按加入顺序)。 */
|
||||
@@ -501,53 +501,58 @@ namespace jdi
|
||||
double meanFilterPanelY = -1.0;
|
||||
|
||||
/**
|
||||
* @brief 「实验性功能 → 弱波增强」开关。
|
||||
* @brief 「实验性功能 → 弱峰增强」开关。
|
||||
*
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条「弱波增强」曲线:
|
||||
* 开启后为本网格内**被勾选**的信号额外绘制一条「弱峰增强」曲线:
|
||||
* 识别纹波的波峰/波谷,把“比左右同类极值更弱”的波峰上拉、波谷下拉,
|
||||
* 并按「弱波增强等级」在左右两个同类极值之间取目标值,使其恢复与周边一致的振幅。
|
||||
* 并按「弱峰增强等级」在左右两个同类极值之间取目标值,使其恢复与周边一致的振幅。
|
||||
* 这条曲线只是叠加显示,不会作为信号加入左侧列表。
|
||||
*/
|
||||
bool weakBoost = false;
|
||||
|
||||
/** 弱波增强等级(0–1),0% = 目标取左右极值中较小/较大的那一个,100% = 取另一个。 */
|
||||
double weakBoostLevel = 0.5;
|
||||
bool weakPeak = false;
|
||||
|
||||
/**
|
||||
* @brief 弱波增强的迭代次数 N(1–10)。
|
||||
* @brief 弱峰增强等级(0–2,面板上显示为 0%–200%)。
|
||||
*
|
||||
* 0 = 目标就是当前值(完全不动);1 = 拉到较温和的一侧(波峰取较低的左/右波峰,
|
||||
* 波谷取较高的左/右波谷);2 = 拉到另一侧(完全拉平)。默认 1.5,等价于旧版的 50%。
|
||||
*/
|
||||
double weakPeakLevel = 1.5;
|
||||
|
||||
/**
|
||||
* @brief 弱峰增强的迭代次数 N(1–10)。
|
||||
*
|
||||
* 与「均值滤波」的迭代次数 N 一样表示“把同一处理重复应用 N 次”(每一阶以上一阶为输入):
|
||||
* 一阶时改定量就已严格等于「等级」对应的目标值;多阶会把相邻弱波互相抬高
|
||||
* 彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||
* 一阶时改定量就已严格等于「等级」对应的目标值;多阶会把相邻弱峰互相抬高
|
||||
* 彼此的基准,从而把较宽的弱峰区逐步恢复。
|
||||
*/
|
||||
int weakBoostIterations = 1;
|
||||
int weakPeakIterations = 1;
|
||||
|
||||
/** 参与弱波增强预览的信号条目(只包含被勾选的信号)。 */
|
||||
std::vector<WeakBoostItem> weakBoostItems;
|
||||
/** 参与弱峰增强预览的信号条目(只包含被勾选的信号)。 */
|
||||
std::vector<WeakPeakItem> weakPeakItems;
|
||||
|
||||
/** 取某个信号的弱波增强条目(未勾选时返回空指针)。 */
|
||||
const WeakBoostItem* FindWeakBoostItem(const SignalRef& ref) const;
|
||||
/** 取某个信号的弱峰增强条目(未勾选时返回空指针)。 */
|
||||
const WeakPeakItem* FindWeakPeakItem(const SignalRef& ref) const;
|
||||
|
||||
/** 取得或创建某信号的弱波增强条目(用于勾选)。 */
|
||||
WeakBoostItem& EnsureWeakBoostItem(const SignalRef& ref);
|
||||
/** 取得或创建某信号的弱峰增强条目(用于勾选)。 */
|
||||
WeakPeakItem& EnsureWeakPeakItem(const SignalRef& ref);
|
||||
|
||||
/** 取消某信号的弱波增强勾选。 */
|
||||
void RemoveWeakBoostItem(const SignalRef& ref);
|
||||
/** 取消某信号的弱峰增强勾选。 */
|
||||
void RemoveWeakPeakItem(const SignalRef& ref);
|
||||
|
||||
/** 取某信号的弱波增强勾选状态。 */
|
||||
bool WeakBoostEnabled(const SignalRef& ref) const
|
||||
/** 取某信号的弱峰增强勾选状态。 */
|
||||
bool WeakPeakEnabled(const SignalRef& ref) const
|
||||
{
|
||||
return FindWeakBoostItem(ref) != nullptr;
|
||||
return FindWeakPeakItem(ref) != nullptr;
|
||||
}
|
||||
|
||||
/** 弱波增强浮动面板是否展开。 */
|
||||
bool weakBoostPanelOpen = true;
|
||||
/** 弱峰增强浮动面板是否展开。 */
|
||||
bool weakPeakPanelOpen = true;
|
||||
|
||||
/** 弱波增强面板左上角位置(客户区像素;<0 表示自动定位)。 */
|
||||
double weakBoostPanelX = -1.0;
|
||||
/** 弱峰增强面板左上角位置(客户区像素;<0 表示自动定位)。 */
|
||||
double weakPeakPanelX = -1.0;
|
||||
|
||||
/** 弱波增强面板左上角纵坐标(<0 表示自动定位)。 */
|
||||
double weakBoostPanelY = -1.0;
|
||||
/** 弱峰增强面板左上角纵坐标(<0 表示自动定位)。 */
|
||||
double weakPeakPanelY = -1.0;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -592,32 +597,36 @@ namespace jdi
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
* @brief 「弱波增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱波增强)。
|
||||
* @brief 「弱峰增强」:把比周边更弱的纹波波峰/波谷恢复(自适应弱峰增强)。
|
||||
*
|
||||
* 本算法**不做任何前置滤波**:需要平滑时用户可以先用「均值滤波」构建一条
|
||||
* 滤波后的信号,再对它做弱波增强。极值点直接在输入采样上识别。
|
||||
* 滤波后的信号,再对它做弱峰增强。极值点直接在输入采样上识别。
|
||||
*
|
||||
* 单阶处理(全 O(n)):
|
||||
* 1. **识别趋势与极值**:扫描单调段,方向发生翻转处的上一个采样点即为
|
||||
* 波峰(先升后降)或波谷(先降后升);波峰与波谷交替出现。
|
||||
* 2. **判定“弱”**:对第 k 个波峰,取同类的左右相邻波峰(k−2、k+2)。
|
||||
* 若本波峰低于两者的均值,说明它是被削弱的那个;波谷反之(高于两者均值)。
|
||||
* 3. **目标值**(`level` = 弱波增强等级,0–1):
|
||||
* * 波峰:左右两个波峰中较小者为 0%、较大者为 100%,
|
||||
* 目标 = min + level × (max − min);
|
||||
* * 波谷:左右两个波谷中较大者为 0%、较小者为 100%,
|
||||
* 目标 = max − level × (max − min)。
|
||||
* 3. **目标值**(`level` = 弱峰增强等级,0–2,对应面板上的 0%–200%):
|
||||
* * 波峰:左右两个波峰中较小者记为 `lower`、较大者记为 `upper`;
|
||||
* 0 ≤ level ≤ 1 → 目标 = 当前值 + level × (`lower` − 当前值);
|
||||
* 1 < level ≤ 2 → 目标 = `lower` + (level − 1) × (`upper` − `lower`)。
|
||||
* * 波谷:左右两个波谷中较大者记为 `upper`、较小者记为 `lower`;
|
||||
* 0 ≤ level ≤ 1 → 目标 = 当前值 + level × (`upper` − 当前值);
|
||||
* 1 < level ≤ 2 → 目标 = `upper` − (level − 1) × (`upper` − `lower`)。
|
||||
* 即 **0% 完全不动**(可做极小幅度调整)、**100% 拉到较温和的一侧**、
|
||||
* **200% 完全拉平**;两段在 100% 处连续。
|
||||
* 4. **平滑偏移**:极值点本身取其目标值,两侧的数据点按相邻两个极值点的偏移量
|
||||
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱波附近被推回。
|
||||
* 平滑过渡(平滑步进,两端导数为 0),使波形整体趋势不变、只在弱峰附近被推回。
|
||||
*
|
||||
* 正常周期的极值不满足条件、偏移量恒为 0,因此输出与输入逐位相同。
|
||||
*
|
||||
* @param [IN] values 原始采样值。
|
||||
* @param [IN] level 弱波增强等级(0–1,超出范围会被夹取)。
|
||||
* @param [IN] level 弱峰增强等级(0–2,对应 0%–200%,超出范围会被夹取)。
|
||||
* @param [IN] iterations 迭代次数 N(重复应用次数,至少为 1)。
|
||||
* @param [OUT] out 输出数组(长度与 values 相同;原值为 NaN 处保持 NaN)。
|
||||
*/
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int iterations,
|
||||
void ComputeWeakPeakBoost(const std::vector<double>& values, double level, int iterations,
|
||||
std::vector<float>& out);
|
||||
|
||||
/**
|
||||
|
||||
+75
-66
@@ -134,13 +134,13 @@ namespace jdi
|
||||
bool meanFilterPanelOpen = true;
|
||||
double meanFilterPanelX = -1.0;
|
||||
double meanFilterPanelY = -1.0;
|
||||
bool weakBoost = false;
|
||||
std::vector<Lane::WeakBoostItem> weakBoostItems;
|
||||
double weakBoostLevel = 0.5;
|
||||
int weakBoostIterations = 1;
|
||||
bool weakBoostPanelOpen = true;
|
||||
double weakBoostPanelX = -1.0;
|
||||
double weakBoostPanelY = -1.0;
|
||||
bool weakPeak = false;
|
||||
std::vector<Lane::WeakPeakItem> weakPeakItems;
|
||||
double weakPeakLevel = 1.5;
|
||||
int weakPeakIterations = 1;
|
||||
bool weakPeakPanelOpen = true;
|
||||
double weakPeakPanelX = -1.0;
|
||||
double weakPeakPanelY = -1.0;
|
||||
};
|
||||
|
||||
// 先记下每个信号原来所在网格的网格自身状态:
|
||||
@@ -166,13 +166,13 @@ namespace jdi
|
||||
carry.meanFilterPanelOpen = lane.meanFilterPanelOpen;
|
||||
carry.meanFilterPanelX = lane.meanFilterPanelX;
|
||||
carry.meanFilterPanelY = lane.meanFilterPanelY;
|
||||
carry.weakBoost = lane.weakBoost;
|
||||
carry.weakBoostItems = lane.weakBoostItems;
|
||||
carry.weakBoostLevel = lane.weakBoostLevel;
|
||||
carry.weakBoostIterations = lane.weakBoostIterations;
|
||||
carry.weakBoostPanelOpen = lane.weakBoostPanelOpen;
|
||||
carry.weakBoostPanelX = lane.weakBoostPanelX;
|
||||
carry.weakBoostPanelY = lane.weakBoostPanelY;
|
||||
carry.weakPeak = lane.weakPeak;
|
||||
carry.weakPeakItems = lane.weakPeakItems;
|
||||
carry.weakPeakLevel = lane.weakPeakLevel;
|
||||
carry.weakPeakIterations = lane.weakPeakIterations;
|
||||
carry.weakPeakPanelOpen = lane.weakPeakPanelOpen;
|
||||
carry.weakPeakPanelX = lane.weakPeakPanelX;
|
||||
carry.weakPeakPanelY = lane.weakPeakPanelY;
|
||||
for (const SignalRef& ref : lane.signals)
|
||||
{
|
||||
previousCarry.emplace_back(ref, carry);
|
||||
@@ -209,13 +209,13 @@ namespace jdi
|
||||
lane.meanFilterPanelOpen = carry.meanFilterPanelOpen;
|
||||
lane.meanFilterPanelX = carry.meanFilterPanelX;
|
||||
lane.meanFilterPanelY = carry.meanFilterPanelY;
|
||||
lane.weakBoost = carry.weakBoost;
|
||||
lane.weakBoostItems = carry.weakBoostItems;
|
||||
lane.weakBoostLevel = carry.weakBoostLevel;
|
||||
lane.weakBoostIterations = carry.weakBoostIterations;
|
||||
lane.weakBoostPanelOpen = carry.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = carry.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = carry.weakBoostPanelY;
|
||||
lane.weakPeak = carry.weakPeak;
|
||||
lane.weakPeakItems = carry.weakPeakItems;
|
||||
lane.weakPeakLevel = carry.weakPeakLevel;
|
||||
lane.weakPeakIterations = carry.weakPeakIterations;
|
||||
lane.weakPeakPanelOpen = carry.weakPeakPanelOpen;
|
||||
lane.weakPeakPanelX = carry.weakPeakPanelX;
|
||||
lane.weakPeakPanelY = carry.weakPeakPanelY;
|
||||
};
|
||||
|
||||
// 收集全部可见信号引用。
|
||||
@@ -1052,9 +1052,9 @@ namespace jdi
|
||||
namespace
|
||||
{
|
||||
/**
|
||||
* @brief 「弱波增强」内部使用的极值点(波峰 / 波谷)。
|
||||
* @brief 「弱峰增强」内部使用的极值点(波峰 / 波谷)。
|
||||
*/
|
||||
struct BoostExtremum
|
||||
struct PeakExtremum
|
||||
{
|
||||
std::size_t index = 0; /**< 采样下标。 */
|
||||
double value = 0.0; /**< 采样值。 */
|
||||
@@ -1062,18 +1062,18 @@ namespace jdi
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 「弱波增强」的单阶处理:把偏弱的波峰上拉、偏弱的波谷下拉。
|
||||
* @brief 「弱峰增强」的单阶处理:把偏弱的波峰上拉、偏弱的波谷下拉。
|
||||
*
|
||||
* **不做任何前置滤波**(需要平滑时用户可以先用「均值滤波」构建一条滤波后的信号):
|
||||
* 极值点直接在输入采样上通过单调段的方向翻转识别。
|
||||
*
|
||||
* @param [IN] input 本阶的输入采样值。
|
||||
* @param [IN] strength 弱波增强等级(0–1,已夹取)。
|
||||
* @param [IN] strength 弱峰增强等级(0–1,已夹取)。
|
||||
* @param [OUT] output 本阶的输出(长度与 input 相同;原值为 NaN 处保持 NaN)。
|
||||
* @return 确实有采样点被改动返回 true;完全无弱波时返回 false。
|
||||
* @return 确实有采样点被改动返回 true;完全无弱峰时返回 false。
|
||||
*/
|
||||
bool ApplyWeakBoostStage(const std::vector<float>& input, double strength,
|
||||
std::vector<float>& output, std::vector<BoostExtremum>& extremes)
|
||||
bool ApplyWeakPeakStage(const std::vector<float>& input, double strength,
|
||||
std::vector<float>& output, std::vector<PeakExtremum>& extremes)
|
||||
{
|
||||
const std::size_t count = input.size();
|
||||
output.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
@@ -1127,7 +1127,7 @@ namespace jdi
|
||||
else if (step != direction)
|
||||
{
|
||||
// 方向在此翻转:上一个采样点就是极值(原方向向上则为波峰)。
|
||||
BoostExtremum extreme;
|
||||
PeakExtremum extreme;
|
||||
extreme.index = previousIndex;
|
||||
extreme.value = previousValue;
|
||||
extreme.type = direction;
|
||||
@@ -1153,9 +1153,9 @@ namespace jdi
|
||||
shift.assign(extremeCount, 0.0);
|
||||
for (std::size_t k = 2; k + 2 < extremeCount; ++k)
|
||||
{
|
||||
const BoostExtremum& current = extremes[k];
|
||||
const BoostExtremum& left = extremes[k - 2];
|
||||
const BoostExtremum& right = extremes[k + 2];
|
||||
const PeakExtremum& current = extremes[k];
|
||||
const PeakExtremum& left = extremes[k - 2];
|
||||
const PeakExtremum& right = extremes[k + 2];
|
||||
if (left.type != current.type || right.type != current.type)
|
||||
{
|
||||
continue;
|
||||
@@ -1168,24 +1168,32 @@ namespace jdi
|
||||
const double mean = (left.value + right.value) * 0.5;
|
||||
const double lower = std::min(left.value, right.value);
|
||||
const double upper = std::max(left.value, right.value);
|
||||
// 目标值分段插值(`strength` = 等级 0–2):
|
||||
// 0 ≤ s ≤ 1:当前值 ↔ 较温和的一侧(波峰取较低的波峰 lower、波谷取较高的波谷 upper);
|
||||
// 1 < s ≤ 2:该侧 ↔ 另一侧(与原先 0–100% 的语义一致)。
|
||||
// 两段在 s = 1 处连续,因此 s = 0 完全不动、s = 2 完全拉平。
|
||||
double destination = 0.0;
|
||||
if (current.type > 0)
|
||||
{
|
||||
// 波峰:低于左右两个波峰的均值 → 判定为弱波,上拉到等级对应的目标值。
|
||||
// 波峰:低于左右两个波峰的均值 → 判定为弱峰,上拉到等级对应的目标值。
|
||||
if (!(current.value < mean))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
destination = lower + strength * (upper - lower);
|
||||
destination = (strength <= 1.0)
|
||||
? (current.value + strength * (lower - current.value))
|
||||
: (lower + (strength - 1.0) * (upper - lower));
|
||||
}
|
||||
else
|
||||
{
|
||||
// 波谷:高于左右两个波谷的均值 → 判定为弱波,下拉到等级对应的目标值。
|
||||
// 波谷:高于左右两个波谷的均值 → 判定为弱峰,下拉到等级对应的目标值。
|
||||
if (!(current.value > mean))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
destination = upper - strength * (upper - lower);
|
||||
destination = (strength <= 1.0)
|
||||
? (current.value + strength * (upper - current.value))
|
||||
: (upper - (strength - 1.0) * (upper - lower));
|
||||
}
|
||||
shift[k] = destination - current.value;
|
||||
}
|
||||
@@ -1235,7 +1243,7 @@ namespace jdi
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void ComputeWeakWaveBoost(const std::vector<double>& values, double level, int iterations,
|
||||
void ComputeWeakPeakBoost(const std::vector<double>& values, double level, int iterations,
|
||||
std::vector<float>& out)
|
||||
{
|
||||
const std::size_t count = values.size();
|
||||
@@ -1247,10 +1255,10 @@ namespace jdi
|
||||
|
||||
// 第 1 阶:从原始采样值出发(非有限值保持 NaN)。
|
||||
// 工作缓冲用线程本地变量复用容量:单条曲线动辄上千万采样,若每次调用都重新分配
|
||||
// 并零初始化,单条「弱波增强」的耗时会被明显放大(内存分配 + page fault)。
|
||||
// 并零初始化,单条「弱峰增强」的耗时会被明显放大(内存分配 + page fault)。
|
||||
thread_local std::vector<float> currentBuffer;
|
||||
thread_local std::vector<float> nextBuffer;
|
||||
thread_local std::vector<BoostExtremum> extremesBuffer;
|
||||
thread_local std::vector<PeakExtremum> extremesBuffer;
|
||||
std::vector<float>& current = currentBuffer;
|
||||
current.assign(count, std::numeric_limits<float>::quiet_NaN());
|
||||
for (std::size_t index = 0; index < count; ++index)
|
||||
@@ -1264,13 +1272,14 @@ namespace jdi
|
||||
|
||||
// 「迭代次数 N」与「均值滤波」的迭代次数语义一致:把同一处理**重复应用 N 次**,
|
||||
// 每一阶以上一阶的结果为输入。一阶时改定量已严格等于「等级」对应的目标值;
|
||||
// 多次迭代时相邻的弱波会互相抬高彼此的基准,从而把较宽的弱波区逐步恢复。
|
||||
// 多次迭代时相邻的弱峰会互相抬高彼此的基准,从而把较宽的弱峰区逐步恢复。
|
||||
const int stages = std::max(1, std::min(iterations, 1000));
|
||||
const double strength = std::max(0.0, std::min(1.0, level));
|
||||
// 增强等级 0–2(对应面板上的 0%–200%):0 完全不动,1 拉到较温和的一侧,2 完全拉平。
|
||||
const double strength = std::max(0.0, std::min(2.0, level));
|
||||
std::vector<float>& next = nextBuffer;
|
||||
for (int stage = 0; stage < stages; ++stage)
|
||||
{
|
||||
if (!ApplyWeakBoostStage(current, strength, next, extremesBuffer))
|
||||
if (!ApplyWeakPeakStage(current, strength, next, extremesBuffer))
|
||||
{
|
||||
// 这一阶没有任何采样点需要调整:后续迭代次数也不会再有变化,提前结束。
|
||||
break;
|
||||
@@ -1350,9 +1359,9 @@ namespace jdi
|
||||
edgeCenterItems.end());
|
||||
}
|
||||
|
||||
const Lane::WeakBoostItem* Lane::FindWeakBoostItem(const SignalRef& ref) const
|
||||
const Lane::WeakPeakItem* Lane::FindWeakPeakItem(const SignalRef& ref) const
|
||||
{
|
||||
for (const WeakBoostItem& item : weakBoostItems)
|
||||
for (const WeakPeakItem& item : weakPeakItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
@@ -1362,27 +1371,27 @@ namespace jdi
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Lane::WeakBoostItem& Lane::EnsureWeakBoostItem(const SignalRef& ref)
|
||||
Lane::WeakPeakItem& Lane::EnsureWeakPeakItem(const SignalRef& ref)
|
||||
{
|
||||
for (WeakBoostItem& item : weakBoostItems)
|
||||
for (WeakPeakItem& item : weakPeakItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
return item;
|
||||
}
|
||||
}
|
||||
WeakBoostItem item;
|
||||
WeakPeakItem item;
|
||||
item.ref = ref;
|
||||
weakBoostItems.push_back(item);
|
||||
return weakBoostItems.back();
|
||||
weakPeakItems.push_back(item);
|
||||
return weakPeakItems.back();
|
||||
}
|
||||
|
||||
void Lane::RemoveWeakBoostItem(const SignalRef& ref)
|
||||
void Lane::RemoveWeakPeakItem(const SignalRef& ref)
|
||||
{
|
||||
weakBoostItems.erase(
|
||||
std::remove_if(weakBoostItems.begin(), weakBoostItems.end(),
|
||||
[&ref](const WeakBoostItem& item) { return item.ref == ref; }),
|
||||
weakBoostItems.end());
|
||||
weakPeakItems.erase(
|
||||
std::remove_if(weakPeakItems.begin(), weakPeakItems.end(),
|
||||
[&ref](const WeakPeakItem& item) { return item.ref == ref; }),
|
||||
weakPeakItems.end());
|
||||
}
|
||||
|
||||
int FindNearestSample(const DataFile& file, double time)
|
||||
@@ -1629,7 +1638,7 @@ namespace jdi
|
||||
};
|
||||
purgeRefs(lane.meanFilterItems);
|
||||
purgeRefs(lane.edgeCenterItems);
|
||||
purgeRefs(lane.weakBoostItems);
|
||||
purgeRefs(lane.weakPeakItems);
|
||||
}
|
||||
// 被移除信号若独占了某个网格,该网格随之消失(与取消勾选的行为一致)。
|
||||
m_lanes.erase(std::remove_if(m_lanes.begin(), m_lanes.end(),
|
||||
@@ -1921,20 +1930,20 @@ namespace jdi
|
||||
lane.edgeCenterPanelOpen = source.edgeCenterPanelOpen;
|
||||
lane.edgeCenterPanelX = source.edgeCenterPanelX;
|
||||
lane.edgeCenterPanelY = source.edgeCenterPanelY;
|
||||
lane.weakBoost = source.weakBoost;
|
||||
lane.weakBoostLevel = source.weakBoostLevel;
|
||||
lane.weakBoostIterations = source.weakBoostIterations;
|
||||
lane.weakBoostPanelOpen = source.weakBoostPanelOpen;
|
||||
lane.weakBoostPanelX = source.weakBoostPanelX;
|
||||
lane.weakBoostPanelY = source.weakBoostPanelY;
|
||||
lane.weakPeak = source.weakPeak;
|
||||
lane.weakPeakLevel = source.weakPeakLevel;
|
||||
lane.weakPeakIterations = source.weakPeakIterations;
|
||||
lane.weakPeakPanelOpen = source.weakPeakPanelOpen;
|
||||
lane.weakPeakPanelX = source.weakPeakPanelX;
|
||||
lane.weakPeakPanelY = source.weakPeakPanelY;
|
||||
lane.meanFilter = source.meanFilter;
|
||||
lane.meanFilterPanelOpen = source.meanFilterPanelOpen;
|
||||
lane.meanFilterPanelX = source.meanFilterPanelX;
|
||||
lane.meanFilterPanelY = source.meanFilterPanelY;
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱波增强条目。
|
||||
// 拆分后每个子网格只保留自己那个信号的均值滤波 / 中心轴 / 弱峰增强条目。
|
||||
lane.meanFilterItems.clear();
|
||||
lane.edgeCenterItems.clear();
|
||||
lane.weakBoostItems.clear();
|
||||
lane.weakPeakItems.clear();
|
||||
for (const Lane::MeanFilterItem& item : source.meanFilterItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
@@ -1949,11 +1958,11 @@ namespace jdi
|
||||
lane.edgeCenterItems.push_back(item);
|
||||
}
|
||||
}
|
||||
for (const Lane::WeakBoostItem& item : source.weakBoostItems)
|
||||
for (const Lane::WeakPeakItem& item : source.weakPeakItems)
|
||||
{
|
||||
if (item.ref == ref)
|
||||
{
|
||||
lane.weakBoostItems.push_back(item);
|
||||
lane.weakPeakItems.push_back(item);
|
||||
}
|
||||
}
|
||||
lane.signals.push_back(ref);
|
||||
@@ -2010,7 +2019,7 @@ namespace jdi
|
||||
++item.ref.signalIndex;
|
||||
}
|
||||
}
|
||||
for (Lane::WeakBoostItem& item : lane.weakBoostItems)
|
||||
for (Lane::WeakPeakItem& item : lane.weakPeakItems)
|
||||
{
|
||||
if (item.ref.fileIndex == fileIndex && item.ref.signalIndex >= insertPos)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user