Quick answer: OpenXR gave VR a common API, but under it sits a different runtime per vendor, and your app binds to whichever is active. A crash can live in your code, in the loader, in a vendor runtime, or in the compositor. Capture process crashes, but also session lost events and frame timing collapses, and record runtime name, version, and headset so you can tell your bug from a runtime regression.

OpenXR gave VR a common API, but underneath it sits a different runtime for every vendor, and your application binds to whichever runtime is active on the player machine. A crash can originate in your code, in the OpenXR loader, in a vendor runtime, or in the compositor that owns frame submission. Add headset diversity and strict frame timing, and crash reporting in VR is its own discipline, because the most damaging failures are often not process crashes at all but session losses and timing collapses that leave a frozen headset and never raise a signal. This post covers the runtime model, capturing crashes across the loader boundary, the timing failure modes, and the variance across headsets and runtimes.

The OpenXR runtime model

Your application links against the OpenXR loader, which dispatches calls to the active runtime supplied by the vendor, whether that is a PC runtime, a standalone headset runtime, or an open source one on Linux. The same session creation call therefore runs different code on different machines. A crash inside a runtime appears in your backtrace as frames in a vendor library you did not build, and recognizing those frames is the first step in deciding whether the fault is yours or belongs to the runtime you happened to bind to.

The compositor is a separate process that owns the display and reprojection. Your app submits frames to it through the runtime, and a protocol violation, like submitting a swapchain image in the wrong layout or missing a frame deadline, can cause the runtime to terminate your session rather than crash outright. That session loss is a distinct failure class from a process signal and must be captured separately, because to the player a torn down session and a hard crash both end in a black headset they want to take off.

Capturing across the loader boundary

Install your platform crash handler as usual, a signal handler on Linux or a structured exception handler on Windows, before you initialize OpenXR, so a fault during runtime negotiation is captured. Record the active runtime name and version reported by the instance properties and the loader version, because a crash that only happens under one runtime points at that runtime rather than your code, exactly as a driver version does in any other layered system.

Beyond process crashes, instrument the OpenXR return codes. A sequence of frame wait or frame end calls returning error codes, or a session lost event, signals a non crash failure that still ends the experience for the player. Log these events with the frame index and the swapchain state, and treat a session lost as a first class incident, because to the player a frozen or black headset is indistinguishable from a crash and deserves the same urgency in your queue even though no signal ever fired.

Frame timing and reprojection

VR is unforgiving about timing. If your app misses the display deadline, the compositor applies reprojection to synthesize a frame from the last one, which masks a dropped frame visually but signals that your render loop is in trouble. Sustained reprojection precedes many session losses, so recording the reprojection rate and the predicted display time around a failure often reveals that the real cause was a timing collapse, not a memory fault, which changes the fix from a bug hunt to an optimization pass.

A common pattern is a frame that takes too long, the runtime times out waiting for submission, and the session is torn down. The process never receives a signal, so a naive crash handler sees nothing at all. Capturing a rolling window of frame durations and the gap between frame wait and frame end turns these invisible session deaths into a clear timing story you can act on by optimizing the offending frame, which is the single most common serious VR failure that ordinary crash reporting completely misses.

Headset and runtime variance

The field is fragmented. Different headsets expose different field of view, refresh rates, controller models, and supported extensions, and a code path that assumes one configuration can crash on another. Capture the system name from the system properties, the refresh rate, the enabled extensions, and the controller interaction profiles in use, because a crash tied to a specific controller binding is invisible without that detail and looks like a random intermittent fault instead.

Extension support is a frequent fault line. Your app may query for an extension like hand tracking or passthrough, gracefully degrade when it is absent on one runtime, yet assume it everywhere in another code path. Recording the negotiated extension set per session lets you spot a crash that only occurs where an optional extension is present or absent, which is otherwise a maddening intermittent bug that reproduces for some players and never for you because your headset has a different extension set.

Setting it up with Bugnet

Integrate the Bugnet SDK into your VR application and initialize it before the OpenXR instance is created so the crash handler is live during setup. The SDK captures the signal or exception, the backtrace, and a context block where you record the runtime name, runtime version, loader version, and headset model. It persists the report and uploads on the next launch, which is the only reliable transport when the headset session has already collapsed, and an in game report button lets players flag non fatal issues.

Bugnet folds crashes together with occurrence grouping and lets you split them by runtime and by headset, the axis that matters most in VR. A signature confined to one runtime is almost certainly a runtime or driver issue you escalate upstream, while one that spans every runtime is your bug. You attach the last OpenXR error event and the frame timing window as custom fields, so a session lost incident carries the evidence to diagnose it, and occurrence counts tell you which timing collapse is hitting the most headsets.

Operating across the VR field

Because runtimes and GPU drivers update independently of your release, a crash spike can follow a runtime update rather than your patch. Always record runtime and driver versions so your dashboard can attribute a spike correctly. Sort signatures by occurrence, but cross reference runtime and headset, since the top signature is often a single runtime regression affecting many users at once rather than a broad problem in your code.

After each release, verify that session lost events and timing windows are still being captured, not just process crashes, because the most damaging VR failures are often the non crash kind. Review the top signatures split by runtime and headset, and reproduce on the configurations that dominate your volume. A short post release check protects you against a runtime update silently breaking your timing on a headset you do not own, which is otherwise a problem you only learn about from refund requests.

OpenXR crash reporting must look beyond process signals to session lost events and timing collapses, because a frozen headset is a crash to the player. Record runtime name, version, headset, and a rolling frame timing window so you can tell your bug from a runtime regression.