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