Repository navigation
feat(stereo-camera): vendor-neutral UVC side-by-side stereo source (Windows MF; V4L2 stub) - #1846
Merged
Merged
Conversation
Collaborator
Author
|
Hardware check (SR laptop + SpatialLabs Eyes 0733:011a, 2026-10-08): no config → claims nothing, tracker 04f2:b70c 'never touched'. With config (sbs-half, lr, 3840x2160@60): CLAIMED, |
…indows MF; V4L2 stub) ADR-043 Amendment 4. Every stereo camera so far came from a display plug-in. This adds the SERVICE's own source for plain UVC side-by-side stereo webcams, so any display + any SBS UVC stereo webcam works with no vendor plug-in. - util/u_stereo_uvc: opt-in config (per-user stereo-cameras.json, or DXR_STEREO_CAMERA_UVC_CONFIG; DXR_STEREO_CAMERA_UVC=0 = off): VID:PID and/or name match, sbs-full|sbs-half, eye order, mode, eye size, nominal baseline + HFOV, optional OpenCV calibration (YAML/JSON). Device assignment (a name-only entry matching several devices claims none; a plug-in camera's platform_device_hint is never claimed), the SBS split (box + bilinear, NV12/ YUY2 in, NV12 out), the nominal pinhole model, and a fake backend. - os/os_uvc_capture: Media Foundation backend (MFEnumDeviceSources, async IMFSourceReader, MJPEG decoded by the reader's decoder MFT); V4L2 stub + plan. Own library (aux_os_uvc) linked only into the service and displayxr-cli. - Camera manager: per-camera source tag + src_* dispatch, so consent, CAMERA_CONSUMER rules, foreground rule, indicator, linger, rings and the rectifier are one path. Uncalibrated UVC pair: no CALIBRATED, RAW calibration refused, RECTIFIED = pass-through with the nominal numbers until the online vertical refinement folds a correction in. create_ex() test seam. - CLI: `camera list` shows the source (plugin|uvc); `camera uvc-devices [--modes]` (local) shows config + devices + which entry claims what. - xrt_stereo_camera_properties.reserved -> source (same wire size). - Tests: tests_stereo_uvc (config, matching, assignment, geometry, calibration files, split, fake source, closed loop) and tests_stereo_camera_manager (the real manager through its IPC handlers on the fake backend: delivered row offset +2.40 -> +0.004 px). - Docs: ADR-043 Amendment 4, spec section 9a + rule changes, roadmap U1, env-var census, index.json summary. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…PU decode + scale) The first hardware run (SpatialLabs Eyes, 3840x2160@60 half-SBS MJPEG) delivered 11.3 Hz / ~93 ms. Root causes and fixes: - Hardware path (Media Foundation): pick the adapter that has a hardware MJPEG decoder MFT (MFTEnumEx HARDWARE, matched to DXGI adapters by vendor; most dedicated memory wins, DXR_STEREO_CAMERA_UVC_ADAPTER picks), create a D3D11 device (VIDEO_SUPPORT, multithread-protected), hand the reader an IMFDXGIDeviceManager + MF_READWRITE_ENABLE_HARDWARE_TRANSFORMS, and request NV12 AT THE OUTPUT SIZE so the GPU decodes and downscales (3840x2160 -> 2560x720). Readback: CopySubresourceRegion to a staging NV12 texture, an event query polled (a blocking Map would hold the device lock MF's decoder needs), one Map, copy out, Unmap, sample returned at once. The decoder MF actually inserted is inspected; a software decoder behind the device manager reopens on the software path. Software fallback with one WARN; DXR_STEREO_CAMERA_UVC_DECODER=auto|hardware|software. Software also asks MF's video processor for the output size. - The CPU split was the real 11 Hz limiter: ~110 ms for a 4K frame. Rewritten (row-major accumulate, reciprocal multiply, single-row fast paths, memcpy when the frame is pre-scaled): ~25 ms for 4K, 0.34 ms for a pre-scaled frame. - Reader callback re-issued ReadSample under its (recursive) lock; the reader can decode inline and call back re-entrantly, starving read(). Now issued outside the lock on an AddRef'd reader. - read() returned a spurious instant TIMEOUT when the auto-reset event was left signalled; it now waits against a deadline. - Backend open() takes the output-size hint; diagnostics: per-open decoder line, close summary (frames, mean readback), delivered/superseded on stop. - CLI: `camera uvc-devices --decoder` (decoders + adapters + policy, no camera), `camera uvc-decode-bench <file>` (a media file through the identical path, paced to its frame rate, no camera). Dev laptop, 4K60 half-SBS MJPEG file: hardware NVIDIA 54-58 Hz / Intel 60 Hz read rate, software with MF scaling 59.9 Hz. - Docs: spec section 9a decode-path table + numbers, ADR consequence, census. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…k ring Real SpatialLabs Eyes run of e5f8e2e: (1) the GPU scale 3840x2160 -> 2560x720 came out PILLARBOXED (the video processor preserves display aspect: a centred ~1280 px picture between black bars, each eye black + half the picture); (2) 36 Hz / 29 ms, readback 22-25 ms per frame (a synchronous copy + Map waits behind the decode). - Output type now declares an anamorphic pixel aspect so its display aspect equals the source's: PAR_out = PAR_src * src_w*out_h / (src_h*out_w) (1:2 for 3840x2160 -> 2560x720) — a plain per-axis resize, each half its own eye. Applies to the GPU and the software video processor alike. - Readback pipelined over a ring of 3 staging NV12 textures: each read queues a copy + event query and delivers the NEWEST copy already finished (this frame when the GPU is quick), polling the older one only when two are in flight; a read with no new sample flushes the waiting frame (no frame is lost at a pause / end of file). The first read primes (TIMEOUT). - `camera uvc-decode-bench` checks the picture: per eye, mean luma of the outer 5 % columns vs the centre; exits 4 on dark side bars. It treats a TIMEOUT as "not yet" (2 s without a frame = end). - tests_uvc_capture_mf (Windows): writes a bright 16:9 YUY2 AVI and opens it through the real backend asking for 32:9, on the hardware AND software policies, asserting the requested size and no dark side bars. Fails on the old 1:1 PAR, passes now. SKIPs without Media Foundation. - Bench (dev laptop, bright 3840x2160@60 half-SBS MJPEG file, out 2560x720, no bars on any path): Intel 60.0 Hz (readback 0.7 ms), software + MF scale 59.7 Hz, NVIDIA 43-53 Hz (readback 5.6-6.8 ms) — a file lets the async NVIDIA MFT decode far ahead of the paced requests (250-300 of 360 frames superseded), so the copies queue behind a backlog a 60 Hz camera does not create. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
dfattal
force-pushed
the
feat/stereo-camera-uvc-source
branch
from
October 9, 2026 19:23
13dcbe5 to
3cbe14a
Compare
dfattal
marked this pull request as ready for review
October 9, 2026 19:40
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
ADR-043 Amendment 4: a vendor-neutral, service-owned stereo camera source for plain UVC side-by-side stereo webcams. Today every stereo camera comes from a display plug-in. With this, any display plus any SBS UVC stereo webcam works through
XR_DXR_stereo_camera, with no vendor plug-in, the same consent and privacy model, and the same rectified output.auxiliary/util/u_stereo_uvc.{h,c}(pure, tested on every OS):stereo-cameras.json(%LOCALAPPDATA%\DisplayXR\,$XDG_CONFIG_HOME/displayxr/).DXR_STEREO_CAMERA_UVC_CONFIGoverrides the path andDXR_STEREO_CAMERA_UVC=0turns the source off.sbs-full|sbs-half), eye order, mode, eye size, nominal baseline and HFOV, plus an optional OpenCV calibration file (YAML or JSON).platform_device_hintis never claimed. The Leia plug-in reports its tracker as"vvvv:pppp", and this form is covered by a test.auxiliary/os/os_uvc_capture*: the Media Foundation backend.MFEnumDeviceSourcesonly lists devices; nothing is activated.IMFSourceReaderwith advanced video processing, so the system MJPEG decoder MFT does the decoding. The newest sample wins.aux_os_uvc) and is linked only into the service anddisplayxr-cli, never the runtime DLL.src_*dispatch picks the source. Consent,CAMERA_CONSUMERrules, the foreground rule, the in-use indicator, linger, rings, the rectifier and the refinement are one code path for both sources.CALIBRATED, and its RAW calibration is refused. RECTIFIED is a pass-through (no remap, no zoom, nominal numbers) until the online vertical refinement applies a correction. After that it uses the normal corrected LUT path and the calibration generation moves.CALIBRATED, and the R2 rectifier runs as usual.create_ex()test seam.cam->indexnow always equals the camera's slot. Before, it was the plug-in index, which went wrong after a skipped malformed camera.camera listshows(source: plugin|uvc), and"source"in--json. The JSON now escapes backslashes in the hint.camera uvc-devices [--modes]runs locally with no service. It shows the config and the devices present, and which entry claims which device (or that an entry is ambiguous).--modeslists modes for claimed devices only.xrt_stereo_camera_properties.reservedbecomessource(same size). The OpenXR struct and the spec version are unchanged.Docs
CALIBRATED | NATIVELY_RECTIFIED.index.jsonsummary updated;gen_extensions_index.py --checkpasses.Tests (Windows, local)
tests_stereo_uvc: 12 cases, 402 assertions. Covers config parsing and errors, VID:PID parsing, matching, the assignment and ambiguity rules, plug-in exclusion, eye-size math, the nominal model, calibration files, the split (lr/rl, full/half, NV12/YUY2), and the fake source end to end (disparity 25.60 px, dy +2.40 px recovered). Closed loop: dy +2.398 → +0.004 px.tests_stereo_camera_manager: the real manager, driven through its IPC handlers on the fake backend with an in-memory consent store. No MF, no device, no service, so it runs on CI. Checks enumerate and calibration semantics, and a RECTIFIED stream through consent and the read-only ring with refinement APPLIED, generation 0 → 1, delivered dy +2.40 → +0.004 px. A RAW stream stays RAW.ctest: 91/91.build_windows.bat buildis warning-clean apart from the known C4090 inoxr_session.c.Not done / follow-ups
🤖 Generated with Claude Code