Scope and prerequisites#
Commands and output are generic examples. Kernel, image, package, and service versions affect specific behavior; check the documentation for the versions you use as you read.
Before modifying containers, images, mounts, or firewalls, back up Android data and stop the relevant sessions; examples involving deletion or forced stops may lose data. Manage NixOS system and user-environment configuration through Nix. The general Linux sections using apt/pacman, writing directly to /etc, or installing with pip do not apply to a NixOS host. Rebuild build, test, and switch operations and post-startup functional verification are separate stages.
The key to Waydroid performance is its GPU detection chain: it determines whether rendering uses hardware acceleration (GBM gralloc + Mesa EGL) or CPU software rendering (SwiftShader). Understanding the full chain, from scanning DRI nodes to mapping the Vulkan HAL and writing configuration files, is essential to choosing correctly between the hardware and software paths.
GPU detection#
Waydroid’s GPU detection is implemented in tools/helpers/gpu.py, driven by the getDriNode() function. It does not depend on the running X11/Wayland environment; instead, it scans the Linux DRM subsystem directly to identify an available acceleration device before the container starts.
The DRM/DRI node model
The Linux kernel manages GPUs through the DRM (Direct Rendering Manager) subsystem. Each GPU exposes two types of character device:
| Device node | Purpose | Example |
|---|---|---|
/dev/dri/cardN |
modesetting — display-mode management (resolution, refresh rate, CRTC) | /dev/dri/card0 |
/dev/dri/renderDN |
rendering — computation/rendering only (still subject to file permissions/ACLs, but does not require DRM master) | /dev/dri/renderD128 |
The key distinction: cardN requires DRM master privileges, usually held by the compositor, while renderDN can be opened for rendering/computation only by processes with the appropriate file permissions or ACLs. As an LXC container, Waydroid needs only a render node to participate in OpenGL/Vulkan acceleration, but still requires driver support in the image and appropriate authorization. It generally does not handle the host’s display modesetting; the host compositor (Wayland) handles display output.
On multi-GPU systems, node numbers increase:
/dev/dri/
├── card0 ← iGPU modesetting
├── card1 ← dGPU modesetting
├── renderD128 ← iGPU 渲染 (对应 card0)
└── renderD129 ← dGPU 渲染 (对应 card1)The getDriNode() detection algorithm
flowchart TD
START(["getDriNode() 开始"]) --> CHECK_CFG{"/var/lib/waydroid/waydroid.cfg<br/>是否指定 drm_device?"}
CHECK_CFG -->|"是"| USE_CFG["验证指定节点存在且驱动受支持<br/>仍过滤 nvidia"]
CHECK_CFG -->|"否"| SCAN["扫描 /dev/dri/renderD*<br/>按编号排序"]
SCAN --> FOR_EACH["遍历每个 renderD* 节点"]
FOR_EACH --> READ_U["读取 /sys/class/drm/<br/>{dev}/device/uevent"]
READ_U --> CHECK_NV{"内核驱动<br/>是 nvidia?"}
CHECK_NV -->|"是 NVIDIA → 过滤"| FOR_EACH
CHECK_NV -->|"否 → 有效"| GET_CARD["getCardFromRender()<br/>查找对应 card* 节点"]
GET_CARD --> VALID["返回 (render_node, card_node)<br/>存入配置属性"]
FOR_EACH -->|"遍历耗尽<br/>未找到有效节点"| NO_GPU["返回两个空字符串<br/>→ 后续处理软件路径"]
style START fill:#0f3460,stroke:#16213e,color:#e0e0e0
style VALID fill:#1b4332,stroke:#2d6a4f,color:#e0e0e0
style NO_GPU fill:#5c1a1a,stroke:#8b0000,color:#e0e0e0
style CHECK_NV fill:#533483,stroke:#3b2665,color:#e0e0e0The algorithm runs in five steps:
Step 1: check for a user override
Waydroid lets you specify a GPU device manually in /var/lib/waydroid/waydroid.cfg, under [waydroid]:
[waydroid]
drm_device = /dev/dri/renderD129This is particularly useful on multi-GPU systems, such as laptops with an Intel iGPU + AMD dGPU. If automatic detection chooses the wrong GPU, specifying one manually bypasses the automatic scan order, but node-existence checks and driver filtering still apply.
Step 2: scan render nodes
Scan /dev/dri/renderD* in alphabetical order. Note that alphabetical order places renderD128 before renderD129, but does not guarantee that 128 corresponds to card0; verify the mapping through sysfs.
Step 3: read the kernel driver name
cat /sys/class/drm/renderD128/device/uevent
# 输出示例:
# DRIVER=amdgpu
# PCI_SLOT_NAME=0000:03:00.0The driver name is read through sysfs rather than lspci, because sysfs provides a direct mapping from device nodes to kernel drivers without parsing the PCI topology.
Step 4: filter out NVIDIA
UNSUPPORTED_GPU_DRIVERS = ["nvidia"]Modern NVIDIA drivers support GBM, but that does not guarantee compatibility between their userspace components and the Mesa/gralloc/EGL integration in Waydroid’s Android image. This upstream version still filters by the nvidia driver name. Nouveau appears in the mapping table; its performance and compatibility depend on the GPU generation, kernel, Mesa, and image, so it should not be declared universally unusable.
Step 5: obtain the corresponding card node
Look it up with getCardFromRender():
/sys/class/drm/{dev}/device/drm/card*Determine which card node is associated with the render node. Although Waydroid does not use the card node directly for rendering, it needs their mapping to generate the complete LXC device-binding configuration.
The fallback path when no GPU is available
If getDriNode() returns None because there are no DRI nodes or all have been filtered out, make_base_props() automatically switches to software rendering:
ro.hardware.gralloc=default
ro.hardware.egl=swiftshaderThis path is most common in VM environments, on NVIDIA platforms, and on servers without GPUs.
Vulkan driver mapping#
After a DRI node is detected, getVulkanDriver() maps the kernel driver name to an Android Vulkan HAL implementation. The mapping logic is called in make_base_props(), and the generated properties are injected into waydroid_base.prop in the Android container.
The complete mapping table
| Host kernel driver | Hardware vendor | Android Vulkan HAL | Notes |
|---|---|---|---|
i915 |
Intel | intel |
Intel Gen 9+ (Skylake and newer) |
i915 (gen < 9) |
Intel | intel_hasvk |
Intel Gen < 9 (Broadwell and older) uses the legacy Vulkan driver |
xe |
Intel | intel |
The next-generation Xe kernel driver (Lunar Lake, etc.) |
amdgpu |
AMD | radeon |
AMD Radeon RX/Pro series |
radeon |
AMD | Not listed in the mapping checked for this article | The original notes treated automatic support for the older driver as guaranteed; actual validation is required |
panfrost |
ARM Mali | panfrost |
Open-source driver for Mali Midgard/Bifrost GPUs |
msm / msm_dpu |
Qualcomm | freedreno |
Qualcomm Adreno GPU (Snapdragon) |
vc4 |
Broadcom | broadcom |
Broadcom VideoCore IV/V |
nouveau |
NVIDIA | nouveau |
Open-source NVIDIA driver (limited performance; generally not recommended) |
Determining Intel GPU generations Intel is the only GPU vendor requiring an additional generation check. Waydroid reads
/sys/kernel/debug/dri/0/i915_capabilitiesto obtain the GPU generation. When generation < 9, it usesintel_hasvkinstead ofintel. This is a separate Vulkan driver Mesa provides for older Intel GPUs and is incompatible with the newerintelVulkan driver (ANV).
The make_base_props() configuration logic
flowchart LR
DRI["getDriNode()<br/>返回结果"]
DRI -->|"有 DRI 节点"| HW["硬件加速路径"]
DRI -->|"None"| SW["软件渲染路径"]
HW --> VK["getVulkanDriver()<br/>内核驱动 → Vulkan HAL"]
HW --> GRALLOC["ro.hardware.gralloc=gbm<br/>GBM Gralloc"]
HW --> EGL["ro.hardware.egl=mesa<br/>Mesa EGL"]
VK --> PROP["写入 /var/lib/waydroid/<br/>waydroid_base.prop"]
GRALLOC --> PROP
EGL --> PROP
SW --> SW_GR["ro.hardware.gralloc=default<br/>默认 Gralloc"]
SW --> SW_EGL["ro.hardware.egl=swiftshader<br/>SwiftShader EGL"]
SW --> SW_VK["无 Vulkan HAL<br/>CPU 模拟 Vulkan"]
style HW fill:#1b4332,stroke:#2d6a4f,color:#e0e0e0
style SW fill:#5c1a1a,stroke:#8b0000,color:#e0e0e0make_base_props() also uses find_hidl() to check whether the android.hardware.graphics.allocator@4.0 HIDL service is available. If it is and a DRI node exists, hardware-accelerated configuration takes highest priority.
Mounting device nodes in the container
The LXC configuration generator, generate_nodes_lxc_config(), bind-mounts the following host device nodes into the Android container:
| Category | Device node | Purpose |
|---|---|---|
| Primary GPU | /dev/dri/renderD* |
OpenGL/Vulkan rendering |
| Qualcomm | /dev/kgsl-3d0 |
Qualcomm Adreno GPU |
| ARM Mali | /dev/mali0 |
ARM Mali GPU |
| PowerVR | /dev/pvr_sync |
PowerVR synchronization device |
| WSLg | /dev/dxg |
WSLg DirectX device |
| Framebuffer | /dev/fb*, /dev/graphics/fb* |
Framebuffer device |
| Camera/hardware encoding | /dev/video* |
V4L2 devices |
| DMA | /dev/dma_heap/* |
DMA-BUF heap allocator |
| Input | /dev/uhid, /dev/input/* |
Userspace HID and input devices |
| Sensors | /sys/class/leds/vibrator |
Vibration motor |
Only the render node selected by getDriNode() is mounted. Other render nodes on a multi-GPU system do not enter the container.
Switching between hardware acceleration and software rendering#
GBM + Mesa EGL (the hardware-accelerated path)
This is Waydroid’s preferred path. It is enabled automatically when a DRI node is available:
ro.hardware.gralloc = gbm
ro.hardware.egl = mesaGBM (Generic Buffer Management) is a userspace buffer-management API provided by Mesa and others that works with kernel DRM drivers. Waydroid’s GBM gralloc allocates Android graphics buffers through Mesa’s GBM backend. These buffers are allocated on the host GPU and can be used directly by the host compositor: zero-copy.
The process:
- An Android app requests a graphics buffer
- GBM gralloc allocates a BO (Buffer Object) on the host GPU through Mesa GBM
- Android SurfaceFlinger passes the BO as a dma-buf
- The Waydroid Session Manager converts the dma-buf to a Wayland wl_buffer
- The compositor renders directly, with no CPU copy
Performance checks: compare the actual renderer, extensions, versions, and application workload. dma-buf can reduce copying, but cross-GPU operation and format/modifier mismatches can still cause copies or failures. It does not guarantee native-equivalent performance or a fixed API version.
SwiftShader (the software-rendering fallback)
ro.hardware.gralloc = default
ro.hardware.egl = swiftshaderSwiftShader is Google’s high-performance CPU renderer; specific API support depends on the build inside the container and its Android integration. It is not traditional slow software rendering: it uses JIT compilation and SIMD optimizations, but its performance gap relative to hardware acceleration must be measured for the workload.
When to use software rendering:
- An optional fallback when the chosen NVIDIA path is incompatible with the image
- A virtual machine without GPU passthrough (such as QEMU without virtio-gpu 3D)
- A server without a GPU
- A temporary fallback when hardware acceleration causes graphical problems
Switching the configuration manually
Edit /var/lib/waydroid/waydroid.cfg and add the following to [properties]:
[properties]
ro.hardware.gralloc=default
ro.hardware.egl=swiftshaderApply the configuration and restart the container:
sudo waydroid upgrade --offline
sudo systemctl restart waydroid-container
waydroid upgradeoverwrites part of base.propwaydroid upgrade --offlinedoes not use the network; it only regenerateswaydroid_base.propand synchronizes the LXC configuration. If you manually editedwaydroid_base.prop, the upgrade will overwrite those changes. Put custom properties inwaydroid.cfg, under[properties], rather than editing base.prop directly.
Restore hardware acceleration:
# 从 waydroid.cfg 中移除 ro.hardware.gralloc 和 ro.hardware.egl
# 然后重新执行:
sudo waydroid upgrade --offline
# upgrade 会重新运行 getDriNode(),自动恢复 GBM + Mesa
sudo systemctl restart waydroid-containerVerify the current rendering path
# 在 Waydroid 内部检查当前使用的 EGL 实现
sudo waydroid shell dumpsys SurfaceFlinger | grep -i "GLES\|EGL\|render"
# 或者直接查看生成的属性
cat /var/lib/waydroid/waydroid_base.prop | grep -E "ro.hardware.gralloc|ro.hardware.egl"Output of gbm + mesa indicates hardware acceleration; default + swiftshader indicates software rendering.
NVIDIA and hybrid-GPU compatibility options#
The underlying problem with NVIDIA’s proprietary driver
Modern NVIDIA drivers support GBM. Waydroid’s limitation is integration across the entire Android graphics stack and driver; the explanation that “NVIDIA does not support GBM” is no longer valid. Waydroid’s hardware-acceleration stack is built entirely on GBM (GBM gralloc → Mesa EGL → dma-buf → Wayland), so getDriNode() has a hard-coded filter for NVIDIA’s proprietary driver.
This leaves NVIDIA users with three options:
| Option | Performance | Complexity | Use case |
|---|---|---|---|
| Pure software rendering (SwiftShader) | Low | Low | Lightweight apps (WeChat, reading) |
| Nouveau open-source driver | Medium | Medium | NVIDIA GTX 9/10 series + Mesa |
| NVK (Nouveau Vulkan) | Medium | High | Turing+ GPUs + the latest kernel |
NVIDIA and Wayland compositor alignment Using the same GPU often reduces cross-device dma-buf compatibility problems, but it is not a universal strict requirement. Android-rendered buffers are exported to the compositor as dma-bufs; cross-device sharing depends on drivers, formats, and modifiers, and may fail, require a copy, or work normally. This means that even if the compositor runs on an NVIDIA GPU, you can first test a supported integrated GPU or software rendering and assess the compositor’s actual behavior.
Software-rendering workflow
Force Waydroid to use SwiftShader and disable GBM:
1. Configure waydroid.cfg
[properties]
ro.hardware.gralloc=default
ro.hardware.egl=swiftshader2. Service configuration and persistence boundaries
# 未在所核对版本确认 WAYDROID_DISABLE_GBM 是受支持接口;使用下方 properties 方案Only the structural example of a systemd drop-in is retained below. There are no verified graphics environment variables, so there is no need to create an empty file solely for this:
sudo mkdir -p /etc/systemd/system/waydroid-container.service.d
sudo tee /etc/systemd/system/waydroid-container.service.d/nvidia-override.conf <<'EOF'
[Service]
# 不加入未核实环境变量;systemd drop-in 结构仅作示意
EOF
sudo systemctl daemon-reload3. Apply and restart
sudo waydroid upgrade --offline
sudo systemctl restart waydroid-containerNote for NixOS users User-session variables and system-service environments have different configuration scopes on NixOS; system services do not automatically inherit environment.sessionVariables. Manage graphics properties through the actual Waydroid configuration. In
configuration.nix:# NixOS 中配置实际服务环境需使用 systemd.services 对应 serviceConfig.Environment
Manage the graphics backend through [properties] in waydroid.cfg#
Hybrid graphics: Intel iGPU + NVIDIA dGPU
This is the most common laptop configuration. The strategy is to make Waydroid use only the Intel iGPU:
Option A: waydroid-choose-gpu.sh
The community project Quackdoc/waydroid-scripts provides waydroid-choose-gpu.sh for interactively selecting the GPU Waydroid uses:
git clone https://github.com/Quackdoc/waydroid-scripts.git
cd waydroid-scripts
chmod +x waydroid-choose-gpu.sh
sudo ./waydroid-choose-gpu.shThe script:
- Lists all GPUs on the system by scanning with
lspci+/dev/dri - Prompts the user to select the target GPU
- Modifies the LXC container’s
config_nodesto bind only the selected render node - Restarts the container to apply the configuration
Option B: specify drm_device manually
Specify the Intel GPU’s render node directly in /var/lib/waydroid/waydroid.cfg:
[waydroid]
drm_device = /dev/dri/renderD128First confirm which render node corresponds to Intel:
for d in /dev/dri/renderD*; do
dev=$(basename "$d")
driver=$(cat "/sys/class/drm/$dev/device/uevent" 2>/dev/null | grep DRIVER | cut -d= -f2)
echo "$d → $driver"
doneAMD + AMD hybrid graphics
AMD APU + AMD dGPU systems, such as Ryzen + RX 6800M, are in a much better position than NVIDIA systems because both GPUs support GBM. However, there are known issues:
- GitHub Issue #1454: on Ryzen 680M (APU) + RX 6800M (dGPU) systems, automatic detection may select the wrong GPU, causing Waydroid to enter a startup loop
- Both GPUs also share the
amdgpukernel driver, andgetDriNode()’s "return the first valid node" strategy may bind the container to a different GPU from the compositor
Solution: use drm_device to force selection of the same GPU as the compositor. AMD’s open-source Mesa drivers provide full GBM support for both GPUs; once the correct GPU is selected, functionality matches Intel’s.
Intel Arc discrete GPUs
Intel Arc (Alchemist/Battlemage) uses the i915 or xe kernel driver and is theoretically supported directly. However, the community currently reports unstable behavior: some Arc users experience graphical flickering or Vulkan initialization failures in Waydroid. The latest Mesa (24.1+) and a Linux 6.9+ kernel are recommended for the best support.
Debugging tools
# 检查 Waydroid 实际使用的 GPU
sudo waydroid shell ls -la /dev/dri/ # input/devices 不能证明实际渲染 GPU
# 检查容器内的 DRM 设备
sudo waydroid shell ls -la /dev/dri/
# 查看 EGL/Vulkan 运行时状态
waydroid logcat | grep -E "gralloc|egl|vulkan|GPU"Resources#
- Waydroid Hardware Integration — a third-party interpretation of the source code
- Waydroid Graphics & Display Core Concepts — core concepts of the graphics architecture
- Quackdoc/waydroid-scripts — GPU-selection scripts and helper tools
- GitHub Issue #1454 — startup loops with AMD hybrid GPUs
- NVIDIA Developer Forum — a request for NVIDIA hardware acceleration
Verification sources#
Waydroid official installation instructions.
Waydroid official command documentation.
GPU selection and Vulkan mapping.
Waydroid software-rendering FAQ.
References & further reading
- Waydroid Official Installation Instructions ↗
- Waydroid Official Command Documentation ↗
- Waydroid Upstream Source ↗
- GPU Selection and Vulkan Mapping ↗
- Waydroid Software-rendering FAQ ↗
- NVIDIA Official GBM Documentation ↗
Adapted from technical notes. Examples use generic names.
Continue along a reading path
Waydroid
- 01Waydroid architecture: LXC, Binder, and session bridging
- 02Waydroid installation and basic setup
- 03Waydroid integration on NixOS
- 04Waydroid GPU detection and hardware acceleration
- 05Waydroid bridges, NAT, and security boundaries
- 06Waydroid properties, multi-window mode, and ARM translation
- 07Waydroid troubleshooting and FAQ
- 08Waydroid tips: windows, translation, and shared directories