Automatic PC VR — experimental

Attach a headset to one existing perspective camera. The engine tracks the head, renders stereo geometry and submits through OpenXR using the usual RenderWorld / Flip loop. The camera entity remains the authored player base. No headset/runtime/GPU combination is yet qualified for production use.

Start with the sample

Open AutomaticVR.bb in Full Win64 Extended mode. Install and select your headset vendor's PC OpenXR runtime, connect the headset, then run the program. If preparation fails the sample displays the reason and continues on the desktop. Full Win32 and OpenXR-disabled builds run the same source with unsupported-backend results.

prepared = InitVR()
Graphics3D 960,640,32,2
camera = CreateCamera()
CameraRange camera,0.05,100
PositionEntity camera,0,1.6,0
If prepared Then EnableVR(camera,1.0)

One scene unit is one metre here. The authored camera is the seated head height. The first valid head sample sets the seated translation and yaw origin; physical pitch and roll remain intact. R in the sample recentres. H toggles the HUD, M the desktop mirror, and +/- adjusts eye resolution. Escape exits. With controllers, each hand shows as a small block with a pointer: point at the orange cube and pull the trigger to change its colour, and the HUD says when you look at the cube. Mouse, keyboard and gamepad retain their ordinary desktop behavior; headset focus is a separate query.

Frame and scene rules

Advance simulation once, then call RenderWorld once and Flip once. The engine prepares one scene snapshot and renders two eyes; it does not call UpdateWorld for either eye. The sample uses fixed 60 Hz ticks with UpdateWorld 1 and elapsed seconds only to accumulate ticks. Do not reinterpret UpdateWorld's elapsed-time argument as seconds.

RenderWorld twice before Flip, another world, or RenderWorldToCanvas while VR is enabled reports an error and abandons the pending stereo frame. Flip without RenderWorld can submit a HUD-only frame; it never replays stale geometry. A runtime request not to render produces no layers. Invalid tracking suppresses stereo until a usable sample returns. Continue servicing the loop while invisible or waiting. Queries never block or pump events.

The camera must be perspective with a finite rigid ancestry and an ordered positive clipping range. Do not scale or mirror its parents. EnableVR's positive world-units-per-metre argument scales translations and eye separation. Windowed Graphics3D modes 2 and 3 are supported; exclusive fullscreen, autosuspend and GUI targets are rejected. Change the world scale only after DisableVR; moving VR to another camera does not need it (see Switching worlds). CameraPick still uses the authored camera; for head or controller picking, cast a LinePick from the tracked pivots below.

Head and controllers

VRHeadPivot() returns a pivot that the engine keeps at the centre of the player's head, and VRHandPivot(hand) one at a controller's aim pose (hand 0 is the left, 1 the right). They are ordinary entities, children of the VR camera, so their local position and rotation are the tracked pose relative to the authored camera: in world units (EnableVR's units per metre applies) and measured from the seated origin that VRRecenter resets. Read them with EntityX(pivot,True), turn their facing into a ray with TFormVector 0,0,1,pivot,0, or parent models to them.

head = VRHeadPivot()
If head
    TFormVector 0,0,10,head,0
    target = LinePick(EntityX(head,True),EntityY(head,True),EntityZ(head,True),TFormedX(),TFormedY(),TFormedZ())
EndIf
right = VRHandPivot(1)
If right Then gun = CreateCube(right)

The engine reads the head and controllers once per frame, inside RenderWorld (or Flip, when a frame has no RenderWorld), at the time the frame will appear in the headset, and moves the pivots before drawing, so models parented to them are drawn where the player sees their hands. Code between frames reads the pose of the last frame. A hand pivot is hidden while its controller is untracked, which hides what is parented to it; VRHandTracked(hand) says whether the pose was valid. The pivots return 0 while VR is not enabled.

The engine owns the pivots: do not free, reparent, hide or show them. A freed pivot is replaced by a new one, with a new handle, at the next call. DisableVR and ShutdownVR free them.

Buttons and sticks: VRButtonDown(hand,button) and VRButtonHit(hand,button) work like KeyDown and KeyHit, for button 1 trigger, 2 grip, 3 A or X, 4 B or Y, 5 menu and 6 thumbstick click. VRAxis#(hand,axis) gives axis 1 thumbstick X and 2 thumbstick Y (up is positive) from -1 to 1, and 3 trigger and 4 grip from 0 to 1. VRRumble hand,strength#,milliseconds buzzes a controller. Bindings are suggested for the Khronos simple controller, Oculus Touch, Valve Index, HP Reverb G2 and Windows Mixed Reality controllers; a controller without a button simply never reports it, and the runtime keeps each controller's system button. While another application has the headset's focus, every button is up and nothing is counted. Without VR all of these return 0, so the same program runs on the desktop.

Switching worlds

Calling EnableVR with another camera, at the same units per metre, while VR is enabled moves VR to that camera and its world without stopping the OpenXR session, so the headset does not drop out. Use it when a program replaces its world, for example after loading a new world document: select the new world with SetWorld, call EnableVR with the new camera, then free the old world as usual. The switch takes effect at the next RenderWorld. A different units per metre still needs DisableVR first.

When the new camera is in the same world, the head and hand pivots move under it and keep their handles. When it is in another world, they are freed with the switch and the next VRHeadPivot or VRHandPivot call creates new ones under the new camera, so call them again after EnableVR. A VRListener listener in the old world is detached; attach the new world's listener with VRListener.

HUD, mirror and sound

Draw ordinary 2D text/images after RenderWorld. They appear on a premultiplied-alpha head-relative panel, 1.2 m wide at 1.5 m by default. Untouched pixels are transparent; Cls or an opaque image produces an opaque panel. HUD dimensions are physical metres, independently of world scale. Resizing the back buffer adjusts its aspect at a subsequent frame boundary.

The optional mirror copies the left eye and composites the desktop HUD. XR pacing bypasses desktop VSync and FrameLimit; a busy mirror can skip presentation. CaptureFrame captures the left eye plus HUD, including when the mirror is disabled. Explicit capture readback can stall and must not be used for latency measurements. VRListener opts the existing audio-world listener into head-centre position/orientation once per rendered sample; authored velocity is retained. Zero detaches it.

Rendering support and effect policy

If the session is waiting or not visible, check that the headset is awake and its PC VR environment is connected. A successful EnableVR accepts the session request; it does not guarantee the runtime is displaying the application. The engine continues the OpenXR frame loop while obeying the runtime's rendering flag. Run the sample with --diagnose to write vr-diagnostics.txt in its working directory, including runtime version, headset system, adapter LUID, eye dimensions and frame-state transitions.

The experimental renderer uses two sequential views with independent targets and temporal history. Asymmetric eye projection reaches culling, shadows, planar reflections, decals and depth reconstruction. GPU texture tests cover asymmetric raster positions, HDR/AA output, HUD alpha and mirror ownership. These checks do not establish headset optical correctness or comfort. Terrain detail selection uses the shared head centre; each eye still culls separately.

Eye output is display-encoded RGBA8 with an sRGB swapchain declaration, so the compositor interprets its colour correctly without a second application-side conversion. The HUD decodes its source colours before blending into a separate linear RGBA8 UNORM, premultiplied-alpha layer. The backend accepts the corresponding typeless Direct3D resources returned by OpenXR.

Camera post effects are skipped by default in VR, including depth of field, motion blur, distortion and unqualified custom effects. Their authored enabled flags and parameters are preserved for desktop use. A format-2/3 layout-4 post manifest may declare vr-compatible 1 after layout and mode. The compiler and loader validate this declaration. It is an author assertion: use full inverse projection for depth reconstruction, operate on the supplied eye inputs, and avoid shared cross-eye history or forced symmetric projection. Qualify the result on hardware before opting it in. Skips are recorded once in diagnostics.

Failure and recovery

Save VRLastError$() immediately after a failed command; the next fallible command clears it. Queries preserve it. InitVR must precede any graphics device. Adapter overrides that conflict with the headset keep the requested desktop adapter and abandon VR. Runtime/session loss returns to desktop when graphics remains healthy. DisableVR may remain STOPPING until the runtime responds. ShutdownVR resets all settings; EndGraphics and IDE Stop clean up owned resources. To restart after terminal loss, close graphics, prepare VR and recreate the scene.

Scope and qualification

This is Windows PC stereo viewing through OpenXR 1.0 and XR_KHR_D3D12_enable. Head and controller poses, buttons, sticks and haptics are provided as described above; there is no locomotion conversion, room-scale collision, independent spectator camera, compositor depth, foveation, Android/standalone-headset builds, or editor VR preview. Camera shake, locomotion, aiming and first-person weapons require application-specific VR design.

Production qualification still requires real headset/runtime/GPU testing, OpenXR validation-layer traces, focus/loss/reconnect/debugger/resize tests and CPU/GPU/frame-delivery measurements at the headset refresh rates. See the source-tree implementation progress record for measured results and outstanding gates.

VR command reference