webXRSessionManager.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. import { Logger } from "../../Misc/logger";
  2. import { Observable } from "../../Misc/observable";
  3. import { Nullable } from "../../types";
  4. import { IDisposable, Scene } from "../../scene";
  5. import { InternalTexture, InternalTextureSource } from "../../Materials/Textures/internalTexture";
  6. import { RenderTargetTexture } from "../../Materials/Textures/renderTargetTexture";
  7. import { WebXRRenderTarget } from './webXRTypes';
  8. import { WebXRManagedOutputCanvas } from './webXRManagedOutputCanvas';
  9. import { WebXRState, Tools } from '../../Legacy/legacy';
  10. interface IRenderTargetProvider {
  11. getRenderTargetForEye(eye: XREye): RenderTargetTexture;
  12. }
  13. class RenderTargetProvider implements IRenderTargetProvider {
  14. private _texture: RenderTargetTexture;
  15. public constructor(texture: RenderTargetTexture) {
  16. this._texture = texture;
  17. }
  18. public getRenderTargetForEye(eye: XREye): RenderTargetTexture {
  19. return this._texture;
  20. }
  21. }
  22. /**
  23. * Manages an XRSession to work with Babylon's engine
  24. * @see https://doc.babylonjs.com/how_to/webxr
  25. */
  26. export class WebXRSessionManager implements IDisposable {
  27. /**
  28. * Fires every time a new xrFrame arrives which can be used to update the camera
  29. */
  30. public onXRFrameObservable: Observable<any> = new Observable<any>();
  31. /**
  32. * Fires when the xr session is ended either by the device or manually done
  33. */
  34. public onXRSessionEnded: Observable<any> = new Observable<any>();
  35. /**
  36. * Underlying xr session
  37. */
  38. public session: XRSession;
  39. /**
  40. * Type of reference space used when creating the session
  41. */
  42. public referenceSpace: XRReferenceSpace;
  43. /**
  44. * Current XR frame
  45. */
  46. public currentFrame: Nullable<XRFrame>;
  47. private _xrNavigator: any;
  48. private baseLayer: Nullable<XRWebGLLayer> = null;
  49. private _rttProvider: Nullable<IRenderTargetProvider>;
  50. private _sessionEnded: boolean = false;
  51. /**
  52. * Constructs a WebXRSessionManager, this must be initialized within a user action before usage
  53. * @param scene The scene which the session should be created for
  54. */
  55. constructor(private scene: Scene) {
  56. }
  57. /**
  58. * Initializes the manager
  59. * After initialization enterXR can be called to start an XR session
  60. * @returns Promise which resolves after it is initialized
  61. */
  62. public initializeAsync(): Promise<void> {
  63. Logger.Warn("The WebXR APIs are still under development and are subject to change in the future.");
  64. // Check if the browser supports webXR
  65. this._xrNavigator = navigator;
  66. if (!this._xrNavigator.xr) {
  67. return Promise.reject("webXR not supported by this browser");
  68. }
  69. return Promise.resolve();
  70. }
  71. /**
  72. * Initializes an xr session
  73. * @param xrSessionMode mode to initialize
  74. * @returns a promise which will resolve once the session has been initialized
  75. */
  76. public initializeSessionAsync(xrSessionMode: XRSessionMode) {
  77. return this._xrNavigator.xr.requestSession(xrSessionMode).then((session: XRSession) => {
  78. this.session = session;
  79. this._sessionEnded = false;
  80. // handle when the session is ended (By calling session.end or device ends its own session eg. pressing home button on phone)
  81. this.session.addEventListener("end", () => {
  82. this._sessionEnded = true;
  83. // Remove render target texture and notify frame obervers
  84. this._rttProvider = null;
  85. // Restore frame buffer to avoid clear on xr framebuffer after session end
  86. this.scene.getEngine().restoreDefaultFramebuffer();
  87. // Need to restart render loop as after the session is ended the last request for new frame will never call callback
  88. this.scene.getEngine().customAnimationFrameRequester = null;
  89. this.onXRSessionEnded.notifyObservers(null);
  90. this.scene.getEngine()._renderLoop();
  91. }, { once: true });
  92. });
  93. }
  94. /**
  95. * Sets the reference space on the xr session
  96. * @param referenceSpace space to set
  97. * @returns a promise that will resolve once the reference space has been set
  98. */
  99. public setReferenceSpaceAsync(referenceSpace: XRReferenceSpaceType) {
  100. return this.session.requestReferenceSpace(referenceSpace).then((referenceSpace: XRReferenceSpace) => {
  101. this.referenceSpace = referenceSpace;
  102. }, (rejectionReason) => {
  103. Tools.Error("XR.requestReferenceSpace failed for the following reason: ");
  104. Tools.Error(rejectionReason);
  105. Tools.Log("Defaulting to universally-supported \"viewer\" reference space type.");
  106. return this.session.requestReferenceSpace("viewer").then((referenceSpace: XRReferenceSpace) => {
  107. this.referenceSpace = referenceSpace;
  108. }, (rejectionReason) => {
  109. Tools.Error(rejectionReason);
  110. throw "XR initialization failed: required \"viewer\" reference space type not supported.";
  111. });
  112. });
  113. }
  114. /**
  115. * Updates the render state of the session
  116. * @param state state to set
  117. * @returns a promise that resolves once the render state has been updated
  118. */
  119. public updateRenderStateAsync(state: XRRenderState) {
  120. if (state.baseLayer) {
  121. this.baseLayer = state.baseLayer;
  122. }
  123. return this.session.updateRenderState(state);
  124. }
  125. /**
  126. * Starts rendering to the xr layer
  127. * @returns a promise that will resolve once rendering has started
  128. */
  129. public startRenderingToXRAsync() {
  130. // Tell the engine's render loop to be driven by the xr session's refresh rate and provide xr pose information
  131. this.scene.getEngine().customAnimationFrameRequester = {
  132. requestAnimationFrame: this.session.requestAnimationFrame.bind(this.session),
  133. renderFunction: (timestamp: number, xrFrame: Nullable<XRFrame>) => {
  134. if (this._sessionEnded) {
  135. return;
  136. }
  137. // Store the XR frame in the manager to be consumed by the XR camera to update pose
  138. this.currentFrame = xrFrame;
  139. this.onXRFrameObservable.notifyObservers(null);
  140. this.scene.getEngine()._renderLoop();
  141. }
  142. };
  143. if (this._xrNavigator.xr.native) {
  144. this._rttProvider = this._xrNavigator.xr.getNativeRenderTargetProvider(this.session, (width: number, height: number) => {
  145. return this.scene.getEngine().createRenderTargetTexture({ width: width, height: height }, false);
  146. });
  147. } else {
  148. // Create render target texture from xr's webgl render target
  149. this._rttProvider = new RenderTargetProvider(WebXRSessionManager._CreateRenderTargetTextureFromSession(this.session, this.scene, this.baseLayer!));
  150. }
  151. // Stop window's animation frame and trigger sessions animation frame
  152. if (window.cancelAnimationFrame) { window.cancelAnimationFrame(this.scene.getEngine()._frameHandler); }
  153. this.scene.getEngine()._renderLoop();
  154. return Promise.resolve();
  155. }
  156. /**
  157. * Gets the correct render target texture to be rendered this frame for this eye
  158. * @param eye the eye for which to get the render target
  159. * @returns the render target for the specified eye
  160. */
  161. public getRenderTargetTextureForEye(eye: XREye) : RenderTargetTexture {
  162. return this._rttProvider!.getRenderTargetForEye(eye);
  163. }
  164. /**
  165. * Stops the xrSession and restores the renderloop
  166. * @returns Promise which resolves after it exits XR
  167. */
  168. public exitXRAsync() {
  169. if (this.session) {
  170. return this.session.end();
  171. }
  172. return Promise.resolve();
  173. }
  174. /**
  175. * Checks if a session would be supported for the creation options specified
  176. * @param sessionMode session mode to check if supported eg. immersive-vr
  177. * @returns true if supported
  178. */
  179. public supportsSessionAsync(sessionMode: XRSessionMode) {
  180. if (!(navigator as any).xr || !(navigator as any).xr.supportsSession) {
  181. return Promise.resolve(false);
  182. } else {
  183. return (navigator as any).xr.supportsSession(sessionMode).then(() => {
  184. return Promise.resolve(true);
  185. }).catch((e: any) => {
  186. Logger.Warn(e);
  187. return Promise.resolve(false);
  188. });
  189. }
  190. }
  191. /**
  192. * Creates a WebXRRenderTarget object for the XR session
  193. * @param onStateChangedObservable optional, mechanism for enabling/disabling XR rendering canvas, used only on Web
  194. * @returns a WebXR render target to which the session can render
  195. */
  196. public getWebXRRenderTarget(onStateChangedObservable?: Observable<WebXRState>) : WebXRRenderTarget {
  197. if (this._xrNavigator.xr.native) {
  198. return this._xrNavigator.xr.getWebXRRenderTarget(this.scene.getEngine());
  199. }
  200. else {
  201. return new WebXRManagedOutputCanvas(this.scene.getEngine(), onStateChangedObservable!, this.scene.getEngine().getRenderingCanvas() as HTMLCanvasElement);
  202. }
  203. }
  204. /**
  205. * @hidden
  206. * Converts the render layer of xrSession to a render target
  207. * @param session session to create render target for
  208. * @param scene scene the new render target should be created for
  209. */
  210. public static _CreateRenderTargetTextureFromSession(session: XRSession, scene: Scene, baseLayer: XRWebGLLayer) {
  211. if (!baseLayer) {
  212. throw "no layer";
  213. }
  214. // Create internal texture
  215. var internalTexture = new InternalTexture(scene.getEngine(), InternalTextureSource.Unknown, true);
  216. internalTexture.width = baseLayer.framebufferWidth;
  217. internalTexture.height = baseLayer.framebufferHeight;
  218. internalTexture._framebuffer = baseLayer.framebuffer;
  219. // Create render target texture from the internal texture
  220. var renderTargetTexture = new RenderTargetTexture("XR renderTargetTexture", { width: internalTexture.width, height: internalTexture.height }, scene, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, true);
  221. renderTargetTexture._texture = internalTexture;
  222. return renderTargetTexture;
  223. }
  224. /**
  225. * Disposes of the session manager
  226. */
  227. public dispose() {
  228. this.onXRFrameObservable.clear();
  229. this.onXRSessionEnded.clear();
  230. }
  231. }