协程架构重构成功, 下一步准备指针交换 无锁化重构
This commit is contained in:
+674
-33
@@ -1,48 +1,689 @@
|
||||
我要重构这个多线程图表库的后台渲染缓冲模型。不要动 git。代码保持 K&R 样式,不要加无意义空行。除非必要,不要破坏已有函数签名语义;如果替换实现,旧实现直接删除,不要做兼容实现。
|
||||
# 多线程图表库后台渲染缓冲模型重构说明
|
||||
|
||||
核心目标: 把每个图抽象成“状态缓冲 -> 后台 CPU 渲染 -> 颜色缓冲 -> Qt 主线程贴图”的通用模型。Qt GUI 线程和 asio
|
||||
调度线程是两个不同线程。asio 调度线程是单线程,负责串行调度所有图的状态修改、任务提交、版本判断、任务完成发布、刷新请求合并。Qt
|
||||
主线程不直接修改渲染状态,只把用户操作、数据输入、缩放、尺寸变化、颜色表变化等修改请求投递给 asio 调度线程。asio 调度线程串行修改
|
||||
edit_state,并递增 edit_state.version。
|
||||
## 基本要求
|
||||
|
||||
后台计算使用 asio/线程池封装成 awaitable。asio 调度线程只负责任务编排,不在调度线程里做重 CPU 计算。state -> color 的重计算放到后台
|
||||
CPU 执行环境里完成。后台计算完成后,把完成事件 post 回 asio 调度线程,由 asio 调度线程发布颜色缓冲。
|
||||
我要重构这个多线程图表库的后台渲染缓冲模型。
|
||||
|
||||
每个图使用两个状态缓冲: edit_state: asio 调度线程持有的最新状态。Qt 主线程收到事件后只投递修改命令,不直接写 edit_state。
|
||||
render_state: 后台渲染任务使用的稳定状态快照。提交任务时由 edit_state 复制、交换或快照生成。
|
||||
要求:
|
||||
|
||||
每个图使用三个颜色缓冲: front_color: Qt 主线程当前正在显示的颜色图。 ready_color: 后台已经生成完,等待 Qt 主线程消费的颜色图。
|
||||
render_color: 后台线程正在写入的颜色图。
|
||||
* 不要动 git。
|
||||
* 代码保持 K&R 样式。
|
||||
* 不要加无意义空行。
|
||||
* 除非必要,不要破坏已有函数签名语义。
|
||||
* 如果替换实现,旧实现直接删除,不要做兼容实现。
|
||||
* 能不做兼容实现就不做兼容实现,除非明确说明设计如此。
|
||||
|
||||
每个缓冲对象都带 version。版本号跟随缓冲对象,不单独跟随槽位。版本比较需要封装成支持回环的函数,不要到处直接写 >。
|
||||
## 核心目标
|
||||
|
||||
版本比较函数语义: version_newer (a, b) 表示 a 是否新于 b。 可以用 uint64_t,并通过有符号差值处理回环,前提是版本差距不会超过半个取值空间。
|
||||
把每个图抽象成下面这个通用模型:
|
||||
|
||||
颜色缓冲原则: 缓冲对象固定分配,槽位只交换指针。 所有跨线程槽位交换必须加锁。 锁内只允许做版本判断、指针交换、状态标记。
|
||||
锁内禁止做像素计算、内存分配、QImage 填充、drawImage。 大计算锁外做,锁内只换身份。
|
||||
```text
|
||||
状态缓冲 -> 后台 CPU 渲染 -> 颜色缓冲 -> Qt 主线程贴图
|
||||
```
|
||||
|
||||
颜色缓冲的唯一合法交换: 后台发布: ready_color <-> render_color Qt 主线程消费: front_color <-> ready_color
|
||||
也就是:
|
||||
|
||||
不允许: front_color <-> render_color 后台线程操作 front_color Qt 主线程操作 render_color 绕过 ready_color 直接发布
|
||||
```text
|
||||
State -> Color -> paintEvent drawImage
|
||||
```
|
||||
|
||||
后台发布流程: 后台只写 render_color。 render_color 填充完成后,完成事件回到 asio 调度线程。 asio 调度线程在 color_lock 下判断:
|
||||
如果 render_color.version 新于 ready_color.version,则交换 ready_color 和 render_color。 交换后,如果 ready_color.version
|
||||
新于 front_color.version,则调用 scheduler->request_update ()。 如果旧 ready_color 还没被 Qt 主线程消费,新 render_color
|
||||
又完成了,直接用新 render_color 覆盖旧 ready_color。图表库追最新状态,不做帧队列。
|
||||
具体图只实现:
|
||||
|
||||
Qt paintEvent 流程: paintEvent 中在 color_lock 下判断: 如果 ready_color.version 新于 front_color.version,则交换
|
||||
ready_color 和 front_color。 释放锁后 drawImage (front_color)。 paintEvent 不做状态计算、不做颜色映射、不做后台等待、不提交后台任务。
|
||||
```text
|
||||
State -> Color 的 CPU 渲染逻辑
|
||||
```
|
||||
|
||||
刷新请求流程: 后台发布 ready_color 后,不直接在后台线程操作 QWidget。 scheduler->request_update () 负责把刷新请求投递到 Qt
|
||||
GUI 线程。 使用 paint_request_pending 合并重复 update 请求。 多个图完成时合并 dirty region,不要每个图都直接 update 整个窗口。
|
||||
Qt 主线程收到刷新任务后调用 QWidget::update (region),由 Qt 稍后触发 paintEvent。
|
||||
线程调度、版本判断、缓冲交换、刷新合并逻辑放到通用框架里。
|
||||
|
||||
任务提交原则: 每个图同一时间最多一个后台渲染任务。 如果任务正在 Queued 或 Running,不再提交同一图的新任务。 任务运行期间
|
||||
edit_state 又变化,只记录新版本;当前任务完成后,下一轮直接提交最新 edit_state,不排队计算中间版本。 图不可见、尺寸无效、版本没变时,不提交任务。
|
||||
## 线程模型
|
||||
|
||||
状态缓冲提交流程: asio 调度线程判断 edit_state.version 是否新于 render_state.version。 如果需要提交,并且当前图没有任务在飞:
|
||||
生成 render_state 快照。 render_state.version = edit_state.version。 标记任务运行。 把 render_state 对应的计算任务提交到后台
|
||||
CPU 执行环境。 CPU 任务只读取 render_state 快照并写 render_color,不直接修改 edit_state。
|
||||
Qt GUI 线程和 asio 调度线程是两个不同线程。
|
||||
|
||||
需要重构出一个通用的缓冲控制抽象,具体图只实现: State -> Color 的 CPU 渲染逻辑。 线程调度、版本判断、缓冲交换、update
|
||||
合并逻辑放在通用框架里。
|
||||
asio 调度线程是单线程,负责串行调度所有图的:
|
||||
|
||||
```text
|
||||
状态修改
|
||||
任务提交
|
||||
版本判断
|
||||
后台任务完成发布
|
||||
刷新请求合并
|
||||
```
|
||||
|
||||
Qt GUI 线程不直接修改渲染状态。
|
||||
|
||||
Qt 主线程收到用户操作、数据输入、缩放、尺寸变化、颜色表变化等事件后,只把修改请求投递给 asio 调度线程。
|
||||
|
||||
asio 调度线程串行修改 `edit_state`,并递增 `edit_state.version`。
|
||||
|
||||
## 后台计算模型
|
||||
|
||||
后台计算使用 asio / 线程池封装成 awaitable。
|
||||
|
||||
asio 调度线程只负责任务编排,不在调度线程里做重 CPU 计算。
|
||||
|
||||
`State -> Color` 的重计算放到后台 CPU 执行环境里完成。
|
||||
|
||||
后台计算完成后,把完成事件 post 回 asio 调度线程。
|
||||
|
||||
颜色缓冲发布由 asio 调度线程执行。
|
||||
|
||||
CPU 后台任务只做计算,不直接操作 QWidget,不直接修改 `edit_state`,不直接操作 `front_color`。
|
||||
|
||||
## 状态缓冲
|
||||
|
||||
每个图使用两个状态缓冲:
|
||||
|
||||
```text
|
||||
edit_state
|
||||
render_state
|
||||
```
|
||||
|
||||
### edit_state
|
||||
|
||||
`edit_state` 是 asio 调度线程持有的最新状态。
|
||||
|
||||
Qt 主线程收到事件后只投递修改命令,不直接写 `edit_state`。
|
||||
|
||||
所有会影响最终图像的状态变化,都必须在 asio 调度线程中修改 `edit_state`,并递增 `edit_state.version`。
|
||||
|
||||
典型状态变化包括:
|
||||
|
||||
```text
|
||||
用户操作
|
||||
数据输入
|
||||
缩放
|
||||
平移
|
||||
尺寸变化
|
||||
颜色表变化
|
||||
显示开关变化
|
||||
采样参数变化
|
||||
坐标范围变化
|
||||
```
|
||||
|
||||
### render_state
|
||||
|
||||
`render_state` 是后台渲染任务使用的稳定状态快照。
|
||||
|
||||
提交后台任务时,由 `edit_state` 复制、交换或快照生成 `render_state`。
|
||||
|
||||
提交时必须保证:
|
||||
|
||||
```text
|
||||
render_state.version = edit_state.version
|
||||
```
|
||||
|
||||
后台 CPU 任务只读取 `render_state` 对应的稳定快照。
|
||||
|
||||
## 状态版本号
|
||||
|
||||
两个状态缓冲都必须带 `version`。
|
||||
|
||||
示例结构:
|
||||
|
||||
```cpp
|
||||
struct State_Buffer {
|
||||
std::uint64_t version = 0;
|
||||
Chart_State state;
|
||||
};
|
||||
```
|
||||
|
||||
`edit_state.version` 是整个渲染管线的源版本。
|
||||
|
||||
`render_state.version` 表示当前提交给后台计算的状态版本。
|
||||
|
||||
状态版本流向:
|
||||
|
||||
```text
|
||||
edit_state.version
|
||||
↓ submit / snapshot
|
||||
render_state.version
|
||||
```
|
||||
|
||||
判断是否需要提交后台任务:
|
||||
|
||||
```text
|
||||
edit_state.version 新于 render_state.version
|
||||
并且当前图没有后台任务在飞
|
||||
```
|
||||
|
||||
## 颜色缓冲
|
||||
|
||||
每个图使用三个颜色缓冲:
|
||||
|
||||
```text
|
||||
front_color
|
||||
ready_color
|
||||
render_color
|
||||
```
|
||||
|
||||
### front_color
|
||||
|
||||
`front_color` 是 Qt 主线程当前正在显示的颜色图。
|
||||
|
||||
Qt 主线程在 `paintEvent` 中绘制 `front_color`。
|
||||
|
||||
后台线程和 asio 调度线程不能写 `front_color` 的像素内容。
|
||||
|
||||
后台发布流程不能直接交换或修改 `front_color`。
|
||||
|
||||
### ready_color
|
||||
|
||||
`ready_color` 是后台已经生成完成、等待 Qt 主线程消费的颜色图。
|
||||
|
||||
`ready_color` 是后台发布线程和 Qt 主线程之间的唯一交接槽。
|
||||
|
||||
如果旧的 `ready_color` 还没被 Qt 主线程消费,而新的 `render_color` 又完成了,直接用新的 `render_color` 覆盖旧的
|
||||
`ready_color`。
|
||||
|
||||
图表库追最新状态,不做帧队列。
|
||||
|
||||
### render_color
|
||||
|
||||
`render_color` 是后台线程正在写入的颜色图。
|
||||
|
||||
Qt 主线程不能读 `render_color`。
|
||||
|
||||
Qt 主线程不能交换 `render_color`。
|
||||
|
||||
后台 CPU 任务只写 `render_color`。
|
||||
|
||||
## 颜色版本号
|
||||
|
||||
每个颜色缓冲对象都带 `version`。
|
||||
|
||||
示例结构:
|
||||
|
||||
```cpp
|
||||
struct Color_Buffer {
|
||||
std::uint64_t version = 0;
|
||||
QImage image;
|
||||
};
|
||||
```
|
||||
|
||||
颜色缓冲的 `version` 表示:
|
||||
|
||||
```text
|
||||
这张颜色图对应哪个 state 版本
|
||||
```
|
||||
|
||||
后台 CPU 渲染完成后,必须设置:
|
||||
|
||||
```text
|
||||
render_color.version = render_state.version
|
||||
```
|
||||
|
||||
颜色版本不是独立递增出来的,而是从状态版本传递过来的。
|
||||
|
||||
完整版本流向:
|
||||
|
||||
```text
|
||||
edit_state.version
|
||||
↓ submit / snapshot
|
||||
render_state.version
|
||||
↓ CPU render
|
||||
render_color.version
|
||||
↓ publish
|
||||
ready_color.version
|
||||
↓ paintEvent consume
|
||||
front_color.version
|
||||
```
|
||||
|
||||
## 版本号原则
|
||||
|
||||
每个缓冲对象都带 `version`。
|
||||
|
||||
版本号跟随缓冲对象,不单独跟随槽位。
|
||||
|
||||
版本号属于缓冲对象,不属于 `front`、`ready`、`render` 这些槽位名称。
|
||||
|
||||
也就是说,交换的是缓冲对象指针,版本号跟着缓冲对象一起移动。
|
||||
|
||||
不要让版本号和图像对象分离。
|
||||
|
||||
## 回环版本比较
|
||||
|
||||
版本比较必须封装成支持回环的函数,不要到处直接写 `>`。
|
||||
|
||||
函数语义:
|
||||
|
||||
```text
|
||||
version_newer(a, b)
|
||||
```
|
||||
|
||||
表示:
|
||||
|
||||
```text
|
||||
a 是否新于 b
|
||||
```
|
||||
|
||||
可以使用 `uint64_t`,并通过有符号差值处理回环。
|
||||
|
||||
前提是两个待比较版本之间的距离不会超过半个取值空间。
|
||||
|
||||
示例语义:
|
||||
|
||||
```cpp
|
||||
static bool version_newer(std::uint64_t a, std::uint64_t b) {
|
||||
return static_cast<std::int64_t>(a - b) > 0;
|
||||
}
|
||||
```
|
||||
|
||||
所有版本新旧判断都必须通过该函数完成。
|
||||
|
||||
## 颜色缓冲分配原则
|
||||
|
||||
颜色缓冲对象固定分配。
|
||||
|
||||
槽位只交换指针。
|
||||
|
||||
不要在发布流程里反复分配或释放颜色缓冲对象。
|
||||
|
||||
推荐模型:
|
||||
|
||||
```text
|
||||
front_color 指针
|
||||
ready_color 指针
|
||||
render_color 指针
|
||||
```
|
||||
|
||||
三个槽位分别指向三个固定分配的 `Color_Buffer` 对象。
|
||||
|
||||
交换槽位时只交换指针,不拷贝 QImage,不拷贝像素数组。
|
||||
|
||||
## 颜色缓冲锁原则
|
||||
|
||||
所有跨线程槽位交换必须加锁。
|
||||
|
||||
锁内只允许做:
|
||||
|
||||
```text
|
||||
版本判断
|
||||
指针交换
|
||||
状态标记
|
||||
读取 dirty rect
|
||||
设置必要标志位
|
||||
```
|
||||
|
||||
锁内禁止做:
|
||||
|
||||
```text
|
||||
像素计算
|
||||
内存分配
|
||||
QImage 填充
|
||||
drawImage
|
||||
复杂状态计算
|
||||
后台等待
|
||||
任务提交等待
|
||||
```
|
||||
|
||||
原则:
|
||||
|
||||
```text
|
||||
大计算锁外做。
|
||||
锁内只换身份。
|
||||
```
|
||||
|
||||
## 颜色缓冲唯一合法交换
|
||||
|
||||
整个系统只允许两个颜色缓冲交换操作。
|
||||
|
||||
后台发布:
|
||||
|
||||
```text
|
||||
ready_color <-> render_color
|
||||
```
|
||||
|
||||
Qt 主线程消费:
|
||||
|
||||
```text
|
||||
front_color <-> ready_color
|
||||
```
|
||||
|
||||
不允许:
|
||||
|
||||
```text
|
||||
front_color <-> render_color
|
||||
后台线程操作 front_color
|
||||
Qt 主线程操作 render_color
|
||||
绕过 ready_color 直接发布
|
||||
```
|
||||
|
||||
## 后台发布流程
|
||||
|
||||
后台 CPU 任务只写 `render_color`。
|
||||
|
||||
`render_color` 填充完成后,完成事件回到 asio 调度线程。
|
||||
|
||||
asio 调度线程在 `color_lock` 下执行发布。
|
||||
|
||||
发布逻辑:
|
||||
|
||||
```text
|
||||
如果 render_color.version 新于 ready_color.version:
|
||||
交换 ready_color 和 render_color
|
||||
```
|
||||
|
||||
交换后判断:
|
||||
|
||||
```text
|
||||
如果 ready_color.version 新于 front_color.version:
|
||||
scheduler->request_update()
|
||||
```
|
||||
|
||||
如果旧 `ready_color` 还没被 Qt 主线程消费,而新 `render_color` 又完成了:
|
||||
|
||||
```text
|
||||
直接用新的 render_color 覆盖旧 ready_color。
|
||||
旧 ready_color 变成新的 render_color,下一轮会被后台覆盖。
|
||||
```
|
||||
|
||||
图表库只保留最新待显示图,不做帧队列。
|
||||
|
||||
## Qt paintEvent 流程
|
||||
|
||||
`paintEvent` 中在 `color_lock` 下判断:
|
||||
|
||||
```text
|
||||
如果 ready_color.version 新于 front_color.version:
|
||||
交换 ready_color 和 front_color
|
||||
```
|
||||
|
||||
释放锁后执行:
|
||||
|
||||
```text
|
||||
drawImage(front_color)
|
||||
```
|
||||
|
||||
`paintEvent` 不允许做:
|
||||
|
||||
```text
|
||||
状态计算
|
||||
颜色映射
|
||||
后台等待
|
||||
任务提交
|
||||
像素生成
|
||||
大数组遍历
|
||||
```
|
||||
|
||||
`paintEvent` 只负责消费最新 `ready_color` 并绘制 `front_color`。
|
||||
|
||||
即使没有新 `ready_color`,`paintEvent` 也必须能够直接绘制旧的 `front_color`。
|
||||
|
||||
因为 Qt 可能因为窗口遮挡、恢复、缩放、系统重绘等原因触发 `paintEvent`。
|
||||
|
||||
## 刷新请求流程
|
||||
|
||||
后台发布 `ready_color` 后,不直接在后台线程操作 QWidget。
|
||||
|
||||
`scheduler->request_update()` 负责把刷新请求投递到 Qt GUI 线程。
|
||||
|
||||
刷新请求需要使用 `paint_request_pending` 合并重复 update 请求。
|
||||
|
||||
多个图完成时合并 dirty region。
|
||||
|
||||
不要每个图都直接 update 整个窗口。
|
||||
|
||||
Qt 主线程收到刷新任务后调用:
|
||||
|
||||
```text
|
||||
QWidget::update(region)
|
||||
```
|
||||
|
||||
由 Qt 稍后触发 `paintEvent`。
|
||||
|
||||
## paint_request_pending 语义
|
||||
|
||||
`paint_request_pending` 表示:
|
||||
|
||||
```text
|
||||
已经有一个刷新请求投递到 Qt GUI 线程,暂时不要重复投递
|
||||
```
|
||||
|
||||
判断是否投递 update:
|
||||
|
||||
```text
|
||||
ready_color.version 新于 front_color.version
|
||||
并且 paint_request_pending == false
|
||||
```
|
||||
|
||||
`paint_request_pending` 只用于合并 Qt update 请求,不用于判断颜色图是否新。
|
||||
|
||||
颜色图是否新仍然通过:
|
||||
|
||||
```text
|
||||
version_newer(ready_color.version, front_color.version)
|
||||
```
|
||||
|
||||
判断。
|
||||
|
||||
## 任务提交原则
|
||||
|
||||
每个图同一时间最多一个后台渲染任务。
|
||||
|
||||
如果任务正在 `Queued` 或 `Running`,不再提交同一图的新任务。
|
||||
|
||||
任务运行期间 `edit_state` 又变化,只记录新版本。
|
||||
|
||||
当前任务完成后,下一轮直接提交最新 `edit_state`。
|
||||
|
||||
不要排队计算中间版本。
|
||||
|
||||
图不可见、尺寸无效、版本没变时,不提交任务。
|
||||
|
||||
## 任务状态
|
||||
|
||||
每个图至少需要表达以下任务状态:
|
||||
|
||||
```text
|
||||
Idle
|
||||
Queued
|
||||
Running
|
||||
```
|
||||
|
||||
状态语义:
|
||||
|
||||
```text
|
||||
Idle:
|
||||
当前图没有后台任务。
|
||||
|
||||
Queued:
|
||||
当前图的后台任务已经提交,但还没开始执行。
|
||||
|
||||
Running:
|
||||
当前图的后台任务正在执行。
|
||||
```
|
||||
|
||||
提交条件:
|
||||
|
||||
```text
|
||||
job_state == Idle
|
||||
edit_state.version 新于 render_state.version
|
||||
图可见
|
||||
尺寸有效
|
||||
没有超过提交频率限制
|
||||
```
|
||||
|
||||
## 状态缓冲提交流程
|
||||
|
||||
asio 调度线程判断:
|
||||
|
||||
```text
|
||||
edit_state.version 是否新于 render_state.version
|
||||
```
|
||||
|
||||
如果需要提交,并且当前图没有任务在飞:
|
||||
|
||||
```text
|
||||
生成 render_state 快照
|
||||
render_state.version = edit_state.version
|
||||
标记任务运行
|
||||
把 render_state 对应的计算任务提交到后台 CPU 执行环境
|
||||
```
|
||||
|
||||
CPU 任务只读取 `render_state` 快照并写 `render_color`。
|
||||
|
||||
CPU 任务不直接修改 `edit_state`。
|
||||
|
||||
CPU 任务不直接发布 `ready_color`。
|
||||
|
||||
CPU 任务完成后 post 回 asio 调度线程,由 asio 调度线程执行颜色缓冲发布。
|
||||
|
||||
## 过期任务处理原则
|
||||
|
||||
如果后台任务计算的是较旧版本,例如:
|
||||
|
||||
```text
|
||||
任务开始时 render_state.version = 100
|
||||
任务运行期间 edit_state.version 已经变成 110
|
||||
```
|
||||
|
||||
默认策略:
|
||||
|
||||
```text
|
||||
允许 version 100 的结果发布
|
||||
任务完成后下一轮直接提交最新 edit_state.version = 110
|
||||
不排队计算 101 到 109
|
||||
```
|
||||
|
||||
这样界面不会长时间完全没有更新,同时也不会浪费 CPU 计算中间版本。
|
||||
|
||||
如果某些图要求严格不显示过期结果,可以单独设计策略,但默认通用框架采用“允许当前任务结果发布,下一轮追最新”的方式。
|
||||
|
||||
## render_color 写入原则
|
||||
|
||||
后台 CPU 任务写 `render_color` 前,必须保证 `render_color` 当前不属于 `front_color` 或 `ready_color`。
|
||||
|
||||
这个由三缓冲槽位交换规则保证。
|
||||
|
||||
后台 CPU 任务只能写当前槽位名为 `render_color` 的缓冲对象。
|
||||
|
||||
不要在未持有正确所有权的情况下写任意颜色缓冲。
|
||||
|
||||
如果使用 `QImage`,要注意 Qt 隐式共享。
|
||||
|
||||
每个颜色缓冲应持有独立的 QImage 存储。
|
||||
|
||||
后台写像素前,需要保证 `render_color.image` 独占可写。
|
||||
|
||||
不要让多个颜色缓冲共享同一块 QImage data。
|
||||
|
||||
## 锁和绘制的关系
|
||||
|
||||
锁保护的是:
|
||||
|
||||
```text
|
||||
front_color / ready_color / render_color 三个槽位身份
|
||||
版本号和图像对象的一致性
|
||||
ready_color 这个共享交接区
|
||||
```
|
||||
|
||||
锁不保护:
|
||||
|
||||
```text
|
||||
后台像素写入过程
|
||||
主线程 drawImage 的耗时过程
|
||||
CPU 计算过程
|
||||
状态计算过程
|
||||
```
|
||||
|
||||
正确方式:
|
||||
|
||||
```text
|
||||
后台:
|
||||
锁外写 render_color.image
|
||||
锁内 ready_color <-> render_color
|
||||
|
||||
Qt GUI:
|
||||
锁内 ready_color <-> front_color
|
||||
锁外 drawImage(front_color.image)
|
||||
```
|
||||
|
||||
## 通用缓冲控制抽象
|
||||
|
||||
需要重构出一个通用缓冲控制抽象。
|
||||
|
||||
该抽象负责:
|
||||
|
||||
```text
|
||||
状态版本管理
|
||||
状态快照提交
|
||||
任务 single-flight 控制
|
||||
颜色三缓冲管理
|
||||
版本回环比较
|
||||
颜色发布
|
||||
Qt update 请求合并
|
||||
dirty region 合并
|
||||
```
|
||||
|
||||
具体图只实现:
|
||||
|
||||
```text
|
||||
State -> Color 的 CPU 渲染逻辑
|
||||
```
|
||||
|
||||
不要把线程控制、版本控制、颜色缓冲交换散落到每个具体图实现里。
|
||||
|
||||
## 最终模型总结
|
||||
|
||||
每个图拥有:
|
||||
|
||||
```text
|
||||
edit_state
|
||||
render_state
|
||||
|
||||
front_color
|
||||
ready_color
|
||||
render_color
|
||||
```
|
||||
|
||||
版本流向:
|
||||
|
||||
```text
|
||||
edit_state.version
|
||||
↓
|
||||
render_state.version
|
||||
↓
|
||||
render_color.version
|
||||
↓
|
||||
ready_color.version
|
||||
↓
|
||||
front_color.version
|
||||
```
|
||||
|
||||
线程职责:
|
||||
|
||||
```text
|
||||
Qt GUI 线程:
|
||||
接收用户事件
|
||||
投递状态修改命令
|
||||
执行 QWidget::update(region)
|
||||
paintEvent 中消费 ready_color
|
||||
drawImage(front_color)
|
||||
|
||||
asio 调度线程:
|
||||
串行修改 edit_state
|
||||
递增 edit_state.version
|
||||
判断是否提交后台任务
|
||||
生成 render_state 快照
|
||||
接收后台任务完成事件
|
||||
发布 ready_color
|
||||
合并刷新请求
|
||||
|
||||
后台 CPU 执行环境:
|
||||
读取 render_state
|
||||
写 render_color
|
||||
完成后 post 回 asio 调度线程
|
||||
```
|
||||
|
||||
核心原则:
|
||||
|
||||
```text
|
||||
state version 驱动计算。
|
||||
color version 追踪产物。
|
||||
single-flight 避免堆任务。
|
||||
三颜色缓冲保证图像安全交接。
|
||||
ready_color 只保存最新待显示图。
|
||||
paintEvent 只贴 front_color。
|
||||
锁内只交换指针。
|
||||
大计算全部锁外完成。
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user