# 图表渲染整体架构设计 ## 1. 总体模型 每个 RenderAble 拆成四类数据: ```text State 三缓冲 Input 三缓冲 Render 私有缓存 Color 三缓冲 ``` 职责边界: ```text State: 小型、可复制、表示显示配置和稳定状态。 Input: 外部输入到渲染线程的增量数据。 RenderCache: 渲染侧长期缓存,不复制,不参与状态交换。 Color: 后台生成的像素图,交给 Qt 主线程贴图。 ``` 核心原则: ```text 配置走 State。 样本走 Input。 缓存留在 RenderData 私有成员。 像素走 Color。 ``` 不能把流式样本、环形缓冲、累计矩阵塞进 State。 State 的同步语义是复制。 Input 的同步语义是角色交换。 RenderCache 的语义是渲染侧所有权。 ## 2. 整体流程 ```text 用户操作 / 属性修改 ↓ 修改 edit_state ↓ state edit -> ready 复制 ↓ state ready -> render 交换 ``` ```text 数据输入 / giveData ↓ 追加到 edit_input ↓ input edit -> ready 交换 ↓ input ready -> render 交换 ↓ prepareData 消费 render_input ↓ 更新 RenderCache ↓ clear render_input ``` ```text draw ↓ 读取 render_state ↓ 读取 RenderCache ↓ 写 render_color ↓ color render -> ready 交换 ↓ Qt paintEvent 消费 ready_color 到 front_color ``` 完整数据链路: ```text edit_state + edit_input ↓ prepareData render_state + render_input ↓ RenderCache ↓ render_color ↓ ready_color ↓ front_color ``` ## 3. 三缓冲控制器 State、Input、Color 的角色交换都使用同一个控制器: ```text Triple_Role_Buffer_Control ``` 控制器只负责: ```text role -> physical index busy 标记 lease swap_role ``` 它不理解实际数据类型。 实际数据可以是: ```text RenderState InputData ColorBuffer ``` 控制器语义: ```text slot_state 管角色映射。 busy 管物理占用。 lease 管一次具体使用。 swap_role 只交换角色,不复制业务数据。 ``` 长时间使用某个角色时,必须释放 lease,不允许按 role 释放。 因为使用期间 role 可能已经被交换。 ## 4. State 三缓冲 State 有三个角色: ```text State_Edit State_Ready State_Render ``` State 里只放: ```text 坐标轴指针 范围 颜色 画笔 字体 hover 状态 marker 状态 模式开关 点数配置 ``` State 里不放: ```text 输入样本列表 环形缓冲区 QImage 累计矩阵 曲线当前数据缓存 WaterFall 行缓存 Afterglow 能量累计缓存 ``` State 同步流程: ```text 如果 edit_state.version 新于 ready_state.version: mark ready_state copy edit_state -> ready_state unmark ready_state 如果 ready_state.version 新于 render_state.version: swap ready_state, render_state ``` State 的核心意义: ```text 把可复制配置从编辑侧稳定发布给渲染侧。 跳过中间状态。 保证 CPU 绘制时读取稳定 render_state。 ``` ## 5. Input 三缓冲 Input 有三个角色: ```text Input_Edit Input_Ready Input_Render ``` InputData 里放增量输入: ```text WaterFall 新行 AudioFrequent 新功率点 TimeAxis 新时间点 SweepFrequent 新块数据 Planisphere 新星座点 Spectrum 新曲线样本 Afterglow 新功率行 ``` 输入线程只追加到 edit_input。 追加成功后: ```text input_slot.version = ++mInputVersion input_slot.pending = true requestRender() ``` Input 同步流程: ```text 如果 edit_input.pending 且 edit_input.version 新于 ready_input.version: swap edit_input, ready_input 如果 ready_input.pending 且 ready_input.version 新于 render_input.version: swap ready_input, render_input ``` prepareData 消费流程: ```text auto input = renderInputData() for each delta in input: 更新 RenderCache clearRenderInput() ``` clearRenderInput 必须同时完成: ```text 清空 render_input 数据 render_input.pending = false ``` Input 三缓冲的核心意义: ```text 输入数据不复制到 State。 输入数据不触发 RenderState 大对象复制。 渲染侧按批消费增量。 edit_input 可以在下一轮继续接收新数据。 ``` ## 6. Render 私有缓存 RenderCache 是 RenderData 的私有成员,不参与三缓冲复制。 典型对象: ```text QImage image WaterFallRingBuffer ringBuffer TimeAxis::Psc::StreamRingBuffer_ST buffer AudioFrequent::Psc::StreamRingBuffer_ST buffer SweepFrequent::mFrequents Planisphere::dataList Afterglow cachedPowerData / oldCachePowerData / mergedPowerData Spectrum curPowers / maxPowers / minPowers / frequents ``` RenderCache 的来源: ```text render_state 配置 render_input 增量 ``` RenderCache 的更新位置: ```text prepareData() ``` RenderCache 的读取位置: ```text draw() ``` RenderCache 不应该由属性 setter 直接修改。 属性 setter 只改 edit_state。 数据输入函数只追加 edit_input。 RenderAble 可以选择开启独立像素缓存: ```text RenderAble::setIndependentPixelCache(true) ``` 开启后: ```text 该 RenderAble 的 markRenderDirty 不再触发整张 Plot renderColor。 该 RenderAble 单独 prepare/draw 到自己的透明 QImage。 Plot::paintEvent 在绘制 Plot 级 ColorBuffer 后叠加独立像素缓存。 ``` 默认不开启。 它适合: ```text PerformanceShower 轻量 overlay 高频变化但不应该触发整图重绘的辅助层 ``` ## 7. Color 三缓冲 Color 有三个角色: ```text Color_Front Color_Ready Color_Render ``` Color_Render: ```text CPU 后台线程写入。 ``` Color_Ready: ```text 后台完成后等待 Qt 主线程消费。 ``` Color_Front: ```text Qt paintEvent 当前显示。 ``` 发布流程: ```text render_color.version 新于 ready_color.version: swap ready_color, render_color ready_color.version 新于 front_color.version: request update ``` paintEvent 流程: ```text 按 Plot_Buffer_Acquire_Mode 消费 ready_color mark front_color drawImage(front_color) unmark front_color ``` Plot_Buffer_Acquire_Mode 有两种策略: ```text Try: paintEvent 尝试交换 ready_color -> front_color。 如果物理 buffer 正忙,立即放弃本次新帧消费。 这是默认模式,GUI 线程不进入 atomic_wait。 Wait: paintEvent 等待参与交换或 front_color lease 可用。 只适合调用方明确接受 GUI 线程短暂等待的场景。 ``` Color 发布侧仍然可以等待 Color_Render / Color_Ready,因为它运行在调度和后台渲染流程,不阻塞 Qt paintEvent。 ## 8. 三缓冲获取策略 三缓冲获取策略不是单一全局开关,而是分层控制。 Plot 层: ```text Plot_Buffer_Acquire_Mode ``` 只控制 Qt paintEvent 消费 Color_Ready / Color_Front 的行为。 默认: ```text Try ``` RenderAble 层: ```text Plot_Buffer_Acquire_Mode ``` 控制单个 RenderAble 在后台 draw 阶段获取 State_Render lease 的行为。 默认: ```text Wait ``` RenderAble 设置为 Try 后: ```text draw 阶段取不到 State_Render lease ↓ 跳过该 RenderAble 本帧绘制 ``` State/Input 的编辑发布路径默认仍使用 Wait。 原因: ```text setter / giveData 当前多数是 void 语义。 Try 失败后没有统一返回值表达本次修改是否被接受。 在没有重新设计 API 返回语义前,不允许静默丢弃编辑和输入。 ``` Plot 还保存一个 RenderAble 默认获取策略: ```text Plot::renderAbleBufferAcquireMode() Plot::setRenderAbleBufferAcquireMode() ``` RenderAble init 到 Plot 时会复制父 Plot 当前的默认策略。 之后单个 RenderAble 可以独立调用: ```text RenderAble::bufferAcquireMode() RenderAble::setBufferAcquireMode() ``` ## 9. 调度版本 渲染调度使用 RenderPipeline 的帧版本。 State dirty 和 Input dirty 都会推进帧版本。 区别: ```text State dirty: edit_state.version++ frame_version++ Input dirty: input_version++ frame_version++ ``` Input dirty 不递增 edit_state.version。 这点非常重要。 它保证流式输入不会导致 RenderState 全量复制。 ## 10. RenderScheduler 渲染调度使用一个全局 RenderScheduler。 RenderScheduler 由两部分组成: ```text 单线程 asio::io_context asio::thread_pool ``` 单线程 asio::io_context 负责: ```text Plot 注册/移除 resize 状态写入 timer async_wait submitRender prepareData finishRender jobState 推进 Color 发布 ``` asio::thread_pool 负责: ```text renderColor draw CPU 像素绘制 ``` 不允许再引入: ```text TimerThread 按 Plot 名称绑定渲染线程 业务层 bindRenderThread 多个 QThread scheduler ``` 所有 Plot 共用一个 scheduler。 ## 11. 渲染管线和调度策略 渲染拆成两层: ```text RenderPipeline: 管 State/Input/Color 三缓冲、jobState、一次提交。 RenderSchedulePolicy: 管什么时候触发一次提交。 ``` 核心边界: ```text submitRenderOnce() ``` 只负责: ```text 当前是否可以提交一帧。 如果可以,准备数据并投递 CPU render。 成功返回 true。 失败返回 false。 ``` 它不负责: ```text 限速 是否追帧 是否响应 timer finish 后是否继续 ``` 这些全部属于调度策略。 Plot 级调度模式: ```text Plot_Render_Schedule_Mode::Timer_Check Plot_Render_Schedule_Mode::Chase_Latest Plot_Render_Schedule_Mode::Max_Fps Plot_Render_Schedule_Mode::Manual ``` 语义: ```text Timer_Check: 只响应 asio timer。 dirty 只推进版本,不立即提交。 Chase_Latest: dirty、timer、finish、manual 都会尝试提交。 finish 后如果仍有新状态,会继续追最新帧。 Max_Fps: dirty 不丢弃。 如果没到下一帧允许提交时间,只记录 pending 并安排 mSubmitTimer。 到点后只提交最新状态。 Manual: 只响应 Render_Request_Source::Manual。 ``` 请求来源: ```text Render_Request_Source::Timer Render_Request_Source::Dirty Render_Request_Source::Finish Render_Request_Source::Manual ``` `startRender(int fps)` 的语义改成: ```text 启动渲染调度。 设置 timer 检查频率。 是否按这个 fps 限制实际 render,由 Plot_Render_Schedule_Mode 决定。 ``` 严格限帧使用: ```text Plot_Render_Schedule_Mode::Max_Fps Plot::setMaxRenderFps() ``` asio timer 分成两个: ```text mRenderTimer: 周期性产生 Render_Request_Source::Timer。 mSubmitTimer: Max_Fps 或 RenderAble 局部 Max_Fps 的延迟提交唤醒。 ``` RenderAble 级策略: ```text RenderAble_Render_Schedule_Mode::Inherit RenderAble_Render_Schedule_Mode::Chase_Latest RenderAble_Render_Schedule_Mode::Max_Fps RenderAble_Render_Schedule_Mode::On_Dirty RenderAble_Render_Schedule_Mode::Manual ``` RenderAble 默认: ```text Inherit ``` RenderAble 局部策略控制: ```text 本轮是否把该 RenderAble 的 State/Input 推进到 render 侧。 ``` 如果一个 RenderAble 没通过自己的策略闸门: ```text prepareData 不执行。 render_state / RenderCache 保持上一帧。 draw 仍读取上一帧已经发布的 render 侧数据。 ``` 这样一个高频对象不会强迫静态对象每帧同步 State/Input。 独立像素缓存对象也走同一套 RenderAble 策略。 PerformanceShower 可以观测: ```text Pipeline.renderScheduleMode Pipeline.renderCheckFps Pipeline.maxRenderFps Pipeline.renderSubmitPending RenderAble..renderScheduleMode RenderAble..renderScheduleMaxFps ``` 右键菜单的 Plot 基础栏提供: ```text 渲染策略下拉框 渲染检查FPS 最大渲染FPS ``` ## 12. prepareData 边界 prepareData 的标准流程: ```text syncStatePipeline() 读取 render_state 配置 读取 render_input 增量 更新 RenderCache clearRenderInput() ``` syncStatePipeline 内部同时处理: ```text State edit -> ready -> render Input edit -> ready -> render ``` prepareData 不再持有 RenderData 对象级锁。 edit_state 写入必须通过 State_Edit lease。 edit_input 写入必须通过 Input_Edit lease。 RenderCache 只允许 render 流程内部更新。 prepareData 不应该写 Color。 prepareData 不应该触发 QWidget 绘制。 prepareData 不应该把 RenderCache 复制回 State。 ## 13. draw 边界 draw 的标准流程: ```text 读取 render_state 读取 RenderCache 写入当前 render_color 对应的 QPainter ``` draw 不消费 Input。 draw 不修改 edit_state。 draw 不修改 ready_state。 draw 不做输入队列交换。 ## 14. 数据分类规则 判断一个字段应该放哪里: ```text 需要被属性 getter/setter 表达,体积小,可复制: 放 State。 外部不断输入,prepareData 批量消费: 放 InputData。 由 render_state + render_input 计算出来,长期保留给 draw 使用: 放 RenderData 私有缓存。 最终像素图: 放 ColorBuffer。 ``` 错误分类: ```text 把 QVector 大缓存放 State。 把 QImage 放 State。 把输入 list 放 State。 把 RenderCache 放 InputData。 把配置项放 InputData。 ``` ## 15. 锁使用边界 radio 渲染链路不允许使用 RenderData 对象级大锁。 允许存在的同步只有下面几类: ```text Triple_Role_Buffer_Control: 使用 atomic busy / slot_state / wait / notify。 只保护物理 buffer lease 和 role 交换。 RenderPipeline: 使用 color buffer version / paintRequestPending / editStateVersion。 只保护帧发布和 Qt update 请求合并。 Plot / RenderScheduler: mRenderEnabled / mDestroying / mActiveRenderTasks / pending render size / paint buffer acquire mode 使用 atomic。 mRenderTaskMutex + mRenderTaskDone 只用于 Plot 析构等待已投递任务结束。 RenderScheduler::postAndWait 的局部 mutex + condition_variable 只用于跨线程同步 shutdown/removePlot。 PerformanceShower: 本身是 RenderAble。 统计写入走 Input 三缓冲。 统计 map 和显示列表是 Render 私有缓存。 计数使用 CPP_Core/Core/Statistics::Speed_Statistics。 耗时使用 CPP_Core/Core/Statistics::Value_Statistics。 Value_Statistics 提供 instant / smooth / variation。 开启独立像素缓存,不进入 Plot 整图 renderColor。 统计写入按观察者节流刷新,不允许每条事件触发整图 dirty。 ``` 不允许重新引入: ```text RenderData::mBufferLock prepareData 全局锁 render size mutex Graphic resize mutex HoverInfo 独立 mutex PrePareMutiDataMutex SpinLock / SpinLockGuard Singleton mutex + atomic 双重检查 ``` prepareData 和 draw 的并发边界由调度流保证: ```text submitRender 进入 asio scheduler。 scheduler 串行推进 jobState。 prepareData 在 scheduler 阶段消费 State/Input。 renderColor 在 asio thread_pool 阶段只读取 State_Render 和 RenderCache。 finishRender 回到 scheduler 发布 Color。 ``` CPP_Core 和第三方库内部同步不属于 radio 渲染架构边界: ```text RingBuffer_MT / StreamRingBuffer_MT: 通用多线程容器版本。 SM_RingBuffer / Cross_Process_Mutex: 跨进程共享内存同步。 Frequency_Limit: 通用频率限制器。 spdlog / asio / googlepinyin: 第三方或通用库内部同步。 ``` 这些同步不能向 RenderData、prepareData、draw 路径扩散。 ## 16. 性能统计 性能测试框是 RenderAble,不再是 QWidget。 开启和停止性能测试只修改 PerformanceShower 的 enabled 状态。 停止性能测试不删除 PerformanceShower 对象。 原因: ```text render/scheduler/cpu 线程可能持有 Cacl。 Cacl 需要在析构时写统计。 如果停止性能测试时删除 QWidget,会形成跨线程悬挂指针。 ``` 统计写入走 Input 三缓冲: 统计类型: ```text Duration: 阶段耗时,使用 Psc::Value_Statistics。 Counter: 事件频率,使用 Psc::Speed_Statistics。 Gauge: 当前数值状态。 State: 当前文本状态。 ``` 统计链路: ```text PerformanceShower::recordDuration / incrementCounter / setGauge / setState ↓ PerformanceShowerInputData events ↓ prepareData() ↓ PerformanceShowerPrivate::mScopes ↓ draw() ``` Value_Statistics 的含义: ```text instant: 本次采样值。 average: 保留旧字段语义的 EWMA 平均值。 smooth: 类似 SRTT 的平滑值。 variation: 类似 RTTVAR 的波动估计。 它不是严格方差或标准差,而是 instant 与 smooth 偏差的 EWMA。 p95: 最近 64 次采样的 95 分位估计。 ``` 性能框当前分组: ```text Pipeline: schedulerQueueWait requestToSubmit cpuQueueWait prepareData renderColor draw publishRenderColor updateToPaint paintEvent jobState activeRenderTasks renderEnabled paintBufferAcquireMode renderAbleDefaultAcquireMode paintRequestPending editStateVersion renderStateVersion RenderAble: .prepareData .draw .stateLeaseMiss .bufferAcquireMode Buffer: Color.mark/swap/busy/swapNoNeed/wait .State.mark/swap/busy/swapNoNeed/wait .Input.mark/swap/busy/swapNoNeed/wait ``` 性能框显示策略: ```text 默认视图: Plot Plot 视图显示: Pipeline General Color buffer 左侧栏显示: 一级:Plot / 每个有统计数据的 RenderAble 二级:当前对象拥有的数据面 Plot 的二级数据面: Pipeline General Color RenderAble 的二级数据面: RenderAble State Input 内容区: 不显示 [Pipeline] / [Buffer] / [RenderAble] 这种分组标题。 当前层级已经表达数据归属。 指标名会去掉已由层级表达的对象和缓冲前缀。 内容过长时显示滚动条,鼠标滚轮滚动内容区。 布局宽度策略: 左侧两级栏宽只在栏项目变化时重算。 右侧指标内容宽只在切换视图时重算。 ``` PerformanceShower 的刷新策略: ```text 统计事件: 写入 PerformanceShower Input。 不逐条 markRenderDirty。 观察者刷新: 普通统计事件最多 10Hz 触发一次独立像素缓存刷新。 Clear / 选择视图 / 滚动 立即刷新。 排除项: PerformanceShower 自身不进入 RenderAble 性能统计列表。 ``` Pull snapshot 的含义: ```text Push gauge: 每帧主动把每个 RenderAble 的每个 Buffer 统计写成事件。 会制造大量 PerformanceShower Input。 Pull snapshot: Buffer 控制器只维护 atomic 计数。 性能面板刷新时按需读取当前快照。 没有面板刷新就不产生统计事件。 ``` 完整性能面板最终应该使用 QWidget: ```text Overlay: 只显示摘要。 右键菜单 QWidget 面板: 显示完整 Pipeline / RenderAble / Buffer 树。 使用低频 pull snapshot 刷新。 ``` ## 17. 最终规则 ```text State 负责配置快照。 Input 负责增量输入交接。 RenderCache 负责渲染侧长期缓存。 Color 负责像素交接。 ``` 一句话: ```text 每个 RenderAble 使用 State 三缓冲发布小状态,使用 Input 三缓冲发布增量输入,使用 RenderData 私有缓存保存重型渲染中间结果,使用 Color 三缓冲把后台像素图交给 Qt 主线程。 ```