修复悬停高亮变长方形色块/联动失效 + 信号列表动画卡顿真因(行位图缓存)
一、悬停高亮恢复"整条曲线发光"(上一轮裁成鼠标附近一段 ⇒ 满幅方波上就是个长方形色块) 光晕的语义是"整条曲线一起发光",对编码器这种满幅方波,一列包络本来就跨满网格高, 裁成 144px 宽 × 满高的窗口在屏幕上就是一个像选区框的色块(用户 2026-10-18 第二轮报告)。 修法(PlotViewPaint.cpp): - PaintSignalHaloFor 恢复**整条**几何(包络带 FillPath / 折线 DrawLines); - 新增 PaintSignalHaloCached:光晕几何只取决于「信号 + 视界 + 网格几何 + 样式」,**与鼠标无关** ⇒ 渲染一次进离屏位图,之后每帧只做一次 AlphaBlend(源位图预乘 alpha,用 256×256 查表); - 缓存键与 AcquirePolyline 同源,**不含网格屏幕位置**(滚动网格只按新位置贴同一张位图)。 210ms/帧(整条描边)→ 一次性 15ms 渲染 + 每帧 ~2ms 贴图。 halocheck 校验:悬停必命中(60/60 位置)、光晕贴合带宽(outsideBand=0)、不越出网格、 bbox 覆盖整条曲线(不是中段方块)、列表行悬停联动仍生效。 二、信号列表动画卡顿的三个真因(探针 panelstutter:129 个可见网格 + 反复"展开→切组复选框→收起") 1) 面板每帧重画 ~36 行 × ~0.35ms(十来个 GDI+ 原语)⇒ panel.Paint 24~35ms; 2) 动画帧里仍在预热网格图层:每帧重建 1 个网格图层,2500 万采样信号 10~30ms ⇒ 帧被拉长; 3) 勾选分组会把上百个**离屏**网格排进待重画队列,随后每帧补 1 个 ⇒ 数秒持续掉帧。 修法: 1) 行位图缓存(SignalPanel::PaintRowCached):按"行的可见内容"做键,整行渲染进离屏 DIB, 命中时一次 BitBlt。panel.Paint 24~35ms → 5~6ms。 ⚠️ 必须 DIB + 内存 DC(gd::Bitmap 上 GDI 会关闭 ClearType,整列文字退化); ⚠️ 行内几何在 RowRect 里平移(GDI 文本不认 GDI+ 世界变换,否则文字画到小位图之外); ⚠️ 渲染到行位图时不按列表矩形裁剪,裁剪放到贴图时的 DC(GDI 不认 GDI+ 裁剪, 必须自己 SaveDC + IntersectClipRect,折起动画时行不许画到组名上); ⚠️ 缓存上限 192 行(≈10MB)。静态画面逐像素一致(面板区域 54/275200 不同)。 2) 新增 PlotView::SetAnimationBusy:UI 动画期间不预热网格图层(队列保留,动画结束后继续)。 3) 待重画队列只保留可视 + 预取带内的网格;RollWideBuffer 不再登记离屏网格。 实测:展开 p50 35ms → 13ms、收起 33ms → 13ms、动画帧内图层重建 56 次 → 0 次。 三、试过并放弃(避免以后重走) - ExtTextOutW 替代 DrawTextW:快 2~4 倍但光栅化不同(面板 10% 像素差异)⇒ 观感变化,放弃; - 用 gd::Bitmap 做行位图:文字丢失 ClearType,放弃。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - 新增 .preflight/perf/panelstutter_main.cpp(8/8)、halocheck_main.cpp(16/16); - 回归 verify 49/49、panelanim 158/158、hoverdrag 20/20、gridanim 93/93、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、panflash 19/19、stress 6/6; - make release / make debug 零警告(TARGET_FILE_NAME=TargetCheck/TargetCheckD)。 文档:Documents/agent_mem.md 追加本轮小节(含 4 条新规则:高亮语义不得为性能让步、 GDI 文本的三个隐形陷阱、面板卡顿先量"每行成本×可见行数"与"本帧是否顺带做绘图区重活"、 动画帧内绝不做可延迟的整帧级工作)。
This commit is contained in:
1 parent
0efaa8c0ca
commit
24f0bf849d
11 files changed
+853
-197
No files matched your search
@@ -1067,7 +1067,22 @@ namespace jdi
|
||||
*/
|
||||
void RefreshLanes();
|
||||
|
||||
/**
|
||||
* @brief 设置「前台正在播放 UI 动画」标志(信号列表滚动 / 分组折起展开动画)。
|
||||
*
|
||||
* ⚠️ 为 true 时**暂停网格图层预热**(`WarmLaneLayersStep`):预热每帧要重画 1 个网格图层,
|
||||
* 而单个巨型信号(2500 万采样)的图层重建实测 **10~30ms** —— 它会直接把 60fps 的
|
||||
* 动画打成一帧一跳(用户 2026-10-18 报告:信号列表只有文字,滚动/展开却明显卡顿)。
|
||||
* 动画结束后预热会自然继续(待重画队列不丢),因此只是"推迟",不改变最终画面。
|
||||
* @param [IN] busy 是否正在播放动画。
|
||||
*/
|
||||
void SetAnimationBusy(bool busy) { m_animationBusy = busy; }
|
||||
|
||||
private:
|
||||
/** @brief 前台是否正在播放 UI 动画(见 `SetAnimationBusy`)。 */
|
||||
bool m_animationBusy = false;
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief 单个网格的绘制信息。
|
||||
*/
|
||||
@@ -2764,11 +2779,87 @@ namespace jdi
|
||||
* @param [IN] resolved 已解析的信号。
|
||||
* @param [IN] lane 该信号所在网格(单点信号需要它把数据点换算到屏幕坐标)。
|
||||
* @param [IN] cached 该信号的折线缓存项(包络带模式下会走带外扩填充)。
|
||||
* @param [IN] windowCenterX 描边窗口中心(屏幕 x);仅当 `windowed` 为 true 时生效。
|
||||
* @param [IN] windowed 是否只描边窗口内的顶点(见 `kHaloWindowHalfWidth` 的说明)。
|
||||
*/
|
||||
void PaintSignalHaloFor(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
const PolylineCacheEntry& cached, float windowCenterX, bool windowed);
|
||||
const PolylineCacheEntry& cached);
|
||||
|
||||
/**
|
||||
* @brief 绘制**悬停**光晕:把整条光晕渲染进一张离屏位图并缓存,命中时只贴图。
|
||||
*
|
||||
* ⚠️ 悬停光晕必须**沿整条曲线**发光(用户 2026-10-18 第二轮报告:上一版只画
|
||||
* 鼠标左右 ±72px 的一段,在满幅方波上看起来就是一个**长方形色块**,像选区框)。
|
||||
* 而整条光晕的直接绘制代价很高(稠密包络带填充实测 ~14ms/帧,加粗折线描边更是
|
||||
* 200ms+/帧)。折中方案:光晕的几何只取决于「信号 + 视图 + 网格几何 + 样式」,
|
||||
* **与鼠标位置无关**,因此渲染一次进离屏位图后每帧只贴图(~0.5ms),
|
||||
* 并随视图/几何/样式变化自动失效重建。
|
||||
*
|
||||
* @param [IN,OUT] g 绘图上下文(调用方已设置绘图区裁剪)。
|
||||
* @param [IN] resolved 已解析的信号。
|
||||
* @param [IN] lane 该信号所在网格。
|
||||
* @param [IN] cached 该信号的折线缓存项。
|
||||
*/
|
||||
void PaintSignalHaloCached(gd::Graphics& g, const ResolvedSignal& resolved,
|
||||
const LaneVisual& lane, const PolylineCacheEntry& cached);
|
||||
|
||||
/**
|
||||
* @brief 悬停光晕的离屏缓存(见 `PaintSignalHaloCached`)。
|
||||
*
|
||||
* 键 = 决定光晕几何的全部输入(与 `AcquirePolyline` 的键同源),
|
||||
* 唯独**不含网格的屏幕纵向位置**:滚动网格时只需按新位置贴同一张位图。
|
||||
*/
|
||||
struct HaloLayerCache
|
||||
{
|
||||
bool valid = false; /**< 位图内容是否有效。 */
|
||||
SignalRef ref; /**< 信号引用。 */
|
||||
const SignalGroup* group = nullptr; /**< 所属分组。 */
|
||||
std::size_t sampleCount = 0; /**< 采样点数。 */
|
||||
double viewMin = 0.0; /**< 时间轴显示下界。 */
|
||||
double viewMax = 0.0; /**< 时间轴显示上界。 */
|
||||
float plotX = 0.0f; /**< 绘图区左边界。 */
|
||||
float plotWidth = 0.0f; /**< 绘图区宽度。 */
|
||||
float laneHeight = 0.0f; /**< 网格高度。 */
|
||||
double laneYMin = 0.0; /**< 网格幅值下界。 */
|
||||
double laneYMax = 0.0; /**< 网格幅值上界。 */
|
||||
float lineWidth = 1.0f; /**< 线宽。 */
|
||||
DrawMode mode = DrawMode::LINE; /**< 绘制方式。 */
|
||||
float gain = 1.0f; /**< 绘制系数。 */
|
||||
bool absolute = false; /**< 是否绝对值。 */
|
||||
bool mirrorVertical = false; /**< 是否垂直镜像。 */
|
||||
bool mirrorHorizontal = false; /**< 是否水平镜像。 */
|
||||
float offsetY = 0.0f; /**< Y 偏移。 */
|
||||
double offsetTime = 0.0; /**< 时间偏移。 */
|
||||
int originX = 0; /**< 位图左上角(屏幕 x)。 */
|
||||
int originY = 0; /**< 位图左上角(屏幕 y)。 */
|
||||
std::unique_ptr<gd::Bitmap> bitmap; /**< 32bpp ARGB 光晕图层(直通 alpha)。 */
|
||||
|
||||
/**
|
||||
* @brief 预乘 alpha 的 DIB 副本(每帧真正贴出去的那张)。
|
||||
*
|
||||
* ⚠️ 每帧贴图必须走 GDI `AlphaBlend` + `AC_SRC_ALPHA`:
|
||||
* `gd::Graphics::DrawImage` 贴一张 32bpp 半透明位图实测约 **6ms**(每帧都付),
|
||||
* 而 `AlphaBlend` 对同一张图约 **0.3ms**。
|
||||
* `AlphaBlend` 要求源位图的 RGB **已按 alpha 预乘**,因此重建时把
|
||||
* `bitmap` 拷进这个 DIB 并顺带做预乘(只付一次)。
|
||||
*/
|
||||
HDC dc = nullptr;
|
||||
HBITMAP dib = nullptr;
|
||||
void* bits = nullptr;
|
||||
int dibWidth = 0;
|
||||
int dibHeight = 0;
|
||||
};
|
||||
|
||||
mutable HaloLayerCache m_haloLayer;
|
||||
|
||||
/** @brief 释放悬停光晕的离屏位图(析构 / 重建尺寸时使用)。 */
|
||||
void ReleaseHaloLayer();
|
||||
|
||||
/**
|
||||
* @brief 把 `m_haloLayer.bitmap` 拷进预乘 alpha 的 DIB(必要时先创建/重建 DIB)。
|
||||
* @param [IN] width 需要的位图宽度。
|
||||
* @param [IN] height 需要的位图高度。
|
||||
* @return DIB 可用返回 true。
|
||||
*/
|
||||
bool UploadHaloLayer(int width, int height);
|
||||
|
||||
/**
|
||||
* @brief 绘制某条信号的悬停/拖拽光晕(折线描边版本)。
|
||||
@@ -2781,12 +2872,9 @@ namespace jdi
|
||||
* @param [IN] lane 该信号所在网格。
|
||||
* @param [IN] points 折线顶点。
|
||||
* @param [IN] breaks 每段折线的起始下标。
|
||||
* @param [IN] windowCenterX 描边窗口中心(屏幕 x);仅当 `windowed` 为 true 时生效。
|
||||
* @param [IN] windowed 是否只描边窗口内的顶点(见 `kHaloWindowHalfWidth` 的说明)。
|
||||
*/
|
||||
void PaintSignalHalo(gd::Graphics& g, const ResolvedSignal& resolved, const LaneVisual& lane,
|
||||
const std::vector<gd::PointF>& points, const std::vector<std::size_t>& breaks,
|
||||
float windowCenterX, bool windowed);
|
||||
const std::vector<gd::PointF>& points, const std::vector<std::size_t>& breaks);
|
||||
|
||||
/**
|
||||
* @brief 单点信号那个数据点绘制时的半径(与散点绘制同源)。
|
||||
|
||||
Reference in new issue
Block a user