Using the ZED SDK with a Virtual Display on NVIDIA® Jetson™
When working with a ZED camera and a ZED SDK-based application running on a remote NVIDIA® Jetson™ device, you may need to access the display output without physically connecting a monitor (headless mode). While a dummy HDMI dongle is a common solution, you can also configure a virtual display entirely through software.
This guide shows how to set up a virtual display on your NVIDIA® Jetson™ device, allowing you to use the ZED camera with remote desktop applications like VNC, NoMachine, or X11 forwarding without requiring a physical dongle.
Prerequisites #
Before starting, ensure you have:
- Set up your remote NVIDIA® Jetson™ device to run ZED SDK applications.
- Access to the device physically or remotely via SSH.
- [Optional] Installed and configured a remote desktop application if you plan to use one.
Virtual Display Setup #
Create the Setup Script #
First, create a script file to configure the virtual display.
You can download a pre-made script from this link or manually create a file named setup-virtual-display.sh with the following content:
Expand to view the content of setup-virtual-display.sh
#!/bin/bash
# =============================================================================
# Jetson Headless Virtual Display Setup (GPU-Accelerated)
# =============================================================================
#
# Creates a virtual 1920x1080@60Hz display on Jetson AGX Orin without a
# physical monitor. EGL, CUDA, and NVIDIA Argus all work. A real monitor
# can still be hot-plugged at any time.
#
# Platform: Jetson AGX Orin
# - JetPack 5.x (L4T R35.x, Ubuntu 20.04) — Xorg "nvidia" DDX driver
# - JetPack 6.x (L4T R36.x, Ubuntu 22.04) — Xorg "modesetting" driver
#
# JP6-SPECIFIC ISSUES (solved by this script):
# -----------------------------------------------
# 1. nvidia-drm.modeset=1 is required for EGL-CUDA interop (e.g. ZED SDK's
# cuGraphicsEGLRegisterImage). But it means the nvidia-drm kernel module
# OWNS modesetting, so the Xorg "nvidia" DDX driver fails with:
# (EE) NVIDIA(GPU-0): Failed to acquire modesetting permission.
# Fix: use the Xorg "modesetting" driver instead of the "nvidia" DDX.
# Application-level EGL/CUDA/Argus still use the NVIDIA GPU directly —
# they go through the NVIDIA EGL ICD / libcuda, not through Xorg.
#
# 2. Jetson has two DRM cards: card0 (nvidia-drm, 13800000.display, has DP)
# and card1 (tegra_drm, host1x, no connectors). Xorg auto-probes both;
# card1's ScreenInit fails fatally:
# (EE) AddScreen/ScreenInit failed for gpu driver 0 -1
# Fix: AutoAddGPU=false in ServerFlags blocks card1 auto-probe.
#
# 3. GDM launches Xorg through systemd-logind, which calls drmSetMaster
# via TakeDevice. On this nvidia-drm + Tegra combo, logind's
# drmSetMaster fails: "Device or resource busy". Running X as root
# directly bypasses this.
# Fix: a dedicated systemd service (xorg-virtual.service) starts Xorg
# as root before GDM, then GDM attaches to the existing display.
#
# 4. seatd.service (if installed) competes with systemd-logind for DRM
# master, causing another "Device or resource busy" failure.
# Fix: disable seatd.
#
# GPU ACCELERATION:
# -----------------
# The modesetting Xorg driver uses software rendering for X11/GLX (llvmpipe).
# This does NOT affect GPU-accelerated workloads:
# - EGL: works via NVIDIA EGL ICD (libEGL_nvidia.so) — verified
# - CUDA: works (cuInit returns 0) — verified
# - Argus: works (GPU initialized by nvidia-drm.modeset=1 at boot)
# X11 desktop compositing will be SW-rendered, which is fine for headless.
#
# REAL MONITOR:
# -------------
# If you plug in a real DP monitor, it appears automatically in xrandr
# and is usable alongside (or instead of) the virtual framebuffer.
#
# USAGE:
# chmod +x setup-virtual-display-fixed.sh
# sudo ./setup-virtual-display-fixed.sh
# sudo reboot
#
# VERIFY:
# DISPLAY=:0 xrandr
# DISPLAY=:0 eglinfo | head -5 # Should show NVIDIA
# python3 -c "import ctypes; cuda = ctypes.CDLL('libcuda.so.1'); \
# print('cuInit:', cuda.cuInit(0))" # Should print 0
#
# TO REVERT:
# sudo systemctl disable --now xorg-virtual.service 2>/dev/null
# sudo rm -f /etc/systemd/system/xorg-virtual.service
# sudo rm -f /etc/udev/rules.d/99-jetson-drm-seat.rules
# sudo cp /etc/X11/xorg.conf.bak.original /etc/X11/xorg.conf # or rm it
# sudo rm -f /etc/X11/edid-1080p.bin
# sudo systemctl enable seatd 2>/dev/null # if you need seatd back
# sudo systemctl daemon-reload
# sudo reboot
#
# =============================================================================
set -euo pipefail
if [[ $EUID -ne 0 ]]; then
echo "ERROR: Must run as root (sudo)."
exit 1
fi
# --- Detect JetPack / L4T version ---
detect_jetpack_version() {
local l4t_major=""
if [[ -f /etc/nv_tegra_release ]]; then
l4t_major=$(sed -n 's/^# R\([0-9]*\).*/\1/p' /etc/nv_tegra_release)
fi
if [[ -z "$l4t_major" ]] && command -v dpkg-query &>/dev/null; then
l4t_major=$(dpkg-query -W -f='${Version}' nvidia-l4t-core 2>/dev/null \
| sed -n 's/^\([0-9]*\)\..*/\1/p') || true
fi
if [[ -n "$l4t_major" && "$l4t_major" -ge 36 ]]; then
echo 6
elif [[ -n "$l4t_major" && "$l4t_major" -ge 35 ]]; then
echo 5
else
echo 0
fi
}
# --- Detect first available DP connector ---
detect_dp_connector_drm() {
# DRM naming (1-indexed): DP-1, DP-2, etc.
for card_dp in /sys/class/drm/card*-DP-*; do
if [[ -d "$card_dp" ]]; then
basename "$card_dp" | sed 's/card[0-9]*-//'
return
fi
done
echo "DP-1"
}
detect_dp_connector_xorg() {
# nvidia DDX uses 0-indexed: DP-0, DP-1, etc.
local drm_idx
drm_idx=$(detect_dp_connector_drm | sed 's/.*DP-//')
echo "DP-$((drm_idx - 1))"
}
JP_VERSION=$(detect_jetpack_version)
DP_DRM=$(detect_dp_connector_drm)
DP_XORG=$(detect_dp_connector_xorg)
echo "=== Jetson Virtual Display Setup ==="
if [[ "$JP_VERSION" -eq 6 ]]; then
echo "Detected: JetPack 6 (L4T R36.x)"
elif [[ "$JP_VERSION" -eq 5 ]]; then
echo "Detected: JetPack 5 (L4T R35.x)"
else
echo "WARNING: Could not detect JetPack version, defaulting to JP5 path"
JP_VERSION=5
fi
# =========================================================================
# JP5 PATH: nvidia DDX driver with ConnectedMonitor + CustomEDID
# =========================================================================
if [[ "$JP_VERSION" -eq 5 ]]; then
echo "Using nvidia DDX driver with connector ${DP_XORG}"
STEP=0; TOTAL=3
# --- Backup ---
STEP=$((STEP+1))
if [[ -f /etc/X11/xorg.conf && ! -f /etc/X11/xorg.conf.bak.original ]]; then
cp /etc/X11/xorg.conf /etc/X11/xorg.conf.bak.original
echo "[${STEP}/${TOTAL}] Backed up xorg.conf"
else
echo "[${STEP}/${TOTAL}] Backup: skipped (already exists or no file)"
fi
# --- EDID binary ---
STEP=$((STEP+1))
base64 -d > /etc/X11/edid-1080p.bin << 'EDID_EOF'
AP///////wBZ5QEAAQAAAAEiAQOAAAB4Cu6Ro1RMmSYPUFQhCAABAQEBAQEBAQEBAQEBAQEBAjqA
GHE4LUBYLEUADyghAAAeAAAA/ABWaXJ0dWFsIDEwODBwAAAA/QAySx5REQAKICAgICAgAAAA/wAw
MDAwMDAwMDAwMDAxAJc=
EDID_EOF
chmod 644 /etc/X11/edid-1080p.bin
echo "[${STEP}/${TOTAL}] Installed EDID binary"
# --- xorg.conf ---
STEP=$((STEP+1))
cat > /etc/X11/xorg.conf << XORG_EOF
# Jetson JP5 - nvidia DDX driver, virtual display
Section "DRI"
Mode 0666
EndSection
Section "Module"
Disable "dri"
SubSection "extmod"
Option "omit xfree86-dga"
EndSubSection
EndSection
Section "Device"
Identifier "Tegra0"
Driver "nvidia"
Option "AllowEmptyInitialConfiguration" "true"
Option "ConnectedMonitor" "${DP_XORG}"
Option "CustomEDID" "${DP_XORG}:/etc/X11/edid-1080p.bin"
Option "HardDPMS" "false"
EndSection
Section "Monitor"
Identifier "Virtual-1080p"
HorizSync 30-81
VertRefresh 50-75
ModeLine "1920x1080_60" 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +HSync +VSync
Option "PreferredMode" "1920x1080_60"
EndSection
Section "Screen"
Identifier "Default Screen"
Device "Tegra0"
Monitor "Virtual-1080p"
DefaultDepth 24
SubSection "Display"
Depth 24
Modes "1920x1080_60"
EndSubSection
EndSection
XORG_EOF
echo "[${STEP}/${TOTAL}] Wrote xorg.conf (nvidia DDX)"
# =========================================================================
# JP6 PATH: modesetting driver + root Xorg service
# =========================================================================
else
echo "Using modesetting driver on card0 with root Xorg service"
STEP=0; TOTAL=6
# ------------------------------------------------------------------
# Step 1: Backup xorg.conf
# ------------------------------------------------------------------
STEP=$((STEP+1))
if [[ -f /etc/X11/xorg.conf && ! -f /etc/X11/xorg.conf.bak.original ]]; then
cp /etc/X11/xorg.conf /etc/X11/xorg.conf.bak.original
echo "[${STEP}/${TOTAL}] Backed up xorg.conf"
else
echo "[${STEP}/${TOTAL}] Backup: skipped"
fi
# ------------------------------------------------------------------
# Step 2: Write xorg.conf (modesetting driver, card0 only)
#
# Key points:
# • modesetting driver talks to nvidia-drm KMS — no DDX conflict
# • AutoAddGPU=false blocks card1 (tegra_drm, no connectors)
# • Virtual 1920 1080 provides framebuffer when headless
# • Real monitors hot-plug via DP and appear in xrandr
# ------------------------------------------------------------------
STEP=$((STEP+1))
cat > /etc/X11/xorg.conf << 'XORG_EOF'
# Jetson AGX Orin JP6 — modesetting (KMS) driver
#
# nvidia-drm.modeset=1 required for EGL-CUDA interop.
# nvidia DDX cannot coexist → modesetting driver instead.
# AutoAddGPU=false prevents card1 (tegra_drm, no connectors) from
# causing "AddScreen/ScreenInit failed for gpu driver".
# Real monitors hot-plug normally via DP.
Section "ServerFlags"
Option "AutoAddGPU" "false"
EndSection
Section "Device"
Identifier "Tegra0"
Driver "modesetting"
EndSection
Section "Monitor"
Identifier "Virtual-1080p"
ModeLine "1920x1080_60" 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +HSync +VSync
Option "PreferredMode" "1920x1080_60"
EndSection
Section "Screen"
Identifier "Default Screen"
Device "Tegra0"
Monitor "Virtual-1080p"
DefaultDepth 24
SubSection "Display"
Depth 24
Virtual 1920 1080
Modes "1920x1080_60"
EndSubSection
EndSection
Section "ServerLayout"
Identifier "Layout"
Screen "Default Screen"
EndSection
XORG_EOF
echo "[${STEP}/${TOTAL}] Wrote /etc/X11/xorg.conf"
# ------------------------------------------------------------------
# Step 3: Disable seatd (conflicts with logind for DRM master)
# ------------------------------------------------------------------
STEP=$((STEP+1))
if systemctl is-enabled seatd &>/dev/null 2>&1; then
systemctl stop seatd 2>/dev/null || true
systemctl disable seatd 2>/dev/null || true
echo "[${STEP}/${TOTAL}] Disabled seatd (conflicts with logind)"
else
echo "[${STEP}/${TOTAL}] seatd not present or already disabled"
fi
# ------------------------------------------------------------------
# Step 4: udev rule — remove card1 (tegra_drm) from seat0
# ------------------------------------------------------------------
STEP=$((STEP+1))
cat > /etc/udev/rules.d/99-jetson-drm-seat.rules << 'UDEV_EOF'
# Remove tegra_drm (card1/host1x) from seat0.
# It has no display connectors and confuses logind DRM master negotiation.
ACTION=="add", SUBSYSTEM=="drm", KERNEL=="card1", TAG-="seat", ENV{ID_SEAT}=""
UDEV_EOF
udevadm control --reload-rules 2>/dev/null || true
echo "[${STEP}/${TOTAL}] Installed udev rule to remove card1 from seat0"
# ------------------------------------------------------------------
# Step 5: systemd service — start Xorg as root
#
# GDM uses logind to open DRM devices, but logind's drmSetMaster
# fails on JP6's nvidia-drm ("Device or resource busy").
# Running Xorg as root directly bypasses logind for DRM and works.
#
# The service starts before GDM. GDM detects :0 is already running
# and attaches to it for session management / login.
# ------------------------------------------------------------------
STEP=$((STEP+1))
cat > /etc/systemd/system/xorg-virtual.service << 'SVCEOF'
[Unit]
Description=Xorg Virtual Display (root, modesetting)
After=systemd-logind.service nvidia-persistenced.service
Before=display-manager.service
Wants=systemd-logind.service
[Service]
Type=simple
ExecStartPre=/bin/sleep 2
ExecStart=/usr/lib/xorg/Xorg :0 -config /etc/X11/xorg.conf -noreset vt7 -keeptty -verbose 3
Restart=on-failure
RestartSec=5
# Run as root — required to bypass logind drmSetMaster bug on JP6
User=root
[Install]
WantedBy=graphical.target
SVCEOF
systemctl daemon-reload
systemctl enable xorg-virtual.service
echo "[${STEP}/${TOTAL}] Created and enabled xorg-virtual.service"
# ------------------------------------------------------------------
# Step 6: Ensure nvidia-drm.modeset=1 in boot params
# ------------------------------------------------------------------
STEP=$((STEP+1))
EXTLINUX_CONF="/boot/extlinux/extlinux.conf"
if [[ -f "$EXTLINUX_CONF" ]]; then
if grep -q "nvidia-drm.modeset=1" "$EXTLINUX_CONF"; then
echo "[${STEP}/${TOTAL}] nvidia-drm.modeset=1 already in extlinux.conf"
else
cp "$EXTLINUX_CONF" "${EXTLINUX_CONF}.bak.$(date +%Y%m%d%H%M%S)"
sed -i '/^\s*APPEND /s/$/ nvidia-drm.modeset=1/' "$EXTLINUX_CONF"
echo "[${STEP}/${TOTAL}] Added nvidia-drm.modeset=1 to extlinux.conf"
fi
else
echo "[${STEP}/${TOTAL}] WARNING: ${EXTLINUX_CONF} not found"
echo " Manually add 'nvidia-drm.modeset=1' to kernel boot params"
fi
fi
# --- Common: ensure nvidia-drm loads at boot ---
MODULES_CONF="/etc/modules-load.d/nvidia-drm.conf"
if [[ -f "$MODULES_CONF" ]] && grep -q "nvidia-drm" "$MODULES_CONF"; then
echo "[+] nvidia-drm already in ${MODULES_CONF}"
else
echo "nvidia-drm" >> "$MODULES_CONF"
echo "[+] Added nvidia-drm to ${MODULES_CONF}"
fi
echo ""
echo "=== Done! ==="
echo ""
echo "A reboot is REQUIRED:"
echo " sudo reboot"
echo ""
echo "After reboot, verify:"
echo " DISPLAY=:0 xrandr # 1920x1080 screen"
echo " DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA"
echo " python3 -c \"import ctypes; c=ctypes.CDLL('libcuda.so.1'); print(c.cuInit(0))\" # 0"
echo ""
if [[ "$JP_VERSION" -eq 6 ]]; then
echo "NOTE: X runs via xorg-virtual.service (as root, before GDM)."
echo " GDM still provides login/session management."
echo " Real monitors hot-plug normally via DP."
echo ""
echo " Status: systemctl status xorg-virtual"
echo " Logs: journalctl -u xorg-virtual"
fi
Make the script executable:
chmod +x setup-virtual-display.sh
Apply the Virtual Display Configuration #
Run the setup script and restart the display manager to apply the changes:
sudo ./setup-virtual-display.sh && sudo systemctl restart gdm
This script configures your system’s X server to create a virtual display. After the display manager restarts, you can connect via remote desktop applications and use the ZED camera as if a physical monitor were connected.
⚠️ Warning: This configuration will disable physical display output on HDMI and DisplayPort connectors. If you need to use a physical monitor directly, revert this configuration first (see below).
Reverting to Physical Display #
To restore physical display functionality (for HDMI or DisplayPort monitors), run:
sudo cp /etc/X11/xorg.conf.bak.original /etc/X11/xorg.conf && sudo systemctl restart gdm
This restores the original X server configuration and restarts the display manager.
How It Works #
The virtual display setup configures the X server to use a virtual framebuffer instead of requiring a physical display connection. This allows the system to:
- Create a display session without a physical monitor
- Enable GPU acceleration for graphics applications
- Allow remote desktop connections to access the display
- Run GUI applications that require a display environment
This is useful for headless robotic systems where you need to run ZED SDK applications with GUI components or perform development and debugging remotely.
Troubleshooting #
If you encounter issues after setting up the virtual display:
- Remote desktop not connecting: Ensure your remote desktop server (VNC, NoMachine, etc.) is properly configured and running.
- Need to switch back to physical display: Use the revert command shown above.
- Display resolution issues: You may need to modify the script to adjust the virtual display resolution. The current script sets it to 1920x1080@60Hz, which is common for ZED SDK applications.