Refactor LED control UI to Vue3

This commit is contained in:
sealks
2026-07-17 02:10:08 +08:00
parent fe906e028b
commit ba9f0044f3
16 changed files with 1906 additions and 752 deletions
+136 -62
View File
@@ -2,48 +2,43 @@
## 目标
将一个前端大屏应用稳定投放到 `14880 x 3510` LED 大屏。
本项目用于把一个逻辑尺寸为 `14880 x 3510` LED 大屏内容,拆分到两台 GPU 输出服务器本地渲染
硬件约束
生产形态
- 两台 GPU 服务器
- 每台服务器 4 路 4K 输出
- 两台服务器品牌或 GPU 可能不同,无法依赖硬件级 GPU 同步
- 控制服务运行 FastAPI,负责场景、动作、时间同步、ACK、状态监控
- 左 GPU 服务器打开 `/output/left`,只显示左半屏 tile
- 右 GPU 服务器打开 `/output/right`,只显示右半屏 tile
- 每台 GPU 服务器输出 4 路 4K 到拼控或 LED 控制器。
- 拼控或 LED 控制器完成最终物理拼接。
因此系统目标不是让两张 GPU 每一帧硬同步,而是:
WebSocket 不传大视频流,只传状态、命令、时间、ACK 和性能指标。画面由两台 GPU 服务器本地渲染。
- 统一业务状态。
- 统一提交时间。
- 统一动画时间轴。
- 输出端本地渲染。
- 拼控完成物理拼接和输入对齐。
## 推荐架构
## 逻辑拓扑
```text
控制服务 FastAPI
- 场景管理
- WebSocket 同步
- 时间校准
- ACK 追踪
- 性能监控
左 GPU 服务器
- 打开 /output/left
- 渲染完整大屏应用的 left tile
- 4 路 4K 输出到拼控
右 GPU 服务器
- 打开 /output/right
- 渲染完整大屏应用的 right tile
- 4 路 4K 输出到拼控
拼控 / LED 控制器
- 接收 8 路 4K
- 按物理坐标拼接成 14880 x 3510
控制台 /
http://control:8000/
|
v
FastAPI 控制服务
场景管理 / WebSocket Hub / SyncCoordinator / 状态接口
| |
prepare/commit/action status/telemetry/ACK
| |
-------------------------------
| |
v v
左 GPU 输出服务器 右 GPU 输出服务器
/output/left /output/right
left tile 本地渲染 right tile 本地渲染
4 路 4K 输出 4 路 4K 输出
| |
---------> 拼控 / LED 控制器 <-
14880 x 3510
```
## 左右屏如何分开
## Tile 切分
完整逻辑画面:
@@ -56,13 +51,19 @@
```text
left:
x=0, y=0, width=7440, height=3510
x=0
y=0
width=7440
height=3510
right:
x=7440, y=0, width=7440, height=3510
x=7440
y=0
width=7440
height=3510
```
输出端页面创建完整逻辑大屏坐标系,然后根据自己的 tile 做视口偏移。真实 Three.js 项目中,应使用:
输出端页面使用完整逻辑大屏坐标系,根据自己的 tile 做视口偏移。Three.js 项目中建议使用:
```js
camera.setViewOffset(
@@ -71,19 +72,19 @@ camera.setViewOffset(
tile.x,
tile.y,
tile.width,
tile.height
tile.height,
);
```
## 每台服务器 4 路 4K
每台服务器建议配置为 `2 x 2` 逻辑桌面:
每台 GPU 服务器建议配置为 `2 x 2` 逻辑桌面:
```text
7680 x 4320
```
四路输出:
四路输出映射
```text
1: x=0, y=0, 3840 x 2160
@@ -96,35 +97,50 @@ camera.setViewOffset(
## 同步协议
场景切换
### 场景切换
控制台调用:
```text
POST /api/scenes/{scene_id}/switch
```
服务端广播:
服务端生成同一个 `command_id`,先广播:
```text
prepare_scene(command_id, apply_at_ms)
commit_scene(command_id, apply_at_ms)
prepare_scene(command_id, apply_at_ms, state, scene)
```
输出端流程
输出端收到 `prepare_scene`
1. 预加载资源。
2. 等待至少两个 RAF,让浏览器完成布局和首帧准备。
3. 如果存在 `window.ledPlatformPrepareScene`,等待业务方的真实预渲染/超分完成。
4. 回 ACK`status=prepared`
服务端 `SyncCoordinator``target_tiles` 中的 `left``right` 都返回 `prepared` 后,才广播:
```text
1. 收到 prepare,记录命令,可预加载资源。
2. 收到 commit,等到 apply_at_ms。
3. 到点提交场景。
4. 回 ACK。
commit_scene(command_id, apply_at_ms, state, scene)
```
局部动作
输出端收到 `commit_scene` 后,等到统一的 `apply_at_ms` 再切换画面,并回 ACK
```text
status=committed
```
这样谁渲染慢就等谁,两个输出端都准备好后才同步放行。
### 局部动作
控制台调用:
```text
POST /api/actions
```
支持结构化动作:
支持动作:
```text
page.next
@@ -135,37 +151,93 @@ timeline.pause
timeline.resume
```
## 关键渲染原则
局部动作目前直接按 `apply_at_ms` 调度并回 ACK
所有动画、Three.js、地图和视频都应基于统一时间轴:
```text
status=action_committed
```
如果后续某些局部动作也需要超分或重资源准备,可以复用场景切换的 prepare/barrier/commit 模式。
## 画面回显
控制台内置两个只读预览 iframe,并把它们无缝拼成一块完整画面:
```text
/output/left?preview=1
/output/right?preview=1
```
预览模式通过 `/ws/admin` 接收状态和命令,不连接 `/ws/output/{tile_id}`,因此:
- 不注册为真实输出节点。
- 不发送 `prepared``committed``telemetry` ACK。
- 不会提前释放同步 barrier。
- 只用于控制台观察画面。
真实生产输出端仍然打开:
```text
/output/left
/output/right
```
## 时间同步
输出端通过 `clock_ping` / `clock_pong` 估算服务端时间:
```text
serverNowMs = Date.now() + serverOffsetFromDateMs
```
RTT 使用 `performance.now()` 估算,服务端时间偏移使用客户端 wall clock 的发送时间和返回时间中点估算。
渲染和动画应基于统一时间轴:
```js
const t = serverNowMs() - sceneStartedAtMs;
renderSceneAt(t);
```
不要让左右服务器各自自由播放
不要让左右输出端各自累计本地 delta
```js
// 不推荐
animation += localDeltaTime;
```
这样即使某台机器偶尔慢一帧,也会在下一帧追到统一时间,不会越播越偏。
## 状态观测
## 为什么不推荐单机渲染后网络分发
控制台显示:
单侧半屏未压缩数据量:
- 输出节点数量。
- 每个输出节点 FPS、frame time、RTT、clock offset。
- 最近命令回显。
- 每个 tile 的 `prepared``committed``late``error` 状态。
- barrier 放行耗时。
接口:
```text
7440 x 3510 x 4 bytes x 60fps ~= 6.3 GB/s
GET /api/sync/status
GET /healthz
```
左右两侧合计超过 `12 GB/s`。普通网络视频流需要编码、传输、解码,会带来延迟、画质损失、文字细线压缩失真,以及新的编码/解码同步问题。
## 为什么不传视频流
单侧半屏未压缩数据量约为:
```text
7440 x 3510 x 4 bytes x 60 fps ~= 6.3 GB/s
```
左右两侧合计超过 `12 GB/s`。普通网络视频流还需要编码、传输、解码,会带来延迟、画质损失、文字细线压缩失真,以及新的编码/解码同步问题。
本系统选择“控制服务发命令,输出端本地渲染”,更适合高分辨率 LED 墙。
## 生产验收指标
建议关注 P95/P99,而不是平均值
建议关注 P95/P99
```text
60 FPS:
@@ -182,6 +254,7 @@ animation += localDeltaTime;
```text
WebSocket RTT < 10ms
clock offset < 5ms
left/right 都返回 prepared
left/right 都返回 committed 或 action_committed
不得频繁出现 late ACK
```
@@ -199,12 +272,13 @@ left/right 都返回 committed 或 action_committed
能保证:
- 两边业务状态一致。
- 两边按同一服务端时间点提交。
- 两边都准备好后才释放场景提交。
- 两边按统一服务端时间点提交。
- 动画长期不漂移。
- 命令和性能可观测。
不能单独保证:
- 两张不同 GPU 每一帧物理扫描完全同相。
- 两张不同 GPU 物理扫描完全同相。
如果必须达到广播级帧同步,需要硬件层支持 Genlock / Frame Lock / 专业视频墙控制器。