32 KiB
Renderive 实时渲染调度与三帧反馈设计
1. 文档目标
本文定义 Renderive 实时图表的生产、发布、消费、反馈和限流模型。
该设计面向以下场景:
- 核心数据持续更新;
- 后台线程生成完整图像帧;
- Qt 主线程通过
paintEvent()消费帧; - 系统采用三个可循环复用的帧槽位;
- 默认追求低延迟和最新状态;
- 不要求每个输入版本都被渲染;
- 不要求每个已生成帧都被显示;
- 需要根据输入、生产和消费能力自动调整生产频率;
- 反馈路径不得引入热路径锁竞争(反馈数据直接记录在帧槽位自身的元信息中,并随帧槽位所有权无锁返回);
默认语义为:
Data_Driven 数据驱动
Latest_Wins 最新数据优先
Triple_Frame 三帧机制
Bounded_In_Flight 有界在途帧
Max_Render_Rate 最大渲染频率限制
Lock_Free_Frame_Feedback 无锁帧反馈
2. 核心目标
系统需要满足以下目标:
- 核心数据变化后尽快生成新帧;
- 数据输入、后台生产和前端消费可以并行;
- 输入过快时合并中间版本;
- 生产者过慢时不积压渲染任务;
- 消费者过慢时不积压历史帧;
- 用户可以设置最大生产速率;
- 生产速率根据消费反馈自动调节;
- 生产能力不足时由完成事件自时钟运行;
- 所有阶段的耗时和排队信息随帧流转;
- 帧返回生产侧时完成统计采集;
- 热路径不创建字符串、动态事件对象或无界队列;
- 控制器避免瞬时样本导致频繁振荡。
3. 基本原则
3.1 Latest_Wins(最新数据优先)
系统只关心当前最新有效状态。
当后台正在渲染版本 100,而输入端连续产生:
101
102
103
104
下一帧直接选择版本 104。
未开始渲染的 101、102、103 被合并,不进入待执行渲染任务队列。
3.2 Bounded_In_Flight(有界在途帧)
系统只允许三个帧槽位参与流水线:
Triple_Frame 三帧机制
├── Frame_Slot_0 第一帧槽位
├── Frame_Slot_1 第二帧槽位
└── Frame_Slot_2 第三帧槽位
三个帧槽位结构完全相同,通过 Frame_State 动态表示空闲、生产、就绪和消费状态,不绑定固定物理角色。
同一时刻最多存在:
一个 Producing 状态帧槽位 生产端正在写入
一个 Ready 状态帧槽位 等待消费者取得
一个 Consuming 状态帧槽位 消费者正在读取
不建立大容量历史帧 FIFO。
3.3 Event_Driven(事件驱动)
调度器不依赖持续轮询。
核心事件包括:
Input_Changed 输入数据已更新
Render_Completed 渲染生产已完成
Frame_Consumed 帧已消费
Rate_Limit_Deadline_Reached 频率限制截止时间已到达
Control_Updated 控制参数已更新
3.4 State_Decides_Now,Statistics_Decide_Trend(状态决定当前,统计决定趋势)
即时行为由当前状态决定:
是否有未渲染的新数据
是否正在渲染
是否存在 Ready 状态帧槽位
消费者是否正在使用帧
是否达到最大生产速率允许时间
当前策略是否允许替代旧的 Ready 状态帧
长期调节由平滑统计决定:
输入间隔
生产耗时
消费间隔
GUI 排队时间
Ready 状态帧等待时间
帧年龄
覆盖率
断供率
4. 命名约定
所有状态、事件、策略和字段使用下划线命名。
4.2 瓶颈状态
Bottleneck_State 瓶颈状态
├── Unknown 未知
├── Input_Limited 输入受限
├── Producer_Limited 生产端受限
├── Consumer_Limited 消费端受限
├── Manual_Limited 手动上限受限
└── Balanced 均衡状态
4.3 调度事件
Scheduler_Event 调度事件
├── Input_Changed 输入数据已更新
├── Render_Completed 渲染生产已完成
├── Frame_Consumed 帧已消费
├── Rate_Limit_Deadline_Reached 频率限制截止时间已到达
└── Control_Updated 控制参数已更新
4.4 三帧槽位
三帧机制不是三个具有固定角色的缓冲区,而是三个结构完全相同、可循环复用的帧槽位:
Triple_Frame 三帧机制
├── Frame_Slot_0 第一帧槽位
├── Frame_Slot_1 第二帧槽位
└── Frame_Slot_2 第三帧槽位
每个帧槽位都是一份完整帧记录:
Frame_Slot 帧槽位
├── Frame_Data 帧数据
├── Frame_Metadata 帧元信息
└── Frame_State 帧生命周期状态
其中:
Frame_Data:当前帧实际使用的数据,例如渲染结果、绘制命令、顶点数据、索引数据、实例数据或场景快照;Frame_Metadata:该帧自身携带的版本、时间、耗时、工作量和反馈信息;Frame_State:该帧槽位当前所处的生命周期状态,并表示当前所有权阶段。
帧槽位状态为:
Frame_State 帧生命周期状态
├── Free 空闲
├── Producing 生产中
├── Ready 已就绪
└── Consuming 消费中
状态流转为:
Free 空闲
↓
Producing 生产中
↓
Ready 已就绪
↓
Consuming 消费中
↓
Free 空闲
Returned_Frame 可以描述消费者刚完成并归还的帧:
Returned_Frame 已归还帧
Returned_Frame 只是所有权交接过程中的瞬时逻辑角色,不是第四个物理帧槽位,也不必成为持久的 Frame_State。
5. 系统角色
5.1 数据输入端
数据输入端负责接收所有会改变最终图像的状态:
核心数据
坐标范围
图表尺寸
样式属性
布局状态
交互状态
可见性状态
其他渲染依赖
每次有效变化更新:
latest_input_version
数据输入端只保存当前最新状态,不为每个版本创建独立渲染任务。
需要区分:
原始提交次数
有效核心数据变化次数
只有会改变最终图像的有效变化才参与输入速率统计。
5.2 生产端
生产端负责一张帧的完整生成过程:
收集一致快照
→ 准备绘制数据
→ 执行颜色缓冲绘制
→ 填写生产阶段元数据
→ 发布完整帧
同一时间只允许一个完整帧生产任务运行。
生产完成后发出:
Render_Completed 渲染生产已完成
生产端不直接决定是否继续生产下一帧。
5.3 消费端
消费端对应 Qt 主线程中的 paintEvent()。
消费端负责:
取得最新完整帧
记录实际消费版本
记录 paintEvent 开始时间
执行图像显示
记录 paintEvent 结束时间
释放当前 `Consuming` 状态帧槽位
消费端只上报事实,不直接控制后台生产线程。
paintEvent() 结束后不会无条件再次调用 update()。
5.4 Plot 调度器
Plot 调度器是唯一的全局决策者。
核心状态包括:
latest_input_version
rendering_input_version
published_input_version
consumed_input_version
render_busy
ready_valid
consumer_busy
update_pending
next_render_time
manual_min_render_interval
adaptive_min_render_interval
production_policy
bottleneck_state
调度器负责决定:
立即开始生产
等待最大速率期限
等待消费者释放
请求一次 QWidget::update()
保持等待并继续合并输入
6. 三帧生命周期
三个帧槽位结构完全相同,不预先绑定生产端、就绪区或消费端。
每个帧槽位都按照以下状态循环:
Frame_State 帧生命周期状态
├── Free 空闲,可由生产端取得
├── Producing 生产中,由生产端独占写入
├── Ready 已就绪,等待消费者取得
└── Consuming 消费中,由消费者独占读取
正常状态流转为:
Free 空闲
→ Producing 生产中
→ Ready 已就绪
→ Consuming 消费中
→ Free 空闲
任意时刻,三个帧槽位可以分别处于不同状态,例如:
Frame_Slot_0 Consuming 正在显示 Frame_N
Frame_Slot_1 Ready 保存 Frame_N_Plus_1
Frame_Slot_2 Producing 正在生成 Frame_N_Plus_2
下一时刻,各槽位可以继续交换状态:
Frame_Slot_0 Free 已完成消费并归还
Frame_Slot_1 Consuming 消费者正在显示
Frame_Slot_2 Ready 新帧已经发布
三帧机制不保存长历史帧序列。
处于 Ready 状态的帧槽位表示当前最新可消费帧,不表示 FIFO 中下一张必须显示的历史帧。
生产、就绪和消费是帧槽位的动态生命周期状态,不是三个物理帧槽位的固定身份。
7. Frame_Metadata(帧元信息)
每个帧槽位携带固定大小的 Frame_Metadata。
Frame_Metadata 帧元信息
├── frame_id 帧唯一标识
├── input_version 核心输入数据版本
├── snapshot_version 完整状态快照版本
├── frame_slot_generation 帧槽位复用代次
├── core_data_time 核心数据时间
├── source_data_time_begin 最早有效源数据时间
├── source_data_time_end 最新有效源数据时间
├── snapshot_capture_begin_time 快照采集开始时间
├── snapshot_capture_end_time 快照采集结束时间
├── render_request_time 调度器批准生产时间
├── render_queue_enter_time 渲染任务进入后台队列时间
├── render_begin_time 后台线程开始执行时间
├── prepare_begin_time 绘制数据准备开始时间
├── prepare_end_time 绘制数据准备结束时间
├── draw_begin_time 最终绘制开始时间
├── draw_end_time 最终绘制结束时间
├── render_end_time 完整帧生产结束时间
├── publish_time 帧发布为 Ready 状态的时间
├── update_request_id GUI 更新请求标识
├── update_request_time GUI 更新请求入队时间
├── paint_begin_time paintEvent 开始时间
├── paint_end_time paintEvent 结束时间
├── consumer_release_time 消费者释放帧槽位时间
├── superseded_time 帧被更新帧替代的时间
├── frame_outcome 帧最终结果
├── raw_input_count 原始输入数量
├── visible_input_count 可见输入数量
├── prepared_output_count 准备后的输出数量
├── draw_point_count 绘制点数量
├── draw_segment_count 绘制线段数量
├── pixel_width 帧像素宽度
├── pixel_height 帧像素高度
└── renderable_count 可渲染对象数量
Frame_Metadata 与帧数据一起经历:
生产
→ 发布
→ 消费
→ 返回
→ 聚合统计
→ 清理
→ 下一代复用
热路径中不通过独立动态事件对象传递帧级统计。
反馈属于具体帧,并随帧槽位所有权一起返回生产侧。
8. 帧身份信息
每个帧携带:
frame_id 帧唯一标识
input_version 核心输入数据版本
snapshot_version 完整状态快照版本
frame_slot_generation 帧槽位复用代次
frame_id(帧唯一标识)
标识一次实际完成的帧生产。
input_version(输入版本)
标识该帧使用的核心输入数据版本。
snapshot_version(快照版本)
标识该帧使用的完整状态快照版本。
frame_slot_generation(帧槽位复用代次)
标识同一个物理帧槽位的复用代次,避免旧反馈与新帧混淆。
9. 数据时间信息
每个帧携带:
core_data_time
source_data_time_begin
source_data_time_end
snapshot_capture_begin_time
snapshot_capture_end_time
含义:
core_data_time
触发该帧生产的核心数据时间
source_data_time_begin
该帧使用的最早有效数据时间
source_data_time_end
该帧使用的最新有效数据时间
snapshot_capture_begin_time
开始收集一致状态快照的时间
snapshot_capture_end_time
完成一致状态快照收集的时间
可派生:
snapshot_capture_duration
data_age_at_snapshot
10. 生产阶段时间信息
每个帧携带:
render_request_time
render_queue_enter_time
render_begin_time
prepare_begin_time
prepare_end_time
draw_begin_time
draw_end_time
render_end_time
publish_time
含义:
render_request_time
调度器批准生产该帧的时间
render_queue_enter_time
任务进入后台任务队列的时间
render_begin_time
后台工作线程实际开始执行的时间
prepare_begin_time
开始准备绘制数据的时间
prepare_end_time
完成绘制数据准备的时间
draw_begin_time
开始执行最终颜色缓冲绘制的时间
draw_end_time
完成最终颜色缓冲绘制的时间
render_end_time
完整帧生产任务结束的时间
publish_time
帧槽位从 `Producing` 状态发布为 `Ready` 状态的时间
可派生:
render_queue_wait
= render_begin_time - render_queue_enter_time
snapshot_to_render_wait
= render_begin_time - snapshot_capture_end_time
prepare_duration
= prepare_end_time - prepare_begin_time
draw_duration
= draw_end_time - draw_begin_time
render_duration
= render_end_time - render_begin_time
production_lifecycle_duration
= publish_time - render_request_time
11. 发布和 GUI 排队时间信息
每个帧携带:
update_request_id
update_request_time
paint_begin_time
含义:
update_request_id
标识实际对应当前 paintEvent 的待处理 update 请求
update_request_time
该 update 请求进入 GUI 事件队列的时间
paint_begin_time
消费者实际开始执行 paintEvent 的时间
可派生:
publish_to_paint_wait
= paint_begin_time - publish_time
update_queue_wait
= paint_begin_time - update_request_time
ready_frame_wait
= paint_begin_time - publish_time
当多个帧在同一个待处理 update() 期间发生替换时,最终被消费帧关联到实际触发本次 paintEvent() 的 update_request_id 和
update_request_time。
12. 消费阶段时间信息
消费者填写:
paint_begin_time
paint_end_time
consumer_release_time
含义:
paint_begin_time
开始读取和绘制该帧的时间
paint_end_time
完成该帧显示的时间
consumer_release_time
消费者完成全部访问并释放当前 `Consuming` 状态帧槽位的时间
可派生:
paint_duration
= paint_end_time - paint_begin_time
consumer_hold_duration
= consumer_release_time - paint_begin_time
data_to_paint_begin
= paint_begin_time - core_data_time
data_to_paint_end
= paint_end_time - core_data_time
frame_total_lifecycle
= consumer_release_time - render_request_time
13. 帧结果信息
每个帧最终记录:
Frame_Outcome 帧结果
├── Produced 已生产
├── Consumed 已消费
├── Superseded 已被更新帧替代
├── Cancelled 已取消
└── Repeated 重复显示上一帧
Produced(已生产)
帧已经生产完成并发布。
Consumed(已消费)
帧被消费者实际取得并完成显示。
Superseded(已被更新帧替代)
帧已经生产完成,但在被消费者取得之前被更新帧替代。
Cancelled(已取消)
帧在完成前因 Plot 销毁、尺寸失效或明确终止条件被取消。
Repeated(重复显示上一帧)
消费者没有取得新帧,继续显示上一张已经消费的图像。
实时模式重点统计:
Consumed 已消费
Superseded 已被更新帧替代
Repeated 重复显示上一帧
14. 帧工作量信息
每个帧携带:
raw_input_count
visible_input_count
prepared_output_count
draw_point_count
draw_segment_count
pixel_width
pixel_height
renderable_count
用于关联性能变化与实际工作量:
输入规模变化
抽点结果变化
可见范围变化
图表尺寸变化
Renderable 数量变化
15. 无锁元数据写入规则
帧元信息采用阶段所有权写入。
15.1 Producing(生产阶段)
处于 Producing 状态的帧槽位由生产端独占。
生产端填写:
帧身份信息
数据时间信息
快照时间信息
后台排队信息
prepare 时间信息
draw 时间信息
publish 时间信息
工作量信息
其他线程不修改这些字段。
15.2 Ready(发布阶段)
生产端完成全部写入后,将帧槽位状态从:
Producing 生产中
发布为:
Ready 已就绪
状态发布建立完整内存可见性边界。
15.3 Consuming(消费阶段)
消费者取得处于 Ready 状态的帧槽位后,将状态变为:
Consuming 消费中
消费者独占填写:
update_request_id GUI 更新请求标识
update_request_time GUI 更新请求入队时间
paint_begin_time paintEvent 开始时间
paint_end_time paintEvent 结束时间
consumer_release_time 消费者释放时间
Frame_Outcome::Consumed 已消费
生产端在消费者释放该帧槽位前不再访问该帧。
15.4 Returned_Frame(返回阶段)
消费者完成访问后,将帧槽位及完整反馈返回生产侧。
返回路径:
消费者填写消费信息
→ 发布消费完成反馈
→ 帧槽位所有权返回生产侧
→ 调度侧读取完整 Frame_Metadata
→ 聚合统计
→ 清理上一代元数据
→ 递增 frame_slot_generation
→ 将帧槽位重新置为 Free
→ 下一代生产时切换为 Producing
不需要为每个时间字段单独使用原子变量。
无锁同步边界只放在:
Frame_State 发布 帧状态发布
frame_slot_generation 发布 帧槽位复用代次发布
帧版本发布
16. Superseded(已被更新帧替代)帧反馈
Latency_Eager(低延迟优先)模式下,旧的 Ready 状态帧可能被更新帧替代。
被替代帧在复用前标记:
Frame_Outcome::Superseded 已被更新帧替代
需要采集:
frame_id
input_version
publish_time
superseded_time
ready_frame_wait
render_duration
production_lifecycle_duration
可派生:
superseded_ready_count
superseded_ready_ratio
superseded_frame_age
wasted_render_time
其中:
wasted_render_time
表示已经完成渲染但最终没有显示的生产时间。
17. 重复显示反馈
当 paintEvent() 没有取得新帧,只能继续显示上一帧时,记录:
consumer_underrun_count
repeated_frame_count
repeat_time
last_consumed_frame_id
last_consumed_input_version
可派生:
consumer_underrun_ratio
repeated_frame_ratio
displayed_frame_age
重复绘制上一帧不创建新的完整 Frame_Metadata。
18. 调度事件
18.1 Input_Changed(输入数据已更新)
处理路径:
更新 latest_input_version
更新有效输入间隔统计
尝试启动生产
18.2 Render_Completed(渲染生产已完成)
处理路径:
完成生产阶段元数据
将帧槽位发布为 `Ready` 状态
请求一次 update()
更新生产能力统计
执行负反馈计算
尝试启动下一帧
18.3 Frame_Consumed(帧已消费)
处理路径:
完成消费阶段元数据
释放当前 `Consuming` 状态帧槽位
采集返回帧的 Frame_Metadata
更新消费能力统计
执行负反馈计算
检查是否存在更新的 Ready 状态帧
尝试启动下一帧
18.4 Rate_Limit_Deadline_Reached(频率限制截止时间已到达)
处理路径:
检查最新数据版本
检查生产状态
检查三帧槽位状态
检查生产策略
满足条件则启动生产
18.5 Control_Updated(控制参数已更新)
低频执行:
更新平滑间隔
更新抖动估计
识别瓶颈
应用滞回
调整 adaptive_min_render_interval
19. 两个完成事件回路
19.1 生产完成自时钟
生产者受限时:
Frame_N 生产完成
→ 发布 Frame_N
→ 检查是否存在更新数据
→ 检查人工速率限制
→ 检查三帧槽位状态
→ 启动 Frame_N_Plus_1
生产循环由:
Render_Completed 渲染生产已完成
驱动。
19.2 消费完成反馈
消费者完成后:
Frame_N 消费完成
→ 填写消费阶段元数据
→ 释放当前 `Consuming` 状态帧槽位
→ 返回 Frame_Metadata
→ 更新消费能力
→ 调度器重新决策
只有存在更新的 Ready 状态帧时才再次请求:
update()
20. 速率模型
调度器维护:
D:有效数据更新速率
P:后台生产能力
C:前端消费能力
M:人工最大生产速率
定义:
D 有效数据更新速率,由有效输入间隔估计
P 后台生产能力,由完整帧生产耗时估计
C 前端消费能力,由不同帧被实际消费的间隔估计
M 人工最大生产速率,由用户配置
长期有效显示速率近似为:
display_rate = min(D, P, C, M)
该公式描述稳定结果,不直接替代每次调度判断。
21. 能力测量有效条件
21.1 生产能力有效条件
生产耗时可解释为生产能力时,需要满足:
持续存在未渲染的新数据
生产完成后立即具备下一次生产资格
没有消费者背压
没有人工最大速率等待
21.2 消费能力有效条件
消费间隔可解释为消费者能力时,需要满足:
Ready 状态帧持续可用
消费者一直有新帧可取
生产端没有断供
21.3 输入速率有效条件
输入速率只统计会改变最终图像的有效核心版本。
22. 平滑平均和抖动
不直接平滑 FPS。
分别平滑:
input_interval
render_queue_wait
render_duration
ready_frame_wait
update_queue_wait
paint_duration
consume_interval
data_to_paint_end
每个指标维护:
srtt
jitter
控制侧使用:
safe_interval = srtt + K × jitter
控制方向:
发现过度生产
→ 快速增加生产间隔
发现消费者断供
→ 缓慢缩短生产间隔
23. 快反馈
每次调度事件立即检查:
latest_input_version > rendered_input_version
render_busy
ready_valid
consumer_busy
update_pending
current_time >= next_render_time
production_policy
直接决定:
立即生产
等待生产完成
等待消费者
等待最大速率期限
请求 update()
保持空闲
24. 慢反馈
从返回的 Frame_Metadata 聚合:
input_interval_srtt
input_interval_jitter
render_queue_wait_srtt
render_queue_wait_jitter
render_duration_srtt
render_duration_jitter
ready_frame_wait_srtt
ready_frame_wait_jitter
update_queue_wait_srtt
update_queue_wait_jitter
paint_duration_srtt
paint_duration_jitter
consume_interval_srtt
consume_interval_jitter
frame_age_srtt
frame_age_jitter
同时维护:
producer_busy_ratio
ready_frame_occupancy_ratio
consumer_busy_ratio
superseded_ready_ratio
consumer_underrun_ratio
unique_consumed_ratio
display_efficiency
其中:
display_efficiency
= consumed_frame_count / produced_frame_count
25. 瓶颈状态
25.1 Input_Limited(输入受限)
特征:
有效输入间隔最长
生产者经常空闲
不存在 Ready 状态帧的时间较多
消费者经常空闲
行为:
有新核心数据立即生产
没有新核心数据完全停止
不重复生成相同版本
25.2 Producer_Limited(生产端受限)
特征:
render_duration 最大
处于 `Producing` 状态的帧槽位长期繁忙
不存在 Ready 状态帧的时间较多
consumer_underrun_ratio 上升
行为:
不增加额外自适应等待
生产完成后立即检查最新数据
存在更新则直接启动下一帧
渲染期间的中间输入版本全部合并
消费者没有新帧时继续显示上一帧
人工最大速率仍然有效。
25.3 Consumer_Limited(消费端受限)
特征:
ready_frame_wait 增加
update_queue_wait 增加
Ready 状态帧槽位长期占用
superseded_ready_ratio 上升
display_efficiency 下降
Cost_Balanced(成本均衡)
已有 Ready 状态帧时停止继续生产
输入端继续更新 latest_input_version
消费者释放帧槽位后再生产当前最新版本
Latency_Eager(低延迟优先)
允许有限更新 Ready 状态帧
允许新的 Ready 状态帧替代旧的 Ready 状态帧
持续统计 superseded_ready_ratio
持续统计 wasted_render_time
覆盖率或浪费时间过高时增加生产间隔
25.4 Manual_Limited(手动上限受限)
特征:
manual_min_render_interval
大于输入、生产和消费安全周期
行为:
只有达到 next_render_time 后才能启动生产
限速期间的输入变化全部合并
每次生产使用当时最新状态
用户配置的是:
max_render_rate
不是必须达到的目标帧率。
25.5 Balanced(均衡状态)
特征:
生产能力和消费能力差异处于抖动安全带内
Ready 状态帧槽位占用稳定
superseded_ready_ratio 较低
consumer_underrun_ratio 较低
行为:
保持当前生产间隔
保持当前策略
允许生产端有限领先一个 Ready 状态帧
不根据单次样本切换瓶颈
26. 滞回控制
新的瓶颈状态必须同时满足:
平滑差异超过抖动安全带
持续多个控制周期
帧槽位占用状态与候选瓶颈一致
帧级反馈指标与候选瓶颈一致
进入 Consumer_Limited 时需要同时观察:
ready_frame_wait 上升
superseded_ready_ratio 上升
update_queue_wait 上升
display_efficiency 下降
进入 Producer_Limited 时需要同时观察:
render_duration 较高
producer_busy_ratio 接近满载
ready_frame_occupancy_ratio 较低
consumer_underrun_ratio 上升
处于安全带内时保持原状态。
27. 负反馈调节
27.1 过度生产反馈
触发特征:
superseded_ready_ratio 持续升高
wasted_render_time 持续升高
ready_frame_wait 持续增加
display_efficiency 持续下降
frame_age 没有因为增加生产而改善
调节动作:
快速增加 adaptive_min_render_interval
减少 Ready 状态帧替换频率
必要时退化到 `Cost_Balanced`(成本均衡)行为
27.2 消费断供反馈
触发特征:
consumer_underrun_ratio 持续升高
不存在 Ready 状态帧的时间较多
消费者经常重复显示上一帧
存在持续未渲染的新输入
生产端未满载
调节动作:
缓慢缩短 adaptive_min_render_interval
直到人工最大速率或生产能力边界
27.3 生产满载反馈
触发特征:
producer_busy_ratio 接近满载
render_queue_wait 上升
render_duration 达到稳定上限
调节动作:
停止继续提速
不堆积渲染任务
生产完成后按最新数据自时钟运行
28. 最大生产速率
最大生产速率属于生产端准入条件。
定义:
相邻两次完整帧生产开始之间允许的最小时间
生产必须同时满足:
存在未渲染的新数据
当前没有生产任务
三帧槽位状态允许生产
当前策略允许生产
最大速率等待时间已经结束
配置:
无限制
30 FPS
60 FPS
120 FPS
其他用户指定值
无限制表示不增加人工等待,不表示绕过数据、消费者和三帧槽位约束。
29. Production_Policy(生产策略)
29.1 Cost_Balanced(成本均衡)
目标:
减少无意义计算
提高已生成帧的实际显示比例
行为:
已有 Ready 状态帧时停止生产
消费者消费后再生产最新状态
长期生产速率接近有效消费速率
29.2 Latency_Eager(低延迟优先)
目标:
降低最新数据到显示完成的年龄
行为:
消费者显示当前帧时允许生产下一帧
允许新的 Ready 状态帧替代旧的 Ready 状态帧
仍受最大生产速率约束
连续覆盖过多时触发负反馈降速
30. update () 调度
完整帧发布后:
ready_valid
且 update_pending 为 false
→ 请求一次 QWidget::update()
连续发布多个帧时,只保持一个待处理的 update()。
paintEvent() 开始时取得当前最新完整帧。
paintEvent() 完成后:
存在更新的 Ready 状态帧
→ 再请求一次 update()
不存在更新的 Ready 状态帧
→ 不请求 update()
系统自身触发的窗口暴露、resize 或重复重绘不计为新帧消费。
31. 数据和帧丢弃规则
31.1 输入版本
未开始渲染的旧输入版本直接合并。
正在渲染 100
收到 101、102、103
下一帧直接使用 103
31.2 正在生产的帧
默认完成,不因新输入到达而中途取消。
31.3 已完成未消费帧
Cost_Balanced(成本均衡):
已有 Ready 状态帧时暂停继续生产
Latency_Eager(低延迟优先):
允许新的 Ready 状态帧替代旧的 Ready 状态帧
旧帧标记为 `Superseded`(已被更新帧替代)
31.4 正在消费的帧
处于 Consuming 状态的帧槽位在释放前不会被覆盖或修改。
32. 完整运行路径
核心数据变化
→ 更新 latest_input_version
→ 触发 Input_Changed
→ 调度器检查生产资格
→ 收集一致快照
→ 填写数据和快照时间
→ 进入后台任务队列
→ 记录 render_queue_wait
→ 执行 prepare
→ 执行 draw
→ 填写生产时间
→ 将帧槽位发布为 `Ready` 状态
→ 请求 update()
→ 记录 update_request_time
→ paintEvent 开始
→ 取得最新 Ready 状态帧
→ 填写 paint_begin_time
→ 执行绘制
→ 填写 paint_end_time
→ 释放当前 `Consuming` 状态帧槽位
→ 帧槽位所有权返回
→ 调度侧读取完整 Frame_Metadata
→ 更新 srtt、jitter 和比例统计
→ 更新 Bottleneck_State(瓶颈状态)
→ 更新 adaptive_min_render_interval
→ 重新判断是否生产下一帧
33. 最终设计结论
默认实时图表采用:
Data_Driven 数据驱动
Latest_Wins 最新数据优先
Triple_Frame 三帧机制
Max_Render_Rate 最大渲染频率限制
Render_Completed_Self_Clock 渲染完成事件自时钟
Frame_Consumed_Feedback 帧消费完成反馈
Frame_Metadata_Lock_Free_Return 帧元信息无锁返回
SRTT_And_Jitter 平滑时间与抖动估计
Bottleneck_Detection 瓶颈识别
Hysteresis_Control 滞回控制
Fast_Backoff 快速退避
Slow_Recovery 缓慢恢复
最终控制原则:
使用三帧槽位状态和版本状态决定当前动作,使用帧自身携带的完整生命周期信息形成无锁反馈,使用平滑平均和抖动识别长期瓶颈,使用已消费率、帧覆盖率、排队时间、渲染时间、
paintEvent()时间和帧年龄控制生产频率。