宽缓冲改为滚动式:消除平移过程中的周期性重建尖峰

视界每帧移动 d 像素时,把缓冲内容整体自贴图平移 round(d) 像素并只补画新暴露的 |round(d)| 像素宽细条(细条渲染复用整条宽窗口的映射,故网格/波形 x 坐标与刻度密度均正确)。用锚点把内容相对时间轴的亚像素偏移限制在半个像素内;缓冲只覆盖绘图区,时间轴由前台每帧绘制。实测 VERY_BIG_DATA 全信号、视界 [40.1756,40.1885]s、5 网格:持续平移拖动约 6ms/帧、长距最大 10.5ms(原为每屏一次 33ms 尖峰);弱波增强滑块 6ms、样式 8ms;静止帧与 HEAD 逐像素 0 差异。
This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-11 23:08:46 +08:00
1 parent b04167a9d6
commit e988da7963
3 files changed
+160 -17

No files matched your search

+127 -17
View File
@@ -5577,19 +5577,32 @@ namespace jdi
}
if (wideUsable)
{
bool blitted = BlitWideBuffer(g);
if (!blitted && RebuildWideBuffer())
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
// 否则(首帧 / 跨度或内容变化)整幅重建一次基底。
bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false;
if (!ready)
{
blitted = BlitWideBuffer(g);
ready = RebuildWideBuffer();
}
if (blitted)
if (ready)
{
// 宽缓冲只含基底:派生预览曲线作为叠加层重画(很轻)。
// 绘图区底色(覆盖时间轴与侧边留白区域)。
UiFillRect(g, m_area, m_theme->plotBg);
// 贴基底(绘图区:网格 + 波形)。
BlitWideBuffer(g);
// 时间轴:宽缓冲不含它,前台按当前视图绘制。
const double viewMin = Tab().ViewMin();
const double viewMax = Tab().ViewMax();
const double spanNow = viewMax - viewMin;
const double timeStep = NiceStep((spanNow > 0.0) ? spanNow : 1.0, kTimeTickTarget);
std::vector<double> timeTicks;
BuildTicks(viewMin, viewMax, timeStep, timeTicks);
PaintTimeAxis(g, timeTicks, timeStep);
// 派生预览曲线作为叠加层重画(很轻);随后装饰层与其它叠加层。
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;
@@ -6817,7 +6830,8 @@ namespace jdi
const int width = static_cast<int>(std::lround(plotWidth));
const int margin = std::max(1, width / 2); // 左右各半屏 → 可平移 ±半屏
const int bitmapWidth = width + 2 * margin;
const int bitmapHeight = static_cast<int>(std::ceil(areaHeight));
const float plotHeight = m_plotRect.Height;
const int bitmapHeight = static_cast<int>(std::ceil(plotHeight));
if (!EnsureWideBuffer(bitmapWidth, bitmapHeight))
{
return false;
@@ -6827,18 +6841,15 @@ namespace jdi
const double wideMin = Tab().ViewMin() - static_cast<double>(margin) / scale; // 位图 x=0 对应的时间
// 保存当前布局 / 视图 / 网格,临时切换到"宽窗口"映射后离屏渲染,再恢复。
// 宽缓冲**只覆盖绘图区**(不含底部时间轴):时间轴由前台每帧绘制。
const gd::RectF savedArea = m_area;
const gd::RectF savedPlot = m_plotRect;
const gd::RectF savedAxis = m_timeAxisRect;
const double savedViewMin = Tab().ViewMin();
const double savedViewMax = Tab().ViewMax();
const std::vector<LaneVisual> savedLanes = m_lanes;
m_area = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), areaHeight);
m_plotRect = gd::RectF(0.0f, savedPlot.Y - savedArea.Y, static_cast<float>(bitmapWidth),
savedPlot.Height);
m_timeAxisRect = gd::RectF(0.0f, savedAxis.Y - savedArea.Y, static_cast<float>(bitmapWidth),
savedAxis.Height);
m_area = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), plotHeight);
m_plotRect = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), plotHeight);
Tab().ViewMin() = wideMin;
Tab().ViewMax() = wideMin + static_cast<double>(bitmapWidth) / scale;
@@ -6869,19 +6880,18 @@ namespace jdi
{
PaintLane(g, static_cast<int>(i), m_lanes[i], timeTicks);
}
PaintTimeAxis(g, timeTicks, timeStep);
}
m_renderWide = false;
// 恢复布局 / 视图 / 网格。
m_area = savedArea;
m_plotRect = savedPlot;
m_timeAxisRect = savedAxis;
Tab().ViewMin() = savedViewMin;
Tab().ViewMax() = savedViewMax;
BuildLaneVisuals();
m_wideMin = wideMin;
m_wideAnchorMin = wideMin + static_cast<double>(margin) / scale; // 视界左端(列 margin)对应的时间
m_wideSpan = span;
m_wideKey = SceneContentHash(true, false);
m_wideValid = true;
@@ -6909,14 +6919,111 @@ namespace jdi
return false;
}
const int destX = static_cast<int>(std::lround(m_plotRect.X));
const int destY = static_cast<int>(std::lround(m_area.Y));
const int height = static_cast<int>(std::ceil(m_area.Height));
const int destY = static_cast<int>(std::lround(m_plotRect.Y));
const int height = static_cast<int>(std::lround(m_plotRect.Height));
BitBlt(target, destX, destY, width, height, m_wideDc, static_cast<int>(std::lround(subX)), 0,
SRCCOPY);
g.ReleaseHDC(target);
return true;
}
bool PlotView::RollWideBuffer()
{
if (!WideBufferMatches())
{
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;
if (m_wideW != bitmapWidth)
{
return false;
}
const double scale = static_cast<double>(width) / m_wideSpan;
// 视界左端相对锚点的位移(像素)。内容需整体平移 round(d) 像素,只补画新暴露的细条。
const double d = (Tab().ViewMin() - m_wideAnchorMin) * scale;
const int shift = static_cast<int>(std::lround(d));
if (shift != 0)
{
if (shift > 0)
{
// 内容整体左移 shift:new[x] = old[x + shift],右侧暴露 shift 列。
BitBlt(m_wideDc, 0, 0, bitmapWidth - shift, m_wideH, m_wideDc, shift, 0, SRCCOPY);
}
else
{
const int amount = -shift;
BitBlt(m_wideDc, amount, 0, bitmapWidth - amount, m_wideH, m_wideDc, 0, 0, SRCCOPY);
}
RenderWideStrip(shift);
m_wideAnchorMin += static_cast<double>(shift) / scale;
m_wideMin = m_wideAnchorMin - static_cast<double>(margin) / scale;
}
return true;
}
void PlotView::RenderWideStrip(int shiftPixels)
{
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 double scale = static_cast<double>(width) / m_wideSpan;
const int stripWidth = std::min(std::abs(shiftPixels), bitmapWidth);
if (stripWidth <= 0)
{
return;
}
const int stripX = (shiftPixels > 0) ? (bitmapWidth - stripWidth) : 0;
// 平移后的缓冲区左端时间。
const double newWideMin = m_wideMin + static_cast<double>(shiftPixels) / scale;
const gd::RectF savedArea = m_area;
const gd::RectF savedPlot = m_plotRect;
const double savedViewMin = Tab().ViewMin();
const double savedViewMax = Tab().ViewMax();
const std::vector<LaneVisual> savedLanes = m_lanes;
// 关键:x 坐标仍用**整条宽窗口**的映射(m_plotRect 为完整宽图),因此网格/波纹位置正确;
// 仅把 m_area 限为细条,使 GDI/GDI+ 的裁剪只落在细条上。
m_area = gd::RectF(static_cast<float>(stripX), 0.0f, static_cast<float>(stripWidth), plotHeight);
m_plotRect = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), plotHeight);
Tab().ViewMin() = newWideMin;
Tab().ViewMax() = newWideMin + static_cast<double>(bitmapWidth) / scale;
m_renderWide = true;
BuildLaneVisuals();
for (std::size_t i = 0; i < m_lanes.size() && i < savedLanes.size(); ++i)
{
m_lanes[i].yMin = savedLanes[i].yMin;
m_lanes[i].yMax = savedLanes[i].yMax;
}
{
gd::Graphics g(m_wideDc);
g.SetSmoothingMode(gd::SmoothingModeAntiAlias);
g.SetPixelOffsetMode(gd::PixelOffsetModeHalf);
g.SetTextRenderingHint(gd::TextRenderingHintClearTypeGridFit);
UiFillRect(g, m_area, m_theme->plotBg);
UiFillRect(g, m_area, m_theme->cardBg);
const double sp = Tab().ViewMax() - Tab().ViewMin();
const double timeStep = NiceStep((sp > 0.0) ? sp : 1.0, kTimeTickTarget);
std::vector<double> timeTicks;
BuildTicks(Tab().ViewMin(), Tab().ViewMax(), timeStep, timeTicks);
for (std::size_t i = 0; i < m_lanes.size(); ++i)
{
PaintLane(g, static_cast<int>(i), m_lanes[i], timeTicks);
}
}
m_renderWide = false;
m_area = savedArea;
m_plotRect = savedPlot;
Tab().ViewMin() = savedViewMin;
Tab().ViewMax() = savedViewMax;
BuildLaneVisuals();
}
void PlotView::PaintRubberBand(gd::Graphics& g)
{
if (!m_dragMoved || m_rubberRect.Width <= 0.0f || m_rubberRect.Height <= 0.0f)
@@ -7447,6 +7554,8 @@ namespace jdi
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));
const int saveState = g.Save();
g.SetClip(lane.rect, gd::CombineModeIntersect);
for (const SignalRef& ref : tabLane.signals)
{
const ResolvedSignal resolved = ResolveSignal(ref);
@@ -7479,6 +7588,7 @@ namespace jdi
}
}
}
g.Restore(saveState);
}
void PlotView::PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,