Skip to content
Back to the blog
· 5 min read

EGLFS: Running Qt on Embedded Hardware Without a Windowing System

How Qt draws straight to the graphics hardware with EGLFS – backends, KMS configuration, input devices, and the failure patterns that help at the first black screen.

Layer diagram of the graphics stack under EGLFS: Qt Quick application on top of the eglfs platform plugin, below it EGL and OpenGL ES with the vendor-specific backend, then KMS/DRM and the kernel graphics driver down to the display; alongside, the input path through evdev and libinput.

In short: An embedded device usually has no window manager, no desktop and no mouse – just one application that owns the screen outright. EGLFS is built for exactly that: the Qt platform plugin draws straight to the graphics hardware through EGL and OpenGL ES, with no X11 or Wayland in between. That saves memory, startup time and a whole layer of potential failure. The price is that you have to know the layer underneath the moment something stops working.

What EGLFS actually is

Qt talks to the operating system through a platform plugin (QPA). On the desktop that is X11, Wayland or Cocoa; on a device without a windowing system, EGLFS takes over. It acquires a drawing surface through EGL that covers the whole screen and renders Qt Quick onto it via OpenGL ES – hardware-accelerated, provided the GPU has a driver.

Selection happens through an environment variable:

export QT_QPA_PLATFORM=eglfs
./my-application

For systems with no GPU at all there is LinuxFB, which writes into the framebuffer directly and rasterizes in software. That is markedly slower and barely usable for animated interfaces, but it rescues projects on hardware without acceleration.

The backends – where it usually goes wrong

EGLFS is not one monolithic piece of code; it selects a device-specific integration. On modern SoCs that is typically the KMS/DRM variant working through the kernel’s standard interfaces. Alongside it sit vendor-specific integrations for graphics cores whose drivers take their own route.

Which integration gets loaded is decided by Qt at runtime based on what it finds on the system – and that is where the most common failure pattern comes from. If the chosen integration does not match the hardware, the application starts without an error and the screen stays black. So the first move should always be the diagnostic output:

export QT_LOGGING_RULES="qt.qpa.*=true"
./my-application

Qt then reports which integration was loaded, which outputs were found and which mode was set. In most cases that makes clear whether the problem lies with the GPU binding, the output, or permissions.

Configuring displays

Without further instruction EGLFS takes the first connected output at its preferred mode. For a product that is rarely enough – resolution, refresh rate and the question of which output gets driven at all belong nailed down. That happens through a JSON file:

{
  "device": "/dev/dri/card0",
  "outputs": [
    { "name": "HDMI1", "mode": "1920x1080", "primary": true },
    { "name": "LVDS1", "mode": "off" }
  ]
}
export QT_QPA_EGLFS_KMS_CONFIG=/etc/qt-kms.json

That fixes reproducibly what the device shows, independent of what happens to be plugged in. For devices with two displays you additionally mark the primary output, because Qt Quick drives only one surface by default.

Input: no mouse, but touch

Without a windowing system there is no central input management either. Qt reads input devices either directly through evdev or through libinput – the latter being the more robust choice, since it recognizes device types and pre-processes gestures.

Two points stand out in practice. First permissions: if the application does not run as root, the executing user needs access to the devices under /dev/input and to the DRM devices. Solving that cleanly through groups and udev rules is part of system configuration and tends to be forgotten until the first test under an unprivileged service. Second touch calibration: a resistive or poorly calibrated touch panel reports coordinates that do not match the image – recognizable by touches landing consistently offset.

A visible mouse cursor is rarely wanted on a touch device and can be switched off:

export QT_QPA_EGLFS_HIDECURSOR=1

What changed with Qt 6

Since Qt 6, Qt Quick no longer renders directly against OpenGL but through the RHI abstraction. For EGLFS setups that means assumptions which held under Qt 5 deserve rechecking after a migration – particularly where your own code issues OpenGL calls directly. The order in which to approach such a migration is covered in Migrating from Qt 5 to Qt 6.

Common failure patterns

SymptomLikely cause
Black screen, application runningWrong EGLFS integration or wrong output selected
Startup aborts with an EGL errorGPU driver or EGL library missing from the image
Image appears but no inputPermissions on /dev/input, or libinput missing from the image
Touches land offsetTouch not calibrated, or axes swapped
Works as root, not as a serviceMissing group permissions on DRM and input devices
Only one display drivenNo primary output set in the KMS configuration

When a compositor is the better answer

EGLFS serves exactly one fullscreen client. As soon as two processes need to draw simultaneously – a main application and a maintenance overlay, a video layer beside the interface – a Wayland compositor such as Weston is the right route. It costs memory and startup time but solves a problem EGLFS cannot solve by construction.

That decision is made early and is expensive to revise, because it reaches into the architecture of the application. We like to settle it at the start of a project, together with the remaining questions of the embedded Linux platform and Qt development.

Conclusion

EGLFS is the lean, obvious choice for a device with exactly one interface: fewer layers, less memory, faster startup. The price is that the first black screen requires looking one level down – at backend, output and permissions. With QPA logging switched on and a pinned KMS configuration, that becomes a matter of minutes rather than days. These topics are also part of our Qt and QML training; if an interface refuses to appear on your target hardware, we are happy to look at it with you.

Frequently asked questions

When is EGLFS the right choice and when Wayland?
EGLFS fits when exactly one application owns the screen – the classic case for a device with a single fixed interface. As soon as several applications need to draw at once, windows overlap, or a second process must show an overlay, a compositor such as Weston is unavoidable. EGLFS is leaner and starts faster, but by design it serves only one fullscreen client.
Why does the screen stay black even though the application is running?
Usually the backend is wrong: EGLFS picks a device-specific integration, and if it does not match the GPU, EGL initializes but nothing appears. The second most common cause is permissions on the DRM and input devices when the application does not run as root. Setting `QT_LOGGING_RULES="qt.qpa.*=true"` makes Qt report which integration and output it chose – that usually settles the question in a minute.
How do we control resolution and multiple displays?
Through a KMS configuration file pointed to by `QT_QPA_EGLFS_KMS_CONFIG`. It names outputs individually, enables or disables them and assigns a mode. For two displays you additionally mark which output carries the primary surface – without that file Qt takes the first connected output at its preferred mode.

Alexander Nassian

Managing Director, bitshift dynamics

Builds hardware-adjacent software for embedded products with his team – C++, Qt/QML, Embedded Linux and the Yocto Project. bitshift dynamics has worked in this field since 2005.

Related articles

· 7 min read

Qt in Embedded Environments

How Qt brings modern user interfaces to resource-constrained hardware – from QML and hardware acceleration to boot time and a lean stack.

  • Qt
  • QML
  • Embedded Linux
Read more

Facing a similar challenge?

We support embedded teams with exactly these questions – from the architecture decision through to production readiness. Tell us briefly what you're working on.

Start a project