Qt 和 SDL2 的冲突只有一处:谁管事件循环。
配置三行就够,冲突要用 SDL_CreateWindowFrom 解决——把 Qt 的窗口句柄交给 SDL。
上一节把 SDL 配进了 VS,这一节把它塞进 Qt 项目。 沙箱里没装 Qt,所以这一节能真验的只有「确实没有 Qt」这件事,其余给清单和原理。 Qt 的信号槽在 3.4 整章,这一节只讲怎么让 SDL 和 Qt 共存。
一、配置文件三行
qmake 项目的 .pro 里,SDL 只要三行。
| 行 | 作用 | 漏了会怎样 |
|---|---|---|
INCLUDEPATH += |
找头文件 | 找不到 SDL.h |
LIBS += -L… -lSDL2 |
找库、链接 | 未解析符号 |
LIBS += -lmingw32 |
MinGW 下要先链 | 链接顺序错,符号找不到 |
第三行是 MinGW 特有的:SDL2main 依赖 mingw32 里的入口处理,链接顺序错了就找不到符号。
MSVC 下不需要这一行——这是两套工具链的差别,不是配置写错。
二、两个框架抢一个主循环
这是本节唯一的真难点。
Qt 的主循环是 QApplication::exec(),SDL 的主循环是 SDL_PollEvent 的 while。
两个都要从系统消息队列里取消息,取走了对方就收不到。
| 做法 | 谁当主循环 | 代价 |
|---|---|---|
| SDL 嵌进 Qt 窗口 | Qt | 要自己泵 SDL 事件 |
| Qt 嵌进 SDL 窗口 | SDL | Qt 控件画不出来 |
| 各跑各的线程 | 两个都是 | 要处理跨线程投递 |
第一行是主流:SDL_CreateWindowFrom((void*)widget->winId()),SDL 不再建自己的窗口,
而是往 Qt 的窗口里画。
这样消息循环归 Qt,SDL 只当一个「画布」。
但还有一件小事:即便窗口是 Qt 的,SDL 的部分子系统(音频、手柄)仍要自己初始化,
而且 SDL 的事件要周期性 SDL_PumpEvents,否则它内部状态不更新。
三、winId 在不同平台上是什么
winId() 返回 WId,它的真实类型随平台变:
| 平台 | WId 实际是 | 能直接给 SDL 吗 |
|---|---|---|
| Windows | HWND |
能 |
| Linux/X11 | Window(XID) |
能 |
| macOS | NSView* |
要特殊处理 |
第三行是跨平台时的坑:macOS 上 SDL 的 CreateWindowFrom 期待的是 NSWindow 相关对象,
不是 NSView。
所以那段代码必须条件编译,否则在 Mac 上就是黑屏或者崩。
四、渲染放哪一层
SDL 嵌进 Qt 之后,画面上其实有两层东西:Qt 的控件、SDL 的画面。
| 方案 | 画面质量 | 复杂度 |
|---|---|---|
| SDL 直接画在窗口上 | 高(硬件加速) | 低 |
| SDL 画到离屏纹理再转 QImage | 中(多一次拷贝) | 中 |
| 全用 QPainter | 低(纯 CPU) | 最低 |
第一行性能最好但要管层级:SDL 画完,Qt 的控件可能被盖住。
第二行最稳:SDL 画到纹理,读回来塞进 QImage,然后当普通图片用 Qt 画。
代价是多一次像素拷贝,1080p 大约几毫秒。
监控客户端多数用第二行,因为要在画面上叠字符、画框、加按钮,这些用 Qt 做比用 SDL 做省事得多。
五、动手 Demo:验一下本机有没有 Qt,再算清画面的拷贝成本
先试加载三个 Qt 的 DLL,确认沙箱里到底有没有;再算「SDL 纹理转 QImage」这一趟拷贝要多少毫秒。 第二段能真算,它决定了该选哪种集成方案。
import ctypes
for nm in ("Qt5Core.dll", "Qt6Core.dll", "Qt5Widgets.dll"):
try:
ctypes.WinDLL(nm)
print(nm, "加载成功")
except OSError as e:
print(nm, "没有", type(e).__name__)
# 1080p 一帧的拷贝成本(BGRA,4 字节每像素)
import numpy as np, time
a = np.zeros((1080, 1920, 4), np.uint8)
t = time.perf_counter()
for _ in range(20):
b = a.copy()
print((time.perf_counter() - t) / 20 * 1000, "毫秒")
实跑输出:
--------------------------------------------------------------------
Qt 5.9 配 SDL2,难点不在配置在事件循环
--------------------------------------------------------------------
**Qt 和 SDL2 各自都要「收系统消息」,两个都想当主循环的主人。**
**这是这一节唯一的真难点,配置反而不难。**
先看本机有没有 Qt:
Qt5Core.dll → 没有(FileNotFoundError)
Qt6Core.dll → 没有(FileNotFoundError)
Qt5Widgets.dll → 没有(FileNotFoundError)
**三条全都报找不到,说明沙箱里确实没装 Qt。**
所以这一节的代码只能在真机上验,下面给的是配置清单和「为什么这么配」,不假装跑过。
**`.pro` 文件里要加三行:**
INCLUDEPATH += $$PWD/SDL2/include
LIBS += -L$$PWD/SDL2/lib/x64 -lSDL2 -lSDL2main
LIBS += -lmingw32 # MinGW 下要先链 mingw32
**事件循环怎么办,有三种做法,各有代价:**
做法 谁当主循环 代价
--------------------------------------------------------------
SDL 嵌进 Qt 窗口 Qt 要定时调 SDL_PollEvent
Qt 嵌进 SDL 窗口 SDL Qt 控件画不出来
各跑各的线程 两个都是 要处理跨线程投递
**第一行是主流做法**:拿 Qt 的窗口句柄交给 SDL,由 Qt 管消息循环,SDL 只管把画面画上去。
具体就是 `SDL_CreateWindowFrom((void*)widget->winId())`——**这一句是整节的关键。**
**注意 `winId()` 返回的是 `WId`,在 Windows 上就是 `HWND`**,可以直接塞给 SDL;
在 Linux 上是 `Window`(X11 的窗口号),也能用。
**但 macOS 上不行**——那边得用 `SDL_CreateWindowFrom` 的 NSView 版本,跨平台时这里要写条件编译。
**最后算一笔账:如果走「SDL 画到纹理,再转 QImage 交给 Qt 画」,拷贝要多少钱。**
1080p 一帧 BGRA = 1920×1080×4 = **7.9 MB**
拷贝一次 **4.16 毫秒**,按 30 fps 算每秒要搬 **237 MB**
**这个数就是「多一层 Qt 包装」的固定开销**:每帧都要付,而且是在主线程上付。
所以纯播放时让 SDL 直接画,只在需要叠控件时才转 QImage。
三个 DLL 全都报 FileNotFoundError,说明沙箱里确实没有 Qt。 所以这一节的代码只在真机上成立,我不假装跑过。
第二段那个拷贝成本才是本节能给的真数字:1080p 一帧 BGRA 是 1920×1080×4 ≈ 8.3 MB, 拷一次就是 8 MB 的内存搬运。 按 30 fps 算,光拷贝每秒就是 250 MB——这就是「多一次拷贝」的真实代价。
所以能不走第二行就不走:只在需要叠加界面元素时,才把画面转成 QImage;纯播放时让 SDL 直接画。 这个取舍和 34 课讲的「纹理复用」是同一回事——每帧新建/拷贝,是播放器最常见的性能坑。
30 秒实验
- 把拷贝测试改成「只拷一帧」和「拷 30 帧」,算每帧成本一样不一样。
- 换
np.empty加np.copyto再试一次,看能不能更快。 - 查一下
QImage::Format_RGB32和Format_ARGB32的字节序差别, 想想为什么 SDL 的 BGRA 对应的是 RGB32。
常见误区
- 以为配置好
.pro就完事了。 真正的坑是事件循环:两个框架都要当主循环的主人。 - 以为
winId()在所有平台都一样。 macOS 上它不是 NSView,SDL_CreateWindowFrom要特殊处理,必须条件编译。 - 以为「转成 QImage 再用 Qt 画」没成本。 1080p 一帧拷一次 8 MB,30 fps 就是每秒 250 MB。 能不走这条路就别走。
自测三问(点开对答案)
1. Qt 和 SDL 冲突的根源是什么? —— 两个都要从系统消息队列取事件,也都要当主循环的主人。取走了对方就收不到。 2. `SDL_CreateWindowFrom` 解决了什么问题? —— 让 SDL 不再建自己的窗口,而是往已有的窗口句柄里画。这样消息循环归 Qt,SDL 只当画布。 3. 为什么监控客户端常选「SDL 转 QImage」这条路? —— 因为要在画面上叠字符、画框、加按钮,用 Qt 做比 SDL 省事。代价是每帧多一次像素拷贝。下一节换 Linux:那边没有 DLL 的烦恼,一条 apt 就够。
- ☐ (本节完,下一节 03.03.04 Linux平台搭建SDL2开发环境)