修复长距离中键拖拽的绘制错误与抽搐("距离太远"不再退化成排队重画)
症状(用户报告 + TestData/PROBLEM_IMG):按住中键把视界纵向移动好几个网格高(或横向拖很远),
不松开再挪回来时出现大量绘制错误与持续抽搐。
根因:RollWideBuffer 对"位移超出图层余量"只做"排队等后续帧重画"(QueueLaneRebuild + continue):
* 该帧 BlitWideBuffer 的 srcY = marginY + vOffset 已落在画布之外 → 只能贴出/贴不到像素
→ 网格里出现空白条带(截图里的白色块);
* 下一帧位移依旧超限 → 每帧都重新入队,而 RenderPendingLaneRebuilds(1) 一帧只补 1 个网格
→ 5 个网格永远补不完 = 持续抽搐;
* 纵向余量只有 ±marginY(半个网格高 ≈80px),中键纵向拖拽每帧很容易移动 20~150px
→ 几乎必现;横向同理(余量 ±半屏,快速拖拽会超过)。
修法:只有"内容真的变了"(contentStale:外观/网格几何/幅值跨度/可见性…)才排队(那时画面本身
仍是旧内容、不缺口);距离类问题一律本帧就地解决:
* |shift| < bitmapWidth 且 |vOffset| < 画布高 → 滚动:BitBlt 整块平移 + 只补画暴露的细条
(横向条 / 纵向条),并同步锚点记账(wideMin / anchorYMax);
* 任一超出画布(内容整体滚出)→ 本帧立刻整块重画并重新居中
(RenderLaneLayerRegion(i, layer, 0, bitmapWidth, 0, 0, LayerDefaultWideMin()),实测 0.4ms/网格)。
探针(.preflight/dragcheck,按用户要求"全程按住中键不放"):
* 图层条带 vs 同锚点新渲染:逐像素(滚动是纯整数平移,必须相同);
* 覆盖率:(ViewMin - layer.wideMin) * scale == marginX(±1px)且 marginY + vOffset 起的一整个
网格高都落在画布内 —— 任一条不成立,BlitWideBuffer 就会留下空白条带;
* 画面结构:网格内"无墨迹行数"与"墨迹占比"对比整幅重绘参考帧(空白条带 / 内容错位探测器)。
场景:上拖半个网格高;按住拖左右/上下/斜向并拖出窗口;纵向拖 4 个网格高再拖回来并小幅摆动;
快速大位移对角拖拽(每帧 150×110px)。A/B 验证探针有效性:临时把纵向滚动关掉
(kLayerRollQuantum = 100000)→ 立刻报 worst layer band diff 76221 / drawing errors 11;
恢复后 0 错误。
两点说明(避免误判):
1. dragcheck 的"画面像素 diff"会稳定出现 ~2.5 万像素(一个网格的 ~11%)差异 —— 那是图层与整幅
重绘的列相位差(图层画布 2449px 按 1px 列聚合,整幅重绘 1224px 列,相位差 ≤0.5px),对稠密脉冲
信号(256kHz 编码器)会放大成大片边缘差异;这是长期存在、由"结算帧"收敛的现象,不是本轮缺陷,
因此画面检查用结构化指标而不是像素 diff。
2. 截图里"整条网格被信号色填满"是正确渲染:把幅值窗口平移到与稠密信号相交的区间时,每列的
min/max 包络本来就覆盖整个网格(示波器上同样如此)。
验证(全部离屏,不占用鼠标/键盘/屏幕):dragcheck PASS(0 绘制错误;长拖拽 80 帧 + 快速对角 24 帧);
uitest 463/463;panperf PASS(竖直中位 4.4ms、水平 7-10ms);layercheck clobbers=0/24、
pan frames wrong=0/4;cursorcheck 30/30;make clean && make all 零警告。
This commit is contained in:
1 parent
59a84ba126
commit
7344bbe9c4
2 files changed
+89
-22
No files matched your search
@@ -7560,7 +7560,6 @@ namespace jdi
|
||||
// 视界左端当前落在该图层的哪个 x 上;期望它等于 `margin`(左右余量对称)。
|
||||
const double desired = (Tab().ViewMin() - layer.wideMin) * scale;
|
||||
const int shift = static_cast<int>(std::llround(desired - static_cast<double>(margin)));
|
||||
const bool tooFar = (std::abs(shift) >= bitmapWidth - 4);
|
||||
// 内容(外观 / 网格几何 / 幅值**跨度** / 可见性…)与当前状态不一致。
|
||||
const bool contentStale = (layer.key != LaneLayerKey(laneIndex));
|
||||
// 纵向:幅值**平移**不改内容,只改贴图偏移(与横向同构)。
|
||||
@@ -7570,33 +7569,34 @@ namespace jdi
|
||||
vOffset = static_cast<int>(
|
||||
std::llround((layer.anchorYMax - m_lanes[index].yMax) * layer.pxPerValue));
|
||||
}
|
||||
const int lanePixels = static_cast<int>(std::ceil(laneHeight));
|
||||
// 可用的纵向余量 = 画布高 - 可见高(= 上下各 `marginY`);偏移超出它就只能重画。
|
||||
const int maxRoll = std::max(0, layer.height - lanePixels);
|
||||
const bool verticalTooFar = (std::abs(vOffset) > maxRoll);
|
||||
|
||||
if (contentStale || tooFar || verticalTooFar)
|
||||
// ⚠️⚠️ **"距离太远"绝不允许退化成"排队等后续帧重画"**:
|
||||
// 长距离拖拽(用户按着中键把视界移动好几个网格高、或横向拖很远)时,
|
||||
// 每一帧的位移都会超过余量;若此时只登记待重画,就会出现
|
||||
// * 本帧 `BlitWideBuffer` 取不到源行 → 网格里出现**空白条带**;
|
||||
// * 每帧都重新入队 → 一帧只补 1 个网格 → **持续抽搐**且总是补不完。
|
||||
// 正确做法是:
|
||||
// * 位移不超过画布 → **滚动**(O(1) BitBlt + 只补画暴露的细条);
|
||||
// * 位移超过画布(内容整体滚出)→ 本帧**立刻整块重画并重新居中**(一次性),
|
||||
// 等价于一次普通的整块渲染(实测 0.4ms),不会累积漂移也不会留空白。
|
||||
// 只有**内容真的变了**(`contentStale`)才交给待重画队列(它可以等后续帧,
|
||||
// 因为画面本身仍然是"旧内容",不缺口)。
|
||||
const bool fullRedraw = (std::abs(shift) >= bitmapWidth) ||
|
||||
(std::abs(vOffset) >= layer.height);
|
||||
if (fullRedraw)
|
||||
{
|
||||
// 登记待重画(本帧不重画,改由 `RenderPendingLaneRebuilds` 在**贴图之前**补画),
|
||||
// 但仍按现有像素平移,避免网格"卡在原地"。
|
||||
QueueLaneRebuild(laneIndex);
|
||||
if (!tooFar && shift != 0)
|
||||
// 两个方向都滚出画布:直接重新居中 + 整块重画(一次搞定,避免"滚动 + 巨幅补条")。
|
||||
if (contentStale || std::abs(shift) >= bitmapWidth)
|
||||
{
|
||||
if (shift > 0)
|
||||
{
|
||||
BitBlt(layer.dc, 0, 0, bitmapWidth - shift, layer.height, layer.dc, shift, 0,
|
||||
SRCCOPY);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int amount = -shift;
|
||||
BitBlt(layer.dc, amount, 0, bitmapWidth - amount, layer.height, layer.dc, 0, 0,
|
||||
SRCCOPY);
|
||||
}
|
||||
layer.wideMin += static_cast<double>(shift) / scale;
|
||||
QueueLaneRebuild(laneIndex);
|
||||
}
|
||||
RenderLaneLayerRegion(laneIndex, layer, 0, bitmapWidth, 0, 0, LayerDefaultWideMin());
|
||||
continue;
|
||||
}
|
||||
if (contentStale)
|
||||
{
|
||||
QueueLaneRebuild(laneIndex);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------
|
||||
// 纵向(幅值)滚动:与横向**完全同构**——内容整体上下移动若干行,
|
||||
@@ -7614,6 +7614,7 @@ namespace jdi
|
||||
// 折线缓存(键含幅值映射)得以命中;否则每帧滚 1~30 行会让折线每帧重算
|
||||
// (实测同样的拖动:量化前 3~4ms 的滚动 + 10~14ms 整帧,量化后回到 ~5ms 量级)。
|
||||
// 余量足够大(上下各 `marginY`),因此"不滚只偏移"绝不会超出画布。
|
||||
// 上面已保证 |vOffset| < 画布高,所以这里的 BitBlt 高度恒为正。
|
||||
// ------------------------------------------------------------------
|
||||
if (std::abs(vOffset) >= kLayerRollQuantum)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user