前言
上一篇文章讲到 QSS 在每次重绘时都要走 CSS 解析和盒模型计算,而 QStyle 直接用 C++ 计算。这只是冰山一角——Qt 的渲染管线远比"画出来"三个字复杂。这篇文章深入到像素之前的每一步:QPainter 的引擎架构、QWidget 的后备存储(backing store)、QML 的场景图(scene graph)机制,以及它们和原生 OpenGL 绘制的本质区别。
读完你会理解:
- 为什么有时候
update()了但看不到变化 - QPainter 画的东西什么时候在你的 GPU 上
- QML 的控件定义逻辑和 QStyle 到底有什么不同
- 什么场景选 QWidgets + QStyle,什么场景选 QML
第一层:Qt 的绘制驱动链——从事件到像素
sequenceDiagram
participant OS as 操作系统
participant QPA as Qt Platform Abstraction
participant W as QWidget
participant BE as QBackingStore
participant PE as QPaintEngine
participant GPU as GPU / 帧缓冲
OS->>QPA: Expose / WM_PAINT
QPA->>W: paintEvent()
W->>W: initStyleOption()
W->>W: 调用 QStyle::drawControl()...
W->>BE: beginPaint(region)
BE->>PE: 创建 QPainter(绑定 paintDevice)
W->>PE: QPainter::drawLine / drawRect / drawText ...
Note over PE: 转化为内部指令<br/>(矢量操作 or 光栅操作)
W->>BE: endPaint()
BE->>QPA: flush()
QPA->>OS: 提交帧缓冲
OS->>GPU: 显示
关键概念:绘制是异步的。
当你调用 QWidget::update() 时,Qt 只是标记了一个"脏区域”。实际的 paintEvent() 会在事件循环的下一个周期才被调用。而且 Qt 会自动合并同一帧内的多次 update() 调用。
// 这段代码只会触发一次 paintEvent,不是两次
widget->update(); // 标记脏区域
widget->update(); // 合并到同一个脏区域
// paintEvent 稍后在事件循环中执行
第二层:QPainter 的 Paint Engine 架构
QPainter 本身不渲染任何东西。它是一个命令记录器,把绘制指令转发给底层的 QPaintEngine。
graph TD
QP["<b>QPainter</b><br/>(命令记录器)"]
QP --> R["<b>QRasterPaintEngine</b><br/>CPU 光栅化<br/>最常用"]
QP --> O["<b>QOpenGLPaintEngine</b><br/>OpenGL ES 2.0<br/>(Qt 5/6)"]
QP --> PDF["<b>QPdfPaintEngine</b><br/>PDF 输出"]
QP --> SVG["<b>QSvgPaintEngine</b><br/>SVG 输出"]
R --> R1["绘制到 QImage"]
R --> R2["绘制到 QPixmap"]
R --> R3["绘制到 QBackingStore"]
R --> R4["绘制到 QPrinter"]
O --> O1["QOpenGLWidget"]
O --> O2["QOpenGLWindow"]
O --> O3["QQuickWindow(部分场景)"]
style QP fill:#E67E22,color:#fff,stroke:#CA6F1E
style R fill:#27AE60,color:#fff,stroke:#1E8449
style O fill:#3498DB,color:#fff,stroke:#2471A3
2.1 QRasterPaintEngine:Qt 的主力引擎
这是 Qt Widgets 默认使用的引擎。所有绘制操作最终转化为 CPU 端的光栅化:
// 当你写:
QPainter p(widget);
p.setBrush(Qt::red);
p.drawRoundedRect(QRect(10, 10, 100, 50), 8, 8);
// QRasterPaintEngine 内部(简化):
// 1. 裁剪检查
// 2. 将圆角矩形拆解为扫描线
// 3. 计算每个扫描线的抗锯齿覆盖值
// 4. 写入 QImage 的像素缓冲区
// 5. 格式转换(如果需要)
重要限制:QRasterPaintEngine 默认运行在 CPU 上。每次 drawControl() 调用中的 QPainter 操作都会经过 CPU 光栅化,结果写入 QImage 像素缓冲区。这个缓冲区存在于系统内存中。只有当 QBackingStore::flush() 被调用时,才会将脏区域拷贝到 GPU 纹理供显示。
2.2 QOpenGLPaintEngine:GPU 加速的 QPainter
从 Qt 5 开始,QPainter 可以工作在 OpenGL 后端上:
#include <QOpenGLWidget>
#include <QPainter>
class MyGLWidget : public QOpenGLWidget {
protected:
void paintGL() override {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
p.setPen(QPen(Qt::white, 2));
p.drawLine(0, 0, width(), height());
// 这个 QPainter 实际上通过 QOpenGLPaintEngine 工作
// 所有操作被转化为 OpenGL ES 2.0 调用
}
};
QOpenGLPaintEngine 的限制:
- 只支持 OpenGL ES 2.0 子集
- 某些高级 QPainter 特性降级为 CPU 端计算(composition modes、complex paths)
- 带有复杂路径的抗锯齿文字在 OpenGL 引擎中效率低于 raster 引擎
- 不支持
QPainter::drawPicture()
2.3 Paint Engine 的选择规则
// QPainter 根据 paintDevice 类型自动选择引擎
QPainter p1(widget); // 普通 QWidget → QRasterPaintEngine
QPainter p2(&image); // QImage → QRasterPaintEngine
QPainter p3(&pixmap); // QPixmap → QRasterPaintEngine
QPainter p4(glWidget); // QOpenGLWidget → QOpenGLPaintEngine
QPainter p5(&printer); // QPrinter → 取决于打印机后端
第三层:QWidget 的 Backing Store 与双缓冲
3.1 什么是 Backing Store
每个顶层窗口(QWindow)都有一个 QBackingStore。它就是窗口的像素缓冲区:
┌──────────────────────────────┐
│ 屏幕显示 │
│ ┌────────────────────────┐ │
│ │ QBackingStore(后缓冲)│ │
│ │ ┌──────┬──────┬──────┐ │ │
│ │ │Widget│Widget│Widget│ │ │
│ │ │ 1 │ 2 │ 3 │ │ │
│ │ └──────┴──────┴──────┘ │ │
│ │ 共享同一个 QImage 缓冲 │ │
│ └────────────────────────┘ │
└──────────────────────────────┘
关键点:同一个顶层窗口内的所有 QWidget 共享同一个 QBackingStore。每个 widget 的 paintEvent 最终写入的是这个共享 QImage 的对应区域。
3.2 完整绘制周期
sequenceDiagram
participant QPA as Qt Platform
participant TBW as QWidgetWindow
participant BS as QBackingStore
participant W1 as QWidget A
participant W2 as QWidget B
QPA->>TBW: 需要重绘(expose / animation / update)
Note over TBW: 收集所有脏区域
TBW->>BS: beginPaint(dirtyRegion)
BS->>BS: 分配 QImage buffer
loop 遍历脏区域内的所有 widget
TBW->>W1: paintEvent()
W1->>BS: QPainter 写入 buffer
end
loop 遍历脏区域内的所有 widget
TBW->>W2: paintEvent()
W2->>BS: QPainter 写入 buffer
end
TBW->>BS: endPaint()
BS->>QPA: flush(dirtyRegion)
Note over QPA: 将脏区域复制到 GPU 纹理<br/>(或直接操作系统 bitblt)
3.3 为什么原生 Qt (Widgets) “不用 GPU”
这不是严格正确的说法。正确的说法是:Qt Widgets 的绘制是在 CPU 端完成光栅化的,最终显示时会经过 GPU 合成。
Qt Widgets 路径:
QPainter → CPU 光栅化 → QImage (RAM) → flush → GPU 纹理 → 合成器 → 显示
↑ 都在系统内存中 ↑ 这一步才进 GPU
原生 OpenGL 路径:
glDrawArrays → GPU 顶点着色 → 光栅化 → 帧缓冲 → 显示
↑ 从一开始就在 GPU 上
这意味着:
- QWidget 的绘制计算在 CPU 上(文字光栅化、路径填充、alpha 合成)
- 最终的显示合成会用到 GPU(窗口合成、vsync)
- 但瓶颈在 CPU 端,尤其是大量控件的复杂样式
第四层:QPainter 与原生 OpenGL 的本质区别
4.1 编程模型
| 维度 | QPainter | 原生 OpenGL |
|---|---|---|
| 范式 | 即时模式(Immediate Mode) | 保留模式 + 命令缓冲(现代 OpenGL) |
| 状态管理 | 栈式 save/restore | 全局状态机(需手动管理) |
| 坐标系统 | 逻辑坐标 + 自动变换矩阵 | 手动管理模型/视图/投影矩阵 |
| 绘制单元 | 几何形状(线、矩形、路径) | 顶点缓冲区 + 着色器 |
| 缓冲管理 | 自动(QBackingStore) | 手动(VBO、VAO、FBO) |
| 抗锯齿 | 自动(子像素覆盖) | 手动(MSAA 或 shader 实现) |
4.2 同一效果,两条路径
QPainter 方式:
void MyWidget::paintEvent(QPaintEvent *) {
QPainter p(this);
p.setRenderHint(QPainter::Antialiasing);
// 画渐变圆角矩形
QLinearGradient grad(0, 0, 0, height());
grad.setColorAt(0, QColor("#667eea"));
grad.setColorAt(1, QColor("#764ba2"));
p.setBrush(grad);
p.setPen(Qt::NoPen);
p.drawRoundedRect(rect().adjusted(10,10,-10,-10), 16, 16);
// 只需要 3 行代码
}
原生 OpenGL 方式(等效效果):
// 1. 顶点着色器
const char *vertexShader = R"(
#version 330 core
layout(location = 0) in vec2 position;
uniform mat4 mvp;
void main() {
gl_Position = mvp * vec4(position, 0.0, 1.0);
}
)";
// 2. 片段着色器(圆角矩形 SDF)
const char *fragmentShader = R"(
#version 330 core
uniform vec2 resolution;
uniform vec2 rectSize;
uniform float radius;
uniform vec4 colorTop;
uniform vec4 colorBottom;
out vec4 fragColor;
float roundedBoxSDF(vec2 p, vec2 b, float r) {
vec2 d = abs(p) - b + vec2(r);
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
}
void main() {
vec2 p = (gl_FragCoord.xy - resolution * 0.5) / resolution.y;
vec2 b = rectSize / resolution.y;
float d = roundedBoxSDF(p, b, radius / resolution.y);
float alpha = 1.0 - smoothstep(-1.0, 1.0, d * resolution.y);
vec4 color = mix(colorBottom, colorTop, gl_FragCoord.y / resolution.y);
fragColor = color * alpha;
}
)";
// 3. 编译着色器、创建 program、设置 uniforms、创建 VAO/VBO...
// 4. glDrawArrays(GL_TRIANGLES, 0, 6);
同样的视觉效果,QPainter 用 3 行,OpenGL 需要 40+ 行着色器和一堆管线设置。
4.3 性能对比
graph LR
subgraph "简单场景(100 个矩形 + 文字)"
A1["QPainter"] -->|"~0.5ms<br/>全部 CPU"| R1["适合"]
A2["OpenGL"] -->|"~0.2ms<br/>但开销更大"| R2["过度工程"]
end
subgraph "复杂场景(10000 个圆角矩形动画)"
B1["QPainter"] -->|"~30ms<br/>CPU 计算瓶颈"| R3["卡顿"]
B2["OpenGL"] -->|"~2ms<br/>GPU 并行"| R4["流畅"]
end
subgraph "极高复杂度(粒子系统)"
C1["QPainter"] -->|"不可行"| R5["放弃"]
C2["OpenGL"] -->|"~1ms"| R6["唯一选择"]
end
style R1 fill:#27AE60,color:#fff
style R2 fill:#E67E22,color:#fff
style R3 fill:#C0392B,color:#fff
style R4 fill:#27AE60,color:#fff
style R5 fill:#C0392B,color:#fff
style R6 fill:#27AE60,color:#fff
量化数据(实测近似值,取决于硬件):
| 场景 | QPainter (Raster) | OpenGL (native) |
|---|---|---|
| 单个按钮重绘 | < 0.1ms | ~0.05ms |
| 复杂表单(50 控件) | 1-3ms | 0.5-1ms |
| 1000 个动画条 | 15-25ms → 卡 | 1-3ms |
| 大量文字渲染 | ⭐ 优秀(FreeType/harfbuzz) | 一般(需纹理图集) |
| 渐变/阴影 | 中等 | ⭐ 极快(GPU 并行) |
QPainter 的优势项:文字渲染。Qt 使用 FreeType 和 HarfBuzz 做 CPU 端文字光栅化,Subpixel 抗锯齿效果极好。GPU 做文字渲染需要纹理图集,而且 SubmitPixel 效果不如 CPU。
第五层:QML / Qt Quick 的场景图(Scene Graph)
5.1 架构概览
QML 的渲染系统与 QWidget 完全不同。它不经过 QPainter、不经过 QStyle、不经过 QBackingStore。它有自己的渲染管线:
graph TD
subgraph "QML 声明层"
QML["QML 代码<br/>Rectangle { color: 'red' }"]
end
subgraph "C++ 对象层"
QML --> ITEM["QQuickItem<br/>(C++ 对象)"]
ITEM --> GEOM["几何属性<br/>x, y, width, height, color..."]
end
subgraph "场景图层"
ITEM --> SG["QQuickWindow<br/>触发场景图同步"]
SG --> SYNC["QSGNode 树同步<br/>(主线程)"]
SYNC --> RN["QSGRenderNode<br/>(渲染线程)"]
end
subgraph "GPU 层"
RN --> GAPI["RHI 抽象层<br/>(Qt 6)"]
GAPI --> GL["OpenGL"]
GAPI --> VK["Vulkan"]
GAPI --> MT["Metal"]
GAPI --> D3D["Direct3D"]
end
style QML fill:#3498DB,color:#fff
style ITEM fill:#8E44AD,color:#fff
style SG fill:#E67E22,color:#fff
style GAPI fill:#27AE60,color:#fff
关键差异:QML 的场景图运行在独立的渲染线程上。主线程只负责同步属性到场景图节点,实际的 OpenGL/Vulkan/Metal 调用在渲染线程中执行。这就是 QML 的动画能够跑到 60fps+ 的原因——主线程阻塞不会影响渲染。
5.2 场景图同步机制
// QQuickItem::updatePaintNode() —— QML 控件的核心虚函数
QSGNode *MyQuickItem::updatePaintNode(QSGNode *oldNode,
UpdatePaintNodeData *) {
QSGSimpleRectNode *node = static_cast<QSGSimpleRectNode*>(oldNode);
if (!node) {
node = new QSGSimpleRectNode();
}
// 把这些属性同步到 GPU 节点
node->setRect(boundingRect());
node->setColor(m_color);
return node;
}
updatePaintNode() 在主线程调用。它不渲染任何东西,只是创建/更新场景图节点对象。实际的 GPU 渲染调用在另一个线程中发生。
sequenceDiagram
participant MT as 主线程
participant SG as 场景图
participant RT as 渲染线程
participant GPU as GPU
Note over MT: 帧 N
MT->>MT: 属性变更 → 触发 update()
MT->>SG: updatePaintNode()<br/>更新 QSGNode 属性
Note over MT: 主线程继续处理事件
Note over RT: 帧 N 渲染
RT->>SG: 遍历 QSGNode 树
SG->>RT: 生成绘制命令
RT->>GPU: glDraw* / vkCmdDraw*
GPU->>GPU: 执行着色器
Note over MT: 帧 N+1(同时)
MT->>MT: 处理新的事件...
Note over RT: 帧 N+1(同时)
RT->>GPU: 渲染下一帧
5.3 QML 的控件属性体系 vs QStyle 的绘制体系
这是最核心的对比:
| 维度 | QML / Qt Quick | QWidgets + QStyle |
|---|---|---|
| 控件定义方式 | 声明式属性组合(Rectangle { color; radius }) |
C++ 虚函数重写(drawControl()) |
| 外观定义者 | QML 文件自身 / QtQuick.Controls 的样式文件 |
QStyle 类的 drawControl() 等虚函数 |
| 主题机制 | qtquickcontrols2.conf + QML 样式委托 |
C++ 的 QStyle 子类 |
| 属性系统 | QML 内置属性绑定(width: parent.width * 0.5) |
Q_PROPERTY + 手动 notify |
| 渲染机制 | 场景图 → GPU | QPainter → CPU 光栅化 → QBackingStore |
| 动画 | 场景图属性动画(GPU 线程) | QPropertyAnimation(主线程,触发 repaint) |
| 布局 | QML Anchor / Layout / Positioner | QLayout 系统 |
| 文字渲染 | 纹理图集(distance field 或 bitmap) | FreeType CPU 光栅化 |
5.4 QML 的控件实现机制
以 QML 的 Button 为例——它和 QPushButton 的结构完全不同:
QML Button 的分层结构:
Button {
id: control
// 背景委托(可替换)
background: Rectangle {
color: control.down ? "#357ABD" :
control.hovered ? "#4A90D9" :
"#5BA0E9"
radius: 4
}
// 内容项
contentItem: Text {
text: control.text
color: "white"
font: control.font
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
每一层都可以被替换。QtQuick.Controls 的样式系统支持:
- 全局样式文件覆盖
- 单个控件的 inline 委托
- 与父控件继承
// 全局样式文件(qtquickcontrols2.conf)
[Controls]
Style=Material
[Material]
Theme=Dark
Accent=DeepPurple
// 单个控件覆盖
Button {
text: "Custom"
background: Rectangle {
gradient: Gradient { ... }
}
}
对比 QStyle 的 Button 定制:
// QStyle 方式:必须重写 drawControl(CE_PushButton)
void MyStyle::drawControl(ControlElement elem, ...) {
if (elem == CE_PushButton) {
// 50 行 QPainter 代码
drawPrimitive(PE_PanelButtonCommand, ...);
// 处理图标
// 处理文字
// 处理焦点
}
}
QML 的声明式委托替换比 QStyle 的 C++ 虚函数重写灵活得多——但代价是运行时开销更大。
5.5 RHI (Rendering Hardware Interface) — Qt 6 的统一抽象
Qt 6 引入 RHI 取代了 Qt 5 的直接 OpenGL 依赖:
// Qt 5(直接 OpenGL)
QSGNode *node;
// 场景图直接发送 OpenGL 调用
// Qt 6(通过 RHI)
QRhi *rhi = window->rhi(); // 可能是 OpenGL / Vulkan / Metal / D3D
// 场景图通过 RHI 发送抽象绘制命令
// RHI 将其翻译为具体图形 API 调用
RHI 支持的后端:
| 后端 | 平台 | 状态 |
|---|---|---|
| OpenGL ES 2.0 / 3.0 | 所有平台 | 主要 / 回退 |
| Vulkan 1.0+ | Windows, Linux, Android | 实验性 |
| Metal | macOS, iOS | 良好支持 |
| Direct3D 11 | Windows | 良好支持 |
| Direct3D 12 | Windows | Qt 6.6+ |
第六层:性能深度对比
6.1 渲染性能
graph LR
subgraph "QWidgets + QStyle"
W1[paintEvent] --> W2["CPU 光栅化<br/>QPainter → QImage"] --> W3["flush → GPU 纹理"] --> W4[显示]
end
subgraph "QML / Qt Quick"
Q1["属性变更"] --> Q2["场景图同步<br/>(主线程)"] --> Q3["GPU 渲染<br/>(渲染线程)"] --> Q4[显示]
end
subgraph "原生 OpenGL"
G1["更新 uniforms"] --> G2["glDraw*"] --> G3[显示]
end
6.2 适合场景对照表
| 场景 | QWidgets + QStyle | QML | 原生 OpenGL |
|---|---|---|---|
| 传统桌面应用(表单、表格、对话框) | ⭐⭐⭐ | ⭐⭐ | ❌(不实用) |
| 移动端 / 嵌入式触摸 UI | ⭐ | ⭐⭐⭐ | ❌(不实用) |
| 复杂数据可视化(图表、仪表盘) | ⭐⭐ | ⭐⭐⭐ | ⭐⭐(灵活但工作量大) |
| 3D 渲染 / 游戏 | ❌ | ⭐ | ⭐⭐⭐ |
| 高密度文字(代码编辑器、文档阅读器) | ⭐⭐⭐ | ⭐ | ❌ |
| 动画密集型 UI | ⭐ | ⭐⭐⭐ | ⭐⭐ |
| 跨平台一致性要求高 | ⭐⭐⭐ | ⭐⭐ | ❌ |
| 低端硬件 / 嵌入式 | ⭐⭐ | ⭐(RHI 有开销) | ⭐⭐⭐(但开发成本高) |
| 自定义控件的灵活度 | 中等(C++ 虚函数) | 高(QML 委托替换) | 极高(但工作量大) |
6.3 内存占用对比
QWidgets + QStyle:
- 每个 widget 一个 C++ 对象(~200B+ 元数据)
- 一个共享 QBackingStore(窗口大小的 QImage)
- 无额外 GPU 资源(除 flush 后的纹理)
QML:
- 每个 Item 一个 C++ 对象(~300B+) + QML 元对象
- 场景图节点(QSGNode)每项 ~200B+
- GPU 纹理、顶点缓冲、uniform 缓冲
- 图标和文字缓存在 GPU 纹理图集中
原生 OpenGL:
- 只有你分配的资源
- 0 额外框架开销
粗略对比(1000 个控件):
| 指标 | QWidgets | QML |
|---|---|---|
| C++ 对象内存 | ~200KB | ~300KB |
| 额外 GPU 内存 | ~5MB(窗口缓存) | ~15-30MB(纹理图集+节点) |
| 创建时间 | ~10ms | ~50-100ms(含 QML 编译) |
| 60fps 动画可行性 | 部分可行(简单控件) | ✅ 设计目标 |
第七层:QML 属性绑定 vs QStyle 查询——两种"动态化"思路
QStyle 的方式:查询模式
// QStyle 的"动态":每次绘制时查询状态
void MyStyle::drawControl(CE_PushButton, ...) {
// 从 QStyleOption 读取当前状态
bool hovered = opt->state & State_MouseOver;
bool pressed = opt->state & State_Sunken;
// 根据状态计算颜色
QColor bg = pressed ? m_pressedColor :
hovered ? m_hoverColor : m_normalColor;
p->fillRect(opt->rect, bg);
}
// 状态变化 → widget->update() → paintEvent → drawControl 重新查询
QML 的方式:属性绑定
Rectangle {
// 声明式绑定:color 自动跟随 control.down 和 control.hovered
color: control.down ? "#357ABD" :
control.hovered ? "#4A90D9" :
"#5BA0E9"
// 绑定自动工作——任何依赖变化都会触发重新求值
// 不需要手写 update() 或 paintEvent
}
本质区别:
| QStyle | QML | |
|---|---|---|
| 范式 | 命令式查询(“此时状态是什么?") | 声明式绑定(“我的值永远等于这个表达式”) |
| 更新机制 | 事件驱动(paintEvent → 查询) | 依赖追踪(属性变化 → 重新求值 → 场景图更新) |
| 线程 | 主线程 | 主线程绑定 + 渲染线程绘制 |
| 过度更新 | 可能(paintEvent 重绘整个控件) | 精确(只更新变化的节点属性) |
第八层:混合使用——何时在 QWidget 中用 QML,反之亦然
QQuickWidget:在 QWidget 中嵌入 QML
#include <QQuickWidget>
#include <QQmlContext>
// 在 QWidget 应用中嵌入 QML 场景
QQuickWidget *quickWidget = new QQuickWidget;
quickWidget->setSource(QUrl("qrc:/MyChart.qml"));
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
// 从 C++ 向 QML 暴露数据
quickWidget->rootContext()->setContextProperty("chartData", &m_dataModel);
QQuickWidget 的限制:
- 渲染通过 FBO(帧缓冲对象)中转,额外一次纹理拷贝
- 不能与普通 QWidget 在半透明方式下完美叠加
- 性能不如纯 QML 窗口
QWidget::createWindowContainer():在 QML 中嵌入 QWidget
import QtQuick.Controls 2.15
ApplicationWindow {
// 在 QML 中嵌入 Qt 3D 或 OpenGL widget
// (主要用于嵌入不能移植到 QML 的遗留控件)
}
一般不推荐这种方向——从 QML 嵌 QWidget 比反过来更困难和低效。
总结:技术选型决策树
graph TD
Q{"你的应用是?"}
Q -->|"传统桌面应用<br/>(表单/表格/编辑器)"| A1["QWidgets + QStyle"]
Q -->|"移动端/嵌入式<br/>触摸优先"| A2["QML"]
Q -->|"需要大量动画<br/>视觉效果丰富"| A3["QML"]
Q -->|"高性能 3D/游戏"| A4["原生 OpenGL/Vulkan"]
Q -->|"混合:桌面主体<br/>+ 部分动画面板"| A5["QWidgets 主体<br/>+ QQuickWidget 嵌动画"]
Q -->|"重用大量现有<br/>QWidget 代码"| A1
style A1 fill:#27AE60,color:#fff
style A2 fill:#3498DB,color:#fff
style A3 fill:#3498DB,color:#fff
style A4 fill:#C0392B,color:#fff
style A5 fill:#E67E22,color:#fff
| 如果看重… | 选这个 |
|---|---|
| 像素级精度 + 文字渲染质量 | QWidgets + QStyle |
| 动画流畅度 + 现代视觉效果 | QML |
| 跨平台 widget 外观一致性 | QWidgets + Fusion QStyle |
| 开发速度(UI 部分) | QML |
| C++ 集成深度 | QWidgets(天然纯 C++) |
| 低端硬件支持 | QWidgets |
| 未来 Qt 的方向 | QML(Qt 官方重点投入) |
最终忠告:如果你在做Logic’s Lab 这样的组件库和课程,QWidgets + QStyle 仍然是最佳选择。它的架构透明性、可控性和 C++ 的集成深度,是学习和展示底层原理的理想环境。QML 适合做产品,QStyle 适合学原理。
这篇文章是 Logic’s Lab「Qt 深度定制 UI 框架」系列的第四篇。下一篇将回到实战——从零构建一个完整的自定义 QStyle。
参考资料