还是一堆bug的状态
This commit is contained in:
+1
-2
@@ -1,6 +1,6 @@
|
||||
if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_LIST_DIR})
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
cmake_policy(SET CMP0141 NEW)
|
||||
# cmake_policy(SET CMP0141 NEW)
|
||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$<$<CONFIG:Debug,RelWithDebInfo>:Embedded>")
|
||||
project(Radio)
|
||||
set(Radio_VERSION 1.0.0)
|
||||
@@ -12,7 +12,6 @@ if (${CMAKE_SOURCE_DIR} STREQUAL ${CMAKE_CURRENT_LIST_DIR})
|
||||
include(${CMAKE_SOURCE_DIR}/main.cmake)
|
||||
include(third_party/build_infra/end.cmake)
|
||||
else ()
|
||||
|
||||
endif ()
|
||||
|
||||
|
||||
|
||||
-156
@@ -1,156 +0,0 @@
|
||||
# startRender(int fps) 作用流程 FAQ
|
||||
|
||||
## startRender(int fps) 做了什么
|
||||
|
||||
`startRender(int refreshTimesPreSecond)` 只做两件事:
|
||||
|
||||
```text
|
||||
保存 mRefreshTimesPreSecond
|
||||
调用 startRender()
|
||||
```
|
||||
|
||||
对应代码流程:
|
||||
|
||||
```text
|
||||
Plot::startRender(int fps)
|
||||
↓
|
||||
d->mRefreshTimesPreSecond = fps
|
||||
↓
|
||||
Plot::startRender()
|
||||
```
|
||||
|
||||
## startRender() 做了什么
|
||||
|
||||
`startRender()` 的作用是启动 Plot 的渲染调度。
|
||||
|
||||
流程:
|
||||
|
||||
```text
|
||||
检查 Plot 是否正在析构
|
||||
↓
|
||||
mRenderEnabled = true
|
||||
↓
|
||||
投递任务到 asio scheduler
|
||||
↓
|
||||
取消旧 mRenderTimer
|
||||
↓
|
||||
scheduleRenderTimer()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
它会立即触发一次 `submitRender()`,不是等下一个 timer 周期。
|
||||
|
||||
## fps 参数现在是不是严格帧率上限
|
||||
|
||||
不是。
|
||||
|
||||
当前 `fps` 只决定 asio timer 的周期:
|
||||
|
||||
```text
|
||||
interval = round(1000.0 / fps)
|
||||
```
|
||||
|
||||
timer 到期后:
|
||||
|
||||
```text
|
||||
submitRender()
|
||||
scheduleRenderTimer()
|
||||
```
|
||||
|
||||
所以它的真实语义是周期性 heartbeat / 兜底触发,不是硬性 FPS cap。
|
||||
|
||||
## 为什么不是严格限帧
|
||||
|
||||
因为当前渲染是事件驱动为主。
|
||||
|
||||
除了 timer 会触发 `submitRender()`,下面这些路径也会立即触发:
|
||||
|
||||
```text
|
||||
RenderAble::markStateDirty()
|
||||
↓
|
||||
Plot::markRenderStateDirty()
|
||||
↓
|
||||
requestRender()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
```text
|
||||
RenderData::markInputDirty()
|
||||
↓
|
||||
RenderAble::markRenderDirty()
|
||||
↓
|
||||
requestRender()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
```text
|
||||
finishRender()
|
||||
↓
|
||||
pipeline.hasNewState()
|
||||
↓
|
||||
submitRender()
|
||||
```
|
||||
|
||||
只要 State/Input 有新版本,系统可以绕过 timer 周期继续推进下一帧。
|
||||
|
||||
## asio 里面还有没有定时器
|
||||
|
||||
有。
|
||||
|
||||
相关成员和函数:
|
||||
|
||||
```text
|
||||
PlotPrivate::mRenderTimer
|
||||
PlotPrivate::mRefreshTimesPreSecond
|
||||
Plot::scheduleRenderTimer()
|
||||
RenderScheduler::initializePlotOnScheduler()
|
||||
```
|
||||
|
||||
`RenderScheduler::initializePlotOnScheduler()` 为每个 Plot 创建 `asio::steady_timer`。
|
||||
|
||||
`scheduleRenderTimer()` 使用 `mRefreshTimesPreSecond` 计算间隔并注册 `async_wait`。
|
||||
|
||||
## timer 在当前架构里还有没有必要
|
||||
|
||||
当前仍然有必要保留。
|
||||
|
||||
原因:
|
||||
|
||||
```text
|
||||
保持 startRender(int) 旧调用语义
|
||||
保证没有显式 dirty 事件时仍有周期性检查
|
||||
作为异常丢事件后的兜底触发
|
||||
给性能测试框提供一个稳定的周期参考
|
||||
```
|
||||
|
||||
但它不是渲染正确性的唯一入口。
|
||||
|
||||
当前正确性主要依赖:
|
||||
|
||||
```text
|
||||
State dirty
|
||||
Input dirty
|
||||
finishRender 后续版本检查
|
||||
```
|
||||
|
||||
## 如果以后要严格限帧应该怎么做
|
||||
|
||||
应该新增独立调度策略,而不是继续复用 `startRender(int)` 的现有语义。
|
||||
|
||||
建议拆成:
|
||||
|
||||
```text
|
||||
Immediate:
|
||||
dirty 后立即 requestRender()
|
||||
|
||||
FixedRate:
|
||||
dirty 后只标记 pending,由 timer 周期统一 submitRender()
|
||||
|
||||
Throttle:
|
||||
dirty 后允许立即渲染,但两帧之间必须满足最小间隔
|
||||
```
|
||||
|
||||
这样 `startRender(int)` 才能变成明确的帧率策略参数。
|
||||
@@ -1,687 +0,0 @@
|
||||
下面这个就是我建议你这个 **多图表后台渲染库**的完整模型。
|
||||
|
||||
核心目标是:
|
||||
|
||||
```text
|
||||
元数据变化才计算
|
||||
后台只算最新状态
|
||||
主线程只贴图
|
||||
多个图统一调度
|
||||
不堆旧任务
|
||||
不显示半成品
|
||||
不因为 paintEvent 无意义重算
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 1. 总体管线
|
||||
|
||||
每个图都是这个流程:
|
||||
|
||||
```text
|
||||
主线程修改元数据
|
||||
↓
|
||||
meta_version++
|
||||
↓
|
||||
调度器定时扫描
|
||||
↓
|
||||
判断这个图是否需要提交 CPU 任务
|
||||
↓
|
||||
生成元数据快照 Render_Input
|
||||
↓
|
||||
扔进 CPU 线程池
|
||||
↓
|
||||
CPU 线程生成 Pixel_Frame
|
||||
↓
|
||||
发布到三缓冲 ready
|
||||
↓
|
||||
主线程 update 合并
|
||||
↓
|
||||
paintEvent 消费 ready,贴 front
|
||||
```
|
||||
|
||||
也就是:
|
||||
|
||||
```text
|
||||
Meta -> Render_Input -> CPU Render -> Pixel_Frame -> paintEvent drawImage
|
||||
```
|
||||
|
||||
每种具体图只需要实现:
|
||||
|
||||
```text
|
||||
元数据 -> 像素图
|
||||
```
|
||||
|
||||
线程控制、版本控制、三缓冲、刷新合并都放到通用框架里。
|
||||
|
||||
---
|
||||
|
||||
# 2. 每个图不要自己开线程
|
||||
|
||||
一堆图不能每个图一个线程、一个 timer。
|
||||
|
||||
应该是:
|
||||
|
||||
```text
|
||||
一个 Render_Scheduler
|
||||
一个 CPU Thread_Pool
|
||||
多个 Chart_Node
|
||||
```
|
||||
|
||||
结构类似:
|
||||
|
||||
```text
|
||||
Render_Scheduler
|
||||
Chart_Node 1
|
||||
Chart_Node 2
|
||||
Chart_Node 3
|
||||
...
|
||||
Thread_Pool
|
||||
```
|
||||
|
||||
调度器负责:
|
||||
|
||||
```text
|
||||
扫描哪些图需要重算
|
||||
限制每轮提交数量
|
||||
限制单图提交频率
|
||||
跳过不可见图
|
||||
合并 update 区域
|
||||
控制 CPU 线程池压力
|
||||
```
|
||||
|
||||
具体图只负责:
|
||||
|
||||
```text
|
||||
准备元数据
|
||||
执行 CPU 绘制算法
|
||||
提供 dirty rect / 绘制区域
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 3. 每个图的版本号
|
||||
|
||||
每个图建议维护这些版本:
|
||||
|
||||
```cpp
|
||||
struct Chart_Versions {
|
||||
std::uint64_t meta = 0;
|
||||
std::uint64_t submitted = 0;
|
||||
std::uint64_t running = 0;
|
||||
std::uint64_t rendered = 0;
|
||||
std::uint64_t requested_paint = 0;
|
||||
std::uint64_t painted = 0;
|
||||
};
|
||||
```
|
||||
|
||||
语义:
|
||||
|
||||
```text
|
||||
meta
|
||||
主线程最新元数据版本。
|
||||
|
||||
submitted
|
||||
已经提交给 CPU 线程池的版本。
|
||||
|
||||
running
|
||||
当前 CPU 正在计算的版本。
|
||||
|
||||
rendered
|
||||
后台已经生成像素图的版本。
|
||||
|
||||
requested_paint
|
||||
已经请求主线程刷新的版本。
|
||||
|
||||
painted
|
||||
paintEvent 已经贴出去的版本。
|
||||
```
|
||||
|
||||
这里不要用 `OK`、`imageReady` 这种 bool。
|
||||
|
||||
bool 只能表示:
|
||||
|
||||
```text
|
||||
有 / 没有
|
||||
```
|
||||
|
||||
版本号能表示:
|
||||
|
||||
```text
|
||||
是哪一版
|
||||
新不新
|
||||
有没有提交
|
||||
有没有生成
|
||||
有没有请求 update
|
||||
有没有真正 paint
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 4. 元数据层:双缓冲或者快照
|
||||
|
||||
元数据是用户操作和数据输入修改的东西,比如:
|
||||
|
||||
```text
|
||||
坐标范围
|
||||
颜色表
|
||||
缩放
|
||||
图尺寸
|
||||
数据源
|
||||
采样参数
|
||||
显示开关
|
||||
曲线样式
|
||||
```
|
||||
|
||||
主线程修改元数据:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::mark_meta_changed() {
|
||||
++versions.meta;
|
||||
}
|
||||
```
|
||||
|
||||
调度器提交任务时生成快照:
|
||||
|
||||
```cpp
|
||||
Render_Input Chart_Node::make_render_input() {
|
||||
Render_Input input;
|
||||
input.meta = edit_meta;
|
||||
input.version = versions.meta;
|
||||
return input;
|
||||
}
|
||||
```
|
||||
|
||||
如果元数据很小,直接拷贝快照。
|
||||
|
||||
如果元数据很大,用双缓冲或者共享不可变快照。
|
||||
|
||||
这一层一般不需要三缓冲。
|
||||
|
||||
原因是元数据中间版本可以丢,只要后台下一轮拿最新版本即可。
|
||||
|
||||
---
|
||||
|
||||
# 5. 任务层:single-flight
|
||||
|
||||
每个图同一时间最多一个后台任务。
|
||||
|
||||
不要这样:
|
||||
|
||||
```text
|
||||
version 100 提交
|
||||
version 101 提交
|
||||
version 102 提交
|
||||
version 103 提交
|
||||
```
|
||||
|
||||
这样会造成旧任务堵新任务。
|
||||
|
||||
应该这样:
|
||||
|
||||
```text
|
||||
一个图最多一个任务在飞
|
||||
任务运行期间 meta 变了,只记录版本变化
|
||||
任务完成后调度器下一轮再提交最新版本
|
||||
```
|
||||
|
||||
任务状态:
|
||||
|
||||
```cpp
|
||||
enum class Render_Job_State {
|
||||
Idle,
|
||||
Queued,
|
||||
Running
|
||||
};
|
||||
```
|
||||
|
||||
提交条件:
|
||||
|
||||
```cpp
|
||||
bool Chart_Node::need_submit(std::uint64_t now_ms) const {
|
||||
if (!visible) {
|
||||
return false;
|
||||
}
|
||||
if (!valid_size) {
|
||||
return false;
|
||||
}
|
||||
if (job_state != Render_Job_State::Idle) {
|
||||
return false;
|
||||
}
|
||||
if (versions.meta == versions.submitted) {
|
||||
return false;
|
||||
}
|
||||
if (now_ms - last_submit_time < min_render_interval) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
```
|
||||
|
||||
核心原则:
|
||||
|
||||
```text
|
||||
版本没变,不算。
|
||||
图不可见,不算。
|
||||
尺寸无效,不算。
|
||||
已经有任务在跑,不算。
|
||||
刚算过,间隔太短,不算。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 6. 像素结果层:三缓冲
|
||||
|
||||
像素层建议三缓冲:
|
||||
|
||||
```cpp
|
||||
struct Pixel_Frame {
|
||||
std::uint64_t version = 0;
|
||||
QImage image;
|
||||
};
|
||||
struct Pixel_Buffers {
|
||||
Pixel_Frame front;
|
||||
Pixel_Frame ready;
|
||||
Pixel_Frame working;
|
||||
};
|
||||
```
|
||||
|
||||
三张图语义:
|
||||
|
||||
```text
|
||||
front
|
||||
主线程 paintEvent 当前稳定显示的图。
|
||||
|
||||
ready
|
||||
后台已经生成好,等待主线程消费的图。
|
||||
|
||||
working
|
||||
后台线程正在写入的图。
|
||||
```
|
||||
|
||||
三缓冲解决的是:
|
||||
|
||||
```text
|
||||
主线程读 front
|
||||
后台写 working
|
||||
ready 作为交接区
|
||||
```
|
||||
|
||||
它不是为了替代版本号,而是为了控制图像内存所有权。
|
||||
|
||||
---
|
||||
|
||||
# 7. 后台任务完成后的发布规则
|
||||
|
||||
后台线程生成完 `working` 后,不直接碰 `front`。
|
||||
|
||||
只发布到 `ready`。
|
||||
|
||||
如果 `ready` 还没被主线程消费,新 `working` 又生成完了:
|
||||
|
||||
```text
|
||||
旧 ready 丢掉
|
||||
新 working 覆盖 ready
|
||||
front 继续稳定显示
|
||||
```
|
||||
|
||||
图表库不需要按帧播放旧图,应该追最新状态。
|
||||
|
||||
发布逻辑:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::publish_working_frame(Pixel_Frame frame) {
|
||||
bool need_update = false;
|
||||
{
|
||||
std::scoped_lock lock(pixel_lock);
|
||||
if (frame.version <= pixels.ready.version) {
|
||||
return;
|
||||
}
|
||||
pixels.working = std::move(frame);
|
||||
pixels.ready.swap(pixels.working);
|
||||
versions.rendered = pixels.ready.version;
|
||||
if (versions.rendered != versions.requested_paint) {
|
||||
versions.requested_paint = versions.rendered;
|
||||
need_update = true;
|
||||
}
|
||||
job_state = Render_Job_State::Idle;
|
||||
}
|
||||
if (need_update) {
|
||||
scheduler->request_update(this);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
状态变化示例:
|
||||
|
||||
```text
|
||||
发布前:
|
||||
front = 100
|
||||
ready = 101
|
||||
working = 102
|
||||
|
||||
发布后:
|
||||
front = 100
|
||||
ready = 102
|
||||
working = 101
|
||||
```
|
||||
|
||||
旧 `ready = 101` 被丢掉,变成新的空闲 working 槽,后面会被覆盖。
|
||||
|
||||
---
|
||||
|
||||
# 8. 已经开始生成的 working 要不要停
|
||||
|
||||
不要停。
|
||||
|
||||
规则是:
|
||||
|
||||
```text
|
||||
已经开始生成的 working:继续生成完。
|
||||
还没开始的新任务:由调度器根据版本、间隔、状态决定是否提交。
|
||||
```
|
||||
|
||||
原因:
|
||||
|
||||
```text
|
||||
CPU 任务中途取消复杂。
|
||||
生成完的新版本可以覆盖旧 ready。
|
||||
single-flight 已经保证不会无限堆任务。
|
||||
```
|
||||
|
||||
所以正确策略是:
|
||||
|
||||
```text
|
||||
working 已经开始:继续。
|
||||
ready 没消费:不等。
|
||||
新 working 完成:覆盖旧 ready。
|
||||
下一轮是否继续算:由调度器判断。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 9. 主线程 paintEvent 消费规则
|
||||
|
||||
主线程 `paintEvent()` 前先尝试消费 `ready`:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::prepare_paint() {
|
||||
std::scoped_lock lock(pixel_lock);
|
||||
if (pixels.ready.version <= pixels.front.version) {
|
||||
return;
|
||||
}
|
||||
pixels.front.swap(pixels.ready);
|
||||
versions.painted = pixels.front.version;
|
||||
}
|
||||
```
|
||||
|
||||
然后只贴 `front`:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::paint(QPainter& painter) {
|
||||
prepare_paint();
|
||||
painter.drawImage(rect, pixels.front.image);
|
||||
}
|
||||
```
|
||||
|
||||
`paintEvent()` 不做:
|
||||
|
||||
```text
|
||||
元数据计算
|
||||
颜色映射
|
||||
衰减计算
|
||||
坐标采样
|
||||
大数组遍历
|
||||
后台等待
|
||||
```
|
||||
|
||||
它只做:
|
||||
|
||||
```text
|
||||
ready -> front
|
||||
drawImage(front)
|
||||
```
|
||||
|
||||
如果没有新图,也照样画旧 `front`。
|
||||
|
||||
因为 Qt 可能因为窗口遮挡、缩放、系统刷新等原因主动触发 `paintEvent()`,不能假设每次 paint 都有新数据。
|
||||
|
||||
---
|
||||
|
||||
# 10. update 合并
|
||||
|
||||
后台发布新图以后,不要每个图都直接 `update()` 整个窗口。
|
||||
|
||||
应该由调度器合并刷新区域:
|
||||
|
||||
```text
|
||||
图 A 有新图
|
||||
图 B 有新图
|
||||
图 C 有新图
|
||||
↓
|
||||
合并 dirty rect
|
||||
↓
|
||||
主线程 update(total_dirty_rect)
|
||||
```
|
||||
|
||||
可以加一个 pending 标志,避免重复投递刷新请求:
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::request_update(Chart_Node* chart) {
|
||||
dirty_region += chart->rect();
|
||||
if (paint_request_pending.exchange(true)) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(widget, [this] {
|
||||
paint_request_pending.store(false);
|
||||
QWidget* w = widget;
|
||||
QRegion region = std::move(dirty_region);
|
||||
dirty_region = QRegion();
|
||||
w->update(region);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
```
|
||||
|
||||
这样多个图连续完成渲染,也只触发一次合并后的主线程刷新。
|
||||
|
||||
---
|
||||
|
||||
# 11. 调度器 tick 流程
|
||||
|
||||
调度器定时扫描所有图:
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::tick() {
|
||||
std::uint64_t now_ms = current_time_ms();
|
||||
int submitted_count = 0;
|
||||
for (Chart_Node* chart : charts) {
|
||||
if (submitted_count >= max_submit_per_tick) {
|
||||
break;
|
||||
}
|
||||
if (!chart->need_submit(now_ms)) {
|
||||
continue;
|
||||
}
|
||||
chart->submit_render(thread_pool, now_ms);
|
||||
++submitted_count;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
提交任务:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::submit_render(Thread_Pool& pool, std::uint64_t now_ms) {
|
||||
Render_Input input;
|
||||
{
|
||||
std::scoped_lock lock(meta_lock);
|
||||
input = make_render_input();
|
||||
versions.submitted = input.version;
|
||||
versions.running = input.version;
|
||||
job_state = Render_Job_State::Queued;
|
||||
last_submit_time = now_ms;
|
||||
}
|
||||
pool.enqueue([this, input = std::move(input)]() mutable {
|
||||
run_render_task(std::move(input));
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
CPU 任务:
|
||||
|
||||
```cpp
|
||||
void Chart_Node::run_render_task(Render_Input input) {
|
||||
{
|
||||
std::scoped_lock lock(job_lock);
|
||||
job_state = Render_Job_State::Running;
|
||||
}
|
||||
Pixel_Frame frame;
|
||||
frame.version = input.version;
|
||||
frame.image = render_image(input);
|
||||
publish_working_frame(std::move(frame));
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 12. 过期任务结果怎么处理
|
||||
|
||||
假设:
|
||||
|
||||
```text
|
||||
提交 version 100
|
||||
CPU 正在算
|
||||
用户已经改到 version 110
|
||||
version 100 算完
|
||||
```
|
||||
|
||||
推荐默认策略:
|
||||
|
||||
```text
|
||||
允许发布 version 100
|
||||
但任务完成后,如果 meta 还是新于 submitted,下一轮马上提交最新 version 110
|
||||
```
|
||||
|
||||
这样用户不会长时间看不到任何结果。
|
||||
|
||||
但是不排队算:
|
||||
|
||||
```text
|
||||
101、102、103、104...
|
||||
```
|
||||
|
||||
只算当前正在跑的 100,然后下一轮追最新的 110。
|
||||
|
||||
也就是:
|
||||
|
||||
```text
|
||||
旧结果可以短暂显示
|
||||
旧任务不排队
|
||||
新任务追最新
|
||||
```
|
||||
|
||||
如果某些图对实时一致性要求特别高,也可以配置成:
|
||||
|
||||
```text
|
||||
任务完成时发现 version < meta_version,直接丢弃结果
|
||||
```
|
||||
|
||||
但默认不建议这么激进,否则用户拖动时可能一直看不到更新。
|
||||
|
||||
---
|
||||
|
||||
# 13. 最终状态机
|
||||
|
||||
每个图的任务状态:
|
||||
|
||||
```text
|
||||
Idle
|
||||
↓ submit
|
||||
Queued
|
||||
↓ thread starts
|
||||
Running
|
||||
↓ publish
|
||||
Idle
|
||||
```
|
||||
|
||||
版本流向:
|
||||
|
||||
```text
|
||||
meta_version
|
||||
↓ submit
|
||||
submitted_version
|
||||
↓ run
|
||||
running_version
|
||||
↓ publish
|
||||
rendered_version
|
||||
↓ request update
|
||||
requested_paint_version
|
||||
↓ paintEvent
|
||||
painted_version
|
||||
```
|
||||
|
||||
像素缓冲流向:
|
||||
|
||||
```text
|
||||
CPU writes working
|
||||
↓ publish
|
||||
working <-> ready
|
||||
↓ paintEvent
|
||||
ready <-> front
|
||||
↓ drawImage(front)
|
||||
```
|
||||
|
||||
如果 ready 没消费,新 working 又来了:
|
||||
|
||||
```text
|
||||
working <-> ready
|
||||
旧 ready 丢掉
|
||||
front 不动
|
||||
```
|
||||
|
||||
如果 meta 没变:
|
||||
|
||||
```text
|
||||
不提交任务
|
||||
不生成图
|
||||
不 request update
|
||||
```
|
||||
|
||||
如果没有新 ready:
|
||||
|
||||
```text
|
||||
paintEvent 只画旧 front
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 14. 最终设计原则
|
||||
|
||||
整个模型可以定成这几条:
|
||||
|
||||
```text
|
||||
1. 每个图有自己的元数据版本。
|
||||
2. 每个图同一时间最多一个 CPU 渲染任务。
|
||||
3. 调度器统一扫描所有图,不让每个图自建线程和 timer。
|
||||
4. 元数据层用快照或者双缓冲。
|
||||
5. 像素结果层用三缓冲。
|
||||
6. 三缓冲只管图像内存所有权,不替代版本号。
|
||||
7. ready 只保存最新待显示图,不做帧队列。
|
||||
8. 旧 ready 没被消费时,新 working 直接覆盖它。
|
||||
9. 已经开始的 working 继续算完。
|
||||
10. 是否继续提交新任务由版本号、single-flight、限速、可见性共同决定。
|
||||
11. 主线程 paintEvent 只贴 front,不做重计算。
|
||||
12. update 由调度器合并,不让每个图疯狂 update。
|
||||
```
|
||||
|
||||
一句话总结:
|
||||
|
||||
```text
|
||||
元数据版本号决定要不要算;
|
||||
single-flight 决定不要堆任务;
|
||||
CPU 线程池负责把 Meta 算成 Pixel;
|
||||
三缓冲负责 Pixel 的安全交接;
|
||||
调度器负责多图公平和 update 合并;
|
||||
paintEvent 只负责贴最新 front。
|
||||
```
|
||||
|
||||
这个模型适合你的场景: **一堆图、多线程后台渲染、高频数据变化、主线程只做轻量显示**。
|
||||
-341
@@ -1,341 +0,0 @@
|
||||
我的建议是:
|
||||
|
||||
```text
|
||||
不要靠主线程固定定时器轮询 update。
|
||||
后台生成完新图后,提交一个刷新任务到 Qt 主事件循环。
|
||||
如果你要限帧,再用单次 QTimer 做合并/节流。
|
||||
```
|
||||
|
||||
也就是主线程刷新应该是 **事件驱动**,不是一直定时扫。
|
||||
|
||||
---
|
||||
|
||||
# 1. 不推荐固定主线程 timer 轮询 update
|
||||
|
||||
比如这种方式:
|
||||
|
||||
```text
|
||||
主线程每 16ms 扫描所有图
|
||||
如果 rendered_version > painted_version
|
||||
update()
|
||||
```
|
||||
|
||||
它能工作,但问题是:
|
||||
|
||||
```text
|
||||
没有新图时也一直醒
|
||||
图很多时每次都要扫一遍
|
||||
后台刚生成完图,最多要等一个 timer 周期
|
||||
主线程刷新逻辑和后台完成事件脱节
|
||||
```
|
||||
|
||||
如果你有很多图,这种轮询会变成一种隐性开销。
|
||||
|
||||
尤其是图不可见、数据静止时,timer 仍然在跑,这不优雅。
|
||||
|
||||
---
|
||||
|
||||
# 2. 更推荐:后台发布 ready 后投递主线程刷新任务
|
||||
|
||||
后台线程生成完像素图:
|
||||
|
||||
```text
|
||||
CPU 线程生成 working
|
||||
↓
|
||||
发布到 ready
|
||||
↓
|
||||
发现 rendered_version > requested_paint_version
|
||||
↓
|
||||
投递一个 Qt 主线程任务
|
||||
↓
|
||||
主线程里调用 update(region)
|
||||
```
|
||||
|
||||
注意:后台线程不要直接调用 QWidget 的 `update()`。Qt GUI 对象应该在 GUI 线程使用。
|
||||
|
||||
应该用:
|
||||
|
||||
```cpp
|
||||
QMetaObject::invokeMethod(widget, [this] {
|
||||
widget->update(dirty_region);
|
||||
}, Qt::QueuedConnection);
|
||||
```
|
||||
|
||||
或者用 signal/slot,连接方式是 `Qt::QueuedConnection`。
|
||||
|
||||
---
|
||||
|
||||
# 3. 但是要加 pending,避免疯狂投递
|
||||
|
||||
后台很多图同时完成时,不能每个图都投递一个主线程任务。
|
||||
|
||||
应该有一个全局 pending 标志:
|
||||
|
||||
```cpp
|
||||
std::atomic_bool update_pending = false;
|
||||
```
|
||||
|
||||
请求刷新:
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::request_update(Chart_Node* chart) {
|
||||
{
|
||||
std::scoped_lock lock(dirty_lock);
|
||||
dirty_region += chart->rect();
|
||||
}
|
||||
if (update_pending.exchange(true, std::memory_order_acq_rel)) {
|
||||
return;
|
||||
}
|
||||
QMetaObject::invokeMethod(widget, [this] {
|
||||
flush_update();
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
```
|
||||
|
||||
主线程执行:
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::flush_update() {
|
||||
QRegion region;
|
||||
{
|
||||
std::scoped_lock lock(dirty_lock);
|
||||
region = dirty_region;
|
||||
dirty_region = QRegion();
|
||||
}
|
||||
update_pending.store(false, std::memory_order_release);
|
||||
if (!region.isEmpty()) {
|
||||
widget->update(region);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这样效果是:
|
||||
|
||||
```text
|
||||
后台完成 100 个图
|
||||
↓
|
||||
只投递 1 个主线程刷新任务
|
||||
↓
|
||||
主线程合并 dirty region
|
||||
↓
|
||||
调用一次 update(region)
|
||||
```
|
||||
|
||||
这个比固定 timer 更合适。
|
||||
|
||||
---
|
||||
|
||||
# 4. Qt 的 update 本身也会合并
|
||||
|
||||
`update()` 不是马上 paint,它只是请求 Qt 稍后重绘。
|
||||
|
||||
所以:
|
||||
|
||||
```cpp
|
||||
widget->update();
|
||||
widget->update();
|
||||
widget->update();
|
||||
```
|
||||
|
||||
通常不会立刻触发三次 `paintEvent()`,Qt 会合并。
|
||||
|
||||
但是你自己这层仍然要合并,因为:
|
||||
|
||||
```text
|
||||
Qt 会合并 paint event
|
||||
但你仍然可能投递了大量 QueuedConnection 任务
|
||||
```
|
||||
|
||||
所以你自己的 `update_pending` 是为了减少主线程事件队列压力。
|
||||
|
||||
---
|
||||
|
||||
# 5. timer 应该用在哪里
|
||||
|
||||
timer 不是完全不用,而是用途要分清。
|
||||
|
||||
## 计算调度 timer:可以有
|
||||
|
||||
比如每 16ms / 33ms 扫描哪些图需要提交 CPU 任务:
|
||||
|
||||
```text
|
||||
Render_Scheduler::tick()
|
||||
扫描 meta_version
|
||||
提交必要 CPU 任务
|
||||
```
|
||||
|
||||
这个 timer 管的是:
|
||||
|
||||
```text
|
||||
要不要开始后台计算
|
||||
```
|
||||
|
||||
不是管:
|
||||
|
||||
```text
|
||||
要不要 update
|
||||
```
|
||||
|
||||
也就是说:
|
||||
|
||||
```text
|
||||
计算调度可以定时
|
||||
UI 刷新最好事件驱动
|
||||
```
|
||||
|
||||
## 刷新节流 timer:可以用 single-shot
|
||||
|
||||
如果你想限制 UI 最多 60 FPS,可以不用立刻 `invokeMethod -> update()`,而是主线程启动一个单次 timer:
|
||||
|
||||
```text
|
||||
后台 ready
|
||||
↓
|
||||
投递主线程任务
|
||||
↓
|
||||
主线程发现 flush_timer 没启动
|
||||
↓
|
||||
启动 singleShot(16ms)
|
||||
↓
|
||||
timer 到了再 update(region)
|
||||
```
|
||||
|
||||
这样是 **节流 timer**,不是一直轮询 timer。
|
||||
|
||||
---
|
||||
|
||||
# 6. 最推荐的刷新模型
|
||||
|
||||
我建议你最终用这个:
|
||||
|
||||
```text
|
||||
后台完成图像
|
||||
↓
|
||||
publish ready
|
||||
↓
|
||||
scheduler->request_update(chart)
|
||||
↓
|
||||
合并 dirty region
|
||||
↓
|
||||
如果没有 pending flush
|
||||
invokeMethod 投递主线程 flush
|
||||
↓
|
||||
主线程 flush_update()
|
||||
↓
|
||||
widget->update(region)
|
||||
↓
|
||||
Qt 稍后调用 paintEvent
|
||||
↓
|
||||
paintEvent 中 ready -> front
|
||||
↓
|
||||
drawImage(front)
|
||||
```
|
||||
|
||||
这个模型里:
|
||||
|
||||
```text
|
||||
没有新图,不投递刷新。
|
||||
多个图完成,合并刷新。
|
||||
主线程不轮询。
|
||||
paintEvent 不计算。
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 7. 如果要限帧,用这个变体
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::request_update(Chart_Node* chart) {
|
||||
{
|
||||
std::scoped_lock lock(dirty_lock);
|
||||
dirty_region += chart->rect();
|
||||
}
|
||||
QMetaObject::invokeMethod(widget, [this] {
|
||||
if (flush_timer.isActive()) {
|
||||
return;
|
||||
}
|
||||
flush_timer.start(16);
|
||||
}, Qt::QueuedConnection);
|
||||
}
|
||||
```
|
||||
|
||||
timer timeout:
|
||||
|
||||
```cpp
|
||||
void Render_Scheduler::flush_update() {
|
||||
QRegion region;
|
||||
{
|
||||
std::scoped_lock lock(dirty_lock);
|
||||
region = dirty_region;
|
||||
dirty_region = QRegion();
|
||||
}
|
||||
if (!region.isEmpty()) {
|
||||
widget->update(region);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个语义是:
|
||||
|
||||
```text
|
||||
后台可以随时完成图
|
||||
但 UI 最多约 60Hz 请求 update
|
||||
```
|
||||
|
||||
如果你想 30 FPS:
|
||||
|
||||
```text
|
||||
flush_timer.start(33)
|
||||
```
|
||||
|
||||
如果你想尽快刷新:
|
||||
|
||||
```text
|
||||
invokeMethod 之后直接 update(region)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 8. 最终结论
|
||||
|
||||
你的主线程不需要启动一个固定 timer 去不停判断:
|
||||
|
||||
```text
|
||||
rendered_version 是否变化
|
||||
```
|
||||
|
||||
更合理的是:
|
||||
|
||||
```text
|
||||
后台生成完新 ready 后,投递一个刷新任务到 Qt 主事件循环。
|
||||
```
|
||||
|
||||
然后根据需求加节流:
|
||||
|
||||
```text
|
||||
低延迟:QueuedConnection -> update(region)
|
||||
限帧:QueuedConnection -> singleShot timer -> update(region)
|
||||
```
|
||||
|
||||
所以最终分工是:
|
||||
|
||||
```text
|
||||
后台线程:
|
||||
生成图
|
||||
发布 ready
|
||||
请求调度器刷新
|
||||
|
||||
调度器:
|
||||
合并 dirty region
|
||||
合并 queued update 请求
|
||||
可选限帧
|
||||
|
||||
主线程:
|
||||
执行 update(region)
|
||||
在 paintEvent 中消费 ready 并贴 front
|
||||
```
|
||||
|
||||
一句话:
|
||||
|
||||
```text
|
||||
计算可以用定时器调度,刷新不要用定时器轮询;刷新应该由后台完成事件驱动,再由主线程事件循环合并执行。
|
||||
```
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 415 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 409 KiB |
-1025
File diff suppressed because it is too large
Load Diff
+7
-8
@@ -1,10 +1,8 @@
|
||||
set(module_dir "${CMAKE_CURRENT_LIST_DIR}/module")
|
||||
set(dir "${module_dir}/radio")
|
||||
file(GLOB_RECURSE srcs "${dir}/*.c" "${dir}/*.cpp" "${dir}/*.h" "${dir}/*.hpp" "${dir}/*.qrc")
|
||||
add_executable(radio ${srcs})
|
||||
target_link_libraries(radio PUBLIC Renderive Renderive_Diagnostics Renderive_Widget)
|
||||
# 为了一起维护代码
|
||||
#target_link_libraries(radio PUBLIC Demo_Gallery_Lib)
|
||||
append_glob_source(radio_src ${dir})
|
||||
add_executable(radio ${radio_src})
|
||||
target_link_libraries(radio PUBLIC Psc_Widget Renderive_Qt)
|
||||
if (MSVC)
|
||||
target_compile_definitions(radio PRIVATE MockBackEnd)
|
||||
target_compile_options(radio PRIVATE
|
||||
@@ -14,13 +12,14 @@ endif ()
|
||||
set_property(TARGET radio PROPERTY AUTOMOC ON)
|
||||
set_property(TARGET radio PROPERTY AUTOUIC ON)
|
||||
set_property(TARGET radio PROPERTY AUTORCC ON)
|
||||
# 代码质量相关工具
|
||||
set(radio_document_dirs
|
||||
"${CMAKE_SOURCE_DIR}/module/radio"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/CPP_Core/Core"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/Graphic_Core/Renderive"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/Graphic_Core/Core"
|
||||
)
|
||||
set(radio_document_exclude_dirs
|
||||
"${CMAKE_SOURCE_DIR}/third_party/Graphic_Core/Renderive/base/Virtual_Keyboard/googlepinyin"
|
||||
"${CMAKE_SOURCE_DIR}/third_party/Graphic_Core/Core/base/Virtual_Keyboard/googlepinyin"
|
||||
)
|
||||
psc_add_doxygen_target(psc_radio_doxygen
|
||||
PROJECT_NAME "Radio"
|
||||
@@ -38,7 +37,7 @@ psc_add_graphviz_directory_target(psc_radio_graphviz
|
||||
COLLAPSE_THRESHOLD 20
|
||||
)
|
||||
psc_add_cmake_graphviz_target(psc_radio_cmake_graphviz
|
||||
TARGETS radio Renderive Renderive_Diagnostics Demo_Gallery Core_Static asio_object
|
||||
TARGETS radio Core Renderive_Diagnostics Demo_Gallery Core_Static asio_object
|
||||
OUTPUT_DIR "${CMAKE_BINARY_DIR}/docs/graphviz/radio"
|
||||
FORMAT svg
|
||||
)
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
#include <memory_resource>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "../../../third_party/Renderive/Widget/base/Generate_Mock_Data.h"
|
||||
#include "../../../third_party/Renderive/Renderive/base/Memory.h"
|
||||
#include "Renderive/export.h"
|
||||
namespace {
|
||||
struct Mock_Config {
|
||||
bool performance{};
|
||||
@@ -43,6 +42,10 @@ double make_random_value(renderive::Range range) {
|
||||
std::swap(range.origin, range.target);
|
||||
return renderive::get_data(range);
|
||||
}
|
||||
renderive::Time_Of_Day current_render_time() {
|
||||
const QTime now = QTime::currentTime();
|
||||
return {now.isValid() ? now.msecsSinceStartOfDay() : -1};
|
||||
}
|
||||
void push_radio_data(Radio_Widget* rw) {
|
||||
if (!rw || !rw->spectrogram_line || !rw->water_fall || !rw->time_power || !rw->audio_spectrogram_plot || !rw->progress_bar1 || !rw->progress_bar2)
|
||||
return;
|
||||
@@ -55,12 +58,12 @@ void push_radio_data(Radio_Widget* rw) {
|
||||
rw->progress_bar1->setValue(middle_power);
|
||||
}
|
||||
{
|
||||
rw->water_fall->give_data(QTime::currentTime(), make_random_data(rw->water_fall->power_range(), rw->water_fall->frequency_point_size(), renderive::Memory_Domain::Waterfall));
|
||||
rw->water_fall->give_data(current_render_time(), make_random_data(rw->water_fall->power_range(), rw->water_fall->frequency_point_size(), renderive::Memory_Domain::Waterfall));
|
||||
}
|
||||
{
|
||||
renderive::Range r = rw->time_power->value_axis()->coord_range();
|
||||
renderive::Range nr = {r.middle() - r.length() / 4, r.middle() + r.length() / 4};
|
||||
rw->time_power->give_data(QTime::currentTime(), make_random_value(nr));
|
||||
rw->time_power->give_data(current_render_time(), make_random_value(nr));
|
||||
}
|
||||
{
|
||||
auto r = rw->audio_spectrogram_plot->power_axis()->coord_range();
|
||||
@@ -80,10 +83,10 @@ void push_radio_data_perf(Radio_Widget* rw) {
|
||||
rw->progress_bar1->setValue(middle_power);
|
||||
}
|
||||
{
|
||||
rw->water_fall->give_data(QTime::currentTime(), make_random_data({0, 160}, 1024, renderive::Memory_Domain::Waterfall));
|
||||
rw->water_fall->give_data(current_render_time(), make_random_data({0, 160}, 1024, renderive::Memory_Domain::Waterfall));
|
||||
}
|
||||
{
|
||||
rw->time_power->give_data(QTime::currentTime(), make_random_value({-6000, 6000}));
|
||||
rw->time_power->give_data(current_render_time(), make_random_value({-6000, 6000}));
|
||||
}
|
||||
{
|
||||
rw->audio_spectrogram_plot->give_data(make_random_data({-6000, 6000}, 512, renderive::Memory_Domain::Spectrum));
|
||||
@@ -156,20 +159,13 @@ Back_End::Back_End() {
|
||||
QTimer::singleShot(0, this, [this, config]() {
|
||||
Radio_Widget* rw = Main_Widget::instance()->radio_widget;
|
||||
if (config.performance) {
|
||||
mock_thread = new QThread(this);
|
||||
auto worker = new QObject;
|
||||
worker->moveToThread(mock_thread);
|
||||
QObject::connect(mock_thread, &QThread::finished, worker, &QObject::deleteLater);
|
||||
QObject::connect(mock_thread, &QThread::started, worker, [worker, config, rw]() {
|
||||
auto timer = new QTimer(worker);
|
||||
timer->setTimerType(Qt::PreciseTimer);
|
||||
QObject::connect(timer, &QTimer::timeout, worker, [config, rw]() {
|
||||
for (int i = 0; i < config.batch_count; ++i)
|
||||
push_radio_data_perf(rw);
|
||||
});
|
||||
timer->start(config.interval_ms);
|
||||
auto timer = new QTimer(this);
|
||||
timer->setTimerType(Qt::PreciseTimer);
|
||||
QObject::connect(timer, &QTimer::timeout, this, [config, rw]() {
|
||||
for (int i = 0; i < config.batch_count; ++i)
|
||||
push_radio_data_perf(rw);
|
||||
});
|
||||
mock_thread->start();
|
||||
timer->start(config.interval_ms);
|
||||
return;
|
||||
}
|
||||
auto timer = new QTimer(this);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
#include "../BaseWidget.h"
|
||||
#include "../menu/BaseWidgetMenu.h"
|
||||
#include "../BackEnd/MainWidget.h"
|
||||
#include "../../../third_party/Renderive/Demo_Gallery/base/Tool.h"
|
||||
#include "Demo_Gallery/base/export.h"
|
||||
static renderive::Range frequent_range = {-12000, 12000};
|
||||
static double max = 160;
|
||||
QWidget* Intermediate_Frequency_Widget::create_spectrogram_power_plot() {
|
||||
@@ -31,13 +31,13 @@ QWidget* Intermediate_Frequency_Widget::create_spectrogram_power_plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_tick_length(-10)
|
||||
.set_coord_range(frequent_range)
|
||||
.set_tick_length(-10)
|
||||
.set_sub_tick_length(-5)
|
||||
.build();
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({160, 0})
|
||||
.set_unit_text("功率")
|
||||
.build();
|
||||
@@ -98,13 +98,13 @@ QWidget* Intermediate_Frequency_Widget::create_Afterglow_Plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_coord_range(frequent_range)
|
||||
.set_tick_length(-10)
|
||||
.set_sub_tick_length(-5)
|
||||
.set_use_drag(true)
|
||||
.build();
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({160, 0})
|
||||
.set_use_drag(true)
|
||||
.build();
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#include "../menu/BaseWidgetMenu.h"
|
||||
#include "BackEnd.h"
|
||||
#include "../BackEnd/MainWidget.h"
|
||||
#include "../../../third_party/Renderive/Demo_Gallery/base/Tool.h"
|
||||
#include "Demo_Gallery/base/export.h"
|
||||
static bool use_Performance_shower = false;
|
||||
static double max = 160;
|
||||
static renderive::Range frequent_range = {-12000, 12000};
|
||||
@@ -47,11 +47,11 @@ QWidget* Radio_Widget::create_spectrogram_power_plot() {
|
||||
auto axis_cache = create_renderable_node(get_root_renderable(), "Power_Axis_Cache");
|
||||
axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
x_axis = renderive::Frequency_Axis::Builder(axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Frequency_Axis::Builder(axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_coord_range(frequent_range)
|
||||
.set_tick_length(-10)
|
||||
.build();
|
||||
y_axis = renderive::Axis::Builder(axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({max, 0})
|
||||
.set_unit_text("功率")
|
||||
.build();
|
||||
@@ -115,12 +115,12 @@ QWidget* Radio_Widget::create_water_fall_plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
stream_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_tick_length(-10)
|
||||
.set_sub_tick_length(-5)
|
||||
.set_coord_range(frequent_range)
|
||||
.build();
|
||||
y_axis = renderive::Time_Axis::Builder(time_axis, Qt::Vertical)
|
||||
y_axis = renderive::Time_Axis::Builder(time_axis, renderive::Orientation::Vertical)
|
||||
.set_time_point_size(100)
|
||||
.build();
|
||||
wf = renderive::Waterfall::Builder(data, x_axis, y_axis)
|
||||
@@ -174,12 +174,12 @@ QWidget* Radio_Widget::create_audio_spectrogram_plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
frequency_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Frequency_Axis::Builder(frequency_axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_coord_range({-12000, 12000})
|
||||
.set_tick_length(-10)
|
||||
.set_sub_tick_length(-5)
|
||||
.build();
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({12000, -12000})
|
||||
.set_unit_text("功率").build();
|
||||
sp = renderive::Spectrum::Builder(data, x_axis, y_axis)
|
||||
@@ -233,11 +233,11 @@ QWidget* Radio_Widget::create_time_power_plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
stream_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
power_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Time_Axis::Builder(time_axis, Qt::Horizontal)
|
||||
x_axis = renderive::Time_Axis::Builder(time_axis, renderive::Orientation::Horizontal)
|
||||
.set_tick_length(-10)
|
||||
.set_sub_tick_length(-5)
|
||||
.build();
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(power_axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({12000, -12000})
|
||||
.build();
|
||||
audio = renderive::Audio_Frequency::Builder(data, x_axis, y_axis)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "../component/ShowTime.h"
|
||||
#include "../Global.h"
|
||||
#include "../menu/BaseWidgetMenu.h"
|
||||
#include "Demo_Gallery/base/Tool.h"
|
||||
#include "Demo_Gallery/base/export.h"
|
||||
QWidget* Sweep_Frequency_Widget::create_spectrogram_power_plot() {
|
||||
class Sweep_Frequent_Plot : public renderive::Latency_Eager_Plot {
|
||||
public:
|
||||
@@ -27,10 +27,10 @@ QWidget* Sweep_Frequency_Widget::create_spectrogram_power_plot() {
|
||||
auto overlay = create_renderable_node(get_root_renderable(), "Overlay_Renderable");
|
||||
x_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
y_axis_cache->set_cache_mode(renderive::Renderable_Cache_Mode::Local_Pixel);
|
||||
x_axis = renderive::Axis::Builder(x_axis_cache, Qt::Horizontal)
|
||||
x_axis = renderive::Axis::Builder(x_axis_cache, renderive::Orientation::Horizontal)
|
||||
.set_coord_range({0, 100})
|
||||
.set_tick_length(-10).build();
|
||||
y_axis = renderive::Axis::Builder(y_axis_cache, Qt::Vertical)
|
||||
y_axis = renderive::Axis::Builder(y_axis_cache, renderive::Orientation::Vertical)
|
||||
.set_coord_range({100, 0})
|
||||
.set_tick_length(-10).build();
|
||||
sf = renderive::Sweep_Frequency::Builder(data, x_axis, y_axis)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#ifndef Global_H
|
||||
#define Global_H
|
||||
#include "Renderive/architecture/export.h"
|
||||
#include "Renderive/export.h"
|
||||
#include "BackEnd/BackEnd.h"
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@@ -14,7 +14,7 @@
|
||||
#include <QtWidgetsDepends>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include "psc_global_include/Singleton.hpp"
|
||||
#include "private_psc_global_include/Singleton.hpp"
|
||||
class Global : public Psc::Singleton<Global> {
|
||||
public:
|
||||
explicit Global();
|
||||
@@ -27,10 +27,6 @@ QRect get_max_rect(const QRect& rect, double WHRate);
|
||||
int calculate_optimal_font_size(const QString& text, const QSize& space_size, const QFont& font);
|
||||
int calculate_optimal_font_size(const std::vector<QString>& text_list, const QSize& space_size, const QFont& font);
|
||||
QPushButton* create_button(const QString& text, int font_size, const std::function<void(void)>& func = nullptr);
|
||||
namespace renderive {
|
||||
template <typename T>
|
||||
class Roll_Object;
|
||||
}
|
||||
class Base_Widget_Menu;
|
||||
void init_click_button(QPushButton* button, const QString& text, int font_size, const std::function<void(void)>& func, renderive::Roll_Object<QPushButton>*);
|
||||
QPushButton* create_click_button(const QString& text, int font_size, const std::function<void(void)>& func, renderive::Roll_Object<QPushButton>*);
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
#ifndef Toolbox_Circular_Linked_List_H
|
||||
#define Toolbox_Circular_Linked_List_H
|
||||
#include <vector>
|
||||
namespace Toolbox {
|
||||
template<typename T>
|
||||
struct Circular_Linked_List_Node {
|
||||
Circular_Linked_List_Node *prev = nullptr, *next = nullptr;
|
||||
T data;
|
||||
};
|
||||
template<typename T>
|
||||
class Circular_Linked_List {
|
||||
public:
|
||||
Circular_Linked_List_Node<T> *head = nullptr;
|
||||
Circular_Linked_List_Node<T> *cur = nullptr;
|
||||
void init(const std::vector<T> &data_list) {
|
||||
head = new Circular_Linked_List_Node<T>();
|
||||
head->data = data_list.front();
|
||||
Circular_Linked_List_Node<T> *prev = head;
|
||||
int n = data_list.size();
|
||||
for (int i = 1; i < n - 1; i++) {
|
||||
auto node = new Circular_Linked_List_Node<T>();
|
||||
node->data = data_list[i];
|
||||
node->prev = prev;
|
||||
prev->next = node;
|
||||
prev = node;
|
||||
}
|
||||
auto tail = new Circular_Linked_List_Node<T>();
|
||||
tail->data = data_list.back();
|
||||
tail->prev = prev;
|
||||
prev->next = tail;
|
||||
tail->next = head;
|
||||
head->prev = tail;
|
||||
cur = head;
|
||||
}
|
||||
Circular_Linked_List_Node<T> *move(int step = 1) {
|
||||
if (step == 0) return nullptr;
|
||||
return step > 0 ? next(step) : prev(-step);
|
||||
}
|
||||
Circular_Linked_List_Node<T> *next(int step = 1) {
|
||||
if (step <= 0) return nullptr;
|
||||
for (int i = 0; i < step; i++) {
|
||||
cur = cur->next;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
Circular_Linked_List_Node<T> *prev(int step = 1) {
|
||||
if (step <= 0) return nullptr;
|
||||
for (int i = 0; i < step; i++) {
|
||||
cur = cur->prev;
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
void insert_next(const T &t, Circular_Linked_List_Node<T> *that = nullptr) {
|
||||
if (that == nullptr) that = cur;
|
||||
auto node = new Circular_Linked_List_Node<T>();
|
||||
node->data = t;
|
||||
node->next = that->next;
|
||||
node->prev = that;
|
||||
that->next = node;
|
||||
}
|
||||
void insert_prev(const T &t, Circular_Linked_List_Node<T> *that = nullptr) {
|
||||
if (that == nullptr) that = cur;
|
||||
auto node = new Circular_Linked_List_Node<T>();
|
||||
node->data = t;
|
||||
node->prev = that->prev;
|
||||
node->next = that;
|
||||
that->prev = node;
|
||||
}
|
||||
std::vector<Circular_Linked_List_Node<T> *> to_vector() {
|
||||
if (head == nullptr) return {};
|
||||
std::vector<Circular_Linked_List_Node<T> *> ret;
|
||||
Circular_Linked_List_Node<T> *cur = head;
|
||||
do {
|
||||
ret.push_back(cur);
|
||||
cur = cur->next;
|
||||
} while (cur != head);
|
||||
return ret;
|
||||
}
|
||||
void clear() {
|
||||
if (head == nullptr) return;
|
||||
Circular_Linked_List_Node<T> *cur = head;
|
||||
do {
|
||||
Circular_Linked_List_Node<T> *temp = cur;
|
||||
cur = cur->next;
|
||||
delete temp;
|
||||
} while (cur != head);
|
||||
head = nullptr;
|
||||
}
|
||||
~Circular_Linked_List() {
|
||||
clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
#ifndef Toolbox_Node_H
|
||||
#define Toolbox_Node_H
|
||||
#include <algorithm>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
namespace Toolbox {
|
||||
template<typename That>
|
||||
class Node {
|
||||
public:
|
||||
explicit Node() = default;
|
||||
int index() {
|
||||
if (!father) return -1;
|
||||
auto it = std::find(father->sons.begin(), father->sons.end(), static_cast<That *>(this));
|
||||
if (it == father->sons.end()) return -1;
|
||||
return static_cast<int>(std::distance(father->sons.begin(), it));
|
||||
}
|
||||
bool root() { return !father; }
|
||||
bool leaf() { return sons.empty(); }
|
||||
bool first() {
|
||||
if (!father) return false;
|
||||
return this == father->sons.front();
|
||||
}
|
||||
bool last() {
|
||||
if (!father) return false;
|
||||
return this == father->sons.back();
|
||||
}
|
||||
That *previous() {
|
||||
if (!father) return nullptr;
|
||||
int i = index() - 1;
|
||||
if (i < 0) return nullptr;
|
||||
return father->sons[i];
|
||||
}
|
||||
That *next() {
|
||||
if (!father) return nullptr;
|
||||
int i = index() + 1;
|
||||
if (i <= 0 || i >= static_cast<int>(father->sons.size())) return nullptr;
|
||||
return father->sons[i];
|
||||
}
|
||||
That *last_child() {
|
||||
if (sons.empty()) return nullptr;
|
||||
return sons.back();
|
||||
}
|
||||
That *first_child() {
|
||||
if (sons.empty()) return nullptr;
|
||||
return sons.front();
|
||||
}
|
||||
That *root_node() {
|
||||
That *ret = static_cast<That *>(this);
|
||||
while (ret->father != nullptr) {
|
||||
ret = ret->father;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int deep() {
|
||||
That *ret = static_cast<That *>(this);
|
||||
int depth = 0;
|
||||
while (ret->father != nullptr) {
|
||||
ret = ret->father;
|
||||
depth++;
|
||||
}
|
||||
return depth;
|
||||
}
|
||||
std::vector<That *> deep_traversed() {
|
||||
std::vector<That *> ret;
|
||||
ret.push_back(static_cast<That *>(this));
|
||||
for (auto &child: sons) {
|
||||
std::vector<That *> child_values = child->deep_traversed();
|
||||
ret.insert(ret.end(), child_values.begin(), child_values.end());
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::vector<That *> sequence_traversed() {
|
||||
std::vector<That *> ret;
|
||||
std::queue<That *> que;
|
||||
That *that = static_cast<That *>(this);
|
||||
que.push(that);
|
||||
while (!que.empty()) {
|
||||
That *cur = que.front();
|
||||
que.pop();
|
||||
int n = static_cast<int>(cur->sons.size());
|
||||
ret.push_back(cur);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
que.push(cur->sons[i]);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
std::vector<That *> descendants() {
|
||||
std::vector<That *> ret = sequence_traversed();
|
||||
if (!ret.empty()) ret.erase(ret.begin());
|
||||
return ret;
|
||||
}
|
||||
That *father = nullptr;
|
||||
std::vector<That *> sons;
|
||||
};
|
||||
template<typename NodeType>
|
||||
class Node_Manager {
|
||||
public:
|
||||
std::vector<NodeType *> roots;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,74 +0,0 @@
|
||||
#ifndef Toolbox_Roll_Widget_H
|
||||
#define Toolbox_Roll_Widget_H
|
||||
#include <QObject>
|
||||
#include <QHBoxLayout>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
namespace Toolbox {
|
||||
template<typename T>
|
||||
class Roll_Object : QObject {
|
||||
public:
|
||||
explicit Roll_Object(QObject *parent = nullptr) : QObject(parent) {}
|
||||
explicit Roll_Object(std::vector<T *> widgets, std::function<void(T *)> enter_func,
|
||||
std::function<void(T *)> leave_func, QObject *parent) :
|
||||
widgets(std::move(widgets)), enter_func(enter_func), leave_func(leave_func), QObject(parent) {
|
||||
if (!this->widgets.empty() && enter_func) enter_func(this->widgets[index]);
|
||||
}
|
||||
int index = 0;
|
||||
std::vector<T *> widgets;
|
||||
std::function<void(T *)> enter_func;
|
||||
std::function<void(T *)> leave_func;
|
||||
T *cur() {
|
||||
if (widgets.empty()) return nullptr;
|
||||
return widgets[index];
|
||||
}
|
||||
void set_cur(T *cur) {
|
||||
if (widgets.empty()) return;
|
||||
auto it = std::find(widgets.begin(), widgets.end(), cur);
|
||||
if (it == widgets.end()) return;
|
||||
int new_index = static_cast<int>(std::distance(widgets.begin(), it));
|
||||
if (leave_func) leave_func(widgets[index]);
|
||||
index = new_index;
|
||||
if (enter_func) enter_func(widgets[index]);
|
||||
widgets[index]->update();
|
||||
}
|
||||
void roll_right() {
|
||||
if (widgets.empty()) return;
|
||||
if (leave_func) leave_func(widgets[index]);
|
||||
widgets[index]->update();
|
||||
if (widgets.back() != widgets[index]) {
|
||||
index++;
|
||||
} else {
|
||||
index = 0;
|
||||
}
|
||||
if (enter_func) enter_func(widgets[index]);
|
||||
widgets[index]->update();
|
||||
}
|
||||
void roll_left() {
|
||||
if (widgets.empty()) return;
|
||||
if (leave_func) leave_func(widgets[index]);
|
||||
widgets[index]->update();
|
||||
if (widgets.front() != widgets[index]) {
|
||||
index--;
|
||||
} else {
|
||||
index = static_cast<int>(widgets.size()) - 1;
|
||||
}
|
||||
if (enter_func) enter_func(widgets[index]);
|
||||
widgets[index]->update();
|
||||
}
|
||||
QHBoxLayout *hbox_layout(int space) {
|
||||
auto ret = new QHBoxLayout;
|
||||
ret->setMargin(0);
|
||||
ret->setSpacing(space);
|
||||
for (auto widget: widgets) {
|
||||
ret->addWidget(widget, 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
#ifndef Toolbox_Singleton_H
|
||||
#define Toolbox_Singleton_H
|
||||
namespace Toolbox {
|
||||
template<typename Derived>
|
||||
class Singleton {
|
||||
public:
|
||||
static Derived *instance() {
|
||||
static Derived instance;
|
||||
return &instance;
|
||||
}
|
||||
Singleton(const Singleton &) = delete;
|
||||
Singleton &operator=(const Singleton &) = delete;
|
||||
protected:
|
||||
Singleton() = default;
|
||||
~Singleton() = default;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
@@ -1,29 +0,0 @@
|
||||
#ifndef Toolbox_Singleton_Widget_H
|
||||
#define Toolbox_Singleton_Widget_H
|
||||
#include <QDebug>
|
||||
namespace Toolbox {
|
||||
template<typename T>
|
||||
class Singleton_Widget {
|
||||
public:
|
||||
static T *instance() {
|
||||
if (!instance) {
|
||||
qInfo() << "error Singleton_Widget 未创建" << instance;
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
Singleton_Widget() {
|
||||
if (instance) {
|
||||
qInfo() << "error Singleton_Widget 重复创建" << instance;
|
||||
}
|
||||
instance = static_cast<T *>(this);
|
||||
}
|
||||
virtual ~Singleton_Widget() {}
|
||||
Singleton_Widget(T &&) = delete;
|
||||
Singleton_Widget(const T &) = delete;
|
||||
void operator=(const T &) = delete;
|
||||
protected:
|
||||
static T *instance;
|
||||
};
|
||||
template<typename T> T *Singleton_Widget<T>::instance = nullptr;
|
||||
}
|
||||
#endif
|
||||
@@ -1,7 +0,0 @@
|
||||
#include "Node.hpp"
|
||||
#include "Circular_Linked_List.hpp"
|
||||
#include "Singleton.hpp"
|
||||
#include "Roll_Object.h"
|
||||
#include "Singleton_Widget.hpp"
|
||||
#include "view/FileFolderBar.h"
|
||||
#include "pinyin/VirtualKeyBoard.h"
|
||||
Binary file not shown.
@@ -1,41 +0,0 @@
|
||||
#include "FileFolderBar.h"
|
||||
#include <QPainter>
|
||||
#include <queue>
|
||||
namespace Toolbox {
|
||||
File_Folder_Bar::File_Folder_Bar(QWidget *parent) : QWidget(parent) {
|
||||
//setAttribute(Qt::WA_OpaquePaintEvent);
|
||||
}
|
||||
File_Folder_Bar::~File_Folder_Bar() {
|
||||
}
|
||||
void File_Folder::draw(int index, QPainter *painter) {
|
||||
static QImage file_folder = QImage(":fileFolder.png");
|
||||
int x = 10 + 20 * deep;
|
||||
int y = 10 + 30 * index;
|
||||
// painter->drawImage(x, y, file_folder);
|
||||
painter->drawImage(0, 0, file_folder);
|
||||
painter->drawText(x + 20, y + 10, name);
|
||||
}
|
||||
void File_Folder_Bar::paintEvent(QPaintEvent *event) {
|
||||
QWidget::paintEvent(event);
|
||||
QPainter painter(this);
|
||||
int index = 0;
|
||||
for (File_Folder *&root: roots) {
|
||||
std::queue<File_Folder *> que;
|
||||
root->deep = 0;
|
||||
que.push(root);
|
||||
while (!que.empty()) {
|
||||
File_Folder *cur = que.front();
|
||||
que.pop();
|
||||
int n = cur->sons.size();
|
||||
cur->draw(index++, &painter);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
if (cur->is_show_sons) {
|
||||
cur->sons[i]->deep = cur->deep + 1;
|
||||
que.push(cur->sons[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
painter.end();
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
#ifndef Toolbox_File_Folder_Bar_H
|
||||
#define Toolbox_File_Folder_Bar_H
|
||||
#include <QWidget>
|
||||
#include "../Node.hpp"
|
||||
namespace Toolbox {
|
||||
class File_Folder : public Node<File_Folder> {
|
||||
public:
|
||||
QString name;
|
||||
bool is_show_sons = false;
|
||||
int deep = 0;
|
||||
void draw(int index, QPainter *painter);
|
||||
};
|
||||
class File_Folder_Bar : public QWidget, public Node_Manager<File_Folder> {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit File_Folder_Bar(QWidget *parent = nullptr);
|
||||
~File_Folder_Bar() override;
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *event) override;
|
||||
};
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>fileFolder.png</file>
|
||||
<file>arrowDown.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 2.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 2.4 KiB |
@@ -1,6 +1,6 @@
|
||||
#include "ExpandInput.h"
|
||||
#include "../BackEnd/MainWidget.h"
|
||||
#include "Widget/base/export.h"
|
||||
#include "Renderive/export.h"
|
||||
#include <QAction>
|
||||
#include <QPainter>
|
||||
Expand_Input::Expand_Input(QWidget *parent) : QLineEdit(parent) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "Renderive/architecture/export.h"
|
||||
#include "Renderive/export.h"
|
||||
#include <QApplication>
|
||||
#ifdef _WIN32
|
||||
#include <QTextCodec>
|
||||
@@ -6,7 +6,6 @@
|
||||
#endif
|
||||
#include "./BackEnd/MainWidget.h"
|
||||
#include "Application.h"
|
||||
#include "Renderive/plot/Plot.h"
|
||||
int main(int argc, char* argv[]) {
|
||||
Application app(argc, argv);
|
||||
Application::setAttribute(Qt::AA_UseHighDpiPixmaps);
|
||||
|
||||
Vendored
+1
-1
@@ -42,4 +42,4 @@ function Fetch-RepoEx
|
||||
|
||||
Fetch-RepoEx "build_infra"
|
||||
Fetch-RepoEx "CPP_Core"
|
||||
Fetch-RepoEx "Renderive"
|
||||
Fetch-RepoEx "Core"
|
||||
@@ -0,0 +1,39 @@
|
||||
include_guard(GLOBAL)
|
||||
#set(base_dir "C:/Program Files/Microsoft Visual Studio/18/Enterprise/VC/Tools/MSVC/14.50.35717/bin/Hostx64/x64")
|
||||
#set(CMAKE_C_COMPILER "${base_dir}/cl.bat" CACHE FILEPATH "")
|
||||
#set(CMAKE_CXX_COMPILER "{${base_dir}}/cl.bat" CACHE FILEPATH "")
|
||||
#set(CMAKE_MAKE_PROGRAM "C:/Program Files/Microsoft Visual Studio/18/Enterprise/Common7/IDE/CommonExtensions/Microsoft/CMake/Ninja/ninja.exe" CACHE FILEPATH "")
|
||||
function(_append_msvc_init_options variable)
|
||||
set(value "${${variable}}")
|
||||
separate_arguments(existing_options WINDOWS_COMMAND "${value}")
|
||||
foreach (option IN LISTS ARGN)
|
||||
list(FIND existing_options "${option}" option_index)
|
||||
if (option_index EQUAL -1)
|
||||
list(APPEND existing_options "${option}")
|
||||
endif ()
|
||||
endforeach ()
|
||||
list(JOIN existing_options " " value)
|
||||
set(${variable} "${value}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
_append_msvc_init_options(
|
||||
CMAKE_CXX_FLAGS_INIT
|
||||
/wd4819
|
||||
/utf-8
|
||||
/Zc:preprocessor
|
||||
/DNOMINMAX
|
||||
/D_USE_MATH_DEFINES
|
||||
/EHsc
|
||||
/bigobj
|
||||
)
|
||||
_append_msvc_init_options(
|
||||
CMAKE_EXE_LINKER_FLAGS_RELEASE_INIT
|
||||
/PROFILE
|
||||
)
|
||||
_append_msvc_init_options(
|
||||
CMAKE_SHARED_LINKER_FLAGS_RELEASE_INIT
|
||||
/PROFILE
|
||||
)
|
||||
_append_msvc_init_options(
|
||||
CMAKE_MODULE_LINKER_FLAGS_RELEASE_INIT
|
||||
/PROFILE
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user