@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/CHANGELOG.md +40 -0
- package/README.md +28 -0
- package/dist/index.cjs +225 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -8
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/src/index.d.ts +60 -0
- package/src/index.js +274 -7
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
|
-
|
|
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":"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","names":["options"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plasius/gpu-xr",
|
|
3
|
-
"version": "0.1.
|
|
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;
|