消除开始拖拽的首帧尖峰:用静态场景缓存初始化宽基底 + 启用预览功能时预建基底

平移首帧不再从零重画 2×视界:平移只是整体横移,上一帧场景缓存内容依然正确,直接贴进宽基底中心再滚动到当前视界(仅当派生预览曲线参数未变时适用)。启用弱波增强/均值滤波/中心轴时预建基底,使首次拖动其参数滑块无需重建。实测 VERY_BIG_DATA 全信号、视界 [40.1756,40.1885]s、5 网格:中键平移拖动最大 8.5ms、弱波增强滑块最大 10.8ms(原分别 35/37.6ms);全部鼠标交互全程 >=60fps、无尖峰;静止帧与 HEAD 逐像素 0 差异。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-11 23:21:37 +08:00
1 parent e988da7963
commit 8b1de5d900
3 files changed
+101 -1

No files matched your search

+10
View File
@@ -2232,6 +2232,16 @@ namespace jdi
bool RollWideBuffer();
/** @brief 渲染宽缓冲区中因滚动而新暴露的一条细条(宽 |shiftPixels|)。 */
void RenderWideStrip(int shiftPixels);
/**
* @brief 用「上一帧的静态场景缓存」初始化宽缓冲区(平移首帧用,避免从零重画)。
*
* 平移时画面只是整体横向移动,场景缓存里的内容(含派生曲线/图例)**依然正确**;
* 因此把场景缓存的绘图区直接贴进宽缓冲中心即可,随后滚动到当前视界。
* 仅当"视界相对场景缓存发生了平移"(跨度一致)时可用。
*
* @return 初始化成功返回 true。
*/
bool InitWideBufferFromScene();
/** @brief 宽缓冲区内容是否仍与当前视图/内容一致。 */
bool WideBufferMatches() const;
/** @brief 使宽缓冲区失效。 */
+74 -1
View File
@@ -1845,6 +1845,8 @@ namespace jdi
{
lane.edgeCenterPanelOpen = false;
}
// 预建「宽缓冲基底」,避免首次拖动参数滑块时的一次性重建尖峰。
RebuildWideBuffer();
return true;
}
@@ -1988,6 +1990,8 @@ namespace jdi
{
lane.weakBoostPanelOpen = false;
}
// 预建「宽缓冲基底」(不含派生曲线),使用户首次拖动该滑块时无需从零重建(避免一次性尖峰)。
RebuildWideBuffer();
return true;
}
@@ -2069,6 +2073,8 @@ namespace jdi
{
lane.meanFilterPanelOpen = false;
}
// 预建「宽缓冲基底」,避免首次拖动参数滑块时的一次性重建尖峰。
RebuildWideBuffer();
return true;
}
@@ -5578,8 +5584,13 @@ namespace jdi
if (wideUsable)
{
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
// 否则(首帧 / 跨度或内容变化)整幅重建一次基底。
// 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画
// 2×视界造成的一次尖峰),再不行才整幅重建。
bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false;
if (!ready && InitWideBufferFromScene())
{
ready = RollWideBuffer();
}
if (!ready)
{
ready = RebuildWideBuffer();
@@ -6963,6 +6974,68 @@ namespace jdi
return true;
}
bool PlotView::InitWideBufferFromScene()
{
if (!m_sceneValid || m_sceneDc == nullptr)
{
return false;
}
const double span = Tab().ViewMax() - Tab().ViewMin();
const double sceneSpan = m_sceneKey.viewMax - m_sceneKey.viewMin;
if (!(span > 0.0) || std::fabs(sceneSpan - span) > 1.0e-9)
{
return false;
}
// 仅当"派生预览曲线参数未变"时可用:此时场景缓存内容(含派生曲线/图例)依然正确。
// (拖弱波增强/均值滤波参数滑块时派生曲线会变,必须从零重建基底。)
std::vector<SceneDerivedEntry> currentDerived;
CollectSceneDerived(currentDerived);
if (currentDerived.size() != m_sceneDerived.size())
{
return false;
}
for (std::size_t i = 0; i < currentDerived.size(); ++i)
{
const SceneDerivedEntry& a = currentDerived[i];
const SceneDerivedEntry& b = m_sceneDerived[i];
if (a.edgeCenter != b.edgeCenter || a.meanFilter != b.meanFilter ||
a.weakBoost != b.weakBoost || a.weakBoostLevelPercent != b.weakBoostLevelPercent ||
a.weakBoostIterations != b.weakBoostIterations ||
a.edgeCenterItems != b.edgeCenterItems || a.meanFilterItems != b.meanFilterItems ||
a.weakBoostItems != b.weakBoostItems)
{
return false;
}
}
const int width = static_cast<int>(std::lround(m_plotRect.Width));
const int margin = std::max(1, width / 2);
const int bitmapWidth = width + 2 * margin;
const float plotHeight = m_plotRect.Height;
const int bitmapHeight = static_cast<int>(std::ceil(plotHeight));
if (!EnsureWideBuffer(bitmapWidth, bitmapHeight))
{
return false;
}
// 场景缓存的绘图区相对其原点(m_sceneX/m_sceneY)的位置。
const int srcX = static_cast<int>(std::floor(m_plotRect.X)) - m_sceneX;
const int srcY = static_cast<int>(std::floor(m_plotRect.Y)) - m_sceneY;
if (BitBlt(m_wideDc, margin, 0, width, bitmapHeight, m_sceneDc, srcX, srcY, SRCCOPY) == FALSE)
{
m_wideValid = false;
return false;
}
const double scale = static_cast<double>(width) / span;
m_wideMin = m_sceneKey.viewMin - static_cast<double>(margin) / scale;
m_wideAnchorMin = m_sceneKey.viewMin;
m_wideSpan = span;
m_wideKey = SceneContentHash(true, false);
m_wideValid = true;
return true;
}
void PlotView::RenderWideStrip(int shiftPixels)
{
const int width = static_cast<int>(std::lround(m_plotRect.Width));