嵌入式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平台抽象是一个更好的解决方案;对于多进程用例,通过Wayland支持。

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

EGLFS

EGL是OpenGL和原生窗口系统之间的接口。Qt可以使用EGL进行上下文和表面管理,然而该API不包含平台特定的内容。创建一个原生窗口,它不一定是屏幕上的实际窗口,仍然需要通过平台特定的方法完成。这就是为什么我们需要板或GPU特定的适配代码。通常,这些适配作为以下内容提供:

  • EGLFS钩子 – 编译入平台插件的单一源文件
  • EGL设备集成 – 动态加载的插件

EGLFS是一个基于EGL和OpenGL ES 2.0运行Qt应用程序的平台插件,而不需要像X11或Wayland这样的实际窗口系统。它是现代化嵌入式Linux设备的推荐插件,这些设备包含GPU。

除了Qt Quick和原生OpenGL应用程序外,EGLFS还支持软件渲染的窗口,如QWidget。对于QWidget,小部件的内容是用CPU生成的图像,然后上传到纹理中,并由插件进行合成。

EGLFS 将第一个顶级窗口(无论是 QWidget 还是 QQuickView)强制设置为全屏。此窗口也被选择为包含所有其他顶级窗口的根小部件窗口,例如,对话框、弹出菜单或组合框。这种做法是必要的,因为在 EGLFS 中,始终有 exactly one 个本地窗口和一个 EGL 窗口表面;它们属于首先创建的小部件或窗口。当存在一个在整个应用程序生命周期存在的常用窗口,且所有其他小部件要么不是顶级窗口,要么在主窗口显示后创建时,这种方法可以很好地工作。

对于基于 OpenGL 的窗口,还有进一步的限制。EGLFS 仅支持一个全屏 GL 窗口(自 Qt 5.3 以来),例如 OpenGL 基础的 QWindowQQuickViewQOpenGLWidget。不支持打开额外的 OpenGL 窗口或与 QWidget 基于的内容混合;Qt 会终止应用程序并显示错误消息。

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

EGLFS 使用的环境变量

如果需要,可以使用以下环境变量配置 eglfs

环境变量描述
QT_QPA_EGLFS_INTEGRATION除了编译时内置的 钩子,还可以使用动态加载的插件来提供设备或厂商特定的适配。此环境变量强制使用特定的插件。例如,将其设置为 eglfs_kms 则使用 KMS/DRM 后端。只有在没有在设备 makespecs 中指定静态或编译时钩子时,此选项才可用。实际上,传统的编译时钩子很少使用,现在几乎所有后端都迁移到了插件。设备 makespecs 中仍包含一个相关但不必要的 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 Quick。Qt Quick 应用程序可以在其 QML 场景中应用转换。标准的 eglfs 鼠标光标始终考虑该值,具有适当定位和旋转的光标图像,无论应用类型如何。但是,特定的鼠标光标实现,如 KMS/DRM 后台的硬件鼠标光标,可能不支持旋转。
QT_QPA_EGLFS_FORCEVSYNC当设置时,eglfs在每次调用eglSwapBuffers()后,会在帧缓冲设备上请求FBIO_WAITFORVSYNC。这个变量仅适用于依赖老旧Linux fbdev子系统的后端。通常情况下,使用默认的1次刷新间隔,Qt假设调用eglSwapBuffers()已经处理了垂直同步;如果没有(例如,由于驱动程序错误),尝试将QT_QPA_EGLFS_FORCEVSYNC设置为非零值。
QT_QPA_EGLFS_FORCE888当设置时,在eglfs创建新的上下文、窗口或离屏表面时,将忽略红色、绿色和蓝色颜色通道的大小。相反,插件请求配置为每个通道8位。这在某些设备上有用,这些设备默认选择小于每像素32或24位的配置(例如,5-6-5或4-4-4),尽管知道这不是最佳选择,例如由于带状效应。无需更改应用代码,此变量提供了强制24或32位/像素配置的快捷方式。

此外,还有一些较少使用的变量可用。

环境变量描述
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 Quick的QSG_INFO变量一起使用时,您可以获得有关EGL配置故障排除的有用信息。

日志记录

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

  • qt.qpa.egldeviceintegration – 启用动态加载的后端的日志记录。使用此类别检查正在使用的后端。
  • qt.qpa.input – 启用从evdevlibinput输入处理程序的调试输出。使用此类别检查是否识别并打开了特定的输入设备。
  • qt.qpa.eglfs.kms – 在KMS/DRM后端启用详细日志记录。

运行 configure 后,请检查其输出。这是最快最简单的方法来确定您是否启用了必要的 EGLFS 后端、libudev 或 libinput。简而言之,如果您的 configure 输出中出现了未预期的 "no",请运行

./configure -v

以打开详细输出,以便您可以看到每个 configure 测试的编译器和链接器调用。

注意:如果您遇到关于缺少头文件、库或看似神秘的链接器错误的错误,通常,这是不完整的或损坏的 sysroot 的标志,与 Qt 无关。

例如,当针对带有 Broadcom 专有图形驱动程序的树莓派时,输出应包含以下内容

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 是一个 实验性 平台插件,支持使用 Vulkan API 进行渲染。为了列出显示并设置渲染,它依赖于 VK_KHR_display 系列扩展。请注意,图形堆栈中的 Vulkan 实现不一定支持此功能。目前此平台插件已在树莓派 4 上运行 Mesa 和 V3DV 进行验证和测试。

此平台插件不支持 OpenGL 或任何软件渲染。因此,尝试基于 QWidget 的用户界面将失败。唯一支持的 QWindow 窗口类型是 QSurface::VulkanSurface。对于 Qt Quick 应用程序,这意味着基于 Vulkan 的渲染必须通过在创建 QQuickWindowQQuickView 之前设置环境中的 QSG_RHI_BACKEND=vulkan 或调用 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 类似,支持 evdevlibinputtslib。但是,尚未实现鼠标光标渲染。这是因为在此环境中没有硬件光标的概念,并且类似于 EGLFS 在 OpenGL 中的操作,在平台插件中使用 Vulkan 渲染光标存在多方面的问题。因此,此平台插件目前不太适合基于鼠标的输入。

相关的环境变量包括

  • QT_QPA_DISABLE_INPUT - 禁用键盘/鼠标/触摸输入。
  • QT_QPA_NO_LIBINPUT - 即使存在libinput,也优先使用基于evdev的输入处理器。
  • QT_QPA_TSLIB - 请求使用传统的tslib库。

LinuxFB

此插件通过Linux的fbdev子系统直接写入framebuffer。仅支持软件渲染的内容。请注意,在某些设置中,显示性能可能有限。要使用此平台插件与Qt Quick应用程序,必须使用software场景图后端,可以通过在环境中设置QT_QUICK_BACKEND=software或者在调用setGraphicsApi()时使用QSGRendererInterface::Software来实现。支持的QWidget应用程序或具有QSurface::RasterSurface表面类型的QWindow,但不包括如QOpenGLWidget之类的特殊控件。

由于fbdev在Linux内核中已被弃用,因此也支持DRM哑缓冲区。要使用它,将QT_QPA_FB_DRM环境变量设置为非零值。设置后,如果您的系统支持哑缓冲区,则不会访问类似/dev/fb0的传统framebuffer设备。相反,将通过DRM API设置渲染,类似于EGLFS中的eglfs_kms后端。输出为双缓冲和页面翻转,为软件渲染内容提供正确的vsync。

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

指定附加设置

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

设置描述
fb=/dev/fbN指定framebuffer设备。在多个显示设置中,此设置允许您在不同的显示上运行应用程序。目前,无法从单个Qt应用程序中使用多个framebuffer。
size=<width>x<height>指定像素级的屏幕尺寸。插件尝试从framebuffer设备查询显示尺寸(物理和逻辑)。然而,此查询可能不会始终得到正确的结果;可能需要显式指定这些值。
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与eglfs_kms后端

当使用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值是:offcurrentpreferredskip、widthxheight、widthxheight@vrefresh,或modeline字符串。

指定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连接器列表中保留原始顺序。

要创建垂直桌面空间(即,从上到下堆叠而不是从左到右),请使用值verticaldevice之后添加一个virtualDesktopLayout属性。

警告:建议虚拟桌面中的所有屏幕使用相同的分辨率,否则当进入仅在单个屏幕上存在的区域时,元素(如鼠标光标)可能会以意外的方式运行。

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实例

来自数组中的每个活动输出都对应于从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分类日志规则。

注意:在嵌入式环境中,与完整的窗口系统相比,虚拟桌面更为有限。多个屏幕重叠的窗口、非全屏窗口以及在不同屏幕之间移动窗口应避免,并且可能无法按预期运行。

一个常见用例

多屏幕设置中最常见且最佳支持的用例是为每个屏幕打开一个专用的 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 (例如示例中的 DP1)关联的任何 QWindow 都永远不能执行 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,它们是 传统原子。DRM 原子 API 的主要优势是在同一个渲染循环内允许多个 DRM 平面更新,而传统 API 需要每个 vsync 更新一个平面。

当您的应用程序需要将内容混合到叠加层并保持所有更新在同一个 vsync 中时,原子 API非常有用。但仍不是所有设备都支持此 API,并且它可能在某些较旧的设备上不可用。KMS 后端默认使用传统 API,但您可以启用 DRM 原子 API,将 QT_QPA_EGLFS_KMS_ATOMIC 环境变量设置为 1。

使用比屏幕分辨率更小的缓冲区也可能很有用。这可以通过在 JSON 文件中使用 size 参数来实现。下面的示例使用 1280x720 的缓冲区在 3840x2160 视频模式上

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

EGLFS 使用 eglfs_kms_egldevice 后端

此后端通常用于 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,通常对多显示器支持有限或没有支持。在基于 i.MX6 且配备 Vivante GPU 的板上,可以使用 QT_QPA_EGLFS_FB 环境变量指定要针对的帧缓冲区,类似于 linuxfb。在 Raspberry Pi 上,可以使用 QT_QPA_EGLFS_DISPMANX_ID 环境变量指定要输出的屏幕。该值对应于 DISPMANX_ID_ 常量之一,请参阅 Dispmanx 文档。请注意,这些方法与 KMS/DRM 不同,通常不会从同一应用程序输出到多个屏幕。或者,可能还有特定于驱动程序的或内核参数也可以用来控制使用的帧缓冲区。请参阅嵌入式板文档。

视频内存

具有固定数量的专用视频内存的系统在运行基于 Qt Quick 或类似 QOpenGLWidget 类的 Qt 应用程序之前可能需要特别注意。默认设置可能不足以支持这类应用程序,特别是在它们显示在高分辨率(例如,全高清)屏幕上时。在这种情况下,它们可能会以意外的方式开始失败。建议确保至少有 128 MB 的 GPU 内存可用。对于没有为 GPU 预留固定内存的系统,这不是问题。

linuxfb

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

Unix 信号处理器

以控制台为导向的平台插件,如 eglfs 和 linuxfb,默认安装信号处理器来捕获中断(SIGINT)、挂起和继续(SIGTSTPSIGCONT)和终止(SIGTERM)。这样可以在应用程序终止或由于 kill、或 Ctrl+CCtrl+Z 而挂起时恢复键盘、终端光标以及可能的图形状态。然而,在某些情况下捕获 SIGINT 可能是不希望的,因为它可能会与远程调试冲突。因此,提供了一个环境变量 QT_QPA_NO_SIGNAL_HANDLER,允许从所有内置信号处理中退出。

字体

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合成器。

有关更多详细信息,请参阅Wayland 和 Qt

性能增强指南

尽可能使用硬件渲染

当性能对您的应用程序至关重要时,请避免使用依赖软件渲染的Qt模块,例如Qt Charts。尽可能使用依赖硬件渲染的模块。

遵循Qt Quick的最佳实践

遵循QML和Qt Quick最佳实践,特别是包括QML CMake API,以便qmllintQML脚本编译器(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实现可能会更快。
  • 如果您的UI从动画平滑滚动缩放渲染效果3D中受益,则需要GPU加速,因此需要Qt Quick。

选择一个适合您UI大小的分辨率

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

将QML Window类型作为您的应用程序根元素

Window作为您的应用程序根元素,并为应用程序的背景设置颜色

这样做的原因是Window组件有一个颜色属性,其效果是缓冲区清除。使用全屏Rectangle作为应用程序的根Item会导致额外的绘制调用。对于某些RHI后端,这可能是一样的,但是glClear调用和绘制四边形之间还是有区别的。在大多数情况下,单个透明度较高的图像可能不会有太大的性能影响,但如果在那个项目的颜色中使用alpha值,您可能会看到一个明显的性能影响。

© 2024 The Qt Company Ltd. 本文档中的文档贡献归其各自的拥有者所有。本提供的文档根据自由软件基金会发布的GNU自由文档许可证版本1.3条款提供。Qt及其相关标志是The Qt Company Ltd在芬兰和/或全球其他国家的商标。所有其他商标均为其各自所有者的财产。