优化
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
|
```text
|
||||||
QImage image
|
QImage image
|
||||||
WaterFallRingBuffer ringBuffer
|
WaterFallRingBuffer ringBuffer
|
||||||
TimeAxis::mRingBuffer
|
TimeAxis::Psc::StreamRingBuffer_ST buffer
|
||||||
|
AudioFrequent::Psc::StreamRingBuffer_ST buffer
|
||||||
SweepFrequent::mFrequents
|
SweepFrequent::mFrequents
|
||||||
Planisphere::dataList
|
Planisphere::dataList
|
||||||
Afterglow cachedPowerData / oldCachePowerData / mergedPowerData
|
Afterglow cachedPowerData / oldCachePowerData / mergedPowerData
|
||||||
@@ -443,7 +444,50 @@ Input dirty 不递增 edit_state.version。
|
|||||||
|
|
||||||
它保证流式输入不会导致 RenderState 全量复制。
|
它保证流式输入不会导致 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:
|
Plot 仍保留 asio steady_timer:
|
||||||
|
|
||||||
@@ -454,6 +498,8 @@ Plot::startRender(int refreshTimesPreSecond)
|
|||||||
Plot::scheduleRenderTimer()
|
Plot::scheduleRenderTimer()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
`mRenderTimer` 绑定到全局 RenderScheduler 的 asio::io_context。
|
||||||
|
|
||||||
当前 timer 的语义:
|
当前 timer 的语义:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
@@ -481,7 +527,7 @@ timer 周期触发为辅
|
|||||||
|
|
||||||
如果后续需要严格限帧,应该增加独立的 render trigger 策略,而不是把 startRender(int) 继续解释成硬性 FPS cap。
|
如果后续需要严格限帧,应该增加独立的 render trigger 策略,而不是把 startRender(int) 继续解释成硬性 FPS cap。
|
||||||
|
|
||||||
## 11. prepareData 边界
|
## 12. prepareData 边界
|
||||||
|
|
||||||
prepareData 的标准流程:
|
prepareData 的标准流程:
|
||||||
|
|
||||||
@@ -514,7 +560,7 @@ prepareData 不应该触发 QWidget 绘制。
|
|||||||
|
|
||||||
prepareData 不应该把 RenderCache 复制回 State。
|
prepareData 不应该把 RenderCache 复制回 State。
|
||||||
|
|
||||||
## 12. draw 边界
|
## 13. draw 边界
|
||||||
|
|
||||||
draw 的标准流程:
|
draw 的标准流程:
|
||||||
|
|
||||||
@@ -532,7 +578,7 @@ draw 不修改 ready_state。
|
|||||||
|
|
||||||
draw 不做输入队列交换。
|
draw 不做输入队列交换。
|
||||||
|
|
||||||
## 13. 数据分类规则
|
## 14. 数据分类规则
|
||||||
|
|
||||||
判断一个字段应该放哪里:
|
判断一个字段应该放哪里:
|
||||||
|
|
||||||
@@ -560,7 +606,7 @@ draw 不做输入队列交换。
|
|||||||
把配置项放 InputData。
|
把配置项放 InputData。
|
||||||
```
|
```
|
||||||
|
|
||||||
## 14. 锁使用边界
|
## 15. 锁使用边界
|
||||||
|
|
||||||
radio 渲染链路不允许使用 RenderData 对象级大锁。
|
radio 渲染链路不允许使用 RenderData 对象级大锁。
|
||||||
|
|
||||||
@@ -575,10 +621,10 @@ RenderPipeline:
|
|||||||
使用 color buffer version / paintRequestPending / editStateVersion。
|
使用 color buffer version / paintRequestPending / editStateVersion。
|
||||||
只保护帧发布和 Qt update 请求合并。
|
只保护帧发布和 Qt update 请求合并。
|
||||||
|
|
||||||
Plot / TimerThread:
|
Plot / RenderScheduler:
|
||||||
mRenderEnabled / mDestroying / mActiveRenderTasks / pending render size / paint buffer acquire mode 使用 atomic。
|
mRenderEnabled / mDestroying / mActiveRenderTasks / pending render size / paint buffer acquire mode 使用 atomic。
|
||||||
mRenderTaskMutex + mRenderTaskDone 只用于 Plot 析构等待已投递任务结束。
|
mRenderTaskMutex + mRenderTaskDone 只用于 Plot 析构等待已投递任务结束。
|
||||||
TimerThread::postAndWait 的局部 mutex + condition_variable 只用于跨线程同步 shutdown/removePlot。
|
RenderScheduler::postAndWait 的局部 mutex + condition_variable 只用于跨线程同步 shutdown/removePlot。
|
||||||
|
|
||||||
PerformanceShower:
|
PerformanceShower:
|
||||||
mMutex 只保护统计 map 和显示列表。
|
mMutex 只保护统计 map 和显示列表。
|
||||||
@@ -606,7 +652,7 @@ prepareData 和 draw 的并发边界由调度流保证:
|
|||||||
submitRender 进入 asio scheduler。
|
submitRender 进入 asio scheduler。
|
||||||
scheduler 串行推进 jobState。
|
scheduler 串行推进 jobState。
|
||||||
prepareData 在 scheduler 阶段消费 State/Input。
|
prepareData 在 scheduler 阶段消费 State/Input。
|
||||||
renderColor 在 CPU 阶段只读取 State_Render 和 RenderCache。
|
renderColor 在 asio thread_pool 阶段只读取 State_Render 和 RenderCache。
|
||||||
finishRender 回到 scheduler 发布 Color。
|
finishRender 回到 scheduler 发布 Color。
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -628,7 +674,7 @@ spdlog / asio / googlepinyin:
|
|||||||
|
|
||||||
这些同步不能向 RenderData、prepareData、draw 路径扩散。
|
这些同步不能向 RenderData、prepareData、draw 路径扩散。
|
||||||
|
|
||||||
## 15. 性能统计
|
## 16. 性能统计
|
||||||
|
|
||||||
性能测试框不再维护自己的耗时平滑算法。
|
性能测试框不再维护自己的耗时平滑算法。
|
||||||
|
|
||||||
@@ -673,7 +719,7 @@ SRTT
|
|||||||
RTTVAR
|
RTTVAR
|
||||||
```
|
```
|
||||||
|
|
||||||
## 16. 最终规则
|
## 17. 最终规则
|
||||||
|
|
||||||
```text
|
```text
|
||||||
State 负责配置快照。
|
State 负责配置快照。
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ QWidget* IntermediateFrequencyWidget::createSpectrogramPowerPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "SpectrumPlot";
|
mObjectName = "SpectrumPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||||
.set_tickLength(-10)
|
.set_tickLength(-10)
|
||||||
.set_coordRange(frequent_range)
|
.set_coordRange(frequent_range)
|
||||||
@@ -92,7 +91,6 @@ QWidget* IntermediateFrequencyWidget::create_Afterglow_Plot(){
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "AfterglowPlot";
|
mObjectName = "AfterglowPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||||
.set_coordRange(frequent_range)
|
.set_coordRange(frequent_range)
|
||||||
.set_tickLength(-10)
|
.set_tickLength(-10)
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ QWidget* RadioWidget::createSpectrogramPowerPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "SpectrumPlot";
|
mObjectName = "SpectrumPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
|
|
||||||
|
|
||||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||||
@@ -89,7 +88,6 @@ QWidget* RadioWidget::createWaterFallPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "WaterFallPlot";
|
mObjectName = "WaterFallPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||||
.set_tickLength(-10)
|
.set_tickLength(-10)
|
||||||
.set_subTickLength(-5)
|
.set_subTickLength(-5)
|
||||||
@@ -146,7 +144,6 @@ QWidget* RadioWidget::createAudioSpectrogramPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "SpectrumPlot";
|
mObjectName = "SpectrumPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
|
|
||||||
|
|
||||||
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::FrequentAxis::Builder(this, Qt::Horizontal)
|
||||||
@@ -210,7 +207,6 @@ QWidget* RadioWidget::createTimePowerPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "AudioPlot";
|
mObjectName = "AudioPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
|
|
||||||
xAxis = YSG::TimeAxis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::TimeAxis::Builder(this, Qt::Horizontal)
|
||||||
.set_tickLength(-10)
|
.set_tickLength(-10)
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ QWidget* SweepFrequencyWidget::createSpectrogramPowerPlot() {
|
|||||||
void init() override {
|
void init() override {
|
||||||
Plot::init();
|
Plot::init();
|
||||||
mObjectName = "AxisTestPlot";
|
mObjectName = "AxisTestPlot";
|
||||||
bindRenderThread(mObjectName + "Thread");
|
|
||||||
|
|
||||||
|
|
||||||
xAxis = YSG::Axis::Builder(this, Qt::Horizontal)
|
xAxis = YSG::Axis::Builder(this, Qt::Horizontal)
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ int main(int argc, char *argv[]) {
|
|||||||
#endif
|
#endif
|
||||||
MainWidget w;
|
MainWidget w;
|
||||||
app.mMainWidget = &w;
|
app.mMainWidget = &w;
|
||||||
|
YSG::startRenderScheduler();
|
||||||
w.show();
|
w.show();
|
||||||
YSG::startAllRenderThread();
|
|
||||||
return QApplication::exec();
|
return QApplication::exec();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user