diff --git a/deps/cloudxr/.env.default b/deps/cloudxr/.env.default index b5725e392..f3b45a9a8 100644 --- a/deps/cloudxr/.env.default +++ b/deps/cloudxr/.env.default @@ -6,7 +6,7 @@ # CloudXR Docker Images and Configs ########################################################### CXR_RUNTIME_SDK_VERSION=6.1.0 -CXR_WEB_SDK_VERSION=6.2.0 +CXR_WEB_SDK_VERSION=mr.fce66ff6 CXR_HOST_VOLUME_PATH=$HOME/.cloudxr ########################################################### diff --git a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx index 6b9eea500..ba428dbd6 100644 --- a/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx +++ b/deps/cloudxr/webxr_client/helpers/react/CloudXRComponent.tsx @@ -230,6 +230,8 @@ export default function CloudXRComponent({ referenceSpace = referenceSpace.getOffsetReferenceSpace(offsetTransform); } + const lowLatency = config.networkLatencyMode === 'LOW'; + // Fill in CloudXR session options. const cloudXROptions: CloudXR.SessionOptions = { serverAddress: connectionConfig.serverIP, @@ -244,7 +246,13 @@ export default function CloudXRComponent({ gl: gl, referenceSpace: referenceSpace, deviceFrameRate: config.deviceFrameRate, - maxStreamingBitrateKbps: config.maxStreamingBitrateMbps * 1000, // Convert Mbps to Kbps + networkLatencyMode: lowLatency + ? CloudXR.NetworkLatencyMode.Low + : CloudXR.NetworkLatencyMode.Default, + // LOW preset sets bitrate and DRC in SessionImpl; do not override. + ...(lowLatency + ? {} + : { maxStreamingBitrateKbps: config.maxStreamingBitrateMbps * 1000 }), enablePoseSmoothing: config.enablePoseSmoothing, posePredictionFactor: config.posePredictionFactor, enableTexSubImage2D: config.enableTexSubImage2D, @@ -262,6 +270,12 @@ export default function CloudXRComponent({ }, }; + // Requires cloudxr-js with UplinkThrottle support; harmless on older SDK tarballs. + Object.assign(cloudXROptions, { + enableTrackingUplinkThrottle: config.enableTrackingUplinkThrottle ?? false, + trackingUplinkMaxHz: config.trackingUplinkMaxHz ?? 30, + }); + // Store the render target and key GL bindings to restore after CloudXR rendering const cloudXRDelegates: CloudXR.SessionDelegates = { onWebGLStateChangeBegin: () => { diff --git a/deps/cloudxr/webxr_client/helpers/utils.ts b/deps/cloudxr/webxr_client/helpers/utils.ts index a19dd1a73..cb22b5d97 100644 --- a/deps/cloudxr/webxr_client/helpers/utils.ts +++ b/deps/cloudxr/webxr_client/helpers/utils.ts @@ -146,6 +146,15 @@ export interface CloudXRConfig { /** Maximum streaming bitrate in Megabits per second (Mbps) */ maxStreamingBitrateMbps: number; + /** CloudXR network latency preset (`LOW` caps downstream bitrate for congested links). */ + networkLatencyMode?: 'DEFAULT' | 'LOW'; + + /** Cap tracking bulk uplink rate (pose, controllers, hands, body) */ + enableTrackingUplinkThrottle?: boolean; + + /** Max tracking uplink rate in Hz when throttle is enabled */ + trackingUplinkMaxHz?: number; + /** Preferred video codec used for streaming */ codec?: 'h264' | 'h265' | 'av1'; diff --git a/deps/cloudxr/webxr_client/package.json b/deps/cloudxr/webxr_client/package.json index 9d0f35a9e..bfeb95c60 100644 --- a/deps/cloudxr/webxr_client/package.json +++ b/deps/cloudxr/webxr_client/package.json @@ -20,7 +20,7 @@ "clean": "rimraf dist" }, "dependencies": { - "@nvidia/cloudxr": "file:../nvidia-cloudxr-6.2.0.tgz", + "@nvidia/cloudxr": "file:../nvidia-cloudxr-mr.fce66ff6.tgz", "@preact/signals-react": "^3.10.0", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.6.0", diff --git a/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx b/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx index f978eb38b..ec6584a33 100644 --- a/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx +++ b/deps/cloudxr/webxr_client/src/CloudXR2DUI.tsx @@ -86,6 +86,8 @@ export class CloudXR2DUI { private immersiveSelect!: HTMLSelectElement; /** Dropdown to select device frame rate (FPS) */ private deviceFrameRateSelect!: HTMLSelectElement; + /** Dropdown for CloudXR networkLatencyMode preset */ + private networkLatencyModeSelect!: HTMLSelectElement; /** Dropdown to select max streaming bitrate (Mbps) */ private maxStreamingBitrateMbpsSelect!: HTMLSelectElement; /** Dropdown to select preferred streaming codec */ @@ -154,6 +156,10 @@ export class CloudXR2DUI { private controllerModelVisibilitySelect!: HTMLSelectElement; /** Skip client CloudXR `render` (headless: client blit off; tracking on) */ private headlessInput!: HTMLInputElement; + /** Cap tracking bulk uplink rate (pose, controllers, hands, body) */ + private trackingUplinkThrottleInput!: HTMLInputElement; + /** Max tracking uplink rate when throttle is enabled */ + private trackingUplinkMaxHzSelect!: HTMLSelectElement; /** Breadcrumb subtitle in header (e.g. "for Real Robot › GEAR › Dexmate"). */ private teleopModeSubtitle!: HTMLElement; /** Hierarchical project selector in header */ @@ -213,6 +219,8 @@ export class CloudXR2DUI { // Set initial display value this.posePredictionFactorValue.textContent = this.posePredictionFactorInput.value; this.updateConfiguration(); + this.updateNetworkLatencyModeUi(); + this.updateTrackingUplinkThrottleUi(); this.updateDeviceProfileWarning(resolveDeviceProfileId(this.deviceProfileSelect.value)); this.updateConnectButtonState(); this.initialized = true; @@ -361,6 +369,7 @@ export class CloudXR2DUI { this.proxyUrlInput = this.getElement('proxyUrl'); this.immersiveSelect = this.getElement('immersive'); this.deviceFrameRateSelect = this.getElement('deviceFrameRate'); + this.networkLatencyModeSelect = this.getElement('networkLatencyMode'); this.maxStreamingBitrateMbpsSelect = this.getElement('maxStreamingBitrateMbps'); this.codecSelect = this.getElement('codec'); @@ -408,6 +417,10 @@ export class CloudXR2DUI { 'controllerModelVisibility' ); this.headlessInput = this.getElement('cloudxrHeadless'); + this.trackingUplinkThrottleInput = this.getElement( + 'cloudxrTrackingUplinkThrottle' + ); + this.trackingUplinkMaxHzSelect = this.getElement('trackingUplinkMaxHz'); this.teleopModeSubtitle = this.getElement('teleopModeSubtitle'); this.teleopProjectSelect = this.getElement('teleopProjectSelect'); } @@ -444,6 +457,7 @@ export class CloudXR2DUI { reprojectionGridRows: 0, deviceFrameRate: 90, maxStreamingBitrateMbps: 150, + networkLatencyMode: 'DEFAULT', codec: 'av1', immersiveMode: 'ar', deviceProfileId: 'custom', @@ -458,6 +472,8 @@ export class CloudXR2DUI { useQuestColorWorkaround: false, hideControllerModel: false, headless: false, + enableTrackingUplinkThrottle: false, + trackingUplinkMaxHz: 30, teleopPath: DEFAULT_TELEOP_PATH, }; } @@ -477,6 +493,7 @@ export class CloudXR2DUI { enableLocalStorage(this.reprojectionGridRowsInput, 'reprojectionGridRows'); enableLocalStorage(this.proxyUrlInput, 'proxyUrl'); enableLocalStorage(this.deviceFrameRateSelect, 'deviceFrameRate'); + enableLocalStorage(this.networkLatencyModeSelect, 'networkLatencyMode'); enableLocalStorage(this.maxStreamingBitrateMbpsSelect, 'maxStreamingBitrateMbps'); enableLocalStorage(this.codecSelect, 'codec'); enableLocalStorage(this.enablePoseSmoothingSelect, 'enablePoseSmoothing'); @@ -493,6 +510,17 @@ export class CloudXR2DUI { enableLocalStorage(this.mediaAddressInput, 'mediaAddress'); enableLocalStorage(this.mediaPortInput, 'mediaPort'); enableLocalStorage(this.controllerModelVisibilitySelect, 'controllerModelVisibility'); + enableLocalStorage(this.trackingUplinkMaxHzSelect, 'trackingUplinkMaxHz'); + this.loadTrackingUplinkThrottleFromLocalStorage(); + } + + private loadTrackingUplinkThrottleFromLocalStorage(): void { + try { + this.trackingUplinkThrottleInput.checked = + localStorage.getItem('cloudxrTrackingUplinkThrottle') === 'true'; + } catch { + this.trackingUplinkThrottleInput.checked = false; + } } /** @@ -563,6 +591,10 @@ export class CloudXR2DUI { addListener(this.reprojectionGridRowsInput, 'keyup', updateGridValidation); this.updateGridValidationMessage(); addListener(this.deviceFrameRateSelect, 'change', onProfileLinkedChange); + addListener(this.networkLatencyModeSelect, 'change', () => { + this.updateNetworkLatencyModeUi(); + onProfileLinkedChange(); + }); addListener(this.maxStreamingBitrateMbpsSelect, 'change', onProfileLinkedChange); addListener(this.codecSelect, 'change', onProfileLinkedChange); addListener(this.enablePoseSmoothingSelect, 'change', onProfileLinkedChange); @@ -609,6 +641,21 @@ export class CloudXR2DUI { this.applyHeadlessImmersiveDropdown(); this.updateConfiguration(); }); + addListener(this.trackingUplinkThrottleInput, 'change', () => { + try { + localStorage.setItem( + 'cloudxrTrackingUplinkThrottle', + this.trackingUplinkThrottleInput.checked ? 'true' : 'false' + ); + } catch { + // ignore + } + this.updateTrackingUplinkThrottleUi(); + this.updateConfiguration(); + }); + addListener(this.trackingUplinkMaxHzSelect, 'change', () => { + this.updateConfiguration(); + }); addListener(this.deviceProfileSelect, 'change', () => { this.applyDeviceProfileToForm(resolveDeviceProfileId(this.deviceProfileSelect.value)); @@ -745,6 +792,7 @@ export class CloudXR2DUI { maxStreamingBitrateMbps: parseInt(this.maxStreamingBitrateMbpsSelect.value) || this.getDefaultConfiguration().maxStreamingBitrateMbps, + networkLatencyMode: this.networkLatencyModeSelect.value === 'LOW' ? 'LOW' : 'DEFAULT', codec: (this.codecSelect.value as 'h264' | 'h265' | 'av1') || this.getDefaultConfiguration().codec, // Headless mode turns off the client's CloudXR frame blit but keeps tracking; the WebXR @@ -791,6 +839,10 @@ export class CloudXR2DUI { // See immersiveMode above: when true, callers must start an immersive-vr WebXR session. headless: this.headlessInput.checked, panelHiddenAtStart: this.panelHiddenAtStartSelect.value === 'true', + enableTrackingUplinkThrottle: this.trackingUplinkThrottleInput.checked, + trackingUplinkMaxHz: + parseInt(this.trackingUplinkMaxHzSelect.value, 10) || + this.getDefaultConfiguration().trackingUplinkMaxHz, teleopPath: this.teleopPath, }; @@ -870,6 +922,7 @@ export class CloudXR2DUI { localStorage.setItem('reprojectionGridCols', this.reprojectionGridColsInput.value); localStorage.setItem('reprojectionGridRows', this.reprojectionGridRowsInput.value); localStorage.setItem('deviceFrameRate', this.deviceFrameRateSelect.value); + localStorage.setItem('networkLatencyMode', this.networkLatencyModeSelect.value); localStorage.setItem('maxStreamingBitrateMbps', this.maxStreamingBitrateMbpsSelect.value); localStorage.setItem('codec', this.codecSelect.value); localStorage.setItem('enablePoseSmoothing', this.enablePoseSmoothingSelect.value); @@ -881,6 +934,28 @@ export class CloudXR2DUI { } } + private updateTrackingUplinkThrottleUi(): void { + const enabled = this.trackingUplinkThrottleInput.checked; + this.trackingUplinkMaxHzSelect.disabled = !enabled; + const help = document.getElementById('trackingUplinkMaxHzHelp'); + if (help) { + help.textContent = enabled + ? 'Maximum rate for all tracking bulk messages (pose, controllers, hands, body).' + : 'Enable tracking uplink throttle above to set a max rate.'; + } + } + + private updateNetworkLatencyModeUi(): void { + const low = this.networkLatencyModeSelect.value === 'LOW'; + this.maxStreamingBitrateMbpsSelect.disabled = low; + const help = document.getElementById('networkLatencyModeHelp'); + if (help) { + help.textContent = low + ? 'Low mode sets ~15 Mbit/s max bitrate and Ragnarok DRC. Settings below do not apply.' + : 'Default: Ragnarok ALL adaptive streaming. Set max Mbps below.'; + } + } + private updateDeviceProfileWarning(profileId: DeviceProfileId): void { if (!this.deviceProfileWarning) return; const profile = getDeviceProfile(profileId); diff --git a/deps/cloudxr/webxr_client/src/index.html b/deps/cloudxr/webxr_client/src/index.html index 2a03f4efb..f4b0635db 100644 --- a/deps/cloudxr/webxr_client/src/index.html +++ b/deps/cloudxr/webxr_client/src/index.html @@ -723,6 +723,36 @@

Debug Settings

+
+ + +
+ Default: set max Mbps below with Ragnarok ALL adaptive streaming. Low: applies ~15 Mbit/s and Ragnarok DRC (ignores Mbps). +
+
+ +
+ +
+ + Cap tracking uplink (pose, controllers, hands, body) to reduce bandwidth. +
+ + +
+ Maximum rate for all tracking bulk messages when throttle is enabled. +
+
+