悬停光晕 210ms/帧 → 4ms(包络带填充 + 光标窗口裁剪)+ 平移丢波形判据修正 + 随机组合压力测试
一、悬停巨型信号时整帧卡死(用户报:VERY_BIG_DATA 的「编码器A相」,浮动提示不再跟随鼠标)
探针复现:hoverMean = 210.31ms / baseline = 0.90ms。
根因:PaintSignalHalo 用「加粗 GDI+ 折线」描边整条曲线,而包络带信号的顶点是
「每像素列上/下包络交替」的满幅竖锯齿 —— GDI+ 描边代价 ≈ 线段数 × 覆盖行数
(2503 顶点 × 1081 行)。同一几何改用 FillPath 只要 ~1ms。
修法(PlotViewPaint.cpp / PlotViewInternal.hpp):
1) 包络带信号(cached.bandMode)的光晕改为**填充与曲线本身同一几何**的包络带
(bandBreaks 段 → 上包络正向 + 下包络反向,alpha 0x55);
2) 只画鼠标附近一段(kHaloWindowHalfWidth=72 逻辑像素、kHaloWindowMinPoints=360):
· 包络带:窗口换算成列区间再填充(下包络反向存储 ⇒ 列 c 在 start+half+(half-1-c),
列区间恰对应一段正向可读的连续区间);
· 折线描边:O(n) 扫一遍取窗口内的连续顶点段,段首尾各多含一个顶点(跨窗口的线段完整画出);
· 散点高亮同样按窗口过滤(步长仍与 PaintLane 同源)。
3) 窗口中心 = 鼠标 x;鼠标不在绘图区时取绘图区中心(列表/样式区悬停代价同样恒定)。
实测 210ms → 4.0ms(提示恢复跟手)。几何正确性用「透明位图上两次绘制」探针确认:
窗口内与整条光晕逐像素相同(mismatched=0)、窗口外 0 像素(far=0)、全部落在包络带内(outsideBand=0)。
二、组折叠/展开动画卡顿甚至中断(用户用 MF4→BIG→MF4 + 只勾「编码器A相」稳定复现)
这是上面那条的连带症状:列表悬停时 Application::Render 把 panel.HoveredSignal() 推给
SetExternalHoverSignal ⇒ 鼠标每动一次就重画一次光晕;悬停在 2576 万采样的编码器行上
= 210ms/帧 ⇒ 动画定时器被饿死,看起来「卡顿甚至中断」。
同一修复后列表悬停帧 0.72ms(≈ 基线 0.81ms)。探针 [3b] 固化该判据。
三、快速中键拖拽「所有网格信号一起消失」——主修复已正确,之前的失败是度量错了
上一轮把「位移超出 kLayerRollMaxShiftPixels 就作废图层」改成
「driftLimit + 每帧最多滚 kLayerRollMaxStepPixels=240px」,剩余位移由 BlitWideBuffer 的
srcX 承担 ⇒ |shift| 可长期停在 ±(margin-24) 附近而**不会空白**。
之前探针拿「适应视图时的墨迹」当基准,而平移本身会移出数据范围(时间轴外没有采样),
墨迹自然下降 ⇒ 误报。正确判据(已固化进 panflash / stress):
同一次拖拽中把当前帧与**同一视界的整幅强制重绘**(m_sceneValid=false + 清空 m_layerFront)比墨迹,
比值 < 0.9 才算丢波形。三条拖拽模式(120px/帧、90×7 斜向、260px/帧)全部 badFrames=0,
worstRatio 0.966~0.999;MF4 8 个单点信号网格 framesWithMissingPoints=0。
四、新增随机组合压力测试(用户第 4 项要求:给出评估方法与压力验证)
.preflight/perf/stress_main.cpp:1 个 VERY_BIG_DATA(5×2576 万采样)+ 6 个 MF4 分组
(~1500 信号、41 个可见网格),固定种子的随机序列覆盖:中键拖拽(抽样做空白判据)、
滚轮缩放/分布缩放、悬停扫掠、组折叠展开(**每帧都把鼠标扫过正在出现/消失的行**,并校验
m_contentHeight 单调)、适应视图、面板滚动条拖拽、勾选/取消信号、标签页新建/切换/关闭。
判据全部为**同一次运行内的相对量**(基线帧 vs 交互帧),避免绝对 ms 阈值的漂移。
实测:ops=26 frames=209 mean=8.53ms worst=44.87ms(toggleSignal) overBudget=0;
pan blank check 90 个车道样本 0 个低于整幅重绘的 80%;group animation heightRegressions=0。
最慢帧是勾选信号(41 个网格全部重画曲线像素),属于用户明确排除的「需要重绘信号曲线」场景。
五、验证(全部离屏,不占用鼠标/键盘/屏幕)
- 新增 .preflight/perf/panflash_main.cpp(panflash.exe)19/19:三条拖拽模式的丢波形判据、
MF4 单点网格、悬停耗时(4.0ms)、列表悬停联动(0.72ms)、光晕窗口化几何精确性;
- 新增 stress 6/6;既有回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、
gridanim 93/93、lanescroll 23/23、datacheck 69/69;
- make release TARGET_FILE_NAME=TargetCheck 通过(Target.exe 被运行中的实例占用)。
文档:Documents/agent_mem.md 新增本轮小节(含 4 条新规则:GDI+ 加粗描边不得用于包络带几何、
随鼠标变化的绘制必须 O(光标附近)、判据必须是同一次运行内的相对量、平移正确性只能与整幅重绘对比)。
This commit is contained in:
1 parent
fd606a6f80
commit
0efaa8c0ca
4 files changed
+288
-50
No files matched your search
@@ -1043,20 +1043,96 @@ namespace jdi
|
||||
}
|
||||
|
||||
void PlotView::PaintSignalHaloFor(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
const PolylineCacheEntry& cached)
|
||||
const PolylineCacheEntry& cached, float windowCenterX, bool windowed)
|
||||
{
|
||||
if (resolved.style == nullptr)
|
||||
{
|
||||
return;
|
||||
}
|
||||
// 光晕一律用折线描边(与"之前"的画法一致)。曾经在包络带模式下改成"带外扩填充",
|
||||
// 虽然更快但观感变了(用户反馈显示效果下降),因此恢复。
|
||||
PaintSignalHalo(g, resolved, lane, cached.points, cached.breaks);
|
||||
// ------------------------------------------------------------------
|
||||
// 稠密信号(走"像素列包络带"):光晕改为**填充与曲线本身同一几何的包络带**。
|
||||
//
|
||||
// ⚠️ 绝不能用「加粗折线描边」画稠密信号的光晕:包络带的顶点序列是
|
||||
// 「每列上/下包络交替」的满幅竖锯齿,GDI+ 描边的代价 = 线段数 × 覆盖行数,
|
||||
// 实测 2503 个顶点要 **210ms/帧**(用户 2026-10-18 报告:鼠标划过巨型信号的
|
||||
// 曲线时整帧卡死、浮动提示不再跟随鼠标)。而 `FillPath` 同一条几何只需 ~1ms,
|
||||
// 且观感更贴切(整条曲线一起"发光",与曲线本身逐像素同源)。
|
||||
// ⚠️ 填充也**只填鼠标附近的一段**(见 `kHaloWindowHalfWidth`):半透明填充
|
||||
// 的代价与「覆盖面积 + 路径顶点数」成正比,整条满幅带实测 ~13ms/帧。
|
||||
// ------------------------------------------------------------------
|
||||
if (resolved.style->mode != DrawMode::SCATTER && cached.bandMode && !cached.bandBreaks.empty())
|
||||
{
|
||||
const float halfWidth = kHaloWindowHalfWidth * m_scale;
|
||||
const int savedSmoothing = g.GetSmoothingMode();
|
||||
g.SetSmoothingMode(gd::SmoothingModeNone); // 半透明填充:抗锯齿没有观感收益
|
||||
gd::GraphicsPath bandPath;
|
||||
bandPath.SetFillMode(gd::FillModeWinding);
|
||||
for (std::size_t k = 0; k < cached.bandBreaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = cached.bandBreaks[k];
|
||||
const std::size_t stop =
|
||||
(k + 1 < cached.bandBreaks.size()) ? cached.bandBreaks[k + 1] : cached.band.size();
|
||||
const std::size_t total = stop - start;
|
||||
if (total < 4 || (total % 2) != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// 段内布局 = 「上包络正向(`half` 个)+ 下包络反向(`half` 个)」
|
||||
//(见 `EmitEnvelopeBand`),因此窗口只需换算成列区间。
|
||||
const std::size_t half = total / 2;
|
||||
std::size_t columnFirst = 0;
|
||||
std::size_t columnLast = half;
|
||||
if (windowed)
|
||||
{
|
||||
while (columnFirst < half &&
|
||||
cached.band[start + columnFirst].X < windowCenterX - halfWidth - 1.0f)
|
||||
{
|
||||
++columnFirst;
|
||||
}
|
||||
while (columnLast > columnFirst &&
|
||||
cached.band[start + columnLast - 1].X > windowCenterX + halfWidth + 1.0f)
|
||||
{
|
||||
--columnLast;
|
||||
}
|
||||
// 两侧各多含 1 列,避免窗口边界处的半透明边缘被切出直边。
|
||||
if (columnFirst > 0)
|
||||
{
|
||||
--columnFirst;
|
||||
}
|
||||
if (columnLast < half)
|
||||
{
|
||||
++columnLast;
|
||||
}
|
||||
}
|
||||
if (columnLast <= columnFirst + 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
const std::size_t columns = columnLast - columnFirst;
|
||||
bandPath.StartFigure();
|
||||
bandPath.AddLines(cached.band.data() + start + columnFirst, static_cast<INT>(columns));
|
||||
// 下包络反向存储:列 c 在 `start + half + (half - 1 - c)`,
|
||||
// 因此列区间 `[columnFirst, columnLast)` 对应一段**正向可读**的连续区间。
|
||||
bandPath.AddLines(cached.band.data() + start + half + (half - columnLast),
|
||||
static_cast<INT>(columns));
|
||||
bandPath.CloseFigure();
|
||||
}
|
||||
if (bandPath.GetPointCount() > 0)
|
||||
{
|
||||
gd::SolidBrush brush(ToGdiColor(WithAlpha(m_theme->accent, 0x55)));
|
||||
g.FillPath(&brush, &bandPath);
|
||||
}
|
||||
g.SetSmoothingMode(static_cast<gd::SmoothingMode>(savedSmoothing));
|
||||
return;
|
||||
}
|
||||
// 其余情形一律用折线描边(与"之前"的画法一致)。
|
||||
PaintSignalHalo(g, resolved, lane, cached.points, cached.breaks, windowCenterX, windowed);
|
||||
}
|
||||
|
||||
void PlotView::PaintSignalHalo(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
const std::vector<gd::PointF>& points,
|
||||
const std::vector<std::size_t>& breaks)
|
||||
const std::vector<std::size_t>& breaks, float windowCenterX,
|
||||
bool windowed)
|
||||
{
|
||||
// ⚠️ **只有单个数据点**的信号(默认样式是「点线」,那个点本身是一个粗圆点):
|
||||
// 折线光晕无从下手(不足两个顶点),改为在数据点外包一圈强调色圆环。
|
||||
@@ -1093,10 +1169,18 @@ namespace jdi
|
||||
const float ringRadius = radius + 2.5f * m_scale;
|
||||
const std::size_t scatterStride =
|
||||
std::max<std::size_t>(1, points.size() / kMaxScatterPoints);
|
||||
// 散点数量本身有上限(`kMaxScatterPoints`),逐点画圈是 O(数千) 的小代价;
|
||||
// 但仍按窗口过滤,保持"只高亮鼠标附近"的一致语义(点少时窗口外也全画)。
|
||||
const bool windowScatter = windowed && points.size() > kHaloWindowMinPoints;
|
||||
const float halfWidth = kHaloWindowHalfWidth * m_scale;
|
||||
gd::Pen ring(ToGdiColor(WithAlpha(m_theme->accent, 0x88)), 1.6f * m_scale);
|
||||
for (std::size_t index = 0; index < points.size(); index += scatterStride)
|
||||
{
|
||||
const gd::PointF& point = points[index];
|
||||
if (windowScatter && std::fabs(point.X - windowCenterX) > halfWidth)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
g.DrawEllipse(&ring, point.X - ringRadius, point.Y - ringRadius, ringRadius * 2.0f,
|
||||
ringRadius * 2.0f);
|
||||
}
|
||||
@@ -1112,6 +1196,12 @@ namespace jdi
|
||||
halo.SetLineJoin(gd::LineJoinRound);
|
||||
halo.SetStartCap(gd::LineCapRound);
|
||||
halo.SetEndCap(gd::LineCapRound);
|
||||
// 顶点多的信号只描边**鼠标左右一小段**(见 `kHaloWindowHalfWidth` 的说明):
|
||||
// 整条描边在满幅锯齿信号上要 100~200ms/帧,鼠标移动时整帧卡死。
|
||||
const bool windowStroke = windowed && points.size() > kHaloWindowMinPoints;
|
||||
const float halfWidth = kHaloWindowHalfWidth * m_scale;
|
||||
const float windowLeft = windowCenterX - halfWidth;
|
||||
const float windowRight = windowCenterX + halfWidth;
|
||||
for (std::size_t k = 0; k < breaks.size(); ++k)
|
||||
{
|
||||
const std::size_t start = breaks[k];
|
||||
@@ -1120,7 +1210,46 @@ namespace jdi
|
||||
{
|
||||
continue;
|
||||
}
|
||||
g.DrawLines(&halo, points.data() + start, static_cast<INT>(stop - start));
|
||||
if (!windowStroke)
|
||||
{
|
||||
g.DrawLines(&halo, points.data() + start, static_cast<INT>(stop - start));
|
||||
continue;
|
||||
}
|
||||
// 窗口内的顶点在段内**连续**(折线沿时间单调),因此顺序扫描一次、按连续区段绘制。
|
||||
// 多含一个窗口外的顶点,让跨越窗口边界的线段完整画出(不出现断头)。
|
||||
std::size_t runStart = stop;
|
||||
std::size_t runStop = stop;
|
||||
for (std::size_t index = start; index < stop; ++index)
|
||||
{
|
||||
const bool inside = (points[index].X >= windowLeft - 1.0f) &&
|
||||
(points[index].X <= windowRight + 1.0f);
|
||||
if (inside && runStart == stop)
|
||||
{
|
||||
runStart = (index > start) ? (index - 1) : index;
|
||||
runStop = index + 1;
|
||||
}
|
||||
else if (inside)
|
||||
{
|
||||
runStop = index + 1;
|
||||
}
|
||||
else if (runStart != stop)
|
||||
{
|
||||
if (index + 1 < stop)
|
||||
{
|
||||
runStop = index + 2;
|
||||
}
|
||||
if (runStop > runStart + 1)
|
||||
{
|
||||
g.DrawLines(&halo, points.data() + runStart, static_cast<INT>(runStop - runStart));
|
||||
}
|
||||
runStart = stop;
|
||||
runStop = stop;
|
||||
}
|
||||
}
|
||||
if (runStart != stop && runStop > runStart + 1)
|
||||
{
|
||||
g.DrawLines(&halo, points.data() + runStart, static_cast<INT>(runStop - runStart));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1173,7 +1302,16 @@ namespace jdi
|
||||
const PolylineCacheEntry* cached = AcquirePolyline(haloRef, resolved, lane);
|
||||
if (cached != nullptr)
|
||||
{
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached);
|
||||
// 光晕只描边鼠标附近的一段(见 `kHaloWindowHalfWidth`):鼠标不在绘图区内时
|
||||
//(列表/样式区悬停)以绘图区中心为窗口中心,保证代价恒定、不平白卡一帧。
|
||||
const bool mouseInPlot = (m_mouseX >= m_plotRect.X &&
|
||||
m_mouseX <= m_plotRect.X + m_plotRect.Width &&
|
||||
m_mouseY >= m_plotRect.Y &&
|
||||
m_mouseY <= m_plotRect.Y + m_plotRect.Height);
|
||||
const float haloCenterX =
|
||||
mouseInPlot ? static_cast<float>(m_mouseX)
|
||||
: (m_plotRect.X + m_plotRect.Width * 0.5f);
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached, haloCenterX, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1246,7 +1384,8 @@ namespace jdi
|
||||
const LaneVisual& lane = m_lanes[static_cast<std::size_t>(laneIndex)];
|
||||
if (const PolylineCacheEntry* cached = AcquirePolyline(m_hoverLegend, resolved, lane))
|
||||
{
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached);
|
||||
// 图例悬停:鼠标就在图例条目上(网格左上角),以它为窗口中心。
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached, static_cast<float>(m_mouseX), true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3165,17 +3304,21 @@ namespace jdi
|
||||
std::llround((layer.anchorYMax - m_lanes[index].yMax) * layer.pxPerValue));
|
||||
}
|
||||
|
||||
// ⚠️⚠️ **位移太大时【作废图层】,绝不能在前台同步整块重画**(用户 2026-10-17 报告):
|
||||
// 旧实现在这里“立刻整块重画并重新居中”。整块重画一条**巨型信号**(`VERY_BIG_DATA`
|
||||
// 25M 采样)实测 **20~130ms** —— 拖拽时只要碰到一个“离屏累积了大位移、刚刚进入预取带”
|
||||
// 的网格,就是一次明显卡顿(实测拖拽最坏帧 28ms、其中 `RollWideBuffer` 占 130ms)。
|
||||
// 现在:位移超出可廉价滚动的范围 ⇒ **作废图层 + 登记待重画**,本帧由 `BlitWideBuffer`
|
||||
// 的**网格底衬**兜底(正是用户要求的“没来得及画信号就按只有网格叠底的状态继续”),
|
||||
// 波形等视界停下后由 `RenderPendingLaneRebuilds` 按预算逐个补。
|
||||
const bool tooFarToRoll = (std::abs(shift) >= kLayerRollMaxShiftPixels) ||
|
||||
(std::abs(vOffset) >= layer.height);
|
||||
if (tooFarToRoll)
|
||||
// ⚠️⚠️ **"位移太大"绝不允许【作废图层】**(用户 2026-10-18 报告:
|
||||
// 快速拖动中键时**所有网格的信号一起消失**,因为一旦作废就要等拖拽结束才重建)。
|
||||
// 关键认识:`BlitWideBuffer` 按 `(ViewMin - layer.wideMin) * scale` 算源偏移,
|
||||
// 只要视界仍落在位图覆盖范围内(`|shift| <= margin`),**不滚动也画得完全正确**。
|
||||
// 因此这里分三种情形:
|
||||
// * `|shift| <= driftLimit`(位图仍完整覆盖视界)⇒ **什么都不做**(免费);
|
||||
// * 超出 ⇒ 向中心滚,但**每帧最多滚 `kLayerRollMaxStepPixels`**(把补条代价摊开),
|
||||
// 剩余位移继续由贴图偏移承担 ⇒ **永远不会空白**;
|
||||
// * 真的跳到画布之外(`|shift| >= bitmapWidth`)⇒ 才作废 + 登记待重画
|
||||
// (那是“点选信号跳转”这类整屏换地方的情形,本来就要重画)。
|
||||
const int driftLimit = std::max(8, margin - 24);
|
||||
const int vOffsetAbs = std::abs(vOffset);
|
||||
if (vOffsetAbs >= layer.height)
|
||||
{
|
||||
// 纵向已超出画布(画布高约等于 2×网格高):像素映射不再可用。
|
||||
layer.valid = false;
|
||||
QueueLaneRebuild(laneIndex);
|
||||
continue;
|
||||
@@ -3231,40 +3374,41 @@ namespace jdi
|
||||
layer.anchorYMax);
|
||||
}
|
||||
|
||||
if (shift == 0)
|
||||
// ⚠️ **横向重新居中只在「快要失去覆盖」时才做**(见上方 `driftLimit` 的说明):
|
||||
// `BlitWideBuffer` 按 `(ViewMin - layer.wideMin)` 算源偏移,因此视界小幅漂移
|
||||
// **完全不需要滚动**(等价且免费)。只有漂移接近 `margin`(位图覆盖边界)时才需要
|
||||
// 向中心滚回去,否则网格右侧/左侧会开始用底衬(看起来像“信号被切成两半”)。
|
||||
if (std::abs(shift) <= driftLimit)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// ⚠️ **横向滚动量按量子量化**(见 `kLayerRollQuantumX`):
|
||||
// `BlitWideBuffer` 贴图时本来就按 `(ViewMin - layer.wideMin)` 算源偏移,
|
||||
// 因此视界小幅移动**不需要**重新居中(不必每帧补一条窄竖带)。
|
||||
// 拖拽时每帧位移只有几像素,旧实现就是“每帧补一次条 × 每个可见网格”,
|
||||
// 而每次补条都要重跑 `BuildLaneVisuals()`(O(网格数))⇒ 明显卡顿。
|
||||
if (std::abs(shift) < kLayerRollQuantumX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// ⚠️ **每帧只重居中有限的几个网格**(见 `kLayerRollsPerFrame`):
|
||||
// 不重居中也不会画错(贴图按 `wideMin` 算源偏移),因此可以安全地分帧完成。
|
||||
// ⚠️ **每帧只重居中有限的几个网格**(见 `kLayerRollsPerFrame`),且单次滚动量有上限
|
||||
// (`kLayerRollMaxStepPixels`):不重居中也不会画错,超出部分继续由贴图偏移承担,
|
||||
// 因此可以安全地分帧完成。
|
||||
if (rollBudget <= 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
--rollBudget;
|
||||
const int maxStep = std::min(kLayerRollMaxStepPixels, bitmapWidth / 2);
|
||||
const int roll = std::max(-maxStep, std::min(maxStep, shift));
|
||||
|
||||
// 整数像素自贴图平移:内容整体移动 `shift` 像素。
|
||||
if (shift > 0)
|
||||
// 整数像素自贴图平移:内容整体移动 `roll` 像素。
|
||||
if (roll > 0)
|
||||
{
|
||||
// 内容整体左移 shift:new[x] = old[x + shift],右侧暴露 shift 列。
|
||||
BitBlt(layer.dc, 0, 0, bitmapWidth - shift, layer.height, layer.dc, shift, 0, SRCCOPY);
|
||||
// 内容整体左移 roll:new[x] = old[x + roll],右侧暴露 roll 列。
|
||||
BitBlt(layer.dc, 0, 0, bitmapWidth - roll, layer.height, layer.dc, roll, 0, SRCCOPY);
|
||||
}
|
||||
else if (roll < 0)
|
||||
{
|
||||
const int amount = -roll;
|
||||
BitBlt(layer.dc, amount, 0, bitmapWidth - amount, layer.height, layer.dc, 0, 0, SRCCOPY);
|
||||
}
|
||||
else
|
||||
{
|
||||
const int amount = -shift;
|
||||
BitBlt(layer.dc, amount, 0, bitmapWidth - amount, layer.height, layer.dc, 0, 0, SRCCOPY);
|
||||
continue;
|
||||
}
|
||||
layer.wideMin += static_cast<double>(shift) / scale;
|
||||
layer.wideMin += static_cast<double>(roll) / scale;
|
||||
|
||||
// 只补画新暴露的细条(每帧代价恒为 O(细条))。
|
||||
// ⚠️ 细条必须用**该图层更新后的映射**渲染(传 `layer.wideMin`);若改用"当前视界"
|
||||
@@ -3273,8 +3417,8 @@ namespace jdi
|
||||
// 竖带,其余像素仍在原锚点上;同时该函数按锚点渲染这条竖带,因此竖带与其余像素
|
||||
// 纵向同源。若在这里把锚点重置为当前 `yMax`,残余的纵向偏移会被静默清零、而像素
|
||||
// 并未上移 → 整个网格的波形纵向错位("垂直拖动后再横向平移出现纵向跳变")。
|
||||
const int stripWidth = std::min(std::abs(shift), bitmapWidth);
|
||||
const int stripX = (shift > 0) ? (bitmapWidth - stripWidth) : 0;
|
||||
const int stripWidth = std::min(std::abs(roll), bitmapWidth - 1);
|
||||
const int stripX = (roll > 0) ? (bitmapWidth - stripWidth) : 0;
|
||||
RenderLaneLayerRegion(laneIndex, layer, stripX, stripWidth, 0, 0, layer.wideMin,
|
||||
layer.anchorYMax);
|
||||
}
|
||||
@@ -3781,7 +3925,8 @@ namespace jdi
|
||||
const bool highlighted = (ref == haloRef);
|
||||
if (highlighted)
|
||||
{
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached);
|
||||
// 拖拽光晕整条描边并进入静态场景缓存(拖拽期间不随鼠标变化,不必裁剪)。
|
||||
PaintSignalHaloFor(g, resolved, lane, *cached, 0.0f, false);
|
||||
}
|
||||
|
||||
if (drawLine && !breaks.empty())
|
||||
|
||||
Reference in new issue
Block a user