reckfellENGINE

XR device models: loading and fallback

Availability gates, event ordering, hand filters and device-geometry lifetime.

On this page

OpenXR device render models

Render models are visual hardware models supplied by the XR runtime. Current Reckfell uses XR_EXT_render_model and XR_EXT_interaction_render_model. An interaction profile does not establish model availability for a particular device.

First configure XR startup and the action map. Enable xr/openxr/extensions/render_model before launching the application. These classes require a build with OpenXR and glTF. A saved checkbox and SDK class availability establish neither activation of both extensions nor loaded geometry.

Start with a general manager

For the first setup, use one OpenXRRenderModelManager with Tracker=Any directly under XROrigin3D. Leave MakeLocalToPose empty and retain your own fallback visual until the actual model is available.

Text
XROrigin3D
├── XRCamera3D
├── OpenXRRenderModelManager (Tracker=Any)
└── FallbackVisuals

Create the manager only after checking the runtime singleton and IsActive. Current manager code accesses its native singleton even during construction notifications; creating it without a wrapper can emit a native error. Never construct OpenXRRenderModelExtension with new in gameplay: OpenXR manages that wrapper.

C#
using System;
using Reckfell;

public static class RenderModelGate
{
    // Returns a new child owned by origin, or null so the caller can retain fallback visuals.
    public static OpenXRRenderModelManager? CreateIfActive(XROrigin3D origin,
        Action<OpenXRRenderModel> added, Action<OpenXRRenderModel> removed)
    {
        ArgumentNullException.ThrowIfNull(origin);
        ArgumentNullException.ThrowIfNull(added);
        ArgumentNullException.ThrowIfNull(removed);
        if (!origin.IsInsideTree()) return null;
        if (!Engine.HasSingleton("OpenXRRenderModelExtension")) return null;
        var extension = Engine.GetSingleton("OpenXRRenderModelExtension") as OpenXRRenderModelExtension;
        if (extension == null || !extension.IsActive()) return null;
        var manager = new OpenXRRenderModelManager
        {
            Tracker = OpenXRRenderModelManager.RenderModelTracker.Any
        };
        // Subscribe before AddChild: existing runtime models can be reported during EnterTree.
        manager.RenderModelAdded += model => added(model);
        manager.RenderModelRemoved += model => removed(model);
        origin.AddChild(manager);
        return manager;
    }
}

Call CreateIfActive after XR startup with a live origin and handlers. null leaves fallback control with your component. A returned manager is already attached to origin; do not AddChild again. It and its models are freed with the tree. The extension singleton is borrowed: never Free/Dispose it.

The helper subscribes before AddChild: the manager can report existing models during EnterTree. Subscribing afterward can miss initial events. The successful real-runtime branch was not tested in this run; rejection without a singleton and argument guards were executed.

An added event is not a ready image

The manager's RenderModelAdded supplies an OpenXRRenderModel Entity. It reports entry creation in the manager tree. If render_model_data is absent, child-geometry creation can return null while the Entity event remains possible. Do not hide fallback merely by counting these events.

Before switching visuals, check the chosen model's actual geometry and current tracking confidence through the extension. At Confidence=None, built-in OpenXRRenderModel stops updating transforms/animatable parts but does not hide prior geometry. Implement stale-model hiding and fallback restoration separately. IsActive is not a “controller loaded” flag either.

RenderModelRemoved is emitted before remove_child/queue_free. The object is still accessible inside the handler, but cannot be retained as a live model for the next frame. A filtered manager can defer processing: its backend RID may already have been revoked, so do not query extension data through it. Do not free manager-owned Entities inside this event: the manager performs deletion. Update fallback state and release your own references.

Separate left and right hands

TrackerPlacement
AnyAll models; manager directly under XROrigin3D.
NoneSetModels without a resolved top-level path; also directly under origin.
LeftHand / RightHandFilter by hand path; origin must be an ancestor, with intermediate Entities permitted.

For nesting inside XRController3D, use the same pose name on the controller and manager MakeLocalToPose, for example grip:

Text
XROrigin3D
└── XRController3D (Tracker=left_hand, Pose=grip)
    └── OpenXRRenderModelManager (Tracker=LeftHand, MakeLocalToPose=grip)

MakeLocalToPose applies the inverse adjusted pose transform to an internal container. It compensates for a pose already applied by the parent; it is neither a filename nor an input-action switch. Use it only for a particular hand and matching parent; Any/NoneSet with this setting produces a configuration warning.

Tracker binds the positional tracker and XR path in its setter when the value changes. If the API/tracker was unavailable then, repeating the same setting does not guarantee rebinding. Configure a hand manager after provider/trackers are ready; recreate your owned manager in a controlled way when changing setup. Source also checks pose-object existence, not HasTrackingData, so handle tracking loss explicitly.

Top-level path /user/hand/left or /user/hand/right associates the model with a hand; it does not prove the controller is physically held. The model system creates no collisions, hand-motion constraints or button gameplay. Keep those tasks separate from visual geometry.

Direct OpenXRRenderModel use

RenderModel accepts an RID belonging to this OpenXR extension. Not a RenderingServer mesh RID or RenderingDevice texture. Obtain current identifiers from the provider; invent no numbers and free no borrowed RIDs through FreeRid/RenderModelDestroy.

The RenderModel setter stores an RID and attempts to add geometry when inside the tree. Current source does not delete previous children: repeating a nonzero RID can add another scene, while a default RID does not itself clear previously loaded geometry. To replace your own direct instance, create a new OpenXRRenderModel and remove the old Entity; leave manager-owned instance lifetime to the manager. These real-geometry paths were source-reviewed, not executed with a headset.

GetTopLevelPath returns a path string or empty string. RenderModelTopLevelPathChanged reports association changes for this model, not mesh loading. Root transforms and animatable parts update from the provider. Do not add origin/reference-frame transforms again over an already configured hierarchy.

Diagnosis

SymptomCheck
Classes exist but the manager emits a native errorActual runtime singleton, IsActive and construction timing; never create the wrapper manually.
IsActive=true but no geometryRuntime models, mesh data, action map and device association are separate states.
Duplicate controllersManagers with overlapping filters, your fallback and repeated SetRenderModel calls.
Double model offsetParent XRController3D, its Pose, MakeLocalToPose and absence of a second origin/pose transform.
Old controller remains after tracking lossConfidence=None does not automatically hide the Entity; apply your fallback policy.
Hand filter does not refresh after late startupTracker-creation/Tracker-setting order; an equal-value setter is not a rebind.

Verification scope

Installed Reckfell 1.0.0, Windows headless with XR off ran 19 checks: actual absence of a registered runtime singleton, exact-helper rejection, null arguments, Entity parameters/empty lifecycle and C# event signatures. Metadata checks temporarily created an owned inactive diagnostic wrapper, not registered in Engine; this is a test technique, not gameplay code. Its QA cleanup was corrected to Free, with no leak warnings in the final run.

Test events were manually emitted, not discovered controllers. Provider signals, real-geometry loading, tracking-loss visuals, hand filtering, pose rebasing, animation, imagery and export were not tested. Their limitations were reviewed in current source. Engine source, Runtime and SDK were unchanged.

Reckfell Engine documentationBack to top ↑
QUICK LINKSGetting startedEntity hierarchyC# & .NET 10Scenes and prefabs
Reckfell guides and full API K
reckfellENGINE
RECKFELL ACCOUNT

Sign in

Sign in to your Reckfell account.

One account for the entire Reckfell platform.