结论:行车画面是一条纯显示链:camerad → VisionIPC → CameraView → EGL 外部纹理或 PC Y/UV 纹理 → AugmentedRoadView → ModelRenderer/HUD/Alert → GuiApplication.render → 屏幕;模型叠加另走 modelV2 + extrinsicsCalibration + radarState → ModelRenderer,并非从当前显示的视频帧反推。这条链不是车辆控制或功能安全的执行链,主循环在顶层 Widget 绘制后才更新 UIState,因此订阅的新消息通常在下一帧绘制,不能仅凭帧率推断相机到显示或故障到告警的端到端时延。实机 GPU、摄像头时序、屏幕亮度和触摸延迟均未测试。
一帧如何到达屏幕
GuiApplication.init_window() 创建窗口和可选的离屏 render texture,render() 每帧收集触摸、绘制栈顶 Widget、结束绘制;关屏时仍 yield False,让外层更新状态。AugmentedRoadView._render() 做行车门控、选宽/窄相机、更新标定、scissor 限定内容区,并按相机、模型、HUD、告警、驾驶员状态的顺序绘制。CameraView._render() 用 recv(timeout_ms=0) 非阻塞取最新帧:无新帧沿用旧画面,连接丢失清帧并显示占位,座舱相机水平翻转。断连时 _ensure_connection() 按 0.2 秒间隔重试,_handle_switch() 在目标相机流有帧后才切换并重建纹理。
| 组件 / 函数 | 静态职责 |
|---|---|
GuiApplication.init_window() / render() |
创建窗口与可选离屏纹理;每帧收集触摸、绘制栈顶 Widget、结束绘制;关屏仍 yield False |
AugmentedRoadView._render() |
行车门控、选宽窄相机、更新标定、scissor 限定内容区,按固定顺序绘制 |
CameraView._render() |
非阻塞取最新帧;无新帧沿用旧画面,断连清帧显示占位;座舱相机水平翻转 |
CameraView._ensure_connection() / _handle_switch() |
断连按 0.2 秒重试;目标流有帧后切换并重建纹理 |
AugmentedRoadView._update_calibration() / _calc_frame_matrix() |
接受有效且 calibrated 的外参,按内参/宽窄镜头/消失点算视频变换,并设车体到屏幕矩阵 |
ModelRenderer._render() |
modelV2 与外参需本次行车后至少到达一次;按脏位重算图形 |
AlertRenderer.get_alert() |
正常显示 selfdriveState 告警;首启等待与状态失联有单独降级提示 |
图像缓冲与 GPU 资源
comma 分支由 CameraView.__init__() 初始化 EGL 和占位纹理。每个 VisionIPC 缓冲的 idx 首次出现时,create_egl_image() 复制 fd,并用 NV12 两平面、stride、UV 偏移属性导入 DMA-BUF;_render_egl() 缓存 idx -> EGLImage,绑定 GL_TEXTURE_EXTERNAL_OES 后由 shader 绘制。切流或关闭时 _clear_textures() 销毁 EGL image,destroy_egl_image() 关闭复制的 fd。这里的「零拷贝」只表示 UI 不执行 CPU 端 YUV 上传,不保证摄像头至显示全链路没有拷贝。
非 comma 分支由 _initialize_textures() 创建 Y 和 UV 纹理,_render_textures() 仅在帧变化时上传数据,fragment shader 把 YUV 转成 RGB。两个分支都依赖 producer 的 NV12 缓冲尺寸、stride 与生命周期;更换摄像头/ISP、显示驱动或 GPU 时应先验证缓冲复用、纹理方向、色彩和断流恢复。idx 缓存以 _initialize_textures() 为释放边界,非切流的异常重连是否会复用相同 idx 但更换底层 fd,需要实机注入断连测试,不能仅凭源码保证。
投影、裁剪和告警
ModelRenderer._update_raw_points() 提取路径、车道线、路缘和加速度,_update_model() 依据前车距离缩短路径,_map_line_to_polygon() 把车体坐标两侧采样点投影成屏幕上的带状多边形,并过滤投影奇点和区域外点。渲染层外侧还有 CLIP_MARGIN=500 的几何筛选与 AugmentedRoadView 的 scissor 裁剪,两者用途不同。shader_polygon.draw_polygon() 把两条边交织成 triangle strip 并懒加载 shader;Gradient 限制最多 20 个色标,但显式 stops 不校验与颜色等长。普通路径颜色跟 longitudinalPlan.allowThrottle 混合,实验模式路径颜色与加速度有关。
需要明确的是:这些门控只保证某些消息在本次行车后到达过一次,并不构成「摄像头帧与模型预测严格同时间戳」的保证,也没有在 CameraView 每帧旧画面保留时自动隐藏模型。告警应作为独立于装饰性图层的受信任通道验证:摄像头占位、UI 消息断流、绘制遮挡、驾驶员接管提示和语音提示须分别测量。
大屏与 mici 的实现差异
mici 的 AugmentedRoadView._render() 为侧栏留宽、添加渐隐纹理、置信球与书签,告警先经 will_render() 决定 HUD 顶部图标和方向盘提示,再绘制告警/HUD;大屏则使用彩色行车边框,叠加顺序不同。mici 的 ModelRenderer._render() 在 disengaged 时隐藏车道/路径,大屏没有这一条件——两者不能视为同一 UI 的简单缩放。
| 对比项 | 大屏 | mici |
|---|---|---|
| 布局与边框 | 彩色行车边框 | 为侧栏留宽、渐隐纹理、置信球与书签 |
| 告警协调 | 按固定叠加顺序绘制 | 先经 will_render() 决定 HUD 顶部图标与方向盘提示 |
| 车道/路径显示 | 无 disengaged 隐藏条件 | disengaged 时隐藏车道与路径 |
| 离路切换 | 重新 onroad 且曾有帧时重建客户端 | 排空现有连接的队列并清帧 |
| 着色器 | 基础 YUV 转换 | 另有 engaged 去饱和/对比度与座舱增强 |
过渡逻辑也不同:mici 的 CameraView._offroad_transition() 排空现有连接的队列并清帧,大屏的 _offroad_transition() 在重新 onroad 且曾有帧时重建客户端;迁移时要为两种屏幕分别验证切换时不会闪出上一行程图像。mici 的着色器另有 engaged 去饱和/对比度和座舱增强。
自制硬件与插件化结论
硬件原型至少需要相机同步/时间戳、可稳定复用的 NV12 缓冲和 DMA-BUF/EGL 支持或经验证的纹理上传替代、可见且可测的显示路径、可靠触摸/背光/屏幕唤醒,以及独立于 UI 的车辆安全网关。台架验收项可列为:相机断连/重启/切宽窄流、GPU 上下文丢失、屏幕休眠唤醒、模型/状态消息失联、告警抢占、长时间热降频,并测量真实最坏时延。
「万物即插件」适合导航、可视化指标和非关键叠加;摄像头底图生命周期、标定/投影契约、接管告警与失联降级、驾驶员状态显示应由受信任宿主掌控。插件只读受版本约束的消息快照,通过宿主分配的低优先级绘图区域输出,不能遮挡或替代核心告警,更不能发布 selfdriveState 或触发车辆控制。
常见问题
「零拷贝」是否意味着摄像头到屏幕全链路都没有拷贝?
不是。源文档明确这里的「零拷贝」只表示 UI 不执行 CPU 端 YUV 上传,不保证摄像头至显示全链路没有拷贝。comma 分支用 DMA-BUF 导入 EGLImage,非 comma 分支则在帧变化时上传 Y/UV 纹理,两条路径的拷贝点不同。
模型叠加会不会因为显示旧画面而与视频帧错位?
存在这种可能。CameraView._render() 无新帧时沿用旧画面,而源码没有在每帧旧画面保留时自动隐藏模型;门控只保证 modelV2 与外参在本次行车后至少到达一次,并不构成摄像头帧与模型预测严格同时间戳的保证。
大屏和 mici 两套界面能不能当作同一 UI 的缩放?
不能。mici 为侧栏留宽、加渐隐纹理与置信球,告警先经 will_render() 协调 HUD,且 ModelRenderer 在 disengaged 时隐藏车道/路径;大屏使用彩色行车边框、叠加顺序不同,也没有 disengaged 隐藏条件。
第三方插件能否自行绘制告警或相机底图?
不建议,也不符合源文档的边界。摄像头底图生命周期、标定/投影契约、接管告警与失联降级、驾驶员状态显示应由受信任宿主掌控;插件只读受版本约束的消息快照,在宿主分配的低优先级区域输出,不能遮挡核心告警或发布 selfdriveState。
延伸阅读
- 相机请求与缓冲生命周期 —— 承接本文 NV12 缓冲复用、
idx缓存释放边界与断连测试。 - UI 运行时 —— 解释绘制栈、触摸采集与
UIState下一帧更新的关系。 - 52 张预览自动校验:五屏 HUD 怎么把「文字出界」变成一条断言 —— 与本文的显示链与几何裁剪互为对照。
- 一套令牌覆盖 5 种分辨率:车载 HUD 从 456×280 到 1280×720 的适配工程 —— 承接本文大屏与 mici 的形态差异。
参考资料
- 项目 02 ——
openpilot-research/ONROAD_GPU_RENDER_DEEP_DIVE.md(取用了帧循环、图像缓冲与 GPU 资源、投影裁剪与告警、大屏与 mici 差异及硬件插件边界)。 - 项目 02 ——
openpilot-research/UI_SHADER_POLYGON_GRADIENT_CONTRACT_DEEP_DIVE.md(仅用于确认shader_polygon与Gradient的边界)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复