修复缩放后平移的绘制错误/抖动与前台卡顿;弱波增强改名弱峰增强并支持 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
+649
-739
No files matched your search
+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