嵌入式 Linux 的 Qt

用于嵌入式 Linux 设备的平台插件

在嵌入式 Linux 系统中,您可以使用多种平台插件:EGLFS、VkKhrDisplay、LinuxFB 或 Wayland。这些插件的可用性取决于 Qt 的配置方式。其中,Wayland 需要存在一个合成器,并提供一个支持多窗口的完整窗口系统,类似于 X11 或 Windows。其他插件则无需任何窗口系统,这意味着 Qt 应用程序可以完全控制渲染和输出。它们通常在每个屏幕上支持一个全屏 Qt "窗口"。

EGLFS 是许多电路板上的默认插件。如果不合适,可使用QT_QPA_PLATFORM 环境变量请求其他插件。另外,要进行快速测试,也可以使用-platform 命令行参数,语法相同。

注意: 从 Qt 5.0 开始,Qt 不再有自己的窗口系统 (QWS) 实现。对于单进程用例,Qt Platform Abstraction是更好的解决方案;多进程用例则通过Wayland 支持。

有关使用嵌入式Linux工具链配置 Qt 以进行交叉编译的概述,请参阅配置嵌入式 Linux设备

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 强制第一个顶级窗口(QWidgetQQuickView )变成全屏。该窗口也被选为窗口,所有其他顶层窗口都在其中合成。例如,对话框、弹出式菜单或组合框。这种行为是必要的,因为在 EGLFS 中,总是存在一个本地窗口和一个 EGL 窗口表面;它们都属于最先创建的部件或窗口。当应用程序的整个生命周期中都存在一个主窗口,而所有其他窗口小部件要么不是顶层的,要么是在主窗口显示后才创建的时候,这种方法就非常有效。

基于 OpenGL 的窗口还有其他限制。EGLFS 支持单个全屏 GL 窗口(截至 Qt 5.3),如基于 OpenGL 的QWindowQQuickViewQOpenGLWidget 。不支持打开其他 OpenGL 窗口或将此类窗口与基于QWidget 的内容混合;Qt 会以错误消息终止应用程序。

此外,EGLFS 还不支持为桌面平台或窗口系统环境设计的 API,如拖放(Drag and Drop)。

EGLFS 使用的环境变量

如有必要,可使用以下环境变量配置eglfs

环境变量说明
QT_QPA_EGLFS_INTEGRATION除编译钩子外,还可以使用动态加载的插件来提供设备或供应商特定的适应性。该环境变量强制执行特定插件。例如,将其设置为eglfs_kms会使用 KMS/DRM 后端。只有在设备 makepecs 中没有指定静态钩子或编译钩子时,才可使用此选项。实际上,传统的编译钩子已经很少使用,几乎所有的后端现在都迁移到了插件。设备规格说明中仍包含一个相关的EGLFS_DEVICE_INTEGRATION 条目(尽管是可选的):特定设备的首选后端名称。如果目标系统中存在多个插件,则应避免设置此环境变量。在桌面环境中,根据DISPLAY 环境变量的存在情况,KMS 或 X11 后端将被优先使用。

注意: 在某些板卡上,none 的特殊值会被使用,而不是实际的插件。这表明在帧缓冲器上使用 EGL 不需要特殊的集成;不需要加载任何插件。

QT_QPA_EGLFS_PHYSICAL_WIDTHQT_QPA_EGLFS_PHYSICAL_HEIGHT以毫米为单位指定物理屏幕的宽度和高度。请注意,自 Qt 6 起,物理屏幕尺寸不再用于确定逻辑 dpi。
QT_QPA_EGLFS_ROTATION指定在基于QWidget 的应用程序中应用于软件渲染内容的旋转。支持的值有 180、90 和 -90。此变量不适用于基于 OpenGL 的窗口,包括Qt QuickQt Quick 应用程序可在其 QML 场景中应用变换。标准的eglfs 鼠标指针总是会考虑到该值,并适当定位和旋转指针图像,与应用程序类型无关。不过,特殊的光标实现(如 KMS/DRM 后端的硬件光标)可能不支持旋转。
QT_QPA_EGLFS_FORCEVSYNC设置后,每次调用 eglSwapBuffers() 后,eglfs 都会请求帧缓冲设备上的FBIO_WAITFORVSYNC 。该变量仅适用于依赖传统 Linuxfbdev 子系统的后端。通常情况下,默认交换间隔为 1,Qt XML 假定调用 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_WIDTHQT_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 QuickQSG_INFO 变量一起使用时,可以获得有用的信息,用于排除与 EGL 配置相关的问题。

记录

除了QT_QPA_EGLFS_DEBUG 之外,eglfs 还支持 Qt XML 的现代分类日志系统。可使用以下日志类别:

  • qt.qpa.egldeviceintegration - 启用动态加载的后端日志。使用该类别可检查正在使用的后端。
  • qt.qpa.input - 启用 和 输入处理程序的调试输出。使用此类别可检查给定的输入设备是否被识别和打开。evdev libinput
  • qt.qpa.eglfs.kms - 启用 KMS/DRM 后端的详细日志记录。

运行configure 后,请务必检查其输出。这是最简单快捷的方法,可以确定是否启用了必要的 EGLFS 后端、libudev 或 libinput。简而言之,如果configure 的输出中出现不希望出现的 "否",请运行:

./configure -v

打开详细输出,这样就能看到每个 configure 测试的编译器和链接器调用情况。

注意: 如果你遇到有关缺少头文件、库或看似隐蔽的链接器故障的错误,这通常是系统根目录不完整或损坏的迹象,与 Qt 无关。

例如,在使用 Broadcom 专有图形驱动程序的 Raspberry Pi 上运行时,输出应包含类似下面的内容:

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

如果不是这种情况,则不建议继续编译,因为如果没有 Raspberry Pi 专用的后端,即使 Qt 的其他部分编译成功,加速图形也无法运行。

VkKhrDisplay

EGLFS 仅支持 OpenGL (ES),而 VkKhrDisplay 是一个实验性平台插件,支持使用VulkanAPI 进行渲染。它依赖VK_KHR_display系列扩展来枚举显示和设置呈现。请注意,图形栈中的 Vulkan 实现并不支持该功能。目前,该平台插件已通过在 Raspberry Pi 4 上运行的Mesa 和 V3DV进行了验证和测试。

本平台插件不支持 OpenGL 或任何软件渲染。因此,尝试显示基于QWidget 的用户界面将失败。QWindow 唯一支持的表面类型是QSurface::VulkanSurface 。对于Qt Quick 应用程序来说,这意味着必须通过在环境中设置QSG_RHI_BACKEND=vulkan 或在创建QQuickWindowQQuickView 之前调用QQuickWindow::setGraphicsApi(QSGRendererInterface::Vulkan); 来实现基于 Vulkan 的渲染。

要使用该平台插件,请使用-platform vkkhrdisplay 运行应用程序,或将QT_QPA_PLATFORM 设置为vkkhrdisplay 。只有在 Qt XML 配置为支持 Vulkan 时,才会构建该插件。

目前尚未实现高级 EGLFS 式配置(如 JSON 配置文件)或从同一应用程序输出到多个屏幕。不过,应用程序可以通过环境变量选择要使用的屏幕。

要确定索引值,请查看插件在调试输出中打印的日志。目前,这些日志未分类(通过qDebug 打印),因为在大多数情况下,检查日志对于确保插件选择适当的显示方式和模式至关重要。

  • QT_VK_DISPLAY_INDEX - 设置后,将使用给定索引的显示屏。
  • QT_VK_MODE_INDEX - 设置后,将使用给定索引的模式。
  • QT_VK_PHYSICAL_DEVICE_INDEX - 设置后,将使用具有给定索引的 Vulkan 物理设备。大多数情况下,这与嵌入式设备无关。请注意,Qt 图形栈的其他部分也会使用此变量。

输入(键盘、鼠标、触摸)处理与 EGLFS 类似,支持evdevlibinputtslib 。但没有实现鼠标光标渲染。这是因为在该环境中没有硬件光标的概念,而且在平台插件中使用 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 ,或使用QSGRendererInterface::Software 调用setGraphicsApi() 。支持QWidget 应用程序或表面类型为QSurface::RasterSurfaceQWindow ,但不包括QOpenGLWidget 等特殊部件。

由于 fbdev 在 Linux 内核中已被弃用,因此还提供了 DRM 哑缓冲区支持。要使用它,可将QT_QPA_FB_DRM 环境变量设置为非零值。设置后,只要系统支持哑缓冲区,就不会访问/dev/fb0 等传统帧缓冲区设备。相反,渲染是通过 DRM API 设置的,类似于 EGLFS 中的eglfs_kms 后端。输出采用双缓冲和翻页方式,为软件渲染内容提供适当的 vsync 功能。

注意: 当使用哑缓冲区时,下面描述的选项都不适用,因为物理和逻辑屏幕尺寸等属性都是自动查询的。

指定其他设置

linuxfb 插件允许您通过QT_QPA_PLATFORM 环境变量或-platform 命令行选项指定其他设置。例如,QT_QPA_PLATFORM=linuxfb:fb=/dev/fb1 指定必须使用帧缓冲器设备/dev/fb1 ,而不是默认的fb0 。要指定多个设置,请用冒号(:)分隔 m。

设置说明
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 XML 徽标,持续几秒钟。

自定义配置

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, 宽度x高度, 宽度x高度@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_WIDTHQT_QPA_EGLFS_PHYSICAL_HEIGHT 环境变量来提供缺失的值。当存在多个屏幕时,这种方法就不再适用了。相反,可以使用outputs 列表中的physicalWidthphysicalHeight 属性来指定以毫米为单位的尺寸。

注意: 我们不鼓励使用不同的物理尺寸和不同的逻辑 DPI,因为这可能会导致一些图形栈组件不知道多屏幕的情况,而只依赖于第一个屏幕的值,从而产生意想不到的问题。

活动输出和 QScreen 实例

outputs 数组中的每个活动输出对应QGuiApplication::screens() 中报告的一个QScreen 实例。默认情况下,QGuiApplication::primaryScreen() 报告的主屏幕是最先注册的屏幕。如果不使用virtualIndex ,这意味着将根据 DRM 连接器的顺序来决定。要覆盖这一点,请在outputs 列表中将所需条目上的primary 属性设置为true

例如,要确保与 VGA 输出相对应的屏幕为主屏幕,即使系统碰巧首先报告的是 HDMI 屏幕,请执行以下操作:

{
  "device": "/dev/dri/card0",
  "outputs": [
      { "name": "HDMI1" },
      { "name": "VGA1", "mode": "1280x720", "primary": true },
      { "name": "LVDS1", "mode": "off" }
  ]
}

为了排除故障,启用 KMS/DRM 后台的调试日志可能会很有用。为此,请启用qt.qpa.eglfs.kms 分类日志规则。

注意: 在嵌入式环境中,虚拟桌面与完整的窗口系统相比受到更多限制。应避免出现多个屏幕重叠的窗口、非全屏窗口以及在屏幕间移动窗口的情况,否则可能无法发挥预期功能。

常见用例

多屏设置中最常见、支持最好的用例是为每个屏幕打开一个专用的QQuickWindowQQuickView 。在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 相关的任何QWindow (在本例中为 DP1)都不得执行QOpenGLContext::swapBuffers() 操作。这需要配置和应用程序来确保。

使用 DRM 渲染的无头模式

通过 DRM 渲染节点支持无头模式。这允许执行 GPU 计算(OpenGL 计算着色器、OpenCL)或屏幕外 OpenGL 渲染,而无需 DRM 主权限。在这种模式下,即使已经有另一个进程在向屏幕输出,应用程序也能正常运行。

仅仅将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 atomic 可以做到这一点。下面的示例在 3840x2160 视频模式下使用了 1280x720 帧缓冲:

{
  "device": "/dev/dri/card0",
  "outputs": [
    { "name": "HDMI1", "mode": "3840x2160", "size": "1280x720", "format": "argb8888" }
  ]
}

带有 eglfs_kms_egldevice 后端的 EGLFS

该后端通常用于 Tegra 设备,与上述 KMS/DRM 后端类似,只是它依赖 EGLDevice 和 EGLStream 扩展而非 GBM。

有关此方法的技术细节,请查看此演示文稿

从 Qt 5.7 开始,该后端与基于 GBM 的后端共享许多内部实现。这意味着通过QT_QPA_EGLFS_KMS_CONFIG 支持多屏幕和高级配置。不过,某些设置(如hwcursorpbuffers )并不适用。

默认情况下,后台会自动为每个输出的默认平面选择正确的 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_INDEXQT_QPA_EGLFS_KMS_PLANE_INDEX 环境变量。设置这些变量后,只有指定的连接器和平面会被使用,所有其他输出都将被忽略。后台将负责选择与所需平面相对应的 EGL 层,并配置平面。

KMS/DRM 多屏幕系统中的触摸输入

在多显示屏系统中,触摸屏需要额外的考虑,因为触摸事件必须路由到正确的虚拟屏幕,这就需要在触摸屏和显示输出之间建立正确的映射。

映射是通过QT_QPA_EGLFS_KMS_CONFIG 中指定的 JSON 配置文件完成的,在前面的章节中已有描述。当touchDevice 属性出现在outputs 数组的一个元素中时,该值将被视为设备节点,触摸设备将与相关的显示输出相关联。

例如,假设我们的触摸屏的设备节点为 /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 类似。在 Raspberry Pi 上,QT_QPA_EGLFS_DISPMANX_ID 环境变量可用于指定输出到哪个屏幕。该值对应DISPMANX_ID_ 常量之一,请参阅 Dispmanx 文档。请注意,这些方法与 KMS/DRM 不同,通常不允许从同一应用程序输出到多个屏幕。另外,也可以使用特定于驱动程序的环境变量或内核参数来控制所使用的帧缓冲器。请参阅嵌入式板的文档。

视频内存

在运行基于Qt Quick 或类(如QOpenGLWidget )的 Qt XML 应用程序之前,使用固定数量专用视频内存的系统可能需要格外小心。默认设置可能无法满足此类应用程序的要求,尤其是在高分辨率(如全高清)屏幕上显示时。在这种情况下,它们可能会以意想不到的方式开始失效。建议确保至少有 128 MB GPU 内存可用。对于没有为 GPU 预留固定内存量的系统,这不是问题。

linuxfb

使用fb 插件参数指定要使用的帧缓冲设备。

Unix 信号处理器

面向控制台的平台插件(如 eglfs 和 linuxfb)默认安装信号处理器,以捕获中断(SIGINT )、挂起和继续(SIGTSTP,SIGCONT )以及终止(SIGTERM )。这样,当应用程序因kill ,或Ctrl+CCtrl+Z 而终止或暂停时,键盘、终端光标以及其他图形状态就可以恢复(不过,通过键盘终止或暂停只有在设置了QT_QPA_ENABLE_TERMINAL_KEYBOARD 时才能实现,如上文 "输入 "部分所述)。不过,在某些情况下,捕获SIGINT 并不可取,因为这可能会与远程调试等发生冲突。因此,环境变量QT_QPA_NO_SIGNAL_HANDLER 可用于退出所有内置信号处理。

字体

Qt XML 通常使用fontconfig 来访问系统字体。如果fontconfig 不可用,Qt 将退回到使用QBasicFontDatabase 。在这种情况下,Qt 应用程序将在 Qt 的lib/fonts 目录中查找字体。Qt 将自动检测预渲染字体和 TrueType 字体。可以通过设置QT_QPA_FONTDIR 环境变量来覆盖该目录。

有关支持格式的更多信息,请参阅Qt for Embedded Linux Fonts

注意: 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是一种轻量级窗口系统;更确切地说,它是一种客户端与显示服务器对话的协议。

Qt Wayland 提供了一个wayland 平台插件,允许 Qt 应用程序连接到 Wayland 合成器。

更多详情,请参阅Wayland 和 Qt

性能提升指南

尽可能使用硬件渲染

当性能对应用程序至关重要时,应避免使用依赖软件渲染的 Qt 模块,如 Qt Charts.在可能的情况下,优先选择依赖硬件渲染的模块。

遵循Qt Quick

遵循QML 和Qt Quick最佳实践,尤其是包含QML CMake API最佳实践,以便qmllintQML 脚本编译器(qmlsc) 和QML 类型编译器(qmltc) 可用。此外,最好编写声明式 QML 并尽量减少 JavaScript。有关使用过多 JavaScript 会如何影响性能的更多信息,请参阅《QML 性能考虑因素和建议》。

使用图像/纹理和着色器效果来代替 Canvas QML 类型

要绘制自定义用户界面元素,请使用图像/纹理和着色器效果。不要使用 QMLCanvas 类型。着色器需要硬件加速(GPU)。

使用Qt Quick 而不是Qt Widgets

Qt Quick ,可以使用硬件加速或软件渲染后端。对于复杂的用户界面,不建议在嵌入式目标上使用Qt Widgets ,因为它将始终使用软件后端。

这需要权衡利弊:

  • 使用 QML 引擎和Qt Quick 会带来初始开销。
  • 如果您的用户界面非常简单,很少重新绘制,那么使用 Widgets 而不是 QML 可能会执行得更快。
  • 如果您的用户界面可以使用动画smooth scrolling,和scaling,rendering effects, 或3D 技术,您就需要使用 GPU 加速,因此Qt Quick

选择适合用户界面大小的分辨率

对于更高的分辨率,您需要谨慎对待。720p 或更高分辨率可能会降低性能。

使用 QML 窗口类型作为应用程序的根元素

使用Window 作为应用程序的根元素,应用程序的背景color

这样做的原因是 Window 组件有一个颜色属性,具有缓冲区清晰的效果。使用全屏Rectangle 作为应用程序的根Item 渲染背景会导致额外的绘制调用。对于某些 RHI 后端来说,这可能是一回事,但glClear 调用与绘制四边形是有区别的。在大多数情况下,单个不透明图像可能不会对性能产生很大影响,但如果在该项目的颜色中使用 alpha 值,则可能会对性能产生重大影响。

© 2025 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.