适用于嵌入式 Linux 的 Qt
适用于嵌入式 Linux 设备的平台插件
在嵌入式 Linux 系统上,您可以使用多种平台插件:EGLFS、VkKhrDisplay、LinuxFB 或 Wayland。这些插件的可用性取决于 Qt 的配置方式。 其中,Wayland 需要组合器(compositor)的支持,并提供了一个完整的窗口系统,支持多窗口,类似于 X11 或 Windows。其余插件则无需任何窗口系统即可运行,这意味着 Qt 应用程序完全控制着渲染和输出。它们通常每屏支持一个全屏的 Qt “窗口”。
EGLFS 是许多主板上的默认插件。如果它不合适,请使用QT_QPA_PLATFORM 环境变量来请求其他插件。或者,为了快速测试,可以使用-platform 命令行参数,其语法与上述相同。
请参阅《配置嵌入式 Linux 设备》,了解如何使用嵌入式 Linux 工具链配置 Qt 进行交叉编译。
EGLFS
EGL是 OpenGL 与本机窗口系统之间的接口。 Qt 可以使用 EGL 进行上下文和表面管理,但该 API 本身并不包含任何平台特有的内容。创建原生窗口(该窗口未必是屏幕上的实际窗口)仍必须通过平台特有的方式来实现。这就是为什么我们需要针对特定板卡或 GPU 的适配代码。通常,这些适配代码以以下形式提供:
- EGLFS 挂钩——一个编译为平台插件的源文件
- EGL 设备集成——动态加载的插件
EGLFS 是一个平台插件,用于在 EGL 和 OpenGL ES 2.0 之上运行 Qt 应用程序,而无需像 X11 或 Wayland 这样的实际窗口系统。对于配备 GPU 的现代嵌入式 Linux 设备,这是推荐使用的插件。
除了Qt Quick 和原生OpenGL应用程序外,EGLFS还支持软件渲染的窗口,例如QWidget 。对于QWidget ,控件的内容会通过CPU渲染为图像,随后这些图像会被上传到纹理中,并由插件进行合成。
EGLFS 会强制将第一个顶级窗口(无论是QWidget 还是QQuickView)设为全屏。该窗口同时被选定为根控件窗口,所有其他顶级控件都将合成到其中。 例如,对话框、弹出菜单或组合框。这种行为是必要的,因为在 EGLFS 中始终仅存在一个本机窗口和一个 EGL 窗口表面;它们属于最先创建的小部件或窗口。 当应用程序生命周期内存在一个主窗口,且所有其他小部件要么不是顶级小部件,要么是在主窗口显示后才创建时,这种方法效果很好。
对于基于 OpenGL 的窗口,还存在进一步的限制。EGLFS 支持单个全屏 GL 窗口(截至 Qt 5.3 版本),例如基于 OpenGL 的 `QWindow`、`QQuickView` 或 `QOpenGLWidget`。不支持打开额外的 OpenGL 窗口,也不支持将此类窗口与基于 `QWidget` 的内容混合使用;Qt 会显示一条错误消息并终止应用程序。
此外,EGLFS 不支持专为桌面平台或带有窗口系统的环境设计的 API,例如拖放功能。
EGLFS 使用的环境变量
如有必要,可通过以下环境变量配置eglfs :
| 环境变量 | 说明 |
|---|---|
QT_QPA_EGLFS_INTEGRATION | 除了编译内置的钩子外,还可以使用动态加载的插件来提供针对特定设备或供应商的适配。此环境变量用于强制使用特定的插件。例如,将其设置为eglfs_kms即使用 KMS/DRM 后端。 仅当设备制造规范(makespec)中未指定静态或编译内置的钩子时,此选项才可用。实际上,传统的编译内置钩子已很少使用,目前几乎所有后端都已迁移到插件。设备制造规范中仍包含一个相关的(尽管是可选的)EGLFS_DEVICE_INTEGRATION 条目:该特定设备首选后端的名称。 如果目标系统上存在多个插件,请避免设置此环境变量。在桌面环境中,根据DISPLAY 环境变量的存在情况,会优先使用 KMS 或 X11 后端。注意:在 某些主板上, 会使用 |
QT_QPA_EGLFS_PHYSICAL_WIDTH 以及QT_QPA_EGLFS_PHYSICAL_HEIGHT | 指定物理屏幕的宽度和高度(单位为毫米)。请注意,自 Qt 6 起,物理屏幕尺寸不再用于确定逻辑 dpi。 |
QT_QPA_EGLFS_ROTATION | 指定在基于QWidget 的应用程序中,对软件渲染内容应用的旋转角度。支持的值为180、90和-90。此变量不适用于基于OpenGL的窗口,包括Qt Quick 。Qt Quick 应用程序可以在其QML场景中应用变换。 标准的eglfs 鼠标光标始终会考虑该值,并显示位置和旋转角度均恰当的指针图像,无论应用程序类型如何。但是,某些特殊的光标实现(例如 KMS/DRM 后端的硬件光标)可能不支持旋转。此设置对其他任何内容(包括触摸输入)均无影响。 触摸输入后端evdevtouch 和libinput 各自拥有配置旋转的机制。有关配置触摸输入的更多信息,请参阅《嵌入式 Linux 设备上的输入》。 |
QT_QPA_EGLFS_FORCEVSYNC | 当启用此选项时,eglfs 会在每次调用 eglSwapBuffers() 之后,向帧缓冲设备请求FBIO_WAITFORVSYNC 。该变量仅与依赖于传统 Linuxfbdev 子系统的后端相关。 通常情况下,当默认交换间隔为 1 时,Qt 会假设调用 eglSwapBuffers() 会自动处理垂直同步(vsync);如果未处理(例如由于驱动程序错误),请尝试将 `QT_QPA_EGLFS_FORCEVSYNC ` 设置为非零值。 |
QT_QPA_EGLFS_FORCE888 | 设置此选项后,当eglfs 创建新的上下文、窗口或离屏表面时,将忽略红色、绿色和蓝色颜色通道的大小。 取而代之的是,插件会请求每通道 8 位的配置。这对于那些尽管知道每像素少于 32 或 24 位的配置(例如 5-6-5 或 4-4-4)并不理想(例如会产生条带效应),但仍默认选择此类配置的设备来说,可能会有所帮助。 无需修改应用程序代码,该变量便提供了一种快捷方式,可强制使用 24 或 32 bpp 的配置。 |
此外,还提供了以下一些较少使用的变量:
| 环境变量 | 描述 |
|---|---|
QT_QPA_EGLFS_FB | 覆盖帧缓冲设备。默认值为/dev/fb0 。在大多数嵌入式平台上,该变量意义不大,因为帧缓冲器仅用于查询显示尺寸等设置。但在某些设备上,该变量允许在多显示器配置中指定要使用的显示器,类似于LinuxFB中的fb 参数。 |
QT_QPA_EGLFS_WIDTH 以及QT_QPA_EGLFS_HEIGHT | 包含屏幕的宽度和高度(以像素为单位)。虽然eglfs 会尝试从帧缓冲设备/dev/fb0 获取尺寸,但这并不总是有效。可能需要手动指定尺寸。 |
QT_QPA_EGLFS_DEPTH | 覆盖屏幕的色深。在无法访问帧缓冲设备/dev/fb0或查询不成功的平台上,将使用默认值32 。使用此变量可覆盖此类默认值。注意:此 变量仅影响QScreen 报告的颜色深度值。它与EGL配置以及OpenGL渲染所使用的颜色深度无关。 |
QT_QPA_EGLFS_SWAPINTERVAL | 默认情况下,会请求1 的交换间隔。此变量可实现与显示器垂直刷新率的同步。使用此变量可覆盖交换间隔的值。例如,传入 0 将禁用交换时的阻塞,从而在不进行任何同步的情况下以最快速度运行。 |
QT_QPA_EGLFS_DEBUG | 启用此选项后,调试输出中将显示部分调试信息。例如,在创建新上下文时,会打印输入值QSurfaceFormat 以及所选EGL配置的属性。若与Qt Quick 的QSG_INFO 变量配合使用,可获取有助于排查EGL配置相关问题的有用信息。 |
日志记录
除了QT_QPA_EGLFS_DEBUG 外,eglfs 还支持Qt的现代分类日志系统。可用的日志类别包括:
qt.qpa.egldeviceintegration– 启用动态加载后端的日志记录。使用此类别可检查当前正在使用的后端。qt.qpa.input– 启用来自evdev和libinput输入处理程序的调试输出。使用此类别可检查给定的输入设备是否已被识别并打开。qt.qpa.eglfs.kms– 启用 KMS/DRM 后端的详细日志记录。
运行configure 之后,请务必检查其输出结果。这是识别您是否已启用必要的 EGLFS 后端、libudev 或 libinput 的最简单、最快捷的方法。简而言之,如果您的configure 输出中出现了不希望看到的“no”,请运行:
./configure -v以启用详细输出,这样您就可以看到每个 configure 测试中调用的编译器和链接器。
注意:如果您 遇到关于缺少头文件、库文件或看似难以理解的链接器错误,通常这表明 sysroot 不完整或已损坏,与 Qt 无关。
例如,当针对搭载博通专有图形驱动程序的树莓派进行编译时,输出内容应类似如下:
QPA backends:
EGLFS ................................ yes
EGLFS details:
EGLFS i.Mx6 ........................ no
EGLFS i.Mx6 Wayland ................ no
EGLFS EGLDevice .................... no
EGLFS GBM .......................... no
EGLFS Mali ......................... no
EGLFS Raspberry Pi ................. yes
EGL on X11 ......................... no如果情况并非如此,则不建议继续进行构建,因为如果没有树莓派专用的后端,即使 Qt 的其余部分编译成功,加速图形功能也将无法正常工作。
VkKhrDisplay
虽然 EGLFS 仅支持 OpenGL (ES),但 VkKhrDisplay是一个实验性的平台插件,支持通过VulkanAPI 进行渲染。它依赖于VK_KHR_display扩展系列来枚举显示设备和设置渲染。 请注意,图形栈中的 Vulkan 实现未必支持此功能。目前,该平台插件已在运行于树莓派 4 上的Mesa 和 V3DV环境中经过验证和测试。
该平台插件不支持 OpenGL 或任何软件渲染。因此,尝试显示基于QWidget 的用户界面将失败。唯一受支持的QWindow 表面类型是QSurface::VulkanSurface 。对于Qt Quick 应用程序,这意味着必须强制采用基于 Vulkan 的渲染,方法是:在环境中设置QSG_RHI_BACKEND=vulkan ,或者在创建QQuickWindow 或QQuickView 之前尽早调用QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan)。
要使用此平台插件,请使用-platform vkkhrdisplay 运行应用程序,或将QT_QPA_PLATFORM 设置为vkkhrdisplay 。该插件仅在 Qt 配置为支持 Vulkan 时才会构建。
目前尚未实现高级 EGLFS 风格的配置(例如 JSON 配置文件),也无法从同一应用程序输出到多个屏幕。不过,应用程序可以通过环境变量选择要使用的屏幕。
要确定索引值,请查看插件在调试输出中打印的日志。目前这些日志未分类(通过qDebug 打印),因为在大多数情况下,检查这些日志对于确保插件选择正确的显示器和模式至关重要。
QT_VK_DISPLAY_INDEX- 设置此选项时,将使用指定索引的显示设备。QT_VK_MODE_INDEX- 设置后,将使用具有给定索引的显示器。QT_VK_PHYSICAL_DEVICE_INDEX- 设置后,将使用指定索引的 Vulkan 物理设备。在嵌入式系统中,这在大多数情况下并不重要。请注意,Qt 图形栈的其余部分也会使用该变量。
输入(键盘、鼠标、触摸)处理与 EGLFS 类似,支持evdev 、libinput 和tslib 。但未实现鼠标光标的渲染。 这是因为该环境中不存在硬件光标的概念,而且在平台插件中使用 Vulkan 渲染光标(类似于 EGLFS 对 OpenGL 的处理方式)会因多种原因而存在问题。因此,目前该平台插件并不适合基于鼠标的输入。
相关的环境变量包括:
QT_QPA_DISABLE_INPUT- 禁用键盘/鼠标/触控输入。QT_QPA_NO_LIBINPUT- 即使libinput可用,也优先使用基于 evdev 的输入处理程序。QT_QPA_TSLIB- 要求使用传统的tslib库。
LinuxFB
该插件通过 Linux 的 fbdev 子系统直接向帧缓冲区写入数据。仅支持软件渲染的内容。 请注意,在某些配置下,显示性能可能会受到限制。若要在该平台插件上使用Qt Quick 应用程序,必须使用software 场景图后端,具体可通过在环境中设置QT_QUICK_BACKEND=software ,或调用setGraphicsApi()并传入QSGRendererInterface::Software 来实现。QWidget 应用程序,或表面类型为QSurface::RasterSurface 的QWindow 均受支持,但不包括QOpenGLWidget 等特殊控件。
由于 fbdev 在 Linux 内核中已被废弃,因此也提供了 DRM 哑缓冲区支持。要使用该功能,请将环境变量QT_QPA_FB_DRM 设置为非零值。 设置后,只要您的系统支持哑缓冲区,就不会访问诸如/dev/fb0 之类的传统帧缓冲设备。取而代之的是,渲染将通过 DRM API 进行配置,类似于 EGLFS 中的eglfs_kms 后端。输出采用双缓冲和页面翻转机制,从而也能为软件渲染的内容提供正确的垂直同步。
注意:当 使用“哑缓冲区”时, 下文所述的所有选项均不适用,因为物理和逻辑屏幕尺寸等属性都会被自动查询。
指定附加设置
linuxfb 插件允许您通过QT_QPA_PLATFORM 环境变量或-platform 命令行选项指定附加设置。例如,QT_QPA_PLATFORM=linuxfb:fb=/dev/fb1 指定必须使用帧缓冲设备/dev/fb1 ,而不是默认的fb0 。要指定多个设置,请用冒号(:)分隔各项。
| 设置 | 说明 |
|---|---|
fb=/dev/fbN | 指定帧缓冲设备。在多显示器配置中,此设置允许您在不同的显示器上运行应用程序。目前,尚无法在一个 Qt 应用程序中使用多个帧缓冲器。 |
size=<width>x<height> | 指定以像素为单位的屏幕尺寸。该插件会尝试从帧缓冲设备查询显示器的物理和逻辑尺寸。但此查询未必总能得到正确结果;可能需要显式指定这些值。 |
mmsize=<width>x<height> | 指定以毫米为单位的物理宽度和高度。 |
offset=<width>x<height> | 指定屏幕左上角的像素偏移量。默认位置为(0, 0) 。 |
nographicsmodeswitch | 指定不将虚拟终端切换至图形模式(KD_GRAPHICS )。通常,启用图形模式会禁用闪烁光标和屏幕清屏功能。但是,当设置此参数时,这两项功能也会被跳过。 |
tty=/dev/ttyN | 覆盖虚拟控制台。仅在未设置nographicsmodeswitch 时使用。 |
从 Qt 5.9 开始,EGLFS 和 LinuxFB 在窗口尺寸策略方面的行为已实现同步:使用这两种平台插件时,第一个顶级窗口都会被强制覆盖整个屏幕。如果不需要这种行为,请将QT_QPA_FB_FORCE_FULLSCREEN 环境变量设置为0 ,以恢复早期 Qt 版本的行为。
显示输出
单个 Qt 应用程序对一个或多个显示器的支持程度因平台插件而异。支持情况通常取决于设备及其图形堆栈。
带 eglfs_kms 后端的 EGLFS
当使用 KMS/DRM 后端时,EGLFS 会通过 `QGuiApplication::screens()` 报告所有可用的屏幕。应用程序可通过 `QWindow::setScreen()` 向不同的屏幕显示不同的窗口。
注意: 每块屏幕仅允许一个全屏窗口的限制 仍然适用。在将QWindow 设为可见后切换屏幕也不受支持。因此,嵌入式应用程序必须在调用QWindow::show()之前,先完成所有必要的QWindow::setScreen()调用。
当您开始在某台嵌入式设备上进行开发时,通常需要验证设备和驱动程序的行为,并确保连接的显示器能够正常工作。一种简单的方法是使用hellowindow示例。使用-platform eglfs --multiscreen --timeout 参数启动该示例,会在每个连接的屏幕上显示旋转的 Qt 徽标几秒钟。
自定义配置
KMS/DRM 后端还支持通过 JSON 文件进行自定义配置。要启用此功能,请将QT_QPA_EGLFS_KMS_CONFIG 环境变量设置为该文件的名称。您还可以通过 Qt 资源系统将此文件嵌入到应用程序中。
这些配置选项中的大多数适用于所有基于 KMS/DRM 的后端,无论其采用何种缓冲区管理技术(GBM 或 EGLStreams)。
注意:配置 文件被视为受信任的内容,完全由设备或平台创建者控制和管理。这些文件不应以任何形式向最终用户公开。
以下是一个配置示例:
{
"device": "/dev/dri/card1",
"hwcursor": false,
"pbuffers": true,
"outputs": [
{
"name": "VGA1",
"mode": "off"
},
{
"name": "HDMI1",
"mode": "1024x768"
}
]
}在此,我们对指定设备进行配置,使其:
- 该设备将不使用硬件光标(转而通过 OpenGL 渲染鼠标光标;默认情况下,硬件光标处于启用状态,因为其效率更高)。
- 它使用标准的 EGL pbuffer 表面来支持QOffscreenSurface (默认情况下此功能被禁用,而是使用 gbm 表面)。
- VGA接口的输出被禁用,而HDMI接口处于活动状态,分辨率为1024x768。
此外,此配置还会禁用通过libudev 搜索设备;而是直接使用指定的设备。
当未定义mode 时,将选择系统首选模式。mode 接受的值包括:off 、current 、preferred 、skip 、widthxheight、widthxheight@vrefresh,或一条模式行字符串。
指定current 将选择一种分辨率与当前模式相匹配的模式。由于模式设置仅在所需模式与活动模式实际不同时才会进行(除非通过QT_QPA_EGLFS_ALWAYS_SET_MODE 环境变量强制执行),因此该值有助于保留当前模式以及Qt未触及的各平面中的内容。
skip 会导致输出连接器被忽略,如同其已断开连接一般。off 效果类似,但它会更改模式并关闭显示。
默认行为
默认情况下,DRM 层报告的所有屏幕都被视为一个大型虚拟桌面。鼠标光标的实现考虑到了这一点,并能如预期般在各个屏幕间移动。虽然不建议这样做,但您可以在配置中将separateScreens 设置为false 来禁用虚拟桌面。
默认情况下,虚拟桌面根据系统报告的连接器顺序从左到右排列。若要更改此顺序,请将virtualIndex 设置为从0开始的数值。
例如,以下配置使用首选分辨率,同时确保虚拟桌面的左侧为连接到HDMI端口的显示器,而右侧为连接到DisplayPort的显示器:
{
"device": "drm-nvdc",
"outputs": [
{
"name": "HDMI1",
"virtualIndex": 0
},
{
"name": "DP1",
"virtualIndex": 1
}
]
}数组中元素的顺序无关紧要。未指定虚拟索引的输出设备将排在其他设备之后,同时保留 DRM 连接器列表中的原始顺序。
要创建垂直桌面空间(即从上到下堆叠,而不是从左到右),请在 `device ` 之后添加一个 `virtualDesktopLayout ` 属性,其值为 `vertical`。
警告:建议 虚拟桌面中的所有屏幕使用相同的分辨率,否则当鼠标光标等元素进入仅存在于某个特定屏幕上的区域时,可能会出现意料之外的行为。
当virtualIndex 不足以满足需求时,可以使用virtualPos 属性来显式指定目标屏幕的左上角位置。以之前的示例为例,假设HDMI1的分辨率为1080p,以下代码片段将第二个基于HDMI的屏幕放置在第一个屏幕下方:
{
...
"outputs": [
...
{
"name": "HDMI2",
"virtualPos": "0, 1080"
}
]
}注意: 若需要鼠标支持,请避免 此类配置。在非线性布局中,鼠标光标的行为可能会出乎意料。触控操作则不会出现此类问题。
自动查询物理屏幕尺寸
在某些情况下,通过DRM自动查询物理屏幕尺寸可能会失败。通常会使用QT_QPA_EGLFS_PHYSICAL_WIDTH 和QT_QPA_EGLFS_PHYSICAL_HEIGHT 环境变量来提供缺失的值。但在存在多台显示器时,此方法已不再适用。取而代之,请在outputs 列表中使用physicalWidth 和physicalHeight 属性,以毫米为单位指定尺寸。
注意: 不建议使用不同的 物理尺寸(从而导致逻辑 DPI 不同),因为某些图形堆栈组件可能无法识别多屏幕环境,且仅依赖于第一块屏幕的值,这可能会导致意外问题。
活动输出和 QScreen 实例
outputs 数组中的每个活动输出都对应一个由QGuiApplication::screens()报告的QScreen 实例。默认情况下,QGuiApplication::primaryScreen()报告的主屏幕是第一个注册的屏幕。如果您未使用virtualIndex ,则意味着该决定基于DRM连接器的顺序。要覆盖此行为,请将outputs 列表中所需条目的primary 属性设置为true 。
例如,为了确保即使系统恰好首先报告了HDMI输出,与VGA输出对应的屏幕仍被设为主要显示器,请执行以下操作:
{
"device": "/dev/dri/card0",
"outputs": [
{ "name": "HDMI1" },
{ "name": "VGA1", "mode": "1280x720", "primary": true },
{ "name": "LVDS1", "mode": "off" }
]
}在故障排除时,启用 KMS/DRM 后端的调试日志可能会有所帮助。要实现这一点,请启用qt.qpa.eglfs.kms 分类日志规则。
注意:在 嵌入式环境中,与完整的窗口系统相比,虚拟桌面的功能更为有限。应避免窗口跨多个屏幕显示、非全屏窗口以及在屏幕之间移动窗口,否则可能无法按预期运行。
常见用例
多屏幕配置中最常见且支持最完善的使用场景,是在每块屏幕上分别打开一个专用的QQuickWindow 或QQuickView 窗口。在Qt Quick 场景图的默认threaded 渲染循环下,每个窗口都将拥有自己的专用渲染线程。这种设计非常理想,因为各线程可以根据垂直同步(vsync)独立进行限速,且不会相互干扰。 而使用basic 渲染循环时,这可能会引发问题,导致动画质量下降。
例如,发现所有连接的屏幕并为每个屏幕创建一个QQuickView ,可以像这样实现:
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
QVector<QQuickView *> views;
for (QScreen *screen : app.screens()) {
QQuickView *view = new QQuickView;
view->setScreen(screen);
view->setResizeMode(QQuickView::SizeRootObjectToView);
view->setSource(QUrl("qrc:/main.qml"));
QObject::connect(view->engine(), &QQmlEngine::quit, qGuiApp, &QCoreApplication::quit);
views.append(view);
view->showFullScreen();
}
int result = app.exec();
qDeleteAll(views);
return result;
}eglfs_kms的高级功能
克隆(镜像)
支持屏幕克隆(镜像)。可通过clones 属性启用此功能:
{
"device": "/dev/dri/card0",
"outputs": [
{ "name": "HDMI1", "mode": "1920x1080" },
{ "name": "DP1", "mode": "1920x1080", "clones": "HDMI1" }
]
}在此情况下,通过 DisplayPort 连接的显示器上的内容将与 HDMI 显示器上的内容相同。这是通过在两者上输出相同的缓冲区来实现的。
不过,此功能仅在以下条件同时满足时才能正常工作:分辨率相同、支持的缓冲区格式无冲突,且应用程序在与克隆目标关联的QScreen 上没有输出。 实际上,后一点意味着:与该QScreen (本例中为DP1)关联的任何QWindow 都绝不能执行QOpenGLContext::swapBuffers()操作。确保这些条件的实现取决于配置和应用程序。
使用 DRM 渲染节点的无头模式
系统支持通过 DRM 渲染节点实现无头模式。这允许在无需 DRM 主权限的情况下执行 GPU 计算(OpenGL 计算着色器、OpenCL)或离屏 OpenGL 渲染。在此模式下,即使已有其他进程正在向屏幕输出内容,应用程序仍可正常运行。
仅将 `device ` 从 `/dev/dri/card0 ` 切换为 `/dev/dri/renderD128 ` 并无实际意义,因为仍有许多操作无法在无头模式下执行。因此,必须将其与 `headless ` 属性结合使用,例如:
{
"device": "/dev/dri/renderD128",
"headless": "1024x768"
}请注意,窗口的大小仍会根据——现在已成为虚拟的——屏幕尺寸进行调整,因此需要在headless 属性中指定尺寸。此外,该模式下还缺乏基于垂直同步(vsync)的限速机制。
启用后,应用程序在无头模式下进行离屏渲染通常有两种选择:
使用普通窗口(例如QOpenGLWindow 的子类),并将其目标设置为窗口的默认帧缓冲区,实际上即为gbm_surface :
MyOpenGLWindow w;
w.show(); // will not actually show up on screen
w.grabFramebuffer().save("output.png");或者采用典型的离屏渲染方法,使用一个额外的 FBO:
QOffscreenSurface s;
s.setFormat(ctx.format());
s.create();
ctx.makeCurrent(&s);
QOpenGLFramebufferObject fbo(1024, 768);
fbo.bind();
ctx.functions()->glClearColor(1, 0, 0, 1);
ctx.functions()->glClear(GL_COLOR_BUFFER_BIT);
fbo.toImage().save("output.png");
ctx.doneCurrent();DRM API 选择
KMS/DRM 可与两种不同的 DRM API 配合使用,即传统 API 和原子 API。DRM 原子 API 的主要优势在于允许在同一个渲染循环内进行多次 DRM 平面更新,而传统 API 则要求每帧垂直同步(vsync)进行一次平面更新。
当您的应用程序需要将内容混合到叠加层中,并确保所有更新都在同一垂直同步周期内完成时,原子 API 非常有用。不过,并非所有设备都支持此 API,某些较旧的设备可能无法使用。KMS 后端默认使用传统 API,但您可以通过将环境变量 `QT_QPA_EGLFS_KMS_ATOMIC ` 设置为 1 来启用 DRM 原子 API。
使用小于屏幕分辨率的帧缓冲区也可能很有用。通过在 JSON 文件中使用size 参数,DRM 原子 API 可实现这一点。下面的示例在 3840x2160 视频模式下使用 1280x720 的帧缓冲区:
{
"device": "/dev/dri/card0",
"outputs": [
{ "name": "HDMI1", "mode": "3840x2160", "size": "1280x720", "format": "argb8888" }
]
}EGLFS 热插拔和热重载
如果通过QT_QPA_EGLFS_KMS_CONFIG 设置了 KMS 配置文件,QFileSystemWatcher 将监视该引用文件。该文件的任何更改都会触发其重新读取,并导致相应的更新。例如:布局更改、模式更改、屏幕开/关等。
类似地,但严格采用“主动启用”机制,可设置QT_QPA_EGLFS_HOTPLUG_ENABLED ,这将使 QDeviceDiscovery 监视 KMS 设备,以检测任何变化,例如屏幕的连接/插入和断开/拔出。
这两项功能可以且通常会并行使用。
为了正确处理由热插拔和热重载触发的动态变化,关键是要通过在相应出现和消失的屏幕上创建和销毁窗口来做出响应。
示例代码:
#include <QGuiApplication>
#include <QQuickView>
#include <QQuickItem>
#include <QScreen>
#include <QQmlContext>
QHash<QString, QQuickView*> screenNameToViewMap;
int main(int argc, char* argv[])
{
QGuiApplication app(argc,argv);
app.setQuitOnLastWindowClosed(false);
auto lRemove = [&](QScreen *screen) {
if (screen->name().compare(QStringLiteral("qt_Headless")) == 0)
return;
if (!screenNameToViewMap.contains(screen->name()))
return;
QQuickView *view = screenNameToViewMap.take(screen->name());
delete view;
};
auto lAdd = [&](QScreen *screen) {
if (screen->handle() == nullptr)
return;
if (screen->name().compare(QStringLiteral("qt_Headless")) == 0)
return;
if (screenNameToViewMap.contains(screen->name()))
return;
QQuickView *view = new QQuickView;
view->setSource(QUrl(QStringLiteral("qrc:/main.qml")));
view->setScreen(screen); // This is not as important as the next line, but good practice
view->setGeometry(screen->geometry()); // This is vital! Otherwise QWindow::show (/QWindowPrivate::create) will change the screen!
view->show();
screenNameToViewMap.insert(screen->name(), view);
};
QObject::connect(&app, &QGuiApplication::screenAdded, &app, lAdd, Qt::QueuedConnection);
QObject::connect(&app, &QGuiApplication::screenRemoved, &app, lRemove);
for (QScreen *screen : app.screens())
lAdd(screen);
return app.exec();
}请注意上例中关于 `QWindow::setGeometry` 的那行代码,这对确保窗口出现在预期的屏幕上至关重要!
由于 QML 中也存在相同的信号,因此也可以在 QML 中编写等效的代码。
特别是对于 `QT_QPA_EGLFS_HOTPLUG_ENABLED `,调整代码至关重要,因为某些屏幕在关闭时会表现得如同已断开连接一般。若未启用此功能,无论是 Qt 还是应用程序层级的代码都不会收到断开连接的通知。所有后端资源通常会保持原样,因此屏幕将重新出现,仿佛什么都没发生过。 启用QT_QPA_EGLFS_HOTPLUG_ENABLED 后,屏幕上的Qt资源(例如QScreen 、QPlatformScreen、backing-store等)将被销毁,并在连接时(例如重新打开屏幕)重新创建。因此,应用程序级别的资源(例如QWindow )也需要重新创建。
名为“qt_Headless”的备用屏幕旨在方便在无屏幕配置与有屏幕配置之间进行切换。对于应用程序级别的窗口,在大多数情况下可以且应当忽略该屏幕。
采用 eglfs_kms_egldevice 后端的 EGLFS
该后端通常用于 Tegra 设备,与上述提到的 KMS/DRM 后端类似,不同之处在于它依赖于 EGLDevice 和 EGLStream 扩展,而非 GBM。
有关此方法的技术细节,请参阅此演示文稿。
截至 Qt 5.7,该后端与其基于 GBM 的后端共享了许多内部实现。这意味着它支持多屏幕以及通过 `QT_QPA_EGLFS_KMS_CONFIG ` 进行的高级配置。但某些设置(如 `hwcursor ` 和 `pbuffers `)并不适用。
默认情况下,该后端会为每个输出的默认平面自动选择正确的 EGL 层。必要时,可通过将环境变量 `QT_QPA_EGLFS_LAYER_INDEX ` 设置为所需层的索引来覆盖此行为。此方法目前不支持多输出,因此其使用应仅限于单屏幕系统。 要查看可用的图层,并调试潜在的启动问题,请启用日志类别qt.qpa.eglfs.kms 。
在某些情况下,即使屏幕报告所需分辨率已设置,也可能需要在应用程序启动时执行视频模式设置。此操作通常会被优化掉,但如果屏幕仍处于关闭状态,请尝试将环境变量QT_QPA_EGLFS_ALWAYS_SET_MODE 设置为非零值,然后重新启动应用程序。
要配置后端使用的 EGLStream 对象的行为,请使用QT_QPA_EGLFS_STREAM_FIFO_LENGTH 环境变量。这假设目标系统支持KHR_stream_fifo 。默认情况下,流以邮箱模式运行。要切换到 FIFO 模式,请将其值设置为 1 或更大。该值指定了流最多可容纳的帧数。
在某些系统上,可能需要通过预定义的连接器来定位特定的叠加平面。仅通过QT_QPA_EGLFS_LAYER_INDEX 强制指定层索引并不会执行平面配置,因此仅靠此方法并不合适。相反,在这种特殊情况下,请使用QT_QPA_EGLFS_KMS_CONNECTOR_INDEX 和QT_QPA_EGLFS_KMS_PLANE_INDEX 环境变量。设置这些变量后,系统将仅使用指定的连接器和平面,所有其他输出都将被忽略。 后端会自动选择与目标平面对应的 EGL 层,并完成平面的配置。
KMS/DRM 多屏幕系统中的触摸输入
在多显示器系统中,触摸屏需要额外的考虑,因为触摸事件必须路由到正确的虚拟屏幕,这要求触摸屏与显示输出之间建立正确的映射关系。
该映射通过QT_QPA_EGLFS_KMS_CONFIG 中指定的 JSON 配置文件实现,并在前几节中进行了说明。当outputs 数组的某个元素中存在touchDevice 属性时,其值将被视为设备节点,触摸设备便会与相应的显示输出相关联。
例如,假设我们的触摸屏设备节点为 /dev/input/event5,且作为辅助屏幕通过 HDMI 连接到显示器中,则以下配置可确保正确的触摸(及合成鼠标)事件转换:
{
"device": "drm-nvdc",
"outputs": [
{
"name": "HDMI1",
"touchDevice": "/dev/input/event5",
"virtualIndex": 1
},
{
"name": "DP1",
"virtualIndex": 0
}
]
}注意:如有疑问,请在 启动应用程序前通过设置环境变量 `QT_LOGGING_RULES=qt.qpa.*=true ` 启用图形和输入子系统的日志记录。这将有助于识别正确的输入设备节点,并可能发现否则难以调试的输出配置问题。
注意:截至 Qt 5.14 版本,上述功能仅支持 evdevtouch 和 libinput 后端。其他变体将继续将事件路由到主屏幕。若要在支持多种输入后端的系统上强制使用 evdevtouch,请将环境变量QT_QPA_EGLFS_NO_LIBINPUT 设置为1 。
EGLFS 与其他后端
其他后端通常基于通过厂商的 EGL 实现直接针对帧缓冲区或合成 API,通常对多显示器支持有限或完全不支持。 在搭载 Vivante GPU 的 i.MX6 架构主板上,可以使用QT_QPA_EGLFS_FB 环境变量来指定目标帧缓冲区,其用法与 linuxfb 类似。在树莓派上,可以使用QT_QPA_EGLFS_DISPMANX_ID 环境变量来指定输出屏幕。该值对应于DISPMANX_ID_ 常量之一,请参阅 Dispmanx 文档。 请注意,与 KMS/DRM 不同,这些方法通常不允许同一应用程序向多个屏幕输出。此外,可能还有驱动程序专用的环境变量或内核参数可用于控制所使用的帧缓冲区。请参阅嵌入式主板的文档。
视频内存
对于配备固定容量专用视频内存的系统,在运行基于Qt Quick 或QOpenGLWidget 等类的 Qt 应用程序前,可能需要格外注意。默认设置对于此类应用程序可能不足,尤其是在高分辨率(例如全高清)屏幕上显示时。 在此情况下,应用程序可能会以意想不到的方式出现故障。建议确保至少有 128 MB 的 GPU 内存可用。对于未为 GPU 预留固定内存量的系统,则不存在此问题。
linuxfb
使用fb 插件参数来指定要使用的帧缓冲设备。
Unix 信号处理程序和控制台状态
eglfs 和 linuxfb 等面向控制台的平台插件默认会安装信号处理程序,以捕获中断(SIGINT )、暂停和继续(SIGTSTP 、SIGCONT )以及终止(SIGTERM )。 这样,当应用程序因kill 、Ctrl+C 或Ctrl+Z 而终止或进入挂起状态时,键盘、终端光标以及可能的其他图形状态均可被恢复。(尽管通过键盘终止或挂起仅在设置了QT_QPA_ENABLE_TERMINAL_KEYBOARD 时才可行,详见《嵌入式 Linux 设备上的输入》)。 然而,在某些情况下,捕获SIGINT 可能是不希望的,例如它可能会与远程调试发生冲突。因此,提供了环境变量QT_QPA_NO_SIGNAL_HANDLER ,用于禁用所有内置信号处理。
这些处理程序在平台插件初始化时被安装,将替换应用程序在此之前设置的任何处理机制。
警告:在执行 `SIGINT ` 和` SIGTERM ` 时, 控制台状态将被恢复,随后进程将通过 `_exit()` 退出。此时不会执行 `atexit() ` 处理程序、静态析构函数或 Qt 关闭代码,且缓冲数据也不会被刷新。因此,`systemctl stop ` 或简单的 `kill ` 都会立即终止应用程序。 需要在SIGTERM 上持久化状态的应用程序必须设置QT_QPA_NO_SIGNAL_HANDLER 并安装自己的处理程序,同时接管控制台状态的恢复工作。
控制台状态仅在上述路径以及正常关机时才会被恢复。当进程因SIGSEGV 、SIGABRT 或std::terminate() 而终止时,控制台状态不会被恢复。对于同样会将虚拟终端切换至图形模式的 linuxfb 而言,异常终止的应用程序可能会导致本地控制台屏幕变空且无法响应键盘输入,直至设备重启。 在该控制台是唯一本地恢复路径的设备上,请提供替代方案,例如串行控制台或可重启设备的看门狗。使用QT_QPA_PRESERVE_CONSOLE_STATE 保持终端光标和屏幕 blanking 设置不变,并使用 linuxfb 的nographicsmodeswitch 跳过图形模式切换。
安全注意事项
本页所述的平台插件在无窗口系统环境下运行,因此承担了通常由合成器或显示服务器负责的职责。本节描述了它们所信任的内容,以及设备创建者应控制的内容。另请参阅Qt 共享安全模型。
配置属于受信任内容
嵌入式平台插件的行为——即打开哪些设备节点、将哪个设备集成插件加载到进程中、触摸坐标如何映射到屏幕上——由环境变量、KMS/DRM JSON 配置文件以及可选的键位映射和光标图集文件决定。 所有这些均属于受信任内容:预期在构建设备镜像时即已确定,此后由设备创建者进行控制。Qt 会检查它们的格式是否正确,但不会进行进一步的验证,且这些内容不应以任何形式暴露给设备的最终用户。
其中有两项内容值得特别关注:
QT_QPA_EGLFS_INTEGRATION不会限制可加载的插件。请求的后端仅会被移至候选列表的首位;若其初始化失败,系统仍会依次尝试其余后端。 在存在多个设备集成插件的系统中,部署中未指定的后端最终仍可能被加载并初始化硬件。请仅分发设备所需的后端。- 触摸校准和旋转设置——无论是通过
QT_QPA_LIBINPUT_TOUCH_MATRIX、evdev、rotate、invertx和inverty参数,还是通过JSON配置文件中的touchDevice映射——决定了按压操作的报告位置。 设置错误会导致用户激活的控件与其实际触碰的控件不一致,而在人机界面中,这种行为属于安全特性。请将这些设置视为设备经过验证的配置的一部分,而非现场可调的参数。
输入设备不经过过滤
Qt 的设备发现机制会打开所有与所查找功能类匹配的/dev/input/event* 节点;当支持libudev 时,还会打开应用程序运行期间后续出现的设备。 该机制不包含白名单,不进行供应商或产品过滤,也不存在预期的设备集合概念。来自所有被接受设备的事件都会被传递给应用程序,仿佛它们来自预期设备一样;应用程序无法区分它们。
因此,限制哪些设备存在以及谁可以读取它们,是设备创建者的任务,且应在内核、udev 以及文件系统权限级别上完成,而非在应用程序中。一个未被允许读取/dev/input/* 的Qt应用程序将无法读取任何输入。此外:
- 在
QT_QPA_EVDEV_MOUSE_PARAMETERS、QT_QPA_EVDEV_KEYBOARD_PARAMETERS和QT_QPA_EVDEV_TOUCHSCREEN_PARAMETERS中显式指定设备名称可绕过自动发现机制,从而固定应用程序所使用的设备集。 - 传递
grab=1可使应用程序获得对设备的独占访问权限,在仅运行单个应用程序的设备上,这值得考虑。 - 请参考
disable-zap,参见《嵌入式 Linux 设备上的输入》。
显示器属于不受信任的输入
显示器通过连接器声明的 EDID 块由所连接的设备提供,该设备可能是显示器、适配器、KVM 切换器或捕获设备,并且在每次热插拔事件时都会被重新解析。 在采用 KMS/DRM 后端的 eglfs 中,其中包含的值会通过 `QScreen::manufacturer()`、`QScreen::model()`、`QScreen::serialNumber()` 以及报告的物理尺寸传递给应用程序。 请将这些值视为受外部因素影响:不要将其用作标识符,并注意它们可能会出现在日志中。物理大小可以通过QT_QPA_EGLFS_PHYSICAL_WIDTH 和QT_QPA_EGLFS_PHYSICAL_HEIGHT 进行覆盖,或者在 JSON 配置文件中按输出进行设置。
运行时依赖项
嵌入式平台插件是基于板级支持包(BSP)或发行版(而非 Qt)提供的库构建的轻量级适配器,并在应用程序进程中运行。根据配置不同,这些插件包括:
- eglfs:EGL,以及根据后端不同,可能包括 GBM、Mesa 和 GPU 驱动程序,或供应商的 EGL 实现;
libdrm,用于 KMS/DRM 后端。 - vkkhrdisplay:Vulkan 加载器及已安装的 Vulkan 驱动程序。
- linuxfb:内核
fbdev接口,或在设置了QT_QPA_FB_DRM时为libdrm。 - 输入:
libinput、libudev和mtdev;xkbcommon,该模式还会从系统中读取键位映射数据;或tslib,其配置文件决定加载哪些过滤器模块,因此 Qt 既无法选择也无法枚举这些模块。
请将这些内容纳入设备的软件物料清单并保持更新,同时仅配置设备实际使用的后端模块。对于新设备,请优先使用libinput 而非旧版tslib 。
字体
Qt 通常使用fontconfig 来访问系统字体。如果fontconfig 不可用,Qt 将回退到使用QBasicFontDatabase 。在这种情况下,Qt 应用程序将在 Qt 的lib/fonts 目录中查找字体。Qt 会自动检测预渲染字体和 TrueType 字体。可以通过设置QT_QPA_FONTDIR 环境变量来覆盖该目录。
有关支持格式的更多信息,请参阅《Qt for Embedded Linux 字体》。
注意:Qt 不再在lib/fonts 目录中提供任何字体。这意味着必须由平台(系统镜像)提供必要的字体。
用于嵌入式 Linux 设备窗口系统的平台插件
XCB
这是常规桌面 Linux 平台上使用的 X11 插件。在某些提供 X 以及xcb 所需开发文件的嵌入式环境中,该插件的功能与在常规 PC 桌面上的表现完全一致。
注意:在某些设备上, 由于 EGL 实现与 Xlib 不兼容,因此 X 环境下无法提供 EGL 和 OpenGL 支持。在这种情况下,XCB 插件是在不支持 EGL 的情况下构建的,这意味着Qt Quick 2 或其他基于 OpenGL 的应用程序无法与该平台插件配合使用。 不过,它仍可用于运行软件渲染的应用程序(例如基于QWidget 的应用程序)。
一般而言,不建议在嵌入式设备上使用 XCB。像 eglfs 这样的插件通常能提供更好的性能和硬件加速。
Wayland
Wayland是一个轻量级的窗口系统;更准确地说,它是一个供客户端与显示服务器通信的协议。
Qt Wayland 提供了一个wayland 平台插件,允许 Qt 应用程序连接到 Wayland Compositor。
更多详细信息,请参阅Wayland 和 Qt。
性能优化指南
尽可能使用硬件渲染
当性能对您的应用程序至关重要时,请避免使用依赖软件渲染的 Qt 模块。在可能的情况下,请优先选择依赖硬件渲染的模块。
遵循以下最佳实践Qt Quick
请遵循QML 和Qt Quick 的最佳实践,特别是在包含QML CMake API 方面,以确保qmllint、QML 脚本编译器(qmlsc) 和QML 类型编译器(qmltc) 可用。 此外,建议编写声明式 QML 代码,并尽量减少 JavaScript 的使用。有关过度使用 JavaScript 可能对性能造成的影响,请参阅《QML 性能注意事项与建议》。
请使用图像/纹理和着色器效果,而非 Canvas QML 类型
绘制自定义 UI 元素时,请使用图像/纹理和着色器效果。请勿使用 QML 中的 `Canvas ` 类型。着色器需要硬件加速(GPU)。
使用Qt Quick 而非Qt Widgets
使用Qt Quick 时,既可使用硬件加速后端,也可使用软件渲染后端。对于复杂的 UI,不建议在嵌入式目标上使用Qt Widgets ,因为它始终会使用软件后端。
这里存在权衡:
- 使用 QML 引擎和 `Qt Quick ` 会带来初始开销。
- 如果您的 UI 非常简单且很少重绘,使用 Widgets 而不是 QML 实现可能会更快。
- 如果您的用户界面需要利用动画、smooth scrolling 、scaling 、rendering effects或3D效果,则必须具备GPU加速功能,因此需要使用Qt Quick 。
选择与您的 UI 尺寸相适应的分辨率
使用较高分辨率时需谨慎。720p及更高的分辨率可能会降低性能。
使用 QML Window 类型作为应用程序的根元素
请将Window 作为应用程序的根元素,并设置应用程序的背景为color 。
这样做的原因是,Window组件具有color属性,其效果相当于清除缓冲区。若将全屏Rectangle 作为应用程序的根Item 来渲染背景,将会产生额外的绘制调用。 对于某些 RHI 后端,这可能效果相同,但glClear 调用与绘制四边形之间存在差异。在大多数情况下,单张不透明图像可能不会对性能产生太大影响,但若在该项的颜色中使用透明度值,则可能会导致显著的性能下降。
相关主题
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.