把宽缓冲区推广为基底复用:拖弱波增强参数滑块降为 O(1)(55ms 到 5ms)

抽出 PaintLaneDerivedOverlays;宽缓冲只存基底(网格/波形/时间轴,跳过派生曲线与图例/Y轴刻度);基底校验键改为 SceneContentHash(true,false)(含外观样式、排除派生参数),因此拖弱波增强等级/迭代滑块不会让基底失效,可复用基底像素 + 只重画派生曲线叠加层。判定为结构未变且外观样式未变(仅视图或派生参数变化)时启用;外观样式变化仍走样式补丁。

实测 VERY_BIG_DATA 全信号、视界 [40.1756,40.1885]s、5 网格:弱波增强滑块 55ms 到 5.2ms,平移 1.3ms、透明度/粗细/颜色 8ms 不变;静止/结算帧与 HEAD 逐像素 0 差异(LockBits)。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-11 22:55:45 +08:00
1 parent fa0d62b016
commit b04167a9d6
3 files changed
+126 -71

No files matched your search

+19
View File
@@ -1076,6 +1076,18 @@ namespace jdi
void PaintLane(gd::Graphics& g, int laneIndex, const LaneVisual& lane,
const std::vector<double>& timeTicks);
/**
* @brief 绘制某网格的全部**派生预览曲线**(均值滤波 / 中心轴 / 弱波增强),叠加在原信号之上。
*
* 从 `PaintLane` 抽出,供「宽缓冲区」前台路径复用:宽缓冲只存"基底"(网格/波形),
* 派生曲线每帧作为叠加层单独绘制,因此拖派生参数滑块时无需重画波形。
*
* @param [IN,OUT] g 绘图上下文。
* @param [IN] laneIndex 网格下标。
* @param [IN] lane 网格绘制信息。
*/
void PaintLaneDerivedOverlays(gd::Graphics& g, int laneIndex, const LaneVisual& lane) const;
/**
* @brief 绘制底部时间轴。
* @param [IN,OUT] g 绘图上下文。
@@ -1933,6 +1945,13 @@ namespace jdi
float opacity = 1.0f; /**< 不透明度。 */
float lineWidth = 1.0f; /**< 线宽。 */
int mode = 0; /**< 绘制模式。 */
/** @brief 相等比较(用于判断"逐信号外观样式是否变化")。 */
bool operator==(const SceneStyleEntry& other) const
{
return color == other.color && opacity == other.opacity &&
lineWidth == other.lineWidth && mode == other.mode;
}
};
/**
+87 -71
View File
@@ -5562,23 +5562,34 @@ namespace jdi
}
};
// 视界平移(左键/中键拖动):画面内容不变、只是横向平移。直接复用「宽缓冲区」
// 已渲染的像素,截取子矩形贴图 —— 前台 O(1)(一次 BitBlt),不重画波形,
// 因此无论放大到多深的区间,平移都与全局显示时一样流畅。
if (m_fastFrame && m_dragMode == PlotDragMode::PAN && m_decorValid && HasDrawableContent())
// 视界平移 / 派生预览曲线参数变化:画面**基底**(网格/波形)不变,只是横向平移或
// 叠加的派生曲线变化。直接复用「宽缓冲区」已渲染的**基底**像素,只把派生曲线作为
// 叠加层重画 —— 前台 O(1),不重画波形,因此无论放大到多深都保持跟手。
// 判定:网格结构未变(几何指纹一致)且**逐信号外观样式未变** → 属视图/派生变化,可用宽基底;
// 外观样式(颜色/粗细/透明度/模式)变化 → 交给下方"样式补丁"分支局部重画。
bool wideUsable = false;
if (m_fastFrame && m_decorValid && HasDrawableContent() && m_sceneValid &&
m_sceneGeometryHash == SceneGeometryHash())
{
std::vector<std::vector<SceneStyleEntry>> currentStyles;
CollectSceneStyles(currentStyles);
wideUsable = (currentStyles == m_sceneStyles);
}
if (wideUsable)
{
bool blitted = BlitWideBuffer(g);
if (!blitted)
if (!blitted && RebuildWideBuffer())
{
// 视界移出了宽缓冲范围,或内容/跨度变化 → 以当前视界为中心重建一次。
if (RebuildWideBuffer())
{
blitted = BlitWideBuffer(g);
}
blitted = BlitWideBuffer(g);
}
if (blitted)
{
// 宽缓冲不含图例与 Y 轴刻度(二者锚定网格左缘、随视界移动),由装饰缓存贴回。
// 宽缓冲只含基底:派生预览曲线作为叠加层重画(很轻)。
for (std::size_t i = 0; i < m_lanes.size(); ++i)
{
PaintLaneDerivedOverlays(g, static_cast<int>(i), m_lanes[i]);
}
// 图例与 Y 轴刻度(锚定网格左缘、随视界移动)由装饰缓存贴回。
BlitDecorCache(g);
paintOverlays();
return;
@@ -6786,8 +6797,10 @@ namespace jdi
{
return false;
}
// 内容(外观样式 / 派生曲线参数 / 网格结构 / 视界几何)变化 → 需重建。
return (m_wideKey == SceneContentHash(true));
// 内容(外观样式 / 网格结构 / 视界几何)变化 → 需重建。
// ⚠️ **不含派生曲线参数**:宽缓冲只存基底(网格 / 波形),派生曲线是每帧叠加的,
// 因此拖弱波增强 / 均值滤波参数滑块不会让基底失效,可直接复用基底像素。
return (m_wideKey == SceneContentHash(true, false));
}
bool PlotView::RebuildWideBuffer()
@@ -6870,7 +6883,7 @@ namespace jdi
m_wideMin = wideMin;
m_wideSpan = span;
m_wideKey = SceneContentHash(true);
m_wideKey = SceneContentHash(true, false);
m_wideValid = true;
return true;
}
@@ -7152,15 +7165,8 @@ namespace jdi
// 只有拖拽中的信号光晕可以进入静态场景缓存(悬停光晕见 PaintHoverHighlight)。
const SignalRef haloRef = SceneHaloRef();
// 「边沿计数(中心轴)」开启时,为**被勾选**的信号各叠加一条均值滤波曲线
//(参数 = 该信号自己的条目:均值点数 X + 迭代次数 N,与「均值滤波」完全同一套语义)。
const bool centerOn = Tab().Lanes()[static_cast<std::size_t>(laneIndex)].edgeCenter;
// 「均值滤波」开启时,为**被勾选**的信号各叠加一条多次迭代滑动平均曲线
//(均值点数 X、迭代次数 N 由该信号自己的条目给出)。
const bool meanFilterOn = Tab().Lanes()[static_cast<std::size_t>(laneIndex)].meanFilter;
// 「弱波增强」开启时同样只为**被勾选**的信号叠加曲线
//(增强等级取整个网格共用的 `weakBoostLevel`)。
const bool weakBoostOn = Tab().Lanes()[static_cast<std::size_t>(laneIndex)].weakBoost;
// 派生预览曲线(中心轴 / 均值滤波 / 弱波增强)在下方"第二遍"统一叠加绘制,
// 见 `PaintLaneDerivedOverlays`(抽出以便宽缓冲区前台路径复用)。
for (const SignalRef& ref : laneSignals)
{
@@ -7286,55 +7292,12 @@ namespace jdi
}
// 第二遍:把所有**派生预览曲线**统一绘制在**本网格全部原信号之上**。
// 若在同一个信号循环里就地叠加,后绘制的原信号会把先前信号的预览曲线盖住
//(用户明确要求"预览信号处于原信号上层不要被覆盖")。
for (const SignalRef& ref : laneSignals)
// 渲染「宽缓冲区」时跳过:宽缓冲只存"基底"(网格/波形,不含派生曲线与装饰),
// 派生曲线改由前台叠加层绘制(见 `PaintLaneDerivedOverlays`),
// 这样拖派生参数滑块时基底不变、可复用宽缓冲像素。
if (!m_renderWide)
{
const ResolvedSignal resolved = ResolveSignal(ref);
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
{
continue;
}
// 中心轴均值滤波曲线(「实验性功能 → 边沿计数(中心轴)」):与「均值滤波」同源,
// 只有被勾选的信号才绘制。
if (centerOn)
{
const Lane::EdgeCenterItem* centerItem =
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].FindEdgeCenterItem(ref);
if (centerItem != nullptr)
{
PaintMeanFilterCurve(g, resolved, lane, centerItem->points, centerItem->iterations);
}
}
// 均值滤波预览曲线:只有被勾选的信号才绘制(参数取自它自己的条目)。
if (meanFilterOn)
{
const Lane::MeanFilterItem* item =
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].FindMeanFilterItem(ref);
if (item != nullptr)
{
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations);
}
}
// 弱波增强预览曲线:只有被勾选的信号才绘制(增强等级取自整个网格)。
if (weakBoostOn)
{
const Lane::WeakBoostItem* item =
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].FindWeakBoostItem(ref);
if (item != nullptr)
{
const int levelPercent = static_cast<int>(std::lround(
std::max(0.0, std::min(1.0,
Tab().Lanes()[static_cast<std::size_t>(laneIndex)]
.weakBoostLevel)) *
100.0));
const int iterations = std::max(
kWeakBoostIterationsMin,
std::min(kWeakBoostIterationsMax,
Tab().Lanes()[static_cast<std::size_t>(laneIndex)].weakBoostIterations));
PaintWeakBoostCurve(g, resolved, lane, levelPercent, iterations);
}
}
PaintLaneDerivedOverlays(g, laneIndex, lane);
}
// g.Restore 也会还原绘制状态,此处直接恢复图形状态即可。
@@ -7465,6 +7428,59 @@ namespace jdi
return SignalRef();
}
void PlotView::PaintLaneDerivedOverlays(gd::Graphics& g, int laneIndex, const LaneVisual& lane) const
{
if (m_tab == nullptr || laneIndex < 0 ||
laneIndex >= static_cast<int>(m_tab->Lanes().size()))
{
return;
}
const Lane& tabLane = Tab().Lanes()[static_cast<std::size_t>(laneIndex)];
const bool centerOn = tabLane.edgeCenter;
const bool meanFilterOn = tabLane.meanFilter;
const bool weakBoostOn = tabLane.weakBoost;
if (!centerOn && !meanFilterOn && !weakBoostOn)
{
return;
}
const int levelPercent = static_cast<int>(
std::lround(std::max(0.0, std::min(1.0, tabLane.weakBoostLevel)) * 100.0));
const int boostIterations =
std::max(kWeakBoostIterationsMin, std::min(kWeakBoostIterationsMax, tabLane.weakBoostIterations));
for (const SignalRef& ref : tabLane.signals)
{
const ResolvedSignal resolved = ResolveSignal(ref);
if (resolved.data == nullptr || resolved.style == nullptr || !resolved.visible)
{
continue;
}
if (centerOn)
{
const Lane::EdgeCenterItem* centerItem = tabLane.FindEdgeCenterItem(ref);
if (centerItem != nullptr)
{
PaintMeanFilterCurve(g, resolved, lane, centerItem->points, centerItem->iterations);
}
}
if (meanFilterOn)
{
const Lane::MeanFilterItem* item = tabLane.FindMeanFilterItem(ref);
if (item != nullptr)
{
PaintMeanFilterCurve(g, resolved, lane, item->points, item->iterations);
}
}
if (weakBoostOn)
{
const Lane::WeakBoostItem* item = tabLane.FindWeakBoostItem(ref);
if (item != nullptr)
{
PaintWeakBoostCurve(g, resolved, lane, levelPercent, boostIterations);
}
}
}
}
void PlotView::PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,
float visibleBottom)
{