非拖拽视界变化(键盘/工具栏)也走宽基底快路径:124.6ms→8.6ms;新增 wideKey 结算帧恢复像素精确;分支顺序固定为静态贴图→宽基底→样式补丁;宽基底强制全质量抗锯齿且竖向刻度改用前台跨度;像素对比验证 7 张静态/结算帧与 HEAD 字节一致、底座管线忠实(meanAbs 0.68/255)

This commit is contained in:
DeepSeek V4.1 Flash committed 2026-10-12 00:04:01 +08:00
1 parent 8b1de5d900
commit 1c9c43233d
3 files changed
+128 -63

No files matched your search

+4
View File
@@ -2258,6 +2258,10 @@ namespace jdi
bool m_wideValid = false; /**< 宽缓冲区内容是否有效。 */
std::uint64_t m_wideKey = 0; /**< 宽缓冲区对应的内容指纹(外观/派生参数等)。 */
bool m_renderWide = false; /**< 正在渲染宽缓冲区(重入 `PaintLane` 时跳过装饰)。 */
/** 上一次走"宽缓冲快路径"时的内容指纹(含视界)。当前帧指纹与之相同 → 视界已停止变化,
* 此时改走一次整幅重绘("结算"),保证最终呈现的是**像素精确**的全质量帧。 */
std::uint64_t m_wideLastKey = 0;
bool m_wideLastValid = false;
/**
* @brief 实验性功能浮动面板的像素图层项。
+95 -62
View File
@@ -5568,58 +5568,6 @@ namespace jdi
}
};
// 视界平移 / 派生预览曲线参数变化:画面**基底**(网格/波形)不变,只是横向平移或
// 叠加的派生曲线变化。直接复用「宽缓冲区」已渲染的**基底**像素,只把派生曲线作为
// 叠加层重画 —— 前台 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)
{
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
// 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画
// 2×视界造成的一次尖峰),再不行才整幅重建。
bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false;
if (!ready && InitWideBufferFromScene())
{
ready = RollWideBuffer();
}
if (!ready)
{
ready = RebuildWideBuffer();
}
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]);
}
BlitDecorCache(g);
paintOverlays();
return;
}
}
// 网格次序拖拽:静态场景缓存仍然有效,但各网格要按位移重新"摆放"。
// 这里逐网格把缓存中的横向条带平移贴回(几十微秒),
// 因此拖动百万级采样的网格时依然流畅,且被拖网格的波形随鼠标自由移动。
@@ -5640,6 +5588,82 @@ namespace jdi
paintOverlays();
return;
}
// 视界平移 / 派生预览曲线参数变化:画面**基底**(网格/波形)不变,只是横向平移或
// 叠加的派生曲线变化。直接复用「宽缓冲区」已渲染的**基底**像素,只把派生曲线作为
// 叠加层重画 —— 前台 O(1),不重画波形,因此无论放大到多深都保持跟手。
// 判定:网格结构未变(几何指纹一致)且**逐信号外观样式未变** → 属视图/派生变化,可用宽基底;
// 外观样式(颜色/粗细/透明度/模式)变化 → 交给下方"样式补丁"分支局部重画。
// ⚠️ 位置必须在"静态场景贴图"**之后**:视界与样式都没变时(典型:鼠标悬停移动)
// 应由静态场景贴图命中,绝不能落到这里 —— 否则每两帧就会触发一次"结算整幅重绘",
// 悬停反而变卡。而它又必须在"样式补丁"**之前**:派生曲线参数变化(拖弱波增强 /
// 均值滤波滑块)同样只需叠加重画派生曲线,走本分支比"逐网格补丁"更快且质量更高。
// ⚠️ **不要求"正在拖拽"**:非拖拽的视界变化(键盘方向键 / 工具栏)同样走此快路径,
// 避免该放大区间"整幅重绘约 124ms"的卡顿。
bool wideUsable = false;
if (m_decorValid && HasDrawableContent() && m_sceneValid &&
m_sceneGeometryHash == SceneGeometryHash())
{
std::vector<std::vector<SceneStyleEntry>> currentStyles;
CollectSceneStyles(currentStyles);
wideUsable = (currentStyles == m_sceneStyles);
}
if (wideUsable)
{
// 内容(含视界)与本帧上一条快路径帧相同 → 视界已停止变化 → 走一次整幅"结算"
// 全质量帧(保证最终呈现**像素精确**;此后命中静态场景缓存,不再回到本分支)。
std::uint64_t wideKey = SceneContentHash(true);
wideKey ^= static_cast<std::uint64_t>(std::llround(Tab().ViewMin() * 1.0e6));
wideKey *= 1099511628211ull;
wideKey ^= static_cast<std::uint64_t>(std::llround(Tab().ViewMax() * 1.0e6));
if (m_wideLastValid && m_wideLastKey == wideKey)
{
m_wideLastValid = false; // 结算:落到下方整幅重绘
}
else
{
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
// 否则优先用"上一帧场景缓存"初始化基底(平移首帧:贴图 + 细条,避免从零重画
// 2×视界造成的一次尖峰),再不行才整幅重建。
bool ready = (m_wideValid && WideBufferMatches()) ? RollWideBuffer() : false;
if (!ready && InitWideBufferFromScene())
{
ready = RollWideBuffer();
}
if (!ready)
{
ready = RebuildWideBuffer();
}
if (ready)
{
m_wideLastKey = wideKey;
m_wideLastValid = true;
// 绘图区底色(覆盖时间轴与侧边留白区域)。
UiFillRect(g, m_area, m_theme->plotBg);
// 贴基底(绘图区:网格 + 波形)。
BlitWideBuffer(g);
// 时间轴:宽缓冲不含它,前台按当前视图绘制。
const double wideViewMin = Tab().ViewMin();
const double wideViewMax = Tab().ViewMax();
const double wideSpanNow = wideViewMax - wideViewMin;
const double wideTimeStep =
NiceStep((wideSpanNow > 0.0) ? wideSpanNow : 1.0, kTimeTickTarget);
std::vector<double> wideTimeTicks;
BuildTicks(wideViewMin, wideViewMax, wideTimeStep, wideTimeTicks);
PaintTimeAxis(g, wideTimeTicks, wideTimeStep);
// 派生预览曲线作为叠加层重画(很轻);随后装饰层与其它叠加层。
for (std::size_t i = 0; i < m_lanes.size(); ++i)
{
PaintLaneDerivedOverlays(g, static_cast<int>(i), m_lanes[i]);
}
BlitDecorCache(g);
paintOverlays();
return;
}
}
}
// 只有"外观样式"变化(典型场景:拖动自定义颜色滑块实时调色):网格结构、视图几何、
// 波形采样都没变,只是颜色/不透明度/线宽/模式不同 —— 此时只重画受影响的网格,
// 再写回缓存。(VERY_BIG_DATA 实测:整幅重绘 30ms+,单网格重画约 4ms。)
@@ -5647,9 +5671,9 @@ namespace jdi
std::vector<int> patchLanes;
if (FindStyleOnlyLanes(patchLanes) && BlitSceneCache(g))
{
// ⚠️ 样式/参数**正在连续变化**(拖透明度/粗细/拾色/实验性功能滑块):这本身就是
// 一种"交互中"。若不切换快速帧,本帧会用**抗锯齿**重画波形包络带 —— 实测
// 单条网格的包络带 AA 填充 ≈29ms(本机负载下 72ms),而关掉 AA 只要 ≈5.6ms。
// ⚠️ 样式**正在连续变化**(拖透明度 / 粗细 / 拾色滑块):这本身就是一种"交互中"。
// 若不切换快速帧,本帧会用**抗锯齿**重画波形包络带 —— 实测单条网格的包络带
// AA 填充 ≈29ms(本机负载下 72ms),而关掉 AA 只要 ≈5.6ms。
// 因此补丁阶段强制快速帧,并在补丁后把场景缓存标记为"已降级",
// 下一帧(参数稳定后)自动补一帧全质量 —— 既保证拖动手感,又不牺牲最终画质。
m_fastFrame = true;
@@ -6865,6 +6889,11 @@ namespace jdi
Tab().ViewMax() = wideMin + static_cast<double>(bitmapWidth) / scale;
m_renderWide = true;
// 宽缓冲必须与"同一视界的整幅渲染"**逐像素同源**,否则平移后网格线数量 / 锯齿会不同:
// * 强制全质量抗锯齿(不受"拖拽快速帧"影响,绝不降级);
// * 网格竖向刻度沿用**前台视界跨度**(而非 2× 的宽窗口跨度),否则竖网格线会减半。
const bool savedFastFrame = m_fastFrame;
m_fastFrame = false;
BuildLaneVisuals();
// 宽缓冲必须沿用**当前视界**的 Y 轴范围,否则幅值映射与前台不一致。
for (std::size_t i = 0; i < m_lanes.size() && i < savedLanes.size(); ++i)
@@ -6881,17 +6910,16 @@ namespace jdi
UiFillRect(g, m_area, m_theme->plotBg);
UiFillRect(g, m_plotRect, m_theme->cardBg);
const double viewMin = Tab().ViewMin();
const double viewMax = Tab().ViewMax();
const double sp = viewMax - viewMin;
const double timeStep = NiceStep((sp > 0.0) ? sp : 1.0, kTimeTickTarget);
const double fgSpan = savedViewMax - savedViewMin;
const double timeStep = NiceStep((fgSpan > 0.0) ? fgSpan : 1.0, kTimeTickTarget);
std::vector<double> timeTicks;
BuildTicks(viewMin, viewMax, timeStep, 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_fastFrame = savedFastFrame;
m_renderWide = false;
// 恢复布局 / 视图 / 网格。
@@ -7066,6 +7094,10 @@ namespace jdi
Tab().ViewMax() = newWideMin + static_cast<double>(bitmapWidth) / scale;
m_renderWide = true;
// 与 `RebuildWideBuffer` 保持完全一致:细条补画也必须**全质量抗锯齿**,且网格竖向刻度
// 沿用**前台视界跨度**,否则滚动后新露出的细条会与既有基底的网格 / 锯齿不一致。
const bool savedFastFrame = m_fastFrame;
m_fastFrame = false;
BuildLaneVisuals();
for (std::size_t i = 0; i < m_lanes.size() && i < savedLanes.size(); ++i)
{
@@ -7079,8 +7111,8 @@ namespace jdi
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);
const double fgSpan = savedViewMax - savedViewMin;
const double timeStep = NiceStep((fgSpan > 0.0) ? fgSpan : 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)
@@ -7088,6 +7120,7 @@ namespace jdi
PaintLane(g, static_cast<int>(i), m_lanes[i], timeTicks);
}
}
m_fastFrame = savedFastFrame;
m_renderWide = false;
m_area = savedArea;