Files
Renderive/Renderive_实时渲染调度与三帧反馈设计.md
2026-07-30 11:21:30 +08:00

32 KiB
Raw Permalink Blame History

Renderive 实时渲染调度与三帧反馈设计

1. 文档目标

本文定义 Renderive 实时图表的生产、发布、消费、反馈和限流模型。

该设计面向以下场景:

  • 核心数据持续更新;
  • 后台线程生成完整图像帧;
  • Qt 主线程通过 paintEvent() 消费帧;
  • 系统采用三个可循环复用的帧槽位;
  • 默认追求低延迟和最新状态;
  • 不要求每个输入版本都被渲染;
  • 不要求每个已生成帧都被显示;
  • 需要根据输入、生产和消费能力自动调整生产频率;
  • 反馈路径不得引入热路径锁竞争(反馈数据直接记录在帧槽位自身的元信息中,并随帧槽位所有权无锁返回);

默认语义为:

Data_Driven                  数据驱动
Latest_Wins                   最新数据优先
Triple_Frame                  三帧机制
Bounded_In_Flight             有界在途帧
Max_Render_Rate               最大渲染频率限制
Lock_Free_Frame_Feedback      无锁帧反馈

2. 核心目标

系统需要满足以下目标:

  1. 核心数据变化后尽快生成新帧;
  2. 数据输入、后台生产和前端消费可以并行;
  3. 输入过快时合并中间版本;
  4. 生产者过慢时不积压渲染任务;
  5. 消费者过慢时不积压历史帧;
  6. 用户可以设置最大生产速率;
  7. 生产速率根据消费反馈自动调节;
  8. 生产能力不足时由完成事件自时钟运行;
  9. 所有阶段的耗时和排队信息随帧流转;
  10. 帧返回生产侧时完成统计采集;
  11. 热路径不创建字符串、动态事件对象或无界队列;
  12. 控制器避免瞬时样本导致频繁振荡。

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_NowStatistics_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_idupdate_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() 时间和帧年龄控制生产频率。