Using the ZED SDK with a Virtual Display on NVIDIA® Jetson™

Open in ClaudeOpen in ChatGPT

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. Works with NoMachine/VNC for remote
$# desktop access.
$#
$# 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 "nvidia" DDX driver
$#
$# HOW IT WORKS:
$# -------------
$# Uses the nvidia DDX Xorg driver with ConnectedMonitor + CustomEDID options
$# to fake a connected 1920x1080 display on the DP port. This provides:
$# - Full NVIDIA EGL support (nvbufsurface, Argus camera can create EGLImages)
$# - CUDA-EGL interop (cuGraphicsEGLRegisterImage works)
$# - Hardware-accelerated X11/GLX rendering via NVIDIA GPU
$# - Proper DRI2 for remote desktop protocols (NoMachine, VNC, etc.)
$#
$# JP6-SPECIFIC NOTES:
$# -------------------
$# nvidia-drm.modeset=1 is NOT compatible with nvidia DDX driver — it causes
$# "Failed to acquire modesetting permission". We remove it from boot params.
$# The nvidia DDX handles modesetting directly without nvidia-drm KMS.
$#
$# EGL/CUDA/Argus work fine WITHOUT nvidia-drm.modeset=1 when using nvidia DDX,
$# because applications access NVIDIA EGL/CUDA directly via the ICD, not through
$# the kernel DRM modesetting path.
$#
$# 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 # 1920x1080 screen
$# DISPLAY=:0 eglinfo 2>&1 | head -15 # EGL vendor: NVIDIA
$# python3 -c "import ctypes; c=ctypes.CDLL('libcuda.so.1'); print(c.cuInit(0))" # 0
$# # Connect via NoMachine — should show full GNOME desktop
$#
$# TO REVERT:
$# sudo cp /etc/X11/xorg.conf.bak.original /etc/X11/xorg.conf # or rm it
$# sudo rm -f /etc/X11/edid-1080p.bin
$# sudo rm -f /etc/udev/rules.d/99-jetson-drm-seat.rules
$# sudo systemctl enable seatd 2>/dev/null # if you need seatd back
$# # For JP6: add back nvidia-drm.modeset=1 to /boot/extlinux/extlinux.conf if needed
$# 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
$
$# =========================================================================
$# COMMON PATH (JP5 + JP6): nvidia DDX driver with ConnectedMonitor + CustomEDID
$# =========================================================================
$
$echo "Using nvidia DDX driver with connector ${DP_XORG}"
$STEP=0
$TOTAL_JP5=3
$TOTAL_JP6=5
$
$# ------------------------------------------------------------------
$# 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}] Backed up xorg.conf"
$else
$ echo "[${STEP}] Backup: skipped (already exists or no file)"
$fi
$
$# ------------------------------------------------------------------
$# Step 2: 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}] Installed EDID binary"
$
$# ------------------------------------------------------------------
$# Step 3: xorg.conf (nvidia DDX)
$# ------------------------------------------------------------------
$STEP=$((STEP+1))
$cat > /etc/X11/xorg.conf << XORG_EOF
$# Jetson JP${JP_VERSION} - 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}] Wrote xorg.conf (nvidia DDX)"
$
$# ------------------------------------------------------------------
$# JP6-SPECIFIC: Additional steps
$# ------------------------------------------------------------------
$if [[ "$JP_VERSION" -eq 6 ]]; then
$
$ # ------------------------------------------------------------------
$ # Step 4: Remove nvidia-drm.modeset=1 from 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
$ cp "$EXTLINUX_CONF" "${EXTLINUX_CONF}.bak.$(date +%Y%m%d%H%M%S)"
$ sed -i 's| nvidia-drm\.modeset=1||g' "$EXTLINUX_CONF"
$ echo "[${STEP}] Removed nvidia-drm.modeset=1 from extlinux.conf"
$ else
$ echo "[${STEP}] nvidia-drm.modeset=1 not present (OK)"
$ fi
$ else
$ echo "[${STEP}] WARNING: ${EXTLINUX_CONF} not found"
$ fi
$
$ # ------------------------------------------------------------------
$ # Step 5: Disable seatd if present (optional, helps avoid conflicts)
$ # ------------------------------------------------------------------
$ 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}] Disabled seatd (avoids DRM master conflicts)"
$ else
$ echo "[${STEP}] seatd not present or already disabled"
$ fi
$
$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 "JP6 NOTES:"
$ echo " - nvidia DDX driver active (NOT modesetting)"
$ echo " - nvidia-drm.modeset=1 removed from boot (incompatible with nvidia DDX)"
$ echo " - EGL/CUDA/Argus work via nvidia DDX's own rendering path"
$ echo " - NoMachine/VNC will show full hardware-accelerated desktop"
$ echo ""
$fi
$echo "Connect via NoMachine or VNC to access the desktop remotely."

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.

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.