A sample application or viewer opens its window, the title bar draws, and then nothing: no depth image, no color stream, just a stubborn grey canvas. The terminal often adds to the confusion with a libEGL warning, bad magic number warnings, or both at once. Those messages come from two different layers — graphics and acquisition — and repairing the wrong one costs an afternoon. This walkthrough gives you a two-step isolation: one headless capture that proves where frames stop, then a branch that names the failing layer, the test for it and the fix. The worked example takes a four-stream USB depth camera that started misbehaving right after a hand-patched kernel driver, and ends with a decision you can copy onto your own stream set.
Run headless first: prove where the frames stop
Before you touch a single graphics setting, run a capture path that never opens a window. Most SDKs ship a headless console example (cpp-headless in several SDK trees), and ten lines of your own code do the same job: enumerate devices, open the streams you need, request 30 frames, and print each frame's size and sequence number. The result sorts every failure of this shape into three buckets.
| Headless result | What still works | Where the fault lives |
|---|---|---|
| Device enumerated; 30 frames arrive at the configured sizes, no warnings | Acquisition | Display path (GL/EGL) |
Device enumerated; frames corrupt or header warnings such as bad magic number
|
Nothing downstream | Kernel driver, USB bandwidth or firmware |
| No device enumerated | Nothing | Cable, port, permissions, driver |
If the first row matches, the camera and its driver are innocent and the whole problem is rendering. Either other row means the libEGL warning is noise: acquisition is the patient, and repairing graphics first only hides the symptom. The capture pipeline startup failure guide and the walkthrough of debug log errors when opening a 3D camera viewer cover the log lines you meet on the way.
Cause 1 — the display path: the GL context never comes up
Signature: headless capture returns clean frames, the viewer draws its frame and menus, but the canvas never presents video, and the terminal prints libEGL warning: failed to create a pipe screen for i965. EGL is the library that wires OpenGL rendering to your display system, a pipe screen is Mesa's name for its connection to one GPU driver, and i965 is the driver it failed to open. The machine could not give the viewer hardware GL, so the context fell back to slow software rendering or failed outright. The window still opens because it belongs to the application; the frames simply never get drawn into it.
Work through three checks:
- Run
eglinfo. If its output lists only llvmpipe — Mesa's software renderer — and no hardware entry, no GPU driver is bound to EGL. - Repair the graphics stack: install the vendor driver that matches your kernel and bring Mesa up to date. A driver older than the kernel's DRM interface, or missing GPU firmware files, produces exactly this warning.
- On a machine with two GPUs, EGL can bind the wrong one; updating the driver or selecting the graphics card for the session clears it.
The same blank window has a second, unrelated root in generic viewer applications: when the app hands video to GStreamer and a plugin is missing, the log names the plugin rather than EGL, and the fix is a package install. See missing GStreamer plugins: why the viewer shows no video. For viewers that upload frames to the GPU themselves, CUDA-OpenGL interop for 3D camera image display shows what a healthy display path looks like from the capture side.
Cause 2 — the stream opens, then corrupts
Signature: the device enumerates, the streams open, and the headless run prints warnings shaped like Subdevice 0 bad magic number 0x20000e. The magic number sits at the head of every frame, so a mismatch means the receiver lost the frame boundaries and buffers arrive cut at the wrong offsets. No graphics repair helps here. The article on why depth frames arrive corrupt with bad magic numbers explains how to read those values and mask the frames they mark.
Three roots cover these warnings:
- Kernel driver mismatch. A hand-patched driver built against the wrong source tree, or never rebuilt after a distribution update, corrupts frames in exactly this way. Redo the patch against the running kernel's headers: the uvcvideo patch and kernel mismatch walkthrough and the rollback guide after patched-driver failures cover both directions, and the source-tree check when a kernel patch cannot find its file explains a patch step that refuses to start.
- USB bandwidth. A link that fell back to USB 2.0 speed — wrong port, a hub in line, a tired cable — cannot carry a multi-stream depth set, and truncation surfaces as broken frame headers. The USB bandwidth fix for depth cameras that stop updating gives the symptoms; the worked example below does the arithmetic for one real stream set.
- Firmware and SDK out of step. Header layouts change between firmware generations, and an SDK expecting the old layout reads garbage in the first bytes of every frame. Align the pair before rewriting code — the firmware and SDK alignment guide walks the check.
A quick separator test: drop to one stream at low resolution and low frame rate. If the warnings vanish, suspect bandwidth; if they persist, suspect the driver or the firmware pairing.
Cause 3 — nothing enumerates at all
Signature: the headless run finds zero devices. The failure sits below your code, in hardware or OS access.
- Another process still holds the device — a viewer left open, or a stale lock from a crashed session. Close every capture program; if the SDK still refuses, clear the stale device lock as the stale /dev/shm lock walkthrough shows.
- Access permissions: a desktop session usually grants the camera to the logged-in user, while services and containers get nothing. The Linux access-error isolation guide works through the failure modes one by one.
- Physical layer: reseat the cable on a root port, remove hubs, try another cable. Watch the kernel log (
dmesg) as you plug in — a device that appears there but not in the SDK points at drivers; one that never appears points at power or cable. - Capture path: generic UVC software and SDK examples enumerate through different layers. The capture path chooser for undetected UVC cameras sorts out which one your tool uses.
Worked example: a four-stream USB depth camera corrupting after a driver patch
The case and its inputs
The case: a first-generation USB 3.0 depth camera on Ubuntu 14.04.5, kernel 4.4.0, whose uvcvideo driver was hand-patched that morning. The viewer opens an empty window. The headless run enumerates the device — firmware 1.0.72.06 — then prints a run of Subdevice 0 bad magic number warnings, while the capture log shows the stream set: depth at 480 × 360, color at 640 × 480, and two infrared streams at 480 × 360.
Inputs, with their sources: stream sizes come from the capture log; pixel formats are 2 bytes per pixel for 16-bit depth and infrared and 3 bytes for 8-bit RGB color, the usual formats for this class; frame rate is 30 fps, the common default for this generation; bus ceilings are 480 Mbit/s signalling for USB 2.0 with roughly 40 MB/s practical, and 5 Gbit/s signalling for USB 3.0 with roughly 400 MB/s practical.
The rule
A stream's rate is its width times its height times bytes per pixel, times the frame rate. The link load is the sum of the stream rates.
The arithmetic
Depth: 480 × 360 × 2 B = 345,600 B per frame, and 345,600 × 30 = 10.37 MB/s. Each infrared stream matches it, so the three 480 × 360 streams add to 3 × 10.37 = 31.1 MB/s. Color: 640 × 480 × 3 B = 921,600 B per frame, and 921,600 × 30 = 27.65 MB/s. Link load: 31.1 + 27.65 = 58.75 MB/s, call it 58.8 MB/s, which is 58.8 × 8 ≈ 470 Mbit/s.
The decision
USB 2.0 carries roughly 40 MB/s — about 320 Mbit/s — in practice, so 470 Mbit/s cannot fit. USB 3.0 carries about 400 MB/s, so the same set uses roughly 15 % of practical capacity. Run lsusb -t: if the camera's line reads 480M, the link fell back, truncation broke the frame headers, and that is the bad magic number signature — the libEGL warning is noise. Fix the streaming layer: a root-port SuperSpeed connection with a sound cable, and the driver rebuilt against the running kernel. Rerun headless, and only after 30 clean frames do graphics settings earn attention.
Sensitivity: at 60 fps the set doubles to about 118 MB/s, still under a third of USB 3.0 practical capacity. With packed-YUV color at 2 bytes per pixel instead of RGB, the color stream drops to 18.43 MB/s and the load to 49.5 MB/s, still past any USB 2.0 link. Every reasonable variation lands in the same place: a depth set of this size belongs on a SuperSpeed link, and about 40 MB/s is where USB 2.0 stops being an option.
Fix order and prevention
- Run the headless capture for 30 frames. It splits the fault in under two minutes.
- Corrupt frames: read
lsusb -t, rebuild the patched driver against the running kernel, align firmware and SDK, and rerun headless until the warnings stop. - Clean frames: repair the display path — driver and Mesa first,
LIBGL_ALWAYS_SOFTWARE=1as the stopgap that proves the rest. - Run the viewer last. Clean headless frames plus working GL and a still-blank canvas point at the sample's own stream selection, and its log names the stream it dropped.
The same order fits area scan cameras on USB3: capture first, rendering second. Two habits prevent the repeat. Patch discipline: a hand-built kernel module belongs to one kernel version, so rebuild it after every distribution update, or pin the kernel on machines that stream in production. And keep the headless script in the bench kit — run it before any GUI, because it is the reference that makes every later symptom readable.
When the hand-patched camera is the recurring cost
If the diagnosis keeps landing on the driver branch because the camera is a discontinued development unit that runs only with hand-patched kernel drivers, the recurring cost is bench hours on every kernel update, on every workstation that streams. Moving the station to a current-production 3D sensor with maker-supported drivers ends the patch cycle instead of managing it. Two from the 3D sensors category:
- Type
- Depth / ToF camera
- Type
- Stereo / structured light
The MV-DT01WMU is a ToF sensing camera; the MV-DPS200P-02 is a projected structured-light 3D camera. Both appear in the camera finder when you filter by type, and the camera configurator pairs whichever you choose with the lens, light and controller steps that follow.
Checklist: the 60-second isolation
- Headless capture, 30 frames — proves whether acquisition produces good frames at all.
- Enumeration line — zero devices moves the fault to cable, power, permissions or driver, not graphics.
-
lsusb -t— 480M on a multi-stream depth camera predicts truncation and broken headers. -
eglinfowhen headless is clean — a software-only list makes the GL path the fault. -
dmesgat plug-in — a device that appears there but not in the SDK is a driver problem; one that never appears is cable or power. - Rebuild patched modules after every kernel update — the patch belongs to one kernel version.
When the tree points at hardware rather than software, the 3D topic page gathers the working-distance, confidence-image and integration guides for the camera you choose next, and free expert support comes with every order — contact us before the patch rabbit hole grows deeper.
Questions
Does the libEGL pipe-screen warning damage anything?
No. It is a graphics-stack message: Mesa could not get hardware acceleration from the GPU driver, so it fell back to another provider, usually software rendering. The camera, its USB link and your acquisition code are untouched, and a program can run correctly with the warning still printed. Treat it as a lead only when headless capture is clean but the window stays blank; then the GL context is the one thing left to repair.
How do I confirm which USB speed the camera negotiated?
Run lsusb -t and read the speed on the camera's line: 5000M means a SuperSpeed USB 3.0 link, 480M means high-speed USB 2.0. A multi-stream depth set-up needs USB 3.0, so 480M on that line predicts truncated frames and broken headers. Reseat the cable in a root port rather than a hub, and swap the cable before blaming software, because a damaged SuperSpeed pair falls back quietly.
Why does the blank window show the desktop behind it?
When the GL surface never presents a frame, the window keeps whatever the compositor last placed there — often a stale copy of the desktop — and that ghost image follows the window as you drag it. The artifact confirms the rendering context failed or fell back; it says nothing about the camera. Headless capture decides which layer is at fault before you change any graphics setting.
Can I run the viewer over SSH?
A windowed viewer needs a display server, so over a plain SSH session it has nothing to draw into and can fail for display reasons alone. Run the headless capture on the machine over SSH to judge acquisition, and save the viewer test for a local login with an active desktop session. Clean headless frames remotely plus a blank window locally put the display path under suspicion.
What breaks again after the next kernel update?
Any out-of-tree module you patched is built against one kernel version. A distribution update replaces the kernel and its headers, and the patched module then fails to load or returns to its broken behaviour. Rebuild it against the running kernel's headers after every update, or pin the kernel version on machines that stream in production so an unattended update cannot undo the fix between maintenance windows.

