3D visionTroubleshooting

libEGL renderD128 permission denied: why depth alignment hits the CPU

Why a libEGL renderD128 permission denied warning pushes depth alignment onto the CPU, the render-group fix, and the checks that prove the GPU took over.

LumaSpec Vision10 min read Share on LinkedIn

On this page 8 sections
  1. 01Why does subscribing to registered depth push one CPU core to 100%?
  2. 02What does 'failed to open /dev/dri/renderD128: Permission denied' mean?
  3. 03Why does glxinfo report an accelerated GPU while the camera node cannot use it?
  4. 04How do you give the ROS user access to the render node?
  5. 05How do you verify the alignment actually moved to the GPU?
  6. 06What does software alignment cost if you keep it on the CPU?
  7. 07How do you keep the fallback from coming back?
  8. 08Questions

Your depth camera streams fine in RViz under WSL2 — until you subscribe to the registered depth topic. One core jumps to 100%, the log fills with libEGL warnings about /dev/dri/renderD128, and the driver announces it has switched to software alignment. Nothing is broken in the camera: the driver tried to open the GPU's render device node, your Linux user failed the permission check, and a software fallback took over. The camera model does not matter; the failing piece is Linux device access. This page walks that diagnosis for a ROS 1 depth-camera driver on WSL2 with systemd enabled — what the render node is, why an accelerated glxinfo proves nothing about it, the three-command group fix, how to verify the alignment truly runs on the GPU, and the arithmetic for what software alignment costs if you decide to keep it.

Why does subscribing to registered depth push one CPU core to 100%?

Because the driver lost access to the GPU render node and fell back to software alignment: a 1280×720 depth stream at 30 fps costs 27,648,000 pixel remaps per second, which pins one desktop core at 100% — the load you are seeing arrives the moment a subscriber touches the rectified topic.

Depth-to-color alignment reprojects every depth pixel into the color camera's frame through a precomputed per-pixel mapping. On the GPU the CPU only submits work; in software it touches every pixel of every frame. The log tells you which path was chosen: this setup prints ====Enable frame sync====, then set align mode to SW, and the libEGL warnings that follow are the failed EGL context creations behind that switch.

The cost is not a standing charge on the camera. Raw depth and color stream at full rate whether or not anyone subscribes; the remap runs only when the registered topic has a consumer. If you are untangling which topic is which, start from the difference between raw and rectified depth frames, then prove alignment on live data with the depth aligned to RGB registration check.

What does 'failed to open /dev/dri/renderD128: Permission denied' mean?

It means the open() call on the GPU's render device node failed the file-permission check: on a systemd distro the node is owned by root with group render and mode 0660, so a user outside the render group cannot open it, and the SDK's GPU path falls back to software.

/dev/dri/renderD128 is a DRM render node — the device file userspace opens for GPU work that needs no display: offscreen EGL contexts, shader jobs, exactly what an alignment routine uses. Standard udev rules give render nodes exactly those ownership and mode bits, which is why the fix is a group membership and not a package.

The warning sequence tells the story end to end: the open fails with EACCES, Mesa cannot load a hardware-backed EGL driver, and the next line — NEEDS EXTENSION: falling back to kms_swrast — announces a CPU rasterizer. The node keeps running; every aligned frame just became a CPU job.

Why does glxinfo report an accelerated GPU while the camera node cannot use it?

glxinfo exercises the display path — your desktop GL context served through WSLg's compositor — while the camera SDK opens the render node directly for offscreen work, and the two paths pass different checks: one succeeds from your desktop session, the other needs render-group membership on the device file itself.

The glxinfo output in a setup like this ends with a D3D12 renderer and Accelerated: yes. Both true, and neither helps the driver: that result certifies the paravirtualized display stack, not the permission bits on /dev/dri/renderD128 for the account running roslaunch. The SDK asks EGL for an offscreen context, EGL opens the render node as a plain device file, and the permission check rejects the user.

The lesson generalizes: test with the tool that exercises the failing path. glxinfo tests GLX on a display; eglinfo tests EGL on the render node. After any GPU, driver or WSL change, run the EGL check as the same user that starts the camera node.

How do you give the ROS user access to the render node?

Add the account that launches the node to the render group and restart the WSL session: sudo usermod -aG render $USER, then wsl --shutdown in PowerShell, then id -nG must list render before the node starts again — three commands and one restart.

  1. Check the node and its group first: ls -l /dev/dri/renderD128. On a systemd distro expect crw-rw---- root render. If a local udev rule has placed it in another group, join that group in the next step instead.
  2. Add the right account: sudo usermod -aG render $USER for your interactive user, or sudo usermod -aG render <service-account> for the account roslaunch runs under.
  3. Restart WSL so the membership applies: close every terminal of the distro and run wsl --shutdown in PowerShell, then reopen. For a quick test without a restart, newgrp render starts a shell that already carries the group — restart anyway before you trust a result.
  4. Confirm as that user: id -nG lists render, and test -w /dev/dri/renderD128 && echo writable succeeds.
  5. Start the camera node and read its log before subscribing: the libEGL warnings should be gone and the align-mode line should no longer report SW.

How do you verify the alignment actually moved to the GPU?

Three checks in order: id -nG must list render; eglinfo must show a hardware EGL renderer instead of llvmpipe or kms_swrast; and the node log must go quiet — no libEGL warnings and no 'align mode to SW' line, after which the pinned core frees within seconds of subscribing.

glinfo prints one renderer string per client API; read the EGL rows in the same shell you launch the node from, so the session's groups and environment match. Success looks like the same D3D12 device glxinfo showed. llvmpipe, softpipe or kms_swrast in an EGL row means the context is still on the CPU.

In top, the core that sat at 100% drops to background within seconds of the first subscribed frame. The marker to watch in the log is SW; the exact wording of the success line differs between driver versions, so treat the disappearance of the warnings as the primary signal.

If alignment now runs but the rectified image sits a pixel or two off the color image, that is a separate fault with its own fix: work through the depth-to-RGB registration offset guide instead of touching permissions again.

What does software alignment cost if you keep it on the CPU?

The remap load is width × height × frame rate, so you can budget it exactly: 1280×720 at 30 fps is 27,648,000 remaps per second and about 55 MB per second of rectified output, and every halving of resolution or frame rate halves the number.

remaps_per_second = width × height × fps output_MB_per_second = width × height × fps × 2 / 1,000,000 frame_budget_ms = 1000 / fps

Budget other streams from your own numbers:

Depth stream Remaps per second Relative load
1280×720 @ 30 fps 27,648,000 1.00×
1280×720 @ 15 fps 13,824,000 0.50×
848×480 @ 30 fps 12,211,200 0.44×
640×480 @ 30 fps 9,216,000 0.33×
1920×1080 @ 30 fps 62,208,000 2.25×

The resolutions and rates in the table are illustrative inputs; the arithmetic is what transfers to your stream.

Software alignment is a sound choice while the arithmetic stays small — an occasional capture or a single-digit frame rate costs a few percent of a core — and a poor one at continuous 30 fps next to a point cloud publisher and RViz. Watch the neighbors too: a pointcloud filter can cost as much as the remap itself (cut the filter's CPU load), a saturated core eventually stalls publishing outright (diagnose the pipeline load), and a CPU-bound driver drags the camera's own frame rate down (frame rate low on software trigger).

The decision: fix the permission first, because the GPU path is performance already sitting in the machine; choose software alignment only where no render node can be enabled, and then choose a resolution and rate the arithmetic above says one core can carry.

How do you keep the fallback from coming back?

Make the group membership part of provisioning, not a one-off shell fix: run the usermod line in the script that builds your distro for every account that starts a camera node, and repeat the id -nG and eglinfo checks whenever you recreate the distro, add a user or update WSL.

Container deployments hit the same wall one layer deeper: the account inside the container needs the render group's numeric GID, not just a mounted device. The device-node pattern in the Docker camera guide — pass the node, match the group — applies node-for-node to /dev/dri.

The same user-versus-device-group diagnosis explains most other Linux device denials, serial ports included: the serial port permission-denied troubleshooting guide walks the identical check for a serial device. And if the machine has no GPU path worth sharing — a headless rack unit, say — running the driver natively on dedicated industrial mini PCs removes the paravirtualized GPU layer altogether.

For a permanent station rather than a rescued laptop, start from the 3D sensors range and narrow it in the camera finder by resolution, range and interface; the configurator below starts the build at the camera step.

Cameras3D sensors

Laser profilers and stereo 3D cameras

170 models · from $699.00
Build the whole station

The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.

Start with the camera

More depth-camera integration guides sit on the 3D topic page, and the contact page reaches the applications team when a setup still refuses to cooperate.

Questions

Which user must join the render group when the camera node runs as a service or in a container?

The account the process actually runs as. For a systemd service, set SupplementaryGroups=render on the unit's User= account. In Docker, pass --device /dev/dri and --group-add with the host GID — read it with getent group render, because container groups match numbers, not names. Your interactive user's membership is irrelevant to both.

Does the same fix apply on native Ubuntu without WSL2?

Yes — the udev rule, the render group and the checks are identical on a native install. Only the restart differs: log out and back in, or reboot, instead of wsl --shutdown. One expectation changes: with a real GPU, eglinfo should name that GPU's own renderer, not the D3D12 paravirtualized device WSL2 shows.

Is software alignment acceptable at a lower frame rate like 5 fps?

Yes while the arithmetic stays small: 640×480 at 5 fps is 640 × 480 × 5 = 1,536,000 remaps/s, about 6% of the 27,648,000 a 720p30 stream costs, comfortably inside one core alongside the rest of the node. Continuous 720p30 or anything at 1080p30 is where the software path becomes the bottleneck rather than a choice.

Do I need to update Mesa or reinstall graphics drivers to fix this?

No. The glxinfo output already proves Mesa loads and accelerates the display, so nothing is missing. The failure is a file permission on a device node, not a library. Apply the group fix first; only revisit driver versions if eglinfo still reports llvmpipe or kms_swrast in its EGL rows after id -nG lists render.

What if /dev/dri is empty and renderD128 does not exist at all?

Then the problem sits upstream of permissions. An empty /dev/dri means WSL2 is not exposing a GPU device to the distro at all, and the log would read 'No such file or directory' rather than 'Permission denied'. Bring the GPU into WSL2 first — update WSL from the host side, restart with wsl --shutdown, re-check ls /dev/dri — then apply the group fix to whatever node appears.