-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathEngineHook.js
166 lines (166 loc) · 8.83 KB
/
EngineHook.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
import { useEffect, useState } from 'react';
import { Platform } from 'react-native';
import { WebXRSessionManager, WebXRExperienceHelper, Color4, Tools } from '@babylonjs/core';
import { ReactNativeEngine } from './ReactNativeEngine';
import { ensureInitialized } from './BabylonModule';
import * as base64 from 'base-64';
// These are errors that are normally thrown by WebXR's requestSession, so we should throw the same errors under similar circumstances so app code can be written the same for browser or native.
// https://developer.mozilla.org/en-US/docs/Web/API/XRSystem/requestSession
// https://developer.mozilla.org/en-US/docs/Web/API/DOMException#Error_names
var DOMError;
(function (DOMError) {
DOMError[DOMError["NotSupportedError"] = 9] = "NotSupportedError";
DOMError[DOMError["InvalidStateError"] = 11] = "InvalidStateError";
DOMError[DOMError["SecurityError"] = 18] = "SecurityError";
})(DOMError || (DOMError = {}));
class DOMException {
error;
constructor(error) {
this.error = error;
}
get code() { return this.error; }
get name() { return DOMError[this.error]; }
}
// Requests the camera permission and throws if the permission could not be granted
async function requestCameraPermissionAsync() {
return;
}
// Override the WebXRSessionManager.initializeSessionAsync to insert a camera permissions request. It would be cleaner to do this directly in the native XR implementation, but there are a couple problems with that:
// 1. React Native does not provide a way to hook into the permissions request result (at least on Android).
// 2. If it is done on the native side, then we need one implementation per platform.
{
const originalInitializeSessionAsync = WebXRSessionManager.prototype.initializeSessionAsync;
WebXRSessionManager.prototype.initializeSessionAsync = async function (...args) {
if (Platform.OS === "windows") {
// Launching into immersive mode on Windows HMDs doesn't require a runtime permission check.
// The Spatial Perception capability should be enabled in the project's Package.appxmanifest.
return originalInitializeSessionAsync.apply(this, args);
}
await requestCameraPermissionAsync();
return originalInitializeSessionAsync.apply(this, args);
};
}
ensureInitialized().then(() => {
// Override the navigator.mediaDevices.getUserMedia to insert a camera permissions request. It would be cleaner to do this directly in the NativeCamera implementation, but there are a couple problems with that:
// 1. React Native does not provide a way to hook into the permissions request result (at least on Android).
// 2. If it is done on the native side, then we need one implementation per platform.
});
if (Platform.OS === "android" || Platform.OS === "ios") {
const originalEnterXRAsync = WebXRExperienceHelper.prototype.enterXRAsync;
WebXRExperienceHelper.prototype.enterXRAsync = async function (...args) {
// TODO: https://github.com/BabylonJS/BabylonNative/issues/649
// Android/iOS require manually clearing the default frame buffer to prevent garbage from being rendered for a few frames during the XR transition
const sessionManager = await originalEnterXRAsync.apply(this, args);
const scene = sessionManager.scene;
const beforeRenderObserver = scene.onBeforeRenderObservable.add(() => {
scene.getEngine().unBindFramebuffer(undefined);
scene.getEngine().clear(scene.clearColor, true, false);
});
sessionManager.onXRSessionEnded.add(() => {
scene.onBeforeRenderObservable.remove(beforeRenderObserver);
});
return sessionManager;
};
}
else if (Platform.OS === "windows") {
const originalEnterXRAsync = WebXRExperienceHelper.prototype.enterXRAsync;
WebXRExperienceHelper.prototype.enterXRAsync = async function (...args) {
// TODO: https://github.com/BabylonJS/BabylonNative/issues/577
// Windows HMDs require different rendering behaviors than default xr rendering for mobile devices
const sessionManager = await originalEnterXRAsync.apply(this, args);
sessionManager.scene.clearColor = new Color4(0, 0, 0, 0);
sessionManager.scene.autoClear = true;
return sessionManager;
};
}
global.atob = base64.decode;
// Polyfill console.time and console.timeEnd if needed (as of React Native 0.64 these are not implemented).
if (!console.time) {
const consoleTimes = new Map();
console.time = (label = "default") => {
consoleTimes.set(label, performance.now());
};
console.timeEnd = (label = "default") => {
const end = performance.now();
const start = consoleTimes.get(label);
if (!!start) {
consoleTimes.delete(label);
console.log(`${label}: ${end - start} ms`);
}
};
}
{
const setPerformanceLogLevel = Object.getOwnPropertyDescriptor(Tools, "PerformanceLogLevel")?.set;
if (!setPerformanceLogLevel) {
console.warn(`NativeTracing was not hooked into Babylon.js performance logging because the Tools.PerformanceLogLevel property does not exist.`);
}
else {
// Keep a map of trace region opaque pointers since Tools.EndPerformanceCounter just takes a counter name as an argument.
const traceRegions = new Map();
let currentLevel = Tools.PerformanceNoneLogLevel;
Object.defineProperty(Tools, "PerformanceLogLevel", {
set: (level) => {
// No-op if the log level isn't changing, otherwise we can end up with multiple wrapper layers repeating the same work.
if (level !== currentLevel) {
currentLevel = level;
// Invoke the original PerformanceLevel setter.
setPerformanceLogLevel(currentLevel);
if (currentLevel === Tools.PerformanceNoneLogLevel) {
_native.disablePerformanceLogging();
}
else {
_native.enablePerformanceLogging();
// When Tools.PerformanceLogLevel is set, it assigns the Tools.StartPerformanceCounter and Tools.EndPerformanceCounter functions, so we need to assign
// these functions again in order to wrap them.
const originalStartPerformanceCounter = Tools.StartPerformanceCounter;
Tools.StartPerformanceCounter = (counterName, condition = true) => {
// Call into native before so the time it takes is not captured in the JS perf counter interval.
if (condition) {
if (traceRegions.has(counterName)) {
console.warn(`Performance counter '${counterName}' already exists.`);
}
else {
traceRegions.set(counterName, _native.startPerformanceCounter(counterName));
}
}
originalStartPerformanceCounter(counterName, condition);
};
const originalEndPerformanceCounter = Tools.EndPerformanceCounter;
Tools.EndPerformanceCounter = (counterName, condition = true) => {
originalEndPerformanceCounter(counterName, condition);
// Call into native after so the time it takes is not captured in the JS perf counter interval.
if (condition) {
const traceRegion = traceRegions.get(counterName);
if (traceRegion) {
_native.endPerformanceCounter(traceRegion);
traceRegions.delete(counterName);
}
else {
console.warn(`Performance counter '${counterName}' does not exist.`);
}
}
};
}
}
},
});
}
}
export function useEngine() {
const [engine, setEngine] = useState();
useEffect(() => {
const abortController = new AbortController();
let engine = undefined;
(async () => {
setEngine(engine = await ReactNativeEngine.tryCreateAsync(abortController.signal) ?? undefined);
})();
return () => {
abortController.abort();
// NOTE: Do not use setEngine with a callback to dispose the engine instance as that callback does not get called during component unmount when compiled in release.
engine?.dispose();
setEngine(undefined);
};
}, []);
return engine;
}
//# sourceMappingURL=EngineHook.js.map