宽缓冲改为滚动式:消除平移过程中的周期性重建尖峰
视界每帧移动 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:
1 parent
b04167a9d6
commit
e988da7963
3 files changed
+160
-17
No files matched your search
@@ -2219,6 +2219,19 @@ namespace jdi
|
|||||||
* @return 命中并贴图返回 true(调用方随后只画叠加层即可)。
|
* @return 命中并贴图返回 true(调用方随后只画叠加层即可)。
|
||||||
*/
|
*/
|
||||||
bool BlitWideBuffer(gd::Graphics& g) const;
|
bool BlitWideBuffer(gd::Graphics& g) const;
|
||||||
|
/**
|
||||||
|
* @brief 让宽缓冲区"滚动"以跟随视界:按整数像素平移已渲染内容,只补画新暴露的细条。
|
||||||
|
*
|
||||||
|
* 视界每帧移动 d 像素时,把缓冲区内容整体挪动 `round(d)` 像素(自贴图),
|
||||||
|
* 再只渲染新暴露出来的 `|round(d)|` 像素宽细条。这样**每帧代价恒为 O(细条)**,
|
||||||
|
* 不会像"移出边界就整幅重建 2×视界"那样出现周期性大尖峰。
|
||||||
|
* 用一个亚像素残差累加器把"内容相对时间轴的偏移"始终限制在半个像素内。
|
||||||
|
*
|
||||||
|
* @return 缓冲区仍可用返回 true。
|
||||||
|
*/
|
||||||
|
bool RollWideBuffer();
|
||||||
|
/** @brief 渲染宽缓冲区中因滚动而新暴露的一条细条(宽 |shiftPixels|)。 */
|
||||||
|
void RenderWideStrip(int shiftPixels);
|
||||||
/** @brief 宽缓冲区内容是否仍与当前视图/内容一致。 */
|
/** @brief 宽缓冲区内容是否仍与当前视图/内容一致。 */
|
||||||
bool WideBufferMatches() const;
|
bool WideBufferMatches() const;
|
||||||
/** @brief 使宽缓冲区失效。 */
|
/** @brief 使宽缓冲区失效。 */
|
||||||
@@ -2230,6 +2243,7 @@ namespace jdi
|
|||||||
int m_wideW = 0; /**< 宽缓冲区位图宽度(像素)。 */
|
int m_wideW = 0; /**< 宽缓冲区位图宽度(像素)。 */
|
||||||
int m_wideH = 0; /**< 宽缓冲区位图高度(像素)。 */
|
int m_wideH = 0; /**< 宽缓冲区位图高度(像素)。 */
|
||||||
double m_wideMin = 0.0; /**< 宽缓冲区最左侧对应的时间。 */
|
double m_wideMin = 0.0; /**< 宽缓冲区最左侧对应的时间。 */
|
||||||
|
double m_wideAnchorMin = 0.0; /**< 视界左端当前在宽缓冲中的锚点时间(滚动基准)。 */
|
||||||
double m_wideSpan = 0.0; /**< 宽缓冲区制作时的视界跨度。 */
|
double m_wideSpan = 0.0; /**< 宽缓冲区制作时的视界跨度。 */
|
||||||
bool m_wideValid = false; /**< 宽缓冲区内容是否有效。 */
|
bool m_wideValid = false; /**< 宽缓冲区内容是否有效。 */
|
||||||
std::uint64_t m_wideKey = 0; /**< 宽缓冲区对应的内容指纹(外观/派生参数等)。 */
|
std::uint64_t m_wideKey = 0; /**< 宽缓冲区对应的内容指纹(外观/派生参数等)。 */
|
||||||
|
|||||||
@@ -5577,19 +5577,32 @@ namespace jdi
|
|||||||
}
|
}
|
||||||
if (wideUsable)
|
if (wideUsable)
|
||||||
{
|
{
|
||||||
bool blitted = BlitWideBuffer(g);
|
// 已有可用基底 → 滚动(只补画新暴露的细条,恒为 O(细条),无周期性大尖峰);
|
||||||
if (!blitted && RebuildWideBuffer())
|
// 否则(首帧 / 跨度或内容变化)整幅重建一次基底。
|
||||||
|
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)
|
for (std::size_t i = 0; i < m_lanes.size(); ++i)
|
||||||
{
|
{
|
||||||
PaintLaneDerivedOverlays(g, static_cast<int>(i), m_lanes[i]);
|
PaintLaneDerivedOverlays(g, static_cast<int>(i), m_lanes[i]);
|
||||||
}
|
}
|
||||||
// 图例与 Y 轴刻度(锚定网格左缘、随视界移动)由装饰缓存贴回。
|
|
||||||
BlitDecorCache(g);
|
BlitDecorCache(g);
|
||||||
paintOverlays();
|
paintOverlays();
|
||||||
return;
|
return;
|
||||||
@@ -6817,7 +6830,8 @@ namespace jdi
|
|||||||
const int width = static_cast<int>(std::lround(plotWidth));
|
const int width = static_cast<int>(std::lround(plotWidth));
|
||||||
const int margin = std::max(1, width / 2); // 左右各半屏 → 可平移 ±半屏
|
const int margin = std::max(1, width / 2); // 左右各半屏 → 可平移 ±半屏
|
||||||
const int bitmapWidth = width + 2 * margin;
|
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))
|
if (!EnsureWideBuffer(bitmapWidth, bitmapHeight))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -6827,18 +6841,15 @@ namespace jdi
|
|||||||
const double wideMin = Tab().ViewMin() - static_cast<double>(margin) / scale; // 位图 x=0 对应的时间
|
const double wideMin = Tab().ViewMin() - static_cast<double>(margin) / scale; // 位图 x=0 对应的时间
|
||||||
|
|
||||||
// 保存当前布局 / 视图 / 网格,临时切换到"宽窗口"映射后离屏渲染,再恢复。
|
// 保存当前布局 / 视图 / 网格,临时切换到"宽窗口"映射后离屏渲染,再恢复。
|
||||||
|
// 宽缓冲**只覆盖绘图区**(不含底部时间轴):时间轴由前台每帧绘制。
|
||||||
const gd::RectF savedArea = m_area;
|
const gd::RectF savedArea = m_area;
|
||||||
const gd::RectF savedPlot = m_plotRect;
|
const gd::RectF savedPlot = m_plotRect;
|
||||||
const gd::RectF savedAxis = m_timeAxisRect;
|
|
||||||
const double savedViewMin = Tab().ViewMin();
|
const double savedViewMin = Tab().ViewMin();
|
||||||
const double savedViewMax = Tab().ViewMax();
|
const double savedViewMax = Tab().ViewMax();
|
||||||
const std::vector<LaneVisual> savedLanes = m_lanes;
|
const std::vector<LaneVisual> savedLanes = m_lanes;
|
||||||
|
|
||||||
m_area = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), areaHeight);
|
m_area = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), plotHeight);
|
||||||
m_plotRect = gd::RectF(0.0f, savedPlot.Y - savedArea.Y, static_cast<float>(bitmapWidth),
|
m_plotRect = gd::RectF(0.0f, 0.0f, static_cast<float>(bitmapWidth), plotHeight);
|
||||||
savedPlot.Height);
|
|
||||||
m_timeAxisRect = gd::RectF(0.0f, savedAxis.Y - savedArea.Y, static_cast<float>(bitmapWidth),
|
|
||||||
savedAxis.Height);
|
|
||||||
Tab().ViewMin() = wideMin;
|
Tab().ViewMin() = wideMin;
|
||||||
Tab().ViewMax() = wideMin + static_cast<double>(bitmapWidth) / scale;
|
Tab().ViewMax() = wideMin + static_cast<double>(bitmapWidth) / scale;
|
||||||
|
|
||||||
@@ -6869,19 +6880,18 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
PaintLane(g, static_cast<int>(i), m_lanes[i], timeTicks);
|
PaintLane(g, static_cast<int>(i), m_lanes[i], timeTicks);
|
||||||
}
|
}
|
||||||
PaintTimeAxis(g, timeTicks, timeStep);
|
|
||||||
}
|
}
|
||||||
m_renderWide = false;
|
m_renderWide = false;
|
||||||
|
|
||||||
// 恢复布局 / 视图 / 网格。
|
// 恢复布局 / 视图 / 网格。
|
||||||
m_area = savedArea;
|
m_area = savedArea;
|
||||||
m_plotRect = savedPlot;
|
m_plotRect = savedPlot;
|
||||||
m_timeAxisRect = savedAxis;
|
|
||||||
Tab().ViewMin() = savedViewMin;
|
Tab().ViewMin() = savedViewMin;
|
||||||
Tab().ViewMax() = savedViewMax;
|
Tab().ViewMax() = savedViewMax;
|
||||||
BuildLaneVisuals();
|
BuildLaneVisuals();
|
||||||
|
|
||||||
m_wideMin = wideMin;
|
m_wideMin = wideMin;
|
||||||
|
m_wideAnchorMin = wideMin + static_cast<double>(margin) / scale; // 视界左端(列 margin)对应的时间
|
||||||
m_wideSpan = span;
|
m_wideSpan = span;
|
||||||
m_wideKey = SceneContentHash(true, false);
|
m_wideKey = SceneContentHash(true, false);
|
||||||
m_wideValid = true;
|
m_wideValid = true;
|
||||||
@@ -6909,14 +6919,111 @@ namespace jdi
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const int destX = static_cast<int>(std::lround(m_plotRect.X));
|
const int destX = static_cast<int>(std::lround(m_plotRect.X));
|
||||||
const int destY = static_cast<int>(std::lround(m_area.Y));
|
const int destY = static_cast<int>(std::lround(m_plotRect.Y));
|
||||||
const int height = static_cast<int>(std::ceil(m_area.Height));
|
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,
|
BitBlt(target, destX, destY, width, height, m_wideDc, static_cast<int>(std::lround(subX)), 0,
|
||||||
SRCCOPY);
|
SRCCOPY);
|
||||||
g.ReleaseHDC(target);
|
g.ReleaseHDC(target);
|
||||||
return true;
|
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)
|
void PlotView::PaintRubberBand(gd::Graphics& g)
|
||||||
{
|
{
|
||||||
if (!m_dragMoved || m_rubberRect.Width <= 0.0f || m_rubberRect.Height <= 0.0f)
|
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));
|
std::lround(std::max(0.0, std::min(1.0, tabLane.weakBoostLevel)) * 100.0));
|
||||||
const int boostIterations =
|
const int boostIterations =
|
||||||
std::max(kWeakBoostIterationsMin, std::min(kWeakBoostIterationsMax, tabLane.weakBoostIterations));
|
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)
|
for (const SignalRef& ref : tabLane.signals)
|
||||||
{
|
{
|
||||||
const ResolvedSignal resolved = ResolveSignal(ref);
|
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,
|
void PlotView::PaintLaneLegend(gd::Graphics& g, int laneIndex, const LaneVisual& lane, float visibleTop,
|
||||||
|
|||||||
@@ -322,6 +322,24 @@ Documents/ 项目文档(含本文件)
|
|||||||
1. **宽缓冲区重建**在视界移出缓冲范围(约每平移 1 屏一次)时于**前台**进行,负载下约 30–50ms 的**一次**尖峰。彻底消除需把"基底渲染"做成**不依赖成员状态的独立渲染器**再搬后台线程(`PaintLane` 会写成员缓存,直接后台化有数据竞争,属较大重构)。
|
1. **宽缓冲区重建**在视界移出缓冲范围(约每平移 1 屏一次)时于**前台**进行,负载下约 30–50ms 的**一次**尖峰。彻底消除需把"基底渲染"做成**不依赖成员状态的独立渲染器**再搬后台线程(`PaintLane` 会写成员缓存,直接后台化有数据竞争,属较大重构)。
|
||||||
2. **非拖拽的视界变化**(键盘方向键 / 工具栏)仍走整幅重绘(约 125ms);仅**鼠标左/中键拖动**走宽缓冲快路径。若需让键盘平移也快,需允许非拖拽帧复用"整数像素对齐"的宽缓冲(当前为避免 ≤0.5px 亚像素偏差而未启用)。
|
2. **非拖拽的视界变化**(键盘方向键 / 工具栏)仍走整幅重绘(约 125ms);仅**鼠标左/中键拖动**走宽缓冲快路径。若需让键盘平移也快,需允许非拖拽帧复用"整数像素对齐"的宽缓冲(当前为避免 ≤0.5px 亚像素偏差而未启用)。
|
||||||
|
|
||||||
|
### 9.12 宽缓冲改为「滚动」:消除周期性重建尖峰(持续拖动恒 ≥60fps)
|
||||||
|
9.11 的残留 1(移出边界时整幅重建 2×视界,负载下约 30–50ms 的一次尖峰)已消除:
|
||||||
|
- **滚动式缓冲**:视界每帧移动 d 像素时,把缓冲内容**整体自贴图平移 `round(d)` 像素**,再**只补画新暴露的 `|round(d)|` 像素宽细条**。因此**每帧代价恒为 O(细条)**,不再有"移出边界就整幅重建"的周期性大尖峰。
|
||||||
|
- **细条渲染复用整条宽窗口的映射**:`RenderWideStrip` 令 `m_area` 只覆盖细条(GDI/GDI+ 裁剪落在细条),而 `m_plotRect` 仍是完整宽窗口 → 网格线与波形 x 坐标正确、刻度密度与主视界一致;仅新暴露的细条被重画。
|
||||||
|
- **亚像素误差有界**:用锚点 `m_wideAnchorMin` 记"视界左端当前对应缓冲中的时间",每帧按 `round((viewMin - anchor)*scale)` 平移并同步推进锚点,使"内容相对时间轴的偏移"始终 ≤0.5 像素。
|
||||||
|
- **缓冲只覆盖绘图区**(不含底部时间轴):时间轴由前台每帧按当前视图绘制,避免滚动细条把时间轴文字切成残字。
|
||||||
|
- **实测**(`VERY_BIG_DATA` 全信号,视界 `[40.1756, 40.1885]s`,5 网格):
|
||||||
|
|
||||||
|
| 场景 | 9.11 | 本次 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 中键平移拖动(短距) | 1.3ms(含每屏一次 33ms 尖峰) | **约 6ms/帧,无尖峰** |
|
||||||
|
| 中键平移拖动(长距 975px) | 1.7ms(最大 33ms) | **约 6ms/帧,最大 10.5ms** |
|
||||||
|
| 弱波增强滑块 | 5.2ms | 6ms |
|
||||||
|
| 透明度/粗细/颜色 | 8ms | 8ms |
|
||||||
|
|
||||||
|
- **显示效果**:滚动平移帧截图核对无缝隙/无残字;**静止/结算帧与 HEAD 逐像素 0 差异**(`LockBits` 验证)。
|
||||||
|
- **剩余残留**:**每次"开始一次拖拽"的第一帧**需新建缓冲(2×视界基底,负载下约 38ms 的一次尖峰);持续拖动过程中无尖峰。要消除这一次性启动尖峰,需把基底渲染做成可后台的独立渲染器(同上,较大重构)。非拖拽视界变化仍整幅重绘。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 变更日志(本文件维护记录)
|
## 10. 变更日志(本文件维护记录)
|
||||||
@@ -337,3 +355,4 @@ Documents/ 项目文档(含本文件)
|
|||||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 探针定位"样式/参数滑块在深放大下极卡":根因是**样式补丁用了抗锯齿**(≈72ms/网格)且**后台曲线发布时整幅作废**导致每帧整幅重绘。修复:样式补丁强制快速帧、拖拽中不整幅作废、交互期省略面板投影 → 透明度/粗细/颜色 77ms→16ms、弱波增强滑块 96–176ms→26ms,静止/结算帧逐像素不变。 |
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 探针定位"样式/参数滑块在深放大下极卡":根因是**样式补丁用了抗锯齿**(≈72ms/网格)且**后台曲线发布时整幅作废**导致每帧整幅重绘。修复:样式补丁强制快速帧、拖拽中不整幅作废、交互期省略面板投影 → 透明度/粗细/颜色 77ms→16ms、弱波增强滑块 96–176ms→26ms,静止/结算帧逐像素不变。 |
|
||||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 落地用户提出的**复用像素**方案:新增**平移宽缓冲区**(以视界为中心左右各多渲染半个视界宽),平移时前台只做一次 **BitBlt 截取子矩形**(O(1)),移出边界才重建 → 深放大下平移 **34ms→1.3ms**;静止/结算帧逐像素 0 差异。 |
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 落地用户提出的**复用像素**方案:新增**平移宽缓冲区**(以视界为中心左右各多渲染半个视界宽),平移时前台只做一次 **BitBlt 截取子矩形**(O(1)),移出边界才重建 → 深放大下平移 **34ms→1.3ms**;静止/结算帧逐像素 0 差异。 |
|
||||||
| 2026-10-11 | DeepSeek-V4.1-Flash | 把宽缓冲推广为**基底复用**:抽出 `PaintLaneDerivedOverlays`、宽缓冲只存基底(不含派生曲线),基底键排除派生参数 → 拖弱波增强滑块 **55ms→5.2ms**;平移 1.3ms、样式 8ms 不变;静止帧逐像素 0 差异。残留:宽缓冲重建在前台(约每屏一次)、非拖拽视界变化仍整幅重绘。 |
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 把宽缓冲推广为**基底复用**:抽出 `PaintLaneDerivedOverlays`、宽缓冲只存基底(不含派生曲线),基底键排除派生参数 → 拖弱波增强滑块 **55ms→5.2ms**;平移 1.3ms、样式 8ms 不变;静止帧逐像素 0 差异。残留:宽缓冲重建在前台(约每屏一次)、非拖拽视界变化仍整幅重绘。 |
|
||||||
|
| 2026-10-11 | DeepSeek-V4.1-Flash | 宽缓冲改为**滚动式**(自贴图平移 + 只补画新暴露细条),消除"移出边界整幅重建"的周期性尖峰;缓冲只覆盖绘图区、时间轴前台绘制、亚像素误差用锚点限制在 ≤0.5px → 持续拖动平移约 6ms/帧、无尖峰(长距最大 10.5ms);静止帧逐像素 0 差异。残留:每次开始拖拽的首帧需新建缓冲(一次启动尖峰)。 |
|
||||||
Reference in new issue
Block a user