优化
This commit is contained in:
+156
@@ -0,0 +1,156 @@
|
||||
# startRender(int fps) 作用流程 FAQ
|
||||
|
||||
## startRender(int fps) 做了什么
|
||||
|
||||
`startRender(int refreshTimesPreSecond)` 只做两件事:
|
||||
|
||||
```text
|
||||
保存 mRefreshTimesPreSecond
|
||||
调用 startRender()
|
||||
```
|
||||
|
||||
对应代码流程:
|
||||
|
||||
```text
|
||||
Plot::startRender(int fps)
|
||||
↓
|
||||
d->mRefreshTimesPreSecond = fps
|
||||
↓
|
||||
Plot::startRender()
|
||||
```
|
||||
|
||||
## startRender() 做了什么
|
||||
|
||||
`startRender()` 的作用是启动 Plot 的渲染调度。
|
||||
|
||||
流程:
|
||||
|
||||
```text
|
||||
检查 Plot 是否正在析构
|
||||
↓
|
||||
mRenderEnabled = true
|
||||
↓
|
||||
投递任务到 asio scheduler
|
||||
↓
|
||||
取消旧 mRenderTimer
|
||||
↓
|
||||
scheduleRenderTimer()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
它会立即触发一次 `submitRender()`,不是等下一个 timer 周期。
|
||||
|
||||
## fps 参数现在是不是严格帧率上限
|
||||
|
||||
不是。
|
||||
|
||||
当前 `fps` 只决定 asio timer 的周期:
|
||||
|
||||
```text
|
||||
interval = round(1000.0 / fps)
|
||||
```
|
||||
|
||||
timer 到期后:
|
||||
|
||||
```text
|
||||
submitRender()
|
||||
scheduleRenderTimer()
|
||||
```
|
||||
|
||||
所以它的真实语义是周期性 heartbeat / 兜底触发,不是硬性 FPS cap。
|
||||
|
||||
## 为什么不是严格限帧
|
||||
|
||||
因为当前渲染是事件驱动为主。
|
||||
|
||||
除了 timer 会触发 `submitRender()`,下面这些路径也会立即触发:
|
||||
|
||||
```text
|
||||
RenderAble::markStateDirty()
|
||||
↓
|
||||
Plot::markRenderStateDirty()
|
||||
↓
|
||||
requestRender()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
```text
|
||||
RenderData::markInputDirty()
|
||||
↓
|
||||
RenderAble::markRenderDirty()
|
||||
↓
|
||||
requestRender()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
```text
|
||||
finishRender()
|
||||
↓
|
||||
pipeline.hasNewState()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
只要 State/Input 有新版本,系统可以绕过 timer 周期继续推进下一帧。
|
||||
|
||||
## asio 里面还有没有定时器
|
||||
|
||||
有。
|
||||
|
||||
相关成员和函数:
|
||||
|
||||
```text
|
||||
PlotPrivate::mRenderTimer
|
||||
PlotPrivate::mRefreshTimesPreSecond
|
||||
Plot::scheduleRenderTimer()
|
||||
RenderScheduler::initializePlotOnScheduler()
|
||||
```
|
||||
|
||||
`RenderScheduler::initializePlotOnScheduler()` 为每个 Plot 创建 `asio::steady_timer`。
|
||||
|
||||
`scheduleRenderTimer()` 使用 `mRefreshTimesPreSecond` 计算间隔并注册 `async_wait`。
|
||||
|
||||
## timer 在当前架构里还有没有必要
|
||||
|
||||
当前仍然有必要保留。
|
||||
|
||||
原因:
|
||||
|
||||
```text
|
||||
保持 startRender(int) 旧调用语义
|
||||
保证没有显式 dirty 事件时仍有周期性检查
|
||||
作为异常丢事件后的兜底触发
|
||||
给性能测试框提供一个稳定的周期参考
|
||||
```
|
||||
|
||||
但它不是渲染正确性的唯一入口。
|
||||
|
||||
当前正确性主要依赖:
|
||||
|
||||
```text
|
||||
State dirty
|
||||
Input dirty
|
||||
finishRender 后续版本检查
|
||||
```
|
||||
|
||||
## 如果以后要严格限帧应该怎么做
|
||||
|
||||
应该新增独立调度策略,而不是继续复用 `startRender(int)` 的现有语义。
|
||||
|
||||
建议拆成:
|
||||
|
||||
```text
|
||||
Immediate:
|
||||
dirty 后立即 requestRender()
|
||||
|
||||
FixedRate:
|
||||
dirty 后只标记 pending,由 timer 周期统一 submitRender()
|
||||
|
||||
Throttle:
|
||||
dirty 后允许立即渲染,但两帧之间必须满足最小间隔
|
||||
```
|
||||
|
||||
这样 `startRender(int)` 才能变成明确的帧率策略参数。
|
||||
+57
-11
@@ -275,7 +275,8 @@ RenderCache 是 RenderData 的私有成员,不参与三缓冲复制。
|
||||
```text
|
||||
QImage image
|
||||
WaterFallRingBuffer ringBuffer
|
||||
TimeAxis::mRingBuffer
|
||||
TimeAxis::Psc::StreamRingBuffer_ST buffer
|
||||
AudioFrequent::Psc::StreamRingBuffer_ST buffer
|
||||
SweepFrequent::mFrequents
|
||||
Planisphere::dataList
|
||||
Afterglow cachedPowerData / oldCachePowerData / mergedPowerData
|
||||
@@ -443,7 +444,50 @@ Input dirty 不递增 edit_state.version。
|
||||
|
||||
它保证流式输入不会导致 RenderState 全量复制。
|
||||
|
||||
## 10. asio timer 和帧率
|
||||
## 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. asio timer 和帧率
|
||||
|
||||
Plot 仍保留 asio steady_timer:
|
||||
|
||||
@@ -454,6 +498,8 @@ Plot::startRender(int refreshTimesPreSecond)
|
||||
Plot::scheduleRenderTimer()
|
||||
```
|
||||
|
||||
`mRenderTimer` 绑定到全局 RenderScheduler 的 asio::io_context。
|
||||
|
||||
当前 timer 的语义:
|
||||
|
||||
```text
|
||||
@@ -481,7 +527,7 @@ timer 周期触发为辅
|
||||
|
||||
如果后续需要严格限帧,应该增加独立的 render trigger 策略,而不是把 startRender(int) 继续解释成硬性 FPS cap。
|
||||
|
||||
## 11. prepareData 边界
|
||||
## 12. prepareData 边界
|
||||
|
||||
prepareData 的标准流程:
|
||||
|
||||
@@ -514,7 +560,7 @@ prepareData 不应该触发 QWidget 绘制。
|
||||
|
||||
prepareData 不应该把 RenderCache 复制回 State。
|
||||
|
||||
## 12. draw 边界
|
||||
## 13. draw 边界
|
||||
|
||||
draw 的标准流程:
|
||||
|
||||
@@ -532,7 +578,7 @@ draw 不修改 ready_state。
|
||||
|
||||
draw 不做输入队列交换。
|
||||
|
||||
## 13. 数据分类规则
|
||||
## 14. 数据分类规则
|
||||
|
||||
判断一个字段应该放哪里:
|
||||
|
||||
@@ -560,7 +606,7 @@ draw 不做输入队列交换。
|
||||
把配置项放 InputData。
|
||||
```
|
||||
|
||||
## 14. 锁使用边界
|
||||
## 15. 锁使用边界
|
||||
|
||||
radio 渲染链路不允许使用 RenderData 对象级大锁。
|
||||
|
||||
@@ -575,10 +621,10 @@ RenderPipeline:
|
||||
使用 color buffer version / paintRequestPending / editStateVersion。
|
||||
只保护帧发布和 Qt update 请求合并。
|
||||
|
||||
Plot / TimerThread:
|
||||
Plot / RenderScheduler:
|
||||
mRenderEnabled / mDestroying / mActiveRenderTasks / pending render size / paint buffer acquire mode 使用 atomic。
|
||||
mRenderTaskMutex + mRenderTaskDone 只用于 Plot 析构等待已投递任务结束。
|
||||
TimerThread::postAndWait 的局部 mutex + condition_variable 只用于跨线程同步 shutdown/removePlot。
|
||||
RenderScheduler::postAndWait 的局部 mutex + condition_variable 只用于跨线程同步 shutdown/removePlot。
|
||||
|
||||
PerformanceShower:
|
||||
mMutex 只保护统计 map 和显示列表。
|
||||
@@ -606,7 +652,7 @@ prepareData 和 draw 的并发边界由调度流保证:
|
||||
submitRender 进入 asio scheduler。
|
||||
scheduler 串行推进 jobState。
|
||||
prepareData 在 scheduler 阶段消费 State/Input。
|
||||
renderColor 在 CPU 阶段只读取 State_Render 和 RenderCache。
|
||||
renderColor 在 asio thread_pool 阶段只读取 State_Render 和 RenderCache。
|
||||
finishRender 回到 scheduler 发布 Color。
|
||||
```
|
||||
|
||||
@@ -628,7 +674,7 @@ spdlog / asio / googlepinyin:
|
||||
|
||||
这些同步不能向 RenderData、prepareData、draw 路径扩散。
|
||||
|
||||
## 15. 性能统计
|
||||
## 16. 性能统计
|
||||
|
||||
性能测试框不再维护自己的耗时平滑算法。
|
||||
|
||||
@@ -673,7 +719,7 @@ SRTT
|
||||
RTTVAR
|
||||
```
|
||||
|
||||
## 16. 最终规则
|
||||
## 17. 最终规则
|
||||
|
||||
```text
|
||||
State 负责配置快照。
|
||||
|
||||
@@ -26,7 +26,6 @@ QWidget* IntermediateFrequencyWidget::createSpectrogramPowerPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "SpectrumPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||
.set_tickLength(-10)
|
||||
.set_coordRange(frequent_range)
|
||||
@@ -92,7 +91,6 @@ QWidget* IntermediateFrequencyWidget::create_Afterglow_Plot(){
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "AfterglowPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||
.set_coordRange(frequent_range)
|
||||
.set_tickLength(-10)
|
||||
|
||||
@@ -24,7 +24,6 @@ QWidget* RadioWidget::createSpectrogramPowerPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "SpectrumPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
|
||||
|
||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||
@@ -89,7 +88,6 @@ QWidget* RadioWidget::createWaterFallPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "WaterFallPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||
.set_tickLength(-10)
|
||||
.set_subTickLength(-5)
|
||||
@@ -146,7 +144,6 @@ QWidget* RadioWidget::createAudioSpectrogramPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "SpectrumPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
|
||||
|
||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||
@@ -210,7 +207,6 @@ QWidget* RadioWidget::createTimePowerPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "AudioPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
|
||||
xAxis = YSG::TimeAxis::Builder(this, Qt::Horizontal)
|
||||
.set_tickLength(-10)
|
||||
|
||||
@@ -22,7 +22,6 @@ QWidget* SweepFrequencyWidget::createSpectrogramPowerPlot() {
|
||||
void init() override {
|
||||
Plot::init();
|
||||
mObjectName = "AxisTestPlot";
|
||||
bindRenderThread(mObjectName + "Thread");
|
||||
|
||||
|
||||
xAxis = YSG::Axis::Builder(this, Qt::Horizontal)
|
||||
|
||||
@@ -25,7 +25,7 @@ int main(int argc, char *argv[]) {
|
||||
#endif
|
||||
MainWidget w;
|
||||
app.mMainWidget = &w;
|
||||
YSG::startRenderScheduler();
|
||||
w.show();
|
||||
YSG::startAllRenderThread();
|
||||
return QApplication::exec();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user