Files
Radio/doc/整体架构思路.md
T
2026-07-23 15:31:31 +08:00

1026 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 图表渲染整体架构设计
## 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.<name>.renderScheduleMode
RenderAble.<name>.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
<RenderAble>.prepareData
<RenderAble>.draw
<RenderAble>.stateLeaseMiss
<RenderAble>.bufferAcquireMode
Buffer
Color.mark/swap/busy/swapNoNeed/wait
<RenderAble>.State.mark/swap/busy/swapNoNeed/wait
<RenderAble>.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 主线程。
```