@plasius/gpu-xr 0.1.4 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -3,6 +3,11 @@ var DEFAULT_VR_SESSION_INIT = Object.freeze({
3
3
  requiredFeatures: ["local-floor"],
4
4
  optionalFeatures: ["bounded-floor", "hand-tracking", "layers"]
5
5
  });
6
+ var DEFAULT_MODE_FRAME_RATES = Object.freeze({
7
+ inline: 60,
8
+ "immersive-vr": 90,
9
+ "immersive-ar": 72
10
+ });
6
11
  var xrSessionModes = Object.freeze([
7
12
  "inline",
8
13
  "immersive-vr",
@@ -15,6 +20,9 @@ var xrReferenceSpaceTypes = Object.freeze([
15
20
  "bounded-floor",
16
21
  "unbounded"
17
22
  ]);
23
+ var xrWorkerQueueClass = "render";
24
+ var xrWorkerSchedulerMode = "dag";
25
+ var defaultXrWorkerBudgetProfile = "xr";
18
26
  function toStringArray(values) {
19
27
  if (!Array.isArray(values)) {
20
28
  return [];
@@ -24,6 +32,31 @@ function toStringArray(values) {
24
32
  function dedupeStrings(values) {
25
33
  return [...new Set(toStringArray(values))];
26
34
  }
35
+ function readPositiveNumber(value) {
36
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? value : null;
37
+ }
38
+ function normalizeFrameRates(values) {
39
+ if (!values || typeof values === "string") {
40
+ return Object.freeze([]);
41
+ }
42
+ let collected;
43
+ try {
44
+ collected = Array.from(values);
45
+ } catch {
46
+ return Object.freeze([]);
47
+ }
48
+ return Object.freeze(
49
+ [...new Set(collected.map((value) => Number(value)).filter((value) => Number.isFinite(value) && value > 0))].sort(
50
+ (left, right) => right - left
51
+ )
52
+ );
53
+ }
54
+ function getDefaultFrameRateForMode(mode) {
55
+ return DEFAULT_MODE_FRAME_RATES[mode] ?? DEFAULT_MODE_FRAME_RATES["immersive-vr"];
56
+ }
57
+ function getWorkerBudgetProfileForMode(mode) {
58
+ return mode === "inline" ? "realtime" : defaultXrWorkerBudgetProfile;
59
+ }
27
60
  function readNavigator(navigatorOverride) {
28
61
  const currentNavigator = navigatorOverride ?? globalThis.navigator;
29
62
  if (!currentNavigator || typeof currentNavigator !== "object") {
@@ -107,6 +140,101 @@ async function requestXrSession(options = {}) {
107
140
  const init = mergeXrSessionInit(baseSessionInit, sessionInit);
108
141
  return xr.requestSession(mode, init);
109
142
  }
143
+ function readXrFrameRateCapabilities(session, options = {}) {
144
+ const {
145
+ mode = "immersive-vr",
146
+ fallbackFrameRates = [],
147
+ defaultFrameRate
148
+ } = options;
149
+ assertSessionMode(mode);
150
+ const sessionFrameRate = readPositiveNumber(session?.frameRate);
151
+ const supportedFrameRates = normalizeFrameRates(session?.supportedFrameRates);
152
+ const fallbackRates = normalizeFrameRates(fallbackFrameRates);
153
+ const mergedSupported = supportedFrameRates.length ? [...supportedFrameRates] : [...fallbackRates];
154
+ if (sessionFrameRate && !mergedSupported.includes(sessionFrameRate)) {
155
+ mergedSupported.push(sessionFrameRate);
156
+ mergedSupported.sort((left, right) => right - left);
157
+ }
158
+ const refreshRateHz = sessionFrameRate ?? mergedSupported[0] ?? readPositiveNumber(defaultFrameRate) ?? getDefaultFrameRateForMode(mode);
159
+ return Object.freeze({
160
+ mode,
161
+ currentFrameRate: sessionFrameRate,
162
+ supportedFrameRates: Object.freeze(mergedSupported),
163
+ refreshRateHz,
164
+ canUpdateTargetFrameRate: Boolean(session) && typeof session.updateTargetFrameRate === "function"
165
+ });
166
+ }
167
+ function createXrPerformanceHint(options = {}) {
168
+ const {
169
+ session = null,
170
+ mode = "immersive-vr",
171
+ preferredFrameRates = [],
172
+ fallbackFrameRates = [],
173
+ defaultFrameRate
174
+ } = options;
175
+ const capabilities = readXrFrameRateCapabilities(session, {
176
+ mode,
177
+ fallbackFrameRates,
178
+ defaultFrameRate
179
+ });
180
+ const filteredPreferredFrameRates = normalizeFrameRates(preferredFrameRates).filter(
181
+ (frameRate) => capabilities.supportedFrameRates.length === 0 || capabilities.supportedFrameRates.includes(frameRate)
182
+ );
183
+ const derivedPreferredFrameRates = filteredPreferredFrameRates.length ? filteredPreferredFrameRates : capabilities.supportedFrameRates.length ? capabilities.supportedFrameRates : Object.freeze([capabilities.refreshRateHz]);
184
+ const targetFrameRate = derivedPreferredFrameRates[0] ?? capabilities.refreshRateHz;
185
+ const rationale = [];
186
+ if (capabilities.currentFrameRate) {
187
+ rationale.push(
188
+ `XR session reports a current frame rate of ${capabilities.currentFrameRate}Hz.`
189
+ );
190
+ } else {
191
+ rationale.push("XR session does not expose a current frame rate; using defaults.");
192
+ }
193
+ if (capabilities.supportedFrameRates.length) {
194
+ rationale.push(
195
+ `XR runtime exposes supported frame rates: ${capabilities.supportedFrameRates.join(", ")}Hz.`
196
+ );
197
+ } else {
198
+ rationale.push("XR runtime does not expose supported frame rates; using fallback targets.");
199
+ }
200
+ if (filteredPreferredFrameRates.length) {
201
+ rationale.push("Preferred XR frame rates were filtered against runtime-supported values.");
202
+ } else {
203
+ rationale.push("XR target frame rate defaults to the highest available runtime target.");
204
+ }
205
+ return Object.freeze({
206
+ ...capabilities,
207
+ preferredFrameRates: Object.freeze([...derivedPreferredFrameRates]),
208
+ targetFrameRate,
209
+ targetFrameTimeMs: 1e3 / targetFrameRate,
210
+ workerBudget: Object.freeze({
211
+ queueClass: xrWorkerQueueClass,
212
+ schedulerMode: xrWorkerSchedulerMode,
213
+ profile: getWorkerBudgetProfileForMode(mode)
214
+ }),
215
+ rationale: Object.freeze(rationale)
216
+ });
217
+ }
218
+ async function updateXrTargetFrameRate(session, frameRate) {
219
+ if (!session || typeof session !== "object") {
220
+ throw new Error("XR session is required to update target frame rate.");
221
+ }
222
+ const requestedFrameRate = readPositiveNumber(frameRate);
223
+ if (!requestedFrameRate) {
224
+ throw new Error("XR target frame rate must be a finite number greater than zero.");
225
+ }
226
+ if (typeof session.updateTargetFrameRate !== "function") {
227
+ throw new Error("XR session does not support updateTargetFrameRate(frameRate).");
228
+ }
229
+ const supportedFrameRates = normalizeFrameRates(session.supportedFrameRates);
230
+ if (supportedFrameRates.length > 0 && !supportedFrameRates.includes(requestedFrameRate)) {
231
+ throw new Error(
232
+ `XR target frame rate ${requestedFrameRate}Hz is not supported by the active session.`
233
+ );
234
+ }
235
+ await session.updateTargetFrameRate(requestedFrameRate);
236
+ return requestedFrameRate;
237
+ }
110
238
  function createXrStore(initialState = {}) {
111
239
  const listeners = /* @__PURE__ */ new Set();
112
240
  let state = {
@@ -115,6 +243,11 @@ function createXrStore(initialState = {}) {
115
243
  isEntering: false,
116
244
  lastError: null,
117
245
  supportedModes: {},
246
+ currentFrameRate: null,
247
+ targetFrameRate: null,
248
+ supportedFrameRates: [],
249
+ canUpdateTargetFrameRate: false,
250
+ workerBudgetProfile: null,
118
251
  ...initialState
119
252
  };
120
253
  const notify = () => {
@@ -146,6 +279,11 @@ function createXrStore(initialState = {}) {
146
279
  isEntering: false,
147
280
  lastError: null,
148
281
  supportedModes: {},
282
+ currentFrameRate: null,
283
+ targetFrameRate: null,
284
+ supportedFrameRates: [],
285
+ canUpdateTargetFrameRate: false,
286
+ workerBudgetProfile: null,
149
287
  ...initialState
150
288
  };
151
289
  notify();
@@ -175,7 +313,12 @@ function createXrManager(options = {}) {
175
313
  store.set({
176
314
  activeSession: null,
177
315
  mode: null,
178
- isEntering: false
316
+ isEntering: false,
317
+ currentFrameRate: null,
318
+ targetFrameRate: null,
319
+ supportedFrameRates: [],
320
+ canUpdateTargetFrameRate: false,
321
+ workerBudgetProfile: null
179
322
  });
180
323
  if (typeof onSessionEnd === "function") {
181
324
  onSessionEnd();
@@ -190,6 +333,46 @@ function createXrManager(options = {}) {
190
333
  };
191
334
  const getState = () => store.getSnapshot();
192
335
  const subscribe = (listener) => store.subscribe(listener);
336
+ const getFrameRateCapabilities = (options2 = {}) => {
337
+ const state = store.getSnapshot();
338
+ return readXrFrameRateCapabilities(
339
+ options2.session ?? state.activeSession,
340
+ {
341
+ mode: options2.mode ?? state.mode ?? defaultMode,
342
+ fallbackFrameRates: options2.fallbackFrameRates ?? state.supportedFrameRates,
343
+ defaultFrameRate: options2.defaultFrameRate ?? state.targetFrameRate ?? state.currentFrameRate ?? void 0
344
+ }
345
+ );
346
+ };
347
+ const getPerformanceHint = (options2 = {}) => {
348
+ const state = store.getSnapshot();
349
+ return createXrPerformanceHint({
350
+ session: options2.session ?? state.activeSession,
351
+ mode: options2.mode ?? state.mode ?? defaultMode,
352
+ preferredFrameRates: options2.preferredFrameRates ?? (state.targetFrameRate ? [state.targetFrameRate] : []),
353
+ fallbackFrameRates: options2.fallbackFrameRates ?? state.supportedFrameRates,
354
+ defaultFrameRate: options2.defaultFrameRate ?? state.targetFrameRate ?? state.currentFrameRate ?? void 0
355
+ });
356
+ };
357
+ const syncSessionPerformanceState = (session, mode, preferredFrameRates = []) => {
358
+ const hint = createXrPerformanceHint({
359
+ session,
360
+ mode,
361
+ preferredFrameRates
362
+ });
363
+ store.set({
364
+ activeSession: session,
365
+ mode,
366
+ isEntering: false,
367
+ lastError: null,
368
+ currentFrameRate: hint.currentFrameRate,
369
+ targetFrameRate: hint.targetFrameRate,
370
+ supportedFrameRates: hint.supportedFrameRates,
371
+ canUpdateTargetFrameRate: hint.canUpdateTargetFrameRate,
372
+ workerBudgetProfile: hint.workerBudget.profile
373
+ });
374
+ return hint;
375
+ };
193
376
  const probeSupport = async (modes = [defaultMode]) => {
194
377
  const supportedModes = {};
195
378
  for (const mode of modes) {
@@ -216,12 +399,7 @@ function createXrManager(options = {}) {
216
399
  navigator: navigatorOverride
217
400
  });
218
401
  attachSessionEndHandler(session);
219
- store.set({
220
- activeSession: session,
221
- mode,
222
- isEntering: false,
223
- lastError: null
224
- });
402
+ syncSessionPerformanceState(session, mode);
225
403
  if (typeof onSessionStart === "function") {
226
404
  onSessionStart(session, mode);
227
405
  }
@@ -238,6 +416,29 @@ function createXrManager(options = {}) {
238
416
  const enterVr = async (sessionInit = {}) => {
239
417
  return enterSession("immersive-vr", sessionInit);
240
418
  };
419
+ const setTargetFrameRate = async (frameRate) => {
420
+ const state = store.getSnapshot();
421
+ const activeSession = state.activeSession;
422
+ if (!activeSession) {
423
+ throw new Error(
424
+ "Cannot update XR target frame rate without an active XR session."
425
+ );
426
+ }
427
+ try {
428
+ const appliedFrameRate = await updateXrTargetFrameRate(
429
+ activeSession,
430
+ frameRate
431
+ );
432
+ syncSessionPerformanceState(activeSession, state.mode ?? defaultMode, [
433
+ appliedFrameRate
434
+ ]);
435
+ return appliedFrameRate;
436
+ } catch (error) {
437
+ const message = error instanceof Error ? error.message : String(error ?? "Unknown XR error");
438
+ store.set({ lastError: message });
439
+ throw error;
440
+ }
441
+ };
241
442
  const exitSession = async () => {
242
443
  const { activeSession } = store.getSnapshot();
243
444
  if (!activeSession) {
@@ -261,8 +462,11 @@ function createXrManager(options = {}) {
261
462
  getState,
262
463
  subscribe,
263
464
  probeSupport,
465
+ getFrameRateCapabilities,
466
+ getPerformanceHint,
264
467
  enterSession,
265
468
  enterVr,
469
+ setTargetFrameRate,
266
470
  exitSession,
267
471
  dispose
268
472
  };
@@ -270,12 +474,18 @@ function createXrManager(options = {}) {
270
474
  var defaultVrSessionInit = DEFAULT_VR_SESSION_INIT;
271
475
  export {
272
476
  createXrManager,
477
+ createXrPerformanceHint,
273
478
  createXrStore,
274
479
  defaultVrSessionInit,
480
+ defaultXrWorkerBudgetProfile,
275
481
  isXrModeSupported,
276
482
  mergeXrSessionInit,
483
+ readXrFrameRateCapabilities,
277
484
  requestXrSession,
485
+ updateXrTargetFrameRate,
278
486
  xrReferenceSpaceTypes,
279
- xrSessionModes
487
+ xrSessionModes,
488
+ xrWorkerQueueClass,
489
+ xrWorkerSchedulerMode
280
490
  };
281
491
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.js"],"sourcesContent":["const DEFAULT_VR_SESSION_INIT = Object.freeze({\n requiredFeatures: [\"local-floor\"],\n optionalFeatures: [\"bounded-floor\", \"hand-tracking\", \"layers\"],\n});\n\nexport const xrSessionModes = Object.freeze([\n \"inline\",\n \"immersive-vr\",\n \"immersive-ar\",\n]);\n\nexport const xrReferenceSpaceTypes = Object.freeze([\n \"viewer\",\n \"local\",\n \"local-floor\",\n \"bounded-floor\",\n \"unbounded\",\n]);\n\nfunction toStringArray(values) {\n if (!Array.isArray(values)) {\n return [];\n }\n return values\n .filter((value) => typeof value === \"string\")\n .map((value) => value.trim())\n .filter(Boolean);\n}\n\nfunction dedupeStrings(values) {\n return [...new Set(toStringArray(values))];\n}\n\nfunction readNavigator(navigatorOverride) {\n const currentNavigator = navigatorOverride ?? globalThis.navigator;\n if (!currentNavigator || typeof currentNavigator !== \"object\") {\n throw new Error(\n \"WebXR navigator unavailable. Provide a browser navigator with navigator.xr.\"\n );\n }\n return currentNavigator;\n}\n\nfunction readXrSystem(navigatorOverride) {\n const currentNavigator = readNavigator(navigatorOverride);\n const xr = currentNavigator.xr;\n if (!xr || typeof xr !== \"object\") {\n throw new Error(\n \"WebXR runtime unavailable. navigator.xr is missing in this environment.\"\n );\n }\n return xr;\n}\n\nfunction assertSessionMode(mode) {\n if (!xrSessionModes.includes(mode)) {\n const available = xrSessionModes.join(\", \");\n throw new Error(\n `Unknown XR session mode \"${mode}\". Available modes: ${available}.`\n );\n }\n}\n\nexport function mergeXrSessionInit(base = {}, override = {}) {\n const requiredFeatures = dedupeStrings([\n ...toStringArray(base.requiredFeatures),\n ...toStringArray(override.requiredFeatures),\n ]);\n\n const optionalFeatures = dedupeStrings([\n ...toStringArray(base.optionalFeatures),\n ...toStringArray(override.optionalFeatures),\n ]);\n\n const merged = {\n ...base,\n ...override,\n requiredFeatures,\n optionalFeatures,\n };\n\n if (requiredFeatures.length === 0) {\n delete merged.requiredFeatures;\n }\n\n if (optionalFeatures.length === 0) {\n delete merged.optionalFeatures;\n }\n\n return merged;\n}\n\nexport async function isXrModeSupported(\n mode = \"immersive-vr\",\n options = {}\n) {\n assertSessionMode(mode);\n\n const { navigator: navigatorOverride } = options;\n let xr;\n try {\n xr = readXrSystem(navigatorOverride);\n } catch {\n return false;\n }\n\n if (typeof xr.isSessionSupported !== \"function\") {\n return false;\n }\n\n try {\n return Boolean(await xr.isSessionSupported(mode));\n } catch {\n return false;\n }\n}\n\nexport async function requestXrSession(options = {}) {\n const {\n mode = \"immersive-vr\",\n sessionInit = {},\n baseSessionInit = DEFAULT_VR_SESSION_INIT,\n navigator: navigatorOverride,\n } = options;\n\n assertSessionMode(mode);\n\n const xr = readXrSystem(navigatorOverride);\n\n if (typeof xr.requestSession !== \"function\") {\n throw new Error(\"WebXR requestSession API unavailable.\");\n }\n\n const init = mergeXrSessionInit(baseSessionInit, sessionInit);\n return xr.requestSession(mode, init);\n}\n\nexport function createXrStore(initialState = {}) {\n const listeners = new Set();\n let state = {\n activeSession: null,\n mode: null,\n isEntering: false,\n lastError: null,\n supportedModes: {},\n ...initialState,\n };\n\n const notify = () => {\n for (const listener of listeners) {\n listener(state);\n }\n };\n\n return {\n getSnapshot() {\n return state;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n set(partialState) {\n state = {\n ...state,\n ...partialState,\n };\n notify();\n },\n reset() {\n state = {\n activeSession: null,\n mode: null,\n isEntering: false,\n lastError: null,\n supportedModes: {},\n ...initialState,\n };\n notify();\n },\n };\n}\n\nexport function createXrManager(options = {}) {\n const {\n navigator: navigatorOverride,\n defaultMode = \"immersive-vr\",\n baseSessionInit = DEFAULT_VR_SESSION_INIT,\n onSessionStart,\n onSessionEnd,\n } = options;\n\n assertSessionMode(defaultMode);\n\n const store = createXrStore();\n let activeSessionEndHandler = null;\n\n const detachSessionEndHandler = () => {\n const { activeSession } = store.getSnapshot();\n if (\n activeSession &&\n activeSessionEndHandler &&\n typeof activeSession.removeEventListener === \"function\"\n ) {\n activeSession.removeEventListener(\"end\", activeSessionEndHandler);\n }\n activeSessionEndHandler = null;\n };\n\n const handleSessionEnded = () => {\n detachSessionEndHandler();\n store.set({\n activeSession: null,\n mode: null,\n isEntering: false,\n });\n if (typeof onSessionEnd === \"function\") {\n onSessionEnd();\n }\n };\n\n const attachSessionEndHandler = (session) => {\n if (!session || typeof session.addEventListener !== \"function\") {\n return;\n }\n activeSessionEndHandler = handleSessionEnded;\n session.addEventListener(\"end\", activeSessionEndHandler);\n };\n\n const getState = () => store.getSnapshot();\n\n const subscribe = (listener) => store.subscribe(listener);\n\n const probeSupport = async (modes = [defaultMode]) => {\n const supportedModes = {};\n for (const mode of modes) {\n assertSessionMode(mode);\n supportedModes[mode] = await isXrModeSupported(mode, {\n navigator: navigatorOverride,\n });\n }\n store.set({ supportedModes });\n return supportedModes;\n };\n\n const enterSession = async (mode = defaultMode, sessionInit = {}) => {\n assertSessionMode(mode);\n\n const existing = store.getSnapshot().activeSession;\n if (existing) {\n return existing;\n }\n\n store.set({ isEntering: true, lastError: null });\n\n try {\n const session = await requestXrSession({\n mode,\n sessionInit,\n baseSessionInit,\n navigator: navigatorOverride,\n });\n\n attachSessionEndHandler(session);\n\n store.set({\n activeSession: session,\n mode,\n isEntering: false,\n lastError: null,\n });\n\n if (typeof onSessionStart === \"function\") {\n onSessionStart(session, mode);\n }\n\n return session;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error ?? \"Unknown XR error\");\n store.set({\n isEntering: false,\n lastError: message,\n });\n throw error;\n }\n };\n\n const enterVr = async (sessionInit = {}) => {\n return enterSession(\"immersive-vr\", sessionInit);\n };\n\n const exitSession = async () => {\n const { activeSession } = store.getSnapshot();\n if (!activeSession) {\n return false;\n }\n\n if (typeof activeSession.end === \"function\") {\n await activeSession.end();\n }\n\n // Fallback for test fakes or runtimes that do not emit an end event.\n if (store.getSnapshot().activeSession) {\n handleSessionEnded();\n }\n\n return true;\n };\n\n const dispose = async () => {\n await exitSession();\n detachSessionEndHandler();\n store.reset();\n };\n\n return {\n store,\n getState,\n subscribe,\n probeSupport,\n enterSession,\n enterVr,\n exitSession,\n dispose,\n };\n}\n\nexport const defaultVrSessionInit = DEFAULT_VR_SESSION_INIT;\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.js"],"sourcesContent":["const DEFAULT_VR_SESSION_INIT = Object.freeze({\n requiredFeatures: [\"local-floor\"],\n optionalFeatures: [\"bounded-floor\", \"hand-tracking\", \"layers\"],\n});\nconst DEFAULT_MODE_FRAME_RATES = Object.freeze({\n inline: 60,\n \"immersive-vr\": 90,\n \"immersive-ar\": 72,\n});\n\nexport const xrSessionModes = Object.freeze([\n \"inline\",\n \"immersive-vr\",\n \"immersive-ar\",\n]);\n\nexport const xrReferenceSpaceTypes = Object.freeze([\n \"viewer\",\n \"local\",\n \"local-floor\",\n \"bounded-floor\",\n \"unbounded\",\n]);\nexport const xrWorkerQueueClass = \"render\";\nexport const xrWorkerSchedulerMode = \"dag\";\nexport const defaultXrWorkerBudgetProfile = \"xr\";\n\nfunction toStringArray(values) {\n if (!Array.isArray(values)) {\n return [];\n }\n return values\n .filter((value) => typeof value === \"string\")\n .map((value) => value.trim())\n .filter(Boolean);\n}\n\nfunction dedupeStrings(values) {\n return [...new Set(toStringArray(values))];\n}\n\nfunction readPositiveNumber(value) {\n return typeof value === \"number\" && Number.isFinite(value) && value > 0\n ? value\n : null;\n}\n\nfunction normalizeFrameRates(values) {\n if (!values || typeof values === \"string\") {\n return Object.freeze([]);\n }\n\n let collected;\n try {\n collected = Array.from(values);\n } catch {\n return Object.freeze([]);\n }\n\n return Object.freeze(\n [...new Set(collected.map((value) => Number(value)).filter((value) => Number.isFinite(value) && value > 0))].sort(\n (left, right) => right - left\n )\n );\n}\n\nfunction getDefaultFrameRateForMode(mode) {\n return DEFAULT_MODE_FRAME_RATES[mode] ?? DEFAULT_MODE_FRAME_RATES[\"immersive-vr\"];\n}\n\nfunction getWorkerBudgetProfileForMode(mode) {\n return mode === \"inline\" ? \"realtime\" : defaultXrWorkerBudgetProfile;\n}\n\nfunction readNavigator(navigatorOverride) {\n const currentNavigator = navigatorOverride ?? globalThis.navigator;\n if (!currentNavigator || typeof currentNavigator !== \"object\") {\n throw new Error(\n \"WebXR navigator unavailable. Provide a browser navigator with navigator.xr.\"\n );\n }\n return currentNavigator;\n}\n\nfunction readXrSystem(navigatorOverride) {\n const currentNavigator = readNavigator(navigatorOverride);\n const xr = currentNavigator.xr;\n if (!xr || typeof xr !== \"object\") {\n throw new Error(\n \"WebXR runtime unavailable. navigator.xr is missing in this environment.\"\n );\n }\n return xr;\n}\n\nfunction assertSessionMode(mode) {\n if (!xrSessionModes.includes(mode)) {\n const available = xrSessionModes.join(\", \");\n throw new Error(\n `Unknown XR session mode \"${mode}\". Available modes: ${available}.`\n );\n }\n}\n\nexport function mergeXrSessionInit(base = {}, override = {}) {\n const requiredFeatures = dedupeStrings([\n ...toStringArray(base.requiredFeatures),\n ...toStringArray(override.requiredFeatures),\n ]);\n\n const optionalFeatures = dedupeStrings([\n ...toStringArray(base.optionalFeatures),\n ...toStringArray(override.optionalFeatures),\n ]);\n\n const merged = {\n ...base,\n ...override,\n requiredFeatures,\n optionalFeatures,\n };\n\n if (requiredFeatures.length === 0) {\n delete merged.requiredFeatures;\n }\n\n if (optionalFeatures.length === 0) {\n delete merged.optionalFeatures;\n }\n\n return merged;\n}\n\nexport async function isXrModeSupported(\n mode = \"immersive-vr\",\n options = {}\n) {\n assertSessionMode(mode);\n\n const { navigator: navigatorOverride } = options;\n let xr;\n try {\n xr = readXrSystem(navigatorOverride);\n } catch {\n return false;\n }\n\n if (typeof xr.isSessionSupported !== \"function\") {\n return false;\n }\n\n try {\n return Boolean(await xr.isSessionSupported(mode));\n } catch {\n return false;\n }\n}\n\nexport async function requestXrSession(options = {}) {\n const {\n mode = \"immersive-vr\",\n sessionInit = {},\n baseSessionInit = DEFAULT_VR_SESSION_INIT,\n navigator: navigatorOverride,\n } = options;\n\n assertSessionMode(mode);\n\n const xr = readXrSystem(navigatorOverride);\n\n if (typeof xr.requestSession !== \"function\") {\n throw new Error(\"WebXR requestSession API unavailable.\");\n }\n\n const init = mergeXrSessionInit(baseSessionInit, sessionInit);\n return xr.requestSession(mode, init);\n}\n\nexport function readXrFrameRateCapabilities(session, options = {}) {\n const {\n mode = \"immersive-vr\",\n fallbackFrameRates = [],\n defaultFrameRate,\n } = options;\n\n assertSessionMode(mode);\n\n const sessionFrameRate = readPositiveNumber(session?.frameRate);\n const supportedFrameRates = normalizeFrameRates(session?.supportedFrameRates);\n const fallbackRates = normalizeFrameRates(fallbackFrameRates);\n const mergedSupported = supportedFrameRates.length\n ? [...supportedFrameRates]\n : [...fallbackRates];\n\n if (sessionFrameRate && !mergedSupported.includes(sessionFrameRate)) {\n mergedSupported.push(sessionFrameRate);\n mergedSupported.sort((left, right) => right - left);\n }\n\n const refreshRateHz =\n sessionFrameRate ??\n mergedSupported[0] ??\n readPositiveNumber(defaultFrameRate) ??\n getDefaultFrameRateForMode(mode);\n\n return Object.freeze({\n mode,\n currentFrameRate: sessionFrameRate,\n supportedFrameRates: Object.freeze(mergedSupported),\n refreshRateHz,\n canUpdateTargetFrameRate:\n Boolean(session) && typeof session.updateTargetFrameRate === \"function\",\n });\n}\n\nexport function createXrPerformanceHint(options = {}) {\n const {\n session = null,\n mode = \"immersive-vr\",\n preferredFrameRates = [],\n fallbackFrameRates = [],\n defaultFrameRate,\n } = options;\n\n const capabilities = readXrFrameRateCapabilities(session, {\n mode,\n fallbackFrameRates,\n defaultFrameRate,\n });\n\n const filteredPreferredFrameRates = normalizeFrameRates(preferredFrameRates).filter(\n (frameRate) =>\n capabilities.supportedFrameRates.length === 0 ||\n capabilities.supportedFrameRates.includes(frameRate)\n );\n\n const derivedPreferredFrameRates = filteredPreferredFrameRates.length\n ? filteredPreferredFrameRates\n : capabilities.supportedFrameRates.length\n ? capabilities.supportedFrameRates\n : Object.freeze([capabilities.refreshRateHz]);\n const targetFrameRate =\n derivedPreferredFrameRates[0] ?? capabilities.refreshRateHz;\n const rationale = [];\n\n if (capabilities.currentFrameRate) {\n rationale.push(\n `XR session reports a current frame rate of ${capabilities.currentFrameRate}Hz.`\n );\n } else {\n rationale.push(\"XR session does not expose a current frame rate; using defaults.\");\n }\n\n if (capabilities.supportedFrameRates.length) {\n rationale.push(\n `XR runtime exposes supported frame rates: ${capabilities.supportedFrameRates.join(\", \")}Hz.`\n );\n } else {\n rationale.push(\"XR runtime does not expose supported frame rates; using fallback targets.\");\n }\n\n if (filteredPreferredFrameRates.length) {\n rationale.push(\"Preferred XR frame rates were filtered against runtime-supported values.\");\n } else {\n rationale.push(\"XR target frame rate defaults to the highest available runtime target.\");\n }\n\n return Object.freeze({\n ...capabilities,\n preferredFrameRates: Object.freeze([...derivedPreferredFrameRates]),\n targetFrameRate,\n targetFrameTimeMs: 1000 / targetFrameRate,\n workerBudget: Object.freeze({\n queueClass: xrWorkerQueueClass,\n schedulerMode: xrWorkerSchedulerMode,\n profile: getWorkerBudgetProfileForMode(mode),\n }),\n rationale: Object.freeze(rationale),\n });\n}\n\nexport async function updateXrTargetFrameRate(session, frameRate) {\n if (!session || typeof session !== \"object\") {\n throw new Error(\"XR session is required to update target frame rate.\");\n }\n\n const requestedFrameRate = readPositiveNumber(frameRate);\n if (!requestedFrameRate) {\n throw new Error(\"XR target frame rate must be a finite number greater than zero.\");\n }\n\n if (typeof session.updateTargetFrameRate !== \"function\") {\n throw new Error(\"XR session does not support updateTargetFrameRate(frameRate).\");\n }\n\n const supportedFrameRates = normalizeFrameRates(session.supportedFrameRates);\n if (\n supportedFrameRates.length > 0 &&\n !supportedFrameRates.includes(requestedFrameRate)\n ) {\n throw new Error(\n `XR target frame rate ${requestedFrameRate}Hz is not supported by the active session.`\n );\n }\n\n await session.updateTargetFrameRate(requestedFrameRate);\n return requestedFrameRate;\n}\n\nexport function createXrStore(initialState = {}) {\n const listeners = new Set();\n let state = {\n activeSession: null,\n mode: null,\n isEntering: false,\n lastError: null,\n supportedModes: {},\n currentFrameRate: null,\n targetFrameRate: null,\n supportedFrameRates: [],\n canUpdateTargetFrameRate: false,\n workerBudgetProfile: null,\n ...initialState,\n };\n\n const notify = () => {\n for (const listener of listeners) {\n listener(state);\n }\n };\n\n return {\n getSnapshot() {\n return state;\n },\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n set(partialState) {\n state = {\n ...state,\n ...partialState,\n };\n notify();\n },\n reset() {\n state = {\n activeSession: null,\n mode: null,\n isEntering: false,\n lastError: null,\n supportedModes: {},\n currentFrameRate: null,\n targetFrameRate: null,\n supportedFrameRates: [],\n canUpdateTargetFrameRate: false,\n workerBudgetProfile: null,\n ...initialState,\n };\n notify();\n },\n };\n}\n\nexport function createXrManager(options = {}) {\n const {\n navigator: navigatorOverride,\n defaultMode = \"immersive-vr\",\n baseSessionInit = DEFAULT_VR_SESSION_INIT,\n onSessionStart,\n onSessionEnd,\n } = options;\n\n assertSessionMode(defaultMode);\n\n const store = createXrStore();\n let activeSessionEndHandler = null;\n\n const detachSessionEndHandler = () => {\n const { activeSession } = store.getSnapshot();\n if (\n activeSession &&\n activeSessionEndHandler &&\n typeof activeSession.removeEventListener === \"function\"\n ) {\n activeSession.removeEventListener(\"end\", activeSessionEndHandler);\n }\n activeSessionEndHandler = null;\n };\n\n const handleSessionEnded = () => {\n detachSessionEndHandler();\n store.set({\n activeSession: null,\n mode: null,\n isEntering: false,\n currentFrameRate: null,\n targetFrameRate: null,\n supportedFrameRates: [],\n canUpdateTargetFrameRate: false,\n workerBudgetProfile: null,\n });\n if (typeof onSessionEnd === \"function\") {\n onSessionEnd();\n }\n };\n\n const attachSessionEndHandler = (session) => {\n if (!session || typeof session.addEventListener !== \"function\") {\n return;\n }\n activeSessionEndHandler = handleSessionEnded;\n session.addEventListener(\"end\", activeSessionEndHandler);\n };\n\n const getState = () => store.getSnapshot();\n\n const subscribe = (listener) => store.subscribe(listener);\n\n const getFrameRateCapabilities = (options = {}) => {\n const state = store.getSnapshot();\n return readXrFrameRateCapabilities(\n options.session ?? state.activeSession,\n {\n mode: options.mode ?? state.mode ?? defaultMode,\n fallbackFrameRates:\n options.fallbackFrameRates ?? state.supportedFrameRates,\n defaultFrameRate:\n options.defaultFrameRate ??\n state.targetFrameRate ??\n state.currentFrameRate ??\n undefined,\n }\n );\n };\n\n const getPerformanceHint = (options = {}) => {\n const state = store.getSnapshot();\n return createXrPerformanceHint({\n session: options.session ?? state.activeSession,\n mode: options.mode ?? state.mode ?? defaultMode,\n preferredFrameRates:\n options.preferredFrameRates ??\n (state.targetFrameRate ? [state.targetFrameRate] : []),\n fallbackFrameRates:\n options.fallbackFrameRates ?? state.supportedFrameRates,\n defaultFrameRate:\n options.defaultFrameRate ??\n state.targetFrameRate ??\n state.currentFrameRate ??\n undefined,\n });\n };\n\n const syncSessionPerformanceState = (session, mode, preferredFrameRates = []) => {\n const hint = createXrPerformanceHint({\n session,\n mode,\n preferredFrameRates,\n });\n\n store.set({\n activeSession: session,\n mode,\n isEntering: false,\n lastError: null,\n currentFrameRate: hint.currentFrameRate,\n targetFrameRate: hint.targetFrameRate,\n supportedFrameRates: hint.supportedFrameRates,\n canUpdateTargetFrameRate: hint.canUpdateTargetFrameRate,\n workerBudgetProfile: hint.workerBudget.profile,\n });\n\n return hint;\n };\n\n const probeSupport = async (modes = [defaultMode]) => {\n const supportedModes = {};\n for (const mode of modes) {\n assertSessionMode(mode);\n supportedModes[mode] = await isXrModeSupported(mode, {\n navigator: navigatorOverride,\n });\n }\n store.set({ supportedModes });\n return supportedModes;\n };\n\n const enterSession = async (mode = defaultMode, sessionInit = {}) => {\n assertSessionMode(mode);\n\n const existing = store.getSnapshot().activeSession;\n if (existing) {\n return existing;\n }\n\n store.set({ isEntering: true, lastError: null });\n\n try {\n const session = await requestXrSession({\n mode,\n sessionInit,\n baseSessionInit,\n navigator: navigatorOverride,\n });\n\n attachSessionEndHandler(session);\n syncSessionPerformanceState(session, mode);\n\n if (typeof onSessionStart === \"function\") {\n onSessionStart(session, mode);\n }\n\n return session;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error ?? \"Unknown XR error\");\n store.set({\n isEntering: false,\n lastError: message,\n });\n throw error;\n }\n };\n\n const enterVr = async (sessionInit = {}) => {\n return enterSession(\"immersive-vr\", sessionInit);\n };\n\n const setTargetFrameRate = async (frameRate) => {\n const state = store.getSnapshot();\n const activeSession = state.activeSession;\n if (!activeSession) {\n throw new Error(\n \"Cannot update XR target frame rate without an active XR session.\"\n );\n }\n\n try {\n const appliedFrameRate = await updateXrTargetFrameRate(\n activeSession,\n frameRate\n );\n syncSessionPerformanceState(activeSession, state.mode ?? defaultMode, [\n appliedFrameRate,\n ]);\n return appliedFrameRate;\n } catch (error) {\n const message =\n error instanceof Error ? error.message : String(error ?? \"Unknown XR error\");\n store.set({ lastError: message });\n throw error;\n }\n };\n\n const exitSession = async () => {\n const { activeSession } = store.getSnapshot();\n if (!activeSession) {\n return false;\n }\n\n if (typeof activeSession.end === \"function\") {\n await activeSession.end();\n }\n\n // Fallback for test fakes or runtimes that do not emit an end event.\n if (store.getSnapshot().activeSession) {\n handleSessionEnded();\n }\n\n return true;\n };\n\n const dispose = async () => {\n await exitSession();\n detachSessionEndHandler();\n store.reset();\n };\n\n return {\n store,\n getState,\n subscribe,\n probeSupport,\n getFrameRateCapabilities,\n getPerformanceHint,\n enterSession,\n enterVr,\n setTargetFrameRate,\n exitSession,\n dispose,\n };\n}\n\nexport const defaultVrSessionInit = DEFAULT_VR_SESSION_INIT;\n"],"mappings":";AAAA,IAAM,0BAA0B,OAAO,OAAO;AAAA,EAC5C,kBAAkB,CAAC,aAAa;AAAA,EAChC,kBAAkB,CAAC,iBAAiB,iBAAiB,QAAQ;AAC/D,CAAC;AACD,IAAM,2BAA2B,OAAO,OAAO;AAAA,EAC7C,QAAQ;AAAA,EACR,gBAAgB;AAAA,EAChB,gBAAgB;AAClB,CAAC;AAEM,IAAM,iBAAiB,OAAO,OAAO;AAAA,EAC1C;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,wBAAwB,OAAO,OAAO;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AACM,IAAM,qBAAqB;AAC3B,IAAM,wBAAwB;AAC9B,IAAM,+BAA+B;AAE5C,SAAS,cAAc,QAAQ;AAC7B,MAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,WAAO,CAAC;AAAA,EACV;AACA,SAAO,OACJ,OAAO,CAAC,UAAU,OAAO,UAAU,QAAQ,EAC3C,IAAI,CAAC,UAAU,MAAM,KAAK,CAAC,EAC3B,OAAO,OAAO;AACnB;AAEA,SAAS,cAAc,QAAQ;AAC7B,SAAO,CAAC,GAAG,IAAI,IAAI,cAAc,MAAM,CAAC,CAAC;AAC3C;AAEA,SAAS,mBAAmB,OAAO;AACjC,SAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,IAClE,QACA;AACN;AAEA,SAAS,oBAAoB,QAAQ;AACnC,MAAI,CAAC,UAAU,OAAO,WAAW,UAAU;AACzC,WAAO,OAAO,OAAO,CAAC,CAAC;AAAA,EACzB;AAEA,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,KAAK,MAAM;AAAA,EAC/B,QAAQ;AACN,WAAO,OAAO,OAAO,CAAC,CAAC;AAAA,EACzB;AAEA,SAAO,OAAO;AAAA,IACZ,CAAC,GAAG,IAAI,IAAI,UAAU,IAAI,CAAC,UAAU,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,OAAO,SAAS,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE;AAAA,MAC3G,CAAC,MAAM,UAAU,QAAQ;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,2BAA2B,MAAM;AACxC,SAAO,yBAAyB,IAAI,KAAK,yBAAyB,cAAc;AAClF;AAEA,SAAS,8BAA8B,MAAM;AAC3C,SAAO,SAAS,WAAW,aAAa;AAC1C;AAEA,SAAS,cAAc,mBAAmB;AACxC,QAAM,mBAAmB,qBAAqB,WAAW;AACzD,MAAI,CAAC,oBAAoB,OAAO,qBAAqB,UAAU;AAC7D,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,aAAa,mBAAmB;AACvC,QAAM,mBAAmB,cAAc,iBAAiB;AACxD,QAAM,KAAK,iBAAiB;AAC5B,MAAI,CAAC,MAAM,OAAO,OAAO,UAAU;AACjC,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,kBAAkB,MAAM;AAC/B,MAAI,CAAC,eAAe,SAAS,IAAI,GAAG;AAClC,UAAM,YAAY,eAAe,KAAK,IAAI;AAC1C,UAAM,IAAI;AAAA,MACR,4BAA4B,IAAI,uBAAuB,SAAS;AAAA,IAClE;AAAA,EACF;AACF;AAEO,SAAS,mBAAmB,OAAO,CAAC,GAAG,WAAW,CAAC,GAAG;AAC3D,QAAM,mBAAmB,cAAc;AAAA,IACrC,GAAG,cAAc,KAAK,gBAAgB;AAAA,IACtC,GAAG,cAAc,SAAS,gBAAgB;AAAA,EAC5C,CAAC;AAED,QAAM,mBAAmB,cAAc;AAAA,IACrC,GAAG,cAAc,KAAK,gBAAgB;AAAA,IACtC,GAAG,cAAc,SAAS,gBAAgB;AAAA,EAC5C,CAAC;AAED,QAAM,SAAS;AAAA,IACb,GAAG;AAAA,IACH,GAAG;AAAA,IACH;AAAA,IACA;AAAA,EACF;AAEA,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO,OAAO;AAAA,EAChB;AAEA,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO;AACT;AAEA,eAAsB,kBACpB,OAAO,gBACP,UAAU,CAAC,GACX;AACA,oBAAkB,IAAI;AAEtB,QAAM,EAAE,WAAW,kBAAkB,IAAI;AACzC,MAAI;AACJ,MAAI;AACF,SAAK,aAAa,iBAAiB;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,GAAG,uBAAuB,YAAY;AAC/C,WAAO;AAAA,EACT;AAEA,MAAI;AACF,WAAO,QAAQ,MAAM,GAAG,mBAAmB,IAAI,CAAC;AAAA,EAClD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,eAAsB,iBAAiB,UAAU,CAAC,GAAG;AACnD,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,cAAc,CAAC;AAAA,IACf,kBAAkB;AAAA,IAClB,WAAW;AAAA,EACb,IAAI;AAEJ,oBAAkB,IAAI;AAEtB,QAAM,KAAK,aAAa,iBAAiB;AAEzC,MAAI,OAAO,GAAG,mBAAmB,YAAY;AAC3C,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAEA,QAAM,OAAO,mBAAmB,iBAAiB,WAAW;AAC5D,SAAO,GAAG,eAAe,MAAM,IAAI;AACrC;AAEO,SAAS,4BAA4B,SAAS,UAAU,CAAC,GAAG;AACjE,QAAM;AAAA,IACJ,OAAO;AAAA,IACP,qBAAqB,CAAC;AAAA,IACtB;AAAA,EACF,IAAI;AAEJ,oBAAkB,IAAI;AAEtB,QAAM,mBAAmB,mBAAmB,SAAS,SAAS;AAC9D,QAAM,sBAAsB,oBAAoB,SAAS,mBAAmB;AAC5E,QAAM,gBAAgB,oBAAoB,kBAAkB;AAC5D,QAAM,kBAAkB,oBAAoB,SACxC,CAAC,GAAG,mBAAmB,IACvB,CAAC,GAAG,aAAa;AAErB,MAAI,oBAAoB,CAAC,gBAAgB,SAAS,gBAAgB,GAAG;AACnE,oBAAgB,KAAK,gBAAgB;AACrC,oBAAgB,KAAK,CAAC,MAAM,UAAU,QAAQ,IAAI;AAAA,EACpD;AAEA,QAAM,gBACJ,oBACA,gBAAgB,CAAC,KACjB,mBAAmB,gBAAgB,KACnC,2BAA2B,IAAI;AAEjC,SAAO,OAAO,OAAO;AAAA,IACnB;AAAA,IACA,kBAAkB;AAAA,IAClB,qBAAqB,OAAO,OAAO,eAAe;AAAA,IAClD;AAAA,IACA,0BACE,QAAQ,OAAO,KAAK,OAAO,QAAQ,0BAA0B;AAAA,EACjE,CAAC;AACH;AAEO,SAAS,wBAAwB,UAAU,CAAC,GAAG;AACpD,QAAM;AAAA,IACJ,UAAU;AAAA,IACV,OAAO;AAAA,IACP,sBAAsB,CAAC;AAAA,IACvB,qBAAqB,CAAC;AAAA,IACtB;AAAA,EACF,IAAI;AAEJ,QAAM,eAAe,4BAA4B,SAAS;AAAA,IACxD;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,8BAA8B,oBAAoB,mBAAmB,EAAE;AAAA,IAC3E,CAAC,cACC,aAAa,oBAAoB,WAAW,KAC5C,aAAa,oBAAoB,SAAS,SAAS;AAAA,EACvD;AAEA,QAAM,6BAA6B,4BAA4B,SAC3D,8BACA,aAAa,oBAAoB,SAC/B,aAAa,sBACb,OAAO,OAAO,CAAC,aAAa,aAAa,CAAC;AAChD,QAAM,kBACJ,2BAA2B,CAAC,KAAK,aAAa;AAChD,QAAM,YAAY,CAAC;AAEnB,MAAI,aAAa,kBAAkB;AACjC,cAAU;AAAA,MACR,8CAA8C,aAAa,gBAAgB;AAAA,IAC7E;AAAA,EACF,OAAO;AACL,cAAU,KAAK,kEAAkE;AAAA,EACnF;AAEA,MAAI,aAAa,oBAAoB,QAAQ;AAC3C,cAAU;AAAA,MACR,6CAA6C,aAAa,oBAAoB,KAAK,IAAI,CAAC;AAAA,IAC1F;AAAA,EACF,OAAO;AACL,cAAU,KAAK,2EAA2E;AAAA,EAC5F;AAEA,MAAI,4BAA4B,QAAQ;AACtC,cAAU,KAAK,0EAA0E;AAAA,EAC3F,OAAO;AACL,cAAU,KAAK,wEAAwE;AAAA,EACzF;AAEA,SAAO,OAAO,OAAO;AAAA,IACnB,GAAG;AAAA,IACH,qBAAqB,OAAO,OAAO,CAAC,GAAG,0BAA0B,CAAC;AAAA,IAClE;AAAA,IACA,mBAAmB,MAAO;AAAA,IAC1B,cAAc,OAAO,OAAO;AAAA,MAC1B,YAAY;AAAA,MACZ,eAAe;AAAA,MACf,SAAS,8BAA8B,IAAI;AAAA,IAC7C,CAAC;AAAA,IACD,WAAW,OAAO,OAAO,SAAS;AAAA,EACpC,CAAC;AACH;AAEA,eAAsB,wBAAwB,SAAS,WAAW;AAChE,MAAI,CAAC,WAAW,OAAO,YAAY,UAAU;AAC3C,UAAM,IAAI,MAAM,qDAAqD;AAAA,EACvE;AAEA,QAAM,qBAAqB,mBAAmB,SAAS;AACvD,MAAI,CAAC,oBAAoB;AACvB,UAAM,IAAI,MAAM,iEAAiE;AAAA,EACnF;AAEA,MAAI,OAAO,QAAQ,0BAA0B,YAAY;AACvD,UAAM,IAAI,MAAM,+DAA+D;AAAA,EACjF;AAEA,QAAM,sBAAsB,oBAAoB,QAAQ,mBAAmB;AAC3E,MACE,oBAAoB,SAAS,KAC7B,CAAC,oBAAoB,SAAS,kBAAkB,GAChD;AACA,UAAM,IAAI;AAAA,MACR,wBAAwB,kBAAkB;AAAA,IAC5C;AAAA,EACF;AAEA,QAAM,QAAQ,sBAAsB,kBAAkB;AACtD,SAAO;AACT;AAEO,SAAS,cAAc,eAAe,CAAC,GAAG;AAC/C,QAAM,YAAY,oBAAI,IAAI;AAC1B,MAAI,QAAQ;AAAA,IACV,eAAe;AAAA,IACf,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,WAAW;AAAA,IACX,gBAAgB,CAAC;AAAA,IACjB,kBAAkB;AAAA,IAClB,iBAAiB;AAAA,IACjB,qBAAqB,CAAC;AAAA,IACtB,0BAA0B;AAAA,IAC1B,qBAAqB;AAAA,IACrB,GAAG;AAAA,EACL;AAEA,QAAM,SAAS,MAAM;AACnB,eAAW,YAAY,WAAW;AAChC,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAEA,SAAO;AAAA,IACL,cAAc;AACZ,aAAO;AAAA,IACT;AAAA,IACA,UAAU,UAAU;AAClB,gBAAU,IAAI,QAAQ;AACtB,aAAO,MAAM;AACX,kBAAU,OAAO,QAAQ;AAAA,MAC3B;AAAA,IACF;AAAA,IACA,IAAI,cAAc;AAChB,cAAQ;AAAA,QACN,GAAG;AAAA,QACH,GAAG;AAAA,MACL;AACA,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AACN,cAAQ;AAAA,QACN,eAAe;AAAA,QACf,MAAM;AAAA,QACN,YAAY;AAAA,QACZ,WAAW;AAAA,QACX,gBAAgB,CAAC;AAAA,QACjB,kBAAkB;AAAA,QAClB,iBAAiB;AAAA,QACjB,qBAAqB,CAAC;AAAA,QACtB,0BAA0B;AAAA,QAC1B,qBAAqB;AAAA,QACrB,GAAG;AAAA,MACL;AACA,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAEO,SAAS,gBAAgB,UAAU,CAAC,GAAG;AAC5C,QAAM;AAAA,IACJ,WAAW;AAAA,IACX,cAAc;AAAA,IACd,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,oBAAkB,WAAW;AAE7B,QAAM,QAAQ,cAAc;AAC5B,MAAI,0BAA0B;AAE9B,QAAM,0BAA0B,MAAM;AACpC,UAAM,EAAE,cAAc,IAAI,MAAM,YAAY;AAC5C,QACE,iBACA,2BACA,OAAO,cAAc,wBAAwB,YAC7C;AACA,oBAAc,oBAAoB,OAAO,uBAAuB;AAAA,IAClE;AACA,8BAA0B;AAAA,EAC5B;AAEA,QAAM,qBAAqB,MAAM;AAC/B,4BAAwB;AACxB,UAAM,IAAI;AAAA,MACR,eAAe;AAAA,MACf,MAAM;AAAA,MACN,YAAY;AAAA,MACZ,kBAAkB;AAAA,MAClB,iBAAiB;AAAA,MACjB,qBAAqB,CAAC;AAAA,MACtB,0BAA0B;AAAA,MAC1B,qBAAqB;AAAA,IACvB,CAAC;AACD,QAAI,OAAO,iBAAiB,YAAY;AACtC,mBAAa;AAAA,IACf;AAAA,EACF;AAEA,QAAM,0BAA0B,CAAC,YAAY;AAC3C,QAAI,CAAC,WAAW,OAAO,QAAQ,qBAAqB,YAAY;AAC9D;AAAA,IACF;AACA,8BAA0B;AAC1B,YAAQ,iBAAiB,OAAO,uBAAuB;AAAA,EACzD;AAEA,QAAM,WAAW,MAAM,MAAM,YAAY;AAEzC,QAAM,YAAY,CAAC,aAAa,MAAM,UAAU,QAAQ;AAExD,QAAM,2BAA2B,CAACA,WAAU,CAAC,MAAM;AACjD,UAAM,QAAQ,MAAM,YAAY;AAChC,WAAO;AAAA,MACLA,SAAQ,WAAW,MAAM;AAAA,MACzB;AAAA,QACE,MAAMA,SAAQ,QAAQ,MAAM,QAAQ;AAAA,QACpC,oBACEA,SAAQ,sBAAsB,MAAM;AAAA,QACtC,kBACEA,SAAQ,oBACR,MAAM,mBACN,MAAM,oBACN;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AAEA,QAAM,qBAAqB,CAACA,WAAU,CAAC,MAAM;AAC3C,UAAM,QAAQ,MAAM,YAAY;AAChC,WAAO,wBAAwB;AAAA,MAC7B,SAASA,SAAQ,WAAW,MAAM;AAAA,MAClC,MAAMA,SAAQ,QAAQ,MAAM,QAAQ;AAAA,MACpC,qBACEA,SAAQ,wBACP,MAAM,kBAAkB,CAAC,MAAM,eAAe,IAAI,CAAC;AAAA,MACtD,oBACEA,SAAQ,sBAAsB,MAAM;AAAA,MACtC,kBACEA,SAAQ,oBACR,MAAM,mBACN,MAAM,oBACN;AAAA,IACJ,CAAC;AAAA,EACH;AAEA,QAAM,8BAA8B,CAAC,SAAS,MAAM,sBAAsB,CAAC,MAAM;AAC/E,UAAM,OAAO,wBAAwB;AAAA,MACnC;AAAA,MACA;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,IAAI;AAAA,MACR,eAAe;AAAA,MACf;AAAA,MACA,YAAY;AAAA,MACZ,WAAW;AAAA,MACX,kBAAkB,KAAK;AAAA,MACvB,iBAAiB,KAAK;AAAA,MACtB,qBAAqB,KAAK;AAAA,MAC1B,0BAA0B,KAAK;AAAA,MAC/B,qBAAqB,KAAK,aAAa;AAAA,IACzC,CAAC;AAED,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO,QAAQ,CAAC,WAAW,MAAM;AACpD,UAAM,iBAAiB,CAAC;AACxB,eAAW,QAAQ,OAAO;AACxB,wBAAkB,IAAI;AACtB,qBAAe,IAAI,IAAI,MAAM,kBAAkB,MAAM;AAAA,QACnD,WAAW;AAAA,MACb,CAAC;AAAA,IACH;AACA,UAAM,IAAI,EAAE,eAAe,CAAC;AAC5B,WAAO;AAAA,EACT;AAEA,QAAM,eAAe,OAAO,OAAO,aAAa,cAAc,CAAC,MAAM;AACnE,sBAAkB,IAAI;AAEtB,UAAM,WAAW,MAAM,YAAY,EAAE;AACrC,QAAI,UAAU;AACZ,aAAO;AAAA,IACT;AAEA,UAAM,IAAI,EAAE,YAAY,MAAM,WAAW,KAAK,CAAC;AAE/C,QAAI;AACF,YAAM,UAAU,MAAM,iBAAiB;AAAA,QACrC;AAAA,QACA;AAAA,QACA;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,8BAAwB,OAAO;AAC/B,kCAA4B,SAAS,IAAI;AAEzC,UAAI,OAAO,mBAAmB,YAAY;AACxC,uBAAe,SAAS,IAAI;AAAA,MAC9B;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,kBAAkB;AAC7E,YAAM,IAAI;AAAA,QACR,YAAY;AAAA,QACZ,WAAW;AAAA,MACb,CAAC;AACD,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,UAAU,OAAO,cAAc,CAAC,MAAM;AAC1C,WAAO,aAAa,gBAAgB,WAAW;AAAA,EACjD;AAEA,QAAM,qBAAqB,OAAO,cAAc;AAC9C,UAAM,QAAQ,MAAM,YAAY;AAChC,UAAM,gBAAgB,MAAM;AAC5B,QAAI,CAAC,eAAe;AAClB,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,YAAM,mBAAmB,MAAM;AAAA,QAC7B;AAAA,QACA;AAAA,MACF;AACA,kCAA4B,eAAe,MAAM,QAAQ,aAAa;AAAA,QACpE;AAAA,MACF,CAAC;AACD,aAAO;AAAA,IACT,SAAS,OAAO;AACd,YAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,SAAS,kBAAkB;AAC7E,YAAM,IAAI,EAAE,WAAW,QAAQ,CAAC;AAChC,YAAM;AAAA,IACR;AAAA,EACF;AAEA,QAAM,cAAc,YAAY;AAC9B,UAAM,EAAE,cAAc,IAAI,MAAM,YAAY;AAC5C,QAAI,CAAC,eAAe;AAClB,aAAO;AAAA,IACT;AAEA,QAAI,OAAO,cAAc,QAAQ,YAAY;AAC3C,YAAM,cAAc,IAAI;AAAA,IAC1B;AAGA,QAAI,MAAM,YAAY,EAAE,eAAe;AACrC,yBAAmB;AAAA,IACrB;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,YAAY;AAC1B,UAAM,YAAY;AAClB,4BAAwB;AACxB,UAAM,MAAM;AAAA,EACd;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAEO,IAAM,uBAAuB;","names":["options"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plasius/gpu-xr",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "Framework-agnostic WebXR session management for Plasius GPU rendering projects.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -80,5 +80,8 @@
80
80
  ],
81
81
  "overrides": {
82
82
  "minimatch": "^10.2.1"
83
+ },
84
+ "engines": {
85
+ "node": ">=24"
83
86
  }
84
87
  }
package/src/index.d.ts CHANGED
@@ -13,6 +13,11 @@ export interface XrStoreState {
13
13
  isEntering: boolean;
14
14
  lastError: string | null;
15
15
  supportedModes: Partial<Record<XrSessionMode, boolean>>;
16
+ currentFrameRate: number | null;
17
+ targetFrameRate: number | null;
18
+ supportedFrameRates: readonly number[];
19
+ canUpdateTargetFrameRate: boolean;
20
+ workerBudgetProfile: XrWorkerBudgetProfile | null;
16
21
  }
17
22
 
18
23
  export interface XrStore {
@@ -41,6 +46,39 @@ export interface XrManagerOptions {
41
46
  onSessionEnd?: () => void;
42
47
  }
43
48
 
49
+ export type XrWorkerBudgetProfile = "realtime" | "xr";
50
+
51
+ export interface XrFrameRateCapabilitiesOptions {
52
+ mode?: XrSessionMode;
53
+ fallbackFrameRates?: readonly number[];
54
+ defaultFrameRate?: number;
55
+ }
56
+
57
+ export interface XrFrameRateCapabilities {
58
+ mode: XrSessionMode;
59
+ currentFrameRate: number | null;
60
+ supportedFrameRates: readonly number[];
61
+ refreshRateHz: number;
62
+ canUpdateTargetFrameRate: boolean;
63
+ }
64
+
65
+ export interface XrPerformanceHintOptions extends XrFrameRateCapabilitiesOptions {
66
+ session?: XRSession | null;
67
+ preferredFrameRates?: readonly number[];
68
+ }
69
+
70
+ export interface XrPerformanceHint extends XrFrameRateCapabilities {
71
+ preferredFrameRates: readonly number[];
72
+ targetFrameRate: number;
73
+ targetFrameTimeMs: number;
74
+ workerBudget: Readonly<{
75
+ queueClass: typeof xrWorkerQueueClass;
76
+ schedulerMode: typeof xrWorkerSchedulerMode;
77
+ profile: XrWorkerBudgetProfile;
78
+ }>;
79
+ rationale: readonly string[];
80
+ }
81
+
44
82
  export interface XrManager {
45
83
  store: XrStore;
46
84
  getState(): XrStoreState;
@@ -48,14 +86,22 @@ export interface XrManager {
48
86
  probeSupport(
49
87
  modes?: XrSessionMode[]
50
88
  ): Promise<Partial<Record<XrSessionMode, boolean>>>;
89
+ getFrameRateCapabilities(
90
+ options?: XrFrameRateCapabilitiesOptions & { session?: XRSession | null }
91
+ ): XrFrameRateCapabilities;
92
+ getPerformanceHint(options?: XrPerformanceHintOptions): XrPerformanceHint;
51
93
  enterSession(mode?: XrSessionMode, sessionInit?: XRSessionInit): Promise<XRSession>;
52
94
  enterVr(sessionInit?: XRSessionInit): Promise<XRSession>;
95
+ setTargetFrameRate(frameRate: number): Promise<number>;
53
96
  exitSession(): Promise<boolean>;
54
97
  dispose(): Promise<void>;
55
98
  }
56
99
 
57
100
  export const xrSessionModes: readonly XrSessionMode[];
58
101
  export const xrReferenceSpaceTypes: readonly XrReferenceSpaceType[];
102
+ export const xrWorkerQueueClass: "render";
103
+ export const xrWorkerSchedulerMode: "dag";
104
+ export const defaultXrWorkerBudgetProfile: "xr";
59
105
  export const defaultVrSessionInit: Readonly<XRSessionInit>;
60
106
 
61
107
  export function mergeXrSessionInit(
@@ -70,6 +116,20 @@ export function isXrModeSupported(
70
116
 
71
117
  export function requestXrSession(options?: RequestXrSessionOptions): Promise<XRSession>;
72
118
 
119
+ export function readXrFrameRateCapabilities(
120
+ session: XRSession | null | undefined,
121
+ options?: XrFrameRateCapabilitiesOptions
122
+ ): XrFrameRateCapabilities;
123
+
124
+ export function createXrPerformanceHint(
125
+ options?: XrPerformanceHintOptions
126
+ ): XrPerformanceHint;
127
+
128
+ export function updateXrTargetFrameRate(
129
+ session: XRSession,
130
+ frameRate: number
131
+ ): Promise<number>;
132
+
73
133
  export function createXrStore(initialState?: Partial<XrStoreState>): XrStore;
74
134
 
75
135
  export function createXrManager(options?: XrManagerOptions): XrManager;